Skip to content

Commit

Permalink
Detect mismatch errors between group and group names when writing `El…
Browse files Browse the repository at this point in the history
…ectrodeGroups` (#1165)
  • Loading branch information
h-mayorquin authored Jan 15, 2025
1 parent 935bf6a commit a66fd20
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Small fixes should be here.
## Features

## Improvements
* Detect mismatch errors between group and group names when writing ElectrodeGroups [PR #1165](https://github.com/catalystneuro/neuroconv/pull/1165)
* Fix metadata bug in `IntanRecordingInterface` where extra devices were added incorrectly if the recording contained multiple electrode groups or names [#1166](https://github.com/catalystneuro/neuroconv/pull/1166)


Expand Down
27 changes: 26 additions & 1 deletion src/neuroconv/tools/spikeinterface/spikeinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,19 @@ def _get_group_name(recording: BaseRecording) -> np.ndarray:
An array containing the group names. If the `group_name` property is not
available, the channel groups will be returned. If the group names are
empty, a default value 'ElectrodeGroup' will be used.
Raises
------
ValueError
If the number of unique group names doesn't match the number of unique groups,
or if the mapping between group names and group numbers is inconsistent.
"""
default_value = "ElectrodeGroup"
group_names = recording.get_property("group_name")
groups = recording.get_channel_groups()

if group_names is None:
group_names = recording.get_channel_groups()
group_names = groups
if group_names is None:
group_names = np.full(recording.get_num_channels(), fill_value=default_value)

Expand All @@ -259,6 +267,23 @@ def _get_group_name(recording: BaseRecording) -> np.ndarray:
# If for any reason the group names are empty, fill them with the default
group_names[group_names == ""] = default_value

# Validate group names against groups
if groups is not None:
unique_groups = set(groups)
unique_names = set(group_names)

if len(unique_names) != len(unique_groups):
raise ValueError("The number of group names must match the number of groups")

# Check consistency of group name to group number mapping
group_to_name_map = {}
for group, name in zip(groups, group_names):
if group in group_to_name_map:
if group_to_name_map[group] != name:
raise ValueError("Inconsistent mapping between group numbers and group names")
else:
group_to_name_map[group] = name

return group_names


Expand Down
7 changes: 7 additions & 0 deletions src/neuroconv/tools/testing/mock_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ def __init__(
es_key=es_key,
)

# Adding this as a safeguard before the spikeinterface changes are merged:
# https://github.com/SpikeInterface/spikeinterface/pull/3588
channel_ids = self.recording_extractor.get_channel_ids()
channel_ids_as_strings = [str(id) for id in channel_ids]
self.recording_extractor = self.recording_extractor.rename_channels(new_channel_ids=channel_ids_as_strings)

def get_metadata(self) -> dict:
"""
Returns the metadata dictionary for the current object.
Expand Down Expand Up @@ -272,6 +278,7 @@ def __init__(
)

# Sorting extractor to have string unit ids until is changed in SpikeInterface
# https://github.com/SpikeInterface/spikeinterface/pull/3588
string_unit_ids = [str(id) for id in self.sorting_extractor.unit_ids]
self.sorting_extractor = self.sorting_extractor.rename_units(new_unit_ids=string_unit_ids)

Expand Down
24 changes: 24 additions & 0 deletions tests/test_ecephys/test_tools_spikeinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from neuroconv.tools.nwb_helpers import get_module
from neuroconv.tools.spikeinterface import (
add_electrical_series_to_nwbfile,
add_electrode_groups_to_nwbfile,
add_electrodes_to_nwbfile,
add_recording_to_nwbfile,
add_sorting_to_nwbfile,
Expand Down Expand Up @@ -1071,6 +1072,29 @@ def test_missing_bool_values(self):
assert np.array_equal(extracted_incomplete_property, expected_incomplete_property)


class TestAddElectrodeGroups:
def test_group_naming_not_matching_group_number(self):
recording = generate_recording(num_channels=4)
recording.set_channel_groups(groups=[0, 1, 2, 3])
recording.set_property(key="group_name", values=["A", "A", "A", "A"])

nwbfile = mock_NWBFile()
with pytest.raises(ValueError, match="The number of group names must match the number of groups"):
add_electrode_groups_to_nwbfile(nwbfile=nwbfile, recording=recording)

def test_inconsistent_group_name_mapping(self):
recording = generate_recording(num_channels=3)
# Set up groups where the same group name is used for different group numbers
recording.set_channel_groups(groups=[0, 1, 0])
recording.set_property(
key="group_name", values=["A", "B", "B"] # Inconsistent: group 0 maps to names "A" and "B"
)

nwbfile = mock_NWBFile()
with pytest.raises(ValueError, match="Inconsistent mapping between group numbers and group names"):
add_electrode_groups_to_nwbfile(nwbfile=nwbfile, recording=recording)


class TestAddUnitsTable(TestCase):
@classmethod
def setUpClass(cls):
Expand Down

0 comments on commit a66fd20

Please sign in to comment.