Skip to content

Commit c947b8b

Browse files
Merge branch 'dev' into missing_unit
2 parents 431bf71 + adf5e05 commit c947b8b

File tree

9 files changed

+149
-48
lines changed

9 files changed

+149
-48
lines changed

docs/best_practices/best_practices_index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ Authors: Oliver Ruebel, Andrew Tritt, Ryan Ly, Cody Baker and Ben Dichter
2020
.. toctree::
2121
:maxdepth: 2
2222

23+
general
2324
nwbfile_metadata
2425
time_series
2526
tables
2627
ecephys
2728
ogen
28-
naming
2929
simulated_data
3030
extensions
Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
Naming
2-
======
1+
General
2+
=======
33

44

5-
Neurodata Types
6-
---------------
75

6+
Names
7+
-----
8+
9+
10+
11+
Standard Names
12+
~~~~~~~~~~~~~~
813

914
As a default, name class instances with the same name as the class.
1015

@@ -22,32 +27,36 @@ Group. In this case the instances must have unique names. If they are both equal
2227
the class name (e.g. "ElectricalSeries_1" and "ElectricalSeries_2"). If one of the instances is an extra of less
2328
importance, name that one something different (e.g. "ElectricalSeries" and "ElectricalSeries_extra_electrode").
2429

25-
Names are not for storing meta-data. If you need to place other data of the same neurodata_type, you will need to
26-
choose another name. Keep in mind that meta-data should not be stored solely in the name of objects. It is OK to name
27-
an object something like “ElectricalSeries_large_array” however the name alone is not sufficient documentation. In this
28-
case, the source of the signal will be clear from the device of the rows from the linked electrodes table region, and
29-
you should also include any important distinguishing information in the description field of the object. Make an effort
30-
to make meta-data as explicit as possible. Good names help users but do not help applications parse your file.
3130

32-
’/’ is not allowed in names. When creating a custom name, using the forward slash (/) is not allowed, as this can
33-
confuse h5py and lead to the creation of an additional group. Instead of including a forward slash in the name, please
34-
use “Over” like in DfOverF.
3531

32+
.. _best_practice_name_slashes:
33+
34+
Do Not Use Slashes
35+
~~~~~~~~~~~~~~~~~~
36+
37+
'/' is not allowed in Group or Dataset names, as this can confuse h5py and lead to the creation of an additional group.
38+
Instead of including a forward slash in the name, please use "Over" like in DfOverF.
3639

37-
Processing Modules
38-
------------------
40+
Check function: :py:meth:`~nwbinspector.checks.general.check_name_slashes`
3941

4042

41-
Indicate Modality
42-
~~~~~~~~~~~~~~~~~
4343

44-
Give preference to default processing module names. These names mirror the extension module names: “ecephys”,
45-
“icephys”, “behavior”, “ophys”, “misc”.
44+
Descriptions
45+
------------
4646

47-
We encourage the use of these defaults, but there may be some cases when deviating from this pattern is appropriate.
4847

49-
For instance, if there is a processing step that involves data from multiple modalities, or if the user wants to
50-
compare two processing pipelines for a single modality (e.g. different spike sorters), you should create
51-
Processing Modules with custom names.
5248

53-
ProcessingModules are themselves neurodata_types, and the other rules for neurodata_types also apply here.
49+
.. _best_practice_description:
50+
51+
Metadata and Descriptions
52+
~~~~~~~~~~~~~~~~~~~~~~~~~
53+
54+
Names are not for storing meta-data, so meta-data should not be stored solely in the name of objects. It is OK to name
55+
an object something like “ElectricalSeries_large_array” however the name alone is not sufficient documentation. In this
56+
case, the source of the signal will be clear from the device of the rows from the linked electrodes table region, and
57+
you should also include any important distinguishing information in the description field of the object. Make an effort
58+
to make meta-data as explicit as possible. Good names help users but do not help applications parse your file.
59+
60+
As such, it is not recommended to use blank or default 'placeholder' descriptions.
61+
62+
Check function: :py:meth:`~nwbinspector.checks.general.check_description`

nwbinspector/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from .register_checks import available_checks, Importance
22
from .nwbinspector import inspect_nwb, inspect_all, load_config
3+
from .checks.general import *
4+
from .checks.nwbfile_metadata import *
35
from .checks.nwb_containers import *
46
from .checks.time_series import *
57
from .checks.tables import *
6-
from .checks.nwbfile_metadata import *
78
from .checks.ecephys import *

nwbinspector/checks/general.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Check functions that examine any general neurodata_type with the available attrbutes."""
2+
from ..register_checks import register_check, InspectorMessage, Importance
3+
4+
COMMON_DESCRIPTION_PLACEHOLDERS = ["no description", "no desc", "none"]
5+
6+
7+
@register_check(importance=Importance.CRITICAL, neurodata_type=None)
8+
def check_name_slashes(obj):
9+
"""Check if there has been added for the session."""
10+
if hasattr(obj, "name") and any((x in obj.name for x in ["/", "\\"])):
11+
return InspectorMessage(message="Object name contains slashes.")
12+
13+
14+
@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=None)
15+
def check_description(obj):
16+
"""Check if the description is a not missing or a placeholder."""
17+
common_description_placeholders = ["no description", "no desc", "none"]
18+
if not hasattr(obj, "description"):
19+
return
20+
if obj.description is None or obj.description.strip(" ") == "":
21+
return InspectorMessage(message="Description is missing.")
22+
if obj.description.lower().strip(".") in common_description_placeholders:
23+
return InspectorMessage(message=f"Description ('{obj.description}') is a placeholder.")

nwbinspector/checks/nwbfile_metadata.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Check functions that examine general NWBFile metadata."""
22
import re
33

4-
from pynwb import NWBFile
5-
from pynwb.file import Subject, ProcessingModule
4+
from pynwb import NWBFile, ProcessingModule
5+
from pynwb.file import Subject
66

77
from ..register_checks import register_check, InspectorMessage, Importance
88

@@ -15,16 +15,6 @@
1515
PROCESSING_MODULE_CONFIG = ["ophys", "ecephys", "icephys", "behavior", "misc", "ogen", "retinotopy"]
1616

1717

18-
@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=ProcessingModule)
19-
def check_processing_module_name(processing_module: ProcessingModule):
20-
"""Check if the name of a processing module is of a valid modality."""
21-
if processing_module.name not in PROCESSING_MODULE_CONFIG:
22-
return InspectorMessage(
23-
f"Processing module is named {processing_module.name}. It is recommended to use the "
24-
f"schema module names: {', '.join(PROCESSING_MODULE_CONFIG)}"
25-
)
26-
27-
2818
@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=NWBFile)
2919
def check_experimenter(nwbfile: NWBFile):
3020
"""Check if an experimenter has been added for the session."""
@@ -115,3 +105,13 @@ def check_subject_species(subject: Subject):
115105
return InspectorMessage(
116106
message="Species should be in latin binomial form, e.g. 'Mus musculus' and 'Homo sapiens'",
117107
)
108+
109+
110+
@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=ProcessingModule)
111+
def check_processing_module_name(processing_module: ProcessingModule):
112+
"""Check if the name of a processing module is of a valid modality."""
113+
if processing_module.name not in PROCESSING_MODULE_CONFIG:
114+
return InspectorMessage(
115+
f"Processing module is named {processing_module.name}. It is recommended to use the "
116+
f"schema module names: {', '.join(PROCESSING_MODULE_CONFIG)}"
117+
)

nwbinspector/nwbinspector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def run_checks(nwbfile: pynwb.NWBFile, checks: list):
378378
"""
379379
for check_function in checks:
380380
for nwbfile_object in nwbfile.objects.values():
381-
if issubclass(type(nwbfile_object), check_function.neurodata_type):
381+
if check_function.neurodata_type is None or issubclass(type(nwbfile_object), check_function.neurodata_type):
382382
try:
383383
output = check_function(nwbfile_object)
384384
# if an individual check fails, include it in the report and continue with the inspection

nwbinspector/register_checks.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,24 @@ class InspectorMessage:
7878
# TODO: neurodata_type could have annotation hdmf.utils.ExtenderMeta, which seems to apply to all currently checked
7979
# objects. We can wait and see how well that holds up before adding it in officially.
8080
def register_check(importance: Importance, neurodata_type):
81-
"""Wrap a check function to add it to the list of default checks for that severity and neurodata type."""
81+
"""
82+
Wrap a check function with this decorator to add it to the check registry and automatically parse some output.
83+
84+
Parameters
85+
----------
86+
importance : Importance
87+
Importance has three levels:
88+
CRITICAL
89+
- potentially incorrect data
90+
BEST_PRACTICE_VIOLATION
91+
- very suboptimal data representation
92+
BEST_PRACTICE_SUGGESTION
93+
- improvable data representation
94+
neurodata_type
95+
The most generic HDMF/PyNWB class the check function applies to.
96+
Should generally match the type annotation of the check.
97+
If this check is intended to apply to any general NWBFile object, set neurodata_type to None.
98+
"""
8299

83100
def register_check_and_auto_parse(check_function):
84101
if importance not in [

tests/unit_tests/test_general.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from hdmf.common import DynamicTable
2+
3+
from nwbinspector import InspectorMessage, Importance
4+
from nwbinspector.checks.general import check_name_slashes, check_description
5+
6+
7+
def test_check_name_slashes_pass():
8+
table = DynamicTable(name="test_name", description="")
9+
assert check_name_slashes(obj=table) is None
10+
11+
12+
def test_check_name_slashes_fail():
13+
"""HDMF/PyNWB forbid "/" in the object names. Might need an external file written in MATLAB to test that?"""
14+
for x in ["\\"]:
15+
table = DynamicTable(name=f"test{x}ing", description="")
16+
assert check_name_slashes(obj=table) == InspectorMessage(
17+
message="Object name contains slashes.",
18+
importance=Importance.CRITICAL,
19+
check_function_name="check_name_slashes",
20+
object_type="DynamicTable",
21+
object_name=f"test{x}ing",
22+
location="/",
23+
)
24+
25+
26+
def test_check_description_pass():
27+
table = DynamicTable(name="test", description="testing")
28+
assert check_description(obj=table) is None
29+
30+
31+
def test_check_description_fail():
32+
table = DynamicTable(name="test", description="No Description.")
33+
assert check_description(obj=table) == InspectorMessage(
34+
message="Description ('No Description.') is a placeholder.",
35+
importance=Importance.BEST_PRACTICE_SUGGESTION,
36+
check_function_name="check_description",
37+
object_type="DynamicTable",
38+
object_name="test",
39+
location="/",
40+
)
41+
42+
43+
def test_check_description_missing():
44+
table = DynamicTable(name="test", description=" ")
45+
assert check_description(obj=table) == InspectorMessage(
46+
message="Description is missing.",
47+
importance=Importance.BEST_PRACTICE_SUGGESTION,
48+
check_function_name="check_description",
49+
object_type="DynamicTable",
50+
object_name="test",
51+
location="/",
52+
)

tests/unit_tests/test_nwbfile_metadata.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from uuid import uuid4
22
from datetime import datetime
33

4-
from pynwb import NWBFile
5-
from pynwb.file import Subject, ProcessingModule
4+
from pynwb import NWBFile, ProcessingModule
5+
from pynwb.file import Subject
66
import pytest
77

88
from nwbinspector import InspectorMessage, Importance
@@ -20,7 +20,6 @@
2020
check_processing_module_name,
2121
PROCESSING_MODULE_CONFIG,
2222
)
23-
from nwbinspector.register_checks import Severity
2423
from nwbinspector.tools import make_minimal_nwbfile
2524

2625

@@ -278,6 +277,11 @@ def test_pass_check_subject_id_exist():
278277
assert check_subject_id_exists(subject) is None
279278

280279

280+
@pytest.mark.skip(reason="TODO")
281+
def test_check_subject_species():
282+
pass
283+
284+
281285
def test_check_processing_module_name():
282286
processing_module = ProcessingModule("test", "desc")
283287
assert check_processing_module_name(processing_module) == InspectorMessage(
@@ -294,8 +298,3 @@ def test_check_processing_module_name():
294298
def test_pass_check_processing_module_name():
295299
processing_module = ProcessingModule("ecephys", "desc")
296300
assert check_processing_module_name(processing_module) is None
297-
298-
299-
@pytest.mark.skip(reason="TODO")
300-
def test_check_subject_species():
301-
pass

0 commit comments

Comments
 (0)