Skip to content

Commit

Permalink
Merge branch 'add_read' into add_container_read
Browse files Browse the repository at this point in the history
  • Loading branch information
oruebel authored Sep 19, 2024
2 parents b0a28f9 + 5608c5a commit 8bbecb8
Show file tree
Hide file tree
Showing 17 changed files with 613 additions and 69 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ add_library(
src/nwb/base/TimeSeries.cpp
src/nwb/device/Device.cpp
src/nwb/ecephys/ElectricalSeries.cpp
src/nwb/ecephys/SpikeEventSeries.cpp
src/nwb/file/ElectrodeGroup.cpp
src/nwb/file/ElectrodeTable.cpp
src/nwb/hdmf/base/Container.cpp
Expand Down Expand Up @@ -81,6 +82,7 @@ target_include_directories(
)

target_compile_features(aqnwb_aqnwb PUBLIC cxx_std_17)
target_compile_definitions(aqnwb_aqnwb PUBLIC BOOST_NO_CXX98_FUNCTION_BASE)

# ---- Additional libraries needed ----
find_package(HDF5 REQUIRED COMPONENTS CXX)
Expand Down
9 changes: 9 additions & 0 deletions src/io/BaseIO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ class BaseIO
virtual std::unique_ptr<BaseRecordingData> getDataSet(
const std::string& path) = 0;

/**
* @brief Checks whether a Dataset, Group, or Link already exists at the
* location in the file.
* @param path The location of the object in the file.
* @return Whether the object exists.
*/
virtual bool objectExists(const std::string& path) = 0;

/**
* @brief Convenience function for creating NWB related attributes.
* @param path The location of the object in the file.
Expand Down Expand Up @@ -376,6 +384,7 @@ class BaseIO
* @return The status of the operation.
*/
Status createTimestampsAttributes(const std::string& path);

/**
* @brief Returns true if the file is open.
* @return True if the file is open, false otherwise.
Expand Down
46 changes: 23 additions & 23 deletions src/io/ReadIO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,29 @@ class DataBlock
}
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<StorageObjectType T>
struct is_allowed_storage_object_type : std::false_type
{
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<>
struct is_allowed_storage_object_type<StorageObjectType::Dataset>
: std::true_type
{
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<>
struct is_allowed_storage_object_type<StorageObjectType::Attribute>
: std::true_type
{
};

/**
* @brief Class for wrapping data objects (datasets or attributes) for reading
* data from a file
Expand All @@ -182,29 +205,6 @@ class ReadDataWrapper
{
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<StorageObjectType T>
struct is_allowed_storage_object_type : std::false_type
{
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<>
struct is_allowed_storage_object_type<StorageObjectType::Dataset>
: std::true_type
{
};

/// Helper struct to check if a StorageObjectType is allowed. Used in static
/// assert.
template<>
struct is_allowed_storage_object_type<StorageObjectType::Attribute>
: std::true_type
{
};

/**
* Static assert to enforce the restriction that ReadDataWrapper can only be
* instantiated for OTYPE of StorageObjectType::Dataset and
Expand Down
10 changes: 10 additions & 0 deletions src/io/hdf5/HDF5IO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,16 @@ bool HDF5IO::canModifyObjects()
return statusOK && !inSWMRMode;
}

bool HDF5IO::objectExists(const std::string& path)
{
htri_t exists = H5Lexists(file->getId(), path.c_str(), H5P_DEFAULT);
if (exists > 0) {
return true;
} else {
return false;
}
}

std::unique_ptr<AQNWB::IO::BaseRecordingData> HDF5IO::getDataSet(
const std::string& path)
{
Expand Down
8 changes: 8 additions & 0 deletions src/io/hdf5/HDF5IO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ class HDF5IO : public BaseIO
std::unique_ptr<IO::BaseRecordingData> getDataSet(
const std::string& path) override;

/**
* @brief Checks whether a Dataset, Group, or Link already exists at the
* location in the file.
* @param path The location of the object in the file.
* @return Whether the object exists.
*/
bool objectExists(const std::string& path) override;

/**
* @brief Returns the HDF5 type of object at a given path.
* @param path The location in the file of the object.
Expand Down
142 changes: 120 additions & 22 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
#include "io/BaseIO.hpp"
#include "nwb/device/Device.hpp"
#include "nwb/ecephys/ElectricalSeries.hpp"
#include "nwb/ecephys/SpikeEventSeries.hpp"
#include "nwb/file/ElectrodeGroup.hpp"
#include "nwb/file/ElectrodeTable.hpp"
#include "spec/core.hpp"
#include "spec/hdmf_common.hpp"
#include "spec/hdmf_experimental.hpp"

using namespace AQNWB::NWB;

constexpr SizeType CHUNK_XSIZE = 2048;
constexpr SizeType CHUNK_XSIZE =
2048; // TODO - replace these with io settings input
constexpr SizeType SPIKE_CHUNK_XSIZE =
8; // TODO - replace with io settings input

std::vector<SizeType> NWBFile::emptyContainerIndexes = {};

Expand Down Expand Up @@ -67,11 +70,9 @@ Status NWBFile::createFileStructure(const std::string& identifierText,
if (!io->canModifyObjects()) {
return Status::Failure;
}

io->createCommonNWBAttributes(
this->path, this->getNamespace(), this->getTypeName(), "");
io->createAttribute(AQNWB::SPEC::CORE::version, "/", "nwb_version");

io->createGroup("/acquisition");
io->createGroup("/analysis");
io->createGroup("/processing");
Expand Down Expand Up @@ -104,12 +105,12 @@ Status NWBFile::createFileStructure(const std::string& identifierText,
io->createStringDataSet("/session_start_time", time);
io->createStringDataSet("/timestamps_reference_time", time);
io->createStringDataSet("/identifier", identifierText);

return Status::Success;
}

Status NWBFile::createElectricalSeries(
std::vector<Types::ChannelVector> recordingArrays,
std::vector<std::string> recordingNames,
const IO::BaseDataType& dataType,
RecordingContainers* recordingContainers,
std::vector<SizeType>& containerIndexes)
Expand All @@ -118,26 +119,43 @@ Status NWBFile::createElectricalSeries(
return Status::Failure;
}

// store all recorded data in the acquisition group
std::string rootPath = "/acquisition/";
if (recordingNames.size() != recordingArrays.size()) {
return Status::Failure;
}

// Setup electrode table
ElectrodeTable elecTable = ElectrodeTable(io);
elecTable.initialize();
// Setup electrode table if it was not yet created
bool electrodeTableCreated =
io->objectExists(ElectrodeTable::electrodeTablePath);
if (!electrodeTableCreated) {
elecTable = std::make_unique<ElectrodeTable>(io);
elecTable->initialize();

// Add electrode information to table (does not write to datasets yet)
for (const auto& channelVector : recordingArrays) {
elecTable->addElectrodes(channelVector);
}
}

// Create datasets
for (size_t i = 0; i < recordingArrays.size(); ++i) {
const auto& channelVector = recordingArrays[i];
const std::string& recordingName = recordingNames[i];

// 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;
std::string electricalSeriesPath = acquisitionPath + "/" + recordingName;

Device device = Device(devicePath, io);
device.initialize("description", "unknown");
// Check if device exists for groupName, create device and electrode group
// if not
if (!io->objectExists(devicePath)) {
Device device = Device(devicePath, io);
device.initialize("description", "unknown");

ElectrodeGroup elecGroup = ElectrodeGroup(electrodePath, io);
elecGroup.initialize("description", "unknown", device);
ElectrodeGroup elecGroup = ElectrodeGroup(electrodePath, io);
elecGroup.initialize("description", "unknown", device);
}

// Setup electrical series datasets
auto electricalSeries =
Expand All @@ -151,14 +169,94 @@ Status NWBFile::createElectricalSeries(
SizeArray {CHUNK_XSIZE, 0});
recordingContainers->addContainer(std::move(electricalSeries));
containerIndexes.push_back(recordingContainers->containers.size() - 1);
}

// write electrode information to datasets
// (requires that the ElectrodeGroup has been written)
if (!electrodeTableCreated) {
elecTable->finalize();
}

return Status::Success;
}

// Add electrode information to electrode table (does not write to datasets
// yet)
elecTable.addElectrodes(channelVector);
Status NWBFile::createSpikeEventSeries(
std::vector<Types::ChannelVector> recordingArrays,
std::vector<std::string> recordingNames,
const IO::BaseDataType& dataType,
RecordingContainers* recordingContainers,
std::vector<SizeType>& containerIndexes)
{
if (!io->canModifyObjects()) {
return Status::Failure;
}

if (recordingNames.size() != recordingArrays.size()) {
return Status::Failure;
}

// Setup electrode table if it was not yet created
bool electrodeTableCreated =
io->objectExists(ElectrodeTable::electrodeTablePath);
if (!electrodeTableCreated) {
elecTable = std::make_unique<ElectrodeTable>(io);
elecTable->initialize();

// Add electrode information to table (does not write to datasets yet)
for (const auto& channelVector : recordingArrays) {
elecTable->addElectrodes(channelVector);
}
}

// Create datasets
for (size_t i = 0; i < recordingArrays.size(); ++i) {
const auto& channelVector = recordingArrays[i];
const std::string& recordingName = recordingNames[i];

// 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 spikeEventSeriesPath = acquisitionPath + "/" + recordingName;

// Check if device exists for groupName, create device and electrode group
// if not
if (!io->objectExists(devicePath)) {
Device device = Device(devicePath, io);
device.initialize("description", "unknown");

ElectrodeGroup elecGroup = ElectrodeGroup(electrodePath, io);
elecGroup.initialize("description", "unknown", device);
}

// Setup Spike Event Series datasets
SizeArray dsetSize;
SizeArray chunkSize;
if (channelVector.size() == 1) {
dsetSize = SizeArray {0, 0};
chunkSize = SizeArray {SPIKE_CHUNK_XSIZE, 1};
} else {
dsetSize = SizeArray {0, channelVector.size(), 0};
chunkSize = SizeArray {SPIKE_CHUNK_XSIZE, 1, 1};
}

auto spikeEventSeries =
std::make_unique<SpikeEventSeries>(spikeEventSeriesPath, io);
spikeEventSeries->initialize(
dataType,
channelVector,
"Stores spike waveforms from an extracellular ephys recording",
dsetSize,
chunkSize);
recordingContainers->addContainer(std::move(spikeEventSeries));
containerIndexes.push_back(recordingContainers->containers.size() - 1);
}

// write electrode information to datasets
elecTable.finalize();
// (requires that the ElectrodeGroup has been written)
if (!electrodeTableCreated) {
elecTable->finalize();
}

return Status::Success;
}
Expand All @@ -170,7 +268,7 @@ void NWBFile::cacheSpecifications(
const std::array<std::pair<std::string_view, std::string_view>, N>&
specVariables)
{
io->createGroup("/specifications/" + specPath + "/");
io->createGroup("/specifications/" + specPath);
io->createGroup("/specifications/" + specPath + "/" + versionNumber);

for (const auto& [name, content] : specVariables) {
Expand Down
Loading

0 comments on commit 8bbecb8

Please sign in to comment.