Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integration related updates #89

Merged
merged 7 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,9 +1,13 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <ctime>
#include <iomanip>
#include <sstream>

#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 @@ -68,6 +72,40 @@ inline std::shared_ptr<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);
stephprince marked this conversation as resolved.
Show resolved Hide resolved

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
stephprince marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* @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 @@ -83,12 +121,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
13 changes: 9 additions & 4 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ NWBFile::NWBFile(const std::string& idText, std::shared_ptr<BaseIO> io)

NWBFile::~NWBFile() {}

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

Expand All @@ -50,7 +51,8 @@ Status NWBFile::finalize()
return io->close();
}

Status NWBFile::createFileStructure()
Status NWBFile::createFileStructure(std::string description,
std::string dataCollection)
{
if (!io->canModifyObjects()) {
return Status::Failure;
Expand All @@ -68,6 +70,9 @@ Status NWBFile::createFileStructure()
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 @@ -84,7 +89,7 @@ Status NWBFile::createFileStructure()
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);
Expand Down
10 changes: 8 additions & 2 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ class NWBFile
/**
* @brief Initializes the NWB file by opening and setting up the file
* structure.
* @param description A description of the NWBFile session.
* @param dataCollection Information about the data collection methods.
*/
Status initialize();
Status initialize(const std::string description = "a recording session",
const std::string dataCollection = "");

/**
* @brief Finalizes the NWB file by closing it.
Expand Down Expand Up @@ -86,9 +89,12 @@ class NWBFile
* Note, this function will fail if the file is in a mode where
* new objects cannot be added, which can be checked via
* nwbfile.io->canModifyObjects()
* @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();
Status createFileStructure(std::string description,
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 @@ -42,9 +42,11 @@ void ElectricalSeries::initialize()
TimeSeries::initialize();

// 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 @@ -54,6 +56,10 @@ void ElectricalSeries::initialize()
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 @@ -48,7 +48,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 @@ -73,14 +73,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 @@ -114,8 +112,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 @@ -80,7 +80,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
Loading