Skip to content

Commit

Permalink
Add example for data read
Browse files Browse the repository at this point in the history
  • Loading branch information
oruebel committed Sep 1, 2024
1 parent 3fc1d7a commit b09a4fa
Showing 1 changed file with 109 additions and 79 deletions.
188 changes: 109 additions & 79 deletions tests/examples/test_ecephys_data_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,119 +7,149 @@
#include "Types.hpp"
#include "Utils.hpp"
#include "hdf5/HDF5IO.hpp"
#include "nwb/NWBFile.hpp"
#include "nwb/device/Device.hpp"
#include "nwb/ecephys/ElectricalSeries.hpp"
#include "nwb/file/ElectrodeGroup.hpp"
#include "nwb/file/ElectrodeTable.hpp"
#include "nwb/hdmf/base/Container.hpp"
#include "testUtils.hpp"

using namespace AQNWB;

TEST_CASE("ElectricalSeriesReadExample", "[ecephys]")
{
// setup recording info
SizeType numSamples = 100;
SizeType numChannels = 2;
SizeType bufferSize = numSamples / 5;
std::vector<float> dataBuffer(bufferSize);
std::vector<double> timestampsBuffer(bufferSize);
std::vector<Types::ChannelVector> mockArrays = getMockChannelArrays();
std::string dataPath = "/esdata";
BaseDataType dataType = BaseDataType::F32;
std::vector<std::vector<float>> mockData =
getMockData2D(numSamples, numChannels);
std::vector<double> mockTimestamps = getMockTimestamps(numSamples, 1);
std::string devicePath = "/device";
std::string electrodePath = "/elecgroup/";

SECTION("ecephys data read example")
{
// setup mock data for writing
SizeType numSamples = 100;
SizeType numChannels = 2;
std::vector<Types::ChannelVector> mockArrays = getMockChannelArrays();
BaseDataType dataType = BaseDataType::F32;
std::vector<std::vector<float>> mockData =
getMockData2D(numSamples, numChannels);
std::vector<double> mockTimestamps = getMockTimestamps(numSamples, 1);
// To veryify that the data was written correctly, we here transpose the

Check failure on line 32 in tests/examples/test_ecephys_data_read.cpp

View workflow job for this annotation

GitHub Actions / Check for spelling errors

veryify ==> verify
// mockData (which is per channel) to the (time x channel) layout used
// in the ElectricalSeries in the NWB file so we can compare
std::vector<std::vector<float>> mockDataTransposed;
mockDataTransposed.resize(numSamples);
for (SizeType s = 0; s < numSamples; s++) {
mockDataTransposed[s].resize(numChannels);
for (SizeType c = 0; c < numChannels; c++) {
mockDataTransposed[s][c] = mockData[c][s];
}
}

// setup io object
std::string path = getTestFilePath("ElectricalSeriesReadExample.h5");
std::shared_ptr<BaseIO> io = createIO("HDF5", path);
io->open();
io->createGroup("/general");
io->createGroup("/general/extracellular_ephys");

// setup electrode table, device, and electrode group
NWB::ElectrodeTable elecTable = NWB::ElectrodeTable(io);
elecTable.initialize();

// setup electrical series
NWB::ElectricalSeries es = NWB::ElectricalSeries(dataPath, io);
es.initialize(dataType,
mockArrays[0],
"no description",
SizeArray {0, mockArrays[0].size()},
SizeArray {1, 1});

// setup the NWBFile
NWB::NWBFile nwbfile(io);
nwbfile.initialize(generateUuid());

// TODO Use RecordingContainers in this example once #84 is merged

// create a new ElectricalSeries
Status resultCreate = nwbfile.createElectricalSeries(mockArrays, dataType);
REQUIRE(resultCreate == Status::Success);

// get the new ElectricalSeries
NWB::ElectricalSeries* electricalSeries =
dynamic_cast<NWB::ElectricalSeries*>(nwbfile.getTimeSeries(0));
REQUIRE(electricalSeries != nullptr);

// start recording
Status resultStart = nwbfile.startRecording();
REQUIRE(resultStart == Status::Success);

// write channel data
for (SizeType ch = 0; ch < numChannels; ++ch) {
es.writeChannel(
electricalSeries->writeChannel(
ch, numSamples, mockData[ch].data(), mockTimestamps.data());
}

io->flush();

// TODO START Move this check to a proper test and expand on it
std::string esdataPath = es.dataPath();
std::string expectedDataPath = dataPath + "/data";
REQUIRE(esdataPath == expectedDataPath);
// Illustrate reading the ElecticalSeries.data back
std::string electricalSeriesDataPath = electricalSeries->dataPath();
std::string electricalSeriesPath = electricalSeries->getPath();
REQUIRE(electricalSeriesDataPath == (electricalSeriesPath + "/data"));

// Get a ReadDataseWrapper for lazy reading of ElectricalSeries.data
auto readDataWrapper = electricalSeries->dataLazy();

auto readDataWrapper = es.dataLazy();
// Read the full ElectricalSeries.data back
DataBlock<float> dataValues = readDataWrapper->values<float>();

// Check that the data we read has the expected size and shape
REQUIRE(dataValues.data.size() == (numSamples * numChannels));
REQUIRE(dataValues.shape[0] == numSamples);
REQUIRE(dataValues.shape[1] == numChannels);
for (SizeType s = 0; s < numSamples; s++) {
std::vector<float> temp;
temp.resize(numChannels);
for (SizeType c = 0; c < numChannels; c++) {
temp[c] = mockData[c][s];
}

// Iterate through all the time steps
for (SizeType t = 0; t < numSamples; t++) {
// Get the data for the single time step t from the DataBlock
std::vector<float> selectedRange(
dataValues.data.begin() + (s * numChannels),
dataValues.data.begin() + ((s + 1) * numChannels));
REQUIRE_THAT(selectedRange, Catch::Matchers::Approx(temp).margin(1));
dataValues.data.begin() + (t * numChannels),
dataValues.data.begin() + ((t + 1) * numChannels));
// Check that the values are correct
REQUIRE_THAT(selectedRange,
Catch::Matchers::Approx(mockDataTransposed[t]).margin(1));
}
// Check the boost multi-array feature
// Use the boost multi-array feature to simply interaction with data
// Create a 2D boost::const_multi_array_ref<float, 2> multidimensional array
auto boostMulitArray = dataValues.as_multi_array<2>();
for (SizeType s = 0; s < numSamples; s++) {
// Get timestamp s (i.e., the values from all channels at time index s)
// Accessing [a, :]
auto row_s = boostMulitArray[s]; // This returns a 1D array representing
// the first row
// Convert the row from boost to a std:vector
std::vector<float> row_s_vector(row_s.begin(), row_s.end());

// Data for comparison
std::vector<float> temp;
temp.resize(numChannels);
for (SizeType c = 0; c < numChannels; c++) {
temp[c] = mockData[c][s];
}
REQUIRE_THAT(row_s_vector, Catch::Matchers::Approx(temp).margin(1));
// Iterate through all the time steps again, but now using the boost array
for (SizeType t = 0; t < numSamples; t++) {
// Access [t, :], i.e., get a 1D array with the data
// from all channels for time step t.
auto row_t = boostMulitArray[t];

// Compare to check that the data is correct.
std::vector<float> row_t_vector(
row_t.begin(), row_t.end()); // convert to std::vector for comparison
REQUIRE_THAT(row_t_vector,
Catch::Matchers::Approx(mockDataTransposed[t]).margin(1));
}

// std::cout<<"Data shape:" << dataValues.shape.size() << std::endl;

//
// REQUIRE_THAT(readDataValues, Catch::Matchers::Approx(data).margin(1));

// TODO Add an attribute getter and test that it works as well
// auto dataLazyWrapper = es.dataLazy();
// auto dataValueGeneric = dataLazyWrapper->valuesGeneric();
// TODO Check why this causes Assertion failed:
// (this->file->attrExists(dataPath)), function readAttribute, file
// HDF5IO.cpp, line 161.
// the attribute does not seem to exists so either the path is wrong or
// something is bad with the write?
// REQUIRE(unitValueGeneric.shape.size() == 0);
// Stop the recording
io->stopRecording();
io->close();

// std::string esUnit = es.unit();
// Open an I/O for reading
std::shared_ptr<BaseIO> readio = createIO("HDF5", path);

// To read from a specific TimeSeries we can just create it directly from
// the read IO Using NWB::Container::create we can create any NWB Container
// class. Alternatively, we can also construct the ElectricalSeries object
// ourselves
auto readElectricalSeries =
NWB::Container::create<NWB::ElectricalSeries>(path, io);

// Now we can read the data in the same way we did during write
auto readElectricalSeriesData = electricalSeries->dataLazy();

// TODO Actually loading the data causes a segfault
// DataBlock<float> readDataValues = readDataWrapper->values<float>();
// auto readBoostMulitArray = readDataValues.as_multi_array<2>();

// Let's read the first 10 timesteps for the first two channels
/*DataBlock<float> dataSlice = readDataWrapper->values<float>(
{0, 0}, // start
{100, 1} // count
);
REQUIRE(dataSlice.shape[0] == 100);
REQUIRE(dataSlice.shape[1] == 1);*/

// TODO Add an attribute getter (e.g., for ElectricalSeries.unit) and test
// that it works as well std::string esUnit = electricalSeries->unit();
// REQUIRE(esUnit == std::string("volts"));
// TODO END Move this code
// REQUIRE(unitValueGeneric.shape.size() == 0);

io->close();
// TODO Check why reading the unit causes the below Assertion in HDF5IO to
// fail: (this->file->attrExists(dataPath)), function readAttribute, file
// HDF5IO.cpp, line 161. i.e., the attribute does not seem to exists so
// either the path is wrong or something is bad with the write?
}
}

0 comments on commit b09a4fa

Please sign in to comment.