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

Add read for neurodata_types, e.g., Container, TimeSeries #91

Open
wants to merge 81 commits into
base: add_read
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
0e8ef12
First draft of Container registry
oruebel Sep 8, 2024
eeaceae
Merge branch 'add_read' into add_container_read
oruebel Sep 8, 2024
e62164b
Register subtypes
oruebel Sep 8, 2024
e99feee
Move the registry to a new RegisteredType class to also cover non-Gro…
oruebel Sep 8, 2024
3683921
Fix formatting
oruebel Sep 8, 2024
20050c7
Ensure all neurodata_types are registered correctly
oruebel Sep 8, 2024
cf9c017
Add unittest for RegisteredTrype
oruebel Sep 8, 2024
be29fb6
Add developer docs for RegisteredTypes
oruebel Sep 8, 2024
779c2af
Fix name of the unit test file for RegisteredType
oruebel Sep 8, 2024
3179c4b
Add example code for RegisteredType
oruebel Sep 8, 2024
dbdd945
Use both namespace and type name to register and access subtypes
oruebel Sep 8, 2024
4a7197f
Update documentation to clarify the use of namespace::classname to ma…
oruebel Sep 8, 2024
d11870b
Update read test to use the registry to construct the class
oruebel Sep 8, 2024
7a64982
Minor dox fixes
oruebel Sep 8, 2024
b42065b
Fix getTypeName and getNamespace and use them instead of hard-coded v…
oruebel Sep 9, 2024
501bc32
Update docs for getTypeName add getNamespace
oruebel Sep 9, 2024
5d959bd
Merge branch 'add_read' into add_container_read
oruebel Sep 9, 2024
a1dc63d
Attempt to fix reading of string attribute
oruebel Sep 9, 2024
bba4a79
Fix read neurodata_type attribute read test
oruebel Sep 9, 2024
a58a44a
Add test for reading a TimeSeries Container back
oruebel Sep 9, 2024
3ac8ca4
Add RegisteredType::create method to read a type from file
oruebel Sep 9, 2024
5f0bdd6
Fix path ecephys read test
oruebel Sep 9, 2024
edc45cf
Change RegisteredType::create to return shared pointer to support cas…
oruebel Sep 9, 2024
7184f96
Update ecephys read example
oruebel Sep 9, 2024
bda279d
Updated read tutorial and example
oruebel Sep 9, 2024
db8c652
Add missing comment
oruebel Sep 9, 2024
cf06a19
Clarify read docs and rename workflow docs
oruebel Sep 9, 2024
b0a28f9
Merge branch 'add_read' into add_container_read
oruebel Sep 19, 2024
8bbecb8
Merge branch 'add_read' into add_container_read
oruebel Sep 19, 2024
b424eaf
Merge branch 'add_read' into add_container_read
oruebel Sep 20, 2024
8aa5680
Fix formatting after merge
oruebel Sep 20, 2024
5798145
Remove SpikeEventSeries.neurodata_type
oruebel Sep 20, 2024
8832b04
Register SpikeEventSeries as a type
oruebel Sep 20, 2024
f244d8c
Updated RegisterTypes to allow overwrite of the typename to use and o…
oruebel Sep 21, 2024
c78e7cb
Fix code formatting
oruebel Sep 21, 2024
9d7f408
Add missing description for DynamicTableRegion
oruebel Sep 21, 2024
435866c
Fix #109 Add missing axis attribute for channel_conversion and remove…
oruebel Sep 21, 2024
ce3227c
Remove undefined functions from Device
oruebel Sep 21, 2024
cc0cd84
Rename member variable in ElectricalSeries
oruebel Sep 21, 2024
f99ed91
Rename member variable in SpikeEventSeries
oruebel Sep 21, 2024
6a4876f
Added mergePaths functions and made functions in Utils.hpp static inline
oruebel Sep 21, 2024
af28b29
Updated ElectrodeTable to use mergePaths method
oruebel Sep 21, 2024
c222955
Updated DynamicTable to use mergePaths method
oruebel Sep 21, 2024
0d402f3
Update ElectrodeGroup to use mergePaths
oruebel Sep 21, 2024
156910c
Updated ElectricalSeries to use mergePaths
oruebel Sep 21, 2024
962faaa
Updated TimerSeries to use mergePaths function
oruebel Sep 21, 2024
8f13baf
Updated NWBFile to use the mergePaths function
oruebel Sep 21, 2024
bf92ce2
Updated ElectrodeTable static paths to not used trailing / for consis…
oruebel Sep 22, 2024
c2251a8
Fix Doxygen error by extracting also static members in the docs
oruebel Sep 22, 2024
f3e30cd
Fix section level in install.dox
oruebel Sep 22, 2024
cea343b
Update ReadDataWrapper to use m_ member naming convention
oruebel Sep 22, 2024
7c884d4
Updated DataBlock / DataBlockGeneric to avoid shadowing of constructo…
oruebel Sep 22, 2024
8145382
Fix reference error in read.dox
oruebel Sep 22, 2024
078c4f9
Add ReadDataWrapper isType, getPath, getIO methods and update docstrings
oruebel Sep 22, 2024
98377cb
Create macro DEFINE_FIELD to simplify creating access methods for fie…
oruebel Sep 22, 2024
281375c
Replace previous field access methods in TimeSeries with DEFINE_FIELD…
oruebel Sep 22, 2024
9eb00cf
Replace TimeSeries.dataLazy and resolutionLazy with DEFINE_FIELD defi…
oruebel Sep 22, 2024
90803a8
Update the Doxygen built to expand the DEFINE_FIELD macro to document…
oruebel Sep 22, 2024
4dfb541
Fix spellcheck
oruebel Sep 22, 2024
9b9c8c3
Fix section levels in read.dox
oruebel Sep 22, 2024
3c4b2d7
Add docs for using the DEFINE_FIELD macro
oruebel Sep 22, 2024
aad6928
Added attributeExists method on IO and ReadDataWrapper::exists method…
oruebel Sep 22, 2024
5a411c4
Add TimeSeries.descriptionLazy field to test that reading string attr…
oruebel Sep 22, 2024
daca8f2
Added function getGroupObjects to the I/O to allow gettings all objec…
oruebel Sep 22, 2024
1f81e6d
Add BaseIO::findTypes method to search for neurodata_types in a file
oruebel Sep 22, 2024
3affcca
Add example for searching for typed objects
oruebel Sep 23, 2024
30e1226
Update ElectrodeTable to use m_ member names
oruebel Sep 23, 2024
fa6a981
Added field definitions for TimeSeries
oruebel Sep 23, 2024
a0e02b2
Add some design docs about reading registered types
oruebel Sep 24, 2024
fbed92c
Update read types design figure
oruebel Sep 24, 2024
d4c2e64
Minor update to docs of RegisteredType
oruebel Sep 24, 2024
423f3d7
Apply suggestions on docs from code review
oruebel Oct 2, 2024
415926c
Merge branch 'add_read' into add_container_read
oruebel Oct 22, 2024
a93cadd
Fix build error due to error during merge with base branch
oruebel Oct 22, 2024
2554667
Apply suggestions from code review
oruebel Oct 22, 2024
6c57527
Fix read.dox figure based on suggestion
oruebel Oct 23, 2024
a7cf936
Fix docs based on code review
oruebel Oct 23, 2024
3d89d78
update getAttribute method to detect object type
stephprince Dec 12, 2024
c22d658
update read example
stephprince Dec 12, 2024
a91cc91
add file mode options and readonly mode to io
stephprince Dec 12, 2024
b20388c
update example to use readonly
stephprince Dec 12, 2024
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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ add_library(
src/io/hdf5/HDF5RecordingData.cpp
src/nwb/NWBFile.cpp
src/nwb/RecordingContainers.cpp
src/nwb/RegisteredType.cpp
src/nwb/base/TimeSeries.cpp
src/nwb/device/Device.cpp
src/nwb/ecephys/ElectricalSeries.cpp
src/nwb/ecephys/SpikeEventSeries.cpp
src/nwb/file/ElectrodeGroup.cpp
src/nwb/file/ElectrodeTable.cpp
src/nwb/hdmf/base/Container.cpp
src/nwb/hdmf/base/Data.cpp
src/nwb/hdmf/table/DynamicTable.cpp
src/nwb/hdmf/table/VectorData.cpp
src/nwb/hdmf/table/ElementIdentifiers.cpp
)

add_library(aqnwb::aqnwb ALIAS aqnwb_aqnwb)
Expand Down
16 changes: 16 additions & 0 deletions docs/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@
PROJECT_NAME = "@PROJECT_NAME@"
PROJECT_NUMBER = "@PROJECT_VERSION@"


# Expand the DEFINE_FIELD macro to create documentation also for functions created by
# this macro as part of the RegisterType.hpp logic, which simplifies the creation
# of functions for lazy read access. Unfortunately, expanding the macro directly
# from the source code directly for some reason did not work with Doxygen and
# using "EXPAND_AS_DEFINED = DEFINE_FIELD" also failed. As a workaround we here define
# a simplified version of the macro as part of the PREDEFINED key to create a
# simplified expansion of the macro for documentation purposes
MACRO_EXPANSION = YES
PREDEFINED += "DEFINE_FIELD(name, storageObjectType, default_type, fieldPath, description)=/** description */ template<typename VTYPE = default_type> inline std::unique_ptr<IO::ReadDataWrapper<storageObjectType, VTYPE>> name() const;"
EXPAND_ONLY_PREDEF = YES


# Add sources
INPUT = "@PROJECT_SOURCE_DIR@/src" "@PROJECT_SOURCE_DIR@/docs/pages"
RECURSIVE = YES
EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/tests" "@PROJECT_SOURCE_DIR@/.github/CODE_OF_CONDUCT.md" "@PROJECT_SOURCE_DIR@/Legal.txt" "@PROJECT_SOURCE_DIR@/LICENSE"
IMAGE_PATH = "@PROJECT_SOURCE_DIR@/resources/images"
EXTRACT_ALL = YES
Expand All @@ -16,6 +30,8 @@ OUTPUT_DIRECTORY = "@DOXYGEN_OUTPUT_DIRECTORY@"

# Also show private members in the docs,
EXTRACT_PRIVATE = YES
# Also show static members in the docs
EXTRACT_STATIC = YES
# HIDE_UNDOC_MEMBERS = YES

# Enable Markdown support
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/1_userdocs.dox
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
*
* - \subpage user_install_page
* - \subpage workflow
* - \subpage hdf5io
* - \subpage read_page
* - \subpage hdf5io
*/
1 change: 1 addition & 0 deletions docs/pages/2_devdocs.dox
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* - \subpage testing
* - \subpage dev_docs_page
* - \subpage nwb_schema_page
* - \subpage registered_type_page
* - \subpage code_of_conduct_page
* - \subpage license_page
* - \subpage copyright_page
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/devdocs/install.dox
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
* cmake --build --preset=dev --target=<name of the target>
* \endcode
*
* \subsubsection devbuild_target_options_subsubsec Target options
* \subsection devbuild_target_options_subsubsec Target options
* - `format-check`: run the `clang-format` tool on the codebase to check for formatting errors
* - `format-fix` : run the `clang-format` tool on the codebase with `FIX=YES` to both check and automatically fix for formatting errors
* - `spell-check`: run the `codespell` tool on the codebase to check for common spelling errors
Expand Down
208 changes: 208 additions & 0 deletions docs/pages/devdocs/registered_types.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
*
* \page registered_type_page Implementing a new Neurodata Type
*
* \tableofcontents
*
*
*
* New neurodata_types typically inherit from at least either \ref AQNWB::NWB::Container "Container"
* or \ref AQNWB::NWB::Data "Data", or a more specialized type of the two. In any case,
* all classes that represent a ``neurodata_type`` defined in the schema should be implemented
* as a subtype of \ref AQNWB::NWB::RegisteredType "RegisteredType".
*
* @section implement_registered_type How to Implement a RegisteredType
*
* To implement a subclass of \ref AQNWB::NWB::RegisteredType "RegisteredType", follow these steps:
*
* 1. Include the `RegisteredType.h` header file in your subclass header file.
oruebel marked this conversation as resolved.
Show resolved Hide resolved
* @code
* #include "nwb/RegisteredType.hpp"
* @endcode
*
* 2. Define your subclass by inheriting from \ref AQNWB::NWB::RegisteredType "RegisteredType".
* Ensure that your subclass implements a constructor with the arguments
* `(const std::string& path, std::shared_ptr<IO::BaseIO> io)`,
* as the "create" method expects this constructor signature.
* @code
* class MySubClass : public AQNWB::NWB::RegisteredType {
* public:
* MySubClass(const std::string& path, std::shared_ptr<IO::BaseIO> io)
* : RegisteredType(path, io) {}
*
* // Implement any additional methods or overrides here
* };
* @endcode
*
* 3. Use the \ref REGISTER_SUBCLASS macro to register your subclass. This should usually appear in
* the header (`hpp`) file as part of the class definition:
* @code
* REGISTER_SUBCLASS(MySubClass, "my-namespace")
* @endcode
*
* 4. In the corresponding source (`cpp`) file, initialize the static member to trigger the registration
* using the \ref REGISTER_SUBCLASS_IMPL macro:
* @code
* #include "MySubClass.h"
*
* // Initialize the static member to trigger registration
* REGISTER_SUBCLASS_IMPL(MySubClass)
* @endcode
*
* 5. To define getter methods for lazy read access to datasets and attributes that belong to our type,
* we can use the \ref DEFINE_FIELD macro. This macro creates a standard method for retrieving a
* \ref AQNWB::IO::ReadDataWrapper "ReadDataWrapper" for lazy reading for the field:
* @code
* DEFINE_FIELD(getData, DatasetField, float, "data", The main data)
* @endcode
*
* \warning
* To ensure proper function on read, the name of the class should match the name of the
* ``neurodata_type`` as defined in the schema. Similarly, "my-namespace" should match
* the name of the namespace in the schema (e.g., "core", "hdmf-common"). In this way
* we can look up the corresponding class for an object in a file based on the
* ``neurodata_type`` and ``namespace`` attributes stored in the file.
*
* \note
* A special version of the ``REGISTER_SUBCLASS`` macro, called ``REGISTER_SUBCLASS_WITH_TYPENAME``,
* allows setting the typename explicitly as a third argument. This is for the **special case**
* where we want to implement a class for a modified type that does not have its
* own `neurodata_type` in the NWB schema. An example is `ElectrodesTable` in NWB <v2.7, which
* did not have an assigned `neurodata_type`, but was implemented as a regular
* `DynamicTable`. To allow us to define a class `ElectrodeTable` to help with writing the table
oruebel marked this conversation as resolved.
Show resolved Hide resolved
* we can then use ``REGISTER_SUBCLASS_WITH_TYPENAME(ElectrodeTable, "core", "DynamicTable")``
* in the `ElectrodesTable` class. This ensures that the `neurodata_type` attribute is set
* correctly to `DynamicTable` on write instead of `ElectrodesTable`. However, on read this
* will by default not use the `ElectrodesTable` class but the regular `DynamicTable` class
* since that is what the schema is indicating to use. In the registry, the class will still
* be registered under the ``core::ElectrodesTable`` key, but with "DynamicTable" as the
* typename value and the `ElectrodesTable.getTypeName` automatic override returning
* the indicated typename instead of the classname.
*
*
* @subsection implement_registered_type_example Example: Implementing a new type
*
* *MySubClass.h*
* @code
* #pragma once
* #include "RegisteredType.h"
oruebel marked this conversation as resolved.
Show resolved Hide resolved
*
* class MySubClass : public AQNWB::NWB::RegisteredType
* {
* public:
* MySubClass(const std::string& path, std::shared_ptr<IO::BaseIO> io)
* : RegisteredType(path, io) {}
*
* DEFINE_FIELD(getData, DatasetField, float, "data", The main data)
*
* REGISTER_SUBCLASS(MySubClass, "my-namespace")
* };
* @endcode
*
* *MySubClass.cpp*
* @code
* #include "MySubClass.h"
*
* // Initialize the static member to trigger registration
* REGISTER_SUBCLASS_IMPL(MySubClass)
* @endcode
*
* @section type_registry How the Type Registry in RegisteredType Works
*
* The type registry in \ref AQNWB::NWB::RegisteredType "RegisteredType" allows for dynamic creation of registered subclasses by name. Here is how it works:
*
* 1. **Registry Storage**:
* - The registry is implemented using 1) an `std::unordered_set` to store subclass names (which can be
* accessed via \ref AQNWB::NWB::RegisteredType::getRegistry "getRegistry()") and
* 2) an `std::unordered_map` to store factory functions for creating instances of the subclasses
* (which can be accessed via \ref AQNWB::NWB::RegisteredType::getFactoryMap() "getFactoryMap()").
* The factory methods are the required constructor that uses the io and path as input.
* - These are defined as static members within the \ref AQNWB::NWB::RegisteredType "RegisteredType" class.
*
* 2. **Registration**:
* - The \ref AQNWB::NWB::RegisteredType::registerSubclass "registerSubclass" method is used to add a
* subclass name and its corresponding factory function to the registry.
* - This method is called via the `REGISTER_SUBCLASS` macro, which defines a static method (`registerSubclass()`)
* and static member (`registered_`) to trigger the registration when the subclass is loaded.
*
* 3. **Dynamic Creation**:
* - The \ref AQNWB::NWB::RegisteredType::create "create" method is used to create an instance of a registered subclass by name.
* - This method looks up the subclass name in the registry and calls the corresponding factory function to create an instance.
*
* 4. **Automatic Registration**:
* - The `REGISTER_SUBCLASS_IMPL` macro initializes the static member (`registered_`), which triggers the
* \ref AQNWB::NWB::RegisteredType::registerSubclass "registerSubclass" method
* and ensures that the subclass is registered when the program starts.
*
* 5. **Class Name and Namespace Retrieval**:
* - The \ref AQNWB::NWB::RegisteredType::getTypeName "getTypeName" and
* \ref AQNWB::NWB::RegisteredType::getNamespace "getNamespace" return the string name the
* class and namespace, respectively. The `REGISTER_SUBCLASS` macro implements and automatic
* override of the methods to ensure the appropriate type and namespace string are returned.
oruebel marked this conversation as resolved.
Show resolved Hide resolved
* These methods should, hence, not be manually overridden by subclasses, to ensure consistency
* in type identification.
*
*
* @section use_registered_type_registry How to Use the RegisteredType Registry
*
* The \ref AQNWB::NWB::RegisteredType "RegisteredType" registry allows for dynamic creation and management of registered subclasses. Here is how you can use it:
*
* 1. **Creating Instances Dynamically**:
* - Use the \ref AQNWB::NWB::RegisteredType::create "create" method to create an instance of a registered subclass by name.
* - This method takes the subclass name, path, and a shared pointer to the IO object as arguments. This
* illustrates how we can read a specific typed object in an NWB file.
* \snippet tests/examples/test_RegisteredType_example.cpp example_RegisterType_get_type_instance
*
* 2. **Retrieving Registered Subclass Names**:
* - Use the \ref AQNWB::NWB::RegisteredType::getRegistry "getRegistry" method to retrieve the
* set of registered subclass names.
* \snippet tests/examples/test_RegisteredType_example.cpp example_RegisterType_get_registered_names
*
* 3. **Retrieving the Factory Map**:
* - Use the \ref AQNWB::NWB::RegisteredType::getFactoryMap "getFactoryMap" method to retrieve
* the map of factory functions for creating instances of registered subclasses.
* \snippet tests/examples/test_RegisteredType_example.cpp example_RegisterType_get_registered_factories
oruebel marked this conversation as resolved.
Show resolved Hide resolved
*
* @subsection use_registered_type_registry_example Example: Using the type registry
*
* \snippet tests/examples/test_RegisteredType_example.cpp example_RegisterType_full
*
* @section use_the_define_field_macro How to Use the DEFINE_FIELD macro
*
* The \ref DEFINE_FIELD macro takes the following main inputs:
*
* * ``name``: The name of the function to generate.
* * ``storageObjectType`` : One of either \ref AQNWB::NWB::DatasetField "DatasetField" or
* \ref AQNWB::NWB::AttributeField "AttributeField" to define the type of storage object used
* to store the field.
* * ``default_type`` : The default data type to use. If not known, we can use ``std::any``.
* * ``fieldPath`` : Literal string with the relative path to the field within the schema of the
* respective neurodata_type. This is automatically being expanded at runtime to the full path.
* * ``description`` : Description of the field to include in the docstring for the docs
*
* All of these inputs are required. A typical example will look as follows:
*
* @code
* DEFINE_FIELD(getData, DatasetField, float, "data", The main data)
* @endcode
*
* The compiler will then expand this definition to create a new method
* called ``getData`` that will return a \ref AQNWB::IO::ReadDataWrapper "ReadDataWrapper"
* for lazy reading for the field. The corresponding expanded function will look something like:
*
* @code
* template<typename VTYPE = float>
* inline std::unique_ptr<IO::ReadDataWrapper<DatasetField, VTYPE>> getData() const
* {
* return std::make_unique<IO::ReadDataWrapper<DatasetField, VTYPE>>(
* m_io,
* AQNWB::mergePaths(m_path, fieldPath));
* }
* @endcode
*
* See \ref read_page for an example of how to use such methods (e.g.,
* \ref AQNWB::NWB::TimeSeries::readData "TimeSeries::readData" )
* for reading data fields from a file.
*
*
*/
Loading
Loading