Skip to content

Sensors and DVS Pipeline

Event camera (DVS) data loading, preprocessing, spike encoding, and bit-true ADC-to-spike window encoding. The public package exports DVSLoader, events_to_spike_trains, events_to_frames, ADCSpikeWindowConfig, ADCSpikeWindowResult, adc_to_spike_windows, adc_to_spike_windows_q, available_backends, and quantise_adc.

Python
from sc_neurocore.sensors import DVSLoader, events_to_spike_trains

loader = DVSLoader(width=128, height=128)
events = loader.from_numpy(raw_events)
spikes = events_to_spike_trains(events, width=128, height=128)
Python
from sc_neurocore.sensors import ADCSpikeWindowConfig, adc_to_spike_windows

config = ADCSpikeWindowConfig(decimation=8, threshold_q=256)
windows = adc_to_spike_windows(raw_adc_samples, config, backend="auto")

See Tutorial 45: DVS Pipeline.

API

sc_neurocore.sensors.dvs

DVS (Dynamic Vision Sensor) event processing pipeline.

Load event camera data, convert to spike trains or frames, and feed into SC-NeuroCore networks for processing and FPGA deployment.

Supports raw event arrays (structured numpy) and integration with the Tonic library for standard DVS datasets (N-MNIST, DVS-Gesture, N-Cars, etc.).

structured array with fields (x, y, t, p)

x: pixel x coordinate y: pixel y coordinate t: timestamp (microseconds) p: polarity (0=OFF, 1=ON)

DVSLoader dataclass

Load and preprocess DVS event camera data.

Parameters

width : int Sensor width in pixels. height : int Sensor height in pixels.

Source code in src/sc_neurocore/sensors/dvs.py
Python
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
@dataclass
class DVSLoader:
    """Load and preprocess DVS event camera data.

    Parameters
    ----------
    width : int
        Sensor width in pixels.
    height : int
        Sensor height in pixels.
    """

    width: int = 346
    height: int = 260

    @property
    def n_pixels(self) -> int:
        """Return the total number of pixels in the DVS frame."""
        return self.width * self.height

    def from_numpy(self, events: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
        """Load events from structured numpy array.

        Expected fields: 'x', 'y', 't', 'p' (or positional columns).
        Returns structured array with named fields.
        """
        if events.dtype.names is not None:
            return events
        if events.ndim == 2 and events.shape[1] >= 4:
            dtype = np.dtype([("x", np.int32), ("y", np.int32), ("t", np.int64), ("p", np.int8)])
            structured = np.zeros(events.shape[0], dtype=dtype)
            structured["x"] = events[:, 0].astype(np.int32)
            structured["y"] = events[:, 1].astype(np.int32)
            structured["t"] = events[:, 2].astype(np.int64)
            structured["p"] = events[:, 3].astype(np.int8)
            return structured
        raise ValueError("Events must be structured array or (N, 4+) array with x, y, t, p columns")

    def from_tonic(self, dataset_name: str, index: int = 0) -> tuple[np.ndarray[Any, Any], int]:
        """Load events from a Tonic dataset (requires tonic package).

        Parameters
        ----------
        dataset_name : str
            Tonic dataset name: 'nmnist', 'dvs_gesture', 'ncars', etc.
        index : int
            Sample index in the dataset.

        Returns
        -------
        (events, target) tuple
        """
        try:
            import tonic
        except ImportError:
            raise ImportError("pip install tonic") from None

        dataset_map = {  # pragma: no cover
            "nmnist": tonic.datasets.NMNIST,
            "dvs_gesture": tonic.datasets.DVSGesture,
        }
        cls = dataset_map.get(dataset_name)  # pragma: no cover
        if cls is None:  # pragma: no cover
            raise ValueError(f"Unknown dataset '{dataset_name}'. Options: {list(dataset_map)}")

        ds = cls(save_to="./data", train=True)  # pragma: no cover
        events, target = ds[index]  # pragma: no cover
        return self.from_numpy(events), target  # pragma: no cover

n_pixels property

Return the total number of pixels in the DVS frame.

from_numpy(events)

Load events from structured numpy array.

Expected fields: 'x', 'y', 't', 'p' (or positional columns). Returns structured array with named fields.

Source code in src/sc_neurocore/sensors/dvs.py
Python
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def from_numpy(self, events: np.ndarray[Any, Any]) -> np.ndarray[Any, Any]:
    """Load events from structured numpy array.

    Expected fields: 'x', 'y', 't', 'p' (or positional columns).
    Returns structured array with named fields.
    """
    if events.dtype.names is not None:
        return events
    if events.ndim == 2 and events.shape[1] >= 4:
        dtype = np.dtype([("x", np.int32), ("y", np.int32), ("t", np.int64), ("p", np.int8)])
        structured = np.zeros(events.shape[0], dtype=dtype)
        structured["x"] = events[:, 0].astype(np.int32)
        structured["y"] = events[:, 1].astype(np.int32)
        structured["t"] = events[:, 2].astype(np.int64)
        structured["p"] = events[:, 3].astype(np.int8)
        return structured
    raise ValueError("Events must be structured array or (N, 4+) array with x, y, t, p columns")

from_tonic(dataset_name, index=0)

Load events from a Tonic dataset (requires tonic package).

Parameters

dataset_name : str Tonic dataset name: 'nmnist', 'dvs_gesture', 'ncars', etc. index : int Sample index in the dataset.

Returns

(events, target) tuple

Source code in src/sc_neurocore/sensors/dvs.py
Python
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def from_tonic(self, dataset_name: str, index: int = 0) -> tuple[np.ndarray[Any, Any], int]:
    """Load events from a Tonic dataset (requires tonic package).

    Parameters
    ----------
    dataset_name : str
        Tonic dataset name: 'nmnist', 'dvs_gesture', 'ncars', etc.
    index : int
        Sample index in the dataset.

    Returns
    -------
    (events, target) tuple
    """
    try:
        import tonic
    except ImportError:
        raise ImportError("pip install tonic") from None

    dataset_map = {  # pragma: no cover
        "nmnist": tonic.datasets.NMNIST,
        "dvs_gesture": tonic.datasets.DVSGesture,
    }
    cls = dataset_map.get(dataset_name)  # pragma: no cover
    if cls is None:  # pragma: no cover
        raise ValueError(f"Unknown dataset '{dataset_name}'. Options: {list(dataset_map)}")

    ds = cls(save_to="./data", train=True)  # pragma: no cover
    events, target = ds[index]  # pragma: no cover
    return self.from_numpy(events), target  # pragma: no cover

events_to_spike_trains(events, width, height, dt_us=1000.0, duration_us=None)

Convert DVS events to binary spike train matrix.

Parameters

events : structured ndarray with x, y, t, p fields width, height : int Sensor dimensions. dt_us : float Time bin width in microseconds (default 1000 = 1ms). duration_us : float, optional Total duration. If None, inferred from event timestamps.

Returns

ndarray of shape (n_bins, width * height * 2) Binary spike trains. Channels: [ON pixels, OFF pixels].

Source code in src/sc_neurocore/sensors/dvs.py
Python
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def events_to_spike_trains(
    events: np.ndarray[Any, Any],
    width: int,
    height: int,
    dt_us: float = 1000.0,
    duration_us: float | None = None,
) -> np.ndarray[Any, Any]:
    """Convert DVS events to binary spike train matrix.

    Parameters
    ----------
    events : structured ndarray with x, y, t, p fields
    width, height : int
        Sensor dimensions.
    dt_us : float
        Time bin width in microseconds (default 1000 = 1ms).
    duration_us : float, optional
        Total duration. If None, inferred from event timestamps.

    Returns
    -------
    ndarray of shape (n_bins, width * height * 2)
        Binary spike trains. Channels: [ON pixels, OFF pixels].
    """
    x = events["x"].astype(np.int64)
    y = events["y"].astype(np.int64)
    t = events["t"].astype(np.float64)
    p = events["p"].astype(np.int8)

    t_min = t.min()
    t_rel = t - t_min

    if duration_us is None:
        duration_us = t_rel.max() + dt_us

    n_bins = max(1, int(np.ceil(duration_us / dt_us)))
    n_channels = width * height * 2

    spikes = np.zeros((n_bins, n_channels), dtype=np.int8)

    for i in range(len(events)):
        bin_idx = min(int(t_rel[i] / dt_us), n_bins - 1)
        pixel_idx = int(y[i]) * width + int(x[i])
        if p[i] > 0:
            channel = pixel_idx
        else:
            channel = width * height + pixel_idx
        if 0 <= channel < n_channels:
            spikes[bin_idx, channel] = 1

    return spikes

events_to_frames(events, width, height, dt_us=10000.0, duration_us=None)

Convert DVS events to event count frames.

Parameters

events : structured ndarray width, height : int dt_us : float Frame duration in microseconds (default 10000 = 10ms). duration_us : float, optional

Returns

ndarray of shape (n_frames, 2, height, width) Event count frames with ON and OFF channels.

Source code in src/sc_neurocore/sensors/dvs.py
Python
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def events_to_frames(
    events: np.ndarray[Any, Any],
    width: int,
    height: int,
    dt_us: float = 10000.0,
    duration_us: float | None = None,
) -> np.ndarray[Any, Any]:
    """Convert DVS events to event count frames.

    Parameters
    ----------
    events : structured ndarray
    width, height : int
    dt_us : float
        Frame duration in microseconds (default 10000 = 10ms).
    duration_us : float, optional

    Returns
    -------
    ndarray of shape (n_frames, 2, height, width)
        Event count frames with ON and OFF channels.
    """
    x = events["x"].astype(np.int64)
    y = events["y"].astype(np.int64)
    t = events["t"].astype(np.float64)
    p = events["p"].astype(np.int8)

    t_min = t.min()
    t_rel = t - t_min

    if duration_us is None:
        duration_us = t_rel.max() + dt_us

    n_frames = max(1, int(np.ceil(duration_us / dt_us)))
    frames = np.zeros((n_frames, 2, height, width), dtype=np.float32)

    for i in range(len(events)):
        f = min(int(t_rel[i] / dt_us), n_frames - 1)
        yi = min(int(y[i]), height - 1)
        xi = min(int(x[i]), width - 1)
        ch = 1 if p[i] > 0 else 0
        frames[f, ch, yi, xi] += 1.0

    return frames

sc_neurocore.sensors.adc_to_spike_kernel

Bit-true integer reference for the ADC-to-spike decimating rate-code encoder.

Each decimation window of raw ADC samples is centred and quantised to a Q-format code, sign-aware averaged, and converted into a deterministic rate code: the spike count is |window| // threshold and the polarity is the window sign. This is the per-window arithmetic of the synthesisable sensor bridge in hdl/sensors/adc_to_spike_quantiser.v and the cycle-stepped golden model in tools/adc_to_spike_reference.py (Indiveri 2003 rate coding); the cycle-accurate handshake/drain FSM stays in that reference, while this kernel is the hot per-window compute.

The whole path is exact integer arithmetic (sign-aware Q-format rounding, truncate-toward-zero window averaging, floor-division rate code), so the Python floor and the Rust, Julia, Go and Mojo backends agree bit-for-bit; the parity tolerance is exactly zero.

ADCSpikeWindowConfig dataclass

Fixed-point and decimation contract for the ADC-to-spike encoder.

Attributes

adc_width : int Raw ADC sample width in bits (must exceed one). q_int : int Q-format integer bits (must be positive). q_frac : int Q-format fractional bits (must be non-negative). decimation : int Number of ADC samples averaged into one spike window (must be positive). signed_input : bool True if the ADC delivers two's-complement samples, False for offset-binary samples centred at mid-scale. threshold_q : int Q-format magnitude that emits one spike (must be positive).

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
@dataclass(frozen=True)
class ADCSpikeWindowConfig:
    """Fixed-point and decimation contract for the ADC-to-spike encoder.

    Attributes
    ----------
    adc_width : int
        Raw ADC sample width in bits (must exceed one).
    q_int : int
        Q-format integer bits (must be positive).
    q_frac : int
        Q-format fractional bits (must be non-negative).
    decimation : int
        Number of ADC samples averaged into one spike window (must be positive).
    signed_input : bool
        ``True`` if the ADC delivers two's-complement samples, ``False`` for
        offset-binary samples centred at mid-scale.
    threshold_q : int
        Q-format magnitude that emits one spike (must be positive).
    """

    adc_width: int = 16
    q_int: int = 8
    q_frac: int = 8
    decimation: int = 8
    signed_input: bool = True
    threshold_q: int = 256

    @property
    def q_total(self) -> int:
        """Total Q-format bit width."""
        return self.q_int + self.q_frac

    @property
    def q_min(self) -> int:
        """Most negative representable Q-format code."""
        return -(1 << (self.q_total - 1))

    @property
    def q_max(self) -> int:
        """Most positive representable Q-format code."""
        return (1 << (self.q_total - 1)) - 1

    def validate(self) -> None:
        """Raise :class:`ValueError` if any field is out of contract.

        Raises
        ------
        ValueError
            If the ADC width, Q-format, decimation or threshold are invalid.
        """
        if self.adc_width <= 1:
            raise ValueError("adc_width must be greater than one")
        if self.q_int <= 0 or self.q_frac < 0:
            raise ValueError("Q-format needs positive integer bits and non-negative fraction bits")
        if self.decimation <= 0:
            raise ValueError("decimation must be positive")
        if self.threshold_q <= 0:
            raise ValueError("threshold_q must be positive")

q_total property

Total Q-format bit width.

q_min property

Most negative representable Q-format code.

q_max property

Most positive representable Q-format code.

validate()

Raise :class:ValueError if any field is out of contract.

Raises

ValueError If the ADC width, Q-format, decimation or threshold are invalid.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def validate(self) -> None:
    """Raise :class:`ValueError` if any field is out of contract.

    Raises
    ------
    ValueError
        If the ADC width, Q-format, decimation or threshold are invalid.
    """
    if self.adc_width <= 1:
        raise ValueError("adc_width must be greater than one")
    if self.q_int <= 0 or self.q_frac < 0:
        raise ValueError("Q-format needs positive integer bits and non-negative fraction bits")
    if self.decimation <= 0:
        raise ValueError("decimation must be positive")
    if self.threshold_q <= 0:
        raise ValueError("threshold_q must be positive")

ADCSpikeWindowResult dataclass

Per-window outputs of the ADC-to-spike encoder.

Each array is indexed by completed decimation window.

Attributes

window_values_q : numpy.ndarray Sign-aware averaged Q-format window codes, int32. spike_counts : numpy.ndarray Deterministic per-window spike counts (|window| // threshold), int32. polarities : numpy.ndarray True where the window code is negative, bool_.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@dataclass(frozen=True)
class ADCSpikeWindowResult:
    """Per-window outputs of the ADC-to-spike encoder.

    Each array is indexed by completed decimation window.

    Attributes
    ----------
    window_values_q : numpy.ndarray
        Sign-aware averaged Q-format window codes, ``int32``.
    spike_counts : numpy.ndarray
        Deterministic per-window spike counts (``|window| // threshold``),
        ``int32``.
    polarities : numpy.ndarray
        ``True`` where the window code is negative, ``bool_``.
    """

    window_values_q: npt.NDArray[np.int32]
    spike_counts: npt.NDArray[np.int32]
    polarities: npt.NDArray[np.bool_]

quantise_adc(sample, config)

Centre and quantise one raw ADC sample to a Q-format code.

Mirrors ADCToSpikeReference.quantise_adc: two's-complement or offset-binary centring, Q-format up-shift or sign-aware round-down, then saturation.

Parameters

sample : int Raw ADC sample. config : ADCSpikeWindowConfig Fixed-point contract.

Returns

int Saturated Q-format code.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def quantise_adc(sample: int, config: ADCSpikeWindowConfig) -> int:
    """Centre and quantise one raw ADC sample to a Q-format code.

    Mirrors ``ADCToSpikeReference.quantise_adc``: two's-complement or offset-binary
    centring, Q-format up-shift or sign-aware round-down, then saturation.

    Parameters
    ----------
    sample : int
        Raw ADC sample.
    config : ADCSpikeWindowConfig
        Fixed-point contract.

    Returns
    -------
    int
        Saturated Q-format code.
    """
    adc_width = config.adc_width
    q_total = config.q_total
    if config.signed_input:
        sign_bit = 1 << (adc_width - 1)
        mask = (1 << adc_width) - 1
        sample &= mask
        centred = sample - (1 << adc_width) if sample & sign_bit else sample
    else:
        centred = sample - (1 << (adc_width - 1))

    if q_total > adc_width:
        rounded = centred << (q_total - adc_width)
    elif adc_width > q_total:
        shift = adc_width - q_total
        half = 1 << (shift - 1)
        rounded = (centred + half) >> shift if centred >= 0 else (centred - half) >> shift
    else:
        rounded = centred
    return max(config.q_min, min(config.q_max, rounded))

adc_to_spike_windows_q(samples, config=None)

Pure-Python ADC-to-spike window encoder — the bit-true floor reference.

Parameters

samples : array_like Raw ADC samples; the first n_windows * decimation are consumed. config : ADCSpikeWindowConfig, optional Fixed-point/decimation contract (defaults to Q8.8, decimation 8).

Returns

ADCSpikeWindowResult Per-window averaged codes, spike counts and polarities.

Raises

ValueError If the config is invalid or fewer than decimation samples are given.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def adc_to_spike_windows_q(
    samples: npt.ArrayLike,
    config: ADCSpikeWindowConfig | None = None,
) -> ADCSpikeWindowResult:
    """Pure-Python ADC-to-spike window encoder — the bit-true floor reference.

    Parameters
    ----------
    samples : array_like
        Raw ADC samples; the first ``n_windows * decimation`` are consumed.
    config : ADCSpikeWindowConfig, optional
        Fixed-point/decimation contract (defaults to Q8.8, decimation 8).

    Returns
    -------
    ADCSpikeWindowResult
        Per-window averaged codes, spike counts and polarities.

    Raises
    ------
    ValueError
        If the config is invalid or fewer than ``decimation`` samples are given.
    """
    cfg = config if config is not None else ADCSpikeWindowConfig()
    cfg.validate()
    sample_arr = np.ascontiguousarray(samples, dtype=np.int64).reshape(-1)
    n_windows = int(sample_arr.size) // cfg.decimation
    if n_windows == 0:
        raise ValueError(
            f"need at least decimation={cfg.decimation} samples, got {sample_arr.size}"
        )

    window_values = np.empty(n_windows, dtype=np.int32)
    spike_counts = np.empty(n_windows, dtype=np.int32)
    polarities = np.empty(n_windows, dtype=np.bool_)
    for window in range(n_windows):
        base = window * cfg.decimation
        total = 0
        for offset in range(cfg.decimation):
            total += quantise_adc(int(sample_arr[base + offset]), cfg)
        window_q = _average_window(total, cfg)
        window_values[window] = window_q
        spike_counts[window] = abs(window_q) // cfg.threshold_q
        polarities[window] = window_q < 0

    return ADCSpikeWindowResult(
        window_values_q=window_values,
        spike_counts=spike_counts,
        polarities=polarities,
    )

available_backends()

Probe which acceleration backends can run the ADC-to-spike kernel.

Returns

dict Mapping of backend name to availability, in fastest-first order. The python floor is always True.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def available_backends() -> dict[str, bool]:
    """Probe which acceleration backends can run the ADC-to-spike kernel.

    Returns
    -------
    dict
        Mapping of backend name to availability, in fastest-first order. The
        ``python`` floor is always ``True``.
    """
    status: dict[str, bool] = {}
    probe_samples = np.zeros(ADCSpikeWindowConfig().decimation, dtype=np.int64)
    probe_config = ADCSpikeWindowConfig()
    for name in FASTEST_FIRST_BACKENDS:
        if name == "python":
            status[name] = True
            continue
        try:
            _BACKEND_DISPATCH[name](probe_samples, probe_config)
            status[name] = True
        except (ImportError, OSError, RuntimeError, FileNotFoundError):
            status[name] = False
    return status

adc_to_spike_windows(samples, config=None, *, backend='auto')

Encode ADC samples into spike windows through the fastest available backend.

Parameters

samples : array_like Raw ADC samples. config : ADCSpikeWindowConfig, optional Fixed-point/decimation contract. backend : str, optional "auto" (default) selects the fastest available backend in :data:FASTEST_FIRST_BACKENDS order; a specific name forces that backend.

Returns

ADCSpikeWindowResult Bit-identical to the Python floor for every backend.

Raises

ValueError If backend is not a known name. ImportError If an explicitly requested accelerator backend is unavailable.

Source code in src/sc_neurocore/sensors/adc_to_spike_kernel.py
Python
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
def adc_to_spike_windows(
    samples: npt.ArrayLike,
    config: ADCSpikeWindowConfig | None = None,
    *,
    backend: str = "auto",
) -> ADCSpikeWindowResult:
    """Encode ADC samples into spike windows through the fastest available backend.

    Parameters
    ----------
    samples : array_like
        Raw ADC samples.
    config : ADCSpikeWindowConfig, optional
        Fixed-point/decimation contract.
    backend : str, optional
        ``"auto"`` (default) selects the fastest available backend in
        :data:`FASTEST_FIRST_BACKENDS` order; a specific name forces that backend.

    Returns
    -------
    ADCSpikeWindowResult
        Bit-identical to the Python floor for every backend.

    Raises
    ------
    ValueError
        If ``backend`` is not a known name.
    ImportError
        If an explicitly requested accelerator backend is unavailable.
    """
    cfg = config if config is not None else ADCSpikeWindowConfig()
    if backend != "auto":
        if backend not in _BACKEND_DISPATCH:
            raise ValueError(
                f"unknown backend {backend!r}; choose from {('auto', *FASTEST_FIRST_BACKENDS)}"
            )
        return _BACKEND_DISPATCH[backend](samples, cfg)
    for name in select_backend_order("adc_to_spike_windows_q"):
        if name == "python":
            break
        try:
            return _BACKEND_DISPATCH[name](samples, cfg)
        except (ImportError, OSError, RuntimeError, FileNotFoundError):
            continue
    return _backend_python(samples, cfg)