diff --git a/Source/aqnwb/aqnwb/BaseIO.cpp b/Source/aqnwb/aqnwb/BaseIO.cpp new file mode 100644 index 0000000..1cd0f45 --- /dev/null +++ b/Source/aqnwb/aqnwb/BaseIO.cpp @@ -0,0 +1,108 @@ +#include "aqnwb/BaseIO.hpp" + +#include "aqnwb/Utils.hpp" + +using namespace AQNWB; + +// BaseDataType + +BaseDataType::BaseDataType(BaseDataType::Type t, SizeType s) + : type(t) + , typeSize(s) +{ +} + +BaseDataType BaseDataType::STR(SizeType size) +{ + return BaseDataType(T_STR, size); +} + +const BaseDataType BaseDataType::U8 = BaseDataType(T_U8, 1); +const BaseDataType BaseDataType::U16 = BaseDataType(T_U16, 1); +const BaseDataType BaseDataType::U32 = BaseDataType(T_U32, 1); +const BaseDataType BaseDataType::U64 = BaseDataType(T_U64, 1); +const BaseDataType BaseDataType::I8 = BaseDataType(T_I8, 1); +const BaseDataType BaseDataType::I16 = BaseDataType(T_I16, 1); +const BaseDataType BaseDataType::I32 = BaseDataType(T_I32, 1); +const BaseDataType BaseDataType::I64 = BaseDataType(T_I64, 1); +const BaseDataType BaseDataType::F32 = BaseDataType(T_F32, 1); +const BaseDataType BaseDataType::F64 = BaseDataType(T_F64, 1); +const BaseDataType BaseDataType::DSTR = BaseDataType(T_STR, DEFAULT_STR_SIZE); + +// BaseIO + +BaseIO::BaseIO() + : readyToOpen(true) + , opened(false) +{ +} + +BaseIO::~BaseIO() {} + +bool BaseIO::isOpen() const +{ + return opened; +} + +bool BaseIO::isReadyToOpen() const +{ + return readyToOpen; +} + +bool BaseIO::canModifyObjects() +{ + return true; +} + +Status BaseIO::createCommonNWBAttributes(const std::string& path, + const std::string& objectNamespace, + const std::string& neurodataType, + const std::string& description) +{ + createAttribute(objectNamespace, path, "namespace"); + createAttribute(generateUuid(), path, "object_id"); + if (neurodataType != "") + createAttribute(neurodataType, path, "neurodata_type"); + if (description != "") + createAttribute(description, path, "description"); + return Status::Success; +} + +Status BaseIO::createDataAttributes(const std::string& path, + const float& conversion, + const float& resolution, + const std::string& unit) +{ + createAttribute(BaseDataType::F32, &conversion, path + "/data", "conversion"); + createAttribute(BaseDataType::F32, &resolution, path + "/data", "resolution"); + createAttribute(unit, path + "/data", "unit"); + + return Status::Success; +} + +Status BaseIO::createTimestampsAttributes(const std::string& path) +{ + int interval = 1; + createAttribute(BaseDataType::I32, + static_cast(&interval), + path + "/timestamps", + "interval"); + createAttribute("seconds", path + "/timestamps", "unit"); + + return Status::Success; +} + +// BaseRecordingData + +BaseRecordingData::BaseRecordingData() {} + +BaseRecordingData::~BaseRecordingData() {} + +// Overload that uses the member variable position (works for simple data +// extension) +Status BaseRecordingData::writeDataBlock(const std::vector& dataShape, + const BaseDataType& type, + const void* data) +{ + return writeDataBlock(dataShape, position, type, data); +} diff --git a/libs/macos/include/aqnwb/BaseIO.hpp b/Source/aqnwb/aqnwb/BaseIO.hpp similarity index 100% rename from libs/macos/include/aqnwb/BaseIO.hpp rename to Source/aqnwb/aqnwb/BaseIO.hpp diff --git a/Source/aqnwb/aqnwb/Channel.cpp b/Source/aqnwb/aqnwb/Channel.cpp new file mode 100644 index 0000000..ccc3f72 --- /dev/null +++ b/Source/aqnwb/aqnwb/Channel.cpp @@ -0,0 +1,43 @@ +#include + +#include "aqnwb/Channel.hpp" + +using namespace AQNWB; + +Channel::Channel(const std::string name, + const std::string groupName, + const SizeType localIndex, + const SizeType globalIndex, + const float conversion, + const float samplingRate, + const float bitVolts, + const std::array position, + const std::string comments) + : name(name) + , groupName(groupName) + , localIndex(localIndex) + , globalIndex(globalIndex) + , position(position) + , conversion(conversion) + , samplingRate(samplingRate) + , bitVolts(bitVolts) + , comments(comments) +{ +} + +Channel::~Channel() {} + +float Channel::getConversion() const +{ + return bitVolts / conversion; +} + +float Channel::getSamplingRate() const +{ + return samplingRate; +} + +float Channel::getBitVolts() const +{ + return bitVolts; +} diff --git a/libs/macos/include/aqnwb/Channel.hpp b/Source/aqnwb/aqnwb/Channel.hpp similarity index 100% rename from libs/macos/include/aqnwb/Channel.hpp rename to Source/aqnwb/aqnwb/Channel.hpp diff --git a/libs/macos/include/aqnwb/Types.hpp b/Source/aqnwb/aqnwb/Types.hpp similarity index 100% rename from libs/macos/include/aqnwb/Types.hpp rename to Source/aqnwb/aqnwb/Types.hpp diff --git a/libs/macos/include/aqnwb/Utils.hpp b/Source/aqnwb/aqnwb/Utils.hpp similarity index 100% rename from libs/macos/include/aqnwb/Utils.hpp rename to Source/aqnwb/aqnwb/Utils.hpp diff --git a/Source/aqnwb/aqnwb/aqnwb.hpp b/Source/aqnwb/aqnwb/aqnwb.hpp new file mode 100644 index 0000000..45c083f --- /dev/null +++ b/Source/aqnwb/aqnwb/aqnwb.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include + +#include "aqnwb/nwb/NWBRecording.hpp" +#include "aqnwb/nwb/NWBFile.hpp" diff --git a/Source/aqnwb/aqnwb/aqnwb_export.hpp b/Source/aqnwb/aqnwb/aqnwb_export.hpp new file mode 100644 index 0000000..1be4353 --- /dev/null +++ b/Source/aqnwb/aqnwb/aqnwb_export.hpp @@ -0,0 +1,42 @@ + +#ifndef AQNWB_EXPORT_H +#define AQNWB_EXPORT_H + +#ifdef AQNWB_STATIC_DEFINE +# define AQNWB_EXPORT +# define AQNWB_NO_EXPORT +#else +# ifndef AQNWB_EXPORT +# ifdef aqnwb_aqnwb_EXPORTS + /* We are building this library */ +# define AQNWB_EXPORT +# else + /* We are using this library */ +# define AQNWB_EXPORT +# endif +# endif + +# ifndef AQNWB_NO_EXPORT +# define AQNWB_NO_EXPORT +# endif +#endif + +#ifndef AQNWB_DEPRECATED +# define AQNWB_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef AQNWB_DEPRECATED_EXPORT +# define AQNWB_DEPRECATED_EXPORT AQNWB_EXPORT AQNWB_DEPRECATED +#endif + +#ifndef AQNWB_DEPRECATED_NO_EXPORT +# define AQNWB_DEPRECATED_NO_EXPORT AQNWB_NO_EXPORT AQNWB_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef AQNWB_NO_DEPRECATED +# define AQNWB_NO_DEPRECATED +# endif +#endif + +#endif /* AQNWB_EXPORT_H */ diff --git a/Source/aqnwb/aqnwb/hdf5/HDF5IO.cpp b/Source/aqnwb/aqnwb/hdf5/HDF5IO.cpp new file mode 100644 index 0000000..1228948 --- /dev/null +++ b/Source/aqnwb/aqnwb/hdf5/HDF5IO.cpp @@ -0,0 +1,727 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "aqnwb/Utils.hpp" +#include "aqnwb/hdf5/HDF5IO.hpp" + +using namespace H5; +using namespace AQNWB::HDF5; + +// HDF5IO + +HDF5IO::HDF5IO() {} + +HDF5IO::HDF5IO(const std::string& fileName, const bool disableSWMRMode) + : filename(fileName) + , disableSWMRMode(disableSWMRMode) +{ +} + +HDF5IO::~HDF5IO() +{ + close(); +} + +std::string HDF5IO::getFileName() +{ + return filename; +} + +Status HDF5IO::open() +{ + if (std::filesystem::exists(getFileName())) { + return open(false); + } else { + return open(true); + } +} + +Status HDF5IO::open(bool newfile) +{ + int accFlags = 0; + + if (opened) + return Status::Failure; + + FileAccPropList fapl = FileAccPropList::DEFAULT; + H5Pset_libver_bounds(fapl.getId(), H5F_LIBVER_LATEST, H5F_LIBVER_LATEST); + + if (newfile) + accFlags = H5F_ACC_TRUNC; + else + accFlags = H5F_ACC_RDWR; + + file = std::make_unique( + getFileName(), accFlags, FileCreatPropList::DEFAULT, fapl); + opened = true; + + return Status::Success; +} + +Status HDF5IO::close() +{ + if (this->file != nullptr && opened) { + this->file->close(); + this->file = nullptr; + this->opened = false; + } + + return Status::Success; +} + +Status checkStatus(int status) +{ + if (status < 0) + return Status::Failure; + else + return Status::Success; +} + +Status HDF5IO::flush() +{ + int status = H5Fflush(this->file->getId(), H5F_SCOPE_GLOBAL); + return checkStatus(status); +} + +Status HDF5IO::createAttribute(const BaseDataType& type, + const void* data, + const std::string& path, + const std::string& name, + const SizeType& size) +{ + H5Object* loc; + Group gloc; + DataSet dloc; + Attribute attr; + DataType H5type; + DataType origType; + + if (!opened) + return Status::Failure; + + // open the group or dataset + H5O_type_t objectType = getObjectType(path); + switch (objectType) { + case H5O_TYPE_GROUP: + gloc = file->openGroup(path); + loc = &gloc; + break; + case H5O_TYPE_DATASET: + dloc = file->openDataSet(path); + loc = &dloc; + break; + default: + return Status::Failure; // not a valid dataset or group type + } + + H5type = getH5Type(type); + origType = getNativeType(type); + + if (size > 1) { + hsize_t dims = static_cast(size); + H5type = ArrayType(H5type, 1, &dims); + origType = ArrayType(origType, 1, &dims); + } + + if (loc->attrExists(name)) { + attr = loc->openAttribute(name); + } else { + DataSpace attr_dataspace(H5S_SCALAR); + attr = loc->createAttribute(name, H5type, attr_dataspace); + } + + attr.write(origType, data); + + return Status::Success; +} + +Status HDF5IO::createAttribute(const std::string& data, + const std::string& path, + const std::string& name) +{ + std::vector dataPtrs; + dataPtrs.push_back(data.c_str()); + + return createAttribute(dataPtrs, path, name, data.length()); +} + +Status HDF5IO::createAttribute(const std::vector& data, + const std::string& path, + const std::string& name) +{ + std::vector dataPtrs; + SizeType maxLength = 0; + for (const std::string& str : data) { + SizeType length = str.length(); + maxLength = std::max(maxLength, length); + dataPtrs.push_back(str.c_str()); + } + + return createAttribute(dataPtrs, path, name, maxLength + 1); +} + +Status HDF5IO::createAttribute(const std::vector& data, + const std::string& path, + const std::string& name, + const SizeType& maxSize) +{ + H5Object* loc; + Group gloc; + DataSet dloc; + Attribute attr; + hsize_t dims[1]; + + if (!opened) + return Status::Failure; + + StrType H5type(PredType::C_S1, maxSize); + H5type.setSize(H5T_VARIABLE); + + // open the group or dataset + H5O_type_t objectType = getObjectType(path); + switch (objectType) { + case H5O_TYPE_GROUP: + gloc = file->openGroup(path); + loc = &gloc; + break; + case H5O_TYPE_DATASET: + dloc = file->openDataSet(path); + loc = &dloc; + break; + default: + return Status::Failure; // not a valid dataset or group type + } + + try { + if (loc->attrExists(name)) { + return Status::Failure; // don't allow overwriting because string + // attributes cannot change size easily + } else { + DataSpace attr_dataspace; + SizeType nStrings = data.size(); + if (nStrings > 1) { + dims[0] = nStrings; + attr_dataspace = DataSpace(1, dims); + } else + attr_dataspace = DataSpace(H5S_SCALAR); + attr = loc->createAttribute(name, H5type, attr_dataspace); + } + attr.write(H5type, data.data()); + } catch (GroupIException error) { + error.printErrorStack(); + } catch (AttributeIException error) { + error.printErrorStack(); + } catch (FileIException error) { + error.printErrorStack(); + } catch (DataSetIException error) { + error.printErrorStack(); + } + return Status::Success; +} + +Status HDF5IO::createReferenceAttribute(const std::string& referencePath, + const std::string& path, + const std::string& name) +{ + H5Object* loc; + Group gloc; + DataSet dloc; + Attribute attr; + + if (!opened) + return Status::Failure; + + // open the group or dataset + H5O_type_t objectType = getObjectType(path); + switch (objectType) { + case H5O_TYPE_GROUP: + gloc = file->openGroup(path); + loc = &gloc; + break; + case H5O_TYPE_DATASET: + dloc = file->openDataSet(path); + loc = &dloc; + break; + default: + return Status::Failure; // not a valid dataset or group type + } + + try { + if (loc->attrExists(name)) { + attr = loc->openAttribute(name); + } else { + DataSpace attr_space(H5S_SCALAR); + attr = loc->createAttribute(name, H5::PredType::STD_REF_OBJ, attr_space); + } + + hobj_ref_t* rdata = new hobj_ref_t[sizeof(hobj_ref_t)]; + + file->reference(rdata, referencePath.c_str()); + + attr.write(H5::PredType::STD_REF_OBJ, rdata); + delete[] rdata; + + } catch (GroupIException error) { + error.printErrorStack(); + } catch (AttributeIException error) { + error.printErrorStack(); + } catch (FileIException error) { + error.printErrorStack(); + } catch (DataSetIException error) { + error.printErrorStack(); + } + + return Status::Success; +} + +Status HDF5IO::createGroup(const std::string& path) +{ + if (!opened) + return Status::Failure; + try { + file->createGroup(path); + } catch (FileIException error) { + error.printErrorStack(); + } catch (GroupIException error) { + error.printErrorStack(); + } + return Status::Success; +} + +Status HDF5IO::createGroupIfDoesNotExist(const std::string& path) +{ + if (!opened) + return Status::Failure; + try { + file->childObjType(path); + } catch (FileIException) { + return createGroup(path); + } + return Status::Success; +} + +/** Creates a link to another location in the file */ +Status HDF5IO::createLink(const std::string& path, const std::string& reference) +{ + if (!opened) + return Status::Failure; + + herr_t error = H5Lcreate_soft(reference.c_str(), + file->getLocId(), + path.c_str(), + H5P_DEFAULT, + H5P_DEFAULT); + + return checkStatus(error); +} + +Status HDF5IO::createReferenceDataSet( + const std::string& path, const std::vector& references) +{ + if (!opened) + return Status::Failure; + + const hsize_t size = references.size(); + + hobj_ref_t* rdata = new hobj_ref_t[size * sizeof(hobj_ref_t)]; + + for (SizeType i = 0; i < size; i++) { + file->reference(&rdata[i], references[i].c_str()); + } + + hid_t space = H5Screate_simple(1, &size, NULL); + + hid_t dset = H5Dcreate(file->getLocId(), + path.c_str(), + H5T_STD_REF_OBJ, + space, + H5P_DEFAULT, + H5P_DEFAULT, + H5P_DEFAULT); + + herr_t writeStatus = + H5Dwrite(dset, H5T_STD_REF_OBJ, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata); + + delete[] rdata; + + herr_t dsetStatus = H5Dclose(dset); + herr_t spaceStatus = H5Sclose(space); + + return checkStatus(writeStatus); +} + +Status HDF5IO::createStringDataSet(const std::string& path, + const std::string& value) +{ + if (!opened) + return Status::Failure; + + std::unique_ptr dataset; + DataType H5type = getH5Type(BaseDataType::STR(value.length())); + DataSpace dSpace(H5S_SCALAR); + + dataset = + std::make_unique(file->createDataSet(path, H5type, dSpace)); + dataset->write(value.c_str(), H5type); + + return Status::Success; +} + +Status HDF5IO::createStringDataSet(const std::string& path, + const std::vector& values) +{ + if (!opened) + return Status::Failure; + + std::vector cStrs; + cStrs.reserve(values.size()); + for (const auto& str : values) { + cStrs.push_back(str.c_str()); + } + + std::unique_ptr dataset; + dataset = std::unique_ptr(createArrayDataSet( + BaseDataType::V_STR, SizeArray {values.size()}, SizeArray {1}, path)); + dataset->writeDataBlock( + std::vector(1, 1), BaseDataType::V_STR, cStrs.data()); + + return Status::Success; +} + +Status HDF5IO::startRecording() +{ + if (!opened) + return Status::Failure; + + if (!disableSWMRMode) { + herr_t status = H5Fstart_swmr_write(this->file->getId()); + return checkStatus(status); + } + return Status::Success; +} + +Status HDF5IO::stopRecording() +{ + // if SWMR mode is disabled, stopping the recording will leave the file open + if (!disableSWMRMode) { + close(); // SWMR mode cannot be disabled so close the file + } else { + this->flush(); + } + return Status::Success; +} + +bool HDF5IO::canModifyObjects() +{ + if (!opened) + return false; + + // Check if we are in SWMR mode + bool inSWMRMode = false; + unsigned int intent; + herr_t status = H5Fget_intent(this->file->getId(), &intent); + bool statusOK = (status >= 0); + if (statusOK) { + inSWMRMode = (intent & (H5F_ACC_SWMR_READ | H5F_ACC_SWMR_WRITE)); + } + + // if the file is opened and we are not in swmr mode then we can modify + // objects + return statusOK && !inSWMRMode; +} + +std::unique_ptr HDF5IO::getDataSet( + const std::string& path) +{ + std::unique_ptr data; + + if (!opened) + return nullptr; + + try { + data = std::make_unique(file->openDataSet(path)); + return std::make_unique(std::move(data)); + } catch (DataSetIException error) { + error.printErrorStack(); + return nullptr; + } catch (FileIException error) { + error.printErrorStack(); + return nullptr; + } catch (DataSpaceIException error) { + error.printErrorStack(); + return nullptr; + } +} + +std::unique_ptr HDF5IO::createArrayDataSet( + const BaseDataType& type, + const SizeArray& size, + const SizeArray& chunking, + const std::string& path) +{ + std::unique_ptr data; + DSetCreatPropList prop; + DataType H5type = getH5Type(type); + + if (!opened) + return nullptr; + + SizeType dimension = size.size(); + if (dimension < 1) // Check for at least one dimension + return nullptr; + + // Ensure chunking is properly allocated and has at least 'dimension' elements + assert(chunking.size() >= dimension); + + // Use vectors to support an arbitrary number of dimensions + std::vector dims(dimension), chunk_dims(dimension), + max_dims(dimension); + + for (SizeType i = 0; i < dimension; i++) { + dims[i] = static_cast(size[i]); + if (chunking[i] > 0) { + chunk_dims[i] = static_cast(chunking[i]); + max_dims[i] = H5S_UNLIMITED; + } else { + chunk_dims[i] = static_cast(size[i]); + max_dims[i] = static_cast(size[i]); + } + } + + DataSpace dSpace(static_cast(dimension), dims.data(), max_dims.data()); + prop.setChunk(static_cast(dimension), chunk_dims.data()); + + data = std::make_unique( + file->createDataSet(path, H5type, dSpace, prop)); + + return std::make_unique(std::move(data)); +} + +H5O_type_t HDF5IO::getObjectType(const std::string& path) +{ +#if H5_VERSION_GE(1, 12, 0) + // get whether path is a dataset or group + H5O_info_t objInfo; // Structure to hold information about the object + H5Oget_info_by_name( + this->file->getId(), path.c_str(), &objInfo, H5O_INFO_BASIC, H5P_DEFAULT); +#else + // get whether path is a dataset or group + H5O_info_t objInfo; // Structure to hold information about the object + H5Oget_info_by_name(this->file->getId(), path.c_str(), &objInfo, H5P_DEFAULT); +#endif + H5O_type_t objectType = objInfo.type; + + return objectType; +} + +H5::DataType HDF5IO::getNativeType(BaseDataType type) +{ + H5::DataType baseType; + + switch (type.type) { + case BaseDataType::Type::T_I8: + baseType = PredType::NATIVE_INT8; + break; + case BaseDataType::Type::T_I16: + baseType = PredType::NATIVE_INT16; + break; + case BaseDataType::Type::T_I32: + baseType = PredType::NATIVE_INT32; + break; + case BaseDataType::Type::T_I64: + baseType = PredType::NATIVE_INT64; + break; + case BaseDataType::Type::T_U8: + baseType = PredType::NATIVE_UINT8; + break; + case BaseDataType::Type::T_U16: + baseType = PredType::NATIVE_UINT16; + break; + case BaseDataType::Type::T_U32: + baseType = PredType::NATIVE_UINT32; + break; + case BaseDataType::Type::T_U64: + baseType = PredType::NATIVE_UINT64; + break; + case BaseDataType::Type::T_F32: + baseType = PredType::NATIVE_FLOAT; + break; + case BaseDataType::Type::T_F64: + baseType = PredType::NATIVE_DOUBLE; + break; + case BaseDataType::Type::T_STR: + return StrType(PredType::C_S1, type.typeSize); + break; + case BaseDataType::Type::V_STR: + return StrType(PredType::C_S1, H5T_VARIABLE); + break; + default: + baseType = PredType::NATIVE_INT32; + } + if (type.typeSize > 1) { + hsize_t size = type.typeSize; + return ArrayType(baseType, 1, &size); + } else + return baseType; +} + +H5::DataType HDF5IO::getH5Type(BaseDataType type) +{ + H5::DataType baseType; + + switch (type.type) { + case BaseDataType::Type::T_I8: + baseType = PredType::STD_I8LE; + break; + case BaseDataType::Type::T_I16: + baseType = PredType::STD_I16LE; + break; + case BaseDataType::Type::T_I32: + baseType = PredType::STD_I32LE; + break; + case BaseDataType::Type::T_I64: + baseType = PredType::STD_I64LE; + break; + case BaseDataType::Type::T_U8: + baseType = PredType::STD_U8LE; + break; + case BaseDataType::Type::T_U16: + baseType = PredType::STD_U16LE; + break; + case BaseDataType::Type::T_U32: + baseType = PredType::STD_U32LE; + break; + case BaseDataType::Type::T_U64: + baseType = PredType::STD_U64LE; + break; + case BaseDataType::Type::T_F32: + return PredType::IEEE_F32LE; + break; + case BaseDataType::Type::T_F64: + baseType = PredType::IEEE_F64LE; + break; + case BaseDataType::Type::T_STR: + return StrType(PredType::C_S1, type.typeSize); + break; + case BaseDataType::Type::V_STR: + return StrType(PredType::C_S1, H5T_VARIABLE); + break; + default: + return PredType::STD_I32LE; + } + if (type.typeSize > 1) { + hsize_t size = type.typeSize; + return ArrayType(baseType, 1, &size); + } else + return baseType; +} + +// HDF5RecordingData +HDF5RecordingData::HDF5RecordingData(std::unique_ptr data) +{ + DataSpace dSpace = data->getSpace(); + DSetCreatPropList prop = data->getCreatePlist(); + + int nDimensions = dSpace.getSimpleExtentNdims(); + std::vector dims(nDimensions), chunk(nDimensions); + + nDimensions = dSpace.getSimpleExtentDims( + dims.data()); // TODO -redefine here or use original? + prop.getChunk(static_cast(nDimensions), chunk.data()); + + this->size = std::vector(nDimensions); + for (int i = 0; i < nDimensions; ++i) { + this->size[i] = dims[i]; + } + this->nDimensions = nDimensions; + this->position = std::vector( + nDimensions, 0); // Initialize position with 0 for each dimension + this->dSet = std::make_unique(*data); +} + +// HDF5RecordingData + +HDF5RecordingData::~HDF5RecordingData() +{ + // Safety + dSet->flush(H5F_SCOPE_GLOBAL); +} + +Status HDF5RecordingData::writeDataBlock( + const std::vector& dataShape, + const std::vector& positionOffset, + const BaseDataType& type, + const void* data) +{ + try { + // check dataShape and positionOffset inputs match the dimensions of the + // dataset + if (dataShape.size() != nDimensions || positionOffset.size() != nDimensions) + { + return Status::Failure; + } + + // Ensure that we have enough space to accommodate new data + std::vector dSetDims(nDimensions), offset(nDimensions); + for (int i = 0; i < nDimensions; ++i) { + offset[i] = static_cast(positionOffset[i]); + + if (dataShape[i] + offset[i] > size[i]) // TODO - do I need offset here + dSetDims[i] = dataShape[i] + offset[i]; + else + dSetDims[i] = size[i]; + } + + // Adjust dataset dimensions if necessary + dSet->extend(dSetDims.data()); + + // Set size to new size based on updated dimensionality + DataSpace fSpace = dSet->getSpace(); + fSpace.getSimpleExtentDims(dSetDims.data()); + for (int i = 0; i < nDimensions; ++i) { + size[i] = dSetDims[i]; + } + + // Create memory space with the shape of the data + // DataSpace mSpace(dimension, dSetDim.data()); + std::vector dataDims(nDimensions); + for (int i = 0; i < nDimensions; ++i) { + if (dataShape[i] == 0) { + dataDims[i] = 1; + } else { + dataDims[i] = static_cast(dataShape[i]); + } + } + DataSpace mSpace(static_cast(nDimensions), dataDims.data()); + + // Select hyperslab in the file space + fSpace.selectHyperslab(H5S_SELECT_SET, dataDims.data(), offset.data()); + + // Write the data + DataType nativeType = HDF5IO::getNativeType(type); + dSet->write(data, nativeType, mSpace, fSpace); + + // Update position for simple extension + for (int i = 0; i < dataShape.size(); ++i) { + position[i] += dataShape[i]; + } + } catch (DataSetIException error) { + error.printErrorStack(); + } catch (DataSpaceIException error) { + error.printErrorStack(); + } catch (FileIException error) { + error.printErrorStack(); + } + return Status::Success; +} + +const H5::DataSet* HDF5RecordingData::getDataSet() +{ + return dSet.get(); +}; diff --git a/libs/macos/include/aqnwb/hdf5/HDF5IO.hpp b/Source/aqnwb/aqnwb/hdf5/HDF5IO.hpp similarity index 100% rename from libs/macos/include/aqnwb/hdf5/HDF5IO.hpp rename to Source/aqnwb/aqnwb/hdf5/HDF5IO.hpp diff --git a/Source/aqnwb/aqnwb/nwb/NWBFile.cpp b/Source/aqnwb/aqnwb/nwb/NWBFile.cpp new file mode 100644 index 0000000..c29169c --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/NWBFile.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include + + +#include "aqnwb/BaseIO.hpp" +#include "aqnwb/Channel.hpp" +#include "aqnwb/Utils.hpp" +#include "aqnwb/nwb/device/Device.hpp" +#include "aqnwb/nwb/ecephys/ElectricalSeries.hpp" +#include "aqnwb/nwb/file/ElectrodeGroup.hpp" +#include "aqnwb/nwb/file/ElectrodeTable.hpp" +#include "aqnwb/nwb/NWBFile.hpp" + +using namespace AQNWB::NWB; + +constexpr SizeType CHUNK_XSIZE = 2048; + +// NWBFile + +NWBFile::NWBFile(const std::string& idText, std::shared_ptr io) + : identifierText(idText) + , io(io) +{ +} + +NWBFile::~NWBFile() {} + +Status NWBFile::initialize() +{ + if (std::filesystem::exists(io->getFileName())) { + return io->open(false); + } else { + io->open(true); + return createFileStructure(); + } +} + +Status NWBFile::finalize() +{ + recordingContainers.reset(); + return io->close(); +} + +Status NWBFile::createFileStructure() +{ + if (!io->canModifyObjects()) { + return Status::Failure; + } + + io->createCommonNWBAttributes("/", "core", "NWBFile", ""); + io->createAttribute(NWBVersion, "/", "nwb_version"); + + io->createGroup("/acquisition"); + io->createGroup("/analysis"); + io->createGroup("/processing"); + io->createGroup("/stimulus"); + io->createGroup("/stimulus/presentation"); + io->createGroup("/stimulus/templates"); + io->createGroup("/general"); + io->createGroup("/general/devices"); + io->createGroup("/general/extracellular_ephys"); + + io->createGroup("/specifications"); + io->createReferenceAttribute("/specifications", "/", ".specloc"); + cacheSpecifications("core/", NWBVersion); + cacheSpecifications("hdmf-common/", HDMFVersion); + cacheSpecifications("hdmf-experimental/", HDMFExperimentalVersion); + + std::string time = getCurrentTime(); + std::vector timeVec = {time}; + io->createStringDataSet("/file_create_date", timeVec); + io->createStringDataSet("/session_description", "a recording session"); + io->createStringDataSet("/session_start_time", time); + io->createStringDataSet("/timestamps_reference_time", time); + io->createStringDataSet("/identifier", identifierText); + + return Status::Success; +} + +Status NWBFile::createElectricalSeries( + std::vector recordingArrays, + const BaseDataType& dataType) +{ + if (!io->canModifyObjects()) { + return Status::Failure; + } + + // store all recorded data in the acquisition group + std::string rootPath = "/acquisition/"; + + // Setup electrode table + ElectrodeTable elecTable = ElectrodeTable(io); + elecTable.initialize(); + + // Create continuous datasets + for (const auto& channelVector : recordingArrays) { + // Setup electrodes and devices + std::string groupName = channelVector[0].groupName; + std::string devicePath = "/general/devices/" + groupName; + std::string electrodePath = "/general/extracellular_ephys/" + groupName; + std::string electricalSeriesPath = rootPath + groupName; + + Device device = Device(devicePath, io, "description", "unknown"); + device.initialize(); + + ElectrodeGroup elecGroup = + ElectrodeGroup(electrodePath, io, "description", "unknown", device); + elecGroup.initialize(); + + // Setup electrical series datasets + auto electricalSeries = std::make_unique( + electricalSeriesPath, + io, + dataType, + channelVector, + "Stores continuously sampled voltage data from an " + "extracellular ephys recording", + SizeArray {0, channelVector.size()}, + SizeArray {CHUNK_XSIZE, 0}); + electricalSeries->initialize(); + recordingContainers->addData(std::move(electricalSeries)); + + // Add electrode information to electrode table (does not write to datasets + // yet) + elecTable.addElectrodes(channelVector); + } + + // write electrode information to datasets + elecTable.finalize(); + + return Status::Success; +} + +Status NWBFile::startRecording() +{ + return io->startRecording(); +} + +void NWBFile::stopRecording() +{ + io->stopRecording(); +} + +void NWBFile::cacheSpecifications(const std::string& specPath, + const std::string& versionNumber) +{ + io->createGroup("/specifications/" + specPath); + io->createGroup("/specifications/" + specPath + versionNumber); + + std::filesystem::path currentFile = __FILE__; + std::filesystem::path schemaDir = + currentFile.parent_path().parent_path().parent_path().parent_path().parent_path() / "Resources/spec" + / specPath / versionNumber; + + for (auto const& entry : std::filesystem::directory_iterator {schemaDir}) + if (std::filesystem::is_regular_file(entry) + && entry.path().extension() == ".json") + { + std::string specName = + entry.path().filename().replace_extension("").string(); + if (specName.find("namespace") != std::string::npos) + specName = "namespace"; + + std::ifstream schemaFile(entry.path()); + std::stringstream buffer; + buffer << schemaFile.rdbuf(); + + io->createStringDataSet( + "/specifications/" + specPath + versionNumber + "/" + specName, + buffer.str()); + } +} + +// recording data factory method / +std::unique_ptr NWBFile::createRecordingData( + BaseDataType type, + const SizeArray& size, + const SizeArray& chunking, + const std::string& path) +{ + return std::unique_ptr( + io->createArrayDataSet(type, size, chunking, path)); +} + +TimeSeries* NWBFile::getTimeSeries(const SizeType& timeseriesInd) +{ + if (timeseriesInd >= this->recordingContainers->containers.size()) { + return nullptr; + } else { + return this->recordingContainers->containers[timeseriesInd].get(); + } +} + +// Recording Container + +RecordingContainers::RecordingContainers(const std::string& name) + : name(name) +{ +} + +RecordingContainers::~RecordingContainers() {} + +void RecordingContainers::addData(std::unique_ptr data) +{ + this->containers.push_back(std::move(data)); +} diff --git a/libs/macos/include/aqnwb/nwb/NWBFile.hpp b/Source/aqnwb/aqnwb/nwb/NWBFile.hpp similarity index 99% rename from libs/macos/include/aqnwb/nwb/NWBFile.hpp rename to Source/aqnwb/aqnwb/nwb/NWBFile.hpp index 9d54289..d645f5f 100644 --- a/libs/macos/include/aqnwb/nwb/NWBFile.hpp +++ b/Source/aqnwb/aqnwb/nwb/NWBFile.hpp @@ -4,6 +4,7 @@ #include #include +#include "aqnwb/aqnwb_export.hpp" #include "aqnwb/BaseIO.hpp" #include "aqnwb/Types.hpp" #include "aqnwb/nwb/base/TimeSeries.hpp" diff --git a/Source/aqnwb/aqnwb/nwb/NWBRecording.cpp b/Source/aqnwb/aqnwb/nwb/NWBRecording.cpp new file mode 100644 index 0000000..9f4178d --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/NWBRecording.cpp @@ -0,0 +1,67 @@ +#include "aqnwb/Channel.hpp" +#include "aqnwb/nwb/NWBRecording.hpp" +#include "aqnwb/Utils.hpp" +#include "aqnwb/hdf5/HDF5IO.hpp" + +using namespace AQNWB::NWB; + +// NWBRecordingEngine +NWBRecording::NWBRecording() {} + +NWBRecording::~NWBRecording() +{ + if (nwbfile != nullptr) { + nwbfile->finalize(); + } +} + +Status NWBRecording::openFile(const std::string& filename, + std::vector recordingArrays, + const std::string& IOType) +{ + // close any existing files + if (nwbfile != nullptr){ + nwbfile->finalize(); + } + + // initialize nwbfile object and create base structure + nwbfile = std::make_unique(generateUuid(), + createIO(IOType, filename)); + nwbfile->initialize(); + + // create the datasets + nwbfile->createElectricalSeries(recordingArrays); + + // start the new recording + return nwbfile->startRecording(); +} + +void NWBRecording::closeFile() +{ + nwbfile->stopRecording(); + nwbfile->finalize(); +} + +Status NWBRecording::writeTimeseriesData( + const std::string& containerName, + const SizeType& timeseriesInd, + const Channel& channel, + const std::vector& dataShape, + const std::vector& positionOffset, + const void* data, + const void* timestamps) +{ + TimeSeries* ts = nwbfile->getTimeSeries(timeseriesInd); + + if (ts == nullptr) + return Status::Failure; + + // write data and timestamps to datasets + if (channel.localIndex == 0) { + // write with timestamps if it's the first channel + return ts->writeData(dataShape, positionOffset, data, timestamps); + } else { + // write without timestamps if its another channel in the same timeseries + return ts->writeData(dataShape, positionOffset, data); + } +} diff --git a/libs/macos/include/aqnwb/nwb/NWBRecording.hpp b/Source/aqnwb/aqnwb/nwb/NWBRecording.hpp similarity index 85% rename from libs/macos/include/aqnwb/nwb/NWBRecording.hpp rename to Source/aqnwb/aqnwb/nwb/NWBRecording.hpp index 6926863..792899f 100644 --- a/libs/macos/include/aqnwb/nwb/NWBRecording.hpp +++ b/Source/aqnwb/aqnwb/nwb/NWBRecording.hpp @@ -1,5 +1,6 @@ #pragma once +#include "aqnwb/aqnwb_export.hpp" #include "aqnwb/Types.hpp" #include "aqnwb/nwb/NWBFile.hpp" @@ -9,7 +10,7 @@ namespace AQNWB::NWB * @brief The NWBRecording class manages the recording process */ -class NWBRecording +class AQNWB_EXPORT NWBRecording { public: /** @@ -34,17 +35,12 @@ class NWBRecording /** * @brief Opens the file for recording. - * @param rootFolder The root folder where the file will be stored. - * @param baseName The base name of the file (will be appended with - * experiment number). - * @param experimentNumber The experiment number. + * @param filename The name of the file to open. * @param recordingArrays ChannelVector objects indicating the electrodes to * use for ElectricalSeries recordings * @param IOType Type of backend IO to use */ - Status openFile(const std::string& rootFolder, - const std::string& baseName, - int experimentNumber, + Status openFile(const std::string& filename, std::vector recordingArrays, const std::string& IOType = "HDF5"); diff --git a/Source/aqnwb/aqnwb/nwb/base/TimeSeries.cpp b/Source/aqnwb/aqnwb/nwb/base/TimeSeries.cpp new file mode 100644 index 0000000..80128ae --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/base/TimeSeries.cpp @@ -0,0 +1,80 @@ +#include "aqnwb/nwb/base/TimeSeries.hpp" + +using namespace AQNWB::NWB; + +// TimeSeries + +/** Constructor */ +TimeSeries::TimeSeries(const std::string& path, + std::shared_ptr io, + const BaseDataType& dataType, + const std::string& unit, + const std::string& description, + const std::string& comments, + const SizeArray& dsetSize, + const SizeArray& chunkSize, + const float& conversion, + const float& resolution, + const float& offset) + : Container(path, io) + , dataType(dataType) + , unit(unit) + , description(description) + , comments(comments) + , dsetSize(dsetSize) + , chunkSize(chunkSize) + , conversion(conversion) + , resolution(resolution) + , offset(offset) +{ +} + +/** Destructor */ +TimeSeries::~TimeSeries() {} + +void TimeSeries::initialize() +{ + Container::initialize(); + + // setup attributes + io->createCommonNWBAttributes(path, "core", neurodataType, description); + io->createAttribute(comments, path, "comments"); + + // setup datasets + this->data = std::unique_ptr(io->createArrayDataSet( + dataType, dsetSize, chunkSize, getPath() + "/data")); + io->createDataAttributes(getPath(), conversion, resolution, unit); + + SizeArray tsDsetSize = { + dsetSize[0]}; // timestamps match data along first dimension + this->timestamps = std::unique_ptr(io->createArrayDataSet( + this->timestampsType, tsDsetSize, chunkSize, getPath() + "/timestamps")); + io->createTimestampsAttributes(getPath()); +} + +Status TimeSeries::writeData(const std::vector& dataShape, + const std::vector& positionOffset, + const void* data, + const void* timestamps) +{ + Status tsStatus = Status::Success; + if (timestamps != nullptr) { + const std::vector timestampsShape = { + dataShape[0]}; // timestamps should match shape of the first data + // dimension + const std::vector timestampsPositionOffset = {positionOffset[0]}; + tsStatus = this->timestamps->writeDataBlock(timestampsShape, + timestampsPositionOffset, + this->timestampsType, + timestamps); + } + + Status dataStatus = this->data->writeDataBlock( + dataShape, positionOffset, this->dataType, data); + + if ((dataStatus != Status::Success) or (tsStatus != Status::Success)) { + return Status::Failure; + } else { + return Status::Success; + } +} diff --git a/libs/macos/include/aqnwb/nwb/base/TimeSeries.hpp b/Source/aqnwb/aqnwb/nwb/base/TimeSeries.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/base/TimeSeries.hpp rename to Source/aqnwb/aqnwb/nwb/base/TimeSeries.hpp diff --git a/Source/aqnwb/aqnwb/nwb/device/Device.cpp b/Source/aqnwb/aqnwb/nwb/device/Device.cpp new file mode 100644 index 0000000..5102f03 --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/device/Device.cpp @@ -0,0 +1,38 @@ +#include "aqnwb/nwb/device/Device.hpp" + +using namespace AQNWB::NWB; + +// Device +/** Constructor */ +Device::Device(const std::string& path, + std::shared_ptr io, + const std::string& description, + const std::string& manufacturer) + : Container(path, io) + , description(description) + , manufacturer(manufacturer) +{ +} + +/** Destructor */ +Device::~Device() {} + +void Device::initialize() +{ + Container::initialize(); + + io->createCommonNWBAttributes(path, "core", "Device", description); + io->createAttribute(manufacturer, path, "manufacturer"); +} + +// Getter for manufacturer +std::string Device::getManufacturer() const +{ + return manufacturer; +} + +// Getter for description +std::string Device::getDescription() const +{ + return description; +} diff --git a/libs/macos/include/aqnwb/nwb/device/Device.hpp b/Source/aqnwb/aqnwb/nwb/device/Device.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/device/Device.hpp rename to Source/aqnwb/aqnwb/nwb/device/Device.hpp diff --git a/Source/aqnwb/aqnwb/nwb/ecephys/ElectricalSeries.cpp b/Source/aqnwb/aqnwb/nwb/ecephys/ElectricalSeries.cpp new file mode 100644 index 0000000..3f1fb2d --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/ecephys/ElectricalSeries.cpp @@ -0,0 +1,94 @@ +#include "aqnwb/nwb/ecephys/ElectricalSeries.hpp" + +#include "aqnwb/nwb/file/ElectrodeTable.hpp" + +using namespace AQNWB::NWB; + +// ElectricalSeries + +/** Constructor */ +ElectricalSeries::ElectricalSeries(const std::string& path, + std::shared_ptr io, + const BaseDataType& dataType, + const Types::ChannelVector& channelVector, + const std::string& description, + const SizeArray& dsetSize, + const SizeArray& chunkSize, + const float& conversion, + const float& resolution, + const float& offset) + : TimeSeries(path, + io, + dataType, + "volts", // default unit for Electrical Series + description, + channelVector[0].comments, + dsetSize, + chunkSize, + channelVector[0].getConversion(), + resolution, + offset) + , channelVector(channelVector) +{ +} + +/** Destructor */ +ElectricalSeries::~ElectricalSeries() {} + +/** Initialization function*/ +void ElectricalSeries::initialize() +{ + TimeSeries::initialize(); + + // setup variables based on number of channels + std::vector electrodeInds(channelVector.size()); + for (size_t i = 0; i < channelVector.size(); ++i) { + electrodeInds[i] = channelVector[i].globalIndex; + } + samplesRecorded = SizeArray(channelVector.size(), 0); + + // make channel conversion dataset + channelConversion = std::unique_ptr( + io->createArrayDataSet(BaseDataType::F32, + SizeArray {1}, + chunkSize, + getPath() + "/channel_conversion")); + io->createCommonNWBAttributes(getPath() + "/channel_conversion", + "hdmf-common", + "", + "Bit volts values for all channels"); + + // make electrodes dataset + electrodesDataset = std::unique_ptr(io->createArrayDataSet( + BaseDataType::I32, SizeArray {1}, chunkSize, getPath() + "/electrodes")); + electrodesDataset->writeDataBlock( + std::vector(1, channelVector.size()), + BaseDataType::I32, + &electrodeInds[0]); + io->createCommonNWBAttributes( + getPath() + "/electrodes", "hdmf-common", "DynamicTableRegion", ""); + io->createReferenceAttribute( + ElectrodeTable::electrodeTablePath, getPath() + "/electrodes", "table"); +} + +Status ElectricalSeries::writeChannel(SizeType channelInd, + const SizeType& numSamples, + const void* data, + const void* timestamps) +{ + // get offsets and datashape + std::vector dataShape = { + numSamples, 1}; // Note: schema has 1D and 3D but planning to deprecate + std::vector positionOffset = {samplesRecorded[channelInd], + channelInd}; + + // track samples recorded per channel + samplesRecorded[channelInd] += numSamples; + + // write channel data + if (channelInd == 0) { + return writeData(dataShape, positionOffset, data, timestamps); + } else { + return writeData(dataShape, positionOffset, data); + } +} diff --git a/libs/macos/include/aqnwb/nwb/ecephys/ElectricalSeries.hpp b/Source/aqnwb/aqnwb/nwb/ecephys/ElectricalSeries.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/ecephys/ElectricalSeries.hpp rename to Source/aqnwb/aqnwb/nwb/ecephys/ElectricalSeries.hpp diff --git a/Source/aqnwb/aqnwb/nwb/file/ElectrodeGroup.cpp b/Source/aqnwb/aqnwb/nwb/file/ElectrodeGroup.cpp new file mode 100644 index 0000000..ac5e017 --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/file/ElectrodeGroup.cpp @@ -0,0 +1,48 @@ +#include "aqnwb/nwb/file/ElectrodeGroup.hpp" + +using namespace AQNWB::NWB; + +// ElectrodeGroup + +/** Constructor */ +ElectrodeGroup::ElectrodeGroup(const std::string& path, + std::shared_ptr io, + const std::string& description, + const std::string& location, + const Device& device) + : Container(path, io) + , description(description) + , location(location) + , device(device) +{ +} + +/** Destructor */ +ElectrodeGroup::~ElectrodeGroup() {} + +void ElectrodeGroup::initialize() +{ + Container::initialize(); + + io->createCommonNWBAttributes(path, "core", "ElectrodeGroup", description); + io->createAttribute(location, path, "location"); + io->createLink("/" + path + "/device", "/" + device.getPath()); +} + +// Getter for description +std::string ElectrodeGroup::getDescription() const +{ + return description; +} + +// Getter for location +std::string ElectrodeGroup::getLocation() const +{ + return location; +} + +// Getter for device +const Device& ElectrodeGroup::getDevice() const +{ + return device; +} diff --git a/libs/macos/include/aqnwb/nwb/file/ElectrodeGroup.hpp b/Source/aqnwb/aqnwb/nwb/file/ElectrodeGroup.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/file/ElectrodeGroup.hpp rename to Source/aqnwb/aqnwb/nwb/file/ElectrodeGroup.hpp diff --git a/Source/aqnwb/aqnwb/nwb/file/ElectrodeTable.cpp b/Source/aqnwb/aqnwb/nwb/file/ElectrodeTable.cpp new file mode 100644 index 0000000..f68231c --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/file/ElectrodeTable.cpp @@ -0,0 +1,84 @@ +#include "aqnwb/nwb/file/ElectrodeTable.hpp" + +#include "aqnwb/Channel.hpp" + +using namespace AQNWB::NWB; + +// ElectrodeTable + +/** Constructor */ +ElectrodeTable::ElectrodeTable(std::shared_ptr io, + const std::string& description) + : DynamicTable(electrodeTablePath, // use the electrodeTablePath + io, + description) +{ +} + +/** Destructor */ +ElectrodeTable::~ElectrodeTable() {} + +/** Initialization function*/ +void ElectrodeTable::initialize() +{ + // create group + DynamicTable::initialize(); + + electrodeDataset->dataset = + std::unique_ptr(io->createArrayDataSet( + BaseDataType::I32, SizeArray {1}, SizeArray {1}, path + "id")); + groupNamesDataset->dataset = std::unique_ptr( + io->createArrayDataSet(BaseDataType::STR(250), + SizeArray {0}, + SizeArray {1}, + path + "group_name")); + locationsDataset + ->dataset = std::unique_ptr(io->createArrayDataSet( + BaseDataType::STR(250), SizeArray {0}, SizeArray {1}, path + "location")); +} + +void ElectrodeTable::addElectrodes(std::vector channels) +{ + // create datasets + for (const auto& ch : channels) { + groupReferences.push_back(groupPathBase + ch.groupName); + groupNames.push_back(ch.groupName); + electrodeNumbers.push_back(ch.globalIndex); + locationNames.push_back("unknown"); + } +} + +void ElectrodeTable::finalize() +{ + setRowIDs(electrodeDataset, electrodeNumbers); + addColumn("group_name", + "the name of the ElectrodeGroup this electrode is a part of", + groupNamesDataset, + groupNames); + addColumn("location", + "the location of channel within the subject e.g. brain region", + locationsDataset, + locationNames); + addColumn("group", + "a reference to the ElectrodeGroup this electrode is a part of", + groupReferences); +} + +// Getter for colNames +const std::vector& ElectrodeTable::getColNames() +{ + return colNames; +} + +// Setter for colNames +void ElectrodeTable::setColNames(const std::vector& newColNames) +{ + colNames = newColNames; +} + +// Getter for groupPath +std::string ElectrodeTable::getGroupPath() const +{ + return groupReferences[0]; // all channels in ChannelVector should have the + // same groupName +} diff --git a/libs/macos/include/aqnwb/nwb/file/ElectrodeTable.hpp b/Source/aqnwb/aqnwb/nwb/file/ElectrodeTable.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/file/ElectrodeTable.hpp rename to Source/aqnwb/aqnwb/nwb/file/ElectrodeTable.hpp diff --git a/Source/aqnwb/aqnwb/nwb/hdmf/base/Container.cpp b/Source/aqnwb/aqnwb/nwb/hdmf/base/Container.cpp new file mode 100644 index 0000000..8cd2865 --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/hdmf/base/Container.cpp @@ -0,0 +1,27 @@ +#include "aqnwb/nwb/hdmf/base/Container.hpp" + +using namespace AQNWB::NWB; + +// Container + +/** Constructor */ +Container::Container(const std::string& path, std::shared_ptr io) + : path(path) + , io(io) +{ +} + +/** Destructor */ +Container::~Container() {} + +/** Initialize */ +void Container::initialize() +{ + io->createGroup(path); +} + +/** Getter for path */ +std::string Container::getPath() const +{ + return path; +} diff --git a/libs/macos/include/aqnwb/nwb/hdmf/base/Container.hpp b/Source/aqnwb/aqnwb/nwb/hdmf/base/Container.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/hdmf/base/Container.hpp rename to Source/aqnwb/aqnwb/nwb/hdmf/base/Container.hpp diff --git a/libs/macos/include/aqnwb/nwb/hdmf/base/Data.hpp b/Source/aqnwb/aqnwb/nwb/hdmf/base/Data.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/hdmf/base/Data.hpp rename to Source/aqnwb/aqnwb/nwb/hdmf/base/Data.hpp diff --git a/Source/aqnwb/aqnwb/nwb/hdmf/table/DynamicTable.cpp b/Source/aqnwb/aqnwb/nwb/hdmf/table/DynamicTable.cpp new file mode 100644 index 0000000..8c2dec7 --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/hdmf/table/DynamicTable.cpp @@ -0,0 +1,84 @@ +#include "aqnwb/nwb/hdmf/table/DynamicTable.hpp" + +using namespace AQNWB::NWB; + +// DynamicTable + +/** Constructor */ +DynamicTable::DynamicTable(const std::string& path, + std::shared_ptr io, + const std::string& description) + : Container(path, io) + , description(description) +{ +} + +/** Destructor */ +DynamicTable::~DynamicTable() {} + +/** Initialization function*/ +void DynamicTable::initialize() +{ + Container::initialize(); + + io->createCommonNWBAttributes( + path, "hdmf-common", "DynamicTable", getDescription()); + io->createAttribute(getColNames(), path, "colnames"); +} + +/** Add column to table */ +void DynamicTable::addColumn(const std::string& name, + const std::string& colDescription, + std::unique_ptr& vectorData, + const std::vector& values) +{ + if (vectorData->dataset == nullptr) { + std::cerr << "VectorData dataset is not initialized" << std::endl; + } else { + // write in loop because variable length string + for (SizeType i = 0; i < values.size(); i++) + vectorData->dataset->writeDataBlock(std::vector(1, 1), + BaseDataType::STR(values[i].size()), + &values[i]); + io->createCommonNWBAttributes( + path + name, "hdmf-common", "VectorData", colDescription); + } +} + +void DynamicTable::setRowIDs(std::unique_ptr& elementIDs, + const std::vector& values) +{ + if (elementIDs->dataset == nullptr) { + std::cerr << "ElementIdentifiers dataset is not initialized" << std::endl; + } else { + elementIDs->dataset->writeDataBlock( + std::vector(1, values.size()), BaseDataType::I32, &values[0]); + io->createCommonNWBAttributes( + path + "id", "hdmf-common", "ElementIdentifiers"); + } +} + +void DynamicTable::addColumn(const std::string& name, + const std::string& colDescription, + const std::vector& values) +{ + if (values.empty()) { + std::cerr << "Data to add to column is empty" << std::endl; + } else { + io->createReferenceDataSet(path + name, values); + io->createCommonNWBAttributes( + path + name, "hdmf-common", "VectorData", colDescription); + } +} + +// Getter for description +std::string DynamicTable::getDescription() const +{ + return description; +} + +// Getter for colNames +const std::vector& DynamicTable::getColNames() +{ + return colNames; +} diff --git a/libs/macos/include/aqnwb/nwb/hdmf/table/DynamicTable.hpp b/Source/aqnwb/aqnwb/nwb/hdmf/table/DynamicTable.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/hdmf/table/DynamicTable.hpp rename to Source/aqnwb/aqnwb/nwb/hdmf/table/DynamicTable.hpp diff --git a/libs/macos/include/aqnwb/nwb/hdmf/table/ElementIdentifiers.hpp b/Source/aqnwb/aqnwb/nwb/hdmf/table/ElementIdentifiers.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/hdmf/table/ElementIdentifiers.hpp rename to Source/aqnwb/aqnwb/nwb/hdmf/table/ElementIdentifiers.hpp diff --git a/Source/aqnwb/aqnwb/nwb/hdmf/table/VectorData.cpp b/Source/aqnwb/aqnwb/nwb/hdmf/table/VectorData.cpp new file mode 100644 index 0000000..5c71ebb --- /dev/null +++ b/Source/aqnwb/aqnwb/nwb/hdmf/table/VectorData.cpp @@ -0,0 +1,9 @@ +#include "aqnwb/nwb/hdmf/table/VectorData.hpp" + +using namespace AQNWB::NWB; + +// VectorData +std::string VectorData::getDescription() const +{ + return description; +} diff --git a/libs/macos/include/aqnwb/nwb/hdmf/table/VectorData.hpp b/Source/aqnwb/aqnwb/nwb/hdmf/table/VectorData.hpp similarity index 100% rename from libs/macos/include/aqnwb/nwb/hdmf/table/VectorData.hpp rename to Source/aqnwb/aqnwb/nwb/hdmf/table/VectorData.hpp diff --git a/libs/macos/bin/libaqnwb.0.1.0.dylib b/libs/macos/bin/libaqnwb.0.1.0.dylib deleted file mode 100755 index 18b1098..0000000 Binary files a/libs/macos/bin/libaqnwb.0.1.0.dylib and /dev/null differ diff --git a/libs/macos/bin/libaqnwb.0.dylib b/libs/macos/bin/libaqnwb.0.dylib deleted file mode 120000 index 3a2323e..0000000 --- a/libs/macos/bin/libaqnwb.0.dylib +++ /dev/null @@ -1 +0,0 @@ -libaqnwb.0.1.0.dylib \ No newline at end of file diff --git a/libs/macos/include/aqnwb/aqnwb.hpp b/libs/macos/include/aqnwb/aqnwb.hpp deleted file mode 100644 index 585ebcc..0000000 --- a/libs/macos/include/aqnwb/aqnwb.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include "aqnwb/aqnwb_export.hpp" - -/** - * @brief Reports the name of the library - * - * Please see the note above for considerations when creating shared libraries. - */ -class AQNWB_EXPORT exported_class -{ -public: - /** - * @brief Initializes the name field to the name of the project - */ - exported_class(); - - /** - * @brief Returns a non-owning pointer to the string stored in this class - */ - auto name() const -> char const*; - -private: - std::string m_name; -}; \ No newline at end of file diff --git a/libs/macos/lib/libaqnwb.dylib b/libs/macos/lib/libaqnwb.dylib deleted file mode 120000 index 61a5762..0000000 --- a/libs/macos/lib/libaqnwb.dylib +++ /dev/null @@ -1 +0,0 @@ -libaqnwb.0.dylib \ No newline at end of file