Skip to main content

N-dimensional image registrations

Project description

ndimreg

Python Implementation PyPI - Version ci OpenSSF Scorecard uv Ruff License

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.

CLI Usage

Use uv to run directly from your terminal:

uvx ndimreg

Or install via PIP first:

pip install ndimreg
ndimreg

Run ndimreg --help to see all available commands.

Extra Backends

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).

Benchmarks

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.

Library Usage

Setting the (CPU) FFT Backend

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

Installation With GPU Support

The following GPUs are supported:

  • NVIDIA (CUDA): Install as pip install ndimreg[cuda12].
  • AMD (ROCm): Install as pip install ndimreg[rocm-5-0].

Choosing the FFT Backend

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!

Examples

Image Source: scikit-image (originally uploaded to NASA Great Images database)

Translation Recovery (2D)

Original Transformed Recovered Fused
Original Image Transformed Image Recoverd Image Fused Image
ndimreg transform 2d \
  --method translation-2d \
  --options '{"upsample_factor": 10}' \
  --image-datasets astronaut \
  --resize 256 \
  --translations-absolute \
  --translation 12.8 25.6
Expected: 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)

Translation and Rotation Recovery (2D)

Original Transformed Recovered Fused
Original Image Transformed Image Recoverd Image Fused Image
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 22
Expected: 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)

Translation, Rotation, and Scale Recovery (2D)

Original Transformed Recovered Fused
Original Image Transformed Image Recoverd Image Fused Image
ndimreg transform 2d \
    --method imregdft-2d \
    --image-datasets astronaut \
    --resize 256 \
    --translations-absolute \
    --translation 12.8 25.6 \
    --rotation 22 \
    --scale 1.1
Expected: 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)

Translation, Rotation, and Scale Recovery (2D) -- 2

Original Transformed Recovered Fused
Original Image Transformed Image Recoverd Image Fused Image
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 hann
Expected: 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)

Translation (3D)

...

Translation and Axis Rotation Recovery (3D)

...

Translation and Full Rotation Recovery (3D)

...

Features

Available Registration Methods

Name Dimension T R S Description
Scikit2DRegistration 2D Based on scikit-image example^1.
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

Image Transformations and Utilities

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.

Backends

Registration

The following backends can be added as alternatives and/or to support more architectures in the future:

Transformation

Interoperability

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ndimreg-0.2.0.tar.gz (7.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ndimreg-0.2.0-py3-none-any.whl (97.8 kB view details)

Uploaded Python 3

File details

Details for the file ndimreg-0.2.0.tar.gz.

File metadata

  • Download URL: ndimreg-0.2.0.tar.gz
  • Upload date:
  • Size: 7.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ndimreg-0.2.0.tar.gz
Algorithm Hash digest
SHA256 5b6fd574fccf8843406a19f81695a92199418ce4d466eb5d6223ce2f4bb78e0e
MD5 9848ceca0574cc9e9795c8c96bb539a2
BLAKE2b-256 e6db54f18441db840b88a37fd1a47a795a8d9402810868b8e5ec98c783e55d59

See more details on using hashes here.

Provenance

The following attestation bundles were made for ndimreg-0.2.0.tar.gz:

Publisher: publish.yml on jnk22/ndimreg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ndimreg-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: ndimreg-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 97.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ndimreg-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6cc1923728799cbd831f730aa9c547609253d2a9234201af88da6449834e613b
MD5 b405b6f9eb7a32462ce94bc996898809
BLAKE2b-256 6638ffb19f9e3448d093566f05f25dea34aef2abc70387dda61de6dbcd56a7fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for ndimreg-0.2.0-py3-none-any.whl:

Publisher: publish.yml on jnk22/ndimreg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page