Skip to content

Commit

Permalink
Merge pull request #84 from NeurodataWithoutBorders/restructure-class…
Browse files Browse the repository at this point in the history
…es-and-workflow

Restructure classes and workflow
  • Loading branch information
stephprince authored Sep 3, 2024
2 parents e873d95 + 46059f7 commit 2876a1f
Show file tree
Hide file tree
Showing 14 changed files with 442 additions and 287 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ add_library(
src/Channel.cpp
src/hdf5/HDF5IO.cpp
src/nwb/NWBFile.cpp
src/nwb/NWBRecording.cpp
src/nwb/RecordingContainers.cpp
src/nwb/base/TimeSeries.cpp
src/nwb/device/Device.cpp
src/nwb/ecephys/ElectricalSeries.cpp
Expand Down
1 change: 1 addition & 0 deletions docs/pages/1_userdocs.dox
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
* with a particular data acquisition software via AqNWB.
*
* - \subpage user_install_page
* - \subpage workflow
* - \subpage hdf5io
*/
102 changes: 102 additions & 0 deletions docs/pages/userdocs/workflow.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* \page workflow AqNWB Workflow
*
* \tableofcontents
*
* \section recording_workflow Overview of a recording workflow
*
* For users wanting to integrate NWB with a particular data acquisition software, here
* we outline the steps for a single recording from file creation to saving.
*
* 1. Create the I/O object (e.g,. \ref AQNWB::HDF5::HDF5IO "HDF5IO") used for
* writing data to the file on disk.
* 2. Create the \ref AQNWB::NWB::RecordingContainers "RecordingContainers" object
* used for managing \ref AQNWB::NWB::Container "Container" objects for storing recordings.
* 3. Create the \ref AQNWB::NWB::NWBFile "NWBFile" object used for managing and creating NWB
* file contents.
* 4. Create the \ref AQNWB::NWB::Container "Container" objects (e.g.,
* \ref AQNWB::NWB::ElectricalSeries "ElectricalSeries") used for recording and add them
* to the \ref AQNWB::NWB::RecordingContainers "RecordingContainers".
* 5. Start the recording.
* 6. Write data.
* 7. Stop the recording and close the \ref AQNWB::NWB::NWBFile "NWBFile".
*
* Below, we walk through these steps in more detail.
*
*
* \subsection create_io 1. Create the I/O object.
*
* First, create an I/O object (e.g., \ref AQNWB::HDF5::HDF5IO "HDF5IO") used for writing
* data to the file. AqNWB provides the convenience method, \ref AQNWB::createIO "createIO"
* to create this object using one of the supported backends. For more fine-grained
* control of different backend parameters, you can create your own `std::shared_ptr`
* using any of the derived \ref AQNWB::BaseIO "BaseIO" classes.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_io_snippet
*
*
* \subsection create_recording_container 2. Create the RecordingContainer object.
*
* Next, create a \ref AQNWB::NWB::RecordingContainers "RecordingContainers" object to manage the
* different \ref AQNWB::NWB::Container "Container" objects with the datasets that you would
* like to write data to.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_recording_containers_snippet
*
*
* \subsection create_nwbfile 3. Create the NWBFile
*
* Next, constructs the \ref AQNWB::NWB::NWBFile "NWBFile" object, using the I/O object as an input.
* Then, initialize the object to create the basic file structure of the NWBFile.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_nwbfile_snippet
*
*
* \subsection create_datasets 4. Create datasets and add to RecordingContainers.
*
* Next, create the different data types (e.g. \ref AQNWB::NWB::ElectricalSeries "ElectricalSeries"
* or other AQNWB::NWB::TimeSeries "TimeSeries") that you would like to write data into. After
* creation, these objects are added to the \ref AQNWB::NWB::RecordingContainers "RecordingContainers"
* object so that it can mana ge access and data writing during the recording process.
* When adding containers, ownership of the \ref AQNWB::NWB::Container "Container" is transferred to the
* \ref AQNWB::NWB::RecordingContainers "RecordingContainers" object, so that we can access it again via
* its index. New containers will always be appended to the end of the
* \ref AQNWB::NWB::RecordingContainers::containers "containers" object and their index can be tracked
* using the size of the input `recordingArrays`.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_datasets_snippet
*
*
* \subsection start_recording 5. Start the recording.
*
* Then, start the recording process with a call to the ``startRecording`` function of the I/O object.
*
* \note
* When using \ref AQNWB::HDF5::HDF5IO "HDF5IO" for writing to HDF5, calling
* \ref AQNWB::HDF5::HDF5IO::startRecording "startRecording" will by default enable
* \ref hdf5io_swmr "SWMR mode" to ensure file integrity and support concurrent read.
* As a result, no additional datasets or groups can be added to the file once a recording
* has been started unless the file is is closed and reopened.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_start_snippet
*
*
* \subsection write_data 6. Write data.
*
* During the recording process, use the \ref AQNWB::NWB::RecordingContainers "RecordingContainers"
* as an interface to access the various \ref AQNWB::NWB::Container "Container" object and corresponding
* datasets and write blocks of data to the file. Calling `flush()` on the I/O object at any time will
* ensure the data is moved to disk.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_write_snippet
*
*
* \subsection stop_recording 7. Stop the recording and finalize the file.
*
* When the recording process is finished, call `stopRecording` from the I/O object
* to flush any data and close the file.
*
* \snippet tests/examples/testWorkflowExamples.cpp example_workflow_stop_snippet
*
*
*/
4 changes: 2 additions & 2 deletions src/Utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ inline std::string getCurrentTime()
* @brief Factory method to create an IO object.
* @return A pointer to a BaseIO object
*/
inline std::unique_ptr<BaseIO> createIO(const std::string& type,
inline std::shared_ptr<BaseIO> createIO(const std::string& type,
const std::string& filename)
{
if (type == "HDF5") {
return std::make_unique<HDF5::HDF5IO>(filename);
return std::make_shared<HDF5::HDF5IO>(filename);
} else {
throw std::invalid_argument("Invalid IO type");
}
Expand Down
43 changes: 7 additions & 36 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ using namespace AQNWB::NWB;

constexpr SizeType CHUNK_XSIZE = 2048;

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

// NWBFile

NWBFile::NWBFile(const std::string& idText, std::shared_ptr<BaseIO> io)
Expand All @@ -45,7 +47,6 @@ Status NWBFile::initialize()

Status NWBFile::finalize()
{
recordingContainers.reset();
return io->close();
}

Expand Down Expand Up @@ -93,7 +94,9 @@ Status NWBFile::createFileStructure()

Status NWBFile::createElectricalSeries(
std::vector<Types::ChannelVector> recordingArrays,
const BaseDataType& dataType)
const BaseDataType& dataType,
RecordingContainers* recordingContainers,
std::vector<SizeType>& containerIndexes)
{
if (!io->canModifyObjects()) {
return Status::Failure;
Expand Down Expand Up @@ -132,7 +135,8 @@ Status NWBFile::createElectricalSeries(
SizeArray {0, channelVector.size()},
SizeArray {CHUNK_XSIZE, 0});
electricalSeries->initialize();
recordingContainers->addData(std::move(electricalSeries));
recordingContainers->addContainer(std::move(electricalSeries));
containerIndexes.push_back(recordingContainers->containers.size() - 1);

// Add electrode information to electrode table (does not write to datasets
// yet)
Expand All @@ -145,16 +149,6 @@ Status NWBFile::createElectricalSeries(
return Status::Success;
}

Status NWBFile::startRecording()
{
return io->startRecording();
}

void NWBFile::stopRecording()
{
io->stopRecording();
}

template<SizeType N>
void NWBFile::cacheSpecifications(
const std::string& specPath,
Expand Down Expand Up @@ -182,26 +176,3 @@ std::unique_ptr<AQNWB::BaseRecordingData> NWBFile::createRecordingData(
return std::unique_ptr<BaseRecordingData>(
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<TimeSeries> data)
{
this->containers.push_back(std::move(data));
}
74 changes: 9 additions & 65 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "BaseIO.hpp"
#include "Types.hpp"
#include "nwb/RecordingContainers.hpp"
#include "nwb/base/TimeSeries.hpp"

/*!
Expand All @@ -18,8 +19,6 @@
namespace AQNWB::NWB
{

class RecordingContainers; // declare here because gets used in NWBFile class

/**
* @brief The NWBFile class provides an interface for setting up and managing
* the NWB file.
Expand Down Expand Up @@ -69,28 +68,17 @@ class NWBFile
* @param recordingArrays vector of ChannelVector indicating the electrodes to
* record from. A separate ElectricalSeries will be
* created for each ChannelVector.
* @param recordingContainers The container to store the created TimeSeries.
* @param containerIndexes The indexes of the containers added to
* recordingContainers
* @param dataType The data type of the elements in the data block.
* @return Status The status of the object creation operation.
*/
Status createElectricalSeries(
std::vector<Types::ChannelVector> recordingArrays,
const BaseDataType& dataType = BaseDataType::I16);

/**
* @brief Starts the recording.
*/
Status startRecording();

/**
* @brief Stops the recording.
*/
void stopRecording();

/**
* @brief Gets the TimeSeries object from the recordingContainers
* @param timeseriesInd The index of the timeseries dataset within the group.
*/
TimeSeries* getTimeSeries(const SizeType& timeseriesInd);
const BaseDataType& dataType = BaseDataType::I16,
RecordingContainers* recordingContainers = nullptr,
std::vector<SizeType>& containerIndexes = emptyContainerIndexes);

protected:
/**
Expand Down Expand Up @@ -133,53 +121,9 @@ class NWBFile
const std::array<std::pair<std::string_view, std::string_view>, N>&
specVariables);

/**
* @brief Holds the Container (usually TimeSeries) objects that have been
* created in the nwb file for recording.
*/
std::unique_ptr<RecordingContainers> recordingContainers =
std::make_unique<RecordingContainers>("RecordingContainers");

const std::string identifierText;
std::shared_ptr<BaseIO> io;
static std::vector<SizeType> emptyContainerIndexes;
};

/**
* @brief The RecordingContainers class provides an interface for managing
* groups of TimeSeries acquired during a recording.
*/
class RecordingContainers
{
public:
/**
* @brief Constructor for RecordingContainer class.
* @param name The name of the group of time series
*/
RecordingContainers(const std::string& name);

/**
* @brief Deleted copy constructor to prevent construction-copying.
*/
RecordingContainers(const RecordingContainers&) = delete;

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

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

/**
* @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>> containers;
std::string name;
};

} // namespace AQNWB::NWB
} // namespace AQNWB::NWB
69 changes: 0 additions & 69 deletions src/nwb/NWBRecording.cpp

This file was deleted.

Loading

0 comments on commit 2876a1f

Please sign in to comment.