Skip to content

Commit 2127c76

Browse files
authored
Add check in PlaneSegmentation init for required cols (#2102)
1 parent ec3d0f0 commit 2127c76

File tree

3 files changed

+58
-6
lines changed

3 files changed

+58
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
### Bug fixes
66
- Fixed reading and exporting of files written with NWB Schema < 2.9.0 that contained a reference to the electrodes table. @rly [#2112](https://github.com/NeurodataWithoutBorders/pynwb/pull/2112)
7-
- Skip streaming tests gracefully if offline. @rly [#2113](https://github.com/NeurodataWithoutBorders/pynwb/pull/2113)
7+
- Updated tests to skip streaming tests gracefully if offline. @rly [#2113](https://github.com/NeurodataWithoutBorders/pynwb/pull/2113)
8+
- Added check in `PlaneSegmentation` constructor for required columns. @rly [#2102](https://github.com/NeurodataWithoutBorders/pynwb/pull/2102)
89

910

1011
## PyNWB 3.1.0 (July 8, 2025)

src/pynwb/ophys.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,24 @@ def __init__(self, **kwargs):
354354
imaging_plane, reference_images = popargs('imaging_plane', 'reference_images', kwargs)
355355
if kwargs['name'] is None:
356356
kwargs['name'] = imaging_plane.name
357+
358+
if kwargs["columns"]:
359+
# check for required ROI columns if table is initialized with non-empty columns
360+
if any(len(c) > 0 for c in kwargs["columns"]):
361+
for c in kwargs["columns"]:
362+
if c.name in ("image_mask", "pixel_mask", "voxel_mask"):
363+
break
364+
else:
365+
raise ValueError("Must provide at least one of 'image_mask', 'pixel_mask', or 'voxel_mask' columns")
366+
elif kwargs["id"]: # there are also no columns
367+
raise ValueError("Must provide at least one of 'image_mask', 'pixel_mask', or 'voxel_mask' columns")
368+
357369
super().__init__(**kwargs)
358370
self.imaging_plane = imaging_plane
359371
if isinstance(reference_images, ImageSeries):
360372
reference_images = (reference_images,)
361373
self.reference_images = reference_images
362-
374+
363375
@docval({'name': 'pixel_mask', 'type': 'array_data', 'default': None,
364376
'doc': 'pixel mask for 2D ROIs: [(x1, y1, weight1), (x2, y2, weight2), ...]',
365377
'shape': (None, 3)},

tests/unit/test_ophys.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import warnings
22

33
import numpy as np
4+
from hdmf.common import VectorData
45

56
from pynwb.base import TimeSeries, ProcessingModule
67
from pynwb.device import Device
@@ -142,7 +143,7 @@ def test_manifold_deprecated(self):
142143
indicator='indicator',
143144
location='location',
144145
manifold=(1, 1, (2, 2, 2)))
145-
146+
146147
# create object with deprecated argument
147148
with self.assertRaisesWith(ValueError, msg):
148149
ImagingPlane(**kwargs)
@@ -227,7 +228,7 @@ def test_init_description_optional(self):
227228
'indicator',
228229
'location',
229230
)
230-
231+
231232
def test_init_missing_required_args(self):
232233
"""Check that ImagingPlane raises an error if required args are missing."""
233234
oc, device = self.set_up_dependencies()
@@ -527,7 +528,7 @@ def test_init(self):
527528
self.assertEqual(iS.name, 'test_iS')
528529
self.assertEqual(iS.plane_segmentations[ps.name], ps)
529530
self.assertEqual(iS[ps.name], iS.plane_segmentations[ps.name])
530-
531+
531532
def test_add_segementation(self):
532533
ps = create_plane_segmentation()
533534
iS = ImageSegmentation(name='test_iS')
@@ -594,6 +595,45 @@ def test_init_no_name(self):
594595
)
595596
self.assertEqual(pS.name, ip.name)
596597

598+
def test_init_missing_roi_col_with_ids(self):
599+
"""If no roi column is provided and ids were provided, an error should be raised"""
600+
iSS, ip = self.set_up_dependencies()
601+
msg = "Must provide at least one of 'image_mask', 'pixel_mask', or 'voxel_mask' columns"
602+
with self.assertRaises(ValueError, msg=msg):
603+
PlaneSegmentation(
604+
description='description',
605+
imaging_plane=ip,
606+
name='test_name',
607+
reference_images=iSS,
608+
id=[1, 2, 3],
609+
)
610+
611+
def test_init_missing_roi_col_with_columns(self):
612+
"""If no roi column is provided and other non-empty columns are provided, an error should be raised"""
613+
iSS, ip = self.set_up_dependencies()
614+
msg = "Must provide at least one of 'image_mask', 'pixel_mask', or 'voxel_mask' columns"
615+
with self.assertRaises(ValueError, msg=msg):
616+
PlaneSegmentation(
617+
description='description',
618+
imaging_plane=ip,
619+
name='test_name',
620+
reference_images=iSS,
621+
columns=[VectorData(name="custom_col", description="custom col", data=[1, 2, 3])],
622+
id=[1, 2, 3],
623+
)
624+
625+
def test_init_missing_roi_col_with_empty_columns(self):
626+
"""If no roi column is provided and other columns are empty, no error should be raised"""
627+
iSS, ip = self.set_up_dependencies()
628+
pS = PlaneSegmentation(
629+
description='description',
630+
imaging_plane=ip,
631+
name='test_name',
632+
reference_images=iSS,
633+
columns=[VectorData(name="custom_col", description="custom col")],
634+
)
635+
self.assertEqual(len(pS), 0)
636+
597637
def test_add_pixel_mask(self):
598638
pix_mask = [[1, 2, 1.0], [3, 4, 1.0], [5, 6, 1.0],
599639
[7, 8, 2.0], [9, 10, 2.0]]
@@ -659,7 +699,6 @@ def test_add_roi_missing_params(self):
659699
with self.assertRaises(ValueError, msg=msg):
660700
pS.add_roi()
661701

662-
663702
def test_conversion_of_2d_pixel_mask_to_image_mask(self):
664703
pixel_mask = [[0, 0, 1.0], [1, 0, 2.0], [2, 0, 2.0]]
665704

0 commit comments

Comments
 (0)