Source code for ewoksndreg.intensities.numpy_backend

import numpy

from ..math import center
from ..math import fft
from ..transformation import TransformationType
from ..transformation.numpy_backend import NumpyHomography
from .base import IntensityMapping

__all__ = ["NumpyCrossCorrelationIntensityMapping"]


[docs] class NumpyCrossCorrelationIntensityMapping( IntensityMapping, registry_id=IntensityMapping.RegistryId("CrossCorrelation", "Numpy"), ): SUPPORTED_TRANSFORMATIONS = ["translation"] def __init__(self, transfo_type: TransformationType, **kw) -> None: self._to_image_ft = None super().__init__(transfo_type, **kw)
[docs] def calculate( self, from_image: numpy.ndarray, to_image: numpy.ndarray ) -> NumpyHomography: from_image_ft = fft.fft2(from_image) if to_image is not self._to_image_ft: self._to_image_fft = fft.fft2(to_image) passive_matrix = numpy.identity(3) passive_matrix[0:2, 2] = determine_shift(from_image_ft, self._to_image_fft) return NumpyHomography(passive_matrix, self._transfo_type)
[docs] def identity(self, dimension: int = 2) -> NumpyHomography: return NumpyHomography( numpy.identity(dimension + 1), TransformationType.identity )
[docs] def determine_shift( img1ft: numpy.ndarray, img2ft: numpy.ndarray, sampling: int = 1, maxmethod: str = "max", ): """Determine shift between images using their Fourier transforms""" is1D = img1ft.size in img1ft.shape # Calculate shift without subpixel precision # Ideally this should be a delta function: real(F.G*/(|F|.|G|)) # In reality this doesn't seem to work, somehow |F.G*| is better, no idea why image_product = img1ft * img2ft.conj() # image_product /= ftmodulus(image_product) cross_correlation = fft.ifft(image_product) shift = cc_maximum(cross_correlation) # Shift indices = [0,...,imax,imin,...,-1] if is1D: _, imax = fft.fft_freqind(cross_correlation.size) if shift > imax: shift -= cross_correlation.size else: s = numpy.array(cross_correlation.shape) _, imax = fft.fft_freqind(s) shift[shift > imax] -= s[shift > imax] if sampling <= 1: return shift # Calculate shift with subpixel precision by interpolating # the cross-correlation around the maximum (or center-of-mass). # real-space: n, d=1, shift=s # super-space: n*sampling, d=1, shift=s*sampling if is1D: ROIsize = 4 * sampling else: # ROI in super-space = 3x3 pixels in real-space ROIsize = sampling * numpy.array((3, 3)) _, ksampled = fft.fft_freqind(ROIsize) # ROI center # ROI = [0,1,...,ROIsize-1] - ksampled + shift*sampling (so that maximum in the middle) ROIoffset = ksampled - shift * sampling cross_correlation = fft.ifft_interpolate(image_product, ROIoffset, ROIsize) # cross_correlation /= img2ft.shape[0]*img2ft.shape[1] * self.sampling ** 2 # Get fit, maximum, centroid, ... shiftsampled = cc_maximum(cross_correlation, maxmethod=maxmethod) # Shift from super space to real space shift = (shiftsampled - ROIoffset) / sampling return shift
[docs] def cc_maximum(cross_correlation: numpy.ndarray, maxmethod: str = "max"): data = ftmodulus(cross_correlation) # data = cross_correlation.real if maxmethod == "centroid": shift = center.fcentroid(data) elif maxmethod == "fit": shift = center.fgaussmax(data) else: shift = center.fmax(data) return shift
[docs] def ftmodulus(imgft: numpy.ndarray) -> numpy.ndarray: imgabs = numpy.abs(imgft) imgabs[imgabs < 1.0e-20] = 1 return imgabs