Skip to content

Commit 110a4ae

Browse files
authored
Merge branch 'main' into add_docstirng_for_generate_unit_locations
2 parents 56a7c63 + b9f50e3 commit 110a4ae

10 files changed

Lines changed: 71 additions & 36 deletions

File tree

.github/scripts/determine_testing_environment.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
sortingcomponents_changed = False
3232
generation_changed = False
3333
stream_extractors_changed = False
34+
github_actions_changed = False
3435

3536

3637
for changed_file in changed_files_in_the_pull_request_paths:
@@ -78,9 +79,12 @@
7879
sorters_internal_changed = True
7980
else:
8081
sorters_changed = True
82+
elif ".github" in changed_file.parts:
83+
if "workflows" in changed_file.parts:
84+
github_actions_changed = True
8185

8286

83-
run_everything = core_changed or pyproject_toml_changed or neobaseextractor_changed
87+
run_everything = core_changed or pyproject_toml_changed or neobaseextractor_changed or github_actions_changed
8488
run_generation_tests = run_everything or generation_changed
8589
run_extractor_tests = run_everything or extractors_changed or plexon2_changed
8690
run_preprocessing_tests = run_everything or preprocessing_changed
@@ -96,7 +100,7 @@
96100
run_sorters_test = run_everything or sorters_changed
97101
run_internal_sorters_test = run_everything or run_sortingcomponents_tests or sorters_internal_changed
98102

99-
run_streaming_extractors_test = stream_extractors_changed
103+
run_streaming_extractors_test = stream_extractors_changed or github_actions_changed
100104

101105
install_plexon_dependencies = plexon2_changed
102106

.github/workflows/all-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
env:
1313
KACHERY_CLOUD_CLIENT_ID: ${{ secrets.KACHERY_CLOUD_CLIENT_ID }}
1414
KACHERY_CLOUD_PRIVATE_KEY: ${{ secrets.KACHERY_CLOUD_PRIVATE_KEY }}
15+
KACHERY_ZONE: ${{ secrets.KACHERY_ZONE }}
1516

1617
concurrency: # Cancel previous workflows on the same pull request
1718
group: ${{ github.workflow }}-${{ github.ref }}
@@ -25,7 +26,7 @@ jobs:
2526
fail-fast: false
2627
matrix:
2728
python-version: ["3.9", "3.12"] # Lower and higher versions we support
28-
os: [macos-13, windows-latest, ubuntu-latest]
29+
os: [macos-latest, windows-latest, ubuntu-latest]
2930
steps:
3031
- uses: actions/checkout@v4
3132
- name: Setup Python ${{ matrix.python-version }}

.github/workflows/full-test-with-codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
env:
99
KACHERY_CLOUD_CLIENT_ID: ${{ secrets.KACHERY_CLOUD_CLIENT_ID }}
1010
KACHERY_CLOUD_PRIVATE_KEY: ${{ secrets.KACHERY_CLOUD_PRIVATE_KEY }}
11+
KACHERY_ZONE: ${{ secrets.KACHERY_ZONE }}
1112

1213
jobs:
1314
full-tests-with-codecov:

src/spikeinterface/core/baserecording.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,11 @@ def _save(self, format="binary", verbose: bool = False, **save_kwargs):
608608
probegroup = self.get_probegroup()
609609
cached.set_probegroup(probegroup)
610610

611-
time_vectors = self._get_time_vectors()
612-
if time_vectors is not None:
613-
for segment_index, time_vector in enumerate(time_vectors):
614-
if time_vector is not None:
615-
cached.set_times(time_vector, segment_index=segment_index)
611+
for segment_index in range(self.get_num_segments()):
612+
if self.has_time_vector(segment_index):
613+
# the use of get_times is preferred since timestamps are converted to array
614+
time_vector = self.get_times(segment_index=segment_index)
615+
cached.set_times(time_vector, segment_index=segment_index)
616616

617617
return cached
618618

@@ -746,6 +746,30 @@ def _select_segments(self, segment_indices):
746746

747747
return SelectSegmentRecording(self, segment_indices=segment_indices)
748748

749+
def get_channel_locations(
750+
self,
751+
channel_ids: list | np.ndarray | tuple | None = None,
752+
axes: "xy" | "yz" | "xz" | "xyz" = "xy",
753+
) -> np.ndarray:
754+
"""
755+
Get the physical locations of specified channels.
756+
757+
Parameters
758+
----------
759+
channel_ids : array-like, optional
760+
The IDs of the channels for which to retrieve locations. If None, retrieves locations
761+
for all available channels. Default is None.
762+
axes : "xy" | "yz" | "xz" | "xyz", default: "xy"
763+
The spatial axes to return, specified as a string (e.g., "xy", "xyz"). Default is "xy".
764+
765+
Returns
766+
-------
767+
np.ndarray
768+
A 2D or 3D array of shape (n_channels, n_dimensions) containing the locations of the channels.
769+
The number of dimensions depends on the `axes` argument (e.g., 2 for "xy", 3 for "xyz").
770+
"""
771+
return super().get_channel_locations(channel_ids=channel_ids, axes=axes)
772+
749773
def is_binary_compatible(self) -> bool:
750774
"""
751775
Checks if the recording is "binary" compatible.

src/spikeinterface/core/baserecordingsnippets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def set_channel_locations(self, locations, channel_ids=None):
349349
raise ValueError("set_channel_locations(..) destroys the probe description, prefer _set_probes(..)")
350350
self.set_property("location", locations, ids=channel_ids)
351351

352-
def get_channel_locations(self, channel_ids=None, axes: str = "xy"):
352+
def get_channel_locations(self, channel_ids=None, axes: str = "xy") -> np.ndarray:
353353
if channel_ids is None:
354354
channel_ids = self.get_channel_ids()
355355
channel_indices = self.ids_to_indices(channel_ids)

src/spikeinterface/core/job_tools.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,8 @@ def divide_segment_into_chunks(num_frames, chunk_size):
136136
else:
137137
n = num_frames // chunk_size
138138

139-
frame_starts = np.arange(n) * chunk_size
140-
frame_stops = frame_starts + chunk_size
141-
142-
frame_starts = frame_starts.tolist()
143-
frame_stops = frame_stops.tolist()
139+
frame_starts = [i * chunk_size for i in range(n)]
140+
frame_stops = [frame_start + chunk_size for frame_start in frame_starts]
144141

145142
if (num_frames % chunk_size) > 0:
146143
frame_starts.append(n * chunk_size)

src/spikeinterface/core/sortinganalyzer.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ def __repr__(self) -> str:
232232
txt += " - sparse"
233233
if self.has_recording():
234234
txt += " - has recording"
235+
if self.has_temporary_recording():
236+
txt += " - has temporary recording"
235237
ext_txt = f"Loaded {len(self.extensions)} extensions: " + ", ".join(self.extensions.keys())
236238
txt += "\n" + ext_txt
237239
return txt
@@ -350,7 +352,7 @@ def create_memory(cls, sorting, recording, sparsity, return_scaled, rec_attribut
350352
def create_binary_folder(cls, folder, sorting, recording, sparsity, return_scaled, rec_attributes):
351353
# used by create and save_as
352354

353-
assert recording is not None, "To create a SortingAnalyzer you need recording not None"
355+
assert recording is not None, "To create a SortingAnalyzer you need to specify the recording"
354356

355357
folder = Path(folder)
356358
if folder.is_dir():
@@ -1221,7 +1223,7 @@ def compute(self, input, save=True, extension_params=None, verbose=False, **kwar
12211223
extensions[ext_name] = ext_params
12221224
self.compute_several_extensions(extensions=extensions, save=save, verbose=verbose, **job_kwargs)
12231225
else:
1224-
raise ValueError("SortingAnalyzer.compute() need str, dict or list")
1226+
raise ValueError("SortingAnalyzer.compute() needs a str, dict or list")
12251227

12261228
def compute_one_extension(self, extension_name, save=True, verbose=False, **kwargs) -> "AnalyzerExtension":
12271229
"""
@@ -1355,7 +1357,9 @@ def compute_several_extensions(self, extensions, save=True, verbose=False, **job
13551357

13561358
for extension_name, extension_params in extensions_with_pipeline.items():
13571359
extension_class = get_extension_class(extension_name)
1358-
assert self.has_recording(), f"Extension {extension_name} need the recording"
1360+
assert (
1361+
self.has_recording() or self.has_temporary_recording()
1362+
), f"Extension {extension_name} requires the recording"
13591363

13601364
for variable_name in extension_class.nodepipeline_variables:
13611365
result_routage.append((extension_name, variable_name))
@@ -1603,17 +1607,17 @@ def _sort_extensions_by_dependency(extensions):
16031607
def _get_children_dependencies(extension_name):
16041608
"""
16051609
Extension classes have a `depend_on` attribute to declare on which class they
1606-
depend. For instance "templates" depend on "waveforms". "waveforms depends on "random_spikes".
1610+
depend on. For instance "templates" depends on "waveforms". "waveforms" depends on "random_spikes".
16071611
1608-
This function is making the reverse way : get all children that depend of a
1612+
This function is going the opposite way: it finds all children that depend on a
16091613
particular extension.
16101614
1611-
This is recursive so this includes : children and so grand children and great grand children
1615+
The implementation is recursive so that the output includes children, grand children, great grand children, etc.
16121616
1613-
This function is usefull for deleting on recompute.
1614-
For instance recompute the "waveforms" need to delete "template"
1615-
This make sens if "ms_before" is change in "waveforms" because the template also depends
1616-
on this parameters.
1617+
This function is useful for deleting existing extensions on recompute.
1618+
For instance, recomputing the "waveforms" needs to delete the "templates", since the latter depends on the former.
1619+
For this particular example, if we change the "ms_before" parameter of the "waveforms", also the "templates" will
1620+
require recomputation as this parameter is inherited.
16171621
"""
16181622
names = []
16191623
children = _extension_children[extension_name]

src/spikeinterface/core/waveforms_extractor_backwards_compatibility.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def _read_old_waveforms_extractor_binary(folder, sorting):
536536
ext = ComputeRandomSpikes(sorting_analyzer)
537537
ext.params = dict()
538538
ext.data = dict(random_spikes_indices=random_spikes_indices)
539+
ext.run_info = None
539540
sorting_analyzer.extensions["random_spikes"] = ext
540541

541542
ext = ComputeWaveforms(sorting_analyzer)
@@ -545,6 +546,7 @@ def _read_old_waveforms_extractor_binary(folder, sorting):
545546
dtype=params["dtype"],
546547
)
547548
ext.data["waveforms"] = waveforms
549+
ext.run_info = None
548550
sorting_analyzer.extensions["waveforms"] = ext
549551

550552
# templates saved dense
@@ -559,6 +561,7 @@ def _read_old_waveforms_extractor_binary(folder, sorting):
559561
ext.params = dict(ms_before=params["ms_before"], ms_after=params["ms_after"], operators=list(templates.keys()))
560562
for mode, arr in templates.items():
561563
ext.data[mode] = arr
564+
ext.run_info = None
562565
sorting_analyzer.extensions["templates"] = ext
563566

564567
for old_name, new_name in old_extension_to_new_class_map.items():
@@ -631,6 +634,7 @@ def _read_old_waveforms_extractor_binary(folder, sorting):
631634
ext.set_params(**updated_params, save=False)
632635
if ext.need_backward_compatibility_on_load:
633636
ext._handle_backward_compatibility_on_load()
637+
ext.run_info = None
634638

635639
sorting_analyzer.extensions[new_name] = ext
636640

src/spikeinterface/extractors/tests/test_neoextractors.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class BlackrockSortingTest(SortingCommonTestSuite, unittest.TestCase):
234234
ExtractorClass = BlackrockSortingExtractor
235235
downloads = ["blackrock"]
236236
entities = [
237-
"blackrock/FileSpec2.3001.nev",
237+
dict(file_path=local_folder / "blackrock/FileSpec2.3001.nev", sampling_frequency=30_000.0),
238238
dict(file_path=local_folder / "blackrock/blackrock_2_1/l101210-001.nev", sampling_frequency=30_000.0),
239239
]
240240

@@ -278,8 +278,8 @@ class Spike2RecordingTest(RecordingCommonTestSuite, unittest.TestCase):
278278

279279

280280
@pytest.mark.skipif(
281-
version.parse(platform.python_version()) >= version.parse("3.10"),
282-
reason="Sonpy only testing with Python < 3.10!",
281+
version.parse(platform.python_version()) >= version.parse("3.10") or platform.system() == "Darwin",
282+
reason="Sonpy only testing with Python < 3.10 and not supported on macOS!",
283283
)
284284
class CedRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
285285
ExtractorClass = CedRecordingExtractor
@@ -293,6 +293,7 @@ class CedRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
293293
]
294294

295295

296+
@pytest.mark.skipif(platform.system() == "Darwin", reason="Maxwell plugin not supported on macOS")
296297
class MaxwellRecordingTest(RecordingCommonTestSuite, unittest.TestCase):
297298
ExtractorClass = MaxwellRecordingExtractor
298299
downloads = ["maxwell"]

src/spikeinterface/postprocessing/principal_component.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ def run_for_all_spikes(self, file_path=None, verbose=False, **job_kwargs):
359359

360360
job_kwargs = fix_job_kwargs(job_kwargs)
361361
p = self.params
362-
we = self.sorting_analyzer
363-
sorting = we.sorting
362+
sorting_analyzer = self.sorting_analyzer
363+
sorting = sorting_analyzer.sorting
364364
assert (
365-
we.has_recording()
366-
), "To compute PCA projections for all spikes, the waveform extractor needs the recording"
367-
recording = we.recording
365+
sorting_analyzer.has_recording() or sorting_analyzer.has_temporary_recording()
366+
), "To compute PCA projections for all spikes, the sorting analyzer needs the recording"
367+
recording = sorting_analyzer.recording
368368

369369
# assert sorting.get_num_segments() == 1
370370
assert p["mode"] in ("by_channel_local", "by_channel_global")
@@ -374,8 +374,9 @@ def run_for_all_spikes(self, file_path=None, verbose=False, **job_kwargs):
374374

375375
sparsity = self.sorting_analyzer.sparsity
376376
if sparsity is None:
377-
sparse_channels_indices = {unit_id: np.arange(we.get_num_channels()) for unit_id in we.unit_ids}
378-
max_channels_per_template = we.get_num_channels()
377+
num_channels = recording.get_num_channels()
378+
sparse_channels_indices = {unit_id: np.arange(num_channels) for unit_id in sorting_analyzer.unit_ids}
379+
max_channels_per_template = num_channels
379380
else:
380381
sparse_channels_indices = sparsity.unit_id_to_channel_indices
381382
max_channels_per_template = max([chan_inds.size for chan_inds in sparse_channels_indices.values()])
@@ -449,9 +450,7 @@ def _fit_by_channel_local(self, n_jobs, progress_bar):
449450
return pca_models
450451

451452
def _fit_by_channel_global(self, progress_bar):
452-
# we = self.sorting_analyzer
453453
p = self.params
454-
# unit_ids = we.unit_ids
455454
unit_ids = self.sorting_analyzer.unit_ids
456455

457456
# there is one unique PCA accross channels

0 commit comments

Comments
 (0)