Skip to content

Commit

Permalink
Merge branch 'main' into add_read
Browse files Browse the repository at this point in the history
  • Loading branch information
oruebel committed Sep 9, 2024
2 parents e75c7ad + a10b62e commit 5ab8464
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 61 deletions.
6 changes: 3 additions & 3 deletions src/Channel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class Channel
const SizeType globalIndex,
const float conversion = 1e6f, // uV to V
const float samplingRate = 30000.f, // placeholder
const float bitVolts = 0.000002f, // least significant bit needed to
// convert 16-bit int to volts
// currently a placeholder
const float bitVolts = 0.05f, // least significant bit needed to
// convert 16-bit int to volts
// currently a placeholder
const std::array<float, 3> position = {0.f, 0.f, 0.f},
const std::string comments = "no comments");

Expand Down
45 changes: 39 additions & 6 deletions src/Utils.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#pragma once

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>

#include <boost/date_time.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
Expand Down Expand Up @@ -71,6 +75,40 @@ inline std::shared_ptr<IO::BaseIO> createIO(const std::string& type,
}
}

/**
* @brief Method to convert float values to uint16 values. This method
* was adapted from JUCE AudioDataConverters using a default value of
* destBytesPerSample = 2.
* @param source The source float data to convert
* @param dest The destination for the converted uint16 data
* @param numSamples The number of samples to convert
*/
inline void convertFloatToInt16LE(const float* source,
void* dest,
int numSamples)
{
// TODO - several steps in this function may be unnecessary for our use
// case. Consider simplifying the intermediate cast to char and the
// final cast to uint16_t.
auto maxVal = static_cast<double>(0x7fff);
auto intData = static_cast<char*>(dest);

for (int i = 0; i < numSamples; ++i) {
auto clampedValue = std::clamp(maxVal * source[i], -maxVal, maxVal);
auto intValue =
static_cast<uint16_t>(static_cast<int16_t>(std::round(clampedValue)));
intValue = boost::endian::native_to_little(intValue);
*reinterpret_cast<uint16_t*>(intData) = intValue;
intData += 2; // destBytesPerSample is always 2
}
}

/**
* @brief Method to scale float values and convert to int16 values
* @param numSamples The number of samples to convert
* @param conversion_factor The conversion factor to scale the data
* @param data The data to convert
*/
inline std::unique_ptr<int16_t[]> transformToInt16(SizeType numSamples,
float conversion_factor,
const float* data)
Expand All @@ -86,12 +124,7 @@ inline std::unique_ptr<int16_t[]> transformToInt16(SizeType numSamples,
[multFactor](float value) { return value * multFactor; });

// convert float to int16
std::transform(
scaledData.get(),
scaledData.get() + numSamples,
intData.get(),
[](float value)
{ return static_cast<int16_t>(std::clamp(value, -32768.0f, 32767.0f)); });
convertFloatToInt16LE(scaledData.get(), intData.get(), numSamples);

return intData;
}
Expand Down
25 changes: 16 additions & 9 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,26 @@ NWBFile::NWBFile(std::shared_ptr<IO::BaseIO> io)

NWBFile::~NWBFile() {}

Status NWBFile::initialize(const std::string& identifierText)
Status NWBFile::initialize(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection)
{
if (std::filesystem::exists(io->getFileName())) {
return io->open(false);
} else {
io->open(true);
return createFileStructure(identifierText);
return createFileStructure(identifierText, description, dataCollection);
}
}

Status NWBFile::createFileStructure(const std::string& identifierText)
Status NWBFile::finalize()
{
return io->close();
}

Status NWBFile::createFileStructure(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection)
{
if (!io->canModifyObjects()) {
return Status::Failure;
Expand All @@ -62,6 +71,9 @@ Status NWBFile::createFileStructure(const std::string& identifierText)
io->createGroup("/general");
io->createGroup("/general/devices");
io->createGroup("/general/extracellular_ephys");
if (dataCollection != "") {
io->createStringDataSet("/general/data_collection", dataCollection);
}

io->createGroup("/specifications");
io->createReferenceAttribute("/specifications", "/", ".specloc");
Expand All @@ -78,19 +90,14 @@ Status NWBFile::createFileStructure(const std::string& identifierText)
std::string time = getCurrentTime();
std::vector<std::string> timeVec = {time};
io->createStringDataSet("/file_create_date", timeVec);
io->createStringDataSet("/session_description", "a recording session");
io->createStringDataSet("/session_description", description);
io->createStringDataSet("/session_start_time", time);
io->createStringDataSet("/timestamps_reference_time", time);
io->createStringDataSet("/identifier", identifierText);

return Status::Success;
}

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

Status NWBFile::createElectricalSeries(
std::vector<Types::ChannelVector> recordingArrays,
const IO::BaseDataType& dataType,
Expand Down
17 changes: 12 additions & 5 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ class NWBFile : public Container
* @brief Initializes the NWB file by opening and setting up the file
* structure.
*
* @param identifierText The identifier text for the NWBFile.
* @param identifierText The identifier text for the NWBFile.
* @param description A description of the NWBFile session.
* @param dataCollection Information about the data collection methods.
*/
Status initialize(const std::string& identifierText);
Status initialize(const std::string& identifierText,
const std::string& description = "a recording session",
const std::string& dataCollection = "");

/**
* @brief Finalizes the NWB file by closing it.
Expand Down Expand Up @@ -88,11 +92,14 @@ class NWBFile : public Container
* new objects cannot be added, which can be checked via
* nwbfile.io->canModifyObjects()
*
* @param identifierText The identifier text for the NWBFile.
*
* @param identifierText The identifier text for the NWBFile.
* @param description A description of the NWBFile session.
* @param dataCollection Information about the data collection methods.
* @return Status The status of the file structure creation.
*/
Status createFileStructure(const std::string& identifierText);
Status createFileStructure(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection);

private:
/**
Expand Down
8 changes: 7 additions & 1 deletion src/nwb/ecephys/ElectricalSeries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ void ElectricalSeries::initialize(const IO::BaseDataType& dataType,
offset);

// setup variables based on number of channels
std::vector<SizeType> electrodeInds(channelVector.size());
std::vector<int> electrodeInds(channelVector.size());
std::vector<float> channelConversions(channelVector.size());
for (size_t i = 0; i < channelVector.size(); ++i) {
electrodeInds[i] = channelVector[i].globalIndex;
channelConversions[i] = channelVector[i].getConversion();
}
samplesRecorded = SizeArray(channelVector.size(), 0);

Expand All @@ -50,6 +52,10 @@ void ElectricalSeries::initialize(const IO::BaseDataType& dataType,
SizeArray {1},
chunkSize,
getPath() + "/channel_conversion"));
channelConversion->writeDataBlock(
std::vector<SizeType>(1, channelVector.size()),
BaseDataType::F32,
&channelConversions[0]);
io->createCommonNWBAttributes(getPath() + "/channel_conversion",
"hdmf-common",
"",
Expand Down
62 changes: 33 additions & 29 deletions tests/reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,39 @@ using namespace H5;

int readerFunction(const std::string& path, const std::string& dataPath)
{
std::unique_ptr<H5File> file =
std::make_unique<H5File>(path, H5F_ACC_RDONLY | H5F_ACC_SWMR_READ);
std::unique_ptr<H5::DataSet> dSet =
std::make_unique<H5::DataSet>(file->openDataSet(dataPath));

std::vector<hsize_t> dsetSizes;
for (int i = 0; i < 3; ++i) {
H5Drefresh(dSet->getId());

// Get the current size of the dataset
DataSpace fSpace = dSet->getSpace();
hsize_t currentSize;
fSpace.getSimpleExtentDims(&currentSize, nullptr);

// Update the size
dsetSizes.push_back(currentSize);
sleep(1); // Simulate real-time data streaming
}

// print out dataset sizes
std::cout << "Dataset sizes: ";
for (int val : dsetSizes) {
std::cout << val << " ";
}
std::cout << std::endl;

// check that data is being appended (last value should be greater than the
// first)
if (dsetSizes[0] >= dsetSizes[2]) {
try {
std::unique_ptr<H5File> file =
std::make_unique<H5File>(path, H5F_ACC_RDONLY | H5F_ACC_SWMR_READ);
std::unique_ptr<H5::DataSet> dSet =
std::make_unique<H5::DataSet>(file->openDataSet(dataPath));

std::vector<hsize_t> dsetSizes;
for (int i = 0; i < 3; ++i) {
H5Drefresh(dSet->getId());

// Get the current size of the dataset
DataSpace fSpace = dSet->getSpace();
hsize_t currentSize;
fSpace.getSimpleExtentDims(&currentSize, nullptr);

// Update the size
dsetSizes.push_back(currentSize);
sleep(1); // Simulate real-time data streaming
}

// print out dataset sizes
std::cout << "Dataset sizes: ";
for (int val : dsetSizes) {
std::cout << val << " ";
}
std::cout << std::endl;

// check that data is being appended (last value should be greater than the
// first)
if (dsetSizes[0] >= dsetSizes[2]) {
return -1;
}
} catch (const FileIException& error) {
return -1;
}

Expand Down
11 changes: 4 additions & 7 deletions tests/testRecordingWorkflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TEST_CASE("writeContinuousData", "[recording]")
{
// 0. setup mock data
SizeType numChannels = 4;
SizeType numSamples = 300;
SizeType numSamples = 100;
SizeType samplesRecorded = 0;
SizeType bufferSize = numSamples / 10;
std::vector<float> dataBuffer(bufferSize);
Expand All @@ -47,7 +47,7 @@ TEST_CASE("writeContinuousData", "[recording]")

// 4. create datasets and add to recording containers
nwbfile->createElectricalSeries(
mockRecordingArrays, BaseDataType::I16, recordingContainers.get());
mockRecordingArrays, BaseDataType::F32, recordingContainers.get());

// 5. start the recording
io->startRecording();
Expand All @@ -72,14 +72,12 @@ TEST_CASE("writeContinuousData", "[recording]")
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());

recordingContainers->writeTimeseriesData(i,
channel,
dataShape,
positionOffset,
intBuffer.get(),
dataBuffer.data(),
timestampsBuffer.data());
}
}
Expand Down Expand Up @@ -113,8 +111,7 @@ TEST_CASE("writeContinuousData", "[recording]")
std::vector<float>(numSamples));
for (SizeType i = 0; i < numChannelsToRead; ++i) {
for (SizeType j = 0; j < numSamples; ++j) {
dataOut[i][j] =
buffer[j * numChannelsToRead + i] * (32767.0f * 0.000002f);
dataOut[i][j] = buffer[j * numChannelsToRead + i];
}
}
delete[] buffer;
Expand Down
2 changes: 1 addition & 1 deletion tests/testUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ inline std::vector<std::vector<float>> getMockData2D(SizeType numSamples = 1000,
for (auto& channelData : mockData) {
for (auto& data : channelData) {
data = static_cast<float>(dis(rng))
* 1000.f; // approximate microvolt unit range
* 100.f; // approximate microvolt unit range
}
}

Expand Down

0 comments on commit 5ab8464

Please sign in to comment.