diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 10542a7f..5d5b82fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,8 +21,8 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-latest, ubuntu-latest] - + os: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: @@ -43,6 +43,13 @@ jobs: - name: Install dependencies - macos if: matrix.os == 'macos-latest' run: brew install hdf5 boost catch2 + + - name: Install dependencies - windows + if: matrix.os == 'windows-latest' + run: | + cd "${VCPKG_INSTALLATION_ROOT}" + vcpkg install hdf5[cpp]:x64-windows boost-date-time:x64-windows boost-endian:x64-windows boost-uuid:x64-windows catch2:x64-windows + vcpkg integrate install - name: Configure shell: pwsh diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dd3f2b8..41bc69d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,9 @@ find_package(Boost REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) target_link_libraries(aqnwb_aqnwb ${HDF5_CXX_LIBRARIES} ${Boost_LIBRARIES}) +if (WIN32) + target_link_libraries(aqnwb_aqnwb bcrypt) +endif() # ---- Install rules ---- diff --git a/CMakePresets.json b/CMakePresets.json index f464eddf..0ff72039 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -102,6 +102,9 @@ "inherits": ["flags-msvc", "ci-std"], "generator": "Visual Studio 17 2022", "architecture": "x64", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + }, "hidden": true }, { diff --git a/docs/pages/devdocs/install.dox b/docs/pages/devdocs/install.dox index dcc27dd1..64c63095 100644 --- a/docs/pages/devdocs/install.dox +++ b/docs/pages/devdocs/install.dox @@ -6,7 +6,12 @@ * \section dev_requirements_sec Requirements * * Please ensure that the required libraries described in the - * \ref user_requirements_sec "User Requirements" section are installed. + * \ref user_requirements_sec "User Requirements" section are installed and + * clone the latest AqNWB source via: + * + * \code{.sh} + * git clone https://github.com/NeurodataWithoutBorders/aqnwb.git + * \endcode * * For developers we also recommend to install the following optional command line tools used for * generating the documentation, for code quality checks, and for debugging. diff --git a/docs/pages/userdocs/hdf5io.dox b/docs/pages/userdocs/hdf5io.dox index 761af080..770510d2 100644 --- a/docs/pages/userdocs/hdf5io.dox +++ b/docs/pages/userdocs/hdf5io.dox @@ -127,6 +127,11 @@ * } * \enddot * + * \warning + * There are known issues using SWMR mode on Windows due to file locking by the reader processes. One workaround + * is to set the environment variable `HDF5_USE_FILE_LOCKING=FALSE` to prevent file access errors when using + * a writer process with other reader processes. + * * \subsection hdf5io_swmr_features Why does AqNWB use SMWR mode? * * Using SWMR has several key advantages for data acquisition applications: diff --git a/docs/pages/userdocs/install.dox b/docs/pages/userdocs/install.dox index 5f647da2..b276a6b0 100644 --- a/docs/pages/userdocs/install.dox +++ b/docs/pages/userdocs/install.dox @@ -10,6 +10,16 @@ * - [HDF5 >= 1.10](https://github.com/HDFGroup/hdf5) * - [Boost](https://www.boost.org/) * + * \section userbuild_source_sec Source + * + * The source code for AqNWB is available online via the + * [AqNWB GitHub](https://github.com/NeurodataWithoutBorders/aqnwb) repository. + * To checkout the latest developer version, clone the repository via: + * + * \code{.sh} + * git clone https://github.com/NeurodataWithoutBorders/aqnwb.git + * \endcode + * * \section userbuild_build_sec Build * * Here are the steps for building in release mode with a multi-configuration generator: diff --git a/resources/generate_spec_files.py b/resources/generate_spec_files.py index d83fd9db..2f1d74c2 100644 --- a/resources/generate_spec_files.py +++ b/resources/generate_spec_files.py @@ -11,7 +11,7 @@ # load file yaml = YAML(typ='safe') with open(file) as f: - namespace = yaml.load(file) + namespace = yaml.load(f) # get all the sources for i, ns in enumerate(namespace['namespaces']): @@ -22,8 +22,8 @@ header_file = Path(f"./src/spec/{ns['name'].replace('-', '_')}.hpp").resolve() with open(header_file, 'w') as fo: fo.write('#pragma once\n\n') - fo.write('#include \n#include \n#include \n\n') - fo.write(f'namespace AQNWB::SPEC::{ns['name'].upper().replace('-', '_')}\n{{\n\n') + fo.write('#include \n#include \n#include \n\n') + fo.write(f'namespace AQNWB::SPEC::{ns["name"].upper().replace("-", "_")}\n{{\n\n') fo.write(f'const std::string version = "{ns["version"]}";\n\n') # load and convert schema files @@ -33,14 +33,55 @@ # load file schema_file = file.parent / s['source'] with open(schema_file) as f: - spec = yaml.load(schema_file) + spec = yaml.load(f) # convert to cpp string print(f'Generating file {header_file} - {s["source"]}') - with open(header_file, 'a') as fo: + json_str = json.dumps(spec, separators=(',', ':')) + chunk_size = 16000 # Adjust the chunk size as needed + if len(json_str) > chunk_size: + # Split string into chunks if needed + chunks = [json_str[i:i + chunk_size] for i in range(0, len(json_str), chunk_size)] + var_name = s['source'].replace('.yaml', '').replace('.', '_') - fo.write(f'constexpr std::string_view {var_name} = R"delimiter(\n{json.dumps(spec, separators=(',', ':'))})delimiter";\n\n') + chunk_var_names = [] + for i, chunk in enumerate(chunks): + chunk_var_name = f'{var_name}_part{i}' + with open(header_file, 'a') as fo: + fo.write(f'constexpr std::string_view {chunk_var_name} = R"delimiter({chunk})delimiter";\n') + chunk_var_names.append(chunk_var_name) + + # Concatenate chunks at compile-time + with open(header_file, 'a') as fo: + fo.write(f'constexpr std::array {var_name}_parts = {{{", ".join(chunk_var_names)}}};\n') + fo.write(f'constexpr std::size_t {var_name}_total_length = []() {{\n') + fo.write(f' std::size_t length = 0;\n') + fo.write(f' for (const auto& part : {var_name}_parts) {{\n') + fo.write(f' length += part.size();\n') + fo.write(f' }}\n') + fo.write(f' return length;\n') + fo.write(f'}}();\n') + fo.write(f'constexpr auto {var_name}_concatenate = []() {{\n') + fo.write(f' std::array result{{}};\n') + fo.write(f' std::size_t pos = 0;\n') + fo.write(f' for (const auto& part : {var_name}_parts) {{\n') + fo.write(f' for (char c : part) {{\n') + fo.write(f' result[pos++] = c;\n') + fo.write(f' }}\n') + fo.write(f' }}\n') + fo.write(f' result[pos] = \'\\0\';\n') + fo.write(f' return result;\n') + fo.write(f'}}();\n') + fo.write(f'constexpr std::string_view {var_name}({var_name}_concatenate.data(), {var_name}_concatenate.size() - 1);\n\n') var_names.append(var_name) + else: + with open(header_file, 'a') as fo: + var_name = s['source'].replace('.yaml', '').replace('.', '_') + fo.write(f'constexpr std::string_view {var_name} = R"delimiter(\n{json.dumps(spec, separators=(',', ':'))})delimiter";\n\n') + var_names.append(var_name) + + + # reformat schema sources for namespace file schema = list() @@ -49,12 +90,12 @@ s = {'source': s['source'].split('.yaml')[0]} schema.append(s) ns['schema'] = schema - + # convert to cpp variables ns_output = {'namespaces': [ns]} with open(header_file, 'a') as fo: fo.write(f'constexpr std::string_view namespaces = R"delimiter(\n{json.dumps(ns_output, separators=(',', ':'))})delimiter";\n\n') - fo.write(f'constexpr std::array, {len(var_names) + 1}> specVariables {{{{\n') - fo.write(''.join([f' {{"{name.replace('_', '.')}", {name}}},\n' for name in var_names])) + fo.write(f'constexpr std::array, {len(var_names) + 1}>\n specVariables {{{{\n') + fo.write(''.join([f' {{"{name.replace("_", ".")}", {name}}},\n' for name in var_names])) fo.write(' {"namespace", namespaces}\n') - fo.write(f'}}}};\n}} // namespace AQNWB::SPEC::{ns['name'].upper().replace('-', '_')}\n') \ No newline at end of file + fo.write(f'}}}};\n}} // namespace AQNWB::SPEC::{ns["name"].upper().replace("-", "_")}\n') \ No newline at end of file diff --git a/src/Utils.hpp b/src/Utils.hpp index a9848cbe..7271a5f7 100644 --- a/src/Utils.hpp +++ b/src/Utils.hpp @@ -127,7 +127,7 @@ static inline std::string mergePaths(const std::string& path1, */ static inline void convertFloatToInt16LE(const float* source, void* dest, - int numSamples) + SizeType numSamples) { // TODO - several steps in this function may be unnecessary for our use // case. Consider simplifying the intermediate cast to char and the @@ -135,8 +135,9 @@ static inline void convertFloatToInt16LE(const float* source, auto maxVal = static_cast(0x7fff); auto intData = static_cast(dest); - for (int i = 0; i < numSamples; ++i) { - auto clampedValue = std::clamp(maxVal * source[i], -maxVal, maxVal); + for (SizeType i = 0; i < numSamples; ++i) { + auto clampedValue = + std::clamp(maxVal * static_cast(source[i]), -maxVal, maxVal); auto intValue = static_cast(static_cast(std::round(clampedValue))); intValue = boost::endian::native_to_little(intValue); @@ -158,7 +159,7 @@ static inline std::unique_ptr transformToInt16( std::unique_ptr intData = std::make_unique(numSamples); // copy data and multiply by scaling factor - double multFactor = 1 / (32767.0f * conversion_factor); + float multFactor = 1.0f / (32767.0f * conversion_factor); std::transform(data, data + numSamples, scaledData.get(), diff --git a/src/io/hdf5/HDF5IO.cpp b/src/io/hdf5/HDF5IO.cpp index 0577d251..b58560da 100644 --- a/src/io/hdf5/HDF5IO.cpp +++ b/src/io/hdf5/HDF5IO.cpp @@ -682,7 +682,14 @@ Status HDF5IO::createReferenceDataSet( delete[] rdata; herr_t dsetStatus = H5Dclose(dset); + if (checkStatus(dsetStatus) == Status::Failure) { + return Status::Failure; + } + herr_t spaceStatus = H5Sclose(space); + if (checkStatus(spaceStatus) == Status::Failure) { + return Status::Failure; + } return checkStatus(writeStatus); } diff --git a/src/nwb/RecordingContainers.cpp b/src/nwb/RecordingContainers.cpp index 59b01bfb..a207abd0 100644 --- a/src/nwb/RecordingContainers.cpp +++ b/src/nwb/RecordingContainers.cpp @@ -62,7 +62,8 @@ Status RecordingContainers::writeElectricalSeriesData( if (es == nullptr) return Status::Failure; - es->writeChannel(channel.getLocalIndex(), numSamples, data, timestamps); + return es->writeChannel( + channel.getLocalIndex(), numSamples, data, timestamps); } Status RecordingContainers::writeSpikeEventData(const SizeType& containerInd, @@ -77,5 +78,5 @@ Status RecordingContainers::writeSpikeEventData(const SizeType& containerInd, if (ses == nullptr) return Status::Failure; - ses->writeSpike(numSamples, numChannels, data, timestamps); + return ses->writeSpike(numSamples, numChannels, data, timestamps); } diff --git a/src/nwb/base/TimeSeries.cpp b/src/nwb/base/TimeSeries.cpp index b6e87d92..846a362a 100644 --- a/src/nwb/base/TimeSeries.cpp +++ b/src/nwb/base/TimeSeries.cpp @@ -55,11 +55,11 @@ void TimeSeries::initialize(const IO::BaseDataType& dataType, Status TimeSeries::writeData(const std::vector& dataShape, const std::vector& positionOffset, - const void* data, - const void* timestamps) + const void* dataInput, + const void* timestampsInput) { Status tsStatus = Status::Success; - if (timestamps != nullptr) { + if (timestampsInput != nullptr) { const std::vector timestampsShape = { dataShape[0]}; // timestamps should match shape of the first data // dimension @@ -67,13 +67,13 @@ Status TimeSeries::writeData(const std::vector& dataShape, tsStatus = this->timestamps->writeDataBlock(timestampsShape, timestampsPositionOffset, this->timestampsType, - timestamps); + timestampsInput); } Status dataStatus = this->data->writeDataBlock( - dataShape, positionOffset, this->dataType, data); + dataShape, positionOffset, this->dataType, dataInput); - if ((dataStatus != Status::Success) or (tsStatus != Status::Success)) { + if ((dataStatus != Status::Success) || (tsStatus != Status::Success)) { return Status::Failure; } else { return Status::Success; diff --git a/src/nwb/base/TimeSeries.hpp b/src/nwb/base/TimeSeries.hpp index 7805f064..cf9812d4 100644 --- a/src/nwb/base/TimeSeries.hpp +++ b/src/nwb/base/TimeSeries.hpp @@ -35,16 +35,16 @@ class TimeSeries : public Container * @brief Writes a timeseries data block to the file. * @param dataShape The size of the data block. * @param positionOffset The position of the data block to write to. - * @param data A pointer to the data block. - * @param timestamps A pointer to the timestamps block. May be null if + * @param dataInput A pointer to the data block. + * @param timestampsInput A pointer to the timestamps block. May be null if * multidimensional TimeSeries and only need to write the timestamps once but * write data in separate blocks. * @return The status of the write operation. */ Status writeData(const std::vector& dataShape, const std::vector& positionOffset, - const void* data, - const void* timestamps = nullptr); + const void* dataInput, + const void* timestampsInput = nullptr); /** * @brief Initializes the TimeSeries by creating NWB related attributes and diff --git a/src/nwb/ecephys/ElectricalSeries.cpp b/src/nwb/ecephys/ElectricalSeries.cpp index b6cfdcb3..83245ab1 100644 --- a/src/nwb/ecephys/ElectricalSeries.cpp +++ b/src/nwb/ecephys/ElectricalSeries.cpp @@ -41,7 +41,7 @@ void ElectricalSeries::initialize(const IO::BaseDataType& dataType, offset); // setup variables based on number of channels - std::vector electrodeInds(channelVector.size()); + std::vector electrodeInds(channelVector.size()); std::vector channelConversions(channelVector.size()); for (size_t i = 0; i < channelVector.size(); ++i) { electrodeInds[i] = channelVector[i].getGlobalIndex(); @@ -91,8 +91,8 @@ void ElectricalSeries::initialize(const IO::BaseDataType& dataType, Status ElectricalSeries::writeChannel(SizeType channelInd, const SizeType& numSamples, - const void* data, - const void* timestamps) + const void* dataInput, + const void* timestampsInput) { // get offsets and datashape std::vector dataShape = { @@ -105,8 +105,8 @@ Status ElectricalSeries::writeChannel(SizeType channelInd, // write channel data if (channelInd == 0) { - return writeData(dataShape, positionOffset, data, timestamps); + return writeData(dataShape, positionOffset, dataInput, timestampsInput); } else { - return writeData(dataShape, positionOffset, data); + return writeData(dataShape, positionOffset, dataInput); } } diff --git a/src/nwb/ecephys/ElectricalSeries.hpp b/src/nwb/ecephys/ElectricalSeries.hpp index 7cbddbfe..41eb3cb1 100644 --- a/src/nwb/ecephys/ElectricalSeries.hpp +++ b/src/nwb/ecephys/ElectricalSeries.hpp @@ -64,14 +64,14 @@ class ElectricalSeries : public TimeSeries * @brief Writes a channel to an ElectricalSeries dataset. * @param channelInd The channel index within the ElectricalSeries * @param numSamples The number of samples to write (length in time). - * @param data A pointer to the data block. - * @param timestamps A pointer to the timestamps block. + * @param dataInput A pointer to the data block. + * @param timestampsInput A pointer to the timestamps block. * @return The status of the write operation. */ Status writeChannel(SizeType channelInd, const SizeType& numSamples, - const void* data, - const void* timestamps); + const void* dataInput, + const void* timestampsInput); /** * @brief Channel group that this time series is associated with. diff --git a/src/nwb/ecephys/SpikeEventSeries.cpp b/src/nwb/ecephys/SpikeEventSeries.cpp index 2cdf7ae6..2bb456e9 100644 --- a/src/nwb/ecephys/SpikeEventSeries.cpp +++ b/src/nwb/ecephys/SpikeEventSeries.cpp @@ -38,8 +38,8 @@ void SpikeEventSeries::initialize(const IO::BaseDataType& dataType, Status SpikeEventSeries::writeSpike(const SizeType& numSamples, const SizeType& numChannels, - const void* data, - const void* timestamps) + const void* dataInput, + const void* timestampsInput) { // get offsets and datashape std::vector dataShape; @@ -54,5 +54,5 @@ Status SpikeEventSeries::writeSpike(const SizeType& numSamples, this->m_eventsRecorded += 1; // write channel data - return writeData(dataShape, positionOffset, data, timestamps); + return writeData(dataShape, positionOffset, dataInput, timestampsInput); } \ No newline at end of file diff --git a/src/nwb/ecephys/SpikeEventSeries.hpp b/src/nwb/ecephys/SpikeEventSeries.hpp index 7d68cfc4..2ab64765 100644 --- a/src/nwb/ecephys/SpikeEventSeries.hpp +++ b/src/nwb/ecephys/SpikeEventSeries.hpp @@ -67,13 +67,13 @@ class SpikeEventSeries : public ElectricalSeries * * @param numSamples The number of samples in the event * @param numChannels The number of channels in the event - * @param data The data of the event - * @param timestamps The timestamps of the event + * @param dataInput The data of the event + * @param timestampsInput The timestamps of the event */ Status writeSpike(const SizeType& numSamples, const SizeType& numChannels, - const void* data, - const void* timestamps); + const void* dataInput, + const void* timestampsInput); DEFINE_FIELD(readData, DatasetField, std::any, "data", Spike waveforms) diff --git a/src/nwb/file/ElectrodeTable.cpp b/src/nwb/file/ElectrodeTable.cpp index 999f4cbb..890b2b28 100644 --- a/src/nwb/file/ElectrodeTable.cpp +++ b/src/nwb/file/ElectrodeTable.cpp @@ -66,15 +66,23 @@ void ElectrodeTable::initialize(const std::string& description) AQNWB::mergePaths(m_path, "location")))); } -void ElectrodeTable::addElectrodes(std::vector channels) +void ElectrodeTable::addElectrodes(std::vector channelsInput) { // create datasets +<<<<<<< HEAD for (const auto& ch : channels) { m_groupReferences.push_back( AQNWB::mergePaths(m_groupPathBase, ch.getGroupName())); m_groupNames.push_back(ch.getGroupName()); m_electrodeNumbers.push_back(ch.getGlobalIndex()); m_locationNames.push_back("unknown"); +======= + for (const auto& ch : channelsInput) { + groupReferences.push_back(groupPathBase + ch.getGroupName()); + groupNames.push_back(ch.getGroupName()); + electrodeNumbers.push_back(static_cast(ch.getGlobalIndex())); + locationNames.push_back("unknown"); +>>>>>>> main } } diff --git a/src/nwb/file/ElectrodeTable.hpp b/src/nwb/file/ElectrodeTable.hpp index d68b3ec2..1676a73a 100644 --- a/src/nwb/file/ElectrodeTable.hpp +++ b/src/nwb/file/ElectrodeTable.hpp @@ -55,9 +55,9 @@ class ElectrodeTable : public DynamicTable /** * @brief Sets up the ElectrodeTable by adding electrodes and their metadata. - * + * @param channelsInput The vector of Channel objects to add to the table. */ - void addElectrodes(std::vector channels); + void addElectrodes(std::vector channelsInput); /** * @brief Gets the group path of the ElectrodeTable. diff --git a/src/nwb/hdmf/table/DynamicTable.hpp b/src/nwb/hdmf/table/DynamicTable.hpp index 85ad48ef..a9389c20 100644 --- a/src/nwb/hdmf/table/DynamicTable.hpp +++ b/src/nwb/hdmf/table/DynamicTable.hpp @@ -28,7 +28,12 @@ class DynamicTable : public Container * @brief Constructor. * @param path The location of the table in the file. * @param io A shared pointer to the IO object. +<<<<<<< HEAD * @param colNames Set the names of the columns for the table +======= + * @param description The description of the table (optional). + * @param colNames Names of the columns in the table +>>>>>>> main */ DynamicTable( const std::string& path, diff --git a/src/nwb/hdmf/table/ElementIdentifiers.hpp b/src/nwb/hdmf/table/ElementIdentifiers.hpp index e720620f..b657af5c 100644 --- a/src/nwb/hdmf/table/ElementIdentifiers.hpp +++ b/src/nwb/hdmf/table/ElementIdentifiers.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../base/Data.hpp" +#include "nwb/hdmf/base/Data.hpp" namespace AQNWB::NWB { diff --git a/src/spec/core.hpp b/src/spec/core.hpp index 0a67f80a..e6b685ab 100644 --- a/src/spec/core.hpp +++ b/src/spec/core.hpp @@ -21,8 +21,34 @@ constexpr std::string_view nwb_epoch = R"delimiter( constexpr std::string_view nwb_image = R"delimiter( {"datasets":[{"neurodata_type_def":"GrayscaleImage","neurodata_type_inc":"Image","dims":["x","y"],"shape":[null,null],"doc":"A grayscale image.","dtype":"numeric"},{"neurodata_type_def":"RGBImage","neurodata_type_inc":"Image","dims":["x","y","r, g, b"],"shape":[null,null,3],"doc":"A color image.","dtype":"numeric"},{"neurodata_type_def":"RGBAImage","neurodata_type_inc":"Image","dims":["x","y","r, g, b, a"],"shape":[null,null,4],"doc":"A color image with transparency.","dtype":"numeric"}],"groups":[{"neurodata_type_def":"ImageSeries","neurodata_type_inc":"TimeSeries","doc":"General image data that is common between acquisition and stimulus time series. Sometimes the image data is stored in the file in a raw format while other times it will be stored as a series of external image files in the host file system. The data field will either be binary data, if the data is stored in the NWB file, or empty, if the data is stored in an external image stack. [frame][x][y] or [frame][x][y][z].","datasets":[{"name":"data","dtype":"numeric","dims":[["frame","x","y"],["frame","x","y","z"]],"shape":[[null,null,null],[null,null,null,null]],"doc":"Binary data representing images across frames. If data are stored in an external file, this should be an empty 3D array."},{"name":"dimension","dtype":"int32","dims":["rank"],"shape":[null],"doc":"Number of pixels on x, y, (and z) axes.","quantity":"?"},{"name":"external_file","dtype":"text","dims":["num_files"],"shape":[null],"doc":"Paths to one or more external file(s). The field is only present if format='external'. This is only relevant if the image series is stored in the file system as one or more image file(s). This field should NOT be used if the image is stored in another NWB file and that file is linked to this file.","quantity":"?","attributes":[{"name":"starting_frame","dtype":"int32","dims":["num_files"],"shape":[null],"doc":"Each external image may contain one or more consecutive frames of the full ImageSeries. This attribute serves as an index to indicate which frames each file contains, to facilitate random access. The 'starting_frame' attribute, hence, contains a list of frame numbers within the full ImageSeries of the first frame of each file listed in the parent 'external_file' dataset. Zero-based indexing is used (hence, the first element will always be zero). For example, if the 'external_file' dataset has three paths to files and the first file has 5 frames, the second file has 10 frames, and the third file has 20 frames, then this attribute will have values [0, 5, 15]. If there is a single external file that holds all of the frames of the ImageSeries (and so there is a single element in the 'external_file' dataset), then this attribute should have value [0]."}]},{"name":"format","dtype":"text","default_value":"raw","doc":"Format of image. If this is 'external', then the attribute 'external_file' contains the path information to the image files. If this is 'raw', then the raw (single-channel) binary data is stored in the 'data' dataset. If this attribute is not present, then the default format='raw' case is assumed.","quantity":"?"}],"links":[{"name":"device","target_type":"Device","doc":"Link to the Device object that was used to capture these images.","quantity":"?"}]},{"neurodata_type_def":"ImageMaskSeries","neurodata_type_inc":"ImageSeries","doc":"An alpha mask that is applied to a presented visual stimulus. The 'data' array contains an array of mask values that are applied to the displayed image. Mask values are stored as RGBA. Mask can vary with time. The timestamps array indicates the starting time of a mask, and that mask pattern continues until it's explicitly changed.","links":[{"name":"masked_imageseries","target_type":"ImageSeries","doc":"Link to ImageSeries object that this image mask is applied to."}]},{"neurodata_type_def":"OpticalSeries","neurodata_type_inc":"ImageSeries","doc":"Image data that is presented or recorded. A stimulus template movie will be stored only as an image. When the image is presented as stimulus, additional data is required, such as field of view (e.g., how much of the visual field the image covers, or how what is the area of the target being imaged). If the OpticalSeries represents acquired imaging data, orientation is also important.","datasets":[{"name":"distance","dtype":"float32","doc":"Distance from camera/monitor to target/eye.","quantity":"?"},{"name":"field_of_view","dtype":"float32","dims":[["width, height"],["width, height, depth"]],"shape":[[2],[3]],"doc":"Width, height and depth of image, or imaged area, in meters.","quantity":"?"},{"name":"data","dtype":"numeric","dims":[["frame","x","y"],["frame","x","y","r, g, b"]],"shape":[[null,null,null],[null,null,null,3]],"doc":"Images presented to subject, either grayscale or RGB"},{"name":"orientation","dtype":"text","doc":"Description of image relative to some reference frame (e.g., which way is up). Must also specify frame of reference.","quantity":"?"}]},{"neurodata_type_def":"IndexSeries","neurodata_type_inc":"TimeSeries","doc":"Stores indices to image frames stored in an ImageSeries. The purpose of the IndexSeries is to allow a static image stack to be stored in an Images object, and the images in the stack to be referenced out-of-order. This can be for the display of individual images, or of movie segments (as a movie is simply a series of images). The data field stores the index of the frame in the referenced Images object, and the timestamps array indicates when that image was displayed.","datasets":[{"name":"data","dtype":"uint32","dims":["num_times"],"shape":[null],"doc":"Index of the image (using zero-indexing) in the linked Images object.","attributes":[{"name":"conversion","dtype":"float32","doc":"This field is unused by IndexSeries.","required":false},{"name":"resolution","dtype":"float32","doc":"This field is unused by IndexSeries.","required":false},{"name":"offset","dtype":"float32","doc":"This field is unused by IndexSeries.","required":false},{"name":"unit","dtype":"text","value":"N/A","doc":"This field is unused by IndexSeries and has the value N/A."}]}],"links":[{"name":"indexed_timeseries","target_type":"ImageSeries","doc":"Link to ImageSeries object containing images that are indexed. Use of this link is discouraged and will be deprecated. Link to an Images type instead.","quantity":"?"},{"name":"indexed_images","target_type":"Images","doc":"Link to Images object containing an ordered set of images that are indexed. The Images object must contain a 'ordered_images' dataset specifying the order of the images in the Images type.","quantity":"?"}]}]})delimiter"; -constexpr std::string_view nwb_file = R"delimiter( -{"groups":[{"neurodata_type_def":"NWBFile","neurodata_type_inc":"NWBContainer","name":"root","doc":"An NWB file storing cellular-based neurophysiology data from a single experimental session.","attributes":[{"name":"nwb_version","dtype":"text","value":"2.7.0-alpha","doc":"File version string. Use semantic versioning, e.g. 1.2.1. This will be the name of the format with trailing major, minor and patch numbers."}],"datasets":[{"name":"file_create_date","dtype":"isodatetime","dims":["num_modifications"],"shape":[null],"doc":"A record of the date the file was created and of subsequent modifications. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted strings: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds. The file can be created after the experiment was run, so this may differ from the experiment start time. Each modification to the nwb file adds a new entry to the array."},{"name":"identifier","dtype":"text","doc":"A unique text identifier for the file. For example, concatenated lab name, file creation date/time and experimentalist, or a hash of these and/or other values. The goal is that the string should be unique to all other files."},{"name":"session_description","dtype":"text","doc":"A description of the experimental session and data in the file."},{"name":"session_start_time","dtype":"isodatetime","doc":"Date and time of the experiment/session start. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted string: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds."},{"name":"timestamps_reference_time","dtype":"isodatetime","doc":"Date and time corresponding to time zero of all timestamps. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted string: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds. All times stored in the file use this time as reference (i.e., time zero)."}],"groups":[{"name":"acquisition","doc":"Data streams recorded from the system, including ephys, ophys, tracking, etc. This group should be read-only after the experiment is completed and timestamps are corrected to a common timebase. The data stored here may be links to raw data stored in external NWB files. This will allow keeping bulky raw data out of the file while preserving the option of keeping some/all in the file. Acquired data includes tracking and experimental data streams (i.e., everything measured from the system). If bulky data is stored in the /acquisition group, the data can exist in a separate NWB file that is linked to by the file being used for processing and analysis.","groups":[{"neurodata_type_inc":"NWBDataInterface","doc":"Acquired, raw data.","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Tabular data that is relevant to acquisition","quantity":"*"}]},{"name":"analysis","doc":"Lab-specific and custom scientific analysis of data. There is no defined format for the content of this group - the format is up to the individual user/lab. To facilitate sharing analysis data between labs, the contents here should be stored in standard types (e.g., neurodata_types) and appropriately documented. The file can store lab-specific and custom data analysis without restriction on its form or schema, reducing data formatting restrictions on end users. Such data should be placed in the analysis group. The analysis data should be documented so that it could be shared with other labs.","groups":[{"neurodata_type_inc":"NWBContainer","doc":"Custom analysis results.","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Tabular data that is relevant to data stored in analysis","quantity":"*"}]},{"name":"scratch","doc":"A place to store one-off analysis results. Data placed here is not intended for sharing. By placing data here, users acknowledge that there is no guarantee that their data meets any standard.","quantity":"?","groups":[{"neurodata_type_inc":"NWBContainer","doc":"Any one-off containers","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Any one-off tables","quantity":"*"}],"datasets":[{"neurodata_type_inc":"ScratchData","doc":"Any one-off datasets","quantity":"*"}]},{"name":"processing","doc":"The home for ProcessingModules. These modules perform intermediate analysis of data that is necessary to perform before scientific analysis. Examples include spike clustering, extracting position from tracking data, stitching together image slices. ProcessingModules can be large and express many data sets from relatively complex analysis (e.g., spike detection and clustering) or small, representing extraction of position information from tracking video, or even binary lick/no-lick decisions. Common software tools (e.g., klustakwik, MClust) are expected to read/write data here. 'Processing' refers to intermediate analysis of the acquired data to make it more amenable to scientific analysis.","groups":[{"neurodata_type_inc":"ProcessingModule","doc":"Intermediate analysis of acquired data.","quantity":"*"}]},{"name":"stimulus","doc":"Data pushed into the system (eg, video stimulus, sound, voltage, etc) and secondary representations of that data (eg, measurements of something used as a stimulus). This group should be made read-only after experiment complete and timestamps are corrected to common timebase. Stores both presented stimuli and stimulus templates, the latter in case the same stimulus is presented multiple times, or is pulled from an external stimulus library. Stimuli are here defined as any signal that is pushed into the system as part of the experiment (eg, sound, video, voltage, etc). Many different experiments can use the same stimuli, and stimuli can be re-used during an experiment. The stimulus group is organized so that one version of template stimuli can be stored and these be used multiple times. These templates can exist in the present file or can be linked to a remote library file.","groups":[{"name":"presentation","doc":"Stimuli presented during the experiment.","groups":[{"neurodata_type_inc":"TimeSeries","doc":"TimeSeries objects containing data of presented stimuli.","quantity":"*"}]},{"name":"templates","doc":"Template stimuli. Timestamps in templates are based on stimulus design and are relative to the beginning of the stimulus. When templates are used, the stimulus instances must convert presentation times to the experiment`s time reference frame.","groups":[{"neurodata_type_inc":"TimeSeries","doc":"TimeSeries objects containing template data of presented stimuli.","quantity":"*"},{"neurodata_type_inc":"Images","doc":"Images objects containing images of presented stimuli.","quantity":"*"}]}]},{"name":"general","doc":"Experimental metadata, including protocol, notes and description of hardware device(s). The metadata stored in this section should be used to describe the experiment. Metadata necessary for interpreting the data is stored with the data. General experimental metadata, including animal strain, experimental protocols, experimenter, devices, etc, are stored under 'general'. Core metadata (e.g., that required to interpret data fields) is stored with the data itself, and implicitly defined by the file specification (e.g., time is in seconds). The strategy used here for storing non-core metadata is to use free-form text fields, such as would appear in sentences or paragraphs from a Methods section. Metadata fields are text to enable them to be more general, for example to represent ranges instead of numerical values. Machine-readable metadata is stored as attributes to these free-form datasets. All entries in the below table are to be included when data is present. Unused groups (e.g., intracellular_ephys in an optophysiology experiment) should not be created unless there is data to store within them.","datasets":[{"name":"data_collection","dtype":"text","doc":"Notes about data collection and analysis.","quantity":"?"},{"name":"experiment_description","dtype":"text","doc":"General description of the experiment.","quantity":"?"},{"name":"experimenter","dtype":"text","doc":"Name of person(s) who performed the experiment. Can also specify roles of different people involved.","quantity":"?","dims":["num_experimenters"],"shape":[null]},{"name":"institution","dtype":"text","doc":"Institution(s) where experiment was performed.","quantity":"?"},{"name":"keywords","dtype":"text","dims":["num_keywords"],"shape":[null],"doc":"Terms to search over.","quantity":"?"},{"name":"lab","dtype":"text","doc":"Laboratory where experiment was performed.","quantity":"?"},{"name":"notes","dtype":"text","doc":"Notes about the experiment.","quantity":"?"},{"name":"pharmacology","dtype":"text","doc":"Description of drugs used, including how and when they were administered. Anesthesia(s), painkiller(s), etc., plus dosage, concentration, etc.","quantity":"?"},{"name":"protocol","dtype":"text","doc":"Experimental protocol, if applicable. e.g., include IACUC protocol number.","quantity":"?"},{"name":"related_publications","dtype":"text","doc":"Publication information. PMID, DOI, URL, etc.","dims":["num_publications"],"shape":[null],"quantity":"?"},{"name":"session_id","dtype":"text","doc":"Lab-specific ID for the session.","quantity":"?"},{"name":"slices","dtype":"text","doc":"Description of slices, including information about preparation thickness, orientation, temperature, and bath solution.","quantity":"?"},{"name":"source_script","dtype":"text","doc":"Script file or link to public source code used to create this NWB file.","quantity":"?","attributes":[{"name":"file_name","dtype":"text","doc":"Name of script file."}]},{"name":"stimulus","dtype":"text","doc":"Notes about stimuli, such as how and where they were presented.","quantity":"?"},{"name":"surgery","dtype":"text","doc":"Narrative description about surgery/surgeries, including date(s) and who performed surgery.","quantity":"?"},{"name":"virus","dtype":"text","doc":"Information about virus(es) used in experiments, including virus ID, source, date made, injection location, volume, etc.","quantity":"?"}],"groups":[{"neurodata_type_inc":"LabMetaData","doc":"Place-holder than can be extended so that lab-specific meta-data can be placed in /general.","quantity":"*"},{"name":"devices","doc":"Description of hardware devices used during experiment, e.g., monitors, ADC boards, microscopes, etc.","quantity":"?","groups":[{"neurodata_type_inc":"Device","doc":"Data acquisition devices.","quantity":"*"}]},{"name":"subject","neurodata_type_inc":"Subject","doc":"Information about the animal or person from which the data was measured.","quantity":"?"},{"name":"extracellular_ephys","doc":"Metadata related to extracellular electrophysiology.","quantity":"?","groups":[{"neurodata_type_inc":"ElectrodeGroup","doc":"Physical group of electrodes.","quantity":"*"},{"name":"electrodes","neurodata_type_inc":"DynamicTable","doc":"A table of all electrodes (i.e. channels) used for recording.","quantity":"?","datasets":[{"name":"x","neurodata_type_inc":"VectorData","dtype":"float32","doc":"x coordinate of the channel location in the brain (+x is posterior).","quantity":"?"},{"name":"y","neurodata_type_inc":"VectorData","dtype":"float32","doc":"y coordinate of the channel location in the brain (+y is inferior).","quantity":"?"},{"name":"z","neurodata_type_inc":"VectorData","dtype":"float32","doc":"z coordinate of the channel location in the brain (+z is right).","quantity":"?"},{"name":"imp","neurodata_type_inc":"VectorData","dtype":"float32","doc":"Impedance of the channel, in ohms.","quantity":"?"},{"name":"location","neurodata_type_inc":"VectorData","dtype":"text","doc":"Location of the electrode (channel). Specify the area, layer, comments on estimation of area/layer, stereotaxic coordinates if in vivo, etc. Use standard atlas names for anatomical regions when possible."},{"name":"filtering","neurodata_type_inc":"VectorData","dtype":"text","doc":"Description of hardware filtering, including the filter name and frequency cutoffs.","quantity":"?"},{"name":"group","neurodata_type_inc":"VectorData","dtype":{"target_type":"ElectrodeGroup","reftype":"object"},"doc":"Reference to the ElectrodeGroup this electrode is a part of."},{"name":"group_name","neurodata_type_inc":"VectorData","dtype":"text","doc":"Name of the ElectrodeGroup this electrode is a part of."},{"name":"rel_x","neurodata_type_inc":"VectorData","dtype":"float32","doc":"x coordinate in electrode group","quantity":"?"},{"name":"rel_y","neurodata_type_inc":"VectorData","dtype":"float32","doc":"y coordinate in electrode group","quantity":"?"},{"name":"rel_z","neurodata_type_inc":"VectorData","dtype":"float32","doc":"z coordinate in electrode group","quantity":"?"},{"name":"reference","neurodata_type_inc":"VectorData","dtype":"text","doc":"Description of the reference electrode and/or reference scheme used for this electrode, e.g., \"stainless steel skull screw\" or \"online common average referencing\".","quantity":"?"}]}]},{"name":"intracellular_ephys","doc":"Metadata related to intracellular electrophysiology.","quantity":"?","datasets":[{"name":"filtering","dtype":"text","doc":"[DEPRECATED] Use IntracellularElectrode.filtering instead. Description of filtering used. Includes filtering type and parameters, frequency fall-off, etc. If this changes between TimeSeries, filter description should be stored as a text attribute for each TimeSeries.","quantity":"?"}],"groups":[{"neurodata_type_inc":"IntracellularElectrode","doc":"An intracellular electrode.","quantity":"*"},{"name":"sweep_table","neurodata_type_inc":"SweepTable","doc":"[DEPRECATED] Table used to group different PatchClampSeries. SweepTable is being replaced by IntracellularRecordingsTable and SimultaneousRecordingsTable tables. Additional SequentialRecordingsTable, RepetitionsTable and ExperimentalConditions tables provide enhanced support for experiment metadata.","quantity":"?"},{"name":"intracellular_recordings","neurodata_type_inc":"IntracellularRecordingsTable","doc":"A table to group together a stimulus and response from a single electrode and a single simultaneous recording. Each row in the table represents a single recording consisting typically of a stimulus and a corresponding response. In some cases, however, only a stimulus or a response are recorded as as part of an experiment. In this case both, the stimulus and response will point to the same TimeSeries while the idx_start and count of the invalid column will be set to -1, thus, indicating that no values have been recorded for the stimulus or response, respectively. Note, a recording MUST contain at least a stimulus or a response. Typically the stimulus and response are PatchClampSeries. However, the use of AD/DA channels that are not associated to an electrode is also common in intracellular electrophysiology, in which case other TimeSeries may be used.","quantity":"?"},{"name":"simultaneous_recordings","neurodata_type_inc":"SimultaneousRecordingsTable","doc":"A table for grouping different intracellular recordings from the IntracellularRecordingsTable table together that were recorded simultaneously from different electrodes","quantity":"?"},{"name":"sequential_recordings","neurodata_type_inc":"SequentialRecordingsTable","doc":"A table for grouping different sequential recordings from the SimultaneousRecordingsTable table together. This is typically used to group together sequential recordings where the a sequence of stimuli of the same type with varying parameters have been presented in a sequence.","quantity":"?"},{"name":"repetitions","neurodata_type_inc":"RepetitionsTable","doc":"A table for grouping different sequential intracellular recordings together. With each SequentialRecording typically representing a particular type of stimulus, the RepetitionsTable table is typically used to group sets of stimuli applied in sequence.","quantity":"?"},{"name":"experimental_conditions","neurodata_type_inc":"ExperimentalConditionsTable","doc":"A table for grouping different intracellular recording repetitions together that belong to the same experimental experimental_conditions.","quantity":"?"}]},{"name":"optogenetics","doc":"Metadata describing optogenetic stimuluation.","quantity":"?","groups":[{"neurodata_type_inc":"OptogeneticStimulusSite","doc":"An optogenetic stimulation site.","quantity":"*"}]},{"name":"optophysiology","doc":"Metadata related to optophysiology.","quantity":"?","groups":[{"neurodata_type_inc":"ImagingPlane","doc":"An imaging plane.","quantity":"*"}]}]},{"name":"intervals","doc":"Experimental intervals, whether that be logically distinct sub-experiments having a particular scientific goal, trials (see trials subgroup) during an experiment, or epochs (see epochs subgroup) deriving from analysis of data.","quantity":"?","groups":[{"name":"epochs","neurodata_type_inc":"TimeIntervals","doc":"Divisions in time marking experimental stages or sub-divisions of a single recording session.","quantity":"?"},{"name":"trials","neurodata_type_inc":"TimeIntervals","doc":"Repeated experimental events that have a logical grouping.","quantity":"?"},{"name":"invalid_times","neurodata_type_inc":"TimeIntervals","doc":"Time intervals that should be removed from analysis.","quantity":"?"},{"neurodata_type_inc":"TimeIntervals","doc":"Optional additional table(s) for describing other experimental time intervals.","quantity":"*"}]},{"name":"units","neurodata_type_inc":"Units","doc":"Data about sorted spike units.","quantity":"?"}]},{"neurodata_type_def":"LabMetaData","neurodata_type_inc":"NWBContainer","doc":"Lab-specific meta-data."},{"neurodata_type_def":"Subject","neurodata_type_inc":"NWBContainer","doc":"Information about the animal or person from which the data was measured.","datasets":[{"name":"age","dtype":"text","doc":"Age of subject. Can be supplied instead of 'date_of_birth'.","quantity":"?","attributes":[{"name":"reference","doc":"Age is with reference to this event. Can be 'birth' or 'gestational'. If reference is omitted, 'birth' is implied.","dtype":"text","required":false,"default_value":"birth"}]},{"name":"date_of_birth","dtype":"isodatetime","doc":"Date of birth of subject. Can be supplied instead of 'age'.","quantity":"?"},{"name":"description","dtype":"text","doc":"Description of subject and where subject came from (e.g., breeder, if animal).","quantity":"?"},{"name":"genotype","dtype":"text","doc":"Genetic strain. If absent, assume Wild Type (WT).","quantity":"?"},{"name":"sex","dtype":"text","doc":"Gender of subject.","quantity":"?"},{"name":"species","dtype":"text","doc":"Species of subject.","quantity":"?"},{"name":"strain","dtype":"text","doc":"Strain of subject.","quantity":"?"},{"name":"subject_id","dtype":"text","doc":"ID of animal/person used/participating in experiment (lab convention).","quantity":"?"},{"name":"weight","dtype":"text","doc":"Weight at time of experiment, at time of surgery and at other important times.","quantity":"?"}]}],"datasets":[{"neurodata_type_def":"ScratchData","neurodata_type_inc":"NWBData","doc":"Any one-off datasets","attributes":[{"name":"notes","doc":"Any notes the user has about the dataset being stored","dtype":"text"}]}]})delimiter"; +constexpr std::string_view nwb_file_part0 = + R"delimiter({"groups":[{"neurodata_type_def":"NWBFile","neurodata_type_inc":"NWBContainer","name":"root","doc":"An NWB file storing cellular-based neurophysiology data from a single experimental session.","attributes":[{"name":"nwb_version","dtype":"text","value":"2.7.0-alpha","doc":"File version string. Use semantic versioning, e.g. 1.2.1. This will be the name of the format with trailing major, minor and patch numbers."}],"datasets":[{"name":"file_create_date","dtype":"isodatetime","dims":["num_modifications"],"shape":[null],"doc":"A record of the date the file was created and of subsequent modifications. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted strings: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds. The file can be created after the experiment was run, so this may differ from the experiment start time. Each modification to the nwb file adds a new entry to the array."},{"name":"identifier","dtype":"text","doc":"A unique text identifier for the file. For example, concatenated lab name, file creation date/time and experimentalist, or a hash of these and/or other values. The goal is that the string should be unique to all other files."},{"name":"session_description","dtype":"text","doc":"A description of the experimental session and data in the file."},{"name":"session_start_time","dtype":"isodatetime","doc":"Date and time of the experiment/session start. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted string: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds."},{"name":"timestamps_reference_time","dtype":"isodatetime","doc":"Date and time corresponding to time zero of all timestamps. The date is stored in UTC with local timezone offset as ISO 8601 extended formatted string: 2018-09-28T14:43:54.123+02:00. Dates stored in UTC end in \"Z\" with no timezone offset. Date accuracy is up to milliseconds. All times stored in the file use this time as reference (i.e., time zero)."}],"groups":[{"name":"acquisition","doc":"Data streams recorded from the system, including ephys, ophys, tracking, etc. This group should be read-only after the experiment is completed and timestamps are corrected to a common timebase. The data stored here may be links to raw data stored in external NWB files. This will allow keeping bulky raw data out of the file while preserving the option of keeping some/all in the file. Acquired data includes tracking and experimental data streams (i.e., everything measured from the system). If bulky data is stored in the /acquisition group, the data can exist in a separate NWB file that is linked to by the file being used for processing and analysis.","groups":[{"neurodata_type_inc":"NWBDataInterface","doc":"Acquired, raw data.","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Tabular data that is relevant to acquisition","quantity":"*"}]},{"name":"analysis","doc":"Lab-specific and custom scientific analysis of data. There is no defined format for the content of this group - the format is up to the individual user/lab. To facilitate sharing analysis data between labs, the contents here should be stored in standard types (e.g., neurodata_types) and appropriately documented. The file can store lab-specific and custom data analysis without restriction on its form or schema, reducing data formatting restrictions on end users. Such data should be placed in the analysis group. The analysis data should be documented so that it could be shared with other labs.","groups":[{"neurodata_type_inc":"NWBContainer","doc":"Custom analysis results.","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Tabular data that is relevant to data stored in analysis","quantity":"*"}]},{"name":"scratch","doc":"A place to store one-off analysis results. Data placed here is not intended for sharing. By placing data here, users acknowledge that there is no guarantee that their data meets any standard.","quantity":"?","groups":[{"neurodata_type_inc":"NWBContainer","doc":"Any one-off containers","quantity":"*"},{"neurodata_type_inc":"DynamicTable","doc":"Any one-off tables","quantity":"*"}],"datasets":[{"neurodata_type_inc":"ScratchData","doc":"Any one-off datasets","quantity":"*"}]},{"name":"processing","doc":"The home for ProcessingModules. These modules perform intermediate analysis of data that is necessary to perform before scientific analysis. Examples include spike clustering, extracting position from tracking data, stitching together image slices. ProcessingModules can be large and express many data sets from relatively complex analysis (e.g., spike detection and clustering) or small, representing extraction of position information from tracking video, or even binary lick/no-lick decisions. Common software tools (e.g., klustakwik, MClust) are expected to read/write data here. 'Processing' refers to intermediate analysis of the acquired data to make it more amenable to scientific analysis.","groups":[{"neurodata_type_inc":"ProcessingModule","doc":"Intermediate analysis of acquired data.","quantity":"*"}]},{"name":"stimulus","doc":"Data pushed into the system (eg, video stimulus, sound, voltage, etc) and secondary representations of that data (eg, measurements of something used as a stimulus). This group should be made read-only after experiment complete and timestamps are corrected to common timebase. Stores both presented stimuli and stimulus templates, the latter in case the same stimulus is presented multiple times, or is pulled from an external stimulus library. Stimuli are here defined as any signal that is pushed into the system as part of the experiment (eg, sound, video, voltage, etc). Many different experiments can use the same stimuli, and stimuli can be re-used during an experiment. The stimulus group is organized so that one version of template stimuli can be stored and these be used multiple times. These templates can exist in the present file or can be linked to a remote library file.","groups":[{"name":"presentation","doc":"Stimuli presented during the experiment.","groups":[{"neurodata_type_inc":"TimeSeries","doc":"TimeSeries objects containing data of presented stimuli.","quantity":"*"}]},{"name":"templates","doc":"Template stimuli. Timestamps in templates are based on stimulus design and are relative to the beginning of the stimulus. When templates are used, the stimulus instances must convert presentation times to the experiment`s time reference frame.","groups":[{"neurodata_type_inc":"TimeSeries","doc":"TimeSeries objects containing template data of presented stimuli.","quantity":"*"},{"neurodata_type_inc":"Images","doc":"Images objects containing images of presented stimuli.","quantity":"*"}]}]},{"name":"general","doc":"Experimental metadata, including protocol, notes and description of hardware device(s). The metadata stored in this section should be used to describe the experiment. Metadata necessary for interpreting the data is stored with the data. General experimental metadata, including animal strain, experimental protocols, experimenter, devices, etc, are stored under 'general'. Core metadata (e.g., that required to interpret data fields) is stored with the data itself, and implicitly defined by the file specification (e.g., time is in seconds). The strategy used here for storing non-core metadata is to use free-form text fields, such as would appear in sentences or paragraphs from a Methods section. Metadata fields are text to enable them to be more general, for example to represent ranges instead of numerical values. Machine-readable metadata is stored as attributes to these free-form datasets. All entries in the below table are to be included when data is present. Unused groups (e.g., intracellular_ephys in an optophysiology experiment) should not be created unless there is data to store within them.","datasets":[{"name":"data_collection","dtype":"text","doc":"Notes about data collection and analysis.","quantity":"?"},{"name":"experiment_description","dtype":"text","doc":"General description of the experiment.","quantity":"?"},{"name":"experimenter","dtype":"text","doc":"Name of person(s) who performed the experiment. Can also specify roles of different people involved.","quantity":"?","dims":["num_experimenters"],"shape":[null]},{"name":"institution","dtype":"text","doc":"Institution(s) where experiment was performed.","quantity":"?"},{"name":"keywords","dtype":"text","dims":["num_keywords"],"shape":[null],"doc":"Terms to search over.","quantity":"?"},{"name":"lab","dtype":"text","doc":"Laboratory where experiment was performed.","quantity":"?"},{"name":"notes","dtype":"text","doc":"Notes about the experiment.","quantity":"?"},{"name":"pharmacology","dtype":"text","doc":"Description of drugs used, including how and when they were administered. Anesthesia(s), painkiller(s), etc., plus dosage, concentration, etc.","quantity":"?"},{"name":"protocol","dtype":"text","doc":"Experimental protocol, if applicable. e.g., include IACUC protocol number.","quantity":"?"},{"name":"related_publications","dtype":"text","doc":"Publication information. PMID, DOI, URL, etc.","dims":["num_publications"],"shape":[null],"quantity":"?"},{"name":"session_id","dtype":"text","doc":"Lab-specific ID for the session.","quantity":"?"},{"name":"slices","dtype":"text","doc":"Description of slices, including information about preparation thickness, orientation, temperature, and bath solution.","quantity":"?"},{"name":"source_script","dtype":"text","doc":"Script file or link to public source code used to create this NWB file.","quantity":"?","attributes":[{"name":"file_name","dtype":"text","doc":"Name of script file."}]},{"name":"stimulus","dtype":"text","doc":"Notes about stimuli, such as how and where they were presented.","quantity":"?"},{"name":"surgery","dtype":"text","doc":"Narrative description about surgery/surgeries, including date(s) and who performed surgery.","quantity":"?"},{"name":"virus","dtype":"text","doc":"Information about virus(es) used in experiments, including virus ID, source, date made, injection location, volume, etc.","quantity":"?"}],"groups":[{"neurodata_type_inc":"LabMetaData","doc":"Place-holder than can be extended so that lab-specific meta-data can be placed in /general.","quantity":"*"},{"name":"devices","doc":"Description of hardware devices used during experiment, e.g., monitors, ADC boards, microscopes, etc.","quantity":"?","groups":[{"neurodata_type_inc":"Device","doc":"Data acquisition devices.","quantity":"*"}]},{"name":"subject","neurodata_type_inc":"Subject","doc":"Information about the animal or person from which the data was measured.","quantity":"?"},{"name":"extracellular_ephys","doc":"Metadata related to extracellular electrophysiology.","quantity":"?","groups":[{"neurodata_type_inc":"ElectrodeGroup","doc":"Physical group of electrodes.","quantity":"*"},{"name":"electrodes","neurodata_type_inc":"DynamicTable","doc":"A table of all electrodes (i.e. channels) used for recording.","quantity":"?","datasets":[{"name":"x","neurodata_type_inc":"VectorData","dtype":"float32","doc":"x coordinate of the channel location in the brain (+x is posterior).","quantity":"?"},{"name":"y","neurodata_type_inc":"VectorData","dtype":"float32","doc":"y coordinate of the channel location in the brain (+y is inferior).","quantity":"?"},{"name":"z","neurodata_type_inc":"VectorData","dtype":"float32","doc":"z coordinate of the channel location in the brain (+z is right).","quantity":"?"},{"name":"imp","neurodata_type_inc":"VectorData","dtype":"float32","doc":"Impedance of the channel, in ohms.","quantity":"?"},{"name":"location","neurodata_type_inc":"VectorData","dtype":"text","doc":"Location of the electrode (channel). Specify the area, layer, comments on estimation of area/layer, stereotaxic coordinates if in vivo, etc. Use standard atlas names for anatomical regions when possible."},{"name":"filtering","neurodata_type_inc":"VectorData","dtype":"text","doc":"Description of hardware filtering, including the filter name and frequency cutoffs.","quantity":"?"},{"name":"group","neurodata_type_inc":"VectorData","dtype":{"target_type":"ElectrodeGroup","reftype":"object"},"doc":"Reference to the ElectrodeGroup this electrode is a part of."},{"name":"group_name","neurodata_type_inc":"VectorData","dtype":"text","doc":"Name of the ElectrodeGroup this electrode is a part of."},{"name":"rel_x","neurodata_type_inc":"VectorData","dtype":"float32","doc":"x coordinate in electrode group","quantity":"?"},{"name":"rel_y","neurodata_type_inc":"VectorData","dtype":"float32","doc":"y coordinate in electrode group","quantity":"?"},{"name":"rel_z","neurodata_type_inc":"VectorData","dtype":"float32","doc":"z coordinate in electrode group","quantity":"?"},{"name":"reference","neurodata_type_inc":"VectorData","dtype":"text","doc":"Description of the reference electrode and/or reference scheme used for this electrode, e.g., \"stainless steel skull screw\" or \"online common average referencing\".","quantity":"?"}]}]},{"name":"intracellular_ephys","doc":"Metadata related to intracellular electrophysiology.","quantity":"?","datasets":[{"name":"filtering","dtype":"text","doc":"[DEPRECATED] Use IntracellularElectrode.filtering instead. Description of filtering used. Includes filtering type and parameters, frequency fall-off, etc. If this changes between TimeSeries, filter description should be stored as a text attribute for each TimeSeries.","quantity":"?"}],"groups":[{"neurodata_type_inc":"IntracellularElectrode","doc":"An intracellular electrode.","quantity":"*"},{"name":"sweep_table","neurodata_type_inc":"SweepTable","doc":"[DEPRECATED] Table used to group different PatchClampSeries. SweepTable is being replaced by IntracellularRecordingsTable and SimultaneousRecordingsTable tables. Additional SequentialRecordingsTable, RepetitionsTable and ExperimentalConditions tables provide enhanced support for experiment metadata.","quantity":"?"},{"name":"intracellular_recordings","neurodata_type_inc":"IntracellularRecordingsTable","doc":"A table to group together a stimulus and response from a single electrode and a single simultaneous recording. Each row in the table represents a single recording consisting typically of a stimulus and a corresponding response. In some cases, however, only a stimulus or a response are recorded as as part of an experiment. In this case both, the stimulus and response will point to the same TimeSeries while the idx_start and count of the invalid column will be set to -1, thus, indicating that no values have been recorded for the stimulus or response, respectively. Note, a recording MUST contain at least a stimulus or a response. Typically the stimulus and response are PatchClampSeries. However, the use of AD/DA channels that are not associated to an electrode is also common in intracellular electrophysiology, in which case other TimeSeries may be used.","quantity":"?"},{"name":"simultaneous_recordings","neurodata_type_inc":"SimultaneousRecordingsTable","doc":"A table for grouping different intracellular recordings from the IntracellularRecordingsTable table together that were recorded simultaneously from different electrodes","quantity":"?"},{"name":"sequential_recordings","neurodata_type_inc":"SequentialRecordingsTable","doc":"A table for grouping different sequential recordings from the SimultaneousRecordingsTable table together. This is typically used to group together sequential recordings where the a sequence of stimuli of the same type with varying parameters have been presented in a sequence.","quantity":"?"},{"name":"repetitions","neurodata_type_inc":"RepetitionsTable","doc":"A table for grouping different sequential intracellular recordings together. With each SequentialRecording typically representing a particular type of stimulus, the RepetitionsTable )delimiter"; +constexpr std::string_view nwb_file_part1 = + R"delimiter(table is typically used to group sets of stimuli applied in sequence.","quantity":"?"},{"name":"experimental_conditions","neurodata_type_inc":"ExperimentalConditionsTable","doc":"A table for grouping different intracellular recording repetitions together that belong to the same experimental experimental_conditions.","quantity":"?"}]},{"name":"optogenetics","doc":"Metadata describing optogenetic stimuluation.","quantity":"?","groups":[{"neurodata_type_inc":"OptogeneticStimulusSite","doc":"An optogenetic stimulation site.","quantity":"*"}]},{"name":"optophysiology","doc":"Metadata related to optophysiology.","quantity":"?","groups":[{"neurodata_type_inc":"ImagingPlane","doc":"An imaging plane.","quantity":"*"}]}]},{"name":"intervals","doc":"Experimental intervals, whether that be logically distinct sub-experiments having a particular scientific goal, trials (see trials subgroup) during an experiment, or epochs (see epochs subgroup) deriving from analysis of data.","quantity":"?","groups":[{"name":"epochs","neurodata_type_inc":"TimeIntervals","doc":"Divisions in time marking experimental stages or sub-divisions of a single recording session.","quantity":"?"},{"name":"trials","neurodata_type_inc":"TimeIntervals","doc":"Repeated experimental events that have a logical grouping.","quantity":"?"},{"name":"invalid_times","neurodata_type_inc":"TimeIntervals","doc":"Time intervals that should be removed from analysis.","quantity":"?"},{"neurodata_type_inc":"TimeIntervals","doc":"Optional additional table(s) for describing other experimental time intervals.","quantity":"*"}]},{"name":"units","neurodata_type_inc":"Units","doc":"Data about sorted spike units.","quantity":"?"}]},{"neurodata_type_def":"LabMetaData","neurodata_type_inc":"NWBContainer","doc":"Lab-specific meta-data."},{"neurodata_type_def":"Subject","neurodata_type_inc":"NWBContainer","doc":"Information about the animal or person from which the data was measured.","datasets":[{"name":"age","dtype":"text","doc":"Age of subject. Can be supplied instead of 'date_of_birth'.","quantity":"?","attributes":[{"name":"reference","doc":"Age is with reference to this event. Can be 'birth' or 'gestational'. If reference is omitted, 'birth' is implied.","dtype":"text","required":false,"default_value":"birth"}]},{"name":"date_of_birth","dtype":"isodatetime","doc":"Date of birth of subject. Can be supplied instead of 'age'.","quantity":"?"},{"name":"description","dtype":"text","doc":"Description of subject and where subject came from (e.g., breeder, if animal).","quantity":"?"},{"name":"genotype","dtype":"text","doc":"Genetic strain. If absent, assume Wild Type (WT).","quantity":"?"},{"name":"sex","dtype":"text","doc":"Gender of subject.","quantity":"?"},{"name":"species","dtype":"text","doc":"Species of subject.","quantity":"?"},{"name":"strain","dtype":"text","doc":"Strain of subject.","quantity":"?"},{"name":"subject_id","dtype":"text","doc":"ID of animal/person used/participating in experiment (lab convention).","quantity":"?"},{"name":"weight","dtype":"text","doc":"Weight at time of experiment, at time of surgery and at other important times.","quantity":"?"}]}],"datasets":[{"neurodata_type_def":"ScratchData","neurodata_type_inc":"NWBData","doc":"Any one-off datasets","attributes":[{"name":"notes","doc":"Any notes the user has about the dataset being stored","dtype":"text"}]}]})delimiter"; +constexpr std::array nwb_file_parts = {nwb_file_part0, + nwb_file_part1}; +constexpr std::size_t nwb_file_total_length = []() +{ + std::size_t length = 0; + for (const auto& part : nwb_file_parts) { + length += part.size(); + } + return length; +}(); +constexpr auto nwb_file_concatenate = []() +{ + std::array result {}; + std::size_t pos = 0; + for (const auto& part : nwb_file_parts) { + for (char c : part) { + result[pos++] = c; + } + } + result[pos] = '\0'; + return result; +}(); +constexpr std::string_view nwb_file(nwb_file_concatenate.data(), + nwb_file_concatenate.size() - 1); constexpr std::string_view nwb_misc = R"delimiter( {"groups":[{"neurodata_type_def":"AbstractFeatureSeries","neurodata_type_inc":"TimeSeries","doc":"Abstract features, such as quantitative descriptions of sensory stimuli. The TimeSeries::data field is a 2D array, storing those features (e.g., for visual grating stimulus this might be orientation, spatial frequency and contrast). Null stimuli (eg, uniform gray) can be marked as being an independent feature (eg, 1.0 for gray, 0.0 for actual stimulus) or by storing NaNs for feature values, or through use of the TimeSeries::control fields. A set of features is considered to persist until the next set of features is defined. The final set of features stored should be the null set. This is useful when storing the raw stimulus is impractical.","datasets":[{"name":"data","dtype":"numeric","dims":[["num_times"],["num_times","num_features"]],"shape":[[null],[null,null]],"doc":"Values of each feature at each time.","attributes":[{"name":"unit","dtype":"text","default_value":"see 'feature_units'","doc":"Since there can be different units for different features, store the units in 'feature_units'. The default value for this attribute is \"see 'feature_units'\".","required":false}]},{"name":"feature_units","dtype":"text","dims":["num_features"],"shape":[null],"doc":"Units of each feature.","quantity":"?"},{"name":"features","dtype":"text","dims":["num_features"],"shape":[null],"doc":"Description of the features represented in TimeSeries::data."}]},{"neurodata_type_def":"AnnotationSeries","neurodata_type_inc":"TimeSeries","doc":"Stores user annotations made during an experiment. The data[] field stores a text array, and timestamps are stored for each annotation (ie, interval=1). This is largely an alias to a standard TimeSeries storing a text array but that is identifiable as storing annotations in a machine-readable way.","datasets":[{"name":"data","dtype":"text","dims":["num_times"],"shape":[null],"doc":"Annotations made during an experiment.","attributes":[{"name":"resolution","dtype":"float32","value":-1.0,"doc":"Smallest meaningful difference between values in data. Annotations have no units, so the value is fixed to -1.0."},{"name":"unit","dtype":"text","value":"n/a","doc":"Base unit of measurement for working with the data. Annotations have no units, so the value is fixed to 'n/a'."}]}]},{"neurodata_type_def":"IntervalSeries","neurodata_type_inc":"TimeSeries","doc":"Stores intervals of data. The timestamps field stores the beginning and end of intervals. The data field stores whether the interval just started (>0 value) or ended (<0 value). Different interval types can be represented in the same series by using multiple key values (eg, 1 for feature A, 2 for feature B, 3 for feature C, etc). The field data stores an 8-bit integer. This is largely an alias of a standard TimeSeries but that is identifiable as representing time intervals in a machine-readable way.","datasets":[{"name":"data","dtype":"int8","dims":["num_times"],"shape":[null],"doc":"Use values >0 if interval started, <0 if interval ended.","attributes":[{"name":"resolution","dtype":"float32","value":-1.0,"doc":"Smallest meaningful difference between values in data. Annotations have no units, so the value is fixed to -1.0."},{"name":"unit","dtype":"text","value":"n/a","doc":"Base unit of measurement for working with the data. Annotations have no units, so the value is fixed to 'n/a'."}]}]},{"neurodata_type_def":"DecompositionSeries","neurodata_type_inc":"TimeSeries","doc":"Spectral analysis of a time series, e.g. of an LFP or a speech signal.","datasets":[{"name":"data","dtype":"numeric","dims":["num_times","num_channels","num_bands"],"shape":[null,null,null],"doc":"Data decomposed into frequency bands.","attributes":[{"name":"unit","dtype":"text","default_value":"no unit","doc":"Base unit of measurement for working with the data. Actual stored values are not necessarily stored in these units. To access the data in these units, multiply 'data' by 'conversion'."}]},{"name":"metric","dtype":"text","doc":"The metric used, e.g. phase, amplitude, power."},{"name":"source_channels","neurodata_type_inc":"DynamicTableRegion","doc":"DynamicTableRegion pointer to the channels that this decomposition series was generated from.","quantity":"?"}],"groups":[{"name":"bands","neurodata_type_inc":"DynamicTable","doc":"Table for describing the bands that this series was generated from. There should be one row in this table for each band.","datasets":[{"name":"band_name","neurodata_type_inc":"VectorData","dtype":"text","doc":"Name of the band, e.g. theta."},{"name":"band_limits","neurodata_type_inc":"VectorData","dtype":"float32","dims":["num_bands","low, high"],"shape":[null,2],"doc":"Low and high limit of each band in Hz. If it is a Gaussian filter, use 2 SD on either side of the center."},{"name":"band_mean","neurodata_type_inc":"VectorData","dtype":"float32","dims":["num_bands"],"shape":[null],"doc":"The mean Gaussian filters, in Hz."},{"name":"band_stdev","neurodata_type_inc":"VectorData","dtype":"float32","dims":["num_bands"],"shape":[null],"doc":"The standard deviation of Gaussian filters, in Hz."}]}],"links":[{"name":"source_timeseries","target_type":"TimeSeries","doc":"Link to TimeSeries object that this data was calculated from. Metadata about electrodes and their position can be read from that ElectricalSeries so it is not necessary to store that information here.","quantity":"?"}]},{"neurodata_type_def":"Units","neurodata_type_inc":"DynamicTable","default_name":"Units","doc":"Data about spiking units. Event times of observed units (e.g. cell, synapse, etc.) should be concatenated and stored in spike_times.","datasets":[{"name":"spike_times_index","neurodata_type_inc":"VectorIndex","doc":"Index into the spike_times dataset.","quantity":"?"},{"name":"spike_times","neurodata_type_inc":"VectorData","dtype":"float64","doc":"Spike times for each unit in seconds.","quantity":"?","attributes":[{"name":"resolution","dtype":"float64","doc":"The smallest possible difference between two spike times. Usually 1 divided by the acquisition sampling rate from which spike times were extracted, but could be larger if the acquisition time series was downsampled or smaller if the acquisition time series was smoothed/interpolated and it is possible for the spike time to be between samples.","required":false}]},{"name":"obs_intervals_index","neurodata_type_inc":"VectorIndex","doc":"Index into the obs_intervals dataset.","quantity":"?"},{"name":"obs_intervals","neurodata_type_inc":"VectorData","dtype":"float64","dims":["num_intervals","start|end"],"shape":[null,2],"doc":"Observation intervals for each unit.","quantity":"?"},{"name":"electrodes_index","neurodata_type_inc":"VectorIndex","doc":"Index into electrodes.","quantity":"?"},{"name":"electrodes","neurodata_type_inc":"DynamicTableRegion","doc":"Electrode that each spike unit came from, specified using a DynamicTableRegion.","quantity":"?"},{"name":"electrode_group","neurodata_type_inc":"VectorData","dtype":{"target_type":"ElectrodeGroup","reftype":"object"},"doc":"Electrode group that each spike unit came from.","quantity":"?"},{"name":"waveform_mean","neurodata_type_inc":"VectorData","dtype":"float32","dims":[["num_units","num_samples"],["num_units","num_samples","num_electrodes"]],"shape":[[null,null],[null,null,null]],"doc":"Spike waveform mean for each spike unit.","quantity":"?","attributes":[{"name":"sampling_rate","dtype":"float32","doc":"Sampling rate, in hertz.","required":false},{"name":"unit","dtype":"text","value":"volts","doc":"Unit of measurement. This value is fixed to 'volts'.","required":false}]},{"name":"waveform_sd","neurodata_type_inc":"VectorData","dtype":"float32","dims":[["num_units","num_samples"],["num_units","num_samples","num_electrodes"]],"shape":[[null,null],[null,null,null]],"doc":"Spike waveform standard deviation for each spike unit.","quantity":"?","attributes":[{"name":"sampling_rate","dtype":"float32","doc":"Sampling rate, in hertz.","required":false},{"name":"unit","dtype":"text","value":"volts","doc":"Unit of measurement. This value is fixed to 'volts'.","required":false}]},{"name":"waveforms","neurodata_type_inc":"VectorData","dtype":"numeric","dims":["num_waveforms","num_samples"],"shape":[null,null],"doc":"Individual waveforms for each spike on each electrode. This is a doubly indexed column. The 'waveforms_index' column indexes which waveforms in this column belong to the same spike event for a given unit, where each waveform was recorded from a different electrode. The 'waveforms_index_index' column indexes the 'waveforms_index' column to indicate which spike events belong to a given unit. For example, if the 'waveforms_index_index' column has values [2, 5, 6], then the first 2 elements of the 'waveforms_index' column correspond to the 2 spike events of the first unit, the next 3 elements of the 'waveforms_index' column correspond to the 3 spike events of the second unit, and the next 1 element of the 'waveforms_index' column corresponds to the 1 spike event of the third unit. If the 'waveforms_index' column has values [3, 6, 8, 10, 12, 13], then the first 3 elements of the 'waveforms' column contain the 3 spike waveforms that were recorded from 3 different electrodes for the first spike time of the first unit. See https://nwb-schema.readthedocs.io/en/stable/format_description.html#doubly-ragged-arrays for a graphical representation of this example. When there is only one electrode for each unit (i.e., each spike time is associated with a single waveform), then the 'waveforms_index' column will have values 1, 2, ..., N, where N is the number of spike events. The number of electrodes for each spike event should be the same within a given unit. The 'electrodes' column should be used to indicate which electrodes are associated with each unit, and the order of the waveforms within a given unit x spike event should be in the same order as the electrodes referenced in the 'electrodes' column of this table. The number of samples for each waveform must be the same.","quantity":"?","attributes":[{"name":"sampling_rate","dtype":"float32","doc":"Sampling rate, in hertz.","required":false},{"name":"unit","dtype":"text","value":"volts","doc":"Unit of measurement. This value is fixed to 'volts'.","required":false}]},{"name":"waveforms_index","neurodata_type_inc":"VectorIndex","doc":"Index into the waveforms dataset. One value for every spike event. See 'waveforms' for more detail.","quantity":"?"},{"name":"waveforms_index_index","neurodata_type_inc":"VectorIndex","doc":"Index into the waveforms_index dataset. One value for every unit (row in the table). See 'waveforms' for more detail.","quantity":"?"}]}]})delimiter"; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad231678..ddf4fa90 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,7 +37,7 @@ target_link_libraries( Catch2::Catch2WithMain ) target_compile_features(aqnwb_test PRIVATE cxx_std_17) - +target_compile_definitions(aqnwb_test PRIVATE BUILD_CONFIG=\"$\") catch_discover_tests(aqnwb_test) # ---- Custom Executable ---- diff --git a/tests/examples/testWorkflowExamples.cpp b/tests/examples/testWorkflowExamples.cpp index 0a2eec5f..03b71d39 100644 --- a/tests/examples/testWorkflowExamples.cpp +++ b/tests/examples/testWorkflowExamples.cpp @@ -70,13 +70,17 @@ TEST_CASE("workflowExamples") for (const auto& channel : channelVector) { // copy data into buffer std::copy( - mockData[channel.getGlobalIndex()].begin() + samplesRecorded, - mockData[channel.getGlobalIndex()].begin() + samplesRecorded - + bufferSize, + mockData[channel.getGlobalIndex()].begin() + + static_cast(samplesRecorded), + mockData[channel.getGlobalIndex()].begin() + + static_cast(samplesRecorded + bufferSize), dataBuffer.begin()); - std::copy(mockTimestamps.begin() + samplesRecorded, - mockTimestamps.begin() + samplesRecorded + bufferSize, - timestampsBuffer.begin()); + std::copy( + mockTimestamps.begin() + + static_cast(samplesRecorded), + mockTimestamps.begin() + + static_cast(samplesRecorded + bufferSize), + timestampsBuffer.begin()); // write timeseries data std::vector positionOffset = {samplesRecorded, diff --git a/tests/reader.cpp b/tests/reader.cpp index 75b65c02..4ab9ded5 100644 --- a/tests/reader.cpp +++ b/tests/reader.cpp @@ -1,9 +1,10 @@ +#include #include #include +#include #include #include // Include HDF5 headers -#include // for sleep using namespace H5; @@ -26,12 +27,13 @@ int readerFunction(const std::string& path, const std::string& dataPath) // Update the size dsetSizes.push_back(currentSize); - sleep(1); // Simulate real-time data streaming + std::this_thread::sleep_for(std::chrono::seconds(1)); + // Simulate real-time data streaming } // print out dataset sizes std::cout << "Dataset sizes: "; - for (int val : dsetSizes) { + for (hsize_t val : dsetSizes) { std::cout << val << " "; } std::cout << std::endl; @@ -41,14 +43,14 @@ int readerFunction(const std::string& path, const std::string& dataPath) if (dsetSizes[0] >= dsetSizes[2]) { return -1; } - } catch (const FileIException& error) { + } catch (const FileIException&) { return -1; } return 0; } -int main(int argc, char* argv[]) +int main(int /*argc*/, char* argv[]) { std::string path = argv[1]; std::string dataPath = argv[2]; diff --git a/tests/testEcephys.cpp b/tests/testEcephys.cpp index 194bea18..74271677 100644 --- a/tests/testEcephys.cpp +++ b/tests/testEcephys.cpp @@ -112,12 +112,17 @@ TEST_CASE("ElectricalSeries", "[ecephys]") SizeType samplesRecorded = 0; for (SizeType b = 0; b * bufferSize < numSamples; b += 1) { // copy chunk of data - std::copy(mockData[ch].begin() + samplesRecorded, - mockData[ch].begin() + samplesRecorded + bufferSize, - dataBuffer.begin()); - std::copy(mockTimestamps.begin() + samplesRecorded, - mockTimestamps.begin() + samplesRecorded + bufferSize, - timestampsBuffer.begin()); + std::copy( + mockData[ch].begin() + static_cast(samplesRecorded), + mockData[ch].begin() + + static_cast(samplesRecorded + bufferSize), + dataBuffer.begin()); + std::copy( + mockTimestamps.begin() + + static_cast(samplesRecorded), + mockTimestamps.begin() + + static_cast(samplesRecorded + bufferSize), + timestampsBuffer.begin()); es.writeChannel( ch, dataBuffer.size(), dataBuffer.data(), timestampsBuffer.data()); @@ -209,7 +214,6 @@ TEST_CASE("SpikeEventSeries", "[ecephys]") H5::DataSpace fSpace = dataset->getSpace(); hsize_t dims[3]; fSpace.getSimpleExtentDims(dims, NULL); - hsize_t memdims = dims[0] * dims[1] * dims[2]; dataset->read(buffer, H5::PredType::NATIVE_FLOAT, fSpace, fSpace); for (SizeType i = 0; i < numEvents; ++i) { @@ -267,7 +271,6 @@ TEST_CASE("SpikeEventSeries", "[ecephys]") H5::DataSpace fSpace = dataset->getSpace(); hsize_t dims[3]; fSpace.getSimpleExtentDims(dims, NULL); - hsize_t memdims = dims[0] * dims[1] * dims[2]; dataset->read(buffer, H5::PredType::NATIVE_FLOAT, fSpace, fSpace); for (SizeType i = 0; i < numEvents; ++i) { diff --git a/tests/testHDF5IO.cpp b/tests/testHDF5IO.cpp index 4e94cd46..b556cf28 100644 --- a/tests/testHDF5IO.cpp +++ b/tests/testHDF5IO.cpp @@ -17,6 +17,15 @@ #include "nwb/file/ElectrodeTable.hpp" #include "testUtils.hpp" +// Get the current working directory +std::filesystem::path currentPath = std::filesystem::current_path(); +#ifdef _WIN32 +std::string executablePath = + (currentPath / BUILD_CONFIG / "reader_executable.exe").string(); +#else +std::string executablePath = "./reader_executable"; +#endif + using namespace AQNWB; namespace fs = std::filesystem; @@ -309,9 +318,14 @@ TEST_CASE("SWMRmode", "[hdf5io]") SECTION("useSWMRMODE") { // create and open file +<<<<<<< HEAD std::string path = getTestFilePath("testSWMRmode.h5"); std::unique_ptr hdf5io = std::make_unique(path); +======= + std::string path = getTestFilePath("testSWMRmodeEnable.h5"); + std::unique_ptr hdf5io = std::make_unique(path); +>>>>>>> main hdf5io->open(); // add a dataset @@ -323,7 +337,9 @@ TEST_CASE("SWMRmode", "[hdf5io]") BaseDataType::I32, SizeArray {0}, SizeArray {1}, dataPath); // try to read the file before starting SWMR mode - std::string command = "./reader_executable " + path + " " + dataPath; + std::string command = executablePath + " " + path + " " + dataPath; + std::cout << "Executing command: " << command << std::endl; + int retPreSWMREnabled = std::system(command.c_str()); REQUIRE(retPreSWMREnabled != 0); // process should fail if SWMR mode is not enabled @@ -339,6 +355,10 @@ TEST_CASE("SWMRmode", "[hdf5io]") std::thread readerThread( [](const std::string& cmd, std::promise promise) { +#ifdef _WIN32 + // required on Windows to allow writer process to access file + _putenv_s("HDF5_USE_FILE_LOCKING", "FALSE"); +#endif int ret = std::system(cmd.c_str()); promise.set_value(ret); }, @@ -383,9 +403,15 @@ TEST_CASE("SWMRmode", "[hdf5io]") SECTION("disableSWMRMode") { // create and open file with SWMR mode disabled +<<<<<<< HEAD std::string path = getTestFilePath("testSWMRmode.h5"); std::unique_ptr hdf5io = std::make_unique(path, true); +======= + std::string path = getTestFilePath("testSWMRmodeDisable.h5"); + std::unique_ptr hdf5io = + std::make_unique(path, true); +>>>>>>> main hdf5io->open(); // add a dataset diff --git a/tests/testNWBFile.cpp b/tests/testNWBFile.cpp index 4fa662cf..97ee2d88 100644 --- a/tests/testNWBFile.cpp +++ b/tests/testNWBFile.cpp @@ -123,6 +123,7 @@ TEST_CASE("createMultipleEcephysDatasets", "[nwb]") mockSpikeChannelNames, BaseDataType::F32, recordingContainers.get()); + REQUIRE(resultCreateSES == Status::Success); // start recording Status resultStart = io->startRecording(); diff --git a/tests/testRecordingWorkflow.cpp b/tests/testRecordingWorkflow.cpp index b834c1ff..76db521c 100644 --- a/tests/testRecordingWorkflow.cpp +++ b/tests/testRecordingWorkflow.cpp @@ -65,13 +65,17 @@ TEST_CASE("writeContinuousData", "[recording]") for (const auto& channel : channelVector) { // copy data into buffer std::copy( - mockData[channel.getGlobalIndex()].begin() + samplesRecorded, - mockData[channel.getGlobalIndex()].begin() + samplesRecorded - + bufferSize, + mockData[channel.getGlobalIndex()].begin() + + static_cast(samplesRecorded), + mockData[channel.getGlobalIndex()].begin() + + static_cast(samplesRecorded + bufferSize), dataBuffer.begin()); - std::copy(mockTimestamps.begin() + samplesRecorded, - mockTimestamps.begin() + samplesRecorded + bufferSize, - timestampsBuffer.begin()); + std::copy( + mockTimestamps.begin() + + static_cast(samplesRecorded), + mockTimestamps.begin() + + static_cast(samplesRecorded + bufferSize), + timestampsBuffer.begin()); // write timeseries data std::vector positionOffset = {samplesRecorded,