Skip to content

Commit 45d7c75

Browse files
authored
Merge branch 'main' into support_python_3.13
2 parents c935f6b + 4a1a45a commit 45d7c75

8 files changed

Lines changed: 105 additions & 15 deletions

File tree

doc/modules/core.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ and merging unit groups.
385385
386386
sorting_analyzer_select = sorting_analyzer.select_units(unit_ids=[0, 1, 2, 3])
387387
sorting_analyzer_remove = sorting_analyzer.remove_units(remove_unit_ids=[0])
388-
sorting_analyzer_merge = sorting_analyzer.merge_units([0, 1], [2, 3])
388+
sorting_analyzer_merge = sorting_analyzer.merge_units([[0, 1], [2, 3]])
389389
390390
All computed extensions will be automatically propagated or merged when curating. Please refer to the
391391
:ref:`modules/curation:Curation module` documentation for more information.

doc/modules/curation.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ The ``censored_period_ms`` parameter is the time window in milliseconds to consi
8888
The :py:func:`~spikeinterface.curation.remove_redundand_units` function removes
8989
redundant units from the sorting output. Redundant units are units that share over
9090
a certain percentage of spikes, by default 80%.
91-
The function can acto both on a ``BaseSorting`` or a ``SortingAnalyzer`` object.
91+
The function can act both on a ``BaseSorting`` or a ``SortingAnalyzer`` object.
9292

9393
.. code-block:: python
9494
@@ -102,13 +102,18 @@ The function can acto both on a ``BaseSorting`` or a ``SortingAnalyzer`` object.
102102
)
103103
104104
# remove redundant units from SortingAnalyzer object
105-
clean_sorting_analyzer = remove_redundant_units(
105+
# note this returns a cleaned sorting
106+
clean_sorting = remove_redundant_units(
106107
sorting_analyzer,
107108
duplicate_threshold=0.9,
108109
remove_strategy="min_shift"
109110
)
111+
# in order to have a SortingAnalyer with only the non-redundant units one must
112+
# select the designed units remembering to give format and folder if one wants
113+
# a persistent SortingAnalyzer.
114+
clean_sorting_analyzer = sorting_analyzer.select_units(clean_sorting.unit_ids)
110115
111-
We recommend usinf the ``SortingAnalyzer`` approach, since the ``min_shift`` strategy keeps
116+
We recommend using the ``SortingAnalyzer`` approach, since the ``min_shift`` strategy keeps
112117
the unit (among the redundant ones), with a better template alignment.
113118

114119

src/spikeinterface/core/baserecordingsnippets.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,10 @@ def _set_probes(self, probe_or_probegroup, group_mode="by_probe", in_place=False
172172
number_of_device_channel_indices = np.max(list(device_channel_indices) + [0])
173173
if number_of_device_channel_indices >= self.get_num_channels():
174174
error_msg = (
175-
f"The given Probe have 'device_channel_indices' that do not match channel count \n"
176-
f"{number_of_device_channel_indices} vs {self.get_num_channels()} \n"
175+
f"The given Probe either has 'device_channel_indices' that does not match channel count \n"
176+
f"{len(device_channel_indices)} vs {self.get_num_channels()} \n"
177+
f"or it's max index {number_of_device_channel_indices} is the same as the number of channels {self.get_num_channels()} \n"
178+
f"If using all channels remember that python is 0-indexed so max device_channel_index should be {self.get_num_channels() - 1} \n"
177179
f"device_channel_indices are the following: {device_channel_indices} \n"
178180
f"recording channels are the following: {self.get_channel_ids()} \n"
179181
)

src/spikeinterface/core/template_tools.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ def get_dense_templates_array(one_object: Templates | SortingAnalyzer, return_sc
3131
)
3232
ext = one_object.get_extension("templates")
3333
if ext is not None:
34-
templates_array = ext.data["average"]
34+
if "average" in ext.data:
35+
templates_array = ext.data.get("average")
36+
elif "median" in ext.data:
37+
templates_array = ext.data.get("median")
38+
else:
39+
raise ValueError("Average or median templates have not been computed.")
3540
else:
3641
raise ValueError("SortingAnalyzer need extension 'templates' to be computed to retrieve templates")
3742
else:

src/spikeinterface/extractors/neoextractors/plexon2.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class Plexon2RecordingExtractor(NeoBaseRecordingExtractor):
2828
ids: ["source3.1" , "source3.2", "source3.3", "source3.4"]
2929
all_annotations : bool, default: False
3030
Load exhaustively all annotations from neo.
31+
reading_attempts : int, default: 25
32+
Number of attempts to read the file before raising an error
33+
This opening process is somewhat unreliable and might fail occasionally. Adjust this higher
34+
if you encounter problems in opening the file.
3135
3236
Examples
3337
--------
@@ -37,8 +41,16 @@ class Plexon2RecordingExtractor(NeoBaseRecordingExtractor):
3741

3842
NeoRawIOClass = "Plexon2RawIO"
3943

40-
def __init__(self, file_path, stream_id=None, stream_name=None, use_names_as_ids=True, all_annotations=False):
41-
neo_kwargs = self.map_to_neo_kwargs(file_path)
44+
def __init__(
45+
self,
46+
file_path,
47+
stream_id=None,
48+
stream_name=None,
49+
use_names_as_ids=True,
50+
all_annotations=False,
51+
reading_attempts: int = 25,
52+
):
53+
neo_kwargs = self.map_to_neo_kwargs(file_path, reading_attempts=reading_attempts)
4254
NeoBaseRecordingExtractor.__init__(
4355
self,
4456
stream_id=stream_id,
@@ -50,8 +62,18 @@ def __init__(self, file_path, stream_id=None, stream_name=None, use_names_as_ids
5062
self._kwargs.update({"file_path": str(file_path)})
5163

5264
@classmethod
53-
def map_to_neo_kwargs(cls, file_path):
65+
def map_to_neo_kwargs(cls, file_path, reading_attempts: int = 25):
66+
5467
neo_kwargs = {"filename": str(file_path)}
68+
69+
from packaging.version import Version
70+
import neo
71+
72+
neo_version = Version(neo.__version__)
73+
74+
if neo_version > Version("0.13.3"):
75+
neo_kwargs["reading_attempts"] = reading_attempts
76+
5577
return neo_kwargs
5678

5779

src/spikeinterface/extractors/tests/test_neoextractors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ class Plexon2RecordingTest(RecordingCommonTestSuite, unittest.TestCase):
368368
ExtractorClass = Plexon2RecordingExtractor
369369
downloads = ["plexon"]
370370
entities = [
371-
("plexon/4chDemoPL2.pl2", {"stream_id": "3"}),
371+
("plexon/4chDemoPL2.pl2", {"stream_name": "WB-Wideband"}),
372372
]
373373

374374

src/spikeinterface/postprocessing/localization_tools.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ def compute_monopolar_triangulation(
7777

7878
contact_locations = sorting_analyzer_or_templates.get_channel_locations()
7979

80-
sparsity = compute_sparsity(sorting_analyzer_or_templates, method="radius", radius_um=radius_um)
80+
if sorting_analyzer_or_templates.sparsity is None:
81+
sparsity = compute_sparsity(sorting_analyzer_or_templates, method="radius", radius_um=radius_um)
82+
else:
83+
sparsity = sorting_analyzer_or_templates.sparsity
84+
8185
templates = get_dense_templates_array(
8286
sorting_analyzer_or_templates, return_scaled=get_return_scaled(sorting_analyzer_or_templates)
8387
)
@@ -157,9 +161,13 @@ def compute_center_of_mass(
157161

158162
assert feature in ["ptp", "mean", "energy", "peak_voltage"], f"{feature} is not a valid feature"
159163

160-
sparsity = compute_sparsity(
161-
sorting_analyzer_or_templates, peak_sign=peak_sign, method="radius", radius_um=radius_um
162-
)
164+
if sorting_analyzer_or_templates.sparsity is None:
165+
sparsity = compute_sparsity(
166+
sorting_analyzer_or_templates, peak_sign=peak_sign, method="radius", radius_um=radius_um
167+
)
168+
else:
169+
sparsity = sorting_analyzer_or_templates.sparsity
170+
163171
templates = get_dense_templates_array(
164172
sorting_analyzer_or_templates, return_scaled=get_return_scaled(sorting_analyzer_or_templates)
165173
)
@@ -650,8 +658,55 @@ def get_convolution_weights(
650658
enforce_decrease_shells = numba.jit(enforce_decrease_shells_data, nopython=True)
651659

652660

661+
def compute_location_max_channel(
662+
templates_or_sorting_analyzer: SortingAnalyzer | Templates,
663+
unit_ids=None,
664+
peak_sign: "neg" | "pos" | "both" = "neg",
665+
mode: "extremum" | "at_index" | "peak_to_peak" = "extremum",
666+
) -> np.ndarray:
667+
"""
668+
Localize a unit using max channel.
669+
670+
This uses internally `get_template_extremum_channel()`
671+
672+
673+
Parameters
674+
----------
675+
templates_or_sorting_analyzer : SortingAnalyzer | Templates
676+
A SortingAnalyzer or Templates object
677+
unit_ids: list[str] | list[int] | None
678+
A list of unit_id to restrict the computation
679+
peak_sign : "neg" | "pos" | "both"
680+
Sign of the template to find extremum channels
681+
mode : "extremum" | "at_index" | "peak_to_peak", default: "at_index"
682+
Where the amplitude is computed
683+
* "extremum" : take the peak value (max or min depending on `peak_sign`)
684+
* "at_index" : take value at `nbefore` index
685+
* "peak_to_peak" : take the peak-to-peak amplitude
686+
687+
Returns
688+
-------
689+
unit_locations: np.ndarray
690+
2d
691+
"""
692+
extremum_channels_index = get_template_extremum_channel(
693+
templates_or_sorting_analyzer, peak_sign=peak_sign, mode=mode, outputs="index"
694+
)
695+
contact_locations = templates_or_sorting_analyzer.get_channel_locations()
696+
if unit_ids is None:
697+
unit_ids = templates_or_sorting_analyzer.unit_ids
698+
else:
699+
unit_ids = np.asarray(unit_ids)
700+
unit_locations = np.zeros((unit_ids.size, 2), dtype="float32")
701+
for i, unit_id in enumerate(unit_ids):
702+
unit_locations[i, :] = contact_locations[extremum_channels_index[unit_id]]
703+
704+
return unit_locations
705+
706+
653707
_unit_location_methods = {
654708
"center_of_mass": compute_center_of_mass,
655709
"grid_convolution": compute_grid_convolution,
656710
"monopolar_triangulation": compute_monopolar_triangulation,
711+
"max_channel": compute_location_max_channel,
657712
}

src/spikeinterface/postprocessing/tests/test_unit_locations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class TestUnitLocationsExtension(AnalyzerExtensionCommonTestSuite):
1313
dict(method="grid_convolution", radius_um=150, weight_method={"mode": "gaussian_2d"}),
1414
dict(method="monopolar_triangulation", radius_um=150),
1515
dict(method="monopolar_triangulation", radius_um=150, optimizer="minimize_with_log_penality"),
16+
dict(method="max_channel"),
1617
],
1718
)
1819
def test_extension(self, params):

0 commit comments

Comments
 (0)