Skip to content

Commit 929972d

Browse files
authored
Support NWB schema 2.2.2 (#1146)
Update PyNWB API to support NWB schema 2.2.2 (and 2.2.0 and 2.2.1) as well as HDMF 1.6.1
1 parent 78537e3 commit 929972d

27 files changed

+687
-125
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ You are under no obligation whatsoever to provide any bug fixes, patches, or upg
9393
COPYRIGHT
9494
=========
9595

96-
"pynwb" Copyright (c) 2017-2019, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved.
96+
"pynwb" Copyright (c) 2017-2020, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved.
9797
If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at [email protected].
9898

9999
NOTICE. This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit other to do so.

requirements-min.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# these minimum requirements specify '==' for testing; setup.py replaces '==' with '>='
22
h5py==2.9 # support for setting attrs to lists of utf-8 added in 2.9
3-
hdmf==1.5.4,<2
3+
hdmf==1.6.1,<2
44
numpy==1.16
55
pandas==0.23
66
python-dateutil==2.7

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
h5py==2.10.0
2-
hdmf==1.5.4
2+
hdmf==1.6.1
33
numpy==1.18.1
44
pandas==0.25.3
5-
python-dateutil==2.8.0
5+
python-dateutil==2.8.1

src/pynwb/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ def get_class(**kwargs):
168168
specification. If you want to define a custom mapping, you should not use this function and you should define the
169169
class manually.
170170
171-
Examples
172-
--------
171+
Examples:
172+
173173
Generating and registering an extension is as simple as::
174174
175175
MyClass = get_class('MyClass', 'ndx-my-extension')

src/pynwb/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,14 @@ def time_unit(self):
232232

233233
@register_class('Image', CORE_NAMESPACE)
234234
class Image(NWBData):
235+
"""
236+
Abstract image class. It is recommended to instead use pynwb.image.GrayscaleImage or pynwb.image.RGPImage where
237+
appropriate.
238+
"""
235239
__nwbfields__ = ('data', 'resolution', 'description')
236240

237241
@docval({'name': 'name', 'type': str, 'doc': 'The name of this TimeSeries dataset'},
238-
{'name': 'data', 'type': ('array_data', 'data'), 'doc': 'data of image',
242+
{'name': 'data', 'type': ('array_data', 'data'), 'doc': 'data of image. Dimensions: x, y [, r,g,b[,a]]',
239243
'shape': ((None, None), (None, None, 3), (None, None, 4))},
240244
{'name': 'resolution', 'type': 'float', 'doc': 'pixels / cm', 'default': None},
241245
{'name': 'description', 'type': str, 'doc': 'description of image', 'default': None})

src/pynwb/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from hdmf.utils import docval, getargs, ExtenderMeta, call_docval_func, popargs, get_docval, fmt_docval_args
66
from hdmf import Container, Data, DataRegion, get_region_slicer
77
from hdmf.container import AbstractContainer
8-
from hdmf.common import DynamicTable, DynamicTableRegion # NOQA
8+
from hdmf.common import DynamicTable, DynamicTableRegion # noqa: F401
9+
from hdmf.common import VectorData, VectorIndex, ElementIdentifiers # noqa: F401
910

1011
from . import CORE_NAMESPACE, register_class
1112

src/pynwb/ecephys.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,25 @@ class ElectrodeGroup(NWBContainer):
1818
__nwbfields__ = ('name',
1919
'description',
2020
'location',
21-
'device')
21+
'device',
22+
'position')
2223

2324
@docval({'name': 'name', 'type': str, 'doc': 'the name of this electrode'},
2425
{'name': 'description', 'type': str, 'doc': 'description of this electrode group'},
2526
{'name': 'location', 'type': str, 'doc': 'description of location of this electrode group'},
26-
{'name': 'device', 'type': Device, 'doc': 'the device that was used to record from this electrode group'})
27+
{'name': 'device', 'type': Device, 'doc': 'the device that was used to record from this electrode group'},
28+
{'name': 'position', 'type': 'array_data',
29+
'doc': 'stereotaxic position of this electrode group (x, y, z)', 'default': None})
2730
def __init__(self, **kwargs):
2831
call_docval_func(super(ElectrodeGroup, self).__init__, kwargs)
29-
description, location, device = popargs("description", "location", "device", kwargs)
32+
description, location, device, position = popargs('description', 'location', 'device', 'position', kwargs)
3033
self.description = description
3134
self.location = location
3235
self.device = device
36+
if position and len(position) != 3:
37+
raise Exception('ElectrodeGroup position argument must have three elements: x, y, z, but received: %s'
38+
% position)
39+
self.position = position
3340

3441

3542
@register_class('ElectricalSeries', CORE_NAMESPACE)

src/pynwb/file.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,14 +493,19 @@ def add_electrode_column(self, **kwargs):
493493
self.__check_electrodes()
494494
call_docval_func(self.electrodes.add_column, kwargs)
495495

496-
@docval({'name': 'x', 'type': 'float', 'doc': 'the x coordinate of the position'},
497-
{'name': 'y', 'type': 'float', 'doc': 'the y coordinate of the position'},
498-
{'name': 'z', 'type': 'float', 'doc': 'the z coordinate of the position'},
496+
@docval({'name': 'x', 'type': 'float', 'doc': 'the x coordinate of the position (+x is posterior)'},
497+
{'name': 'y', 'type': 'float', 'doc': 'the y coordinate of the position (+y is inferior)'},
498+
{'name': 'z', 'type': 'float', 'doc': 'the z coordinate of the position (+z is right)'},
499499
{'name': 'imp', 'type': 'float', 'doc': 'the impedance of the electrode'},
500500
{'name': 'location', 'type': str, 'doc': 'the location of electrode within the subject e.g. brain region'},
501501
{'name': 'filtering', 'type': str, 'doc': 'description of hardware filtering'},
502502
{'name': 'group', 'type': ElectrodeGroup, 'doc': 'the ElectrodeGroup object to add to this NWBFile'},
503503
{'name': 'id', 'type': int, 'doc': 'a unique identifier for the electrode', 'default': None},
504+
{'name': 'rel_x', 'type': 'float', 'doc': 'the x coordinate within the electrode group', 'default': None},
505+
{'name': 'rel_y', 'type': 'float', 'doc': 'the y coordinate within the electrode group', 'default': None},
506+
{'name': 'rel_z', 'type': 'float', 'doc': 'the z coordinate within the electrode group', 'default': None},
507+
{'name': 'reference', 'type': str, 'doc': 'Description of the reference used for this electrode.',
508+
'default': None},
504509
allow_extra=True)
505510
def add_electrode(self, **kwargs):
506511
"""
@@ -515,6 +520,17 @@ def add_electrode(self, **kwargs):
515520
d = _copy.copy(kwargs['data']) if kwargs.get('data') is not None else kwargs
516521
if d.get('group_name', None) is None:
517522
d['group_name'] = d['group'].name
523+
524+
new_cols = [('rel_x', 'the x coordinate within the electrode group'),
525+
('rel_y', 'the y coordinate within the electrode group'),
526+
('rel_z', 'the z coordinate within the electrode group'),
527+
('reference', 'Description of the reference used for this electrode.')]
528+
for col_name, col_doc in new_cols:
529+
if kwargs[col_name] is not None and col_name not in self.electrodes:
530+
self.electrodes.add_column(col_name, col_doc)
531+
else:
532+
d.pop(col_name) # remove args from d if not set
533+
518534
call_docval_func(self.electrodes.add_row, d)
519535

520536
@docval({'name': 'region', 'type': (slice, list, tuple), 'doc': 'the indices of the table'},
@@ -757,7 +773,8 @@ def ElectrodeTable(name='electrodes',
757773
('location', 'the location of channel within the subject e.g. brain region'),
758774
('filtering', 'description of hardware filtering'),
759775
('group', 'a reference to the ElectrodeGroup this electrode is a part of'),
760-
('group_name', 'the name of the ElectrodeGroup this electrode is a part of')]
776+
('group_name', 'the name of the ElectrodeGroup this electrode is a part of')
777+
]
761778
)
762779

763780

src/pynwb/image.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class ImageSeries(TimeSeries):
2121

2222
@docval(*get_docval(TimeSeries.__init__, 'name'), # required
2323
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ([None] * 3, [None] * 4),
24-
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames',
24+
'doc': 'The data this TimeSeries dataset stores. Can also store binary data e.g. image frames. '
25+
'dimensions: time, x, y [, z]',
2526
'default': None},
2627
*get_docval(TimeSeries.__init__, 'unit'),
2728
{'name': 'format', 'type': str,
@@ -130,7 +131,9 @@ class OpticalSeries(ImageSeries):
130131
'field_of_view',
131132
'orientation')
132133

133-
@docval(*get_docval(ImageSeries.__init__, 'name', 'data'), # required
134+
@docval(*get_docval(ImageSeries.__init__, 'name'),
135+
{'name': 'data', 'type': ('array_data', 'data'), 'shape': ([None] * 3, [None, None, None, 3]),
136+
'doc': 'Images presented to subject, either grayscale or RGB'},
134137
*get_docval(ImageSeries.__init__, 'unit', 'format'),
135138
{'name': 'distance', 'type': 'float', 'doc': 'Distance from camera/monitor to target/eye.'}, # required
136139
{'name': 'field_of_view', 'type': ('array_data', 'data', 'TimeSeries'), 'shape': ((2, ), (3, )), # required

src/pynwb/io/core.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from hdmf.build import ObjectMapper, RegionBuilder
2+
from hdmf.common import VectorData
3+
from hdmf.utils import getargs, docval
4+
from hdmf.spec import AttributeSpec
5+
from hdmf.build import BuildManager
26

37
from .. import register_map
48

59
from pynwb.file import NWBFile
610
from pynwb.core import NWBData, NWBContainer
11+
from pynwb.misc import Units
712

813

914
class NWBBaseTypeMapper(ObjectMapper):
@@ -19,7 +24,6 @@ def get_nwb_file(container):
1924

2025
@register_map(NWBContainer)
2126
class NWBContainerMapper(NWBBaseTypeMapper):
22-
2327
pass
2428

2529

@@ -46,3 +50,27 @@ def carg_region(self, builder, manager):
4650
if not isinstance(builder.data, RegionBuilder):
4751
raise ValueError("'builder' must be a RegionBuilder")
4852
return builder.data.region
53+
54+
55+
@register_map(VectorData)
56+
class VectorDataMap(ObjectMapper):
57+
58+
@docval({"name": "spec", "type": AttributeSpec, "doc": "the spec to get the attribute value for"},
59+
{"name": "container", "type": VectorData, "doc": "the container to get the attribute value from"},
60+
{"name": "manager", "type": BuildManager, "doc": "the BuildManager used for managing this build"},
61+
returns='the value of the attribute')
62+
def get_attr_value(self, **kwargs):
63+
''' Get the value of the attribute corresponding to this spec from the given container '''
64+
spec, container, manager = getargs('spec', 'container', 'manager', kwargs)
65+
66+
# handle custom mapping of container Units.waveform_rate -> spec Units.waveform_mean.sampling_rate
67+
if isinstance(container.parent, Units):
68+
if container.name == 'waveform_mean' or container.name == 'waveform_sd':
69+
if spec.name == 'sampling_rate':
70+
return container.parent.waveform_rate
71+
if spec.name == 'unit':
72+
return container.parent.waveform_unit
73+
if container.name == 'spike_times':
74+
if spec.name == 'resolution':
75+
return container.parent.resolution
76+
return super().get_attr_value(**kwargs)

0 commit comments

Comments
 (0)