Skip to content
12 changes: 10 additions & 2 deletions src/spikeinterface/core/sortinganalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,8 +830,16 @@ def _handle_backward_compatibility_settings_pre_init(cls, settings: dict[str, An
def main_channel_indices(self):
if self._main_channel_indices is None:
sorting_main_channel_ids = self.get_sorting_property("main_channel_id")
if sorting_main_channel_ids is not None and self.has_recording():
main_channel_indices = self.recording.ids_to_indices(sorting_main_channel_ids)
if sorting_main_channel_ids is not None and (self.has_recording() or self.rec_attributes):
if self.has_recording():
main_channel_indices = self.recording.ids_to_indices(sorting_main_channel_ids)
elif self.rec_attributes.get("channel_ids") is not None:
channel_ids = np.array(self.rec_attributes.get("channel_ids"))
main_channel_indices = np.array(
[list(channel_ids).index(id) for id in sorting_main_channel_ids], dtype=int
)
else:
main_channel_indices = None
self._main_channel_indices = main_channel_indices
else:
self._main_channel_indices = self._compute_main_channel_backwards_compatibility()
Expand Down
28 changes: 13 additions & 15 deletions src/spikeinterface/core/tests/test_sortinganalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,10 +1181,10 @@ def test_select_channels_independent(dataset):
assert np.all(sa_one.get_channel_locations() == sa_two.get_channel_locations()[shuffle_order])


def test_main_channel_from_templates_dense_recordingless(tmp_path):
"""When a dense analyzer has a `templates` extension but no attached recording, `get_main_channels`
recovers each unit's main channel from the templates (its peak channel) and reports it consistently
both as a positional index and as a channel id.
def test_main_channel_from_templates_dense_no_sorting_main_channel_ids(tmp_path):
"""When a dense analyzer has a `templates` extension but the sorting has no main channel
ids, `get_main_channels, recovers each unit's main channel from the templates (its peak
channel) and reports it consistently both as a positional index and as a channel id.
"""
recording, sorting = generate_ground_truth_recording(
num_channels=8,
Expand All @@ -1210,18 +1210,16 @@ def test_main_channel_from_templates_dense_recordingless(tmp_path):
expected_main_channel_ids = [unit0_main_channel_id, unit1_main_channel_id]

folder = tmp_path / "analyzer_recordingless"
analyzer = create_sorting_analyzer(sorting, recording, format="binary_folder", folder=folder, sparse=False)
analyzer = create_sorting_analyzer(sorting, recording, format="memory", folder=folder, sparse=False)
analyzer.compute(["random_spikes", "templates"])

# simulate the recording being unavailable at load time by removing its serialized copy on disk
for file_ext in ("json", "pickle"):
recording_file = folder / f"recording.{file_ext}"
if recording_file.exists():
recording_file.unlink()
analyzer._recording = None
analyzer.sorting._properties.pop("main_channel_id")

analyzer.save_as(format="binary_folder", folder=folder)

reloaded_analyzer = load_sorting_analyzer(folder)
assert not reloaded_analyzer.has_recording()
assert reloaded_analyzer.has_extension("templates")
assert "main_channel_id" not in reloaded_analyzer.get_sorting_property_keys()

# craft a clear negative peak on each unit's main channel
templates = reloaded_analyzer.get_extension("templates")
Expand All @@ -1238,8 +1236,8 @@ def test_main_channel_from_templates_dense_recordingless(tmp_path):
assert np.array_equal(recovered_main_channel_ids, expected_main_channel_ids)


def test_main_channel_from_templates_sparse_recordingless():
"""When a sparse analyzer has a `templates` extension but no attached recording, `get_main_channels`
def test_main_channel_from_templates_sparse_no_sorting_main_channel_ids():
"""When a sparse analyzer has a `templates` extension but sorting has no main channel ids, `get_main_channels`
reports each unit's main channel as its template peak channel, not the geometric centroid of the
unit's sparse channel set.
"""
Expand Down Expand Up @@ -1293,8 +1291,8 @@ def test_main_channel_from_templates_sparse_recordingless():
templates.data["average"] = average

# recordingless, and force the backwards-compatibility recompute
analyzer._recording = None
analyzer._main_channel_indices = None
analyzer.sorting._properties.pop("main_channel_id")

# must be the template peak channels, not the geometric centroids ("c", "f" / indices [2, 5])
recovered_main_channel_indices = np.asarray(analyzer.get_main_channels(outputs="index"))
Expand Down
Loading