Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Fixed reading a file whose dates carry a sub-minute UTC offset (e.g. `1900-10-01T00:00:00-05:50:36`). @h-mayorquin [#2230](https://github.com/NeurodataWithoutBorders/pynwb/pull/2230)
- Fixed wide pandas DataFrames in the tutorials spilling out of the content column and into the right margin. @bendichter [#2236](https://github.com/NeurodataWithoutBorders/pynwb/pull/2236)
- Fixed `set_data_io` being silently ignored on `NWBData` subclasses (`GrayscaleImage`, `RGBImage`, `RGBAImage`, `ExternalImage`, `ImageReferences`, and `ScratchData`), so requested chunking and compression were dropped without warning and the datasets were written uncompressed. @h-mayorquin [#2233](https://github.com/NeurodataWithoutBorders/pynwb/pull/2233)
- Fixed the `Units` waveforms test fixtures, which labelled the dimensions of the 3-D `add_unit` waveforms input as `(num_electrodes, num_spikes, num_samples)` when `add_unit` reads them as `(num_spikes, num_electrodes, num_samples)`, and so encoded a number of spike events that disagreed with the unit's `spike_times`. Added `TestUnitsIO.test_waveforms_structure` asserting `waveforms_index_index`, `waveforms_index`, and the 2-D `waveforms` dataset after a roundtrip. @adityasingh2400 [#2240](https://github.com/NeurodataWithoutBorders/pynwb/pull/2240)


## PyNWB 4.1.0 (July 23, 2026)
Expand Down
78 changes: 44 additions & 34 deletions tests/integration/hdf5/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,30 @@ class TestUnitsIO(AcquisitionH5IOMixin, TestCase):

def setUpContainer(self):
""" Return the test Units to read/write """
# A 3-D waveforms input to add_unit is ordered (num_spikes, num_electrodes, num_samples).
# Dim 0 indexes spike events and becomes waveforms_index_index, dim 1 indexes the electrodes
# that observed each spike event and becomes waveforms_index, and dim 2 holds the samples of
# each waveform. Every unit below has one waveform per electrode per spike time, so the
# number of spike events matches the number of spike_times.
ut = Units(name='UnitsTest', description='a simple table for testing Units')
ut.add_unit(spike_times=[0., 1., 2.], obs_intervals=[[0., 1.], [2., 3.]],
waveform_mean=[1., 2., 3.], waveform_sd=[4., 5., 6.],
waveforms=[
[ # elec 1
[1, 2, 3],
[1, 2, 3],
[1, 2, 3]
], [ # elec 2
[1, 2, 3],
[1, 2, 3],
[1, 2, 3]
waveforms=[ # 3 spike times, 2 electrodes, 3 samples
[ # spike 1
[1, 2, 3], # elec 1, [sample 1, sample 2, sample 3]
[4, 5, 6] # elec 2
], [ # spike 2
[7, 8, 9],
[10, 11, 12]
], [ # spike 3
[13, 14, 15],
[16, 17, 18]
]
])
ut.add_unit(spike_times=[3., 4., 5.], obs_intervals=[[2., 5.], [6., 7.]],
waveform_mean=[1., 2., 3.], waveform_sd=[4., 5., 6.],
waveforms=np.array([
[ # elec 1
[1, 2, 3], # spike 1, [sample 1, sample 2, sample 3]
[1, 2, 3], # spike 2
[1, 2, 3], # spike 3
[1, 2, 3] # spike 4
], [ # elec 2
[1, 2, 3], # spike 1
[1, 2, 3], # spike 2
[1, 2, 3], # spike 3
[1, 2, 3] # spike 4
], [ # elec 3
[1, 2, 3], # spike 1
[1, 2, 3], # spike 2
[1, 2, 3], # spike 3
[1, 2, 3] # spike 4
]
]))
# 3 spike times, 4 electrodes, 3 samples, continuing the sample values above
waveforms=np.arange(19, 55).reshape(3, 4, 3))
ut.waveform_rate = 40000.
ut.resolution = 1/40000
return ut
Expand All @@ -71,6 +61,24 @@ def test_get_obs_intervals(self):
np.testing.assert_array_equal(received, [[2., 5.], [6., 7.]])
np.testing.assert_array_equal(ut['obs_intervals'][:], [[[0., 1.], [2., 3.]], [[2., 5.], [6., 7.]]])

def test_waveforms_structure(self):
""" Test the structure of the doubly indexed waveforms column read from file """
ut = self.roundtripContainer()
waveforms_index_index = ut['waveforms']
waveforms_index = waveforms_index_index.target
waveforms = waveforms_index.target

# waveforms_index_index holds the number of spike events of each unit
np.testing.assert_array_equal(waveforms_index_index.data[:], [3, 6])
# waveforms_index holds the number of waveforms, one per electrode, of each spike event
np.testing.assert_array_equal(waveforms_index.data[:], [2, 4, 6, 10, 14, 18])
# the waveforms dataset itself is 2-D, (num_waveforms, num_samples)
np.testing.assert_array_equal(waveforms.data[:], np.arange(1, 55).reshape(18, 3))

# unit 0 has 2 electrodes per spike event, unit 1 has 4
self.assertEqual([len(spike_event) for spike_event in waveforms_index_index[0]], [2, 2, 2])
self.assertEqual([len(spike_event) for spike_event in waveforms_index_index[1]], [4, 4, 4])


class TestUnitsWaveformsOnlyIO(AcquisitionH5IOMixin, TestCase):
"""Test roundtripping waveform metadata when only waveforms are present."""
Expand All @@ -79,27 +87,29 @@ def setUpContainer(self):
ut = Units(name='UnitsWaveformsOnlyTest', description='a simple table for testing Units waveforms')
ut.add_unit(
spike_times=[0., 1., 2.],
waveforms=[
[
[1, 2, 3],
waveforms=[ # 3 spike times, 2 electrodes, 3 samples
[ # spike 1
[1, 2, 3], # elec 1, [sample 1, sample 2, sample 3]
[1, 2, 3] # elec 2
], [ # spike 2
[1, 2, 3],
[1, 2, 3]
], [
[1, 2, 3],
], [ # spike 3
[1, 2, 3],
[1, 2, 3]
]
]
)
ut.add_unit(
spike_times=[3., 4., 5.],
waveforms=np.array([
waveforms=np.array([ # 3 spike times, 2 electrodes, 3 samples
[
[1, 2, 3],
[1, 2, 3],
[1, 2, 3]
], [
[1, 2, 3],
[1, 2, 3]
], [
[1, 2, 3],
[1, 2, 3]
]
Expand Down