Skip to content

Commit b5d5cb1

Browse files
bendichterpre-commit-ci[bot]CodyCBakerPhDoruebelstephprince
authored
add extension best practices (#338)
* add best practices from NeurodataWithoutBorders/nwb-overview#72 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update docs/best_practices/extensions.rst * Update conf.py * Update requirements-rtd.txt * fix underline length * fix bullet list * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * adjust language * fix external links * fix warning * fix warning * Update docs/best_practices/extensions.rst Co-authored-by: Cody Baker <[email protected]> * Update docs/best_practices/extensions.rst Co-authored-by: Cody Baker <[email protected]> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add css formatting for lists * update docs requirements * Update docs/best_practices/extensions.rst Co-authored-by: Oliver Ruebel <[email protected]> * Update docs/best_practices/extensions.rst Co-authored-by: Oliver Ruebel <[email protected]> * Update docs/best_practices/extensions.rst Co-authored-by: Oliver Ruebel <[email protected]> * add intersphinx links to nwb-schema page * add character approximation for attribute limit * fix minor typos and formatting --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Cody Baker <[email protected]> Co-authored-by: Oliver Ruebel <[email protected]> Co-authored-by: Steph Prince <[email protected]>
1 parent 6694c43 commit b5d5cb1

File tree

5 files changed

+199
-6
lines changed

5 files changed

+199
-6
lines changed

docs/_static/css/custom.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,19 @@ code.literal {
2929
color: #404040 !important;
3030
background-color: #fbfbfb !important;
3131
}
32+
33+
/* Restore bullet points for simple lists */
34+
ul.simple {
35+
list-style-type: disc !important;
36+
margin-left: 24px !important;
37+
margin-bottom: 24px !important;
38+
}
39+
40+
ul.simple li {
41+
list-style-type: disc !important;
42+
display: list-item !important;
43+
}
44+
45+
ul.simple li p {
46+
margin-bottom: 12px !important;
47+
}

docs/best_practices/extensions.rst

Lines changed: 179 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Extensions
22
==========
33

44
Extend the core NWB schema only when necessary. Extensions are an essential mechanism to integrate
5-
data with NWB that is otherwise not supported. However, we here need to consider that there are certain costs associated
5+
data with NWB that is otherwise not supported. However, we need to consider that there are certain costs associated
66
with extensions, *e.g.*, cost of creating, supporting, documenting, and maintaining new extensions and effort for users
77
to use and learn already-created extensions. As such, users should attempt to use core ``neurodata_types`` or
88
pre-existing extensions before creating new ones. :ref:`hdmf-schema:sec-dynamictable`, which are used throughout the
@@ -16,26 +16,121 @@ If an extension is required, tutorials for the process may be found through the
1616
It is also encouraged for extensions to contain their own check functions for their own best practices.
1717
See the :ref:`adding_custom_checks` section of the Developer Guide for how to do this.
1818

19+
Define new ``neurodata_types`` at the top-level (do not nest type definitions)
20+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1921

22+
Rather than nesting definitions of ``neurodata_types``, all new types should be defined
23+
at the top-level of the schema. To include a specific ``neurodata_type`` in another type
24+
use the ``neurodata_type_inc`` key instead. For example:
2025

21-
Use Existing Neurodata Types
22-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26+
.. tabs::
27+
28+
.. tab:: Do This
29+
30+
.. tabs::
31+
32+
.. code-tab:: py Python
33+
34+
from pynwb.spec import NWBGroupSpec
35+
36+
# Define the first type
37+
type1_ext = NWBGroupSpec(
38+
name='custom_type1',
39+
doc='Example extension type 1',
40+
neurodata_type_def='MyNewType1',
41+
neurodata_type_inc='LabMetaData',
42+
)
43+
44+
# Then define the second type and include the first type
45+
type2_ext = NWBGroupSpec(
46+
name='custom_type2',
47+
doc='Example extension type 2',
48+
neurodata_type_def='MyNewType2',
49+
groups=[NWBGroupSpec(neurodata_type_inc='MyNewType1',
50+
doc='Included group of type MyNewType1')]
51+
)
52+
53+
.. code-tab:: yaml YAML
54+
55+
groups:
56+
- neurodata_type_def: MyNewType1
57+
neurodata_type_inc: LabMetaData
58+
name: custom_type1
59+
doc: Example extension type 1
60+
- neurodata_type_def: MyNewType2
61+
neurodata_type_inc: NWBContainer
62+
name: custom_type2
63+
doc: Example extension type 2
64+
groups:
65+
- neurodata_type_inc: MyNewType1
66+
doc: Included group of type MyNewType1
67+
68+
.. tab:: Do NOT do this
69+
70+
.. tabs::
71+
72+
.. code-tab:: py Python
73+
74+
from pynwb.spec import NWBGroupSpec
75+
76+
# Do NOT define a new type via ``neurodata_type_def`` within the
77+
# definition of another type. Always define the types separately
78+
# and use ``neurodata_type_inc`` to include the type
79+
type2_ext = NWBGroupSpec(
80+
name='custom_type2',
81+
doc='Example extension type 2',
82+
neurodata_type_def='MyNewType2',
83+
groups=[NWBGroupSpec(
84+
name='custom_type1',
85+
doc='Example extension type 1',
86+
neurodata_type_def='MyNewType1',
87+
neurodata_type_inc='LabMetaData',
88+
)]
89+
)
90+
91+
.. code-tab:: yaml YAML
92+
93+
groups:
94+
- neurodata_type_def: MyNewType2
95+
neurodata_type_inc: NWBContainer
96+
name: custom_type2
97+
doc: Example extension type 2
98+
groups:
99+
- neurodata_type_def: MyNewType1
100+
neurodata_type_inc: LabMetaData
101+
name: custom_type1
102+
doc: Example extension type 1
103+
104+
Build on and reuse existing neurodata types
105+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23106

24107
When possible, use existing types when creating extensions either by creating new ``neurodata_types`` that inherit from
25108
existing ones, or by creating ``neurodata_types`` that contain existing ones. Building on existing types facilitates the
26109
reuse of existing functionality and interpretation of the data. If a community extension already exists that has a
27-
similar scope, it is preferable to use that extension rather than creating a new one.
110+
similar scope, it is preferable to use that extension rather than creating a new one. For example:
111+
112+
* Extend :ref:`nwb-schema:sec-TimeSeries` for storing timeseries data. NWB provides main types of :ref:`nwb-schema:sec-TimeSeries`
113+
and you should identify the most specific type of :ref:`nwb-schema:sec-TimeSeries` relevant for your use case
114+
(e.g., extend :ref:`nwb-schema:sec-ElectricalSeries` to define a new kind of electrical recording).
115+
* Extend :ref:`hdmf-schema:sec-dynamictable` to store tabular data.
116+
* Extend :ref:`nwb-schema:sec-TimeIntervals` to store specific annotations of intervals in time.
117+
28118

119+
Strive for backward compatible changes
120+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29121

30-
Provide Documentation
122+
NWB is already incorporated in many tools - proposing a change that will make already released NWB datasets non-compliant will cause a lot of confusion and will lead to significant cost to update codes.
123+
124+
125+
Provide documentation
31126
~~~~~~~~~~~~~~~~~~~~~
32127

33128
When creating extensions be sure to provide thorough, meaningful documentation as part of the extension specification.
34129
Explain all fields (groups, datasets, attributes, links etc.) and describe what they store and how they
35130
should be used.
36131

37132

38-
Write the Specification to the NWBFile
133+
Write the specification to the NWBFile
39134
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40135

41136
You can store the specification (core and extension) within the NWBFile through caching.
@@ -45,3 +140,81 @@ anybody who receives the data also receives the necessary data to interpret it.
45140
.. note::
46141
In :pynwb-docs:`PyNWB <>`, the extension is cached automatically. This can be specified explicitly with
47142
``io.write(filepath, cache_spec=True)``
143+
144+
145+
Use Attributes for small metadata related to a particular data object (Group or Dataset)
146+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147+
148+
Attributes should be used mainly to store small metadata (usually less than 64 Kbytes, which is approximately 1900
149+
characters for a string attribute) that is associated with a particular Group or Dataset. Typical uses of
150+
attributes are, e.g., to define the ``unit`` of measurement of a dataset or to store a short ``description`` of
151+
a group or dataset. For larger data, datasets should be used instead.
152+
153+
In practice, the main difference is that in PyNWB and MatNWB all attributes are read into memory when reading the
154+
NWB file. If you would like to allow users to read a file without reading all of these particular data values, use a
155+
Dataset.
156+
157+
158+
Link data to relevant metadata
159+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160+
161+
Often metadata relevant to a particular type of data is stored elsewhere, e.g., information
162+
about the ``Device`` used. To ensure relevant metadata can be uniquely identified, the data
163+
should include links to the relevant metadata. NWB provides a few key mechanisms for linking:
164+
165+
* Use ``links`` (defined via :py:class:`~pynwb.spec.NWBLinkSpec`) to link to a particular dataset or group
166+
* Use :ref:`hdmf-schema:sec-dynamictableregion` to link to a set of rows in a :ref:`hdmf-schema:sec-dynamictable`
167+
* Use a ``dataset`` with an object reference data type to store collections of links
168+
to other objects, e.g., the following dtype to define a dataset of links to :ref:`nwb-schema:sec-TimeSeries`
169+
170+
.. code-block:: yaml
171+
172+
dtype:
173+
target_type: TimeSeries
174+
reftype: object
175+
176+
177+
Best practices for object names
178+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179+
180+
Names for groups, datasets, attributes, or links should typically:
181+
182+
* **Use lowercase letters only**
183+
* **Use underscores instead of spaces to separate parts in names**. E.g., use the name
184+
``starting_time`` instead of ``starting time``
185+
* **Explicit**. E.g., avoid the use of ambiguous abbreviation in names.
186+
187+
188+
Best practices for naming ``neurodata_types``
189+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
190+
191+
For defining new types via ``neurodata_type_def`` use:
192+
193+
* **Use camelcase:** notation, i.e., names of types should NOT include spaces,
194+
always start with an uppercase letter, and use a single capitalized letter to
195+
separate parts of the name. E.g,. ``neurodata_type_def: LaserMeasurement``
196+
* **Use the postfix "Series" when extending a TimeSeries type.** E.g., when
197+
creating a new :ref:`nwb-schema:sec-TimeSeries` for laser measurements then add ``Series`` to
198+
the type name, e.g,. ``neurodata_type_def: LaserMeasurementSeries``
199+
* **Use the postfix "Table" when extending a DynamicTable type.** e.g.,
200+
``neurodata_type_def: LaserSettingsTable``
201+
* **Explicit**. E.g., avoid the use of ambiguous abbreviation in names.
202+
203+
204+
Limit flexibility: Consider data reuse and tool developers
205+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206+
207+
One of the aims of NWB is to make reusing data easier. This means that when proposing an extension you need to put yourself in the shoes of someone who will receive an NWB dataset and attempt to analyze it. Additionally, consider developers that will try to write tools that take NWB datasets as inputs. It’s worth assessing how much additional code different ways of approaching your extension will lead to.
208+
209+
210+
Use the ``ndx-template`` to create new extensions
211+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212+
213+
By using the :nwb_extension_git:`ndx-template` to create new extensions helps ensure
214+
that extensions can be easily shared and reused and published via the :ndx-catalog:`NDX Catalog <>`.
215+
216+
217+
Get the community involved
218+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
219+
220+
Try to reach out to colleagues working with the type of data you are trying to add support for. The more eyes you will get on your extension the better it will get.

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"sphinx.ext.intersphinx",
2525
"sphinx.ext.extlinks",
2626
"sphinx_copybutton",
27+
"sphinx_tabs.tabs",
2728
]
2829
templates_path = ["_templates"]
2930
master_doc = "index"

docs/conf_extlinks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"black-coding-style": ("https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html%s", None),
3434
"ncbi": ("https://www.ncbi.nlm.nih.gov/taxonomy%s", None),
3535
"ontobee": ("https://ontobee.org/%s", None),
36+
"ndx-catalog": ("https://nwb-extensions.github.io/%s", None),
37+
"nwb_extension_git": ("https://github.com/nwb-extensions/%s", None),
3638
}
3739

3840
# Use this for mapping for links to commonly used documentation

docs/requirements-rtd.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ sphinx==5.1.1
55
sphinx_rtd_theme==0.5.1
66
readthedocs-sphinx-search==0.3.2
77
sphinx-copybutton==0.5.0
8+
sphinx-tabs==3.4.7

0 commit comments

Comments
 (0)