Skip to content

Commit

Permalink
Deprecate broken ImagingRetinotopy module (#1813)
Browse files Browse the repository at this point in the history
* Update retinotopy.py

* Remove references to retinotopy module

* Update schema

* Fix test

* Updated changelog

* Update schema submodule

* Update test_retinotopy.py

* Update retinotopy.py

* Update test_retinotopy.py

* Update retinotopy.py

* Update test_retinotopy.py

* Update retinotopy.py

* Update test_retinotopy.py

* RuntimeError -> ImportError

* Update schema

* Revert "Update schema"

This reverts commit 08f3a88.

* Update schema
  • Loading branch information
rly authored Feb 7, 2024
1 parent 5c760da commit ff26456
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 325 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Enhancements and minor changes
- Added support for NWB schema 2.7.0.
- ...
- ...
- Deprecated `ImagingRetinotopy` neurodata type. @rly [#1813](https://github.com/NeurodataWithoutBorders/pynwb/pull/1813)
- Modified `OptogeneticSeries` to allow 2D data, primarily in extensions of `OptogeneticSeries`. @rly [#1812](https://github.com/NeurodataWithoutBorders/pynwb/pull/1812)
- Support `stimulus_template` as optional predefined column in `IntracellularStimuliTable`. @stephprince [#1815](https://github.com/NeurodataWithoutBorders/pynwb/pull/1815)
- ...
Expand Down
5 changes: 2 additions & 3 deletions docs/gallery/general/plot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@
:py:class:`~pynwb.ophys.ImageSegmentation`,
:py:class:`~pynwb.ophys.MotionCorrection`.
* **Others:** :py:class:`~pynwb.retinotopy.ImagingRetinotopy`,
:py:class:`~pynwb.base.Images`.
* **Others:** :py:class:`~pynwb.base.Images`.
* **TimeSeries:** Any :ref:`timeseries_overview` is also a subclass of :py:class:`~pynwb.core.NWBDataInterface`
and can be used anywhere :py:class:`~pynwb.core.NWBDataInterface` is allowed.
Expand Down Expand Up @@ -372,7 +371,7 @@
# Processing modules can be thought of as folders within the file for storing the related processed data.
#
# .. tip:: Use the NWB schema module names as processing module names where appropriate.
# These are: ``"behavior"``, ``"ecephys"``, ``"icephys"``, ``"ophys"``, ``"ogen"``, ``"retinotopy"``, and ``"misc"``.
# These are: ``"behavior"``, ``"ecephys"``, ``"icephys"``, ``"ophys"``, ``"ogen"``, and ``"misc"``.
#
# Let's assume that the subject's position was computed from a video tracking algorithm,
# so it would be classified as processed data.
Expand Down
1 change: 0 additions & 1 deletion docs/source/api_docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ API Documentation
Intracellular Electrophysiology <pynwb.icephys>
Optophysiology <pynwb.ophys>
Optogenetics <pynwb.ogen>
Retinotopy <pynwb.retinotopy>
General Imaging <pynwb.image>
Behavior <pynwb.behavior>
NWB Base Classes <pynwb.base>
Expand Down
1 change: 0 additions & 1 deletion src/pynwb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ def export(self, **kwargs):
from . import misc # noqa: F401,E402
from . import ogen # noqa: F401,E402
from . import ophys # noqa: F401,E402
from . import retinotopy # noqa: F401,E402
from . import legacy # noqa: F401,E402
from hdmf.data_utils import DataChunkIterator # noqa: F401,E402
from hdmf.backends.hdf5 import H5DataIO # noqa: F401,E402
Expand Down
1 change: 0 additions & 1 deletion src/pynwb/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@
from . import misc as __misc
from . import ogen as __ogen
from . import ophys as __ophys
from . import retinotopy as __retinotopy
1 change: 0 additions & 1 deletion src/pynwb/legacy/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@
from . import misc as __misc
from . import ogen as __ogen
from . import ophys as __ophys
from . import retinotopy as __retinotopy
2 changes: 1 addition & 1 deletion src/pynwb/nwb-schema
294 changes: 149 additions & 145 deletions src/pynwb/retinotopy.py
Original file line number Diff line number Diff line change
@@ -1,145 +1,149 @@
import warnings
from collections.abc import Iterable

from hdmf.utils import docval, popargs, get_docval

from . import register_class, CORE_NAMESPACE
from .core import NWBDataInterface, NWBData


class RetinotopyImage(NWBData):
"""Gray-scale anatomical image of cortical surface. Array structure: [rows][columns]
"""

__nwbfields__ = ('bits_per_pixel',
'dimension',
'format',
'field_of_view')

@docval({'name': 'name', 'type': str, 'doc': 'Name of this retinotopy image'},
{'name': 'data', 'type': Iterable, 'doc': 'Data field.'},
{'name': 'bits_per_pixel', 'type': int,
'doc': 'Number of bits used to represent each value. This is necessary to determine maximum '
'(white) pixel value.'},
{'name': 'dimension', 'type': Iterable, 'shape': (2, ), 'doc': 'Number of rows and columns in the image.'},
{'name': 'format', 'type': Iterable, 'doc': 'Format of image. Right now only "raw" supported.'},
{'name': 'field_of_view', 'type': Iterable, 'shape': (2, ), 'doc': 'Size of viewing area, in meters.'})
def __init__(self, **kwargs):
bits_per_pixel, dimension, format, field_of_view = popargs(
'bits_per_pixel', 'dimension', 'format', 'field_of_view', kwargs)
super().__init__(**kwargs)
self.bits_per_pixel = bits_per_pixel
self.dimension = dimension
self.format = format
self.field_of_view = field_of_view


class FocalDepthImage(RetinotopyImage):
"""Gray-scale image taken with same settings/parameters (e.g., focal depth,
wavelength) as data collection. Array format: [rows][columns].
"""

__nwbfields__ = ('focal_depth', )

@docval(*get_docval(RetinotopyImage.__init__),
{'name': 'focal_depth', 'type': float, 'doc': 'Focal depth offset, in meters.'})
def __init__(self, **kwargs):
focal_depth = popargs('focal_depth', kwargs)
super().__init__(**kwargs)
self.focal_depth = focal_depth


class RetinotopyMap(NWBData):
"""Abstract two-dimensional map of responses to stimuli along a single response axis (e.g., altitude)
"""

__nwbfields__ = ('field_of_view',
'dimension')

@docval({'name': 'name', 'type': str, 'doc': 'the name of this axis map'},
{'name': 'data', 'type': Iterable, 'shape': (None, None), 'doc': 'data field.'},
{'name': 'field_of_view', 'type': Iterable, 'shape': (2, ), 'doc': 'Size of viewing area, in meters.'},
{'name': 'dimension', 'type': Iterable, 'shape': (2, ),
'doc': 'Number of rows and columns in the image'})
def __init__(self, **kwargs):
field_of_view, dimension = popargs('field_of_view', 'dimension', kwargs)
super().__init__(**kwargs)
self.field_of_view = field_of_view
self.dimension = dimension


class AxisMap(RetinotopyMap):
"""Abstract two-dimensional map of responses to stimuli along a single response axis (e.g., altitude) with unit
"""

__nwbfields__ = ('unit', )

@docval(*get_docval(RetinotopyMap.__init__, 'name', 'data', 'field_of_view'),
{'name': 'unit', 'type': str, 'doc': 'Unit that axis data is stored in (e.g., degrees)'},
*get_docval(RetinotopyMap.__init__, 'dimension'))
def __init__(self, **kwargs):
unit = popargs('unit', kwargs)
super().__init__(**kwargs)
self.unit = unit


@register_class('ImagingRetinotopy', CORE_NAMESPACE)
class ImagingRetinotopy(NWBDataInterface):
"""
Intrinsic signal optical imaging or widefield imaging for measuring retinotopy. Stores orthogonal
maps (e.g., altitude/azimuth; radius/theta) of responses to specific stimuli and a combined
polarity map from which to identify visual areas.
This group does not store the raw responses imaged during retinotopic mapping or the
stimuli presented, but rather the resulting phase and power maps after applying a Fourier
transform on the averaged responses.
Note: for data consistency, all images and arrays are stored in the format [row][column] and
[row, col], which equates to [y][x]. Field of view and dimension arrays may appear backward
(i.e., y before x).
"""

__nwbfields__ = ({'name': 'sign_map', 'child': True},
{'name': 'axis_1_phase_map', 'child': True},
{'name': 'axis_1_power_map', 'child': True},
{'name': 'axis_2_phase_map', 'child': True},
{'name': 'axis_2_power_map', 'child': True},
{'name': 'focal_depth_image', 'child': True},
{'name': 'vasculature_image', 'child': True},
'axis_descriptions')

@docval({'name': 'sign_map', 'type': RetinotopyMap,
'doc': 'Sine of the angle between the direction of the gradient in axis_1 and axis_2.'},
{'name': 'axis_1_phase_map', 'type': AxisMap,
'doc': 'Phase response to stimulus on the first measured axis.'},
{'name': 'axis_1_power_map', 'type': AxisMap,
'doc': 'Power response on the first measured axis. Response is scaled so 0.0 is no power in '
'the response and 1.0 is maximum relative power.'},
{'name': 'axis_2_phase_map', 'type': AxisMap,
'doc': 'Phase response to stimulus on the second measured axis.'},
{'name': 'axis_2_power_map', 'type': AxisMap,
'doc': 'Power response on the second measured axis. Response is scaled so 0.0 is no '
'power in the response and 1.0 is maximum relative power.'},
{'name': 'axis_descriptions', 'type': Iterable, 'shape': (2, ),
'doc': 'Two-element array describing the contents of the two response axis fields. '
'Description should be something like ["altitude", "azimuth"] or ["radius", "theta"].'},
{'name': 'focal_depth_image', 'type': FocalDepthImage,
'doc': 'Gray-scale image taken with same settings/parameters (e.g., focal depth, wavelength) '
'as data collection. Array format: [rows][columns].'},
{'name': 'vasculature_image', 'type': RetinotopyImage,
'doc': 'Gray-scale anatomical image of cortical surface. Array structure: [rows][columns].'},
{'name': 'name', 'type': str, 'doc': 'the name of this container', 'default': 'ImagingRetinotopy'})
def __init__(self, **kwargs):
axis_1_phase_map, axis_1_power_map, axis_2_phase_map, axis_2_power_map, axis_descriptions, \
focal_depth_image, sign_map, vasculature_image = popargs(
'axis_1_phase_map', 'axis_1_power_map', 'axis_2_phase_map', 'axis_2_power_map',
'axis_descriptions', 'focal_depth_image', 'sign_map', 'vasculature_image', kwargs)
super().__init__(**kwargs)
warnings.warn("The ImagingRetinotopy class currently cannot be written to or read from a file. "
"This is a known bug and will be fixed in a future release of PyNWB.")
self.axis_1_phase_map = axis_1_phase_map
self.axis_1_power_map = axis_1_power_map
self.axis_2_phase_map = axis_2_phase_map
self.axis_2_power_map = axis_2_power_map
self.axis_descriptions = axis_descriptions
self.focal_depth_image = focal_depth_image
self.sign_map = sign_map
self.vasculature_image = vasculature_image
raise ImportError(
"The pynwb.retinotopy module is deprecated. If you are interested in using these neurodata types, "
"please create an issue on https://github.com/NeurodataWithoutBorders/nwb-schema/issues."
)

# import warnings
# from collections.abc import Iterable

# from hdmf.utils import docval, popargs, get_docval

# from . import register_class, CORE_NAMESPACE
# from .core import NWBDataInterface, NWBData


# class RetinotopyImage(NWBData):
# """Gray-scale anatomical image of cortical surface. Array structure: [rows][columns]
# """

# __nwbfields__ = ('bits_per_pixel',
# 'dimension',
# 'format',
# 'field_of_view')

# @docval({'name': 'name', 'type': str, 'doc': 'Name of this retinotopy image'},
# {'name': 'data', 'type': Iterable, 'doc': 'Data field.'},
# {'name': 'bits_per_pixel', 'type': int,
# 'doc': 'Number of bits used to represent each value. This is necessary to determine maximum '
# '(white) pixel value.'},
# {'name': 'dimension', 'type': Iterable, 'shape': (2, ),
# 'doc': 'Number of rows and columns in the image.'},
# {'name': 'format', 'type': Iterable, 'doc': 'Format of image. Right now only "raw" supported.'},
# {'name': 'field_of_view', 'type': Iterable, 'shape': (2, ), 'doc': 'Size of viewing area, in meters.'})
# def __init__(self, **kwargs):
# bits_per_pixel, dimension, format, field_of_view = popargs(
# 'bits_per_pixel', 'dimension', 'format', 'field_of_view', kwargs)
# super().__init__(**kwargs)
# self.bits_per_pixel = bits_per_pixel
# self.dimension = dimension
# self.format = format
# self.field_of_view = field_of_view


# class FocalDepthImage(RetinotopyImage):
# """Gray-scale image taken with same settings/parameters (e.g., focal depth,
# wavelength) as data collection. Array format: [rows][columns].
# """

# __nwbfields__ = ('focal_depth', )

# @docval(*get_docval(RetinotopyImage.__init__),
# {'name': 'focal_depth', 'type': float, 'doc': 'Focal depth offset, in meters.'})
# def __init__(self, **kwargs):
# focal_depth = popargs('focal_depth', kwargs)
# super().__init__(**kwargs)
# self.focal_depth = focal_depth


# class RetinotopyMap(NWBData):
# """Abstract two-dimensional map of responses to stimuli along a single response axis (e.g., altitude)
# """

# __nwbfields__ = ('field_of_view',
# 'dimension')

# @docval({'name': 'name', 'type': str, 'doc': 'the name of this axis map'},
# {'name': 'data', 'type': Iterable, 'shape': (None, None), 'doc': 'data field.'},
# {'name': 'field_of_view', 'type': Iterable, 'shape': (2, ), 'doc': 'Size of viewing area, in meters.'},
# {'name': 'dimension', 'type': Iterable, 'shape': (2, ),
# 'doc': 'Number of rows and columns in the image'})
# def __init__(self, **kwargs):
# field_of_view, dimension = popargs('field_of_view', 'dimension', kwargs)
# super().__init__(**kwargs)
# self.field_of_view = field_of_view
# self.dimension = dimension


# class AxisMap(RetinotopyMap):
# """Abstract two-dimensional map of responses to stimuli along a single response axis (e.g., altitude) with unit
# """

# __nwbfields__ = ('unit', )

# @docval(*get_docval(RetinotopyMap.__init__, 'name', 'data', 'field_of_view'),
# {'name': 'unit', 'type': str, 'doc': 'Unit that axis data is stored in (e.g., degrees)'},
# *get_docval(RetinotopyMap.__init__, 'dimension'))
# def __init__(self, **kwargs):
# unit = popargs('unit', kwargs)
# super().__init__(**kwargs)
# self.unit = unit


# @register_class('ImagingRetinotopy', CORE_NAMESPACE)
# class ImagingRetinotopy(NWBDataInterface):
# """
# Intrinsic signal optical imaging or widefield imaging for measuring retinotopy. Stores orthogonal
# maps (e.g., altitude/azimuth; radius/theta) of responses to specific stimuli and a combined
# polarity map from which to identify visual areas.
# This group does not store the raw responses imaged during retinotopic mapping or the
# stimuli presented, but rather the resulting phase and power maps after applying a Fourier
# transform on the averaged responses.
# Note: for data consistency, all images and arrays are stored in the format [row][column] and
# [row, col], which equates to [y][x]. Field of view and dimension arrays may appear backward
# (i.e., y before x).
# """

# __nwbfields__ = ({'name': 'sign_map', 'child': True},
# {'name': 'axis_1_phase_map', 'child': True},
# {'name': 'axis_1_power_map', 'child': True},
# {'name': 'axis_2_phase_map', 'child': True},
# {'name': 'axis_2_power_map', 'child': True},
# {'name': 'focal_depth_image', 'child': True},
# {'name': 'vasculature_image', 'child': True},
# 'axis_descriptions')

# @docval({'name': 'sign_map', 'type': RetinotopyMap,
# 'doc': 'Sine of the angle between the direction of the gradient in axis_1 and axis_2.'},
# {'name': 'axis_1_phase_map', 'type': AxisMap,
# 'doc': 'Phase response to stimulus on the first measured axis.'},
# {'name': 'axis_1_power_map', 'type': AxisMap,
# 'doc': 'Power response on the first measured axis. Response is scaled so 0.0 is no power in '
# 'the response and 1.0 is maximum relative power.'},
# {'name': 'axis_2_phase_map', 'type': AxisMap,
# 'doc': 'Phase response to stimulus on the second measured axis.'},
# {'name': 'axis_2_power_map', 'type': AxisMap,
# 'doc': 'Power response on the second measured axis. Response is scaled so 0.0 is no '
# 'power in the response and 1.0 is maximum relative power.'},
# {'name': 'axis_descriptions', 'type': Iterable, 'shape': (2, ),
# 'doc': 'Two-element array describing the contents of the two response axis fields. '
# 'Description should be something like ["altitude", "azimuth"] or ["radius", "theta"].'},
# {'name': 'focal_depth_image', 'type': FocalDepthImage,
# 'doc': 'Gray-scale image taken with same settings/parameters (e.g., focal depth, wavelength) '
# 'as data collection. Array format: [rows][columns].'},
# {'name': 'vasculature_image', 'type': RetinotopyImage,
# 'doc': 'Gray-scale anatomical image of cortical surface. Array structure: [rows][columns].'},
# {'name': 'name', 'type': str, 'doc': 'the name of this container', 'default': 'ImagingRetinotopy'})
# def __init__(self, **kwargs):
# axis_1_phase_map, axis_1_power_map, axis_2_phase_map, axis_2_power_map, axis_descriptions, \
# focal_depth_image, sign_map, vasculature_image = popargs(
# 'axis_1_phase_map', 'axis_1_power_map', 'axis_2_phase_map', 'axis_2_power_map',
# 'axis_descriptions', 'focal_depth_image', 'sign_map', 'vasculature_image', kwargs)
# super().__init__(**kwargs)
# self.axis_1_phase_map = axis_1_phase_map
# self.axis_1_power_map = axis_1_power_map
# self.axis_2_phase_map = axis_2_phase_map
# self.axis_2_power_map = axis_2_power_map
# self.axis_descriptions = axis_descriptions
# self.focal_depth_image = focal_depth_image
# self.sign_map = sign_map
# self.vasculature_image = vasculature_image
1 change: 0 additions & 1 deletion tests/back_compat/test_import_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def test_outer_import_structure(self):
"popargs",
"register_class",
"register_map",
"retinotopy",
"spec",
"testing",
"validate",
Expand Down
Loading

0 comments on commit ff26456

Please sign in to comment.