Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2be97c9
ElectrodesTable
mavaylon1 Apr 18, 2024
ea7d60f
foo
mavaylon1 Apr 18, 2024
fe28745
check
mavaylon1 Apr 18, 2024
5ac7321
file
mavaylon1 Apr 18, 2024
b24d47d
checkpoint
mavaylon1 Apr 25, 2024
45dbe9e
Merge branch 'dev' into electrode
mavaylon1 May 2, 2024
6caa774
Merge branch 'dev' into electrode
mavaylon1 Aug 1, 2024
f00b134
not sure clean up
mavaylon1 Aug 1, 2024
dbfb805
Merge branch 'dev' into electrode
mavaylon1 Sep 25, 2024
be0614c
checkpoint repr works as well as able to write
mavaylon1 Sep 29, 2024
c97a0df
backwards compat seems to work
mavaylon1 Sep 30, 2024
64ad906
draft complete
mavaylon1 Oct 1, 2024
dc1f7b8
cleanup 1
mavaylon1 Oct 1, 2024
c02886b
cleanup 1
mavaylon1 Oct 1, 2024
0cd8edc
clean up 2
mavaylon1 Oct 2, 2024
06cf465
Add note
mavaylon1 Oct 2, 2024
81fa0e6
rebase:
mavaylon1 Feb 27, 2025
6cb8577
Update test_nwbfile.py
mavaylon1 Feb 27, 2025
5785f75
clean up
mavaylon1 Feb 27, 2025
ec3a476
back compat
mavaylon1 Apr 24, 2025
d3c5b3c
Add BaseImage and ExternalImage
rly May 10, 2025
57d7be7
Update changelog
rly May 10, 2025
4509172
Remove unnecessary import
rly May 10, 2025
012e4be
Merge branch 'nwb-schema-2.9.0' into external-image
rly May 10, 2025
db04219
Merge remote-tracking branch 'origin/nwb-schema-2.9.0' into electrode
rly May 10, 2025
c51e7d5
Merge branch 'external-image' into electrode
rly May 10, 2025
1ed1184
Re-add ElectrodeTable class, fix style
rly May 10, 2025
844266a
Add back mock_ElectrodeTable
rly May 10, 2025
17378f7
Fix add_electrode and ElectrodesTable.__init__
rly May 11, 2025
eed1aee
Fix ruff
rly May 11, 2025
c803fb9
Remove accidental test file
rly May 11, 2025
872d369
Merge branch 'external-image' into electrode
rly May 11, 2025
de2fead
Clean up tests
rly May 11, 2025
f9eb256
add test file generation function
stephprince May 14, 2025
f52d327
Fix missing __nwbfields__ for NWBData (#2082)
rly May 14, 2025
ae145c7
Merge branch 'dev' into nwb-schema-2.9.0
rly May 15, 2025
b1c99e6
Merge branch 'nwb-schema-2.9.0' into electrode
rly May 15, 2025
5daeed9
update nwb-schema to point to dev
stephprince May 15, 2025
dac4e02
fix positional argument warnings
stephprince May 15, 2025
0b7ad0f
add test for old ElectrodeTable initialization
stephprince May 15, 2025
4ce3619
Update src/pynwb/io/file.py
rly May 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# PyNWB Changelog

## PyNWB 3.0.1 (February 26, 2025)

### Enhancements
- Formally defined and renamed `ElectrodeTable` as the `ElectrodesTable` neurodata_type. @mavaylon1 [#1890](https://github.com/NeurodataWithoutBorders/pynwb/pull/1890)

## PyNWB 3.0.0 (February 26, 2025)

### Breaking changes
- The validation methods have been updated with multiple breaking changes. @stephprince [#1911](https://github.com/NeurodataWithoutBorders/pynwb/pull/1911)
- The behavior of `pynwb.validate(io=...)` now matches the behavior of `pynwb.validate(path=...)`. In previous pynwb versions, `pynwb.validate(io=...)` did not use the cached namespaces during validation. To obtain the same behavior as in previous versions, you can update the function call to `pynwb.validate(io=..., use_cached_namespaces=False)`
- `pynwb.validate` will return only a list of validation errors instead of a tuple: (list of validation_errors, status code)
- the `pynwb.validate(path=...)` argument has been added as a replacement for `pynwb.validate(paths=[...])`, which will be deprecated in a future major release [#2024](https://github.com/NeurodataWithoutBorders/pynwb/pull/2024)
- The validate module has been renamed to `validation.py`. The validate method can be
- The validate module has been renamed to `validation.py`. The validate method can be
imported using `import pynwb; pynwb.validate` or `from pynwb import validate`

### Deprecations
Expand All @@ -23,7 +28,7 @@
- ``ImageSeries.format`` is fixed to 'external' if an external file is provided.
- ``ImageSeries.bits_per_pixel`` is deprecated.
- ``ImagingPlane.manifold``, ``ImagingPlane.conversion`` and ``ImagingPlane.unit`` are deprecated. Use ``ImagingPlane.origin_coords`` and ``ImagingPlane.grid_spacing`` instead.
- ``IndexSeries.unit`` is fixed to "N\A".
- ``IndexSeries.unit`` is fixed to "N\A".
- ``IndexSeries.indexed_timeseries`` is deprecated. Use ``IndexSeries.indexed_images`` instead.
- The following deprecated methods have been removed:
- ``NWBFile.add_ic_electrode`` is removed. Use ``NWBFile.add_icephys_electrode`` instead.
Expand Down Expand Up @@ -55,7 +60,7 @@
- Updated `SpikeEventSeries`, `DecompositionSeries`, and `FilteredEphys` examples. @stephprince [#2012](https://github.com/NeurodataWithoutBorders/pynwb/pull/2012)
- Replaced deprecated `scipy.misc.face` dataset in the images tutorial with another example. @stephprince [#2016](https://github.com/NeurodataWithoutBorders/pynwb/pull/2016)
- Removed Allen Brain Observatory example which was unnecessary and difficult to maintain. @rly [#2026](https://github.com/NeurodataWithoutBorders/pynwb/pull/2026)

## PyNWB 2.8.3 (November 19, 2024)

### Enhancements and minor changes
Expand Down
56 changes: 55 additions & 1 deletion src/pynwb/ecephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import numpy as np
from collections.abc import Iterable

from hdmf.common import DynamicTableRegion
from hdmf.common import DynamicTableRegion, DynamicTable, VectorData
from hdmf.data_utils import DataChunkIterator, assertEqualShape
from hdmf.utils import docval, popargs, get_docval, popargs_to_dict, get_data_shape, AllowPositional

Expand Down Expand Up @@ -67,6 +67,60 @@ def __init__(self, **kwargs):
setattr(self, key, val)


@register_class('ElectrodesTable', CORE_NAMESPACE)
class ElectrodesTable(DynamicTable):
"""A table of all electrodes (i.e. channels) used for recording. Introduced in NWB 3.0.0. Replaces the "electrodes"
table (neurodata_type_inc DynamicTable, no neurodata_type_def) that is part of NWBFile."""

__columns__ = (
{'name': 'location', 'description': 'Location of the electrode (channel).', 'required': True},
{'name': 'group', 'description': 'Reference to the ElectrodeGroup.', 'required': True},
{'name': 'group_name', 'description': 'Name of the ElectrodeGroup.', 'required': False })

@docval({'name': 'x', 'type': VectorData, 'doc':'x coordinate of the channel location in the brain',
'default': None},
{'name': 'y', 'type': VectorData, 'doc':'y coordinate of the channel location in the brain',
'default': None},
{'name': 'z', 'type': VectorData, 'doc':'z coordinate of the channel location in the brain',
'default': None},
{'name': 'imp', 'type': VectorData, 'doc':'Impedance of the channel, in ohms.', 'default': None},
{'name': 'filtering', 'type': VectorData, 'doc':'Description of hardware filtering.', 'default': None},
{'name': 'rel_x', 'type': VectorData, 'doc':'x coordinate in electrode group', 'default': None},
{'name': 'rel_y', 'type': VectorData, 'doc':'xy coordinate in electrode group', 'default': None},
{'name': 'rel_z', 'type': VectorData, 'doc':'z coordinate in electrode group', 'default': None},
{'name': 'reference', 'type': VectorData, 'default': None,
'doc':'Description of the reference electrode and/or reference scheme used for this electrode'},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'))
def __init__(self, **kwargs):
kwargs['name'] = 'electrodes'
kwargs['description'] = 'metadata about extracellular electrodes'

# optional fields
keys_to_set = (
'x',
'y',
'z',
'imp',
'filtering',
'rel_x',
'rel_y',
'rel_z',
'reference')
args_to_set = popargs_to_dict(keys_to_set, kwargs)
for key, val in args_to_set.items():
setattr(self, key, val)

super().__init__(**kwargs)

def copy(self):
"""
Return a copy of this ElectrodesTable.
This is useful for linking.
"""
kwargs = dict(id=self.id, columns=self.columns, colnames=self.colnames)
return self.__class__(**kwargs)


@register_class('ElectricalSeries', CORE_NAMESPACE)
class ElectricalSeries(TimeSeries):
"""
Expand Down
24 changes: 7 additions & 17 deletions src/pynwb/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .base import TimeSeries, ProcessingModule
from .device import Device
from .epoch import TimeIntervals
from .ecephys import ElectrodeGroup
from .ecephys import ElectrodeGroup, ElectrodesTable
from .icephys import (IntracellularElectrode, SweepTable, PatchClampSeries, IntracellularRecordingsTable,
SimultaneousRecordingsTable, SequentialRecordingsTable, RepetitionsTable,
ExperimentalConditionsTable)
Expand Down Expand Up @@ -389,7 +389,7 @@
{'name': 'lab_meta_data', 'type': (list, tuple), 'default': None,
'doc': 'an extension that contains lab-specific meta-data'},
{'name': 'electrodes', 'type': DynamicTable,
'doc': 'the ElectrodeTable that belongs to this NWBFile', 'default': None},
'doc': 'the ElectrodesTable that belongs to this NWBFile', 'default': None},
{'name': 'electrode_groups', 'type': Iterable,
'doc': 'the ElectrodeGroups that belong to this NWBFile', 'default': None},
{'name': 'ic_electrodes', 'type': (list, tuple),
Expand Down Expand Up @@ -615,7 +615,7 @@

def __check_electrodes(self):
if self.electrodes is None:
self.electrodes = ElectrodeTable()
self.electrodes = ElectrodesTable()

@docval(*get_docval(DynamicTable.add_column), allow_extra=True)
def add_electrode_column(self, **kwargs):
Expand Down Expand Up @@ -709,7 +709,7 @@
for idx in region:
if idx < 0 or idx >= len(self.electrodes):
raise IndexError('The index ' + str(idx) +
' is out of range for the ElectrodeTable of length '
' is out of range for the ElectrodesTable of length '
+ str(len(self.electrodes)))
desc = getargs('description', kwargs)
name = getargs('name', kwargs)
Expand Down Expand Up @@ -791,13 +791,13 @@
self.__check_invalid_times()
self.invalid_times.add_interval(**kwargs)

@docval({'name': 'electrode_table', 'type': DynamicTable, 'doc': 'the ElectrodeTable for this file'})
@docval({'name': 'electrode_table', 'type': ElectrodesTable, 'doc': 'the ElectrodesTable for this file'})
def set_electrode_table(self, **kwargs):
"""
Set the electrode table of this NWBFile to an existing ElectrodeTable
Set the electrode table of this NWBFile to an existing ElectrodesTable
"""
if self.electrodes is not None:
msg = 'ElectrodeTable already exists, cannot overwrite'
msg = 'ElectrodesTable already exists, cannot overwrite'

Check warning on line 800 in src/pynwb/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/file.py#L800

Added line #L800 was not covered by tests
raise ValueError(msg)
electrode_table = getargs('electrode_table', kwargs)
self.electrodes = electrode_table
Expand Down Expand Up @@ -1150,16 +1150,6 @@
return t


def ElectrodeTable(name='electrodes',
description='metadata about extracellular electrodes'):
return _tablefunc(name, description,
[('location', 'the location of channel within the subject e.g. brain region'),
('group', 'a reference to the ElectrodeGroup this electrode is a part of'),
('group_name', 'the name of the ElectrodeGroup this electrode is a part of')
]
)


def TrialTable(name='trials', description='metadata about experimental trials'):
return _tablefunc(name, description, ['start_time', 'stop_time'])

Expand Down
18 changes: 18 additions & 0 deletions src/pynwb/io/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@
ret.append(manager.construct(d))
return tuple(ret) if len(ret) > 0 else None

@ObjectMapper.constructor_arg('electrodes')
def electrodes(self, builder, manager):
try:
electrodes_builder = builder['general']['extracellular_ephys']['electrodes']
except KeyError:
# Note: This is here because the ObjectMapper pulls argname from docval and checks to see
# if there is an override even if the file doesn't have what is looking for. In this case,
# electrodes for NWBFile.
electrodes_builder = None
if (electrodes_builder is not None and electrodes_builder.attributes['neurodata_type'] != 'ElectrodesTable'):
electrodes_builder.attributes['neurodata_type'] = 'ElectrodesTable'
electrodes_builder.attributes['namespace'] = 'core'

Check warning on line 195 in src/pynwb/io/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/io/file.py#L194-L195

Added lines #L194 - L195 were not covered by tests

new_container = manager.construct(electrodes_builder, True)
return new_container

Check warning on line 198 in src/pynwb/io/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/io/file.py#L197-L198

Added lines #L197 - L198 were not covered by tests
else:
return None

@ObjectMapper.constructor_arg('session_start_time')
def dateconversion(self, builder, manager):
"""Set the constructor arg for 'session_start_time' to a datetime object.
Expand Down
12 changes: 6 additions & 6 deletions src/pynwb/testing/mock/ecephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from hdmf.common.table import DynamicTableRegion, DynamicTable

from ...device import Device
from ...file import ElectrodeTable, NWBFile
from ...ecephys import ElectricalSeries, ElectrodeGroup, SpikeEventSeries
from ...file import NWBFile
from ...ecephys import ElectricalSeries, ElectrodeGroup, SpikeEventSeries, ElectrodesTable
from .device import mock_Device
from .utils import name_generator
from ...misc import Units
Expand Down Expand Up @@ -35,10 +35,10 @@ def mock_ElectrodeGroup(
return electrode_group


def mock_ElectrodeTable(
def mock_ElectrodesTable(
n_rows: int = 5, group: Optional[ElectrodeGroup] = None, nwbfile: Optional[NWBFile] = None
) -> DynamicTable:
electrodes_table = ElectrodeTable()
electrodes_table = ElectrodesTable()
group = group if group is not None else mock_ElectrodeGroup(nwbfile=nwbfile)
for i in range(n_rows):
electrodes_table.add_row(
Expand All @@ -57,7 +57,7 @@ def mock_electrodes(
n_electrodes: int = 5, table: Optional[DynamicTable] = None, nwbfile: Optional[NWBFile] = None
) -> DynamicTableRegion:

table = table or mock_ElectrodeTable(n_rows=5, nwbfile=nwbfile)
table = table or mock_ElectrodesTable(n_rows=5, nwbfile=nwbfile)
return DynamicTableRegion(
name="electrodes",
data=list(range(n_electrodes)),
Expand All @@ -80,7 +80,7 @@ def mock_ElectricalSeries(
conversion: float = 1.0,
offset: float = 0.,
) -> ElectricalSeries:

# Set a default rate if timestamps are not provided
rate = 30_000.0 if (timestamps is None and rate is None) else rate
n_electrodes = data.shape[1] if data is not None else 5
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/integration/hdf5/test_ecephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
FeatureExtraction,
)
from pynwb.device import Device
from pynwb.file import ElectrodeTable as get_electrode_table
from pynwb.ecephys import ElectrodesTable as get_electrode_table
from pynwb.testing import NWBH5IOMixin, AcquisitionH5IOMixin, NWBH5IOFlexMixin, TestCase


Expand Down
2 changes: 1 addition & 1 deletion tests/integration/hdf5/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pynwb.testing import NWBH5IOMixin, AcquisitionH5IOMixin, TestCase
from pynwb.ecephys import ElectrodeGroup
from pynwb.device import Device
from pynwb.file import ElectrodeTable as get_electrode_table
from pynwb.ecephys import ElectrodesTable as get_electrode_table


class TestUnitsIO(AcquisitionH5IOMixin, TestCase):
Expand Down
Loading
Loading