Skip to content

Commit be7ce8d

Browse files
authored
Merge pull request #3210 from mhhennig/main
Now exclusive support for HS v0.4 (Lightning)
2 parents 0a60f7c + a20c5c3 commit be7ce8d

3 files changed

Lines changed: 82 additions & 152 deletions

File tree

doc/get_started/install_sorters.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ Herdingspikes2
4343

4444
* Python + C++
4545
* Url: https://github.com/mhhennig/hs2
46-
* Authors: Matthias Hennig, Jano Horvath,Cole Hurwitz, Oliver Muthmann, Albert Puente Encinas, Martino Sorbaro, Cesar Juarez Ramirez, Raimon Wintzer: GUI and visualisation
46+
* Authors: Matthias Hennig, Jano Horvath, Cole Hurwitz, Rickey K. Liang, Oliver Muthmann, Albert Puente Encinas, Martino Sorbaro, Cesar Juarez Ramirez, Raimon Wintzer
4747
* Installation::
4848

49+
pip install cython numpy
4950
pip install herdingspikes
5051

5152

src/spikeinterface/comparison/multicomparisons.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def save_to_folder(self, save_folder):
186186
warnings.warn(
187187
"save_to_folder() is deprecated. "
188188
"You should save and load the multi sorting comparison object using pickle."
189-
"\n>>> pickle.dump(mcmp, open('mcmp.pkl', 'wb')))))\n>>> mcmp_loaded = pickle.load(open('mcmp.pkl', 'rb'))",
189+
"\n>>> pickle.dump(mcmp, open('mcmp.pkl', 'wb'))\n>>> mcmp_loaded = pickle.load(open('mcmp.pkl', 'rb'))",
190190
DeprecationWarning,
191191
stacklevel=2,
192192
)
@@ -218,7 +218,7 @@ def load_from_folder(folder_path):
218218
warnings.warn(
219219
"load_from_folder() is deprecated. "
220220
"You should save and load the multi sorting comparison object using pickle."
221-
"\n>>> pickle.dump(mcmp, open('mcmp.pkl', 'wb')))))\n>>> mcmp_loaded = pickle.load(open('mcmp.pkl', 'rb'))",
221+
"\n>>> pickle.dump(mcmp, open('mcmp.pkl', 'wb'))\n>>> mcmp_loaded = pickle.load(open('mcmp.pkl', 'rb'))",
222222
DeprecationWarning,
223223
stacklevel=2,
224224
)

src/spikeinterface/sorters/external/herdingspikes.py

Lines changed: 78 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
from __future__ import annotations
22

33
from pathlib import Path
4-
import copy
54
from packaging import version
65

76
from ..basesorter import BaseSorter
8-
from spikeinterface.core.old_api_utils import NewToOldRecording
97

10-
from spikeinterface.core import load_extractor
118
from spikeinterface.extractors import HerdingspikesSortingExtractor
129

1310

@@ -19,90 +16,72 @@ class HerdingspikesSorter(BaseSorter):
1916
requires_locations = True
2017
compatible_with_parallel = {"loky": True, "multiprocessing": True, "threading": False}
2118
_default_params = {
22-
# core params
23-
"clustering_bandwidth": 5.5, # 5.0,
24-
"clustering_alpha": 5.5, # 5.0,
19+
"chunk_size": None,
20+
"rescale": True,
21+
"rescale_value": -1280.0,
22+
"common_reference": "median",
23+
"spike_duration": 1.0,
24+
"amp_avg_duration": 0.4,
25+
"threshold": 8.0,
26+
"min_avg_amp": 1.0,
27+
"AHP_thr": 0.0,
28+
"neighbor_radius": 90.0,
29+
"inner_radius": 70.0,
30+
"peak_jitter": 0.25,
31+
"rise_duration": 0.26,
32+
"decay_filtering": False,
33+
"decay_ratio": 1.0,
34+
"localize": True,
35+
"save_shape": True,
36+
"out_file": "HS2_detected",
37+
"left_cutout_time": 0.3,
38+
"right_cutout_time": 1.8,
39+
"verbose": True,
40+
"clustering_bandwidth": 4.0,
41+
"clustering_alpha": 4.5,
2542
"clustering_n_jobs": -1,
2643
"clustering_bin_seeding": True,
27-
"clustering_min_bin_freq": 16, # 10,
44+
"clustering_min_bin_freq": 4,
2845
"clustering_subset": None,
29-
"left_cutout_time": 0.3, # 0.2,
30-
"right_cutout_time": 1.8, # 0.8,
31-
"detect_threshold": 20, # 24, #15,
32-
# extra probe params
33-
"probe_masked_channels": [],
34-
"probe_inner_radius": 70,
35-
"probe_neighbor_radius": 90,
36-
"probe_event_length": 0.26,
37-
"probe_peak_jitter": 0.2,
38-
# extra detection params
39-
"t_inc": 100000,
40-
"num_com_centers": 1,
41-
"maa": 12,
42-
"ahpthr": 11,
43-
"out_file_name": "HS2_detected",
44-
"decay_filtering": False,
45-
"save_all": False,
46-
"amp_evaluation_time": 0.4, # 0.14,
47-
"spk_evaluation_time": 1.0,
48-
# extra pca params
4946
"pca_ncomponents": 2,
5047
"pca_whiten": True,
51-
# bandpass filter
52-
"freq_min": 300.0,
53-
"freq_max": 6000.0,
54-
"filter": True,
55-
# rescale traces
56-
"pre_scale": True,
57-
"pre_scale_value": 20.0,
58-
# remove duplicates (based on spk_evaluation_time)
59-
"filter_duplicates": True,
6048
}
6149

6250
_params_description = {
63-
# core params
64-
"clustering_bandwidth": "Meanshift bandwidth, average spatial extent of spike clusters (um)",
65-
"clustering_alpha": "Scalar for the waveform PC features when clustering.",
66-
"clustering_n_jobs": "Number of cores to use for clustering.",
67-
"clustering_bin_seeding": "Enable clustering bin seeding.",
68-
"clustering_min_bin_freq": "Minimum spikes per bin for bin seeding.",
69-
"clustering_subset": "Number of spikes used to build clusters. All by default.",
70-
"left_cutout_time": "Cutout size before peak (ms).",
71-
"right_cutout_time": "Cutout size after peak (ms).",
72-
"detect_threshold": "Detection threshold",
73-
# extra probe params
74-
"probe_masked_channels": "Masked channels",
75-
"probe_inner_radius": "Radius of area around probe channel for localization",
76-
"probe_neighbor_radius": "Radius of area around probe channel for neighbor classification.",
77-
"probe_event_length": "Duration of a spike event (ms)",
78-
"probe_peak_jitter": "Maximum peak misalignment for synchronous spike (ms)",
79-
# extra detection params
80-
"t_inc": "Number of samples per chunk during detection.",
81-
"num_com_centers": "Number of centroids to average when localizing.",
82-
"maa": "Minimum summed spike amplitude for spike acceptance.",
83-
"ahpthr": "Requires magnitude of spike rebound for acceptance",
84-
"out_file_name": "File name for storage of unclustered detected spikes",
85-
"decay_filtering": "Experimental: Set to True at your risk",
86-
"save_all": "Save all working files after sorting (slow)",
87-
"amp_evaluation_time": "Amplitude evaluation time (ms)",
88-
"spk_evaluation_time": "Spike evaluation time (ms)",
89-
# extra pca params
90-
"pca_ncomponents": "Number of principal components to use when clustering",
91-
"pca_whiten": "If true, whiten data for pca",
92-
# bandpass filter
93-
"freq_min": "High-pass filter cutoff frequency",
94-
"freq_max": "Low-pass filter cutoff frequency",
95-
"filter": "Enable or disable filter",
96-
# rescale traces
97-
"pre_scale": "Scales recording traces to optimize HerdingSpikes performance",
98-
"pre_scale_value": "Scale to apply in case of pre-scaling of traces",
99-
# remove duplicates (based on spk_evaluation_time)
100-
"filter_duplicates": "Remove spike duplicates (based on spk_evaluation_time)",
51+
"localize": "Perform spike localization. (`bool`, `True`)",
52+
"save_shape": "Save spike shape. (`bool`, `True`)",
53+
"out_file": "Path and filename to store detection and clustering results. (`str`, `HS2_detected`)",
54+
"verbose": "Print progress information. (`bool`, `True`)",
55+
"chunk_size": " Number of samples per chunk during detection. If `None`, a suitable value will be estimated. (`int`, `None`)",
56+
"common_reference": "Method for common reference filtering, can be `average` or `median` (`str`, `median`)",
57+
"rescale": "Automatically re-scale the data. (`bool`, `True`)",
58+
"rescale_value": "Factor by which data is re-scaled. (`float`, `-1280.0`)",
59+
"threshold": "Spike detection threshold. (`float`, `8.0`)",
60+
"spike_duration": "Maximum duration over which a spike is evaluated (ms). (`float`, `1.0`)",
61+
"amp_avg_duration": "Maximum duration over which the spike amplitude is evaluated (ms). (`float`, `0.4`)",
62+
"min_avg_amp": "Minimum integrated spike amplitude for a true spike. (`float`, `1.0`)",
63+
"AHP_thr": "Minimum value of the spike repolarisation for a true spike. (`float`, `0.0`)",
64+
"neighbor_radius": "Radius of area around probe channel for neighbor classification (microns). (`float`, `90.0`)",
65+
"inner_radius": "Radius of area around probe channel for spike localisation (microns). (`float`, `70.0`)",
66+
"peak_jitter": "Maximum peak misalignment for synchronous spike (ms). (`float`, `0.25`)",
67+
"rise_duration": "Maximum spike rise time, in milliseconds. (`float`, `0.26`)",
68+
"decay_filtering": "Exclude duplicate spikes based on spatial decay pattern, experimental. (`bool`,`False`)",
69+
"decay_ratio": "Spatial decay rate for `decay_filtering`. (`float`,`1.0`)",
70+
"left_cutout_time": "Length of cutout before peak (ms). (`float`, `0.3`)",
71+
"right_cutout_time": "Length of cutout after peak (ms). (`float`, `1.8`)",
72+
"pca_ncomponents": "Number of principal components to use when clustering. (`int`, `2`)",
73+
"pca_whiten": "If `True`, whiten data for PCA. (`bool`, `True`)",
74+
"clustering_bandwidth": "Meanshift bandwidth, average spatial extent of spike clusters (microns). (`float`, `4.0`)",
75+
"clustering_alpha": "Scalar for the waveform PC features when clustering. (`float`, `4.5`)",
76+
"clustering_n_jobs": "Number of cores to use for clustering, use `-1` for all available cores. (`int`, `-1`)",
77+
"clustering_bin_seeding": "Enable clustering bin seeding. (`bool`, `True`)",
78+
"clustering_min_bin_freq": "Minimum spikes per bin for bin seeding. (`int`, `4`)",
79+
"clustering_subset": "Number of spikes used to build clusters. All by default. (`int`, `None`)",
10180
}
10281

103-
sorter_description = """Herding Spikes is a density-based spike sorter designed for high-density retinal recordings.
82+
sorter_description = """Herding Spikes is a density-based spike sorter designed for large-scale high-density recordings.
10483
It uses both PCA features and an estimate of the spike location to cluster different units.
105-
For more information see https://doi.org/10.1016/j.jneumeth.2016.06.006"""
84+
For more information see https://www.sciencedirect.com/science/article/pii/S221112471730236X"""
10685

10786
installation_mesg = """\nTo use HerdingSpikes run:\n
10887
>>> pip install herdingspikes
@@ -130,100 +109,50 @@ def get_sorter_version(cls):
130109

131110
@classmethod
132111
def _check_apply_filter_in_params(cls, params):
133-
return params["filter"]
112+
return False
134113

135114
@classmethod
136115
def _setup_recording(cls, recording, sorter_output_folder, params, verbose):
137-
# nothing to copy inside the folder : Herdingspikes used natively spikeinterface
116+
# nothing to copy inside the folder : Herdingspikes uses spikeinterface natively
138117
pass
139118

140119
@classmethod
141120
def _run_from_folder(cls, sorter_output_folder, params, verbose):
142121
import herdingspikes as hs
143-
from spikeinterface.preprocessing import bandpass_filter, normalize_by_quantile
144122

145123
hs_version = version.parse(hs.__version__)
146124

147-
if hs_version >= version.parse("0.3.99"):
148-
new_api = True
125+
if hs_version >= version.parse("0.4.001"):
126+
lightning_api = True
149127
else:
150-
new_api = False
128+
lightning_api = False
129+
130+
assert (
131+
lightning_api
132+
), "HerdingSpikes version <0.4.001 is no longer supported. run:\n>>> pip install --upgrade herdingspikes"
151133

152134
recording = cls.load_recording_from_folder(sorter_output_folder.parent, with_warnings=False)
153135

136+
sorted_file = str(sorter_output_folder / "HS2_sorted.hdf5")
137+
params["out_file"] = str(sorter_output_folder / "HS2_detected")
154138
p = params
155139

156-
# Bandpass filter
157-
if p["filter"] and p["freq_min"] is not None and p["freq_max"] is not None:
158-
recording = bandpass_filter(recording=recording, freq_min=p["freq_min"], freq_max=p["freq_max"])
159-
160-
if p["pre_scale"]:
161-
recording = normalize_by_quantile(
162-
recording=recording, scale=p["pre_scale_value"], median=0.0, q1=0.05, q2=0.95
163-
)
164-
165-
if new_api:
166-
recording_to_hs = recording
167-
else:
168-
print(
169-
"herdingspikes version<0.3.99 uses the OLD spikeextractors with NewToOldRecording.\n"
170-
"Consider updating herdingspikes (pip install herdingspikes>=0.3.99)"
171-
)
172-
recording_to_hs = NewToOldRecording(recording)
173-
174-
# this should have its name changed
175-
Probe = hs.probe.RecordingExtractor(
176-
recording_to_hs,
177-
masked_channels=p["probe_masked_channels"],
178-
inner_radius=p["probe_inner_radius"],
179-
neighbor_radius=p["probe_neighbor_radius"],
180-
event_length=p["probe_event_length"],
181-
peak_jitter=p["probe_peak_jitter"],
182-
)
183-
184-
H = hs.HSDetection(
185-
Probe,
186-
file_directory_name=str(sorter_output_folder),
187-
left_cutout_time=p["left_cutout_time"],
188-
right_cutout_time=p["right_cutout_time"],
189-
threshold=p["detect_threshold"],
190-
to_localize=True,
191-
num_com_centers=p["num_com_centers"],
192-
maa=p["maa"],
193-
ahpthr=p["ahpthr"],
194-
out_file_name=p["out_file_name"],
195-
decay_filtering=p["decay_filtering"],
196-
save_all=p["save_all"],
197-
amp_evaluation_time=p["amp_evaluation_time"],
198-
spk_evaluation_time=p["spk_evaluation_time"],
140+
det = hs.HSDetectionLightning(recording, p)
141+
det.DetectFromRaw()
142+
C = hs.HSClustering(det)
143+
C.ShapePCA()
144+
C.CombinedClustering(
145+
alpha=p["clustering_alpha"],
146+
cluster_subset=p["clustering_subset"],
147+
bandwidth=p["clustering_bandwidth"],
148+
bin_seeding=p["clustering_bin_seeding"],
149+
min_bin_freq=p["clustering_min_bin_freq"],
150+
n_jobs=p["clustering_n_jobs"],
199151
)
200152

201-
H.DetectFromRaw(load=True, tInc=int(p["t_inc"]))
202-
203-
sorted_file = str(sorter_output_folder / "HS2_sorted.hdf5")
204-
if not H.spikes.empty:
205-
C = hs.HSClustering(H)
206-
C.ShapePCA(pca_ncomponents=p["pca_ncomponents"], pca_whiten=p["pca_whiten"])
207-
C.CombinedClustering(
208-
alpha=p["clustering_alpha"],
209-
cluster_subset=p["clustering_subset"],
210-
bandwidth=p["clustering_bandwidth"],
211-
bin_seeding=p["clustering_bin_seeding"],
212-
n_jobs=p["clustering_n_jobs"],
213-
min_bin_freq=p["clustering_min_bin_freq"],
214-
)
215-
else:
216-
C = hs.HSClustering(H)
217-
218-
if p["filter_duplicates"]:
219-
uids = C.spikes.cl.unique()
220-
for u in uids:
221-
s = C.spikes[C.spikes.cl == u].t.diff() < p["spk_evaluation_time"] / 1000 * Probe.fps
222-
C.spikes = C.spikes.drop(s.index[s])
223-
224153
if verbose:
225154
print("Saving to", sorted_file)
226-
C.SaveHDF5(sorted_file, sampling=Probe.fps)
155+
C.SaveHDF5(sorted_file, sampling=recording.get_sampling_frequency())
227156

228157
@classmethod
229158
def _get_result_from_folder(cls, sorter_output_folder):

0 commit comments

Comments
 (0)