Repository: SpikeInterface/spikeinterface
Pull request #2947 (2024-05-31 12:26:19)

Fix marker collection to work on windows
If you look at the diff you will realize that there are unix slahses (e.g. `/`) hardcoded in the conftest to collect the tests for the markers. This PR changes this and also changes the core test to use the markers to test that this works correctly. If it does, the core test should runc orrectly as they are tested for all OSs.


Patch:
diff --git a/src/spikeinterface/preprocessing/motion.py b/src/spikeinterface/preprocessing/motion.py
index 14a0d36d7..db5ebb152 100644
--- a/src/spikeinterface/preprocessing/motion.py
+++ b/src/spikeinterface/preprocessing/motion.py
@@ -5,7 +5,6 @@ from pathlib import Path
 
 import numpy as np
 import json
-import copy
 
 from spikeinterface.core import get_noise_levels, fix_job_kwargs
 from spikeinterface.core.job_tools import _shared_job_kwargs_doc
diff --git a/src/spikeinterface/qualitymetrics/utils.py b/src/spikeinterface/qualitymetrics/utils.py
index 553719bba..0a267125b 100644
--- a/src/spikeinterface/qualitymetrics/utils.py
+++ b/src/spikeinterface/qualitymetrics/utils.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 
 import numpy as np
-from scipy.stats import multivariate_normal
 
 
 def create_ground_truth_pc_distributions(center_locations, total_points):
@@ -22,6 +21,8 @@ def create_ground_truth_pc_distributions(center_locations, total_points):
     numpy.array
         Labels for each point
     """
+    from scipy.stats import multivariate_normal
+
     np.random.seed(0)
 
     if len(np.array(center_locations).shape) == 1:
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_clustering.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_clustering.py
index 7a0f9ba25..2da950ced 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_clustering.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_clustering.py
@@ -6,24 +6,13 @@ from spikeinterface.comparison import GroundTruthComparison
 from spikeinterface.widgets import (
     plot_probe_map,
     plot_agreement_matrix,
-    plot_comparison_collision_by_similarity,
-    plot_unit_templates,
-    plot_unit_waveforms,
 )
-from spikeinterface.comparison.comparisontools import make_matching_events
 
-import matplotlib.patches as mpatches
 
-# from spikeinterface.postprocessing import get_template_extremum_channel
-from spikeinterface.core import get_noise_levels
-
-import pylab as plt
 import numpy as np
 
 
 from .benchmark_tools import BenchmarkStudy, Benchmark
-from spikeinterface.core.basesorting import minimum_spike_dtype
-from spikeinterface.core.basesorting import minimum_spike_dtype
 from spikeinterface.core.sortinganalyzer import create_sorting_analyzer
 from spikeinterface.core.template_tools import get_template_extremum_channel
 
@@ -180,6 +169,7 @@ class ClusteringStudy(BenchmarkStudy):
     def plot_agreements(self, case_keys=None, figsize=(15, 15)):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -193,6 +183,7 @@ class ClusteringStudy(BenchmarkStudy):
     def plot_performances_vs_snr(self, case_keys=None, figsize=(15, 15)):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=1, nrows=3, figsize=figsize)
 
@@ -218,6 +209,7 @@ class ClusteringStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -254,6 +246,7 @@ class ClusteringStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -308,6 +301,7 @@ class ClusteringStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -365,6 +359,7 @@ class ClusteringStudy(BenchmarkStudy):
         return fig
 
     def plot_unit_losses(self, case_before, case_after, metric="agreement", figsize=None):
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=1, nrows=3, figsize=figsize)
 
@@ -407,6 +402,7 @@ class ClusteringStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         num_methods = len(case_keys)
         fig, axs = plt.subplots(ncols=num_methods, nrows=num_methods, figsize=(10, 10))
@@ -442,6 +438,8 @@ class ClusteringStudy(BenchmarkStudy):
                         ax.set_xticks([])
                     if i == num_methods - 1 and j == num_methods - 1:
                         patches = []
+                        import matplotlib.patches as mpatches
+
                         for color, name in zip(colors, performance_names):
                             patches.append(mpatches.Patch(color=color, label=name))
                         ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0.0)
@@ -460,6 +458,7 @@ class ClusteringStudy(BenchmarkStudy):
     def plot_some_over_merged(self, case_keys=None, overmerged_score=0.05, max_units=5, figsize=None):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         figs = []
         for count, key in enumerate(case_keys):
@@ -498,6 +497,7 @@ class ClusteringStudy(BenchmarkStudy):
     def plot_some_over_splited(self, case_keys=None, oversplit_score=0.05, max_units=5, figsize=None):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         figs = []
         for count, key in enumerate(case_keys):
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_matching.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_matching.py
index c003c71d7..cf91c8b87 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_matching.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_matching.py
@@ -8,8 +8,6 @@ from spikeinterface.widgets import (
     plot_comparison_collision_by_similarity,
 )
 
-import pylab as plt
-import matplotlib.patches as mpatches
 import numpy as np
 from spikeinterface.sortingcomponents.benchmark.benchmark_tools import Benchmark, BenchmarkStudy
 from spikeinterface.core.basesorting import minimum_spike_dtype
@@ -66,6 +64,7 @@ class MatchingStudy(BenchmarkStudy):
     def plot_agreements(self, case_keys=None, figsize=None):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -132,6 +131,8 @@ class MatchingStudy(BenchmarkStudy):
             case_keys = list(self.cases.keys())
 
         num_methods = len(case_keys)
+        import pylab as plt
+
         fig, axs = plt.subplots(ncols=num_methods, nrows=num_methods, figsize=(10, 10))
         for i, key1 in enumerate(case_keys):
             for j, key2 in enumerate(case_keys):
@@ -165,6 +166,8 @@ class MatchingStudy(BenchmarkStudy):
                         ax.set_xticks([])
                     if i == num_methods - 1 and j == num_methods - 1:
                         patches = []
+                        import matplotlib.patches as mpatches
+
                         for color, name in zip(colors, performance_names):
                             patches.append(mpatches.Patch(color=color, label=name))
                         ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0.0)
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_estimation.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_estimation.py
index 5d3c9c207..3212f95e7 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_estimation.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_estimation.py
@@ -1,12 +1,9 @@
 from __future__ import annotations
 
-import json
 import time
 from pathlib import Path
-import pickle
 
 import numpy as np
-import scipy.interpolate
 
 from spikeinterface.core import get_noise_levels
 from spikeinterface.sortingcomponents.peak_detection import detect_peaks
@@ -16,7 +13,6 @@ from spikeinterface.sortingcomponents.motion_estimation import estimate_motion
 from spikeinterface.sortingcomponents.benchmark.benchmark_tools import Benchmark, BenchmarkStudy, _simpleaxis
 
 
-import matplotlib.pyplot as plt
 from spikeinterface.widgets import plot_probe_map
 
 # import MEArec as mr
@@ -36,6 +32,7 @@ def get_gt_motion_from_unit_displacement(
     spatial_bins,
     direction_dim=1,
 ):
+    import scipy.interpolate
 
     unit_displacements = unit_displacements[:, :, direction_dim]
     times = np.arange(unit_displacements.shape[0]) / displacement_sampling_frequency
@@ -166,6 +163,7 @@ class MotionEstimationStudy(BenchmarkStudy):
         self.plot_drift(case_keys=case_keys, tested_drift=False, scaling_probe=scaling_probe, figsize=figsize)
 
     def plot_drift(self, case_keys=None, gt_drift=True, tested_drift=True, scaling_probe=1.0, figsize=(8, 6)):
+        import matplotlib.pyplot as plt
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
@@ -231,6 +229,7 @@ class MotionEstimationStudy(BenchmarkStudy):
             # ax0.set_ylim()
 
     def plot_errors(self, case_keys=None, figsize=None, lim=None):
+        import matplotlib.pyplot as plt
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_interpolation.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_interpolation.py
index af45f7421..a51542464 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_interpolation.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_motion_interpolation.py
@@ -1,13 +1,9 @@
 from __future__ import annotations
 
 import numpy as np
-import pandas as pd
 
-from pathlib import Path
-import shutil
 
-
-from spikeinterface.sorters import run_sorter, read_sorter_folder
+from spikeinterface.sorters import run_sorter
 
 from spikeinterface.comparison import GroundTruthComparison
 from spikeinterface.sortingcomponents.motion_interpolation import InterpolateMotionRecording
@@ -17,9 +13,6 @@ from spikeinterface.curation import MergeUnitsSorting
 from spikeinterface.sortingcomponents.benchmark.benchmark_tools import Benchmark, BenchmarkStudy, _simpleaxis
 
 
-import matplotlib.pyplot as plt
-
-
 class MotionInterpolationBenchmark(Benchmark):
     def __init__(
         self,
@@ -132,6 +125,7 @@ class MotionInterpolationStudy(BenchmarkStudy):
         ax=None,
         axes=None,
     ):
+        import matplotlib.pyplot as plt
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
@@ -143,6 +137,7 @@ class MotionInterpolationStudy(BenchmarkStudy):
 
         if mode == "ordered_accuracy":
             if ax is None:
+
                 fig, ax = plt.subplots(figsize=figsize)
             else:
                 fig = ax.figure
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_detection.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_detection.py
index 09220d162..062309b58 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_detection.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_detection.py
@@ -1,24 +1,15 @@
 from __future__ import annotations
 
-from spikeinterface.preprocessing import bandpass_filter, common_reference
 from spikeinterface.sortingcomponents.peak_detection import detect_peaks
 from spikeinterface.core import NumpySorting
-from spikeinterface.qualitymetrics import compute_quality_metrics
 from spikeinterface.comparison import GroundTruthComparison
 from spikeinterface.widgets import (
-    plot_probe_map,
     plot_agreement_matrix,
-    plot_comparison_collision_by_similarity,
-    plot_unit_templates,
-    plot_unit_waveforms,
 )
 from spikeinterface.comparison.comparisontools import make_matching_events
 from spikeinterface.core import get_noise_levels
 
-import time
-import string, random
-import pylab as plt
-import os
+
 import numpy as np
 
 from spikeinterface.sortingcomponents.benchmark.benchmark_tools import Benchmark, BenchmarkStudy
@@ -136,6 +127,7 @@ class PeakDetectionStudy(BenchmarkStudy):
     def plot_agreements_by_channels(self, case_keys=None, figsize=(15, 15)):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -147,6 +139,7 @@ class PeakDetectionStudy(BenchmarkStudy):
     def plot_agreements_by_units(self, case_keys=None, figsize=(15, 15)):
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -184,6 +177,7 @@ class PeakDetectionStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
 
@@ -206,6 +200,7 @@ class PeakDetectionStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=len(case_keys), nrows=1, figsize=figsize, squeeze=False)
         for count, key in enumerate(case_keys):
@@ -226,6 +221,7 @@ class PeakDetectionStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figsize, squeeze=True)
         for key in case_keys:
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_localization.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_localization.py
index 8e2f3d496..5c4085af7 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_localization.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_peak_localization.py
@@ -5,8 +5,6 @@ from spikeinterface.postprocessing.unit_localization import (
     compute_monopolar_triangulation,
     compute_grid_convolution,
 )
-from spikeinterface.qualitymetrics import compute_quality_metrics
-import pylab as plt
 import numpy as np
 from spikeinterface.sortingcomponents.benchmark.benchmark_tools import Benchmark, BenchmarkStudy
 from spikeinterface.core.sortinganalyzer import create_sorting_analyzer
@@ -86,6 +84,7 @@ class PeakLocalizationStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=3, nrows=1, figsize=(15, 5))
 
@@ -213,6 +212,7 @@ class UnitLocalizationStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=3, nrows=1, figsize=(15, 5))
 
@@ -238,6 +238,7 @@ class UnitLocalizationStudy(BenchmarkStudy):
 
         if case_keys is None:
             case_keys = list(self.cases.keys())
+        import pylab as plt
 
         fig, axs = plt.subplots(ncols=3, nrows=1, figsize=(15, 5))
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/benchmark_tools.py b/src/spikeinterface/sortingcomponents/benchmark/benchmark_tools.py
index 6afac8d13..b2cf56eb9 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/benchmark_tools.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/benchmark_tools.py
@@ -4,16 +4,12 @@ from pathlib import Path
 import shutil
 import json
 import numpy as np
-import pandas as pd
 
-import matplotlib.pyplot as plt
 
 import time
 
-import os
 
 from spikeinterface.core import SortingAnalyzer
-from spikeinterface.core.core_tools import check_json
 from spikeinterface import load_extractor, split_job_kwargs, create_sorting_analyzer, load_sorting_analyzer
 from spikeinterface.widgets import get_some_colors
 
@@ -252,6 +248,7 @@ class BenchmarkStudy:
             benchmark = self.benchmarks[key]
             assert benchmark is not None
             run_times[key] = benchmark.result["run_time"]
+        import pandas as pd
 
         df = pd.DataFrame(dict(run_times=run_times))
         if not isinstance(self.levels, str):
@@ -264,6 +261,8 @@ class BenchmarkStudy:
         run_times = self.get_run_times(case_keys=case_keys)
 
         colors = self.get_colors()
+        import matplotlib.pyplot as plt
+
         fig, ax = plt.subplots()
         labels = []
         for i, key in enumerate(case_keys):
diff --git a/src/spikeinterface/sortingcomponents/clustering/circus.py b/src/spikeinterface/sortingcomponents/clustering/circus.py
index 288e0cb97..65a89702c 100644
--- a/src/spikeinterface/sortingcomponents/clustering/circus.py
+++ b/src/spikeinterface/sortingcomponents/clustering/circus.py
@@ -21,7 +21,6 @@ from spikeinterface.core.recording_tools import get_noise_levels, get_channel_di
 from spikeinterface.core.job_tools import fix_job_kwargs
 from spikeinterface.sortingcomponents.peak_selection import select_peaks
 from spikeinterface.sortingcomponents.waveforms.temporal_pca import TemporalPCAProjection
-from sklearn.decomposition import TruncatedSVD, PCA
 from spikeinterface.core.template import Templates
 from spikeinterface.core.sparsity import compute_sparsity
 from spikeinterface.sortingcomponents.tools import remove_empty_templates
@@ -32,6 +31,7 @@ from spikeinterface.core.node_pipeline import (
     PeakRetriever,
 )
 
+
 from spikeinterface.sortingcomponents.tools import extract_waveform_at_max_channel
 
 
@@ -96,6 +96,8 @@ class CircusClustering:
         )
 
         wfs = few_wfs[:, :, 0]
+        from sklearn.decomposition import TruncatedSVD
+
         tsvd = TruncatedSVD(params["n_svd"][0])
         tsvd.fit(wfs)
 
diff --git a/src/spikeinterface/sortingcomponents/clustering/merge.py b/src/spikeinterface/sortingcomponents/clustering/merge.py
index ba2792bfd..4a7b722ae 100644
--- a/src/spikeinterface/sortingcomponents/clustering/merge.py
+++ b/src/spikeinterface/sortingcomponents/clustering/merge.py
@@ -1,23 +1,24 @@
 from __future__ import annotations
 
-from pathlib import Path
 from multiprocessing import get_context
-from concurrent.futures import ProcessPoolExecutor
 from threadpoolctl import threadpool_limits
 from tqdm.auto import tqdm
 
-import scipy.spatial
-from sklearn.decomposition import PCA
-from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
 
 import numpy as np
-import networkx as nx
 
 from spikeinterface.core.job_tools import get_poolexecutor, fix_job_kwargs
 
+try:
+    import numba
+    import networkx as nx
+    import scipy.spatial
+    from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
 
-from .isocut5 import isocut5
+    from .isocut5 import isocut5
 
+except:
+    pass
 from .tools import aggregate_sparse_features, FeaturesLoader, compute_template_from_sparse
 
 
diff --git a/src/spikeinterface/sortingcomponents/clustering/split.py b/src/spikeinterface/sortingcomponents/clustering/split.py
index 45f2f4475..5934bdfbb 100644
--- a/src/spikeinterface/sortingcomponents/clustering/split.py
+++ b/src/spikeinterface/sortingcomponents/clustering/split.py
@@ -4,15 +4,18 @@ from multiprocessing import get_context
 from threadpoolctl import threadpool_limits
 from tqdm.auto import tqdm
 
-from sklearn.decomposition import TruncatedSVD, PCA
 
 import numpy as np
 
 from spikeinterface.core.job_tools import get_poolexecutor, fix_job_kwargs
 
 from .tools import aggregate_sparse_features, FeaturesLoader
-from .isocut5 import isocut5
 
+try:
+    import numba
+    from .isocut5 import isocut5
+except:
+    pass  # isocut requires numba
 
 # important all DEBUG and matplotlib are left in the code intentionally
 
@@ -217,6 +220,8 @@ class LocalFeatureClustering:
         flatten_features = aligned_wfs.reshape(aligned_wfs.shape[0], -1)
 
         if flatten_features.shape[1] > n_pca_features:
+            from sklearn.decomposition import PCA
+
             if scale_n_pca_by_depth:
                 # tsvd = TruncatedSVD(n_pca_features * recursion_level)
                 tsvd = PCA(n_pca_features * recursion_level, whiten=True)
diff --git a/src/spikeinterface/sortingcomponents/clustering/tdc.py b/src/spikeinterface/sortingcomponents/clustering/tdc.py
index a6c39c05e..46a9f1d18 100644
--- a/src/spikeinterface/sortingcomponents/clustering/tdc.py
+++ b/src/spikeinterface/sortingcomponents/clustering/tdc.py
@@ -8,15 +8,12 @@ import string
 import shutil
 
 from spikeinterface.core import (
-    get_noise_levels,
-    NumpySorting,
     get_channel_distances,
     Templates,
     compute_sparsity,
     get_global_tmp_folder,
 )
 
-from spikeinterface.sortingcomponents.matching import find_spikes_from_templates
 from spikeinterface.core.node_pipeline import (
     run_node_pipeline,
     ExtractDenseWaveforms,
@@ -34,8 +31,6 @@ from spikeinterface.sortingcomponents.clustering.split import split_clusters
 from spikeinterface.sortingcomponents.clustering.merge import merge_clusters
 from spikeinterface.sortingcomponents.clustering.tools import compute_template_from_sparse
 
-from sklearn.decomposition import TruncatedSVD
-
 
 class TdcClustering:
     """
@@ -85,6 +80,8 @@ class TdcClustering:
         )
 
         wfs = few_wfs[:, :, 0]
+        from sklearn.decomposition import TruncatedSVD
+
         tsvd = TruncatedSVD(params["svd"]["n_components"])
         tsvd.fit(wfs)
 
diff --git a/src/spikeinterface/sortingcomponents/clustering/triage.py b/src/spikeinterface/sortingcomponents/clustering/triage.py
index 68bddeb00..38f333998 100644
--- a/src/spikeinterface/sortingcomponents/clustering/triage.py
+++ b/src/spikeinterface/sortingcomponents/clustering/triage.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 
 import numpy as np
-from scipy.spatial import KDTree
 
 
 def nearest_neighor_triage(
@@ -14,6 +13,8 @@ def nearest_neighor_triage(
     ptp_weighting=True,
 ):
     feats = np.c_[scales[0] * x, scales[1] * y, scales[2] * np.log(maxptps)]
+    from scipy.spatial import KDTree
+
     tree = KDTree(feats)
     dist, _ = tree.query(feats, k=6)
     dist = dist[:, 1:]
diff --git a/src/spikeinterface/sortingcomponents/matching/circus.py b/src/spikeinterface/sortingcomponents/matching/circus.py
index a637b5f58..f78dd2a07 100644
--- a/src/spikeinterface/sortingcomponents/matching/circus.py
+++ b/src/spikeinterface/sortingcomponents/matching/circus.py
@@ -4,11 +4,7 @@ from __future__ import annotations
 
 
 import numpy as np
-import warnings
 
-import scipy.spatial
-
-import scipy
 
 try:
     import sklearn
@@ -23,9 +19,16 @@ from spikeinterface.core import get_noise_levels
 from spikeinterface.sortingcomponents.peak_detection import DetectPeakByChannel
 from spikeinterface.core.template import Templates
 
-(potrs,) = scipy.linalg.get_lapack_funcs(("potrs",), dtype=np.float32)
+try:
+    import scipy.spatial
+
+    import scipy
+
+    (potrs,) = scipy.linalg.get_lapack_funcs(("potrs",), dtype=np.float32)
 
-(nrm2,) = scipy.linalg.get_blas_funcs(("nrm2",), dtype=np.float32)
+    (nrm2,) = scipy.linalg.get_blas_funcs(("nrm2",), dtype=np.float32)
+except:
+    pass
 
 spike_dtype = [
     ("sample_index", "int64"),
diff --git a/src/spikeinterface/sortingcomponents/matching/tdc.py b/src/spikeinterface/sortingcomponents/matching/tdc.py
index 44a7aa00e..e66929e2b 100644
--- a/src/spikeinterface/sortingcomponents/matching/tdc.py
+++ b/src/spikeinterface/sortingcomponents/matching/tdc.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 
 import numpy as np
-import scipy
 from spikeinterface.core import (
     get_noise_levels,
     get_channel_distances,
@@ -129,6 +128,8 @@ class TridesclousPeeler(BaseTemplateMatchingEngine):
         # ~ print(unit_locations)
 
         # distance between units
+        import scipy
+
         unit_distances = scipy.spatial.distance.cdist(unit_locations, unit_locations, metric="euclidean")
 
         # seach for closet units and unitary discriminant vector
@@ -156,6 +157,8 @@ class TridesclousPeeler(BaseTemplateMatchingEngine):
         d["closest_units"] = closest_units
 
         # distance channel from unit
+        import scipy
+
         distances = scipy.spatial.distance.cdist(channel_locations, unit_locations, metric="euclidean")
         near_cluster_mask = distances < d["radius_um"]
 
diff --git a/src/spikeinterface/sortingcomponents/matching/wobble.py b/src/spikeinterface/sortingcomponents/matching/wobble.py
index a8ce32dc4..3b692c3bf 100644
--- a/src/spikeinterface/sortingcomponents/matching/wobble.py
+++ b/src/spikeinterface/sortingcomponents/matching/wobble.py
@@ -1,10 +1,8 @@
 from __future__ import annotations
 
 import numpy as np
-from scipy import signal
 from dataclasses import dataclass
 from typing import List, Tuple, Optional
-import matplotlib.pyplot as plt
 
 from .main import BaseTemplateMatchingEngine
 from spikeinterface.core.template import Templates
@@ -556,6 +554,8 @@ class WobbleMatch(BaseTemplateMatchingEngine):
         Finally, it generates a new spike train from the spike times, and returns it along with additional metrics about
         each spike.
         """
+        from scipy import signal
+
         # Get spike times (indices) using peaks in the objective
         objective_template_max = np.max(objective_normalized, axis=0)
         spike_window = (template_meta.num_samples - 1, objective_normalized.shape[1] - template_meta.num_samples)
@@ -718,6 +718,8 @@ class WobbleMatch(BaseTemplateMatchingEngine):
 
         # Upsample and compute optimal template shift
         window_len_upsampled = template_meta.peak_window_len * params.jitter_factor
+        from scipy import signal
+
         if not params.scale_amplitudes:
             # Perform simple upsampling using scipy.signal.resample
             high_resolution_peaks = signal.resample(objective_peaks, window_len_upsampled, axis=0)
@@ -862,6 +864,8 @@ def upsample_and_jitter(temporal, jitter_factor, num_samples):
     approx_rank = temporal.shape[2]
     num_samples_super_res = num_samples * jitter_factor
     temporal_flipped = np.flip(temporal, axis=1)  # TODO: why do we need to flip the temporal components?
+    from scipy import signal
+
     temporal_jittered = signal.resample(temporal_flipped, num_samples_super_res, axis=1)
 
     original_index = np.arange(0, num_samples_super_res, jitter_factor)  # indices of original data
@@ -962,6 +966,8 @@ def compute_objective(traces, template_data, approx_rank):
     # Filter using overlap-and-add convolution
     spatially_filtered_data = np.matmul(spatial_filters, traces.T[np.newaxis, :, :])
     scaled_filtered_data = spatially_filtered_data * singular_filters
+    from scipy import signal
+
     objective_by_rank = signal.oaconvolve(scaled_filtered_data, temporal_filters, axes=2, mode="full")
     objective += np.sum(objective_by_rank, axis=0)
     return objective
diff --git a/src/spikeinterface/sortingcomponents/motion_estimation.py b/src/spikeinterface/sortingcomponents/motion_estimation.py
index ef3a39bed..9eb541531 100644
--- a/src/spikeinterface/sortingcomponents/motion_estimation.py
+++ b/src/spikeinterface/sortingcomponents/motion_estimation.py
@@ -2,7 +2,6 @@ from __future__ import annotations
 
 import numpy as np
 from tqdm.auto import tqdm, trange
-import scipy.interpolate
 
 try:
     import torch
@@ -319,12 +318,14 @@ class DecentralizedRegistration:
             spatial_bin_edges=spatial_bin_edges,
             weight_with_amplitude=weight_with_amplitude,
         )
+        import scipy.signal
 
         if histogram_depth_smooth_um is not None:
             bins = np.arange(motion_histogram.shape[1]) * bin_um
             bins = bins - np.mean(bins)
             smooth_kernel = np.exp(-(bins**2) / (2 * histogram_depth_smooth_um**2))
             smooth_kernel /= np.sum(smooth_kernel)
+
             motion_histogram = scipy.signal.fftconvolve(motion_histogram, smooth_kernel[None, :], mode="same", axes=1)
 
         if histogram_time_smooth_s is not None:
@@ -958,10 +959,14 @@ def compute_global_displacement(
         One of "gradient"
 
     """
+    import scipy
+    from scipy.optimize import minimize
+    from scipy.sparse import csr_matrix
+    from scipy.sparse.linalg import lsqr
+    from scipy.stats import zscore
+
     if convergence_method == "gradient_descent":
         size = pairwise_displacement.shape[0]
-        from scipy.optimize import minimize
-        from scipy.sparse import csr_matrix
 
         D = pairwise_displacement
         if pairwise_displacement_weight is not None or sparse_mask is not None:
@@ -1004,9 +1009,6 @@ def compute_global_displacement(
         displacement = res.x
 
     elif convergence_method == "lsqr_robust":
-        from scipy.sparse import csr_matrix
-        from scipy.sparse.linalg import lsqr
-        from scipy.stats import zscore
 
         if sparse_mask is not None:
             I, J = np.nonzero(sparse_mask > 0)
@@ -1043,7 +1045,6 @@ def compute_global_displacement(
     elif convergence_method == "lsmr":
         import gc
         from scipy import sparse
-        from scipy.stats import zscore
 
         D = pairwise_displacement
 
@@ -1526,6 +1527,8 @@ def clean_motion_vector(motion, temporal_bins, bin_duration_s, speed_threshold=3
         mask = np.ones(motion_clean.shape[0], dtype="bool")
         for i in range(inds.size // 2):
             mask[inds[i * 2] : inds[i * 2 + 1]] = False
+        import scipy.interpolate
+
         f = scipy.interpolate.interp1d(temporal_bins[mask], one_motion[mask])
         one_motion[~mask] = f(temporal_bins[~mask])
 
diff --git a/src/spikeinterface/sortingcomponents/motion_interpolation.py b/src/spikeinterface/sortingcomponents/motion_interpolation.py
index f71ae0304..5e3733b36 100644
--- a/src/spikeinterface/sortingcomponents/motion_interpolation.py
+++ b/src/spikeinterface/sortingcomponents/motion_interpolation.py
@@ -1,23 +1,13 @@
 from __future__ import annotations
 
 import numpy as np
-import scipy.interpolate
-from tqdm import tqdm
 
-import scipy.spatial
 
 from spikeinterface.core.core_tools import define_function_from_class
 from spikeinterface.preprocessing.basepreprocessor import BasePreprocessor, BasePreprocessorSegment
 from spikeinterface.preprocessing import get_spatial_interpolation_kernel
 
 
-# try:
-#     import numba
-#     HAVE_NUMBA = True
-# except ImportError:
-#     HAVE_NUMBA = False
-
-
 def correct_motion_on_peaks(
     peaks,
     peak_locations,
@@ -52,6 +42,7 @@ def correct_motion_on_peaks(
         Motion-corrected peak locations
     """
     corrected_peak_locations = peak_locations.copy()
+    import scipy.interpolate
 
     spike_times = peaks["sample_index"] / sampling_frequency
     if spatial_bins.shape[0] == 1:
@@ -138,6 +129,8 @@ def interpolate_motion_on_traces(
             channel_motions = motion[bin_ind, 0]
         else:
             # non rigid : interpolation channel motion for this temporal bin
+            import scipy.interpolate
+
             f = scipy.interpolate.interp1d(
                 spatial_bins, motion[bin_ind, :], kind="linear", axis=0, bounds_error=False, fill_value="extrapolate"
             )
@@ -296,6 +289,9 @@ class InterpolateMotionRecording(BasePreprocessor):
                     best_motions = operator(motion[:, 0])
                 else:
                     # non rigid : interpolation channel motion for this temporal bin
+                    import scipy.spatial
+                    import scipy.interpolate
+
                     f = scipy.interpolate.interp1d(
                         spatial_bins,
                         operator(motion[:, :], axis=0),
diff --git a/src/spikeinterface/sortingcomponents/peak_selection.py b/src/spikeinterface/sortingcomponents/peak_selection.py
index eb810efea..397f59dbd 100644
--- a/src/spikeinterface/sortingcomponents/peak_selection.py
+++ b/src/spikeinterface/sortingcomponents/peak_selection.py
@@ -4,7 +4,6 @@ from __future__ import annotations
 
 
 import numpy as np
-from sklearn.preprocessing import QuantileTransformer
 
 
 def select_peaks(peaks, method="uniform", seed=None, return_indices=False, **method_kwargs):
@@ -83,6 +82,7 @@ def select_peak_indices(peaks, method, seed, **method_kwargs):
 
     :py:func:`spikeinterface.sortingcomponents.peak_selection.select_peaks` for detailed documentation.
     """
+    from sklearn.preprocessing import QuantileTransformer
 
     selected_indices = []
 
diff --git a/src/spikeinterface/sortingcomponents/waveforms/savgol_denoiser.py b/src/spikeinterface/sortingcomponents/waveforms/savgol_denoiser.py
index e03d52fb3..2a54fe231 100644
--- a/src/spikeinterface/sortingcomponents/waveforms/savgol_denoiser.py
+++ b/src/spikeinterface/sortingcomponents/waveforms/savgol_denoiser.py
@@ -1,9 +1,7 @@
 from __future__ import annotations
 
-from pathlib import Path
-import json
+
 from typing import List, Optional
-import scipy.signal
 
 from spikeinterface.core import BaseRecording
 from spikeinterface.core.node_pipeline import PipelineNode, WaveformsNode, find_parent_of_type
@@ -56,6 +54,8 @@ class SavGolDenoiser(WaveformsNode):
 
     def compute(self, traces, peaks, waveforms):
         # Denoise
+        import scipy.signal
+
         denoised_waveforms = scipy.signal.savgol_filter(waveforms, self.window_length, self.order, axis=1)
 
         return denoised_waveforms
diff --git a/src/spikeinterface/sortingcomponents/waveforms/temporal_pca.py b/src/spikeinterface/sortingcomponents/waveforms/temporal_pca.py
index 3a16ef184..4b3cc8741 100644
--- a/src/spikeinterface/sortingcomponents/waveforms/temporal_pca.py
+++ b/src/spikeinterface/sortingcomponents/waveforms/temporal_pca.py
@@ -6,14 +6,11 @@ from pathlib import Path
 from typing import List
 
 import numpy as np
-from sklearn.decomposition import IncrementalPCA
 
 from spikeinterface.core.node_pipeline import PipelineNode, WaveformsNode, find_parent_of_type
 from spikeinterface.sortingcomponents.peak_detection import detect_peaks
 from spikeinterface.sortingcomponents.peak_selection import select_peaks
-from spikeinterface.postprocessing import compute_principal_components
 from spikeinterface.core import BaseRecording
-from spikeinterface.core.sparsity import ChannelSparsity
 from spikeinterface import NumpySorting, create_sorting_analyzer
 from spikeinterface.core.job_tools import _shared_job_kwargs_doc
 from .waveform_utils import to_temporal_representation, from_temporal_representation
@@ -96,7 +93,7 @@ class TemporalPCBaseNode(WaveformsNode):
         ms_after: float = 1.0,
         whiten: bool = True,
         radius_um: float = None,
-    ) -> IncrementalPCA:
+    ) -> "IncrementalPCA":
         """
         Train a pca model using the data in the recording object and the parameters provided.
         Note that this model returns the pca model from scikit-learn but the model is also saved in the path provided
diff --git a/src/spikeinterface/sortingcomponents/waveforms/waveform_thresholder.py b/src/spikeinterface/sortingcomponents/waveforms/waveform_thresholder.py
index 8dd925ff1..76d72f3b0 100644
--- a/src/spikeinterface/sortingcomponents/waveforms/waveform_thresholder.py
+++ b/src/spikeinterface/sortingcomponents/waveforms/waveform_thresholder.py
@@ -1,9 +1,6 @@
 from __future__ import annotations
 
-from pathlib import Path
-import json
 from typing import List, Optional
-import scipy.signal
 import numpy as np
 import operator
 from typing import Literal


Test patch:
diff --git a/.github/workflows/core-test.yml b/.github/workflows/core-test.yml
index 1609b8619..a513d48f3 100644
--- a/.github/workflows/core-test.yml
+++ b/.github/workflows/core-test.yml
@@ -22,7 +22,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
         with:
-          python-version: '3.10'
+          python-version: '3.11'
       - name: Install dependencies
         run: |
           git config --global user.email "CI@example.com"
@@ -31,7 +31,7 @@ jobs:
           pip install -e .[test_core]
       - name: Test core with pytest
         run: |
-          pytest -vv -ra --durations=0 --durations-min=0.001 src/spikeinterface/core | tee report.txt; test ${PIPESTATUS[0]} -eq 0 || exit 1
+          pytest -m "core" -vv -ra --durations=0 --durations-min=0.001 | tee report.txt; test $? -eq 0 || exit 1
         shell: bash  # Necessary for pipeline to work on windows
       - name: Build test summary
         run: |
diff --git a/conftest.py b/conftest.py
index e1ec0edac..040818320 100644
--- a/conftest.py
+++ b/conftest.py
@@ -25,31 +25,27 @@ def pytest_sessionstart(session):
     for mark_name in mark_names:
         (pytest.global_test_folder / mark_name).mkdir()
 
-
 def pytest_collection_modifyitems(config, items):
     """
     This function marks (in the pytest sense) the tests according to their name and file_path location
     Marking them in turn allows the tests to be run by using the pytest -m marker_name option.
     """
 
-
-    # python 3.4/3.5 compat: rootdir = pathlib.Path(str(config.rootdir))
     rootdir = Path(config.rootdir)
-
+    modules_location = rootdir / "src" / "spikeinterface"
     for item in items:
-        rel_path = Path(item.fspath).relative_to(rootdir)
-        if "sorters" in str(rel_path):
-            if "/internal/" in str(rel_path):
+        rel_path = Path(item.fspath).relative_to(modules_location)
+        module = rel_path.parts[0]
+        if module == "sorters":
+            if "internal" in rel_path.parts:
                 item.add_marker("sorters_internal")
-            elif "/external/" in str(rel_path):
+            elif "external" in rel_path.parts:
                 item.add_marker("sorters_external")
             else:
                 item.add_marker("sorters")
         else:
-            for mark_name in mark_names:
-                if f"/{mark_name}/" in str(rel_path):
-                    mark = getattr(pytest.mark, mark_name)
-                    item.add_marker(mark)
+            item.add_marker(module)
+
 
 
 def pytest_sessionfinish(session, exitstatus):
diff --git a/src/spikeinterface/comparison/tests/test_groundtruthcomparison.py b/src/spikeinterface/comparison/tests/test_groundtruthcomparison.py
index 75bbfb540..b58a03eff 100644
--- a/src/spikeinterface/comparison/tests/test_groundtruthcomparison.py
+++ b/src/spikeinterface/comparison/tests/test_groundtruthcomparison.py
@@ -1,7 +1,6 @@
 import numpy as np
 from numpy.testing import assert_array_equal
 
-import pandas as pd
 
 from spikeinterface.extractors import NumpySorting
 from spikeinterface.comparison import compare_sorter_to_ground_truth
@@ -57,6 +56,8 @@ def test_compare_sorter_to_ground_truth():
             "pooled_with_average",
         ]
         for method in methods:
+            import pandas as pd
+
             perf_df = sc.get_performance(method=method, output="pandas")
             assert isinstance(perf_df, (pd.Series, pd.DataFrame))
             perf_dict = sc.get_performance(method=method, output="dict")
diff --git a/src/spikeinterface/extractors/tests/test_iblextractors.py b/src/spikeinterface/extractors/tests/test_iblextractors.py
index 6995b7a11..c7c2dfaca 100644
--- a/src/spikeinterface/extractors/tests/test_iblextractors.py
+++ b/src/spikeinterface/extractors/tests/test_iblextractors.py
@@ -4,7 +4,6 @@ from unittest import TestCase
 import numpy as np
 from numpy.testing import assert_array_equal
 import pytest
-import requests
 
 from spikeinterface.extractors import read_ibl_recording, read_ibl_sorting, IblRecordingExtractor
 
@@ -16,6 +15,7 @@ PID = "80f6ffdd-f692-450f-ab19-cd6d45bfd73e"
 class TestDefaultIblRecordingExtractorApBand(TestCase):
     @classmethod
     def setUpClass(cls):
+        import requests
         from one.api import ONE
 
         cls.eid = EID
diff --git a/src/spikeinterface/extractors/tests/test_nwbextractors.py b/src/spikeinterface/extractors/tests/test_nwbextractors.py
index c10222dc0..b698f7dfe 100644
--- a/src/spikeinterface/extractors/tests/test_nwbextractors.py
+++ b/src/spikeinterface/extractors/tests/test_nwbextractors.py
@@ -1,17 +1,10 @@
 import unittest
-import unittest
 from pathlib import Path
-from tempfile import mkdtemp
-from datetime import datetime
+
 
 import pytest
 import numpy as np
-from pynwb import NWBHDF5IO
-from hdmf_zarr import NWBZarrIO
-from pynwb.ecephys import ElectricalSeries, LFP, FilteredEphys
-from pynwb.testing.mock.file import mock_NWBFile
-from pynwb.testing.mock.device import mock_Device
-from pynwb.testing.mock.ecephys import mock_ElectricalSeries, mock_ElectrodeGroup, mock_electrodes
+
 from spikeinterface.extractors import NwbRecordingExtractor, NwbSortingExtractor
 
 from spikeinterface.extractors.tests.common_tests import RecordingCommonTestSuite, SortingCommonTestSuite
@@ -30,10 +23,12 @@ class NwbSortingTest(SortingCommonTestSuite, unittest.TestCase):
     entities = []
 
 
-from pynwb.testing.mock.ecephys import mock_ElectrodeGroup
-
-
 def nwbfile_with_ecephys_content():
+    from pynwb.ecephys import ElectricalSeries, LFP, FilteredEphys
+    from pynwb.testing.mock.file import mock_NWBFile
+    from pynwb.testing.mock.device import mock_Device
+    from pynwb.testing.mock.ecephys import mock_ElectricalSeries, mock_ElectrodeGroup
+
     to_micro_volts = 1e6
 
     nwbfile = mock_NWBFile()
@@ -160,6 +155,9 @@ def nwbfile_with_ecephys_content():
 
 
 def _generate_nwbfile(backend, file_path):
+    from pynwb import NWBHDF5IO
+    from hdmf_zarr import NWBZarrIO
+
     nwbfile = nwbfile_with_ecephys_content()
     if backend == "hdf5":
         io_class = NWBHDF5IO
@@ -367,6 +365,9 @@ def test_failure_with_wrong_electrical_series_path(generate_nwbfile, use_pynwb):
 
 @pytest.mark.parametrize("use_pynwb", [True, False])
 def test_sorting_extraction_of_ragged_arrays(tmp_path, use_pynwb):
+    from pynwb import NWBHDF5IO
+    from pynwb.testing.mock.file import mock_NWBFile
+
     nwbfile = mock_NWBFile()
 
     # Add the spikes
@@ -433,6 +434,10 @@ def test_sorting_extraction_of_ragged_arrays(tmp_path, use_pynwb):
 
 @pytest.mark.parametrize("use_pynwb", [True, False])
 def test_sorting_extraction_start_time(tmp_path, use_pynwb):
+
+    from pynwb import NWBHDF5IO
+    from pynwb.testing.mock.file import mock_NWBFile
+
     nwbfile = mock_NWBFile()
 
     # Add the spikes
@@ -477,6 +482,12 @@ def test_sorting_extraction_start_time(tmp_path, use_pynwb):
 
 @pytest.mark.parametrize("use_pynwb", [True, False])
 def test_sorting_extraction_start_time_from_series(tmp_path, use_pynwb):
+    from pynwb import NWBHDF5IO
+    from pynwb.testing.mock.file import mock_NWBFile
+    from pynwb.ecephys import ElectricalSeries, LFP, FilteredEphys
+
+    from pynwb.testing.mock.ecephys import mock_electrodes
+
     nwbfile = mock_NWBFile()
     electrical_series_name = "ElectricalSeries"
     t_start = 10.0
@@ -530,6 +541,8 @@ def test_sorting_extraction_start_time_from_series(tmp_path, use_pynwb):
 @pytest.mark.parametrize("use_pynwb", [True, False])
 def test_multiple_unit_tables(tmp_path, use_pynwb):
     from pynwb.misc import Units
+    from pynwb import NWBHDF5IO
+    from pynwb.testing.mock.file import mock_NWBFile
 
     nwbfile = mock_NWBFile()
 
diff --git a/src/spikeinterface/extractors/tests/test_nwbextractors_streaming.py b/src/spikeinterface/extractors/tests/test_nwbextractors_streaming.py
index 2732e5077..b3c5b9c93 100644
--- a/src/spikeinterface/extractors/tests/test_nwbextractors_streaming.py
+++ b/src/spikeinterface/extractors/tests/test_nwbextractors_streaming.py
@@ -1,10 +1,8 @@
 from pathlib import Path
 import pickle
-from tabnanny import check
 
 import pytest
 import numpy as np
-import h5py
 
 from spikeinterface import load_extractor
 from spikeinterface.core.testing import check_recordings_equal
@@ -12,43 +10,6 @@ from spikeinterface.core.testing import check_recordings_equal, check_sortings_e
 from spikeinterface.extractors import NwbRecordingExtractor, NwbSortingExtractor
 
 
-@pytest.mark.streaming_extractors
-@pytest.mark.skipif("ros3" not in h5py.registered_drivers(), reason="ROS3 driver not installed")
-def test_recording_s3_nwb_ros3(tmp_path):
-    file_path = (
-        "https://dandi-api-staging-dandisets.s3.amazonaws.com/blobs/5f4/b7a/5f4b7a1f-7b95-4ad8-9579-4df6025371cc"
-    )
-    rec = NwbRecordingExtractor(file_path, stream_mode="ros3")
-
-    start_frame = 0
-    end_frame = 300
-    num_frames = end_frame - start_frame
-
-    num_seg = rec.get_num_segments()
-    num_chans = rec.get_num_channels()
-    dtype = rec.get_dtype()
-
-    for segment_index in range(num_seg):
-        num_samples = rec.get_num_samples(segment_index=segment_index)
-
-        full_traces = rec.get_traces(segment_index=segment_index, start_frame=start_frame, end_frame=end_frame)
-        assert full_traces.shape == (num_frames, num_chans)
-        assert full_traces.dtype == dtype
-
-    if rec.has_scaleable_traces():
-        trace_scaled = rec.get_traces(segment_index=segment_index, return_scaled=True, end_frame=2)
-        assert trace_scaled.dtype == "float32"
-
-    tmp_file = tmp_path / "test_ros3_recording.pkl"
-    with open(tmp_file, "wb") as f:
-        pickle.dump(rec, f)
-
-    with open(tmp_file, "rb") as f:
-        reloaded_recording = pickle.load(f)
-
-    check_recordings_equal(rec, reloaded_recording)
-
-
 @pytest.mark.streaming_extractors
 @pytest.mark.parametrize("cache", [True, False])  # Test with and without cache
 def test_recording_s3_nwb_fsspec(tmp_path, cache):
@@ -154,37 +115,6 @@ def test_recording_s3_nwb_remfile_file_like(tmp_path):
     check_recordings_equal(rec, rec2)
 
 
-@pytest.mark.streaming_extractors
-@pytest.mark.skipif("ros3" not in h5py.registered_drivers(), reason="ROS3 driver not installed")
-def test_sorting_s3_nwb_ros3(tmp_path):
-    file_path = "https://dandiarchive.s3.amazonaws.com/blobs/84b/aa4/84baa446-cf19-43e8-bdeb-fc804852279b"
-    # we provide the 'sampling_frequency' because the NWB file does not the electrical series
-    sort = NwbSortingExtractor(file_path, sampling_frequency=30000, stream_mode="ros3", t_start=0)
-
-    start_frame = 0
-    end_frame = 300
-    num_frames = end_frame - start_frame
-
-    num_seg = sort.get_num_segments()
-    num_units = len(sort.unit_ids)
-
-    for segment_index in range(num_seg):
-        for unit in sort.unit_ids:
-            spike_train = sort.get_unit_spike_train(unit_id=unit, segment_index=segment_index)
-            assert len(spike_train) > 0
-            assert spike_train.dtype == "int64"
-            assert np.all(spike_train >= 0)
-
-    tmp_file = tmp_path / "test_ros3_sorting.pkl"
-    with open(tmp_file, "wb") as f:
-        pickle.dump(sort, f)
-
-    with open(tmp_file, "rb") as f:
-        reloaded_sorting = pickle.load(f)
-
-    check_sortings_equal(reloaded_sorting, sort)
-
-
 @pytest.mark.streaming_extractors
 @pytest.mark.parametrize("cache", [True, False])  # Test with and without cache
 def test_sorting_s3_nwb_fsspec(tmp_path, cache):
diff --git a/src/spikeinterface/postprocessing/tests/common_extension_tests.py b/src/spikeinterface/postprocessing/tests/common_extension_tests.py
index bf462a946..605997f5f 100644
--- a/src/spikeinterface/postprocessing/tests/common_extension_tests.py
+++ b/src/spikeinterface/postprocessing/tests/common_extension_tests.py
@@ -2,9 +2,7 @@ from __future__ import annotations
 
 import pytest
 import numpy as np
-import pandas as pd
 import shutil
-import platform
 from pathlib import Path
 
 from spikeinterface.core import generate_ground_truth_recording
diff --git a/src/spikeinterface/preprocessing/tests/test_detect_bad_channels.py b/src/spikeinterface/preprocessing/tests/test_detect_bad_channels.py
index 4071bfe0e..4622be144 100644
--- a/src/spikeinterface/preprocessing/tests/test_detect_bad_channels.py
+++ b/src/spikeinterface/preprocessing/tests/test_detect_bad_channels.py
@@ -1,6 +1,5 @@
 import pytest
 import numpy as np
-import scipy.stats
 
 from spikeinterface import NumpyRecording, get_random_data_chunks
 from probeinterface import generate_linear_probe
@@ -167,6 +166,8 @@ def test_detect_bad_channels_ibl(num_channels):
         channel_flags_ibl[:, i] = channel_flags
 
     # Take the mode of the chunk estimates as final result. Convert to binary good / bad channel output.
+    import scipy.stats
+
     bad_channel_labels_ibl, _ = scipy.stats.mode(channel_flags_ibl, axis=1, keepdims=False)
 
     # Compare
diff --git a/src/spikeinterface/preprocessing/tests/test_filter.py b/src/spikeinterface/preprocessing/tests/test_filter.py
index 95e5a097f..fc28463df 100644
--- a/src/spikeinterface/preprocessing/tests/test_filter.py
+++ b/src/spikeinterface/preprocessing/tests/test_filter.py
@@ -1,14 +1,11 @@
 import pytest
 from pathlib import Path
-import shutil
 
 import numpy as np
-from numpy.testing import assert_array_almost_equal
 from spikeinterface.core import generate_recording
 from spikeinterface import NumpyRecording, set_global_tmp_folder
 
 from spikeinterface.preprocessing import filter, bandpass_filter, notch_filter
-from scipy.signal import iirfilter
 
 if hasattr(pytest, "global_test_folder"):
     cache_folder = pytest.global_test_folder / "preprocessing"
@@ -41,6 +38,8 @@ def test_filter():
     rec4 = notch_filter(rec, freq=3000, q=30, margin_ms=5.0)
 
     # filter from coefficients
+    from scipy.signal import iirfilter
+
     coeff = iirfilter(8, [0.02, 0.4], rs=30, btype="band", analog=False, ftype="cheby2", output="sos")
     rec5 = filter(rec, coeff=coeff, filter_mode="sos")
 
diff --git a/src/spikeinterface/preprocessing/tests/test_filter_gaussian.py b/src/spikeinterface/preprocessing/tests/test_filter_gaussian.py
index 7a53e1f06..10fdc5e8d 100644
--- a/src/spikeinterface/preprocessing/tests/test_filter_gaussian.py
+++ b/src/spikeinterface/preprocessing/tests/test_filter_gaussian.py
@@ -6,8 +6,6 @@ from spikeinterface.core.testing import check_recordings_equal
 from spikeinterface.core.generate import generate_recording
 from spikeinterface.preprocessing import gaussian_filter
 from numpy.testing import assert_allclose
-import scipy
-import matplotlib.pyplot as plt
 from spikeinterface.core import NumpyRecording
 
 
@@ -71,10 +69,14 @@ def test_bandpower(freq_min, freq_max, debug=False):
     # Welch power density
     trace = rec.get_traces()[:, 0]
     trace_filt = rec_filt.get_traces(0)[:, 0]
+    import scipy
+
     f, Pxx = scipy.signal.welch(trace, fs=fs)
     _, Pxx_filt = scipy.signal.welch(trace_filt, fs=fs)
 
     if debug:
+        import matplotlib.pyplot as plt
+
         plt.plot(f, Pxx, label="Welch original")
         plt.plot(f, Pxx_filt, label="Welch gaussian filter")
         plt.plot(f, Pxx - Pxx_filt, label="difference")
diff --git a/src/spikeinterface/preprocessing/tests/test_motion.py b/src/spikeinterface/preprocessing/tests/test_motion.py
index ea4611b37..a7f3fe1ef 100644
--- a/src/spikeinterface/preprocessing/tests/test_motion.py
+++ b/src/spikeinterface/preprocessing/tests/test_motion.py
@@ -18,6 +18,7 @@ print(cache_folder.absolute())
 
 
 def test_estimate_and_correct_motion():
+
     rec = generate_recording(durations=[30.0], num_channels=12)
 
     print(rec)
diff --git a/src/spikeinterface/preprocessing/tests/test_phase_shift.py b/src/spikeinterface/preprocessing/tests/test_phase_shift.py
index 11f0a9c76..6bf7d50dd 100644
--- a/src/spikeinterface/preprocessing/tests/test_phase_shift.py
+++ b/src/spikeinterface/preprocessing/tests/test_phase_shift.py
@@ -1,17 +1,7 @@
-import pytest
-from pathlib import Path
-import shutil
-
 import numpy as np
-from numpy.testing import assert_array_almost_equal
 from spikeinterface import NumpyRecording
-from spikeinterface.core import generate_recording
-from spikeinterface import NumpyRecording, set_global_tmp_folder
 
 from spikeinterface.preprocessing import phase_shift
-from spikeinterface.preprocessing.phase_shift import apply_fshift
-
-import scipy.fft
 
 
 def create_shifted_channel():
diff --git a/src/spikeinterface/preprocessing/tests/test_resample.py b/src/spikeinterface/preprocessing/tests/test_resample.py
index d17617487..2fa76ffe0 100644
--- a/src/spikeinterface/preprocessing/tests/test_resample.py
+++ b/src/spikeinterface/preprocessing/tests/test_resample.py
@@ -1,13 +1,12 @@
 import pytest
 from pathlib import Path
 
-from spikeinterface import NumpyRecording, set_global_tmp_folder
-from spikeinterface.core import generate_recording
+
 from spikeinterface.preprocessing import resample
+from spikeinterface.core import NumpyRecording
 
 
 import numpy as np
-from scipy.fft import fft, fftfreq
 
 if hasattr(pytest, "global_test_folder"):
     cache_folder = pytest.global_test_folder / "preprocessing"
@@ -72,6 +71,8 @@ def create_sinusoidal_traces(sampling_frequency=3e4, duration=30, freqs_n=10, ma
 
 
 def get_fft(traces, sampling_frequency):
+    from scipy.fft import fft, fftfreq
+
     # Return the power spectrum of the positive fft
     N = len(traces)
     yf = fft(traces)
diff --git a/src/spikeinterface/qualitymetrics/tests/test_metrics_functions.py b/src/spikeinterface/qualitymetrics/tests/test_metrics_functions.py
index 88908d05c..5a7d43cba 100644
--- a/src/spikeinterface/qualitymetrics/tests/test_metrics_functions.py
+++ b/src/spikeinterface/qualitymetrics/tests/test_metrics_functions.py
@@ -1,5 +1,4 @@
 import pytest
-import shutil
 from pathlib import Path
 import numpy as np
 from spikeinterface.core import (
@@ -13,7 +12,6 @@ from spikeinterface.core import (
 
 from spikeinterface.qualitymetrics.utils import create_ground_truth_pc_distributions
 
-from spikeinterface.qualitymetrics import calculate_pc_metrics
 
 from spikeinterface.qualitymetrics import (
     mahalanobis_metrics,
diff --git a/src/spikeinterface/qualitymetrics/tests/test_pca_metrics.py b/src/spikeinterface/qualitymetrics/tests/test_pca_metrics.py
index e5196708e..415166a54 100644
--- a/src/spikeinterface/qualitymetrics/tests/test_pca_metrics.py
+++ b/src/spikeinterface/qualitymetrics/tests/test_pca_metrics.py
@@ -1,17 +1,11 @@
 import pytest
-import shutil
 from pathlib import Path
 import numpy as np
-import pandas as pd
 from spikeinterface.core import (
-    NumpySorting,
-    synthetize_spike_train_bad_isi,
-    add_synchrony_to_sorting,
     generate_ground_truth_recording,
     create_sorting_analyzer,
 )
 
-from spikeinterface.qualitymetrics.utils import create_ground_truth_pc_distributions
 
 from spikeinterface.qualitymetrics import (
     calculate_pc_metrics,
@@ -54,6 +48,8 @@ def sorting_analyzer_simple():
 
 
 def test_calculate_pc_metrics(sorting_analyzer_simple):
+    import pandas as pd
+
     sorting_analyzer = sorting_analyzer_simple
     res1 = calculate_pc_metrics(sorting_analyzer, n_jobs=1, progress_bar=True)
     res1 = pd.DataFrame(res1)
diff --git a/src/spikeinterface/qualitymetrics/tests/test_quality_metric_calculator.py b/src/spikeinterface/qualitymetrics/tests/test_quality_metric_calculator.py
index f26e80068..3ae879e3f 100644
--- a/src/spikeinterface/qualitymetrics/tests/test_quality_metric_calculator.py
+++ b/src/spikeinterface/qualitymetrics/tests/test_quality_metric_calculator.py
@@ -1,11 +1,7 @@
-import unittest
 import pytest
-import warnings
 from pathlib import Path
 import numpy as np
-import shutil
 
-from pandas import isnull
 
 from spikeinterface.core import (
     generate_ground_truth_recording,
@@ -161,6 +157,8 @@ def test_empty_units(sorting_analyzer_simple):
     )
 
     for empty_unit_id in sorting_empty.get_empty_unit_ids():
+        from pandas import isnull
+
         assert np.all(isnull(metrics_empty.loc[empty_unit_id].values))
 
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_clustering.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_clustering.py
index d9d07370c..bb9d3b4ed 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_clustering.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_clustering.py
@@ -1,7 +1,4 @@
 import pytest
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
 import numpy as np
 
 import shutil
@@ -75,6 +72,8 @@ def test_benchmark_clustering():
     study.plot_run_times()
     study.plot_metrics_vs_snr("cosine")
     study.homogeneity_score(ignore_noise=False)
+    import matplotlib.pyplot as plt
+
     plt.show()
 
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_matching.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_matching.py
index 1aae51c9e..4837160dc 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_matching.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_matching.py
@@ -2,10 +2,6 @@ import pytest
 
 import shutil
 
-import spikeinterface.full as si
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
 
 from spikeinterface.core import (
     get_noise_levels,
@@ -71,6 +67,8 @@ def test_benchmark_matching():
     study.plot_performances_vs_snr()
     study.plot_agreements()
     study.plot_comparison_matching()
+    import matplotlib.pyplot as plt
+
     plt.show()
 
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_estimation.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_estimation.py
index 7f24c07d3..dec0e612f 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_estimation.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_estimation.py
@@ -1,9 +1,5 @@
 import pytest
 
-import spikeinterface.full as si
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
 
 import shutil
 
@@ -72,6 +68,7 @@ def test_benchmark_motion_estimaton():
     study.plot_true_drift()
     study.plot_errors()
     study.plot_summary_errors()
+    import matplotlib.pyplot as plt
 
     plt.show()
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_interpolation.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_interpolation.py
index 06a3fa914..bf4522df9 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_interpolation.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_motion_interpolation.py
@@ -1,9 +1,5 @@
 import pytest
 
-import spikeinterface.full as si
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
 
 import numpy as np
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_detection.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_detection.py
index fd0957519..e37e8eca1 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_detection.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_detection.py
@@ -2,12 +2,6 @@ import pytest
 
 import shutil
 
-import spikeinterface.full as si
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
-import numpy as np
-
 
 from spikeinterface.sortingcomponents.benchmark.tests.common_benchmark_testing import make_dataset, cache_folder
 from spikeinterface.sortingcomponents.benchmark.benchmark_peak_detection import PeakDetectionStudy
@@ -68,6 +62,7 @@ def test_benchmark_peak_detection():
     study.plot_performances_vs_snr()
     study.plot_template_similarities()
     study.plot_run_times()
+    import matplotlib.pyplot as plt
 
     plt.show()
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_localization.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_localization.py
index fb3ecc61a..8627034ce 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_localization.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_localization.py
@@ -2,8 +2,6 @@ import pytest
 
 import shutil
 
-import matplotlib.pyplot as plt
-
 
 from spikeinterface.sortingcomponents.benchmark.tests.common_benchmark_testing import make_dataset, cache_folder
 
@@ -51,6 +49,8 @@ def test_benchmark_peak_localization():
     study.plot_comparison_positions()
     study.plot_run_times()
 
+    import matplotlib.pyplot as plt
+
     plt.show()
 
 
@@ -91,6 +91,8 @@ def test_benchmark_unit_localization():
     study.plot_template_errors()
     study.plot_run_times()
 
+    import matplotlib.pyplot as plt
+
     plt.show()
 
 
diff --git a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_selection.py b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_selection.py
index f90a0c56d..1e65dfe6c 100644
--- a/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_selection.py
+++ b/src/spikeinterface/sortingcomponents/benchmark/tests/test_benchmark_peak_selection.py
@@ -1,12 +1,5 @@
 import pytest
 
-import spikeinterface.full as si
-import pandas as pd
-from pathlib import Path
-import matplotlib.pyplot as plt
-
-from spikeinterface.sortingcomponents.benchmark.tests.common_benchmark_testing import make_dataset, cache_folder
-
 
 @pytest.mark.skip()
 def test_benchmark_peak_selection():
diff --git a/src/spikeinterface/widgets/tests/test_widgets.py b/src/spikeinterface/widgets/tests/test_widgets.py
index 5366fb864..156d1d92e 100644
--- a/src/spikeinterface/widgets/tests/test_widgets.py
+++ b/src/spikeinterface/widgets/tests/test_widgets.py
@@ -4,11 +4,12 @@ import os
 from pathlib import Path
 
 if __name__ != "__main__":
-    import matplotlib
+    try:
+        import matplotlib
 
-    matplotlib.use("Agg")
-
-import matplotlib.pyplot as plt
+        matplotlib.use("Agg")
+    except:
+        pass
 
 
 from spikeinterface import (
@@ -578,12 +579,15 @@ class TestWidgets(unittest.TestCase):
         for backend in possible_backends_by_sorter:
             sw.plot_multicomparison_agreement_by_sorter(mcmp)
             if backend == "matplotlib":
+                import matplotlib.pyplot as plt
+
                 _, axes = plt.subplots(len(mcmp.object_list), 1)
                 sw.plot_multicomparison_agreement_by_sorter(mcmp, axes=axes)
 
 
 if __name__ == "__main__":
     # unittest.main()
+    import matplotlib.pyplot as plt
 
     TestWidgets.setUpClass()
     mytest = TestWidgets()