44import numpy as np
55from typing import Literal
66
7+ from spikeinterface .core .core_tools import define_function_handling_dict_from_class
78from .filter import highpass_filter
89from 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
9149
10150
11151def detect_bad_channels (
@@ -32,69 +172,7 @@ def detect_bad_channels(
32172 Perform bad channel detection.
33173 The recording is assumed to be filtered. If not, a highpass filter is applied on the fly.
34174
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+ {}
98176
99177 Returns
100178 -------
@@ -269,6 +347,9 @@ def detect_bad_channels(
269347 return bad_channel_ids , channel_labels
270348
271349
350+ detect_bad_channels .__doc__ = detect_bad_channels .__doc__ .format (_bad_channel_detection_kwargs_doc )
351+
352+
272353# ----------------------------------------------------------------------------------------------
273354# IBL Detect Bad Channels
274355# ----------------------------------------------------------------------------------------------
0 commit comments