neu_proc.ops.backend

Which library runs an op: scipy on the CPU, or cupyx on the GPU.

Dispatch follows the array, never a flag: cupyx.scipy.ndimage needs a cupy.ndarray, so one body serves both backends:

def dilate(array, *, radius_vox):
    ndi, array = ndimage_for(array, "grey_dilation")
    return ndi.grey_dilation(array, footprint=kernels.ball(radius_vox))

An op handed a CPU array may still compute on the GPU and copy back, when it has enough arithmetic to pay for the transfers; each op declares this with stage(). Measured on an RTX A6000, an EDT dilation of a 192^3 array takes 811 ms in scipy, 16 ms on the GPU including both transfers, and 2 ms on an array already there. A bandwidth-bound op (a threshold, a cast) cannot win the round trip and should not stage. like() returns each result to where the input lived, so dilate(numpy_array) returns numpy. For a chain of ops, move once with to_gpu() and every step stays on the GPU.

NEU_PROC_GPU=0 (or backend.enabled = False) makes to_gpu() a no-op, so nothing moves; a GPU array handed in is still processed on the GPU. cupyx lacks a few ndimage functions (distance_transform_bf, distance_transform_cdt, geometric_transform, watershed_ift); ndimage_for() then falls back to the CPU and warns once. When timing GPU code, call synchronize() first: cupy queues work and returns immediately, and the first call in a process also pays CUDA setup.

Attributes

Functions

array_module(value)

cupy for a GPU array, numpy otherwise (the array-API xp).

cpu_only(value, what)

value on the CPU, warning once per what if it had to be moved. For libraries

gpu_available() → bool

True if cupy imports and a GPU device is found; cached after the first call. cupy

is_gpu_array(value) → bool

True if value is a cupy array. Checked by module name, so cupy is never imported.

like(result, reference)

result moved to wherever reference lives, CPU or GPU, so an op returns the

ndimage_for(value[, need])

(ndimage module, array) for value.

stage(value, *[, prefer_gpu])

value where the op should compute: moved to the GPU if prefer_gpu and a GPU

synchronize() → None

Wait for queued GPU work to finish. Needed only when timing: cupy returns before the

to_cpu(value)

value as a numpy array, moved off the GPU if it is on one.

to_gpu(value)

value on the GPU; unchanged, without error, if enabled is false or no GPU

Module Contents

neu_proc.ops.backend.array_module(value: Any)

cupy for a GPU array, numpy otherwise (the array-API xp).

neu_proc.ops.backend.cpu_only(value: Any, what: str)

value on the CPU, warning once per what if it had to be moved. For libraries with no GPU implementation (fastmorph, cc3d, fill_voids, edt), which would otherwise fail with cupy’s unhelpful “Implicit conversion to a NumPy array” error.

neu_proc.ops.backend.gpu_available() → bool

True if cupy imports and a GPU device is found; cached after the first call. cupy can import on a machine with no GPU or driver and fail only at first use, hence the device check.

neu_proc.ops.backend.is_gpu_array(value: Any) → bool

True if value is a cupy array. Checked by module name, so cupy is never imported.

neu_proc.ops.backend.like(result: Any, reference: Any)

result moved to wherever reference lives, CPU or GPU, so an op returns the kind of array it was given.

neu_proc.ops.backend.ndimage_for(value: Any, need: str | None = None)

(ndimage module, array) for value.

need names the function about to be called. If value is on the GPU and cupyx lacks that function, the array is moved to the CPU and scipy is returned, with a warning once per name. Without need the array is returned untouched.

neu_proc.ops.backend.stage(value: Any, *, prefer_gpu: bool = True)

value where the op should compute: moved to the GPU if prefer_gpu and a GPU is enabled and available, otherwise unchanged. Pass prefer_gpu=False for a bandwidth-bound op. Pair with like():

work = stage(arr)
...
return like(result, arr)
neu_proc.ops.backend.synchronize() → None

Wait for queued GPU work to finish. Needed only when timing: cupy returns before the work is done, while reading a result synchronizes on its own.

neu_proc.ops.backend.to_cpu(value: Any)

value as a numpy array, moved off the GPU if it is on one.

neu_proc.ops.backend.to_gpu(value: Any)

value on the GPU; unchanged, without error, if enabled is false or no GPU is available.

neu_proc.ops.backend.enabled: bool
neu_proc.ops.backend.logger