Skip to content

Commit 78923ba

Browse files
authored
Merge branch 'main' into add-BaseRasterWidget
2 parents 59c6571 + bb0e8a4 commit 78923ba

13 files changed

Lines changed: 137 additions & 132 deletions

File tree

.github/scripts/test_kilosort4_ci.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import pytest
2222
import copy
23-
from typing import Any
23+
from packaging.version import parse
2424
from inspect import signature
2525

2626
import numpy as np
@@ -84,8 +84,6 @@
8484
"duplicate_spike_ms": 0.3,
8585
}
8686

87-
PARAMS_TO_TEST = list(PARAMS_TO_TEST_DICT.keys())
88-
8987
PARAMETERS_NOT_AFFECTING_RESULTS = [
9088
"artifact_threshold",
9189
"ccg_threshold",
@@ -95,13 +93,24 @@
9593
"duplicate_spike_ms", # this is because ground-truth spikes don't have violations
9694
]
9795

98-
# THIS IS A PLACEHOLDER FOR FUTURE PARAMS TO TEST
99-
# if parse(version("kilosort")) >= parse("4.0.X"):
100-
# PARAMS_TO_TEST_DICT.update(
101-
# [
102-
# {"new_param": new_value},
103-
# ]
104-
# )
96+
97+
# Add/Remove version specific parameters
98+
if parse(kilosort.__version__) >= parse("4.0.22"):
99+
PARAMS_TO_TEST_DICT.update(
100+
{"position_limit": 50}
101+
)
102+
# Position limit only affects computing spike locations after sorting
103+
PARAMETERS_NOT_AFFECTING_RESULTS.append("position_limit")
104+
105+
if parse(kilosort.__version__) >= parse("4.0.24"):
106+
PARAMS_TO_TEST_DICT.update(
107+
{"max_peels": 200},
108+
)
109+
# max_peels is not affecting the results in this short dataset
110+
PARAMETERS_NOT_AFFECTING_RESULTS.append("max_peels")
111+
112+
113+
PARAMS_TO_TEST = list(PARAMS_TO_TEST_DICT.keys())
105114

106115

107116
class TestKilosort4Long:

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,16 @@ test = [
159159
"s3fs",
160160

161161
# tridesclous
162-
"numba",
162+
"numba<0.61.0;python_version<'3.13'",
163+
"numba>=0.61.0;python_version>='3.13'",
163164
"hdbscan>=0.8.33", # Previous version had a broken wheel
164165

165166
# for sortingview backend
166167
"sortingview",
167168

169+
# for motion and sortingcomponents
170+
"torch",
171+
168172
# curation
169173
"skops",
170174
"huggingface_hub",

src/spikeinterface/core/base.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,31 @@ def __init__(self, main_ids: Sequence) -> None:
7979
# preferred context for multiprocessing
8080
self._preferred_mp_context = None
8181

82+
def _repr_html_(self, display_name=True):
83+
pass
84+
85+
def _get_common_repr_html(self, common_style):
86+
html_annotations = f"<details style='{common_style}'> <summary><strong>Annotations</strong></summary><ul>"
87+
for key, value in self._annotations.items():
88+
html_annotations += f"<li> <strong> {key} </strong>: {value}</li>"
89+
html_annotations += f"</details>"
90+
91+
html_properties = f"<details style='{common_style}'><summary><strong>Properties</strong></summary><ul>"
92+
for key, value in self._properties.items():
93+
# Add a further indent for each property
94+
value_formatted = np.asarray(value)
95+
html_properties += f"<details><summary><strong>{key}</strong></summary>{value_formatted}</details>"
96+
html_properties += "</ul></details>"
97+
98+
if self.get_parent():
99+
html_parent = f"<details style='{common_style}'> <summary><strong>Parent</strong></summary><ul>"
100+
display_name = self.name != self.get_parent().name
101+
html_parent += self.get_parent()._repr_html_(display_name=display_name)
102+
html_parent += "</ul></details>"
103+
else:
104+
html_parent = ""
105+
return html_annotations + html_properties + html_parent
106+
82107
@property
83108
def name(self):
84109
name = self._annotations.get("name", None)

src/spikeinterface/core/baserecording.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def list_to_string(lst, max_size=6):
8989

9090
return txt
9191

92-
def _repr_header(self):
92+
def _repr_header(self, display_name=True):
9393
num_segments = self.get_num_segments()
9494
num_channels = self.get_num_channels()
9595
dtype = self.get_dtype()
@@ -105,8 +105,13 @@ def _repr_header(self):
105105
# Khz for high sampling rate and Hz for LFP
106106
sampling_frequency_repr = f"{(sf_hz/1000.0):0.1f}kHz" if sf_hz > 10_000.0 else f"{sf_hz:0.1f}Hz"
107107

108+
if display_name and self.name != self.__class__.__name__:
109+
name = f"{self.name} ({self.__class__.__name__})"
110+
else:
111+
name = self.__class__.__name__
112+
108113
txt = (
109-
f"{self.name}: "
114+
f"{name}: "
110115
f"{num_channels} channels - "
111116
f"{sampling_frequency_repr} - "
112117
f"{num_segments} segments - "
@@ -118,11 +123,11 @@ def _repr_header(self):
118123

119124
return txt
120125

121-
def _repr_html_(self):
126+
def _repr_html_(self, display_name=True):
122127
common_style = "margin-left: 10px;"
123128
border_style = "border:1px solid #ddd; padding:10px;"
124129

125-
html_header = f"<div style='{border_style}'><strong>{self._repr_header()}</strong></div>"
130+
html_header = f"<div style='{border_style}'><strong>{self._repr_header(display_name)}</strong></div>"
126131

127132
html_segments = ""
128133
if self.get_num_segments() > 1:
@@ -143,19 +148,8 @@ def _repr_html_(self):
143148
html_channel_ids = f"<details style='{common_style}'> <summary><strong>Channel IDs</strong></summary><ul>"
144149
html_channel_ids += f"{self.channel_ids} </details>"
145150

146-
html_annotations = f"<details style='{common_style}'> <summary><strong>Annotations</strong></summary><ul>"
147-
for key, value in self._annotations.items():
148-
html_annotations += f"<li> <strong> {key} </strong>: {value}</li>"
149-
html_annotations += "</ul> </details>"
150-
151-
html_properties = f"<details style='{common_style}'><summary><strong>Channel Properties</strong></summary><ul>"
152-
for key, value in self._properties.items():
153-
# Add a further indent for each property
154-
value_formatted = np.asarray(value)
155-
html_properties += f"<details><summary> <strong> {key} </strong> </summary>{value_formatted}</details>"
156-
html_properties += "</ul></details>"
157-
158-
html_repr = html_header + html_segments + html_channel_ids + html_annotations + html_properties
151+
html_extra = self._get_common_repr_html(common_style)
152+
html_repr = html_header + html_segments + html_channel_ids + html_extra
159153
return html_repr
160154

161155
def get_num_segments(self) -> int:

src/spikeinterface/core/basesorting.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,33 @@ def __init__(self, sampling_frequency: float, unit_ids: List):
3030
self._cached_spike_trains = {}
3131

3232
def __repr__(self):
33+
return self._repr_header()
34+
35+
def _repr_header(self, display_name=True):
3336
nseg = self.get_num_segments()
3437
nunits = self.get_num_units()
3538
sf_khz = self.get_sampling_frequency() / 1000.0
36-
txt = f"{self.name}: {nunits} units - {nseg} segments - {sf_khz:0.1f}kHz"
39+
if display_name and self.name != self.__class__.__name__:
40+
name = f"{self.name} ({self.__class__.__name__})"
41+
else:
42+
name = self.__class__.__name__
43+
txt = f"{name}: {nunits} units - {nseg} segments - {sf_khz:0.1f}kHz"
3744
if "file_path" in self._kwargs:
3845
txt += "\n file_path: {}".format(self._kwargs["file_path"])
3946
return txt
4047

41-
def _repr_html_(self):
48+
def _repr_html_(self, display_name=True):
4249
common_style = "margin-left: 10px;"
4350
border_style = "border:1px solid #ddd; padding:10px;"
4451

45-
html_header = f"<div style='{border_style}'><strong>{self.__repr__()}</strong></div>"
52+
html_header = f"<div style='{border_style}'><strong>{self._repr_header(display_name)}</strong></div>"
4653

4754
html_unit_ids = f"<details style='{common_style}'> <summary><strong>Unit IDs</strong></summary><ul>"
4855
html_unit_ids += f"{self.unit_ids} </details>"
4956

50-
html_annotations = f"<details style='{common_style}'> <summary><strong>Annotations</strong></summary><ul>"
51-
for key, value in self._annotations.items():
52-
html_annotations += f"<li> <strong> {key} </strong>: {value}</li>"
53-
html_annotations += f"</details>"
54-
55-
html_unit_properties = (
56-
f"<details style='{common_style}'><summary><strong>Unit Properties</strong></summary><ul>"
57-
)
58-
for key, value in self._properties.items():
59-
# Add a further indent for each property
60-
value_formatted = np.asarray(value)
61-
html_unit_properties += f"<details><summary><strong>{key}</strong></summary>{value_formatted}</details>"
62-
html_unit_properties += "</ul></details>"
57+
html_extra = self._get_common_repr_html(common_style)
6358

64-
html_repr = html_header + html_unit_ids + html_annotations + html_unit_properties
59+
html_repr = html_header + html_unit_ids + html_extra
6560
return html_repr
6661

6762
@property

src/spikeinterface/core/channelslice.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ def __init__(self, parent_recording, channel_ids=None, renamed_channel_ids=None)
2525
np.unique(renamed_channel_ids)
2626
), "renamed_channel_ids must be unique!"
2727

28-
self._parent_recording = parent_recording
2928
self._channel_ids = np.asarray(channel_ids)
3029
self._renamed_channel_ids = np.asarray(renamed_channel_ids)
3130

32-
parents_chan_ids = self._parent_recording.get_channel_ids()
31+
parents_chan_ids = parent_recording.get_channel_ids()
3332

3433
# some checks
3534
assert all(
@@ -54,7 +53,7 @@ def __init__(self, parent_recording, channel_ids=None, renamed_channel_ids=None)
5453
self._parent_channel_indices = parent_recording.ids_to_indices(self._channel_ids)
5554

5655
# link recording segment
57-
for parent_segment in self._parent_recording._recording_segments:
56+
for parent_segment in parent_recording._recording_segments:
5857
sub_segment = ChannelSliceRecordingSegment(parent_segment, self._parent_channel_indices)
5958
self.add_recording_segment(sub_segment)
6059

src/spikeinterface/core/recording_tools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,9 @@ def get_random_recording_slices(
586586
for segment_index in range(num_segments):
587587
num_frames = recording.get_num_frames(segment_index)
588588
high = num_frames - chunk_size - margin_frames
589-
random_starts = rng.integers(low=low, high=high, size=size)
589+
# here we set endpoint to True, because the this represents the start of the
590+
# chunk, and should be inclusive
591+
random_starts = rng.integers(low=low, high=high, size=size, endpoint=True)
590592
random_starts = np.sort(random_starts)
591593
recording_slices += [
592594
(segment_index, start_frame, (start_frame + chunk_size)) for start_frame in random_starts

src/spikeinterface/core/segmentutils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ def __init__(self, recording: BaseRecording, segment_indices: int | list[int]):
243243
for segment_index in segment_indices:
244244
rec_seg = recording._recording_segments[segment_index]
245245
self.add_recording_segment(rec_seg)
246+
self._parent = recording
246247

247248
self._kwargs = {"recording": recording, "segment_indices": segment_indices}
248249

@@ -564,6 +565,7 @@ def __init__(self, parent_sorting: BaseSorting, recording_or_recording_list=None
564565
)
565566
sliced_segment = sliced_parent_sorting._sorting_segments[0]
566567
self.add_sorting_segment(sliced_segment)
568+
self._parent = parent_sorting
567569

568570
self._kwargs = {"parent_sorting": parent_sorting, "recording_or_recording_list": recording_list}
569571

src/spikeinterface/core/zarrextractors.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def super_zarr_open(folder_path: str | Path, mode: str = "r", storage_options: d
6262
else:
6363
storage_options_to_test = (storage_options,)
6464

65+
root = None
6566
if is_path_remote(str(folder_path)):
66-
root = None
6767
for open_func in open_funcs:
6868
if root is not None:
6969
break
@@ -74,6 +74,8 @@ def super_zarr_open(folder_path: str | Path, mode: str = "r", storage_options: d
7474
except Exception as e:
7575
pass
7676
else:
77+
if not Path(folder_path).is_dir():
78+
raise ValueError(f"Folder {folder_path} does not exist")
7779
for open_func in open_funcs:
7880
try:
7981
root = open_func(str(folder_path), mode=mode, storage_options=storage_options)

src/spikeinterface/preprocessing/basepreprocessor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class BasePreprocessor(BaseRecording):
99
def __init__(self, recording, sampling_frequency=None, channel_ids=None, dtype=None):
1010
assert isinstance(recording, BaseRecording), "'recording' must be a RecordingExtractor"
1111

12-
self._parent_recording = recording
1312
if sampling_frequency is None:
1413
sampling_frequency = recording.get_sampling_frequency()
1514
if channel_ids is None:

0 commit comments

Comments
 (0)