Skip to content

Commit 0089579

Browse files
authored
Update inspector for compliance with nwb schema 2.9.0 (#602)
* update pynwb minimum * update electrodes table usage * update device model usage * fix warnings in tests * fix message with pynwb 3.0 deprecations * update CHANGELOG
1 parent 98c6234 commit 0089579

File tree

12 files changed

+46
-32
lines changed

12 files changed

+46
-32
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
* Fixed error when checking for negative values in a time column with array data [#600](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/600)
1010
* Fixed issue where the io object remained open after inspection was completed. [#601](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/601)
1111

12+
### Improvements
13+
* Added support for PyNWB 3.1 and NWB Schema 2.9 [#602](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/602)
14+
1215
# v0.6.3 (March 13, 2025)
1316

1417
### Improvements

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ classifiers = [
3434
]
3535
requires-python = ">=3.9"
3636
dependencies = [
37-
"pynwb>=3.0", # NWB Inspector should always be used with most recent minor versions of PyNWB
37+
"pynwb>=3.1", # NWB Inspector should always be used with most recent minor versions of PyNWB
3838
"hdmf-zarr",
3939
"fsspec",
4040
"requests",

src/nwbinspector/_nwb_inspection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,7 @@ def inspect_nwbfile(
278278
in_memory_nwbfile, io = read_nwbfile_and_io(nwbfile_path=nwbfile_path)
279279

280280
if not skip_validate:
281-
# TODO - update validation call when pynwb 3.0 is the minimal
282-
validation_result = pynwb.validate(paths=[nwbfile_path])
281+
validation_result = pynwb.validate(path=nwbfile_path)
283282
if isinstance(validation_result, tuple):
284283
validation_errors = validation_result[0]
285284
else:

src/nwbinspector/checks/_images.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def check_order_of_images_len(images: Images) -> Optional[InspectorMessage]:
3737
def check_index_series_points_to_image(index_series: IndexSeries) -> Optional[InspectorMessage]:
3838
if index_series.indexed_timeseries is not None:
3939
return InspectorMessage(
40-
message="Pointing an IndexSeries to a TimeSeries will be deprecated. Please point to an Images "
40+
message="Pointing an IndexSeries to a TimeSeries is deprecated. Please point to an Images "
4141
"container instead."
42-
) # TODO - update when pynwb 3.0 is the minimum version
42+
)
4343

4444
return None

tests/unit_tests/test_general.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def test_check_description_missing():
8686
def test_check_description_feature_extraction():
8787
import numpy as np
8888
from pynwb.ecephys import FeatureExtraction
89-
from pynwb.testing.mock.ecephys import mock_ElectrodeTable
89+
from pynwb.testing.mock.ecephys import mock_ElectrodesTable
9090

91-
electrodes = mock_ElectrodeTable()
91+
electrodes = mock_ElectrodesTable()
9292

9393
dynamic_table_region = DynamicTableRegion(
9494
name="electrodes", description="I am wrong", data=[0, 1, 2, 3, 4], table=electrodes

tests/unit_tests/test_images.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ def test_fail_check_index_series_points_to_image():
106106
object_name="stimuli",
107107
importance=Importance.BEST_PRACTICE_VIOLATION,
108108
object_type="IndexSeries",
109-
message="Pointing an IndexSeries to a TimeSeries will be deprecated. Please point to an Images container "
110-
"instead.", # TODO - update message when PyNWB 3.0 is released
109+
message="Pointing an IndexSeries to a TimeSeries is deprecated. Please point to an Images container "
110+
"instead.",
111111
location="/",
112112
check_function_name="check_index_series_points_to_image",
113113
)

tests/unit_tests/test_nwb_containers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ def test_no_error_raised_when_dataset_is_compressed():
114114

115115

116116
def test_hit_check_empty_string_for_optional_attribute():
117-
nwbfile = NWBFile(session_description="aa", identifier="aa", session_start_time=datetime.now(), pharmacology="")
117+
nwbfile = NWBFile(
118+
session_description="aa", identifier="aa", session_start_time=datetime.now().astimezone(), pharmacology=""
119+
)
118120

119121
assert check_empty_string_for_optional_attribute(nwb_container=nwbfile)[0] == InspectorMessage(
120122
message='The attribute "pharmacology" is optional and you have supplied an empty string. Improve my omitting '
@@ -128,7 +130,7 @@ def test_hit_check_empty_string_for_optional_attribute():
128130

129131

130132
def test_miss_check_empty_string_for_optional_attribute():
131-
nwbfile = NWBFile(session_description="aa", identifier="aa", session_start_time=datetime.now())
133+
nwbfile = NWBFile(session_description="aa", identifier="aa", session_start_time=datetime.now().astimezone())
132134
assert check_empty_string_for_optional_attribute(nwb_container=nwbfile) is None
133135

134136

tests/unit_tests/test_nwbfile_metadata.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ def test_check_session_start_time_old_date_fail():
5252

5353

5454
def test_check_session_start_time_future_date_pass():
55-
nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=datetime(2010, 1, 1))
55+
nwbfile = NWBFile(
56+
session_description="", identifier=str(uuid4()), session_start_time=datetime(2010, 1, 1).astimezone()
57+
)
5658
assert check_session_start_time_future_date(nwbfile) is None
5759

5860

@@ -329,7 +331,7 @@ def test_check_subject_sex_c_elegans_xx_sex():
329331

330332

331333
def test_pass_check_subject_age_with_dob():
332-
subject = Subject(subject_id="001", sex="F", date_of_birth=datetime.now())
334+
subject = Subject(subject_id="001", sex="F", date_of_birth=datetime.now().astimezone())
333335
assert check_subject_age(subject) is None
334336

335337

tests/unit_tests/test_ogen.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
class TestCheckOptogeneticStimulusSiteHasOptogeneticSeries(TestCase):
1212
def setUp(self) -> None:
1313
self.nwbfile = NWBFile(
14-
session_description="session_description", identifier="identifier", session_start_time=datetime.now()
14+
session_description="session_description",
15+
identifier="identifier",
16+
session_start_time=datetime.now().astimezone(),
1517
)
1618

1719
device = Device(name="device_name")

tests/unit_tests/test_ophys.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
from hdmf.common.table import DynamicTable, DynamicTableRegion
77
from pynwb import NWBFile
8-
from pynwb.device import Device
8+
from pynwb.device import Device, DeviceModel
99
from pynwb.ophys import (
1010
ImageSegmentation,
1111
ImagingPlane,
@@ -32,9 +32,12 @@ def setUp(self):
3232
session_description="", identifier=str(uuid4()), session_start_time=datetime.now().astimezone()
3333
)
3434

35-
device = nwbfile.create_device(
36-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
35+
device_model = nwbfile.create_device_model(
36+
name="My Microscope",
37+
description="My two-photon microscope",
38+
manufacturer="The best microscope manufacturer",
3739
)
40+
device = nwbfile.create_device(name="Microscope", description="My two-photon microscope", model=device_model)
3841
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
3942
imaging_plane = nwbfile.create_imaging_plane(
4043
name="ImagingPlane",
@@ -180,9 +183,10 @@ def test_pass_check_roi_response_series_link_to_plane_segmentation(self):
180183

181184

182185
def test_check_excitation_lambda_in_nm():
183-
device = Device(
184-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
186+
device_model = DeviceModel(
187+
name="My Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
185188
)
189+
device = Device(name="Microscope", description="My two-photon microscope", model=device_model)
186190
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
187191
imaging_plane = ImagingPlane(
188192
name="ImagingPlane",
@@ -203,9 +207,10 @@ def test_check_excitation_lambda_in_nm():
203207

204208

205209
def test_pass_check_excitation_lambda_in_nm():
206-
device = Device(
207-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
210+
device_model = DeviceModel(
211+
name="My Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
208212
)
213+
device = Device(name="Microscope", description="My two-photon microscope", model=device_model)
209214
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
210215
imaging_plane = ImagingPlane(
211216
name="ImagingPlane",
@@ -236,9 +241,10 @@ def test_pass_check_emission_lambda_in_nm():
236241

237242

238243
def test_pass_check_plane_segmentation_image_mask_dims_against_imageseries():
239-
device = Device(
240-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
244+
device_model = DeviceModel(
245+
name="My Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
241246
)
247+
device = Device(name="Microscope", description="My two-photon microscope", model=device_model)
242248
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
243249
imaging_plane = ImagingPlane(
244250
name="ImagingPlane",
@@ -275,9 +281,10 @@ def test_pass_check_plane_segmentation_image_mask_dims_against_imageseries():
275281

276282

277283
def test_fail_check_plane_segmentation_image_mask_dims_against_imageseries():
278-
device = Device(
279-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
284+
device_model = DeviceModel(
285+
name="My Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
280286
)
287+
device = Device(name="Microscope", description="My two-photon microscope", model=device_model)
281288
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
282289
imaging_plane = ImagingPlane(
283290
name="ImagingPlane",
@@ -323,9 +330,10 @@ def test_fail_check_plane_segmentation_image_mask_dims_against_imageseries():
323330

324331

325332
def test_false_positive_skip_check_image_series_data_size():
326-
device = Device(
327-
name="Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
333+
device_model = DeviceModel(
334+
name="My Microscope", description="My two-photon microscope", manufacturer="The best microscope manufacturer"
328335
)
336+
device = Device(name="Microscope", description="My two-photon microscope", model=device_model)
329337
optical_channel = OpticalChannel(name="OpticalChannel", description="an optical channel", emission_lambda=500.0)
330338
imaging_plane = ImagingPlane(
331339
name="ImagingPlane",

0 commit comments

Comments
 (0)