Skip to content

Commit

Permalink
add RecordingContainers to manage getting and storing TimeSeries
Browse files Browse the repository at this point in the history
  • Loading branch information
stephprince committed Jul 23, 2024
1 parent db9dfe2 commit 7f84c9e
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 91 deletions.
58 changes: 41 additions & 17 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ Status NWBFile::startRecording(std::vector<Types::ChannelGroup> recordingArrays,
// store all recorded data in the acquisition group
std::string rootPath = "/acquisition/";

timeseriesData.clear();
timeseriesData.reserve(recordingArrays.size());
// setup containers for different data types (e.g. SpikeEventSeries container
// would go here as well)
std::unique_ptr<RecordingContainer> electricalSeriesContainer =
std::make_unique<RecordingContainer>("ElectricalSeries",
recordingArrays.size());

// Setup electrode table
std::string electrodeTablePath = "general/extracellular_ephys/electrodes/";
Expand Down Expand Up @@ -120,7 +123,7 @@ Status NWBFile::startRecording(std::vector<Types::ChannelGroup> recordingArrays,
SizeArray {0, channelGroup.size()},
SizeArray {CHUNK_XSIZE});
electricalSeries->initialize();
timeseriesData.push_back(std::move(electricalSeries));
electricalSeriesContainer->addData(std::move(electricalSeries));

// Add electrode information to electrode table (does not write to datasets
// yet)
Expand All @@ -130,25 +133,14 @@ Status NWBFile::startRecording(std::vector<Types::ChannelGroup> recordingArrays,
// write electrode information to datasets
elecTable.finalize();

// add all data containers to the recording container manager
recordingContainers.push_back(std::move(electricalSeriesContainer));

return Status::Success;
}

void NWBFile::stopRecording() {}

Status NWBFile::writeTimeseries(SizeType datasetInd,
const std::vector<SizeType>& dataShape,
const std::vector<SizeType>& positionOffset,
const void* data,
const BaseDataType& dataType,
const void* timestamps)
{
if (!timeseriesData[datasetInd])
return Status::Failure;

return timeseriesData[datasetInd]->writeData(
dataShape, positionOffset, data, timestamps);
}

void NWBFile::cacheSpecifications(const std::string& specPath,
const std::string& versionNumber)
{
Expand Down Expand Up @@ -189,3 +181,35 @@ std::unique_ptr<AQNWB::BaseRecordingData> NWBFile::createRecordingData(
return std::unique_ptr<BaseRecordingData>(
io->createDataSet(type, size, chunking, path));
}

TimeSeries* NWBFile::getTimeSeries(const std::string& containerName,
const SizeType& timeseriesInd)
{
for (auto& container : this->recordingContainers) {
if (container->containerName == containerName) {
if (timeseriesInd >= container->data.size()) {
return nullptr;
} else {
return container->data[timeseriesInd].get();
}
}
}
return nullptr;
}

// Recording Container

RecordingContainer::RecordingContainer(const std::string& containerName,
const SizeType& containerSize)
: containerName(containerName)
, containerSize(containerSize)
{
this->data.reserve(containerSize);
};

RecordingContainer::~RecordingContainer() {}

void RecordingContainer::addData(std::unique_ptr<TimeSeries> data)
{
this->data.push_back(std::move(data));
}
81 changes: 51 additions & 30 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace AQNWB::NWB
{

using TimeSeriesData = std::vector<std::unique_ptr<TimeSeries>>;
class RecordingContainer; // declare here because gets used in NWBFile class

/**
* @brief The NWBFile class provides an interface for setting up and managing
Expand Down Expand Up @@ -64,25 +64,6 @@ class NWBFile
*/
void stopRecording();

/**
* @brief Write timeseries to an NWB file.
* @param datasetInd The index of the timeseries dataset.
* @param dataShape The size of the data block.
* @param positionOffset The position of the data block to write to.
* @param dataType The data type of the elements in the data block.
* @param data A pointer to the data block.
* @param timestamps A pointer to the timestamps block. May be null if
* multidimensional TimeSeries and only need to write the timestamps once but
* write data multiple times.
* @return The status of the write operation.
*/
Status writeTimeseries(SizeType datasetInd,
const std::vector<SizeType>& dataShape,
const std::vector<SizeType>& positionOffset,
const void* data,
const BaseDataType& dataType = BaseDataType::I16,
const void* timestamps = nullptr);

/**
* @brief Indicates the NWB schema version.
*/
Expand All @@ -98,6 +79,14 @@ class NWBFile
*/
const std::string HDMFExperimentalVersion = "0.5.0";

/**
* @brief Gets the TimeSeries object from the recording containers
* @param containerName The name of the timeseries group.
* @param timeseriesInd The index of the timeseries dataset within the group.
*/
TimeSeries* getTimeSeries(const std::string& containerName,
const SizeType& timeseriesInd);

protected:
/**
* @brief Creates the default file structure.
Expand Down Expand Up @@ -129,18 +118,50 @@ class NWBFile
void cacheSpecifications(const std::string& specPath,
const std::string& versionNumber);

const std::string identifierText;
std::shared_ptr<BaseIO> io;
std::vector<std::unique_ptr<RecordingContainer>> recordingContainers;
};

/**
* @brief The RecordingContainer class provides an interface for managing
* groups of TimeSeries acquired during a recording.
*/
class RecordingContainer
{
public:
/**
* @brief Creates a new dataset to hold text data (messages).
* @param path The location in the file for the dataset.
* @param name The name of the dataset.
* @param text The text data to be stored in the dataset.
* @brief Constructor for RecordingContainer class.
* @param containerName The name of the group of time series
* @param containerSize The size to preallocate for the group of timeseries.
*/
void createTextDataSet(const std::string& path,
const std::string& name,
const std::string& text);
RecordingContainer(const std::string& containerName,
const SizeType& containerSize);

const std::string identifierText;
std::shared_ptr<BaseIO> io;
TimeSeriesData timeseriesData;
/**
* @brief Deleted copy constructor to prevent construction-copying.
*/
RecordingContainer(const RecordingContainer&) = delete;

/**
* @brief Deleted copy assignment operator to prevent copying.
*/
RecordingContainer& operator=(const RecordingContainer&) = delete;

/**
* @brief Destructor for RecordingContainer class.
*/
~RecordingContainer();

/**
* @brief Adds a TimeSeries object to the container.
* @param data The TimeSeries object to add.
*/
void addData(std::unique_ptr<TimeSeries> data);

std::vector<std::unique_ptr<TimeSeries>> data;
std::string containerName;
SizeType containerSize;
};

} // namespace AQNWB::NWB
27 changes: 15 additions & 12 deletions src/nwb/NWBRecording.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,26 @@ void NWBRecording::closeFile()
nwbfile->finalize();
}

void NWBRecording::writeTimeseriesData(SizeType timeseriesInd,
Channel channel,
const void* data,
const BaseDataType& dataType,
const void* timestamps,
SizeType numSamples,
std::vector<SizeType> positionOffset)
Status NWBRecording::writeTimeseriesData(
const std::string& containerName,
const SizeType& timeseriesInd,
const Channel& channel,
const std::vector<SizeType>& dataShape,
const std::vector<SizeType>& positionOffset,
const void* data,
const void* timestamps)
{
TimeSeries* ts = nwbfile->getTimeSeries(containerName, timeseriesInd);

if (ts == nullptr)
return Status::Failure;

// write data and timestamps to datasets
std::vector<SizeType> dataShape = {numSamples, 1};
if (channel.localIndex == 0) {
// write with timestamps if it's the first channel
nwbfile->writeTimeseries(
timeseriesInd, dataShape, positionOffset, data, dataType, timestamps);
return ts->writeData(dataShape, positionOffset, data, timestamps);
} else {
// write without timestamps if its another channel in the same timeseries
nwbfile->writeTimeseries(
timeseriesInd, dataShape, positionOffset, data, dataType);
return ts->writeData(dataShape, positionOffset, data);
}
}
34 changes: 19 additions & 15 deletions src/nwb/NWBRecording.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,26 @@ class NWBRecording
void closeFile();

/**
* @brief Writes data for a timeseries.
* @param timeseriesInd The index of the timeseries.
* @param channel The channel information.
* @param data The data to write.
* @param dataType The data type of the data.
* @param timestamps The timestamps for the data.
* @param numSamples The number of samples to write.
* @param positionOffset The position offset for the data.
* @brief Write timeseries to an NWB file.
* @param containerName The name of the timeseries group to write to.
* @param timeseriesInd The index of the timeseries dataset within the
* timeseries group.
* @param channel The channel index to use for writing timestamps.
* @param dataShape The size of the data block.
* @param positionOffset The position of the data block to write to.
* @param data A pointer to the data block.
* @param timestamps A pointer to the timestamps block. May be null if
* multidimensional TimeSeries and only need to write the timestamps once but
* write data multiple times.
* @return The status of the write operation.
*/
void writeTimeseriesData(SizeType timeseriesInd,
Channel channel,
const void* data,
const BaseDataType& dataType,
const void* timestamps,
SizeType numSamples,
std::vector<SizeType> positionOffset);
Status writeTimeseriesData(const std::string& containerName,
const SizeType& timeseriesInd,
const Channel& channel,
const std::vector<SizeType>& dataShape,
const std::vector<SizeType>& positionOffset,
const void* data,
const void* timestamps);

private:
/**
Expand Down
20 changes: 8 additions & 12 deletions tests/testNWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "Utils.hpp"
#include "hdf5/HDF5IO.hpp"
#include "nwb/NWBFile.hpp"
#include "nwb/base/TimeSeries.hpp"
#include "testUtils.hpp"

using namespace AQNWB;
Expand Down Expand Up @@ -37,18 +38,13 @@ TEST_CASE("startRecording", "[nwb]")
std::vector<double> mockTimestamps = {0.1, 0.2, 0.3, 0.4, 0.5};
std::vector<SizeType> positionOffset = {0, 0};
std::vector<SizeType> dataShape = {mockData.size(), 0};
nwbfile.writeTimeseries(0,
dataShape,
positionOffset,
mockData.data(),
BaseDataType::F32,
mockTimestamps.data());
nwbfile.writeTimeseries(1,
dataShape,
positionOffset,
mockData.data(),
BaseDataType::F32,
mockTimestamps.data());

NWB::TimeSeries* ts0 = nwbfile.getTimeSeries("ElectricalSeries", 0);
ts0->writeData(
dataShape, positionOffset, mockData.data(), mockTimestamps.data());
NWB::TimeSeries* ts1 = nwbfile.getTimeSeries("ElectricalSeries", 1);
ts1->writeData(
dataShape, positionOffset, mockData.data(), mockTimestamps.data());

nwbfile.finalize();

Expand Down
11 changes: 6 additions & 5 deletions tests/testNWBRecording.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,17 @@ TEST_CASE("writeContinuousData", "[recording]")
// write timseries data
std::vector<SizeType> positionOffset = {samplesRecorded,
channel.localIndex};
std::vector<SizeType> dataShape = {dataBuffer.size(), 1};
std::unique_ptr<int16_t[]> intBuffer = transformToInt16(
dataBuffer.size(), channel.getBitVolts(), dataBuffer.data());

nwbRecording.writeTimeseriesData(i,
nwbRecording.writeTimeseriesData("ElectricalSeries",
i,
channel,
dataShape,
positionOffset,
intBuffer.get(),
BaseDataType::I16,
timestampsBuffer.data(),
dataBuffer.size(),
positionOffset);
timestampsBuffer.data());
}
}
// check if recording is done
Expand Down

0 comments on commit 7f84c9e

Please sign in to comment.