diff --git a/CHANGELOG.md b/CHANGELOG.md index ce200d012..229604909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ [#1586](https://github.com/NeurodataWithoutBorders/pynwb/pull/1586) - More informative error message for common installation error. @bendichter, @rly [#1591](https://github.com/NeurodataWithoutBorders/pynwb/pull/1591) +- Update the [images tutorial](https://pynwb.readthedocs.io/en/stable/tutorials/domain/images.html) to provide example usage of an ``IndexSeries`` + with a reference to ``Images``. @bendichter [#1602](https://github.com/NeurodataWithoutBorders/pynwb/pull/1602) ## PyNWB 2.2.0 (October 19, 2022) diff --git a/docs/gallery/domain/images.py b/docs/gallery/domain/images.py index 0705cac1d..ab0b4ac50 100644 --- a/docs/gallery/domain/images.py +++ b/docs/gallery/domain/images.py @@ -245,6 +245,58 @@ nwbfile.add_acquisition(images) #################### +# IndexSeries for repeated images +# ------------------------------- +# +# You may want to set up a time series of images where some images are repeated many +# times. You could create an :py:class:`~pynwb.image.ImageSeries` that repeats the data +# each time the image is shown, but that would be inefficient, becuase it would store +# the same data multiple times. A better solution would be to store the unique images +# once and reference those images. This is how :py:class:`~pynwb.image.IndexSeries` +# works. First, create an :py:class:`~pynwb.base.Images` container with the order of +# images defined using an :py:class:`~pynwb.base.ImageReferences`. Then create an +# :py:class:`~pynwb.image.IndexSeries` that indexes into the +# :py:class:`~pynwb.base.Images`. + +from scipy import misc + +from pynwb.image import Images, IndexSeries, GrayscaleImage, RGBImage +from pynwb.base import ImageReferences + + +gs_face = GrayscaleImage( + name="gs_face", + data=misc.face(gray=True), + description="Grayscale version of a raccoon.", + resolution=35.433071, +) + +rgb_face = RGBImage( + name="rgb_face", + data=misc.face(), + resolution=70.0, + description="RGB version of a raccoon.", +) + +images = Images( + name="raccoons", + images=[rgb_face, gs_face], + description="A collection of raccoons.", + order_of_images=ImageReferences("order_of_images", [rgb_face, gs_face]), +) + +idx_series = IndexSeries( + name="stimuli", + data=[0, 1, 0, 1], + indexed_images=images, + unit="N/A", + timestamps=[.1, .2, .3, .4], +) + +#################### +# Here `data` contains the (0-indexed) index of the displayed image as they are ordered +# in the :py:class:`~pynwb.base.ImageReference`. +# # Writing the images to an NWB File # --------------------------------------- # As demonstrated in the :ref:`basic_writing` tutorial, we will use :py:class:`~pynwb.NWBHDF5IO` diff --git a/src/pynwb/image.py b/src/pynwb/image.py index c399289ed..0295183b8 100644 --- a/src/pynwb/image.py +++ b/src/pynwb/image.py @@ -212,23 +212,46 @@ class IndexSeries(TimeSeries): array indicates when that image was displayed. ''' - __nwbfields__ = ('indexed_timeseries',) + __nwbfields__ = ("indexed_timeseries",) # # value used when an ImageSeries is read and missing data # DEFAULT_UNIT = 'N/A' - @docval(*get_docval(TimeSeries.__init__, 'name'), # required - {'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': (None, ), # required - 'doc': ('The data values. Must be 1D, where the first dimension must be time (frame)')}, - *get_docval(TimeSeries.__init__, 'unit'), # required - {'name': 'indexed_timeseries', 'type': TimeSeries, # required - 'doc': 'Link to TimeSeries containing images that are indexed.', 'default': None}, - {'name': 'indexed_images', 'type': Images, # required - 'doc': ("Link to Images object containing an ordered set of images that are indexed. The Images object " - "must contain a 'ordered_images' dataset specifying the order of the images in the Images type."), - 'default': None}, - *get_docval(TimeSeries.__init__, 'resolution', 'conversion', 'timestamps', 'starting_time', 'rate', - 'comments', 'description', 'control', 'control_description', 'offset')) + @docval( + *get_docval(TimeSeries.__init__, 'name'), # required + { + 'name': 'data', + 'type': ('array_data', 'data', TimeSeries), + 'shape': (None,), # required + 'doc': 'The data values. Must be 1D, where the first dimension must be time (frame)', + }, + *get_docval(TimeSeries.__init__, 'unit'), # required + { + 'name': 'indexed_timeseries', 'type': TimeSeries, # required + 'doc': 'Link to TimeSeries containing images that are indexed.', + 'default': None, + }, + { + 'name': 'indexed_images', + 'type': Images, # required + 'doc': "Link to Images object containing an ordered set of images that are indexed. The Images object must " + "contain a 'ordered_images' dataset specifying the order of the images in the Images type.", + 'default': None + }, + *get_docval( + TimeSeries.__init__, + 'resolution', + 'conversion', + 'timestamps', + 'starting_time', + 'rate', + 'comments', + 'description', + 'control', + 'control_description', + 'offset', + ), + ) def __init__(self, **kwargs): indexed_timeseries, indexed_images = popargs('indexed_timeseries', 'indexed_images', kwargs) if kwargs['unit'] and kwargs['unit'] != 'N/A':