diff --git a/docs/gallery/domain/ophys.py b/docs/gallery/domain/ophys.py index 7a2413b21..c621906b8 100644 --- a/docs/gallery/domain/ophys.py +++ b/docs/gallery/domain/ophys.py @@ -47,13 +47,13 @@ optical_channel = OpticalChannel('my_optchan', 'Ca2+ imaging example', - 'pi wavelength', '3.14') + 'pi wavelength', 500.) imaging_plane = nwbfile.create_imaging_plane('my_imgpln', 'Ca2+ imaging example', optical_channel, 'a very interesting part of the brain', 'imaging_device_1', - '6.28', '2.718', 'GFP', 'my favorite brain location', + 600., '2.718', 'GFP', 'my favorite brain location', (1, 2, 1, 2, 3), 4.0, 'manifold unit', 'A frame to refer to') diff --git a/docs/gallery/general/linking_data.py b/docs/gallery/general/linking_data.py index af75bee7f..89a7c951f 100644 --- a/docs/gallery/general/linking_data.py +++ b/docs/gallery/general/linking_data.py @@ -252,5 +252,5 @@ # # External links are convenient but to share data we may want to hand a single file with all the # data to our collaborator rather than having to collect all relevant files. To do this, -# :py:class:`~pynwb.from.backends.hdf5.h5tools.HDF5IO` (and in turn :py:class:`~pynwb.NWBHDF5IO`) -# provide the convenience function :py:func:`~pynwb.from.backends.hdf5.h5tools.HDF5IO.copy_file` +# :py:class:`~pynwb.form.backends.hdf5.h5tools.HDF5IO` (and in turn :py:class:`~pynwb.NWBHDF5IO`) +# provide the convenience function :py:func:`~pynwb.form.backends.hdf5.h5tools.HDF5IO.copy_file` diff --git a/src/pynwb/data/nwb.file.yaml b/src/pynwb/data/nwb.file.yaml index 9743f4f8f..aff5a6a87 100644 --- a/src/pynwb/data/nwb.file.yaml +++ b/src/pynwb/data/nwb.file.yaml @@ -59,6 +59,10 @@ groups: end users. Such data should be placed in the analysis group. The analysis data should be documented so that it is sharable with other labs' name: analysis + groups: + - doc: Custom analysis results + neurodata_type_inc: NWBContainer + quantity: '*' - doc: "Experimental intervals, whether that be logically distinct sub-experiments\ \ having a particular scientific goal, trials during an experiment, or epochs\ \ deriving from analysis of data. COMMENT: Epochs provide pointers to time\ diff --git a/src/pynwb/data/nwb.ophys.yaml b/src/pynwb/data/nwb.ophys.yaml index 9a1ae2afb..481ab3bbe 100644 --- a/src/pynwb/data/nwb.ophys.yaml +++ b/src/pynwb/data/nwb.ophys.yaml @@ -227,7 +227,7 @@ groups: dtype: text name: device - doc: Excitation wavelength - dtype: text + dtype: float name: excitation_lambda - doc: Rate images are acquired, in Hz. dtype: text @@ -283,7 +283,7 @@ groups: dtype: text name: description - doc: Emission lambda for channel - dtype: text + dtype: float name: emission_lambda doc: 'One of possibly many groups storing channel-specific data COMMENT: Name is arbitrary but should be meaningful' diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 15b40c6ab..1db4a6eec 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -81,6 +81,12 @@ class NWBFile(MultiContainerInterface): 'type': NWBDataInterface, 'get': 'get_acquisition' }, + { + 'attr': 'analysis', + 'add': 'add_analysis', + 'type': NWBContainer, + 'get': 'get_analysis' + }, { 'attr': 'stimulus', 'add': 'add_stimulus', diff --git a/src/pynwb/form/container.py b/src/pynwb/form/container.py index 08fd1f92f..0bef25a1c 100644 --- a/src/pynwb/form/container.py +++ b/src/pynwb/form/container.py @@ -9,7 +9,10 @@ class Container(with_metaclass(abc.ABCMeta, object)): {'name': 'parent', 'type': 'Container', 'doc': 'the Container that holds this Container', 'default': None}, {'name': 'container_source', 'type': str, 'doc': 'the source of this container', 'default': None}) def __init__(self, **kwargs): - self.__name = getargs('name', kwargs) + name = getargs('name', kwargs) + if '/' in name: + raise ValueError("name '" + name + "' cannot contain '/'") + self.__name = name self.__parent = getargs('parent', kwargs) self.__container_source = getargs('container_source', kwargs) self.__children = list() diff --git a/src/pynwb/ophys.py b/src/pynwb/ophys.py index 24c734958..f76769710 100644 --- a/src/pynwb/ophys.py +++ b/src/pynwb/ophys.py @@ -23,7 +23,7 @@ class OpticalChannel(NWBContainer): @docval({'name': 'name', 'type': str, 'doc': 'the name of this electrode'}, {'name': 'source', 'type': str, 'doc': 'the source of the data'}, {'name': 'description', 'type': str, 'doc': 'Any notes or comments about the channel.'}, - {'name': 'emission_lambda', 'type': str, 'doc': 'Emission lambda for channel.'}, + {'name': 'emission_lambda', 'type': float, 'doc': 'Emission lambda for channel.'}, {'name': 'parent', 'type': 'NWBContainer', 'doc': 'The parent NWBContainer for this NWBContainer', 'default': None}) def __init__(self, **kwargs): @@ -57,11 +57,11 @@ class ImagingPlane(NWBContainer): 'doc': 'One of possibly many groups storing channelspecific data.'}, {'name': 'description', 'type': str, 'doc': 'Description of this ImagingPlane.'}, {'name': 'device', 'type': str, 'doc': 'Name of device in /general/devices'}, - {'name': 'excitation_lambda', 'type': str, 'doc': 'Excitation wavelength.'}, + {'name': 'excitation_lambda', 'type': float, 'doc': 'Excitation wavelength.'}, {'name': 'imaging_rate', 'type': str, 'doc': 'Rate images are acquired, in Hz.'}, {'name': 'indicator', 'type': str, 'doc': 'Calcium indicator'}, {'name': 'location', 'type': str, 'doc': 'Location of image plane.'}, - {'name': 'manifold', 'type': Iterable, 'doc': 'Physical position of each pixel. height, weight, x, y, z.', + {'name': 'manifold', 'type': Iterable, 'doc': 'Physical position of each pixel. height, weight, xyz.', 'default': None}, {'name': 'conversion', 'type': float, 'doc': 'Multiplier to get from stored values to specified unit (e.g., 1e-3 for millimeters)', diff --git a/tests/integration/ui_write/test_ophys.py b/tests/integration/ui_write/test_ophys.py index ca35dc3c9..22c7362e4 100644 --- a/tests/integration/ui_write/test_ophys.py +++ b/tests/integration/ui_write/test_ophys.py @@ -23,9 +23,9 @@ class TestImagingPlaneIO(base.TestMapRoundTrip): def setUpContainer(self): self.optical_channel = OpticalChannel('optchan1', 'unit test TestImagingPlaneIO', - 'a fake OpticalChannel', '3.14') + 'a fake OpticalChannel', 500.) return ImagingPlane('imgpln1', 'unit test TestImagingPlaneIO', self.optical_channel, - 'a fake ImagingPlane', 'imaging_device_1', '6.28', '2.718', 'GFP', 'somewhere in the brain') + 'a fake ImagingPlane', 'imaging_device_1', 600., '2.718', 'GFP', 'somewhere in the brain') def setUpBuilder(self): optchan_builder = GroupBuilder( @@ -37,7 +37,7 @@ def setUpBuilder(self): 'source': 'unit test TestImagingPlaneIO'}, datasets={ 'description': DatasetBuilder('description', 'a fake OpticalChannel'), - 'emission_lambda': DatasetBuilder('emission_lambda', '3.14')}, + 'emission_lambda': DatasetBuilder('emission_lambda', 500.)}, ) return GroupBuilder( 'imgpln1', @@ -49,7 +49,7 @@ def setUpBuilder(self): datasets={ 'description': DatasetBuilder('description', 'a fake ImagingPlane'), 'device': DatasetBuilder('device', 'imaging_device_1'), - 'excitation_lambda': DatasetBuilder('excitation_lambda', '6.28'), + 'excitation_lambda': DatasetBuilder('excitation_lambda', 600.), 'imaging_rate': DatasetBuilder('imaging_rate', '2.718'), 'indicator': DatasetBuilder('indicator', 'GFP'), 'location': DatasetBuilder('location', 'somewhere in the brain')}, @@ -70,10 +70,10 @@ def getContainer(self, nwbfile): class TestTwoPhotonSeries(base.TestDataInterfaceIO): def make_imaging_plane(self, source): - self.optical_channel = OpticalChannel('optchan1', source, 'a fake OpticalChannel', '3.14') + self.optical_channel = OpticalChannel('optchan1', source, 'a fake OpticalChannel', 500.) self.imaging_plane = ImagingPlane('imgpln1', source, self.optical_channel, 'a fake ImagingPlane', - 'imaging_device_1', '6.28', '2.718', 'GFP', 'somewhere in the brain') + 'imaging_device_1', 600., '2.718', 'GFP', 'somewhere in the brain') def setUpContainer(self): self.make_imaging_plane('unit test TestTwoPhotonSeries') @@ -95,7 +95,7 @@ def setUpBuilder(self): 'source': 'unit test TestTwoPhotonSeries'}, datasets={ 'description': DatasetBuilder('description', 'a fake OpticalChannel'), - 'emission_lambda': DatasetBuilder('emission_lambda', '3.14')}, + 'emission_lambda': DatasetBuilder('emission_lambda', 500.)}, ) imgpln_builder = GroupBuilder( 'imgpln1', @@ -107,7 +107,7 @@ def setUpBuilder(self): datasets={ 'description': DatasetBuilder('description', 'a fake ImagingPlane'), 'device': DatasetBuilder('device', 'imaging_device_1'), - 'excitation_lambda': DatasetBuilder('excitation_lambda', '6.28'), + 'excitation_lambda': DatasetBuilder('excitation_lambda', 600.), 'imaging_rate': DatasetBuilder('imaging_rate', '2.718'), 'indicator': DatasetBuilder('indicator', 'GFP'), 'location': DatasetBuilder('location', 'somewhere in the brain')}, @@ -167,13 +167,13 @@ def buildPlaneSegmentation(self): starting_frame=[1, 2, 3], format='tiff', timestamps=list()) self.optical_channel = OpticalChannel('test_optical_channel', 'optical channel source', - 'optical channel description', '3.14') + 'optical channel description', 500.) self.imaging_plane = ImagingPlane('test_imaging_plane', 'ophys integration tests', self.optical_channel, 'imaging plane description', 'imaging_device_1', - '6.28', '2.718', 'GFP', 'somewhere in the brain', + 600., '2.718', 'GFP', 'somewhere in the brain', (1, 2, 1, 2, 3), 4.0, 'manifold unit', 'A frame to refer to') self.img_mask = deepcopy(img_mask) @@ -195,7 +195,7 @@ def get_plane_segmentation_builder(self): 'source': 'optical channel source'}, datasets={ 'description': DatasetBuilder('description', 'optical channel description'), - 'emission_lambda': DatasetBuilder('emission_lambda', '3.14')}, + 'emission_lambda': DatasetBuilder('emission_lambda', 500.)}, ) self.imgpln_builder = GroupBuilder( 'imgpln1', @@ -207,7 +207,7 @@ def get_plane_segmentation_builder(self): datasets={ 'description': DatasetBuilder('description', 'imaging plane description'), 'device': DatasetBuilder('device', 'imaging_device_1'), - 'excitation_lambda': DatasetBuilder('excitation_lambda', '6.28'), + 'excitation_lambda': DatasetBuilder('excitation_lambda', 600.), 'imaging_rate': DatasetBuilder('imaging_rate', '2.718'), 'indicator': DatasetBuilder('indicator', 'GFP'), 'manifold': DatasetBuilder('manifold', (1, 2, 1, 2, 3), diff --git a/tests/unit/form_tests/test_container.py b/tests/unit/form_tests/test_container.py new file mode 100644 index 000000000..58ee3ce6f --- /dev/null +++ b/tests/unit/form_tests/test_container.py @@ -0,0 +1,52 @@ +import unittest2 as unittest + +from pynwb.form.container import Container + + +class MyTestClass(Container): + + def __init__(self, src, name, parent=None): + super(MyTestClass, self).__init__(src, name, parent=parent) + + def basic_add(self, **kwargs): + return kwargs + + def basic_add2(self, **kwargs): + return kwargs + + def basic_add2_kw(self, **kwargs): + return kwargs + + +class MyTestSubclass(MyTestClass): + + def basic_add(self, **kwargs): + return kwargs + + def basic_add2_kw(self, **kwargs): + return kwargs + + +class TestNWBContainer(unittest.TestCase): + + def test_constructor(self): + """Test that constructor properly sets parent + """ + parent_obj = MyTestClass('test source', 'obj1') + child_obj = MyTestSubclass('test source', 'obj2', parent=parent_obj) + self.assertIs(child_obj.parent, parent_obj) + + def test_set_parent_parent(self): + """Test that parent setter properly sets parent + """ + parent_obj = MyTestClass('test source', 'obj1') + child_obj = MyTestSubclass('test source', 'obj2') + child_obj.parent = parent_obj + self.assertIs(child_obj.parent, parent_obj) + + def test_slash_restriction(self): + self.assertRaises(ValueError, Container, 'bad/name') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/pynwb_tests/test_ophys.py b/tests/unit/pynwb_tests/test_ophys.py index f236ae77e..7661f76ad 100644 --- a/tests/unit/pynwb_tests/test_ophys.py +++ b/tests/unit/pynwb_tests/test_ophys.py @@ -17,8 +17,8 @@ def CreatePlaneSegmentation(): iSS = ImageSeries(name='test_iS', source='a hypothetical source', data=list(), unit='unit', external_file=['external_file'], starting_frame=[1, 2, 3], format='tiff', timestamps=list()) - oc = OpticalChannel('test_optical_channel', 'test_source', 'description', 'emission_lambda') - ip = ImagingPlane('test_imaging_plane', 'test_source', oc, 'description', 'device', 'excitation_lambda', + oc = OpticalChannel('test_optical_channel', 'test_source', 'description', 500.) + ip = ImagingPlane('test_imaging_plane', 'test_source', oc, 'description', 'device', 600., 'imaging_rate', 'indicator', 'location', (1, 2, 1, 2, 3), 4.0, 'unit', 'reference_frame') pS = PlaneSegmentation('test source', 'description', ip, 'test_name', iSS) @@ -29,19 +29,19 @@ def CreatePlaneSegmentation(): class TwoPhotonSeriesConstructor(unittest.TestCase): def test_init(self): - oc = OpticalChannel('test_name', 'test_source', 'description', 'emission_lambda') + oc = OpticalChannel('test_name', 'test_source', 'description', 500.) self.assertEqual(oc.description, 'description') - self.assertEqual(oc.emission_lambda, 'emission_lambda') + self.assertEqual(oc.emission_lambda, 500.) - ip = ImagingPlane('test_imaging_plane', 'test source', oc, 'description', 'device', 'excitation_lambda', - 'imaging_rate', 'indicator', 'location', (1, 2, 1, 2, 3), 4.0, 'unit', 'reference_frame') + ip = ImagingPlane('test_imaging_plane', 'test source', oc, 'description', 'device', 600., + 'imaging_rate', 'indicator', 'location', (50, 100, 3), 4.0, 'unit', 'reference_frame') self.assertEqual(ip.optical_channel[0], oc) self.assertEqual(ip.device, 'device') - self.assertEqual(ip.excitation_lambda, 'excitation_lambda') + self.assertEqual(ip.excitation_lambda, 600.) self.assertEqual(ip.imaging_rate, 'imaging_rate') self.assertEqual(ip.indicator, 'indicator') self.assertEqual(ip.location, 'location') - self.assertEqual(ip.manifold, (1, 2, 1, 2, 3)) + self.assertEqual(ip.manifold, (50, 100, 3)) self.assertEqual(ip.conversion, 4.0) self.assertEqual(ip.unit, 'unit') self.assertEqual(ip.reference_frame, 'reference_frame') @@ -145,8 +145,8 @@ def test_init(self): iSS = ImageSeries(name='test_iS', source='a hypothetical source', data=list(), unit='unit', external_file=['external_file'], starting_frame=[1, 2, 3], format='tiff', timestamps=list()) - oc = OpticalChannel('test_optical_channel', 'test_source', 'description', 'emission_lambda') - ip = ImagingPlane('test_imaging_plane', 'test_source', oc, 'description', 'device', 'excitation_lambda', + oc = OpticalChannel('test_optical_channel', 'test_source', 'description', 500.) + ip = ImagingPlane('test_imaging_plane', 'test_source', oc, 'description', 'device', 600., 'imaging_rate', 'indicator', 'location', (1, 2, 1, 2, 3), 4.0, 'unit', 'reference_frame') pS = PlaneSegmentation('test source', 'description', ip, 'test_name', iSS)