Creating an extension
Using ndx-template
Extensions should be created in their own repository, not alongside data conversion code. This facilitates sharing and editing of the extension separately from the code that uses it. When starting a new extension, we highly recommend using the ndx-template repository, which automatically generates a repository with the appropriate directory structure.
After you finish the instructions here, you should have a directory structure that looks like this
├── LICENSE.txt
├── MANIFEST.in
├── NEXTSTEPS.md
├── README.md
├── docs
│ ├── Makefile
│ ├── README.md
│ ├── make.bat
│ └── source
│ ├── _static
│ │ └── theme_overrides.css
│ ├── conf.py
│ ├── conf_doc_autogen.py
│ ├── credits.rst
│ ├── description.rst
│ ├── format.rst
│ ├── index.rst
│ └── release_notes.rst
├── requirements.txt
├── setup.cfg
├── setup.py
├── spec
│ ├── ndx-example.extensions.yaml
│ └── ndx-example.namespace.yaml
└── src
├── matnwb
│ └── README.md
├── pynwb
│ ├── README.md
│ ├── ndx_example
│ │ └── __init__.py
│ └── tests
│ ├── __init__.py
│ └── test_tetrodeseries.py
└── spec
└── create_extension_spec.py
At its core, an NWB extension consists of YAML text files, such as those generated in the spec
folder. While you can write these YAML extension files by hand, PyNWB provides a convenient API
via the spec
module for creating extensions.
Open src/spec/create_extension_spec.py
. You will be
modifying this script to create your own NWB extension. Let’s first walk through each piece.
Creating a namespace
NWB organizes types into namespaces. You must define a new namespace before creating any new types. After following
the instructions from the ndx-template, you should have a file
ndx-my-ext/src/spec/create_extension_spec.py
. The beginning of this file should look like
from pynwb.spec import NWBNamespaceBuilder, export_spec, NWBGroupSpec, NWBAttributeSpec
# TODO: import the following spec classes as needed
# from pynwb.spec import NWBDatasetSpec, NWBLinkSpec, NWBDtypeSpec, NWBRefSpec
def main():
# these arguments were auto-generated from your cookiecutter inputs
ns_builder = NWBNamespaceBuilder(
doc="my description",
name="ndx-my-ext",
version="0.1.0",
author="John Doe",
contact="contact@gmail.com"
)
Here, after the initial imports, we are defining meta-data of the extension.
Pay particular attention to version
. If you make changes to your extension
after the initial release, you should increase the numbers in your version
number, so that you can keep track of what exact version of the extension was
used for each file. We recommend using a semantic versioning approach.
Including types
Next, we need to include types from the core schemas. This is analogous to importing classes in Python. The generated file includes some example imports.
ns_builder.include_type('ElectricalSeries', namespace='core')
ns_builder.include_type('TimeSeries', namespace='core')
ns_builder.include_type('NWBDataInterface', namespace='core')
ns_builder.include_type('NWBContainer', namespace='core')
ns_builder.include_type('DynamicTableRegion', namespace='hdmf-common')
ns_builder.include_type('VectorData', namespace='hdmf-common')
ns_builder.include_type('Data', namespace='hdmf-common')
Neuroscience-specific data types are defined in the namespace 'core'
(which means core NWB). More general organizational data types that are not
specific to neuroscience and are relevant across scientific fields are defined
in 'hdmf-common'
. You can see which types are defined in which namespace by
exploring the NWB schema documentation
and hdmf-common schema documentation <https://hdmf-common-schema.readthedocs.io/en/latest/>`_.
Defining new neurodata types
Next, the create_extension_spec.py
file declares an example extension
for a new neurodata type called TetrodeSeries
, which extends the ElectricalSeries
type. Then it creates a list of all new data types.
tetrode_series = NWBGroupSpec(
neurodata_type_def='TetrodeSeries',
neurodata_type_inc='ElectricalSeries',
doc=('An extension of ElectricalSeries to include the tetrode ID for '
'each time series.'),
attributes=[
NWBAttributeSpec(
name='trode_id',
doc='The tetrode ID.',
dtype='int32'
)
],
)
# TODO: add all of your new data types to this list
new_data_types = [tetrode_series]
The remainder of the file is to generate the YAML files from the spec definition, and should not be changed.
After you make changes to this file, you should run it to re-generate the ndx-[name].extensions.yaml
and
ndx-[name].namespace.yaml
files. In the next section, we will go into more detail into how to create neurodata
types.