Skip to content

Commit dea0722

Browse files
authored
Apply shape constraints (#706)
* apply shape to all datasets * fix tests
1 parent 60d263c commit dea0722

File tree

21 files changed

+105
-93
lines changed

21 files changed

+105
-93
lines changed

src/pynwb/behavior.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SpatialSeries(TimeSeries):
2525
_help = "Stores points in space over time. The data[] array structure is [num samples][num spatial dimensions]"
2626

2727
@docval({'name': 'name', 'type': str, 'doc': 'The name of this SpatialSeries dataset'},
28-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
28+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, None),
2929
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames'},
3030
{'name': 'reference_frame', 'type': str, 'doc': 'description defining what the zero-position is'},
3131
{'name': 'conversion', 'type': float,
@@ -34,7 +34,7 @@ class SpatialSeries(TimeSeries):
3434
{'name': 'resolution', 'type': float,
3535
'doc': 'The smallest meaningful difference (in specified unit) between values in data',
3636
'default': _default_resolution},
37-
{'name': 'timestamps', 'type': ('array_data', 'data', 'TimeSeries'),
37+
{'name': 'timestamps', 'type': ('array_data', 'data', 'TimeSeries'), 'shape': (None, ),
3838
'doc': 'Timestamps for samples stored in data', 'default': None},
3939
{'name': 'starting_time', 'type': float, 'doc': 'The timestamp of the first sample', 'default': None},
4040
{'name': 'rate', 'type': float, 'doc': 'Sampling rate in Hz', 'default': None},

src/pynwb/data/nwb.behavior.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ groups:
1313
attributes:
1414
- name: help
1515
dtype: text
16-
doc: Value is 'Stores points in space over time. The data[] array structure is
16+
doc: 'Value is: Stores points in space over time. The data[] array structure is
1717
[num samples][num spatial dimensions]'
1818
value: Stores points in space over time. The data[] array structure is [num samples][num
1919
spatial dimensions]
@@ -42,7 +42,7 @@ groups:
4242
quantity: '?'
4343
- neurodata_type_def: BehavioralEpochs
4444
neurodata_type_inc: NWBDataInterface
45-
doc: 'TimeSeries for storing behavoioral epochs. The objective of this and the other
45+
doc: 'TimeSeries for storing behavioral epochs. The objective of this and the other
4646
two Behavioral interfaces (e.g. BehavioralEvents and BehavioralTimeSeries) is
4747
to provide generic hooks for software tools/scripts. This allows a tool/script
4848
to take the output one specific interface (e.g., UnitTimes) and plot that data

src/pynwb/data/nwb.image.yaml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ groups:
5353
doc: Either binary data containing image or empty.
5454
quantity: '?'
5555
dims:
56-
- - x
57-
- y
5856
- - frame
5957
- y
6058
- x
@@ -63,8 +61,6 @@ groups:
6361
- y
6462
- x
6563
shape:
66-
- - null
67-
- null
6864
- - null
6965
- null
7066
- null
@@ -145,13 +141,14 @@ groups:
145141
quantity: '?'
146142
- name: field_of_view
147143
dtype: float32
148-
doc: Width, height and depto of image, or imaged area (meters).
144+
doc: Width, height and depth of image, or imaged area (meters).
149145
dims:
150146
- - width|height
151-
- width|height|depth
147+
- - width|height|depth
152148
quantity: '?'
153149
shape:
154-
- 2
150+
- - 2
151+
- - 3
155152
- name: orientation
156153
dtype: text
157154
doc: Description of image relative to some reference frame (e.g., which way is

src/pynwb/data/nwb.misc.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ groups:
3030
default_value: see 'feature_units'
3131
required: false
3232
dims:
33-
- num_times
34-
- num_features
33+
- - num_times
34+
- - num_times
35+
- num_features
3536
shape:
36-
- null
37-
- null
37+
- - null
38+
- - null
39+
- null
3840
- name: feature_units
3941
dtype: text
4042
doc: Units of each feature.

src/pynwb/data/nwb.ophys.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ groups:
2222
dtype: float32
2323
doc: Width, height and depth of image, or imaged area (meters).
2424
dims:
25+
- width|height
2526
- width|height|depth
2627
quantity: '?'
2728
required: false
2829
shape:
29-
- 3
30+
- - 2
31+
- - 3
3032
links:
3133
- name: imaging_plane
3234
doc: link to ImagingPlane group from which this TimeSeries data was generated

src/pynwb/ecephys.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ class Clustering(NWBDataInterface):
220220
@docval({'name': 'description', 'type': str,
221221
'doc': 'Description of clusters or clustering, (e.g. cluster 0 is noise, \
222222
clusters curated using Klusters, etc).'},
223-
{'name': 'num', 'type': ('array_data', 'data'), 'doc': 'Cluster number of each event.', 'shape': (None,)},
224-
{'name': 'peak_over_rms', 'type': Iterable,
223+
{'name': 'num', 'type': ('array_data', 'data'), 'doc': 'Cluster number of each event.', 'shape': (None, )},
224+
{'name': 'peak_over_rms', 'type': Iterable, 'shape': (None, ),
225225
'doc': 'Maximum ratio of waveform peak to RMS on any channel in the cluster\
226226
(provides a basic clustering metric).'},
227227
{'name': 'times', 'type': ('array_data', 'data'), 'doc': 'Times of clustered events, in seconds.',
@@ -256,8 +256,10 @@ class ClusterWaveforms(NWBDataInterface):
256256
'doc': 'the clustered spike data used as input for computing waveforms'},
257257
{'name': 'waveform_filtering', 'type': str,
258258
'doc': 'filter applied to data before calculating mean and standard deviation'},
259-
{'name': 'waveform_mean', 'type': Iterable, 'doc': 'the mean waveform for each cluster'},
260-
{'name': 'waveform_sd', 'type': Iterable, 'doc': 'the standard deviations of waveforms for each cluster'},
259+
{'name': 'waveform_mean', 'type': Iterable, 'shape': (None, None),
260+
'doc': 'the mean waveform for each cluster'},
261+
{'name': 'waveform_sd', 'type': Iterable, 'shape': (None, None),
262+
'doc': 'the standard deviations of waveforms for each cluster'},
261263
{'name': 'name', 'type': str, 'doc': 'the name of this container', 'default': 'ClusterWaveforms'})
262264
def __init__(self, **kwargs):
263265
import warnings
@@ -334,10 +336,10 @@ class FeatureExtraction(NWBDataInterface):
334336
@docval({'name': 'electrodes', 'type': DynamicTableRegion,
335337
'doc': 'the table region corresponding to the electrodes from which this series was recorded'},
336338
{'name': 'description', 'type': (list, tuple, np.ndarray, DataChunkIterator),
337-
'doc': 'A description for each feature extracted', 'ndim': 1},
338-
{'name': 'times', 'type': ('array_data', 'data'),
339-
'doc': 'The times of events that features correspond to', 'ndim': 1},
340-
{'name': 'features', 'type': ('array_data', 'data'),
339+
'doc': 'A description for each feature extracted', 'shape': (None, )},
340+
{'name': 'times', 'type': ('array_data', 'data'), 'shape': (None, ),
341+
'doc': 'The times of events that features correspond to'},
342+
{'name': 'features', 'type': ('array_data', 'data'), 'shape': (None, None, None),
341343
'doc': 'Features for each channel', 'ndim': 3},
342344
{'name': 'name', 'type': str, 'doc': 'the name of this container', 'default': 'FeatureExtraction'})
343345
def __init__(self, **kwargs):

src/pynwb/form/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,12 @@ def __parse_args(validator, args, kwargs, enforce_type=True, enforce_shape=True,
176176
argsi += 1
177177
else:
178178
ret[argname] = arg['default']
179+
argval = ret[argname]
179180
if enforce_type:
180-
argval = ret[argname]
181181
if not __type_okay(argval, arg['type'], arg['default'] is None):
182182
fmt_val = (argname, type(argval).__name__, __format_type(arg['type']))
183183
type_errors.append("incorrect type for '%s' (got '%s', expected '%s')" % fmt_val)
184-
if enforce_shape and 'shape' in arg:
184+
if enforce_shape and 'shape' in arg and argval is not None:
185185
if not __shape_okay_multi(argval, arg['shape']):
186186
fmt_val = (argname, get_data_shape(argval), arg['shape'])
187187
value_errors.append("incorrect shape for '%s' (got '%s, expected '%s')" % fmt_val)

src/pynwb/image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ImageSeries(TimeSeries):
2323
_help = "Storage object for time-series 2-D image data"
2424

2525
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
26-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
26+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ([None] * 3, [None] * 4),
2727
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames',
2828
'default': None},
2929
{'name': 'unit', 'type': str,
@@ -88,7 +88,7 @@ class IndexSeries(TimeSeries):
8888
an arbitrary order. The data[] field stores frame number in reference stack."
8989

9090
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
91-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
91+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ),
9292
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames'},
9393
{'name': 'unit', 'type': str, 'doc': 'The base unit of measurement (should be SI unit)'},
9494
@@ -203,7 +203,7 @@ class OpticalSeries(ImageSeries):
203203
{'name': 'format', 'type': str,
204204
'doc': 'Format of image. Three types: 1) Image format; tiff, png, jpg, etc. 2) external 3) raw.'},
205205
{'name': 'distance', 'type': float, 'doc': 'Distance from camera/monitor to target/eye.'},
206-
{'name': 'field_of_view', 'type': (list, np.ndarray, 'TimeSeries'),
206+
{'name': 'field_of_view', 'type': (list, np.ndarray, 'TimeSeries'), 'shape': ((2, ), (3, )),
207207
'doc': 'Width, height and depth of image, or imaged area (meters).'},
208208
{'name': 'orientation', 'type': str,
209209
'doc': 'Description of image relative to some reference frame (e.g., which way is up). \

src/pynwb/misc.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AnnotationSeries(TimeSeries):
2626
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
2727
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames',
2828
'default': list()},
29-
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries),
29+
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ),
3030
'doc': 'Timestamps for samples stored in data', 'default': None},
3131
{'name': 'comments', 'type': str,
3232
'doc': 'Human-readable comments about this TimeSeries dataset', 'default': 'no comments'},
@@ -68,19 +68,18 @@ class AbstractFeatureSeries(TimeSeries):
6868
_help = "Features of an applied stimulus. This is useful when storing the raw stimulus is impractical."
6969

7070
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
71-
{'name': 'feature_units', 'type': (str, Iterable), 'doc': 'The unit of each feature'},
72-
{'name': 'features', 'type': (str, Iterable), 'doc': 'Description of each feature'},
73-
74-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
75-
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames',
76-
'default': list()},
71+
{'name': 'feature_units', 'type': Iterable, 'shape': (None, ), 'doc': 'The unit of each feature'},
72+
{'name': 'features', 'type': Iterable, 'shape': (None, ), 'doc': 'Description of each feature'},
73+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None,), (None, None)),
74+
'default': list(),
75+
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames'},
7776
{'name': 'resolution', 'type': float,
7877
'doc': 'The smallest meaningful difference (in specified unit) between values in data',
7978
'default': _default_resolution},
8079
{'name': 'conversion', 'type': float,
8180
'doc': 'Scalar to multiply each element in data to convert it to the specified unit',
8281
'default': _default_conversion},
83-
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries),
82+
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ),
8483
'doc': 'Timestamps for samples stored in data', 'default': None},
8584
{'name': 'starting_time', 'type': float, 'doc': 'The timestamp of the first sample', 'default': None},
8685
{'name': 'rate', 'type': float, 'doc': 'Sampling rate in Hz', 'default': None},
@@ -105,8 +104,11 @@ def __init__(self, **kwargs):
105104
{'name': 'features', 'type': (list, np.ndarray), 'doc': 'the feature values for this time point'})
106105
def add_features(self, **kwargs):
107106
time, features = getargs('time', 'features', kwargs)
108-
self.timestamps.append(time)
109-
self.data.append(features)
107+
if type(self.timestamps) == list and type(self.data) is list:
108+
self.timestamps.append(time)
109+
self.data.append(features)
110+
else:
111+
raise ValueError('Can only add feature if timestamps and data are lists')
110112

111113

112114
@register_class('IntervalSeries', CORE_NAMESPACE)
@@ -116,7 +118,7 @@ class IntervalSeries(TimeSeries):
116118
data field stores whether the interval just started (>0 value) or ended (<0 value). Different interval
117119
types can be represented in the same series by using multiple key values (eg, 1 for feature A, 2
118120
for feature B, 3 for feature C, etc). The field data stores an 8-bit integer. This is largely an alias
119-
of a standard TimeSeries but that is identifiable as representing time intervals in a machinereadable
121+
of a standard TimeSeries but that is identifiable as representing time intervals in a machine-readable
120122
way.
121123
"""
122124

@@ -125,9 +127,9 @@ class IntervalSeries(TimeSeries):
125127
_help = "Stores the start and stop times for events."
126128

127129
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
128-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
130+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': (None,),
129131
'doc': '>0 if interval started, <0 if interval ended.', 'default': list()},
130-
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries),
132+
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries), 'shape': (None,),
131133
'doc': 'Timestamps for samples stored in data', 'default': list()},
132134
{'name': 'comments', 'type': str,
133135
'doc': 'Human-readable comments about this TimeSeries dataset', 'default': 'no comments'},

src/pynwb/ogen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class OptogeneticSeries(TimeSeries):
4545
_help = "Optogenetic stimulus."
4646

4747
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
48-
{'name': 'data', 'type': ('array_data', 'data', TimeSeries),
48+
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ),
4949
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames'},
5050
{'name': 'unit', 'type': str, 'doc': 'Value is the string "Watt".', 'default': 'Watt'},
5151
{'name': 'site', 'type': OptogeneticStimulusSite,
@@ -55,7 +55,7 @@ class OptogeneticSeries(TimeSeries):
5555
{'name': 'conversion', 'type': float,
5656
'doc': 'Scalar to multiply each element by to conver to volts', 'default': _default_conversion},
5757
58-
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries),
58+
{'name': 'timestamps', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ),
5959
'doc': 'Timestamps for samples stored in data', 'default': None},
6060
{'name': 'starting_time', 'type': float, 'doc': 'The timestamp of the first sample', 'default': None},
6161
{'name': 'rate', 'type': float, 'doc': 'Sampling rate in Hz', 'default': None},

0 commit comments

Comments
 (0)