Skip to content

Commit

Permalink
Merge branch 'main' into general_obj_lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
oruebel authored Jan 23, 2025
2 parents 19fecb4 + d76f8b7 commit 1a713e0
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 3 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_library(
src/nwb/ecephys/SpikeEventSeries.cpp
src/nwb/file/ElectrodeGroup.cpp
src/nwb/file/ElectrodeTable.cpp
src/nwb/misc/AnnotationSeries.cpp
src/nwb/hdmf/base/Container.cpp
src/nwb/hdmf/base/Data.cpp
src/nwb/hdmf/table/DynamicTable.cpp
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Codespell](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/codespell.yml/badge.svg)](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/codespell.yml)
[![Lint](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/lint.yml/badge.svg)](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/lint.yml)
[![Docs build](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/doxygen-gh-pages.yml/badge.svg)](https://github.com/NeurodataWithoutBorders/aqnwb/actions/workflows/doxygen-gh-pages.yml)
[![Coverage](https://codecov.io/github/NeurodataWithoutBorders/aqnwb/coverage.svg?branch=main)](https://app.codecov.io/github/NeurodataWithoutBorders/aqnwb?branch=main)

[![Docs](https://img.shields.io/badge/AqNWB-Docs-8A2BE2?style=flat)](https://neurodatawithoutborders.github.io/aqnwb/)
[![Code Stats](https://img.shields.io/badge/AqNWB-Code%20Statistics-8A2BE2?style=flat)](https://nwb-overview.readthedocs.io/en/latest/nwb-project-analytics/docs/source/code_stat_pages/AqNWB_stats.html)
Expand All @@ -30,4 +31,4 @@ See the [AqNWB Documentation](https://neurodatawithoutborders.github.io/aqnwb/)

For more information about the license, contributing guidelines, code of conduct
and other relevant documentation for developers please see the
[Developer Documentation](https://neurodatawithoutborders.github.io/aqnwb/devdocs.html).
[Developer Documentation](https://neurodatawithoutborders.github.io/aqnwb/devdocs.html).
2 changes: 1 addition & 1 deletion cmake/coverage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# customization issues
set(
COVERAGE_TRACE_COMMAND
lcov -c -q
lcov -c --verbose
-o "${PROJECT_BINARY_DIR}/coverage.info"
-d "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/src/*"
Expand Down
3 changes: 2 additions & 1 deletion src/io/hdf5/HDF5RecordingData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ Status HDF5RecordingData::writeDataBlock(
|| type.type == BaseDataType::Type::T_STR)
{
std::cerr << "HDF5RecordingData::writeDataBlock called for string data, "
"use HDF5RecordingData::writeStringDataBlock instead."
"use HDF5RecordingData::writeDataBlock with a string array "
"data input instead of void* data."
<< std::endl;
return Status::Failure;
}
Expand Down
30 changes: 30 additions & 0 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "nwb/ecephys/ElectricalSeries.hpp"
#include "nwb/ecephys/SpikeEventSeries.hpp"
#include "nwb/file/ElectrodeGroup.hpp"
#include "nwb/misc/AnnotationSeries.hpp"
#include "spec/core.hpp"
#include "spec/hdmf_common.hpp"
#include "spec/hdmf_experimental.hpp"
Expand Down Expand Up @@ -299,6 +300,35 @@ Status NWBFile::createSpikeEventSeries(
return Status::Success;
}

Status NWBFile::createAnnotationSeries(std::vector<std::string> recordingNames,
RecordingContainers* recordingContainers,
std::vector<SizeType>& containerIndexes)
{
if (!m_io->canModifyObjects()) {
return Status::Failure;
}

for (size_t i = 0; i < recordingNames.size(); ++i) {
const std::string& recordingName = recordingNames[i];

std::string annotationSeriesPath =
AQNWB::mergePaths(acquisitionPath, recordingName);

// Setup annotation series datasets
auto annotationSeries =
std::make_unique<AnnotationSeries>(annotationSeriesPath, m_io);
annotationSeries->initialize(
"Stores user annotations made during an experiment",
"no comments",
SizeArray {0},
SizeArray {CHUNK_XSIZE});
recordingContainers->addContainer(std::move(annotationSeries));
containerIndexes.push_back(recordingContainers->size() - 1);
}

return Status::Success;
}

template<SizeType N>
void NWBFile::cacheSpecifications(
const std::string& specPath,
Expand Down
14 changes: 14 additions & 0 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ class NWBFile : public Container
RecordingContainers* recordingContainers = nullptr,
std::vector<SizeType>& containerIndexes = emptyContainerIndexes);

/** @brief Create AnnotationSeries objects to record data into.
* Created objects are stored in recordingContainers.
* @param recordingNames vector indicating the names of the AnnotationSeries
* within the acquisition group
* @param recordingContainers The container to store the created TimeSeries.
* @param containerIndexes The indexes of the containers added to
* recordingContainers
* @return Status The status of the object creation operation.
*/
Status createAnnotationSeries(
std::vector<std::string> recordingNames,
RecordingContainers* recordingContainers = nullptr,
std::vector<SizeType>& containerIndexes = emptyContainerIndexes);

protected:
/**
* @brief Creates the default file structure.
Expand Down
17 changes: 17 additions & 0 deletions src/nwb/RecordingContainers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "nwb/ecephys/ElectricalSeries.hpp"
#include "nwb/ecephys/SpikeEventSeries.hpp"
#include "nwb/hdmf/base/Container.hpp"
#include "nwb/misc/AnnotationSeries.hpp"

using namespace AQNWB::NWB;
// Recording Container
Expand Down Expand Up @@ -86,3 +87,19 @@ Status RecordingContainers::writeSpikeEventData(const SizeType& containerInd,
return ses->writeSpike(
numSamples, numChannels, data, timestamps, controlInput);
}

Status RecordingContainers::writeAnnotationSeriesData(
const SizeType& containerInd,
const SizeType& numSamples,
const std::vector<std::string> data,
const void* timestamps,
const void* controlInput)
{
AnnotationSeries* as =
dynamic_cast<AnnotationSeries*>(getContainer(containerInd));

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

return as->writeAnnotation(numSamples, data, timestamps, controlInput);
}
16 changes: 16 additions & 0 deletions src/nwb/RecordingContainers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ class RecordingContainers
const void* timestamps,
const void* controlInput = nullptr);

/**
* @brief Write AnnotationSeries data to a recordingContainer dataset.
* @param containerInd The index of the AnnotationSeries dataset within the
* AnnotationSeries containers.
* @param numSamples Number of samples in the time for the single event.
* @param data A vector of strings of data to write.
* @param timestamps A pointer to the timestamps block
* @param controlInput A pointer to the control block data (optional)
* @return The status of the write operation.
*/
Status writeAnnotationSeriesData(const SizeType& containerInd,
const SizeType& numSamples,
const std::vector<std::string> data,
const void* timestamps,
const void* controlInput = nullptr);

/**
* @brief Get the number of recording containers
*/
Expand Down
71 changes: 71 additions & 0 deletions src/nwb/misc/AnnotationSeries.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

#include "nwb/misc/AnnotationSeries.hpp"

#include "Utils.hpp"

using namespace AQNWB::NWB;

// AnnotationSeries
// Initialize the static registered_ member to trigger registration
REGISTER_SUBCLASS_IMPL(AnnotationSeries)

/** Constructor */
AnnotationSeries::AnnotationSeries(const std::string& path,
std::shared_ptr<IO::BaseIO> io)
: TimeSeries(path, io)
{
}

/** Destructor */
AnnotationSeries::~AnnotationSeries() {}

/** Initialization function*/
void AnnotationSeries::initialize(const std::string& description,
const std::string& comments,
const SizeArray& dsetSize,
const SizeArray& chunkSize)
{
TimeSeries::initialize(
IO::BaseDataType::V_STR, // fixed to string according to schema
"n/a", // unit fixed to "n/a"
description,
comments,
dsetSize,
chunkSize,
1.0f, // conversion fixed to 1.0, since unit is n/a
-1.0f, // resolution fixed to -1.0
0.0f); // offset fixed to 0.0, since unit is n/a
}

Status AnnotationSeries::writeAnnotation(const SizeType& numSamples,
std::vector<std::string> dataInput,
const void* timestampsInput,
const void* controlInput)
{
std::vector<SizeType> dataShape = {numSamples};
std::vector<SizeType> positionOffset = {this->m_samplesRecorded};

// Write timestamps
Status tsStatus = Status::Success;
tsStatus = this->timestamps->writeDataBlock(
dataShape, positionOffset, this->timestampsType, timestampsInput);

// Write the data
Status dataStatus = this->data->writeDataBlock(
dataShape, positionOffset, this->m_dataType, dataInput);

// Write the control data if it exists
if (controlInput != nullptr) {
tsStatus = this->control->writeDataBlock(
dataShape, positionOffset, this->controlType, controlInput);
}

// track samples recorded
m_samplesRecorded += numSamples;

if ((dataStatus != Status::Success) || (tsStatus != Status::Success)) {
return Status::Failure;
} else {
return Status::Success;
}
}
71 changes: 71 additions & 0 deletions src/nwb/misc/AnnotationSeries.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

#pragma once

#include <string>

#include "Utils.hpp"
#include "io/BaseIO.hpp"
#include "io/ReadIO.hpp"
#include "nwb/base/TimeSeries.hpp"

namespace AQNWB::NWB
{
/**
* @brief TimeSeries storing text-based records about the experiment.
*/
class AnnotationSeries : public TimeSeries
{
public:
// Register the AnnotationSeries
REGISTER_SUBCLASS(AnnotationSeries, "core")

/**
* @brief Constructor.
* @param path The location of the AnnotationSeries in the file.
* @param io A shared pointer to the IO object.
*/
AnnotationSeries(const std::string& path, std::shared_ptr<IO::BaseIO> io);

/**
* @brief Destructor
*/
~AnnotationSeries();

/**
* @brief Initializes the AnnotationSeries
* @param description The description of the AnnotationSeries.
* @param dsetSize Initial size of the main dataset. This must be a vector
* with one element specifying the length in time.
* @param chunkSize Chunk size to use.
*/
void initialize(const std::string& description,
const std::string& comments,
const SizeArray& dsetSize,
const SizeArray& chunkSize);

/**
* @brief Writes a channel to an AnnotationSeries dataset.
* @param numSamples The number of samples to write (length in time).
* @param dataInput A vector of strings.
* @param timestampsInput A pointer to the timestamps block.
* @param controlInput A pointer to the control block data (optional)
* @return The status of the write operation.
*/
Status writeAnnotation(const SizeType& numSamples,
const std::vector<std::string> dataInput,
const void* timestampsInput,
const void* controlInput = nullptr);

DEFINE_FIELD(readData,
DatasetField,
std::string,
"data",
Annotations made during an experiment.)

private:
/**
* @brief The number of samples already written per channel.
*/
SizeType m_samplesRecorded = 0;
};
} // namespace AQNWB::NWB
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_executable(aqnwb_test
testElementIdentifiers.cpp
testFile.cpp
testHDF5IO.cpp
testMisc.cpp
testNWBFile.cpp
testRecordingWorkflow.cpp
testRegisteredType.cpp
Expand Down
73 changes: 73 additions & 0 deletions tests/testMisc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <H5Cpp.h>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include "Types.hpp"
#include "Utils.hpp"
#include "io/BaseIO.hpp"
#include "nwb/misc/AnnotationSeries.hpp"
#include "testUtils.hpp"

using namespace AQNWB;

TEST_CASE("AnnotationSeries", "[misc]")
{
// setup recording info
SizeType numSamples = 3;
std::string dataPath = "/annotations";
std::vector<std::string> mockAnnotations = {
"Subject moved",
"Break started",
"Break ended",
};
std::vector<double> mockTimestamps = getMockTimestamps(numSamples, 1);
std::vector<double> mockTimestamps2 = mockTimestamps;
for (double& value : mockTimestamps2) {
value += 5;
}

SECTION("test writing annotations")
{
// setup io object
std::string path = getTestFilePath("AnnotationSeries.h5");
std::shared_ptr<BaseIO> io = createIO("HDF5", path);
io->open();

// setup annotation series
NWB::AnnotationSeries as = NWB::AnnotationSeries(dataPath, io);
as.initialize(
"Test annotations", "Test comments", SizeArray {0}, SizeArray {1});

// write annotations multiple times to test adding to same dataset
Status writeStatus =
as.writeAnnotation(numSamples, mockAnnotations, mockTimestamps.data());
REQUIRE(writeStatus == Status::Success);
Status writeStatus2 =
as.writeAnnotation(numSamples, mockAnnotations, mockTimestamps2.data());
REQUIRE(writeStatus2 == Status::Success);
io->flush();

// Read annotations back from file
std::vector<std::string> expectedAnnotations = mockAnnotations;
expectedAnnotations.insert(expectedAnnotations.end(),
mockAnnotations.begin(),
mockAnnotations.end());
std::vector<std::string> dataOut(expectedAnnotations.size());

auto readDataWrapper = as.readData();
auto readAnnotationsDataTyped = readDataWrapper->values();
REQUIRE(readAnnotationsDataTyped.data == expectedAnnotations);

// Read timestamps
std::vector<double> expectedTimestamps = mockTimestamps;
expectedTimestamps.insert(expectedTimestamps.end(),
mockTimestamps2.begin(),
mockTimestamps2.end());
std::vector<double> timestampsOut(expectedTimestamps.size());

auto readTimestampsWrapper = as.readTimestamps();
auto readTimestampsDataTyped = readTimestampsWrapper->values();
REQUIRE_THAT(readTimestampsDataTyped.data,
Catch::Matchers::Approx(expectedTimestamps));
}
}
Loading

0 comments on commit 1a713e0

Please sign in to comment.