4
4
import numpy as np
5
5
from typing import Literal
6
6
7
+ from spikeinterface .core .core_tools import define_function_handling_dict_from_class
7
8
from .filter import highpass_filter
8
9
from spikeinterface .core import get_random_data_chunks , order_channels_by_depth , BaseRecording
10
+ from spikeinterface .core .channelslice import ChannelSliceRecording
11
+
12
+ from inspect import signature
13
+
14
+ _bad_channel_detection_kwargs_doc = """Different methods are implemented:
15
+
16
+ * std : threhshold on channel standard deviations
17
+ If the standard deviation of a channel is greater than `std_mad_threshold` times the median of all
18
+ channels standard deviations, the channel is flagged as noisy
19
+ * mad : same as std, but using median absolute deviations instead
20
+ * coeherence+psd : method developed by the International Brain Laboratory that detects bad channels of three types:
21
+ * Dead channels are those with low similarity to the surrounding channels (n=`n_neighbors` median)
22
+ * Noise channels are those with power at >80% Nyquist above the psd_hf_threshold (default 0.02 uV^2 / Hz)
23
+ and a high coherence with "far away" channels"
24
+ * Out of brain channels are contigious regions of channels dissimilar to the median of all channels
25
+ at the top end of the probe (i.e. large channel number)
26
+ * neighborhood_r2
27
+ A method tuned for LFP use-cases, where channels should be highly correlated with their spatial
28
+ neighbors. This method estimates the correlation of each channel with the median of its spatial
29
+ neighbors, and considers channels bad when this correlation is too small.
30
+
31
+ Parameters
32
+ ----------
33
+ recording : BaseRecording
34
+ The recording for which bad channels are detected
35
+ method : "coeherence+psd" | "std" | "mad" | "neighborhood_r2", default: "coeherence+psd"
36
+ The method to be used for bad channel detection
37
+ std_mad_threshold : float, default: 5
38
+ The standard deviation/mad multiplier threshold
39
+ psd_hf_threshold : float, default: 0.02
40
+ For coherence+psd - an absolute threshold (uV^2/Hz) used as a cutoff for noise channels.
41
+ Channels with average power at >80% Nyquist larger than this threshold
42
+ will be labeled as noise
43
+ dead_channel_threshold : float, default: -0.5
44
+ For coherence+psd - threshold for channel coherence below which channels are labeled as dead
45
+ noisy_channel_threshold : float, default: 1
46
+ Threshold for channel coherence above which channels are labeled as noisy (together with psd condition)
47
+ outside_channel_threshold : float, default: -0.75
48
+ For coherence+psd - threshold for channel coherence above which channels at the edge of the recording are marked as outside
49
+ of the brain
50
+ outside_channels_location : "top" | "bottom" | "both", default: "top"
51
+ For coherence+psd - location of the outside channels. If "top", only the channels at the top of the probe can be
52
+ marked as outside channels. If "bottom", only the channels at the bottom of the probe can be
53
+ marked as outside channels. If "both", both the channels at the top and bottom of the probe can be
54
+ marked as outside channels
55
+ n_neighbors : int, default: 11
56
+ For coeherence+psd - number of channel neighbors to compute median filter (needs to be odd)
57
+ nyquist_threshold : float, default: 0.8
58
+ For coherence+psd - frequency with respect to Nyquist (Fn=1) above which the mean of the PSD is calculated and compared
59
+ with psd_hf_threshold
60
+ direction : "x" | "y" | "z", default: "y"
61
+ For coherence+psd - the depth dimension
62
+ highpass_filter_cutoff : float, default: 300
63
+ If the recording is not filtered, the cutoff frequency of the highpass filter
64
+ chunk_duration_s : float, default: 0.5
65
+ Duration of each chunk
66
+ num_random_chunks : int, default: 100
67
+ Number of random chunks
68
+ Having many chunks is important for reproducibility.
69
+ welch_window_ms : float, default: 10
70
+ Window size for the scipy.signal.welch that will be converted to nperseg
71
+ neighborhood_r2_threshold : float, default: 0.95
72
+ R^2 threshold for the neighborhood_r2 method.
73
+ neighborhood_r2_radius_um : float, default: 30
74
+ Spatial radius below which two channels are considered neighbors in the neighborhood_r2 method.
75
+ seed : int or None, default: None
76
+ The random seed to extract chunks
77
+ """
78
+
79
+
80
+ class DetectAndRemoveBadChannelsRecording (ChannelSliceRecording ):
81
+ """
82
+ Detects and removes bad channels. If `bad_channel_ids` are given,
83
+ the detection is skipped and uses these instead.
84
+
85
+ {}
86
+ bad_channel_ids : np.array | list | None, default: None
87
+ If given, these are used rather than being detected.
88
+ channel_labels : np.array | list | None, default: None
89
+ If given, these are labels given to the channels by the
90
+ detection process. Only intended for use when loading.
91
+
92
+ Returns
93
+ -------
94
+ removed_bad_channels_recording : DetectAndRemoveBadChannelsRecording
95
+ The recording with bad channels removed
96
+ """
97
+
98
+ _precomputable_kwarg_names = ["bad_channel_ids" , "channel_labels" ]
99
+
100
+ def __init__ (
101
+ self ,
102
+ parent_recording : BaseRecording ,
103
+ bad_channel_ids = None ,
104
+ channel_labels = None ,
105
+ ** detect_bad_channels_kwargs ,
106
+ ):
107
+
108
+ if bad_channel_ids is None :
109
+ bad_channel_ids , channel_labels = detect_bad_channels (
110
+ recording = parent_recording , ** detect_bad_channels_kwargs
111
+ )
112
+ else :
113
+ channel_labels = None
114
+
115
+ self ._main_ids = parent_recording .get_channel_ids ()
116
+ new_channel_ids = self .channel_ids [~ np .isin (self .channel_ids , bad_channel_ids )]
117
+
118
+ ChannelSliceRecording .__init__ (
119
+ self ,
120
+ parent_recording = parent_recording ,
121
+ channel_ids = new_channel_ids ,
122
+ )
123
+
124
+ self ._kwargs .update ({"bad_channel_ids" : bad_channel_ids })
125
+ if channel_labels is not None :
126
+ self ._kwargs .update ({"channel_labels" : channel_labels })
127
+
128
+ all_bad_channels_kwargs = _get_all_detect_bad_channel_kwargs (detect_bad_channels_kwargs )
129
+ self ._kwargs .update (all_bad_channels_kwargs )
130
+
131
+
132
+ detect_and_remove_bad_channels = define_function_handling_dict_from_class (
133
+ source_class = DetectAndRemoveBadChannelsRecording , name = "detect_and_remove_bad_channels"
134
+ )
135
+ DetectAndRemoveBadChannelsRecording .__doc__ = DetectAndRemoveBadChannelsRecording .__doc__ .format (
136
+ _bad_channel_detection_kwargs_doc
137
+ )
138
+
139
+
140
+ def _get_all_detect_bad_channel_kwargs (detect_bad_channels_kwargs ):
141
+ """Get the default parameters from `detect_bad_channels`, and update with any user-specified parameters."""
142
+
143
+ sig = signature (detect_bad_channels )
144
+ all_detect_bad_channels_kwargs = {
145
+ k : v .default for k , v in sig .parameters .items () if k not in ["recording" , "parent_recording" ]
146
+ }
147
+ all_detect_bad_channels_kwargs .update (detect_bad_channels_kwargs )
148
+ return all_detect_bad_channels_kwargs
9
149
10
150
11
151
def detect_bad_channels (
@@ -32,69 +172,7 @@ def detect_bad_channels(
32
172
Perform bad channel detection.
33
173
The recording is assumed to be filtered. If not, a highpass filter is applied on the fly.
34
174
35
- Different methods are implemented:
36
-
37
- * std : threhshold on channel standard deviations
38
- If the standard deviation of a channel is greater than `std_mad_threshold` times the median of all
39
- channels standard deviations, the channel is flagged as noisy
40
- * mad : same as std, but using median absolute deviations instead
41
- * coeherence+psd : method developed by the International Brain Laboratory that detects bad channels of three types:
42
- * Dead channels are those with low similarity to the surrounding channels (n=`n_neighbors` median)
43
- * Noise channels are those with power at >80% Nyquist above the psd_hf_threshold (default 0.02 uV^2 / Hz)
44
- and a high coherence with "far away" channels"
45
- * Out of brain channels are contigious regions of channels dissimilar to the median of all channels
46
- at the top end of the probe (i.e. large channel number)
47
- * neighborhood_r2
48
- A method tuned for LFP use-cases, where channels should be highly correlated with their spatial
49
- neighbors. This method estimates the correlation of each channel with the median of its spatial
50
- neighbors, and considers channels bad when this correlation is too small.
51
-
52
- Parameters
53
- ----------
54
- recording : BaseRecording
55
- The recording for which bad channels are detected
56
- method : "coeherence+psd" | "std" | "mad" | "neighborhood_r2", default: "coeherence+psd"
57
- The method to be used for bad channel detection
58
- std_mad_threshold : float, default: 5
59
- The standard deviation/mad multiplier threshold
60
- psd_hf_threshold : float, default: 0.02
61
- For coherence+psd - an absolute threshold (uV^2/Hz) used as a cutoff for noise channels.
62
- Channels with average power at >80% Nyquist larger than this threshold
63
- will be labeled as noise
64
- dead_channel_threshold : float, default: -0.5
65
- For coherence+psd - threshold for channel coherence below which channels are labeled as dead
66
- noisy_channel_threshold : float, default: 1
67
- Threshold for channel coherence above which channels are labeled as noisy (together with psd condition)
68
- outside_channel_threshold : float, default: -0.75
69
- For coherence+psd - threshold for channel coherence above which channels at the edge of the recording are marked as outside
70
- of the brain
71
- outside_channels_location : "top" | "bottom" | "both", default: "top"
72
- For coherence+psd - location of the outside channels. If "top", only the channels at the top of the probe can be
73
- marked as outside channels. If "bottom", only the channels at the bottom of the probe can be
74
- marked as outside channels. If "both", both the channels at the top and bottom of the probe can be
75
- marked as outside channels
76
- n_neighbors : int, default: 11
77
- For coeherence+psd - number of channel neighbors to compute median filter (needs to be odd)
78
- nyquist_threshold : float, default: 0.8
79
- For coherence+psd - frequency with respect to Nyquist (Fn=1) above which the mean of the PSD is calculated and compared
80
- with psd_hf_threshold
81
- direction : "x" | "y" | "z", default: "y"
82
- For coherence+psd - the depth dimension
83
- highpass_filter_cutoff : float, default: 300
84
- If the recording is not filtered, the cutoff frequency of the highpass filter
85
- chunk_duration_s : float, default: 0.5
86
- Duration of each chunk
87
- num_random_chunks : int, default: 100
88
- Number of random chunks
89
- Having many chunks is important for reproducibility.
90
- welch_window_ms : float, default: 10
91
- Window size for the scipy.signal.welch that will be converted to nperseg
92
- neighborhood_r2_threshold : float, default: 0.95
93
- R^2 threshold for the neighborhood_r2 method.
94
- neighborhood_r2_radius_um : float, default: 30
95
- Spatial radius below which two channels are considered neighbors in the neighborhood_r2 method.
96
- seed : int or None, default: None
97
- The random seed to extract chunks
175
+ {}
98
176
99
177
Returns
100
178
-------
@@ -269,6 +347,9 @@ def detect_bad_channels(
269
347
return bad_channel_ids , channel_labels
270
348
271
349
350
+ detect_bad_channels .__doc__ = detect_bad_channels .__doc__ .format (_bad_channel_detection_kwargs_doc )
351
+
352
+
272
353
# ----------------------------------------------------------------------------------------------
273
354
# IBL Detect Bad Channels
274
355
# ----------------------------------------------------------------------------------------------
0 commit comments