"""Contains filter functions that take a 2D array representing an image as an input, as well as necessary parameters,
and return a 2D array of the same size representing the filtered image."""
# pylint: disable=no-name-in-module
# pylint: disable=invalid-name
# pylint: disable=fixme
from pathlib import Path
from typing import Union
import logging
import numpy as np
import pySPM
from pySPM.SPM import SPM_image
from topostats.logs.logs import LOGGER_NAME
LOGGER = logging.getLogger(LOGGER_NAME)
[docs]def amplify(image: np.array, level: float) -> np.array:
"""The amplify filter mulitplies the value of all pixels by the `level` argument.
Parameters
----------
image: np.array
Numpy array representing image.
level: np.array
Factor by which to amplify the array.
Returns
-------
np.array
Numpy array of image amplified by level.
"""
LOGGER.info(f'[amplify] Level : {level}')
return image * level
[docs]def row_col_quantiles(image: np.array, mask: np.array = None) -> np.array:
"""Returns the height value quantiles for the rows and columns.
Parameters
----------
image: np.array
Numpy array representing image.
mask: np.array
Mask array to apply.
Returns
-------
np.array
Numpy array of image but with any linear plane slant removed.
"""
# Mask the data if applicable
if mask is not None:
image = np.ma.masked_array(image, mask=mask, fill_value=np.nan)
LOGGER.info('[row_col_quantiles] Masking enabled')
else:
LOGGER.info('[row_col_quantiles] Masking disabled')
row_quantiles = np.quantile(image, [0.25, 0.5, 0.75], axis=1).T
col_quantiles = np.quantile(image, [0.25, 0.5, 0.75], axis=0).T
LOGGER.info('Row and column quantiles calculated.')
return row_quantiles, col_quantiles
[docs]def align_rows(image: np.array, mask: np.array = None) -> np.array:
"""Returns the input image with rows aligned by median height
Parameters
----------
image: np.array
Numpy array representing image.
mask: np.array
Mask array to apply.
Returns
-------
np.array
Numpy array of image but with any linear plane slant removed.
"""
# Get row height quantiles for the image.
row_quantiles, _ = row_col_quantiles(image, mask)
# Calculate median row height
row_medians = row_quantiles[:, 1]
# Calculate median row height
median_row_height = np.quantile(row_medians, 0.5)
LOGGER.info(f'[align_rows] median_row_height: {median_row_height}')
# Calculate the differences between the row medians and the median row height
row_median_diffs = row_medians - median_row_height
# Adjust the row medians accordingly
for i in range(image.shape[0]):
if np.isnan(row_median_diffs[i]):
logging.info(f'{i} row_median is nan! : {row_median_diffs[i]}')
image[i] -= row_median_diffs[i]
return image
[docs]def remove_x_y_tilt(image: np.array, mask: np.array = None) -> np.array:
"""Returns the input image after removing any linear plane slant
Parameters
----------
image: np.array
Numpy array representing image.
mask: np.array
Mask array to apply.
Returns
-------
np.array
Numpy array of image but with any linear plane slant removed.
"""
# Get the row and column quantiles of the data
row_quantiles, col_quantiles = row_col_quantiles(image, mask)
# Calculate the x and y gradient from the left to the right
x_grad = calc_gradient(row_quantiles, row_quantiles.shape[0])
logging.info(f'[remove_x_y_tilt] x_grad: {x_grad}')
y_grad = calc_gradient(col_quantiles, col_quantiles.shape[0])
logging.info(f'[remove_x_y_tilt] y_grad: {y_grad}')
# Add corrections to the data
for i in range(image.shape[0]):
for j in range(image.shape[1]):
image[i, j] -= x_grad * i
image[i, j] -= y_grad * j
LOGGER.info('[remove_x_y_tilt] Linear plane slant removed')
return image
[docs]def calc_diff(array: np.array) -> np.array:
"""Calculate the difference of an array.
Parameters
----------
image: np.array
Numpy array representing image.
Returns
-------
np.array
"""
LOGGER.info('[calc_diff] Calculating difference in array.')
return array[-1][1] - array[0][1]
[docs]def calc_gradient(array: np.array, shape: int) -> np.array:
"""Calculate the gradient of an array.
Parameters
----------
image: np.array
Numpy array representing image.
shape: int
Shape of the array in terms of the number of rows, typically it will be array[0].
Returns
-------
np.array
"""
LOGGER.info('[calc_gradient] Calculating gradient')
return calc_diff(array) / shape
[docs]def average_background(image: np.array, mask: np.array = None) -> np.array:
"""Zero the background based on the median.
Parameters
----------
image: np.array
Numpy array representing image.
mask: np.array
Mask of the array, should have the same dimensions as image.
Returns
-------
np.array
Numpy array of image zero averaged.
"""
row_quantiles, _ = row_col_quantiles(image, mask)
LOGGER.info('[average_background] Zero averaging background')
return image - np.array(row_quantiles[:, 1], ndmin=2).T