This is a library and CLI for image registrations and image transformations supporting 2D and 3D images.
Its main goal is to provide a collection of fast and optimized image registration algorithms with an easy-to-use API. Additionally, a comprehensive benchmarking suite can be used to compare various registration methods and configurations.
Use uv to run directly from your terminal:
uvx ndimregOr install via PIP first:
pip install ndimreg
ndimregRun ndimreg --help to see all available commands.
The following additional device backends and FFT backends (CPU) are available as extras:
| Backend | Type | Extra |
|---|---|---|
| pyFFTW | FFT Backend (CPU) | pyfftw |
| mkl_fft | FFT Backend (CPU) | mkl |
| CuPy | GPU Support (NVIDIA, CUDA-12) | cuda12 |
| CuPy | GPU Support (NVIDIA, CUDA-11) | cuda11 |
| CuPy | GPU Support (AMD, ROCm 5.0) | rocm-5-0 |
| CuPy | GPU Support (AMD, ROCm 4.3) | rocm-4-3 |
To install an extra backend, use pip install ndimreg[extra], (e.g., pip install ndimreg[cuda12] for NVIDIA support with CUDA-12).
You can perform extensive benchmarks for various registrations, images, and
configurations with ndimreg benchmark.
To see all possible input variations, run ndimreg benchmark 2d --help or
ndimreg benchmark 3d --help respectively.
All outputs are generated as CSV and JSON data.
ndimreg respects your scipy.fft backend.
Set a global FFT backend for all following registrations:
import mkl_fft._scipy_fft_backend as mkl_fft
from scipy import fft
# Set the global FFT backend to `mkl_fft` which will then be used
# during registration. You can use any backend that supports SciPy FFT.
fft.set_global_backend(mkl_fft)
# ...setup registration method and load images...
result = registration.register(fixed_image, moving_image)Set a temporary FFT backend as context manager:
import mkl_fft._scipy_fft_backend as mkl_fft
from scipy import fft
# ...setup registration method and load images...
# Temporarily set the FFT backend to `mkl_fft` which will then be used
# during registration. You can use any backend that supports SciPy FFT.
# The global/default backend will not be changed or overwritten.
with fft.set_backend(mkl_fft):
result = registration.register(fixed_image, moving_image)For more information, check out SciPy: Discrete Fourier transforms: Backend control
The following GPUs are supported:
- NVIDIA (CUDA): Install as
pip install ndimreg[cuda12]. - AMD (ROCm): Install as
pip install ndimreg[rocm-5-0].
You can choose between the following CPU FFT backends by setting the
--fft-backend parameter:
| Backend | Parameter | Extra |
|---|---|---|
| scipy | scipy (default) |
-- |
| pyFFTW | pyfftw |
pyfftw |
| mkl_fft | mkl |
mkl |
To install an extra FFT backend such as mkl, install ndimreg as pip install ndimreg[mkl].
Note: When registering data that is on the GPU, FFT operations will always
use the CuPy backend!
Image Source: scikit-image (originally uploaded to NASA Great Images database)
| Original | Transformed | Recovered | Fused |
|---|---|---|---|
ndimreg transform 2d \
--method translation-2d \
--options '{"upsample_factor": 10}' \
--image-datasets astronaut \
--resize 256 \
--translations-absolute \
--translation 12.8 25.6Expected: Transformation2D(translation=(12.80, 25.60), rotation=None, scale=None)
Recovered: Transformation2D(translation=(12.90, 25.70), rotation=None, scale=None)
Duration: 11.85 ms
Click here to show the sample code!
You can download the full sample here!
from ndimreg.fusion import MergeFusion
from ndimreg.image import Image2D
from ndimreg.registration import TranslationFFT2DRegistration
from ndimreg.transform import Transformation2D
from ndimreg.utils import format_time
# Load a test 2D image from 'scikit-image' and resize it to 256x256.
original = Image2D.from_skimage("astronaut").resize_to_shape(256)
# Copy the original image and transform it.
transformation = Transformation2D(translation=(12.8, 25.6))
transformed = original.copy().transform(transformation=transformation)
# Use the registration method with 'upsample_factor=10' for a translation
# precision of 0.1 to register the original image with the transformed image.
registration = TranslationFFT2DRegistration(upsample_factor=10)
result = original.register(registration, transformed)[0]
# We now transform the previously modified image with the recovered
# transformation output and fuse it with the original image.
recovered = transformed.copy().transform(
transformation=result.transformation, inverse=True
)
fused = original.fuse(MergeFusion(), recovered)| Original | Transformed | Recovered | Fused |
|---|---|---|---|
ndimreg transform 2d \
--method keller-adf-2d \
--options '{"shift_upsample_factor": 10}' \
--image-datasets astronaut \
--resize 256 \
--translations-absolute \
--translation 12.8 25.6 \
--rotation 22Expected: Transformation2D(translation=(12.80, 25.60), rotation=22.00, scale=None)
Recovered: Transformation2D(translation=(12.80, 25.70), rotation=22.10, scale=None)
Duration: 116.43 ms
Click here to show the sample code!
You can download the full sample here!
from ndimreg.fusion import MergeFusion
from ndimreg.image import Image2D
from ndimreg.registration import Keller2DRegistration
from ndimreg.transform import Transformation2D
from ndimreg.utils import format_time
# Load a test 2D image from 'scikit-image' and resize it to 256x256.
original = Image2D.from_skimage("astronaut").resize_to_shape(256)
# Copy the original image and transform it.
transformation = Transformation2D(translation=(12.8, 25.6), rotation=22)
transformed = original.copy().transform(transformation=transformation)
# Use the registration method with 'shift_upsample_factor=10' for a translation
# precision of 0.1 to register the original image with the transformed image.
registration = Keller2DRegistration(shift_upsample_factor=10)
result = original.register(registration, transformed)[0]
# We now transform the previously modified image with the recovered
# transformation output and fuse it with the original image.
recovered = transformed.copy().transform(
transformation=result.transformation, inverse=True
)
fused = original.fuse(MergeFusion(), recovered)| Original | Transformed | Recovered | Fused |
|---|---|---|---|
ndimreg transform 2d \
--method imregdft-2d \
--image-datasets astronaut \
--resize 256 \
--translations-absolute \
--translation 12.8 25.6 \
--rotation 22 \
--scale 1.1Expected: Transformation2D(translation=(12.80, 25.60), rotation=22.00, scale=1.10)
Recovered: Transformation2D(translation=(13.09, 25.42), rotation=22.01, scale=1.10)
Duration: 84.78 ms
Click here to show the sample code!
You can download the full sample here!
# Load a test 2D image from 'scikit-image' and resize it to 256x256.
original = Image2D.from_skimage("astronaut").resize_to_shape(256)
# Copy the original image and transform it.
transformation = Transformation2D(translation=(12.8, 25.6), rotation=22, scale=1.1)
transformed = original.copy().transform(transformation=transformation)
# Use the default options for the 'imreg_dft' wrapped registration method
# to register the original image with the transformed image.
registration = ImregDft2DRegistration()
result = original.register(registration, transformed)[0]
# We now transform the previously modified image with the recovered
# transformation output and fuse it with the original image.
recovered = transformed.copy().transform(
transformation=result.transformation, inverse=True
)
fused = original.fuse(MergeFusion(), recovered)| Original | Transformed | Recovered | Fused |
|---|---|---|---|
ndimreg transform 2d \
--method scikit-2d \
--options '{"shift_upsample_factor": 10}' \
--image-datasets astronaut \
--resize 256 \
--translations-absolute \
--translation 12.8 25.6 \
--rotation 22 \
--scale 1.1 \
--bandpass 0 1 \
--window hannExpected: Transformation2D(translation=(12.80, 25.60), rotation=22.00, scale=1.10)
Recovered: Transformation2D(translation=(13.20, 26.00), rotation=21.09, scale=1.08)
Duration: 65.77 ms
Click here to show the sample code!
You can download the full sample here!
from ndimreg.fusion import MergeFusion
from ndimreg.image import Image2D
from ndimreg.processor import GaussianBandPassFilter, WindowFilter
from ndimreg.registration import Scikit2DRegistration
from ndimreg.transform import Transformation2D
from ndimreg.utils import format_time
# Load a test 2D image from 'scikit-image' and resize it to 256x256.
original = Image2D.from_skimage("astronaut").resize_to_shape(256)
# Copy the original image and transform it.
transformation = Transformation2D(translation=(12.8, 25.6), rotation=22, scale=1.1)
transformed = original.copy().transform(transformation=transformation)
# Use the registration method with 'shift_upsample_factor=10' for a translation
# precision of 0.1 to register the original image with the transformed image.
# This method also requires pre-processing with a bandpass and window filter.
pre_processors = [GaussianBandPassFilter(0, 1), WindowFilter("hann")]
registration = Scikit2DRegistration(shift_upsample_factor=10, processors=pre_processors)
result = original.register(registration, transformed)[0]
# We now transform the previously modified image with the recovered
# transformation output and fuse it with the original image.
recovered = transformed.copy().transform(
transformation=result.transformation, inverse=True
)
fused = original.fuse(MergeFusion(), recovered)...
...
...
| Name | Dimension | T | R | S | Description |
|---|---|---|---|---|---|
Scikit2DRegistration |
2D | ✅ | ✅ | ✅ | Based on scikit-image example1. |
ImregDft2DRegistration |
2D | ✅ | ✅ | ✅ | Wrapper around imreg_dft library. |
Keller2DRegistration |
2D | ✅ | ✅ | ❌ | Implementation of 10.1109/TPAMI.2005.128. |
TranslationFFT2DRegistration |
2D | ✅ | ❌ | ❌ | Translation recovery using Fourier Shift Theorem. |
TranslationFFT3DRegistration |
3D | ✅ | ❌ | ❌ | Translation recovery using Fourier Shift Theorem. |
Keller3DRegistration |
3D | ✅ | ✅ | ❌ | Implementation of 10.1109/TSP.2006.881217. |
RotationAxis3DRegistration |
3D | ✅ | ✅ | ❌ | Part of Keller3DRegistration for single axis rotation recovery. |
T: Translation
R: Rotation
S: Scale
| Feature | State | Backend(s) |
|---|---|---|
| Translate | ✅ | scipy/CuPy: [cupyx.]scipy.ndimage.affine_transform() |
| Rotate | ✅ | scipy/CuPy: [cupyx.]scipy.ndimage.affine_transform() |
| Scale | ✅ | scipy/CuPy: [cupyx.]scipy.ndimage.affine_transform() |
| Resize | ✅ | scikit-image/cuCIM: [cucim.]skimage.transform.resize_local_mean() |
| Pad | 🚧 | -- |
| Normalize | ✅ | scikit-image/cuCIM: [cucim.]skimage.exposure.rescale_intensity() |
| Grayscale | ✅ | scikit-image/cuCIM: [cucim.]skimage.color.rgb2gray() |
| Clip | ✅ | -- |
| Cut | ✅ | -- |
| Copy | ✅ | numpy: numpy.copy() |
| Save | ✅ | scikit-image: skimage.io.imsave() |
| Show | ✅ | matplotlib (2D), napari (3D) |
✅ Feature exists.
🚧 Feature is WIP.
- NumPy
- scipy
- scikit-image
- Optional: CuPy + cuCIM (GPU support on Nvidia/AMD)
- Optional: pyFFTW
- Optional: mkl_fft
The following backends can be added as alternatives and/or to support more architectures in the future:
- NumPy
- scipy
- scikit-image
- Optional: CuPy + cuCIM (GPU support on Nvidia/AMD)
- Interoperability for general array operations is implemented via the Python array API standard.
- Interoperability for FFT operations is implemented via scipy's backend API.