nanopyx.core.analysis.rcc

  1# REF: based on https://github.com/jungmannlab/picasso/blob/d867f561ffeafce752f37968a20698556d04dafb/picasso/imageprocess.py
  2
  3import numpy as np
  4import lmfit
  5from tqdm import tqdm
  6
  7from .ccm import calculate_ccm_from_ref
  8from .ccm_helper_functions import check_even_square, make_even_square
  9
 10# TODO: fix max_shift parameter
 11
 12
 13def calculate_x_corr(im1: np.ndarray, im2: np.ndarray):
 14    """
 15    Calculate the cross-correlation between two images.
 16
 17    Args:
 18        im1 (np.ndarray): The first image as a NumPy array.
 19        im2 (np.ndarray): The second image as a NumPy array.
 20
 21    Returns:
 22        np.ndarray: The cross-correlation matrix as a NumPy array.
 23    """
 24    ccm = calculate_ccm_from_ref(np.array([im2]).astype(np.float32), im1.astype(np.float32))[0]
 25
 26    return np.array(ccm)
 27
 28
 29def get_image_shift(im1: np.ndarray, im2: np.ndarray, box: int, max_shift: int = None):
 30    """
 31    Calculate the shift in coordinates between two input images.
 32
 33    Parameters:
 34    - im1: A numpy array representing the first image.
 35    - im2: A numpy array representing the second image.
 36    - box: An integer specifying the size of the fitting region of interest (ROI) in pixels.
 37    - max_shift: An optional integer specifying the maximum allowed shift in pixels. If not provided, the entire image will be used for correlation.
 38
 39    Returns:
 40    - A tuple of two integers representing the shift in the x and y coordinates, respectively. The shift is calculated as the difference between the centroid of the fitting ROI and the centroid of the correlation peak.
 41
 42    Note:
 43    - If either of the input images is completely black (i.e., all pixels have a value of zero), the function will return (0, 0).
 44    - The correlation between the two images is computed using the `calculate_x_corr` function.
 45    - The correlation matrix is cropped based on the `max_shift` parameter. If the `max_shift` is positive, the correlation matrix is cropped symmetrically around the center of the image. If the `max_shift` is zero or negative, the entire correlation matrix is used.
 46    - The brightest pixel in the correlation matrix is identified, and a fitting ROI centered on this pixel is extracted. The size of the fitting ROI is specified by the `box` parameter.
 47    - The function fits a 2D Gaussian model to the values in the fitting ROI using the `lmfit` library. The parameters of the Gaussian model are then used to calculate the centroid coordinates.
 48    - The centroid coordinates are adjusted to account for the cropping of the correlation matrix and the size of the fitting ROI.
 49    - Finally, the calculated shift in coordinates is returned as a negative value to align the images.
 50
 51    Example usage:
 52    shift_x, shift_y = get_image_shift(image1, image2, 10, max_shift=5)
 53    """
 54    if (np.sum(im1) == 0) or (np.sum(im2) == 0):
 55        return 0, 0
 56
 57    # Compute image correlation
 58    x_corr = calculate_x_corr(im1, im2)
 59
 60    # crop XCorr based on max_shift
 61    w, h = im1.shape
 62    if max_shift > 0:
 63        x_border = int((w - max_shift) / 2)
 64        y_border = int((h - max_shift) / 2)
 65        if x_border > 0:
 66            x_corr = x_corr[x_border:-x_border, :]
 67        else:
 68            x_border = 0
 69        if y_border > 0:
 70            x_corr = x_corr[:, y_border:-y_border]
 71        else:
 72            y_border = 0
 73    else:
 74        x_border = y_border = 0
 75
 76    # A quarter of the fit ROI
 77    fit_box = int(box / 2)
 78
 79    # A coordinate grid for the fitting ROI
 80    x, y = np.mgrid[-fit_box : fit_box + 1, -fit_box : fit_box + 1]
 81
 82    # Find the brightest pixel and cut out the fit ROI
 83    x_max_xc, y_max_xc = np.unravel_index(x_corr.argmax(), x_corr.shape)
 84    fit_roi = x_corr[
 85        x_max_xc - fit_box : y_max_xc + fit_box + 1,
 86        y_max_xc - fit_box : y_max_xc + fit_box + 1,
 87    ]
 88
 89    dimensions = fit_roi.shape
 90
 91    if 0 in dimensions or dimensions[0] != dimensions[1]:
 92        xc, yc = 0, 0
 93    else:
 94        # The fit model
 95        def flat_2d_gaussian(a, xc, yc, s, b):
 96            A = a * np.exp(-0.5 * ((x - xc) ** 2 + (y - yc) ** 2) / s**2) + b
 97            return A.flatten()
 98
 99        gaussian2d = lmfit.Model(flat_2d_gaussian, name="2D Gaussian", independent_vars=[])
100
101        # Set up initial parameters and fit
102        params = lmfit.Parameters()
103        params.add("a", value=fit_roi.max(), vary=True, min=0)
104        params.add("xc", value=0, vary=True)
105        params.add("yc", value=0, vary=True)
106        params.add("s", value=1, vary=True, min=0)
107        params.add("b", value=fit_roi.min(), vary=True, min=0)
108        tmp = fit_roi.flatten()
109        results = gaussian2d.fit(tmp, params)
110
111        # Get maximum coordinates and add offsets
112        xc = results.best_values["xc"]
113        yc = results.best_values["yc"]
114        xc += y_border + x_max_xc
115        yc += x_border + y_max_xc
116
117        xc -= np.floor(w / 2)
118        yc -= np.floor(h / 2)
119
120    return -xc, -yc
121
122
123def rcc(im_frames: np.ndarray, max_shift=None) -> tuple:
124    """
125    Calculate the relative shifts between a set of input images using the RCC (Relative Cross Correlation) algorithm.
126
127    Parameters:
128    - im_frames: A NumPy array representing a set of input images. The shape of the array is (n_frames, height, width).
129    - max_shift: An optional parameter specifying the maximum shift allowed between image pairs. If not provided, the default value is None.
130
131    Returns:
132    - A tuple containing the minimized shifts in the x and y directions between all image pairs.
133
134    References:
135    - REF: https://github.com/yinawang28/RCC
136    """
137    if not check_even_square(im_frames.astype(np.float32)):
138        im_frames = np.array(make_even_square(im_frames.astype(np.float32)))
139    n_frames = im_frames.shape[0]
140    shifts_x = np.zeros((n_frames, n_frames))
141    shifts_y = np.zeros((n_frames, n_frames))
142    n_pairs = int(n_frames * (n_frames - 1) / 2)
143    flag = 0
144
145    with tqdm(total=n_pairs, desc="Correlating image pairs", unit="pairs") as progress_bar:
146        for i in range(n_frames - 1):
147            for j in range(i + 1, n_frames):
148                progress_bar.update()
149                shifts_x[i, j], shifts_y[i, j] = get_image_shift(im_frames[i], im_frames[j], 5, max_shift)
150                flag += 1
151
152    return minimize_shifts(shifts_x, shifts_y)
153
154
155def minimize_shifts(shifts_x, shifts_y, shifts_z=None):
156    """
157    Functions that minimizes from the input arrays.
158
159    Parameters:
160        shifts_x (ndarray): An array containing the shifts in the x direction.
161        shifts_y (ndarray): An array containing the shifts in the y direction.
162        shifts_z (ndarray, optional): An array containing the shifts in the z direction. Defaults to None.
163
164    Returns:
165        tuple: A tuple containing the shift values in the x, y, and z directions (if shifts_z is not None) or just the shift values in the x and y directions.
166    """
167    n_channels = shifts_x.shape[0]
168    n_pairs = int(n_channels * (n_channels - 1) / 2)
169    n_dims = 2 if shifts_z is None else 3
170    rij = np.zeros((n_pairs, n_dims))
171    A = np.zeros((n_pairs, n_channels - 1))
172    flag = 0
173    for i in range(n_channels - 1):
174        for j in range(i + 1, n_channels):
175            rij[flag, 0] = shifts_x[i, j]
176            rij[flag, 1] = shifts_y[i, j]
177            if n_dims == 3:
178                rij[flag, 2] = shifts_z[i, j]
179            A[flag, i:j] = 1
180            flag += 1
181    Dj = np.dot(np.linalg.pinv(A), rij)
182    shift_x = np.insert(np.cumsum(Dj[:, 0]), 0, 0)
183    shift_y = np.insert(np.cumsum(Dj[:, 1]), 0, 0)
184    if n_dims == 2:
185        return shift_x, shift_y
186    else:
187        shift_z = np.insert(np.cumsum(Dj[:, 2]), 0, 0)
188        return shift_x, shift_y, shift_z
def calculate_x_corr(im1: numpy.ndarray, im2: numpy.ndarray):
14def calculate_x_corr(im1: np.ndarray, im2: np.ndarray):
15    """
16    Calculate the cross-correlation between two images.
17
18    Args:
19        im1 (np.ndarray): The first image as a NumPy array.
20        im2 (np.ndarray): The second image as a NumPy array.
21
22    Returns:
23        np.ndarray: The cross-correlation matrix as a NumPy array.
24    """
25    ccm = calculate_ccm_from_ref(np.array([im2]).astype(np.float32), im1.astype(np.float32))[0]
26
27    return np.array(ccm)

Calculate the cross-correlation between two images.

Args: im1 (np.ndarray): The first image as a NumPy array. im2 (np.ndarray): The second image as a NumPy array.

Returns: np.ndarray: The cross-correlation matrix as a NumPy array.

def get_image_shift( im1: numpy.ndarray, im2: numpy.ndarray, box: int, max_shift: int = None):
 30def get_image_shift(im1: np.ndarray, im2: np.ndarray, box: int, max_shift: int = None):
 31    """
 32    Calculate the shift in coordinates between two input images.
 33
 34    Parameters:
 35    - im1: A numpy array representing the first image.
 36    - im2: A numpy array representing the second image.
 37    - box: An integer specifying the size of the fitting region of interest (ROI) in pixels.
 38    - max_shift: An optional integer specifying the maximum allowed shift in pixels. If not provided, the entire image will be used for correlation.
 39
 40    Returns:
 41    - A tuple of two integers representing the shift in the x and y coordinates, respectively. The shift is calculated as the difference between the centroid of the fitting ROI and the centroid of the correlation peak.
 42
 43    Note:
 44    - If either of the input images is completely black (i.e., all pixels have a value of zero), the function will return (0, 0).
 45    - The correlation between the two images is computed using the `calculate_x_corr` function.
 46    - The correlation matrix is cropped based on the `max_shift` parameter. If the `max_shift` is positive, the correlation matrix is cropped symmetrically around the center of the image. If the `max_shift` is zero or negative, the entire correlation matrix is used.
 47    - The brightest pixel in the correlation matrix is identified, and a fitting ROI centered on this pixel is extracted. The size of the fitting ROI is specified by the `box` parameter.
 48    - The function fits a 2D Gaussian model to the values in the fitting ROI using the `lmfit` library. The parameters of the Gaussian model are then used to calculate the centroid coordinates.
 49    - The centroid coordinates are adjusted to account for the cropping of the correlation matrix and the size of the fitting ROI.
 50    - Finally, the calculated shift in coordinates is returned as a negative value to align the images.
 51
 52    Example usage:
 53    shift_x, shift_y = get_image_shift(image1, image2, 10, max_shift=5)
 54    """
 55    if (np.sum(im1) == 0) or (np.sum(im2) == 0):
 56        return 0, 0
 57
 58    # Compute image correlation
 59    x_corr = calculate_x_corr(im1, im2)
 60
 61    # crop XCorr based on max_shift
 62    w, h = im1.shape
 63    if max_shift > 0:
 64        x_border = int((w - max_shift) / 2)
 65        y_border = int((h - max_shift) / 2)
 66        if x_border > 0:
 67            x_corr = x_corr[x_border:-x_border, :]
 68        else:
 69            x_border = 0
 70        if y_border > 0:
 71            x_corr = x_corr[:, y_border:-y_border]
 72        else:
 73            y_border = 0
 74    else:
 75        x_border = y_border = 0
 76
 77    # A quarter of the fit ROI
 78    fit_box = int(box / 2)
 79
 80    # A coordinate grid for the fitting ROI
 81    x, y = np.mgrid[-fit_box : fit_box + 1, -fit_box : fit_box + 1]
 82
 83    # Find the brightest pixel and cut out the fit ROI
 84    x_max_xc, y_max_xc = np.unravel_index(x_corr.argmax(), x_corr.shape)
 85    fit_roi = x_corr[
 86        x_max_xc - fit_box : y_max_xc + fit_box + 1,
 87        y_max_xc - fit_box : y_max_xc + fit_box + 1,
 88    ]
 89
 90    dimensions = fit_roi.shape
 91
 92    if 0 in dimensions or dimensions[0] != dimensions[1]:
 93        xc, yc = 0, 0
 94    else:
 95        # The fit model
 96        def flat_2d_gaussian(a, xc, yc, s, b):
 97            A = a * np.exp(-0.5 * ((x - xc) ** 2 + (y - yc) ** 2) / s**2) + b
 98            return A.flatten()
 99
100        gaussian2d = lmfit.Model(flat_2d_gaussian, name="2D Gaussian", independent_vars=[])
101
102        # Set up initial parameters and fit
103        params = lmfit.Parameters()
104        params.add("a", value=fit_roi.max(), vary=True, min=0)
105        params.add("xc", value=0, vary=True)
106        params.add("yc", value=0, vary=True)
107        params.add("s", value=1, vary=True, min=0)
108        params.add("b", value=fit_roi.min(), vary=True, min=0)
109        tmp = fit_roi.flatten()
110        results = gaussian2d.fit(tmp, params)
111
112        # Get maximum coordinates and add offsets
113        xc = results.best_values["xc"]
114        yc = results.best_values["yc"]
115        xc += y_border + x_max_xc
116        yc += x_border + y_max_xc
117
118        xc -= np.floor(w / 2)
119        yc -= np.floor(h / 2)
120
121    return -xc, -yc

Calculate the shift in coordinates between two input images.

Parameters:

  • im1: A numpy array representing the first image.
  • im2: A numpy array representing the second image.
  • box: An integer specifying the size of the fitting region of interest (ROI) in pixels.
  • max_shift: An optional integer specifying the maximum allowed shift in pixels. If not provided, the entire image will be used for correlation.

Returns:

  • A tuple of two integers representing the shift in the x and y coordinates, respectively. The shift is calculated as the difference between the centroid of the fitting ROI and the centroid of the correlation peak.

Note:

  • If either of the input images is completely black (i.e., all pixels have a value of zero), the function will return (0, 0).
  • The correlation between the two images is computed using the calculate_x_corr function.
  • The correlation matrix is cropped based on the max_shift parameter. If the max_shift is positive, the correlation matrix is cropped symmetrically around the center of the image. If the max_shift is zero or negative, the entire correlation matrix is used.
  • The brightest pixel in the correlation matrix is identified, and a fitting ROI centered on this pixel is extracted. The size of the fitting ROI is specified by the box parameter.
  • The function fits a 2D Gaussian model to the values in the fitting ROI using the lmfit library. The parameters of the Gaussian model are then used to calculate the centroid coordinates.
  • The centroid coordinates are adjusted to account for the cropping of the correlation matrix and the size of the fitting ROI.
  • Finally, the calculated shift in coordinates is returned as a negative value to align the images.

Example usage: shift_x, shift_y = get_image_shift(image1, image2, 10, max_shift=5)

def rcc(im_frames: numpy.ndarray, max_shift=None) -> tuple:
124def rcc(im_frames: np.ndarray, max_shift=None) -> tuple:
125    """
126    Calculate the relative shifts between a set of input images using the RCC (Relative Cross Correlation) algorithm.
127
128    Parameters:
129    - im_frames: A NumPy array representing a set of input images. The shape of the array is (n_frames, height, width).
130    - max_shift: An optional parameter specifying the maximum shift allowed between image pairs. If not provided, the default value is None.
131
132    Returns:
133    - A tuple containing the minimized shifts in the x and y directions between all image pairs.
134
135    References:
136    - REF: https://github.com/yinawang28/RCC
137    """
138    if not check_even_square(im_frames.astype(np.float32)):
139        im_frames = np.array(make_even_square(im_frames.astype(np.float32)))
140    n_frames = im_frames.shape[0]
141    shifts_x = np.zeros((n_frames, n_frames))
142    shifts_y = np.zeros((n_frames, n_frames))
143    n_pairs = int(n_frames * (n_frames - 1) / 2)
144    flag = 0
145
146    with tqdm(total=n_pairs, desc="Correlating image pairs", unit="pairs") as progress_bar:
147        for i in range(n_frames - 1):
148            for j in range(i + 1, n_frames):
149                progress_bar.update()
150                shifts_x[i, j], shifts_y[i, j] = get_image_shift(im_frames[i], im_frames[j], 5, max_shift)
151                flag += 1
152
153    return minimize_shifts(shifts_x, shifts_y)

Calculate the relative shifts between a set of input images using the RCC (Relative Cross Correlation) algorithm.

Parameters:

  • im_frames: A NumPy array representing a set of input images. The shape of the array is (n_frames, height, width).
  • max_shift: An optional parameter specifying the maximum shift allowed between image pairs. If not provided, the default value is None.

Returns:

  • A tuple containing the minimized shifts in the x and y directions between all image pairs.

References:

def minimize_shifts(shifts_x, shifts_y, shifts_z=None):
156def minimize_shifts(shifts_x, shifts_y, shifts_z=None):
157    """
158    Functions that minimizes from the input arrays.
159
160    Parameters:
161        shifts_x (ndarray): An array containing the shifts in the x direction.
162        shifts_y (ndarray): An array containing the shifts in the y direction.
163        shifts_z (ndarray, optional): An array containing the shifts in the z direction. Defaults to None.
164
165    Returns:
166        tuple: A tuple containing the shift values in the x, y, and z directions (if shifts_z is not None) or just the shift values in the x and y directions.
167    """
168    n_channels = shifts_x.shape[0]
169    n_pairs = int(n_channels * (n_channels - 1) / 2)
170    n_dims = 2 if shifts_z is None else 3
171    rij = np.zeros((n_pairs, n_dims))
172    A = np.zeros((n_pairs, n_channels - 1))
173    flag = 0
174    for i in range(n_channels - 1):
175        for j in range(i + 1, n_channels):
176            rij[flag, 0] = shifts_x[i, j]
177            rij[flag, 1] = shifts_y[i, j]
178            if n_dims == 3:
179                rij[flag, 2] = shifts_z[i, j]
180            A[flag, i:j] = 1
181            flag += 1
182    Dj = np.dot(np.linalg.pinv(A), rij)
183    shift_x = np.insert(np.cumsum(Dj[:, 0]), 0, 0)
184    shift_y = np.insert(np.cumsum(Dj[:, 1]), 0, 0)
185    if n_dims == 2:
186        return shift_x, shift_y
187    else:
188        shift_z = np.insert(np.cumsum(Dj[:, 2]), 0, 0)
189        return shift_x, shift_y, shift_z

Functions that minimizes from the input arrays.

Parameters: shifts_x (ndarray): An array containing the shifts in the x direction. shifts_y (ndarray): An array containing the shifts in the y direction. shifts_z (ndarray, optional): An array containing the shifts in the z direction. Defaults to None.

Returns: tuple: A tuple containing the shift values in the x, y, and z directions (if shifts_z is not None) or just the shift values in the x and y directions.