Repository: SpikeInterface/spikeinterface

Issue:
PCA computation : pickle run out of inputs
Hello,
I used to get some 'shared memory issues" (#1704 ) to extract my waveforms. Since the PR #1704  has been merged, I updated my Spikeinterface. Now the waveform extraction goes well, but I have big issue with the PCAs computation. I compute the PCA with default parameters: `pca = si.compute_principal_components(we, n_jobs=job_kwargs['n_jobs'])`

Python crashes only when I compute PCA from several sortings in parallel. The associated error is  the following:
```
Traceback (most recent call last):
  File "/home/users/j/juventin/yggdrasil_python_envs/py310/lib/python3.10/site-packages/joblib/externals/loky/process_executor.py", line 436, in _process_worker
    r = call_item()
  File "/home/users/j/juventin/yggdrasil_python_envs/py310/lib/python3.10/site-packages/joblib/externals/loky/process_executor.py", line 288, in __call__
    return self.fn(*self.args, **self.kwargs)
  File "/home/users/j/juventin/yggdrasil_python_envs/py310/lib/python3.10/site-packages/joblib/_parallel_backends.py", line 595, in __call__
    return self.func(*args, **kwargs)
  File "/home/users/j/juventin/yggdrasil_python_envs/py310/lib/python3.10/site-packages/joblib/parallel.py", line 263, in __call__
    return [func(*args, **kwargs)
  File "/home/users/j/juventin/yggdrasil_python_envs/py310/lib/python3.10/site-packages/joblib/parallel.py", line 263, in <listcomp>
    return [func(*args, **kwargs)
  File "/home/users/j/juventin/spike_sorters/spikeinterface/src/spikeinterface/postprocessing/principal_component.py", line 727, in partial_fit_one_channel
    pca_model = pickle.load(fid)
EOFError: Ran out of input
```
When I compute PCA for one single sorting, it runs smoothly.

I noticed that a "tmp" folder was created during PCA computation. Since the name of the "tmp" folder is not unique,  I suspect that when PCAs are computed from multiple sortings at the same time, the "tmp" folder is overwritten, leading to the error. . Does this sound possible ?

Cheers 
Maxime

Discussion:

Patch:
diff --git a/examples/modules_gallery/core/plot_1_recording_extractor.py b/examples/modules_gallery/core/plot_1_recording_extractor.py
index f20bf6497..a7c2403c4 100644
--- a/examples/modules_gallery/core/plot_1_recording_extractor.py
+++ b/examples/modules_gallery/core/plot_1_recording_extractor.py
@@ -79,7 +79,7 @@ se.BinaryRecordingExtractor.write_recording(recording, file_paths)
 # Note that this new recording is now "on disk" and not "in memory" as the Numpy recording.
 # This means that the loading is "lazy" and the data are not loaded in memory.
 
-recording2 = se.BinaryRecordingExtractor(file_paths=file_paths, sampling_frequency=sampling_frequency, num_channels=num_channels, dtype=traces0.dtype)
+recording2 = se.BinaryRecordingExtractor(file_paths, sampling_frequency, num_channels, traces0.dtype)
 print(recording2)
 
 ##############################################################################
diff --git a/src/spikeinterface/core/baserecording.py b/src/spikeinterface/core/baserecording.py
index e3baf12e8..d4d0beb5f 100644
--- a/src/spikeinterface/core/baserecording.py
+++ b/src/spikeinterface/core/baserecording.py
@@ -450,7 +450,7 @@ class BaseRecording(BaseRecordingSnippets):
             binary_rec = BinaryRecordingExtractor(
                 file_paths=file_paths,
                 sampling_frequency=self.get_sampling_frequency(),
-                num_channels=self.get_num_channels(),
+                num_chan=self.get_num_channels(),
                 dtype=dtype,
                 t_starts=t_starts,
                 channel_ids=self.get_channel_ids(),
diff --git a/src/spikeinterface/core/binaryrecordingextractor.py b/src/spikeinterface/core/binaryrecordingextractor.py
index f56cc2866..c04a1c6ec 100644
--- a/src/spikeinterface/core/binaryrecordingextractor.py
+++ b/src/spikeinterface/core/binaryrecordingextractor.py
@@ -1,12 +1,13 @@
 from typing import List, Union
 import mmap
-import warnings
+
+import shutil
 from pathlib import Path
 
 import numpy as np
 
 from .baserecording import BaseRecording, BaseRecordingSegment
-from .core_tools import write_binary_recording, define_function_from_class
+from .core_tools import read_binary_recording, write_binary_recording, define_function_from_class
 from .job_tools import _shared_job_kwargs_doc
 
 
@@ -20,9 +21,7 @@ class BinaryRecordingExtractor(BaseRecording):
         Path to the binary file
     sampling_frequency: float
         The sampling frequency
-    num_channels: int
-        Number of channels
-    num_chan: int [deprecated, use num_channels instead, will be removed as early as v0.100.0]
+    num_chan: int
         Number of channels
     dtype: str or dtype
         The dtype of the binary file
@@ -41,10 +40,6 @@ class BinaryRecordingExtractor(BaseRecording):
     is_filtered: bool or None
         If True, the recording is assumed to be filtered. If None, is_filtered is not set.
 
-    Notes
-    -----
-    When both num_channels and num_chan are provided, `num_channels` is used and `num_chan` is ignored.
-
     Returns
     -------
     recording: BinaryRecordingExtractor
@@ -60,8 +55,8 @@ class BinaryRecordingExtractor(BaseRecording):
         self,
         file_paths,
         sampling_frequency,
+        num_chan,
         dtype,
-        num_channels=None,
         t_starts=None,
         channel_ids=None,
         time_axis=0,
@@ -69,41 +64,34 @@ class BinaryRecordingExtractor(BaseRecording):
         gain_to_uV=None,
         offset_to_uV=None,
         is_filtered=None,
-        num_chan=None,
     ):
-        # This assigns num_channels if num_channels is not None, otherwise num_chan is assigned
-        num_channels = num_channels or num_chan
-        assert num_channels is not None, "You must provide num_channels or num_chan"
-        if num_chan is not None:
-            warnings.warn("`num_chan` is to be deprecated in version 0.100, please use `num_channels` instead")
-
         if channel_ids is None:
-            channel_ids = list(range(num_channels))
+            channel_ids = list(range(num_chan))
         else:
-            assert len(channel_ids) == num_channels, "Provided recording channels have the wrong length"
+            assert len(channel_ids) == num_chan, "Provided recording channels have the wrong length"
 
         BaseRecording.__init__(self, sampling_frequency, channel_ids, dtype)
 
         if isinstance(file_paths, list):
             # several segment
-            file_path_list = [Path(p) for p in file_paths]
+            datfiles = [Path(p) for p in file_paths]
         else:
             # one segment
-            file_path_list = [Path(file_paths)]
+            datfiles = [Path(file_paths)]
 
         if t_starts is not None:
-            assert len(t_starts) == len(file_path_list), "t_starts must be a list of same size than file_paths"
+            assert len(t_starts) == len(datfiles), "t_starts must be a list of same size than file_paths"
             t_starts = [float(t_start) for t_start in t_starts]
 
         dtype = np.dtype(dtype)
 
-        for i, file_path in enumerate(file_path_list):
+        for i, datfile in enumerate(datfiles):
             if t_starts is None:
                 t_start = None
             else:
                 t_start = t_starts[i]
             rec_segment = BinaryRecordingSegment(
-                file_path, sampling_frequency, t_start, num_channels, dtype, time_axis, file_offset
+                datfile, sampling_frequency, t_start, num_chan, dtype, time_axis, file_offset
             )
             self.add_recording_segment(rec_segment)
 
@@ -117,11 +105,10 @@ class BinaryRecordingExtractor(BaseRecording):
             self.set_channel_offsets(offset_to_uV)
 
         self._kwargs = {
-            "file_paths": [str(e.absolute()) for e in file_path_list],
+            "file_paths": [str(e.absolute()) for e in datfiles],
             "sampling_frequency": sampling_frequency,
             "t_starts": t_starts,
-            "num_channels": num_channels,
-            "num_chan": num_channels,  # TODO: This should be here at least till version 0.100.0
+            "num_chan": num_chan,
             "dtype": dtype.str,
             "channel_ids": channel_ids,
             "time_axis": time_axis,
@@ -155,7 +142,7 @@ class BinaryRecordingExtractor(BaseRecording):
         d = dict(
             file_paths=self._kwargs["file_paths"],
             dtype=np.dtype(self._kwargs["dtype"]),
-            num_channels=self._kwargs["num_channels"],
+            num_channels=self._kwargs["num_chan"],
             time_axis=self._kwargs["time_axis"],
             file_offset=self._kwargs["file_offset"],
         )
@@ -168,23 +155,23 @@ BinaryRecordingExtractor.write_recording.__doc__ = BinaryRecordingExtractor.writ
 
 
 class BinaryRecordingSegment(BaseRecordingSegment):
-    def __init__(self, datfile, sampling_frequency, t_start, num_channels, dtype, time_axis, file_offset):
+    def __init__(self, datfile, sampling_frequency, t_start, num_chan, dtype, time_axis, file_offset):
         BaseRecordingSegment.__init__(self, sampling_frequency=sampling_frequency, t_start=t_start)
-        self.num_channels = num_channels
+        self.num_chan = num_chan
         self.dtype = np.dtype(dtype)
         self.file_offset = file_offset
         self.time_axis = time_axis
         self.datfile = datfile
         self.file = open(self.datfile, "r")
-        self.num_samples = (Path(datfile).stat().st_size - file_offset) // (num_channels * np.dtype(dtype).itemsize)
+        self.num_samples = (Path(datfile).stat().st_size - file_offset) // (num_chan * np.dtype(dtype).itemsize)
         if self.time_axis == 0:
-            self.shape = (self.num_samples, self.num_channels)
+            self.shape = (self.num_samples, self.num_chan)
         else:
-            self.shape = (self.num_channels, self.num_samples)
+            self.shape = (self.num_chan, self.num_samples)
 
         byte_offset = self.file_offset
         dtype_size_bytes = self.dtype.itemsize
-        data_size_bytes = dtype_size_bytes * self.num_samples * self.num_channels
+        data_size_bytes = dtype_size_bytes * self.num_samples * self.num_chan
         self.memmap_offset, self.array_offset = divmod(byte_offset, mmap.ALLOCATIONGRANULARITY)
         self.memmap_length = data_size_bytes + self.array_offset
 
diff --git a/src/spikeinterface/core/core_tools.py b/src/spikeinterface/core/core_tools.py
index 0e138962a..316d8f79a 100644
--- a/src/spikeinterface/core/core_tools.py
+++ b/src/spikeinterface/core/core_tools.py
@@ -6,11 +6,10 @@ import datetime
 import json
 from copy import deepcopy
 import gc
-import mmap
-import inspect
 
 import numpy as np
 from tqdm import tqdm
+import inspect
 
 from .job_tools import (
     ensure_chunk_size,
@@ -190,17 +189,24 @@ def read_binary_recording(file, num_chan, dtype, time_axis=0, offset=0):
 
 
 # used by write_binary_recording + ChunkRecordingExecutor
-def _init_binary_worker(recording, file_path_dict, dtype, byte_offest, cast_unsigned):
+def _init_binary_worker(recording, rec_memmaps_dict, dtype, cast_unsigned):
     # create a local dict per worker
     worker_ctx = {}
-    worker_ctx["recording"] = recording
-    worker_ctx["byte_offset"] = byte_offest
+    if isinstance(recording, dict):
+        from spikeinterface.core import load_extractor
+
+        worker_ctx["recording"] = load_extractor(recording)
+    else:
+        worker_ctx["recording"] = recording
+
+    rec_memmaps = []
+    for d in rec_memmaps_dict:
+        rec_memmaps.append(np.memmap(**d))
+
+    worker_ctx["rec_memmaps"] = rec_memmaps
     worker_ctx["dtype"] = np.dtype(dtype)
     worker_ctx["cast_unsigned"] = cast_unsigned
 
-    file_dict = {segment_index: open(file_path, "r+") for segment_index, file_path in file_path_dict.items()}
-    worker_ctx["file_dict"] = file_dict
-
     return worker_ctx
 
 
@@ -209,36 +215,15 @@ def _write_binary_chunk(segment_index, start_frame, end_frame, worker_ctx):
     # recover variables of the worker
     recording = worker_ctx["recording"]
     dtype = worker_ctx["dtype"]
-    byte_offset = worker_ctx["byte_offset"]
+    rec_memmap = worker_ctx["rec_memmaps"][segment_index]
     cast_unsigned = worker_ctx["cast_unsigned"]
-    file = worker_ctx["file_dict"][segment_index]
 
-    # Open the memmap
-    # What we need is the file_path
-    num_channels = recording.get_num_channels()
-    num_frames = recording.get_num_frames(segment_index=segment_index)
-    shape = (num_frames, num_channels)
-    dtype_size_bytes = np.dtype(dtype).itemsize
-    data_size_bytes = dtype_size_bytes * num_frames * num_channels
-
-    # Offset (The offset needs to be multiple of the page size)
-    # The mmap offset is associated to be as big as possible but still a multiple of the page size
-    # The array offset takes care of the reminder
-    mmap_offset, array_offset = divmod(byte_offset, mmap.ALLOCATIONGRANULARITY)
-    mmmap_length = data_size_bytes + array_offset
-    memmap_obj = mmap.mmap(file.fileno(), length=mmmap_length, access=mmap.ACCESS_WRITE, offset=mmap_offset)
-
-    array = np.ndarray.__new__(np.ndarray, shape=shape, dtype=dtype, buffer=memmap_obj, order="C", offset=array_offset)
     # apply function
     traces = recording.get_traces(
         start_frame=start_frame, end_frame=end_frame, segment_index=segment_index, cast_unsigned=cast_unsigned
     )
-    if traces.dtype != dtype:
-        traces = traces.astype(dtype)
-    array[start_frame:end_frame, :] = traces
-
-    # Close the memmap
-    memmap_obj.flush()
+    traces = traces.astype(dtype)
+    rec_memmap[start_frame:end_frame, :] = traces
 
 
 def write_binary_recording(
@@ -276,38 +261,39 @@ def write_binary_recording(
     assert file_paths is not None, "Provide 'file_path'"
     job_kwargs = fix_job_kwargs(job_kwargs)
 
-    file_path_list = [file_paths] if not isinstance(file_paths, list) else file_paths
-    num_segments = recording.get_num_segments()
-    if len(file_path_list) != num_segments:
-        raise ValueError("'file_paths' must be a list of the same size as the number of segments in the recording")
-
-    file_path_list = [Path(file_path) for file_path in file_path_list]
+    if not isinstance(file_paths, list):
+        file_paths = [file_paths]
+    file_paths = [Path(e) for e in file_paths]
     if add_file_extension:
-        file_path_list = [add_suffix(file_path, ["raw", "bin", "dat"]) for file_path in file_path_list]
+        file_paths = [add_suffix(file_path, ["raw", "bin", "dat"]) for file_path in file_paths]
+
+    if dtype is None:
+        dtype = recording.get_dtype()
+    else:
+        dtype = np.dtype(dtype)
 
-    dtype = dtype if dtype is not None else recording.get_dtype()
-    cast_unsigned = False
     if auto_cast_uint:
         cast_unsigned = determine_cast_unsigned(recording, dtype)
+    else:
+        cast_unsigned = False
 
-    dtype_size_bytes = np.dtype(dtype).itemsize
-    num_channels = recording.get_num_channels()
-
-    file_path_dict = {segment_index: file_path for segment_index, file_path in enumerate(file_path_list)}
-    for segment_index, file_path in file_path_dict.items():
-        num_frames = recording.get_num_frames(segment_index=segment_index)
-        data_size_bytes = dtype_size_bytes * num_frames * num_channels
-        file_size_bytes = data_size_bytes + byte_offset
+    # create memmap files
+    rec_memmaps = []
+    rec_memmaps_dict = []
+    for segment_index in range(recording.get_num_segments()):
+        num_frames = recording.get_num_samples(segment_index)
 
-        file = open(file_path, "wb+")
-        file.truncate(file_size_bytes)
-        file.close()
-        assert Path(file_path).is_file()
+        file_path = file_paths[segment_index]
+        num_channels = recording.get_num_channels()
+        shape = (num_frames, num_channels)
+        rec_memmap = np.memmap(str(file_path), dtype=dtype, mode="w+", offset=byte_offset, shape=shape)
+        rec_memmaps.append(rec_memmap)
+        rec_memmaps_dict.append(dict(filename=str(file_path), dtype=dtype, mode="r+", offset=byte_offset, shape=shape))
 
     # use executor (loop or workers)
     func = _write_binary_chunk
     init_func = _init_binary_worker
-    init_args = (recording, file_path_dict, dtype, byte_offset, cast_unsigned)
+    init_args = (recording, rec_memmaps_dict, dtype, cast_unsigned)
     executor = ChunkRecordingExecutor(
         recording, func, init_func, init_args, job_name="write_binary_recording", **job_kwargs
     )
diff --git a/src/spikeinterface/extractors/shybridextractors.py b/src/spikeinterface/extractors/shybridextractors.py
index cb48e3d20..434f7f5df 100644
--- a/src/spikeinterface/extractors/shybridextractors.py
+++ b/src/spikeinterface/extractors/shybridextractors.py
@@ -65,7 +65,7 @@ class SHYBRIDRecordingExtractor(BinaryRecordingExtractor):
             self,
             file_paths=bin_file,
             sampling_frequency=float(params["fs"]),
-            num_channels=nb_channels,
+            num_chan=nb_channels,
             dtype=params["dtype"],
             time_axis=time_axis,
         )
diff --git a/src/spikeinterface/postprocessing/principal_component.py b/src/spikeinterface/postprocessing/principal_component.py
index f6bdf5c5e..a94f275f6 100644
--- a/src/spikeinterface/postprocessing/principal_component.py
+++ b/src/spikeinterface/postprocessing/principal_component.py
@@ -43,7 +43,9 @@ class WaveformPrincipalComponent(BaseWaveformExtractorExtension):
                 txt += " - sparse"
         return txt
 
-    def _set_params(self, n_components=5, mode="by_channel_local", whiten=True, dtype="float32", sparsity=None):
+    def _set_params(
+        self, n_components=5, mode="by_channel_local", whiten=True, dtype="float32", sparsity=None, tmp_folder=None
+    ):
         assert mode in _possible_modes, "Invalid mode!"
 
         if self.waveform_extractor.is_sparse():
@@ -56,6 +58,7 @@ class WaveformPrincipalComponent(BaseWaveformExtractorExtension):
             whiten=bool(whiten),
             dtype=np.dtype(dtype).str,
             sparsity=sparsity,
+            tmp_folder=tmp_folder,
         )
 
         return params
@@ -361,7 +364,12 @@ class WaveformPrincipalComponent(BaseWaveformExtractorExtension):
 
         mode = p["mode"]
         pca_model_files = []
-        tmp_folder = Path("tmp")
+
+        tmp_folder = p["tmp_folder"]
+        if tmp_folder is None:
+            tmp_folder = "tmp"
+        tmp_folder = Path(tmp_folder)
+
         for chan_ind, chan_id in enumerate(channel_ids):
             pca_model = pca_models[chan_ind]
             if n_jobs > 1:
@@ -662,6 +670,7 @@ def compute_principal_components(
     sparsity=None,
     whiten=True,
     dtype="float32",
+    tmp_folder=None,
     **job_kwargs,
 ):
     """
@@ -680,17 +689,22 @@ def compute_principal_components(
         - 'by_channel_local': a local PCA is fitted for each channel (projection by channel)
         - 'by_channel_global': a global PCA is fitted for all channels (projection by channel)
         - 'concatenated': channels are concatenated and a global PCA is fitted
+        default 'by_channel_local'
     sparsity: ChannelSparsity or None
         The sparsity to apply to waveforms.
-        If waveform_extractor is already sparse, the default sparsity will be used.
+        If waveform_extractor is already sparse, the default sparsity will be used - default None
     whiten: bool
-        If True, waveforms are pre-whitened
+        If True, waveforms are pre-whitened - default True
     dtype: dtype
-        Dtype of the pc scores (default float32)
+        Dtype of the pc scores - default float32
     n_jobs: int
         Number of jobs used to fit the PCA model (if mode is 'by_channel_local') - default 1
     progress_bar: bool
         If True, a progress bar is shown - default False
+    tmp_folder: str
+        The temporary folder to use for parallel computation. If you run several `compute_principal_components`
+        functions in parallel with mode 'by_channel_local', you need to specify a different `tmp_folder` for each call,
+        to avoid overwriting to the same folder - default None
 
     Returns
     -------
@@ -716,7 +730,9 @@ def compute_principal_components(
         pc = waveform_extractor.load_extension(WaveformPrincipalComponent.extension_name)
     else:
         pc = WaveformPrincipalComponent.create(waveform_extractor)
-        pc.set_params(n_components=n_components, mode=mode, whiten=whiten, dtype=dtype, sparsity=sparsity)
+        pc.set_params(
+            n_components=n_components, mode=mode, whiten=whiten, dtype=dtype, sparsity=sparsity, tmp_folder=tmp_folder
+        )
         pc.run(**job_kwargs)
 
     return pc


Test patch:
diff --git a/src/spikeinterface/core/tests/test_baserecording.py b/src/spikeinterface/core/tests/test_baserecording.py
index ed9a79d05..230ff40b4 100644
--- a/src/spikeinterface/core/tests/test_baserecording.py
+++ b/src/spikeinterface/core/tests/test_baserecording.py
@@ -34,9 +34,7 @@ def test_BaseRecording():
     for i in range(num_seg):
         a = np.memmap(file_paths[i], dtype=dtype, mode="w+", shape=(num_samples, num_chan))
         a[:] = np.random.randn(*a.shape).astype(dtype)
-    rec = BinaryRecordingExtractor(
-        file_paths=file_paths, sampling_frequency=sampling_frequency, num_channels=num_chan, dtype=dtype
-    )
+    rec = BinaryRecordingExtractor(file_paths, sampling_frequency, num_chan, dtype)
 
     assert rec.get_num_segments() == 2
     assert rec.get_num_channels() == 3
@@ -230,25 +228,14 @@ def test_BaseRecording():
     assert np.dtype(rec_float32.get_traces().dtype) == np.float32
 
     # test with t_start
-    rec = BinaryRecordingExtractor(
-        file_paths=file_paths,
-        sampling_frequency=sampling_frequency,
-        num_channels=num_chan,
-        dtype=dtype,
-        t_starts=np.arange(num_seg) * 10.0,
-    )
+    rec = BinaryRecordingExtractor(file_paths, sampling_frequency, num_chan, dtype, t_starts=np.arange(num_seg) * 10.0)
     times1 = rec.get_times(1)
     folder = cache_folder / "recording_with_t_start"
     rec2 = rec.save(folder=folder)
     assert np.allclose(times1, rec2.get_times(1))
 
     # test with time_vector
-    rec = BinaryRecordingExtractor(
-        file_paths=file_paths,
-        sampling_frequency=sampling_frequency,
-        num_channels=num_chan,
-        dtype=dtype,
-    )
+    rec = BinaryRecordingExtractor(file_paths, sampling_frequency, num_chan, dtype)
     rec.set_times(np.arange(num_samples) / sampling_frequency + 30.0, segment_index=0)
     rec.set_times(np.arange(num_samples) / sampling_frequency + 40.0, segment_index=1)
     times1 = rec.get_times(1)
diff --git a/src/spikeinterface/core/tests/test_binaryrecordingextractor.py b/src/spikeinterface/core/tests/test_binaryrecordingextractor.py
index fb4c3ee3c..1d2c6e4c2 100644
--- a/src/spikeinterface/core/tests/test_binaryrecordingextractor.py
+++ b/src/spikeinterface/core/tests/test_binaryrecordingextractor.py
@@ -13,21 +13,17 @@ else:
 
 def test_BinaryRecordingExtractor():
     num_seg = 2
-    num_channels = 3
+    num_chan = 3
     num_samples = 30
     sampling_frequency = 10000
     dtype = "int16"
 
     file_paths = [cache_folder / f"test_BinaryRecordingExtractor_{i}.raw" for i in range(num_seg)]
     for i in range(num_seg):
-        np.memmap(file_paths[i], dtype=dtype, mode="w+", shape=(num_samples, num_channels))
+        np.memmap(file_paths[i], dtype=dtype, mode="w+", shape=(num_samples, num_chan))
 
-    rec = BinaryRecordingExtractor(
-        file_paths=file_paths,
-        sampling_frequency=sampling_frequency,
-        num_channels=num_channels,
-        dtype=dtype,
-    )
+    rec = BinaryRecordingExtractor(file_paths, sampling_frequency, num_chan, dtype)
+    print(rec)
 
     file_paths = [cache_folder / f"test_BinaryRecordingExtractor_copied_{i}.raw" for i in range(num_seg)]
     BinaryRecordingExtractor.write_recording(rec, file_paths)
@@ -48,12 +44,9 @@ def test_round_trip(tmp_path):
     BinaryRecordingExtractor.write_recording(recording=recording, dtype=dtype, file_paths=file_path)
 
     sampling_frequency = recording.get_sampling_frequency()
-    num_channels = recording.get_num_channels()
+    num_chan = recording.get_num_channels()
     binary_recorder = BinaryRecordingExtractor(
-        file_paths=file_path,
-        sampling_frequency=sampling_frequency,
-        num_channels=num_channels,
-        dtype=dtype,
+        file_paths=file_path, sampling_frequency=sampling_frequency, num_chan=num_chan, dtype=dtype
     )
 
     assert np.allclose(recording.get_traces(), binary_recorder.get_traces())
diff --git a/src/spikeinterface/core/tests/test_channelslicerecording.py b/src/spikeinterface/core/tests/test_channelslicerecording.py
index 08bb22a2c..c5ba89bd8 100644
--- a/src/spikeinterface/core/tests/test_channelslicerecording.py
+++ b/src/spikeinterface/core/tests/test_channelslicerecording.py
@@ -25,12 +25,7 @@ def test_ChannelSliceRecording():
     for i in range(num_seg):
         traces = np.memmap(file_paths[i], dtype=dtype, mode="w+", shape=(num_samples, num_chan))
         traces[:] = np.arange(3)[None, :]
-    rec = BinaryRecordingExtractor(
-        file_paths=file_paths,
-        sampling_frequency=sampling_frequency,
-        num_channels=num_chan,
-        dtype=dtype,
-    )
+    rec = BinaryRecordingExtractor(file_paths, sampling_frequency, num_chan, dtype)
 
     # keep original ids
     rec_sliced = ChannelSliceRecording(rec, channel_ids=[0, 2])
diff --git a/src/spikeinterface/exporters/tests/test_report.py b/src/spikeinterface/exporters/tests/test_report.py
index 253114c34..c4a628456 100644
--- a/src/spikeinterface/exporters/tests/test_report.py
+++ b/src/spikeinterface/exporters/tests/test_report.py
@@ -1,3 +1,5 @@
+import pytest
+import shutil
 from pathlib import Path
 
 import pytest
@@ -10,14 +12,24 @@ from spikeinterface.exporters import export_report
 # from spikeinterface.qualitymetrics import compute_quality_metrics
 
 
-def test_export_report(tmp_path):
+if hasattr(pytest, "global_test_folder"):
+    cache_folder = pytest.global_test_folder / "exporters"
+else:
+    cache_folder = Path("cache_folder") / "exporters"
+
+
+def test_export_report():
     repo = "https://gin.g-node.org/NeuralEnsemble/ephy_testing_data"
     remote_path = "mearec/mearec_test_10s.h5"
     local_path = download_dataset(repo=repo, remote_path=remote_path, local_folder=None)
     recording, sorting = se.read_mearec(local_path)
 
-    waveform_folder = tmp_path / "waveforms"
-    output_folder = tmp_path / "mearec_GT_report"
+    waveform_folder = cache_folder / "waveforms"
+    output_folder = cache_folder / "mearec_GT_report"
+
+    for f in (waveform_folder, output_folder):
+        if f.is_dir():
+            shutil.rmtree(f)
 
     waveform_extractor = extract_waveforms(recording, sorting, waveform_folder)
 
@@ -30,9 +42,4 @@ def test_export_report(tmp_path):
 
 
 if __name__ == "__main__":
-    # Create a temporary folder using the standard library
-    import tempfile
-
-    with tempfile.TemporaryDirectory() as tmpdirname:
-        tmp_path = Path(tmpdirname)
-        test_export_report(tmp_path)
+    test_export_report()