Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ spikeinterface.preprocessing
.. autofunction:: detect_artifact_periods
.. autofunction:: detect_artifact_periods_by_envelope
.. autofunction:: detect_saturation_periods
.. autofunction:: detect_and_remove_artifacts
.. autofunction:: directional_derivative
.. autofunction:: filter
.. autofunction:: gaussian_filter
Expand Down
29 changes: 19 additions & 10 deletions src/spikeinterface/preprocessing/detect_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,14 @@ def compute(
Cut-off frequency (Hz) for the Gaussian low-pass filter applied to the
rectified signal when building the envelope.
seed : int | None, default: None
Random seed forwarded to :func:`~spikeinterface.core.get_noise_levels`.
If ``None``, ``get_noise_levels`` uses ``seed=0``.
Random seed forwarded to :func:`~spikeinterface.core.get_random_data_chunks`.
job_kwargs : dict | None, default: None
Keyword arguments forwarded to :func:`run_node_pipeline` (e.g.
``n_jobs``, ``chunk_duration``).
random_slices_kwargs : dict | None, default: None
Additional keyword arguments forwarded to the ``random_slices_kwargs``
argument of :func:`~spikeinterface.core.get_noise_levels`."""
argument of :func:`~spikeinterface.core.get_random_data_chunks`. Any ``"seed"``
key in this dictionary is overridden by the ``seed`` argument of this function."""


def detect_artifact_periods_by_envelope(
Expand Down Expand Up @@ -584,13 +584,15 @@ def _transform_internal_dtype_to_artifact_dtype(
sub_thr = artifacts[mask]
if len(sub_thr) > 0:
if not sub_thr["front"][0]:
local_thr = np.zeros(1, dtype=np.dtype(base_period_dtype + [("front", "bool")]))
local_thr = np.zeros(1, dtype=sub_thr.dtype)
local_thr["sample_index"] = 0
local_thr["segment_index"] = seg_index
local_thr["front"] = True
sub_thr = np.hstack((local_thr, sub_thr))
if sub_thr["front"][-1]:
local_thr = np.zeros(1, dtype=np.dtype(base_period_dtype + [("front", "bool")]))
local_thr = np.zeros(1, dtype=sub_thr.dtype)
local_thr["sample_index"] = recording.get_num_samples(seg_index)
local_thr["segment_index"] = seg_index
local_thr["front"] = False
sub_thr = np.hstack((sub_thr, local_thr))

Expand Down Expand Up @@ -710,9 +712,8 @@ class DetectAndRemoveArtifactsRecording(SilencedPeriodsRecording):
Keyword arguments forwarded to :func:`run_node_pipeline` (e.g.
``n_jobs``, ``chunk_duration``).
noise_levels_kwargs : dict | None, default: None
Keyword arguments for `spikeinterface.core.get_noise_levels()` function.

If none, `get_noise_levels` uses `seed=0` and `NoiseGeneratorRecording` generates a random seed using `numpy.random.default_rng`.
Keyword arguments for `spikeinterface.core.get_noise_levels()`, used only
when ``mode="noise"``.
mode : "zeros" | "noise" | "apodization", default: "zeros"
Determines what periods are replaced by. Can be one of the following:

Expand All @@ -725,7 +726,9 @@ class DetectAndRemoveArtifactsRecording(SilencedPeriodsRecording):
apodization_samples : int, default: 7
The factor used for the cosine taper when mode is "apodization". Higher values create a wider taper.
seed : int | None, default: None
Random seed for `get_noise_levels` and `NoiseGeneratorRecording`.
Random seed, if applicable. When ``method="envelope"``, used for chunk
subsampling. When ``mode="noise"``, for the noise used to fill the
silenced periods.
artifact_periods : np.ndarray | None, default: None
Optionally, pre-computed artifact periods can be passed directly to the constructor to skip the
detection step. If ``None``, artifact periods are detected on the fly using the specified method
Expand All @@ -749,8 +752,14 @@ def __init__(
else:
if recording_to_detect is None:
recording_to_detect = recording
detect_method_kwargs = dict(method_kwargs) if method_kwargs else {}
if method == "envelope":
detect_method_kwargs["seed"] = seed
artifact_periods = detect_artifact_periods(
recording_to_detect, method=method, method_kwargs=method_kwargs, job_kwargs=job_kwargs
recording_to_detect,
method=method,
method_kwargs=detect_method_kwargs,
job_kwargs=job_kwargs,
)
super().__init__(
recording,
Expand Down
8 changes: 4 additions & 4 deletions src/spikeinterface/preprocessing/preprocessing_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
from .clip import BlankSaturationRecording, blank_saturation, ClipRecording, clip
from .common_reference import CommonReferenceRecording, common_reference
from .remove_artifacts import RemoveArtifactsRecording, remove_artifacts
from .silence_periods import SilencedPeriodsRecording, silence_periods
from .silence_periods import (
SilencedPeriodsRecording,
silence_periods,
)
from .phase_shift import PhaseShiftRecording, phase_shift
from .zero_channel_pad import ZeroChannelPaddedRecording, zero_channel_pad
from .deepinterpolation import DeepInterpolatedRecording, deepinterpolate, train_deepinterpolation
Expand All @@ -50,8 +53,6 @@
from .unsigned_to_signed import UnsignedToSignedRecording, unsigned_to_signed
from .detect_artifacts import DetectAndRemoveArtifactsRecording, detect_and_remove_artifacts

# from .silence_artifacts import SilencedArtifactsRecording, silence_artifacts

_all_preprocesser_dict = {
# filter stuff
FilterRecording: filter,
Expand Down Expand Up @@ -92,7 +93,6 @@
DirectionalDerivativeRecording: directional_derivative,
AstypeRecording: astype,
UnsignedToSignedRecording: unsigned_to_signed,
# SilencedArtifactsRecording: silence_artifacts,
}
# we control import in the preprocessing init by setting an __all__

Expand Down
50 changes: 4 additions & 46 deletions src/spikeinterface/preprocessing/silence_periods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from spikeinterface.core.core_tools import define_function_handling_dict_from_class
from spikeinterface.core.recording_tools import get_noise_levels, get_chunk_with_margin
from spikeinterface.core.generate import MockRecording
from spikeinterface.core.job_tools import split_job_kwargs

from .basepreprocessor import BasePreprocessor, BasePreprocessorSegment
from .normalize_scale import ScaleRecording
Expand Down Expand Up @@ -41,8 +40,10 @@ class SilencedPeriodsRecording(BasePreprocessor):
noise_levels : array
Noise levels if already computed
seed : int | None, default: None
Random seed for `get_noise_levels` and `NoiseGeneratorRecording`.
If none, `get_noise_levels` uses `seed=0` and `NoiseGeneratorRecording` generates a random seed using `numpy.random.default_rng`.
Random seed for the noise fill (only used when ``mode="noise"``): both the
per-channel noise-level estimation (`spikeinterface.core.get_noise_levels`) and
the generated noise. Takes precedence over ``noise_levels_kwargs["random_slices_kwargs"]["seed"]``
if present.
**noise_levels_kwargs : Keyword arguments for `spikeinterface.core.get_noise_levels()` function

Returns
Expand Down Expand Up @@ -249,46 +250,3 @@ def get_traces(self, start_frame, end_frame, channel_indices):
silence_periods = define_function_handling_dict_from_class(
source_class=SilencedPeriodsRecording, name="silence_periods"
)


class DetectArtifactAndSilentPeriodsRecording(SilencedPeriodsRecording):
"""
Class doing artifact detection and lient at the same time.

See SilencedPeriodsRecording and detect_artifact_periods for details.
"""

_precomputable_kwarg_names = ["artifacts"]

def __init__(
self,
recording,
detect_artifact_method="envelope",
detect_artifact_kwargs=dict(),
periods=None,
mode="zeros",
noise_levels=None,
seed=None,
**noise_levels_kwargs,
):

if artifacts is None:
from spikeinterface.preprocessing import detect_artifact_periods

artifacts = detect_artifact_periods(
recording,
method=detect_artifact_method,
method_kwargs=detect_artifact_kwargs,
job_kwargs=None,
)

SilencedPeriodsRecording.__init__(
self, recording, periods=artifacts, mode=mode, noise_levels=noise_levels, seed=seed, **noise_levels_kwargs
)
# note self._kwargs["periods"] is done by SilencedPeriodsRecording and so the computaion is done once


# function for API
detect_artifacts_and_silent_periods = define_function_handling_dict_from_class(
source_class=DetectArtifactAndSilentPeriodsRecording, name="silence_artifacts"
)
11 changes: 10 additions & 1 deletion src/spikeinterface/preprocessing/tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,16 @@ def test_pipeline_equiv_to_step():

pp_dict = {pp_name: {}}

if pp_name in ["normalize_by_quantile", "center", "zscore", "whiten"]:
if pp_name in [
"normalize_by_quantile",
"center",
"zscore",
"whiten",
"detect_and_remove_bad_channels",
"detect_and_interpolate_bad_channels",
"detect_and_remove_artifacts",
]: # These steps aren't deterministic (because they sample random chunks),
# so make sure that both pipeline and class get the same seed.
pp_dict[pp_name] = {"seed": 1205}
pp_rec_from_class = pp_class(rec, seed=1205)
elif pp_name == "blank_saturation":
Expand Down
Loading