Skip to content

Commit c08c199

Browse files
authored
Merge branch 'main' into add_distance_function
2 parents b98b7b2 + 12a1276 commit c08c199

17 files changed

Lines changed: 254 additions & 113 deletions

File tree

examples/tutorials/curation/plot_1_automated_curation.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ def calculate_moving_avg(label_df, confidence_label, window_size):
209209
# trained on real data.
210210
#
211211
# For example, the following classifiers are trained on Neuropixels data from 11 mice recorded in
212-
# V1,SC and ALM: https://huggingface.co/AnoushkaJain3/noise_neural_classifier/ and
213-
# https://huggingface.co/AnoushkaJain3/sua_mua_classifier/ . One will classify units into
212+
# V1,SC and ALM: https://huggingface.co/SpikeInterface/UnitRefine_noise_neural_classifier/ and
213+
# https://huggingface.co/SpikeInterface/UnitRefine_sua_mua_classifier/. One will classify units into
214214
# `noise` or `not-noise` and the other will classify the `not-noise` units into single
215215
# unit activity (sua) units and multi-unit activity (mua) units.
216216
#
@@ -221,8 +221,8 @@ def calculate_moving_avg(label_df, confidence_label, window_size):
221221

222222
# Apply the noise/not-noise model
223223
noise_neuron_labels = sc.auto_label_units(
224-
sorting_analyzer = sorting_analyzer,
225-
repo_id = "AnoushkaJain3/noise_neural_classifier",
224+
sorting_analyzer=sorting_analyzer,
225+
repo_id="SpikeInterface/UnitRefine_noise_neural_classifier",
226226
trust_model=True,
227227
)
228228

@@ -231,8 +231,8 @@ def calculate_moving_avg(label_df, confidence_label, window_size):
231231

232232
# Apply the sua/mua model
233233
sua_mua_labels = sc.auto_label_units(
234-
sorting_analyzer = analyzer_neural,
235-
repo_id = "AnoushkaJain3/sua_mua_classifier",
234+
sorting_analyzer=analyzer_neural,
235+
repo_id="SpikeInterface/UnitRefine_sua_mua_classifier",
236236
trust_model=True,
237237
)
238238

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ extractors = [
6262
"MEArec>=1.8",
6363
"pynwb>=2.6.0",
6464
"hdmf-zarr>=0.11.0",
65-
"pyedflib>=0.1.30",
65+
"pyedflib>=0.1.30,<0.1.39",
6666
"sonpy;python_version<'3.10'",
6767
"lxml", # lxml for neuroscope
6868
"scipy",

src/spikeinterface/core/globals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def is_set_global_dataset_folder() -> bool:
9898

9999
########################################
100100
_default_job_kwargs = dict(
101-
pool_engine="thread", n_jobs=1, chunk_duration="1s", progress_bar=True, mp_context=None, max_threads_per_worker=1
101+
pool_engine="process", n_jobs=1, chunk_duration="1s", progress_bar=True, mp_context=None, max_threads_per_worker=1
102102
)
103103

104104
global global_job_kwargs

src/spikeinterface/core/node_pipeline.py

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def __init__(
191191
self._dtype = spike_peak_dtype
192192

193193
self.include_spikes_in_margin = include_spikes_in_margin
194-
if include_spikes_in_margin is not None:
194+
if include_spikes_in_margin:
195195
self._dtype = spike_peak_dtype + [("in_margin", "bool")]
196196

197197
self.peaks = sorting_to_peaks(sorting, extremum_channel_inds, self._dtype)
@@ -228,12 +228,6 @@ def compute(self, traces, start_frame, end_frame, segment_index, max_margin, pea
228228
# get local peaks
229229
sl = self.segment_slices[segment_index]
230230
peaks_in_segment = self.peaks[sl]
231-
# if self.include_spikes_in_margin:
232-
# i0, i1 = np.searchsorted(
233-
# peaks_in_segment["sample_index"], [start_frame - max_margin, end_frame + max_margin]
234-
# )
235-
# else:
236-
# i0, i1 = np.searchsorted(peaks_in_segment["sample_index"], [start_frame, end_frame])
237231
i0, i1 = peak_slice
238232

239233
local_peaks = peaks_in_segment[i0:i1]
@@ -435,21 +429,59 @@ def compute(self, traces, peaks):
435429
return sparse_wfs
436430

437431

438-
def find_parent_of_type(list_of_parents, parent_type, unique=True):
432+
def find_parent_of_type(list_of_parents, parent_type):
433+
"""
434+
Find a single parent of a given type(s) in a list of parents.
435+
If multiple parents of the given type are found, the first parent is returned.
436+
437+
Parameters
438+
----------
439+
list_of_parents : list of PipelineNode
440+
List of parents to search through.
441+
parent_type : type | tuple of types
442+
The type of parent to search for.
443+
444+
Returns
445+
-------
446+
parent : PipelineNode or None
447+
The parent of the given type. Returns None if no parent of the given type is found.
448+
"""
439449
if list_of_parents is None:
440450
return None
441451

452+
parents = find_parents_of_type(list_of_parents, parent_type)
453+
454+
if len(parents) > 0:
455+
return parents[0]
456+
else:
457+
return None
458+
459+
460+
def find_parents_of_type(list_of_parents, parent_type):
461+
"""
462+
Find all parents of a given type(s) in a list of parents.
463+
464+
Parameters
465+
----------
466+
list_of_parents : list of PipelineNode
467+
List of parents to search through.
468+
parent_type : type | tuple of types
469+
The type(s) of parents to search for.
470+
471+
Returns
472+
-------
473+
parents : list of PipelineNode
474+
List of parents of the given type(s). Returns an empty list if no parents of the given type(s) are found.
475+
"""
476+
if list_of_parents is None:
477+
return []
478+
442479
parents = []
443480
for parent in list_of_parents:
444481
if isinstance(parent, parent_type):
445482
parents.append(parent)
446483

447-
if unique and len(parents) == 1:
448-
return parents[0]
449-
elif not unique and len(parents) > 1:
450-
return parents[0]
451-
else:
452-
return None
484+
return parents
453485

454486

455487
def check_graph(nodes):
@@ -471,7 +503,7 @@ def check_graph(nodes):
471503
assert parent in nodes, f"Node {node} has parent {parent} that was not passed in nodes"
472504
assert (
473505
nodes.index(parent) < i
474-
), f"Node are ordered incorrectly: {node} before {parent} in the pipeline definition."
506+
), f"Nodes are ordered incorrectly: {node} before {parent} in the pipeline definition."
475507

476508
return nodes
477509

@@ -607,12 +639,16 @@ def _compute_peak_pipeline_chunk(segment_index, start_frame, end_frame, worker_c
607639
skip_after_n_peaks_per_worker = worker_ctx["skip_after_n_peaks_per_worker"]
608640

609641
recording_segment = recording._recording_segments[segment_index]
610-
node0 = nodes[0]
611-
612-
if isinstance(node0, (SpikeRetriever, PeakRetriever)):
613-
# in this case PeakSource could have no peaks and so no need to load traces just skip
614-
peak_slice = i0, i1 = node0.get_peak_slice(segment_index, start_frame, end_frame, max_margin)
615-
load_trace_and_compute = i0 < i1
642+
retrievers = find_parents_of_type(nodes, (SpikeRetriever, PeakRetriever))
643+
# get peak slices once for all retrievers
644+
peak_slice_by_retriever = {}
645+
for retriever in retrievers:
646+
peak_slice = i0, i1 = retriever.get_peak_slice(segment_index, start_frame, end_frame, max_margin)
647+
peak_slice_by_retriever[retriever] = peak_slice
648+
649+
if len(peak_slice_by_retriever) > 0:
650+
# in this case the retrievers could have no peaks, so we test if any spikes are in the chunk
651+
load_trace_and_compute = any(i0 < i1 for i0, i1 in peak_slice_by_retriever.values())
616652
else:
617653
# PeakDetector always need traces
618654
load_trace_and_compute = True
@@ -646,7 +682,8 @@ def _compute_peak_pipeline_chunk(segment_index, start_frame, end_frame, worker_c
646682
node_output = node.compute(trace_detection, start_frame, end_frame, segment_index, max_margin)
647683
# set sample index to local
648684
node_output[0]["sample_index"] += extra_margin
649-
elif isinstance(node, PeakSource):
685+
elif isinstance(node, (PeakRetriever, SpikeRetriever)):
686+
peak_slice = peak_slice_by_retriever[node]
650687
node_output = node.compute(traces_chunk, start_frame, end_frame, segment_index, max_margin, peak_slice)
651688
else:
652689
# TODO later when in master: change the signature of all nodes (or maybe not!)

src/spikeinterface/core/sortinganalyzer.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,19 +2033,18 @@ def load(cls, sorting_analyzer):
20332033
return None
20342034

20352035
def load_run_info(self):
2036+
run_info = None
20362037
if self.format == "binary_folder":
20372038
extension_folder = self._get_binary_extension_folder()
20382039
run_info_file = extension_folder / "run_info.json"
20392040
if run_info_file.is_file():
20402041
with open(str(run_info_file), "r") as f:
20412042
run_info = json.load(f)
2042-
else:
2043-
warnings.warn(f"Found no run_info file for {self.extension_name}, extension should be re-computed.")
2044-
run_info = None
20452043

20462044
elif self.format == "zarr":
20472045
extension_group = self._get_zarr_extension_group(mode="r")
20482046
run_info = extension_group.attrs.get("run_info", None)
2047+
20492048
if run_info is None:
20502049
warnings.warn(f"Found no run_info file for {self.extension_name}, extension should be re-computed.")
20512050
self.run_info = run_info

src/spikeinterface/core/tests/test_globals.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_global_tmp_folder(create_cache_folder):
3737

3838
def test_global_job_kwargs():
3939
job_kwargs = dict(
40-
pool_engine="thread",
40+
pool_engine="process",
4141
n_jobs=4,
4242
chunk_duration="1s",
4343
progress_bar=True,
@@ -47,7 +47,7 @@ def test_global_job_kwargs():
4747
global_job_kwargs = get_global_job_kwargs()
4848

4949
assert global_job_kwargs == dict(
50-
pool_engine="thread",
50+
pool_engine="process",
5151
n_jobs=1,
5252
chunk_duration="1s",
5353
progress_bar=True,
@@ -62,7 +62,7 @@ def test_global_job_kwargs():
6262
set_global_job_kwargs(**partial_job_kwargs)
6363
global_job_kwargs = get_global_job_kwargs()
6464
assert global_job_kwargs == dict(
65-
pool_engine="thread",
65+
pool_engine="process",
6666
n_jobs=2,
6767
chunk_duration="1s",
6868
progress_bar=True,

src/spikeinterface/postprocessing/template_metrics.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,18 @@ def _run(self, verbose=False):
327327
)
328328

329329
existing_metrics = []
330-
tm_extension = self.sorting_analyzer.get_extension("template_metrics")
331-
if (
332-
delete_existing_metrics is False
333-
and tm_extension is not None
334-
and tm_extension.data.get("metrics") is not None
335-
):
336-
existing_metrics = tm_extension.params["metric_names"]
330+
331+
# Check if we need to propogate any old metrics. If so, we'll do that.
332+
# Otherwise, we'll avoid attempting to load an empty template_metrics.
333+
if set(self.params["metrics_to_compute"]) != set(self.params["metric_names"]):
334+
335+
tm_extension = self.sorting_analyzer.get_extension("template_metrics")
336+
if (
337+
delete_existing_metrics is False
338+
and tm_extension is not None
339+
and tm_extension.data.get("metrics") is not None
340+
):
341+
existing_metrics = tm_extension.params["metric_names"]
337342

338343
existing_metrics = []
339344
# here we get in the loaded via the dict only (to avoid full loading from disk after params reset)

src/spikeinterface/sorters/external/kilosort.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def _get_specific_options(cls, ops, params) -> dict:
206206

207207
# options for posthoc merges (under construction)
208208
ops["fracse"] = 0.1 # binning step along discriminant axis for posthoc merges (in units of sd)
209-
ops["epu"] = np.Inf
209+
ops["epu"] = np.inf
210210

211211
ops["ForceMaxRAMforDat"] = 20e9 # maximum RAM the algorithm will try to use; on Windows it will autodetect.
212212

src/spikeinterface/sorters/external/kilosortbase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def _generate_ops_file(cls, recording, params, sorter_output_folder, binary_file
9696
ops["fbinary"] = str(binary_file_path.absolute()) # will be created for 'openEphys'
9797
ops["fproc"] = str((sorter_output_folder / "temp_wh.dat").absolute()) # residual from RAM of preprocessed data
9898
ops["root"] = str(sorter_output_folder.absolute()) # 'openEphys' only: where raw files are
99-
ops["trange"] = [0, np.Inf] # time range to sort
99+
ops["trange"] = [0, np.inf] # time range to sort
100100
ops["chanMap"] = str((sorter_output_folder / "chanMap.mat").absolute())
101101

102102
ops["fs"] = recording.get_sampling_frequency() # sample rate

src/spikeinterface/sorters/internal/spyking_circus2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Spykingcircus2Sorter(ComponentsBasedSorter):
3232
"sparsity": {"method": "snr", "amplitude_mode": "peak_to_peak", "threshold": 0.25},
3333
"filtering": {"freq_min": 150, "freq_max": 7000, "ftype": "bessel", "filter_order": 2, "margin_ms": 10},
3434
"whitening": {"mode": "local", "regularize": False},
35-
"detection": {"peak_sign": "neg", "detect_threshold": 4},
35+
"detection": {"peak_sign": "neg", "detect_threshold": 5},
3636
"selection": {
3737
"method": "uniform",
3838
"n_peaks_per_channel": 5000,
@@ -134,7 +134,7 @@ def _run_from_folder(cls, sorter_output_folder, params, verbose):
134134
ms_before = params["general"].get("ms_before", 2)
135135
ms_after = params["general"].get("ms_after", 2)
136136
radius_um = params["general"].get("radius_um", 75)
137-
exclude_sweep_ms = params["detection"].get("exclude_sweep_ms", max(ms_before, ms_after) / 2)
137+
exclude_sweep_ms = params["detection"].get("exclude_sweep_ms", max(ms_before, ms_after))
138138

139139
## First, we are filtering the data
140140
filtering_params = params["filtering"].copy()
@@ -265,6 +265,7 @@ def _run_from_folder(cls, sorter_output_folder, params, verbose):
265265
clustering_params["ms_after"] = ms_after
266266
clustering_params["verbose"] = verbose
267267
clustering_params["tmp_folder"] = sorter_output_folder / "clustering"
268+
clustering_params["debug"] = params["debug"]
268269
clustering_params["noise_threshold"] = detection_params.get("detect_threshold", 4)
269270

270271
legacy = clustering_params.get("legacy", True)

0 commit comments

Comments
 (0)