Source code for aydin.it.transforms.highpass
import numbers
import numpy
from numpy.typing import ArrayLike
from scipy.ndimage import median_filter, gaussian_filter
from aydin.it.transforms.base import ImageTransformBase
from aydin.util.log.log import lsection, lprint
[docs]class HighpassTransform(ImageTransformBase):
"""Highpass Image Simplification
For images with little noise, applying a high-pass filter can help denoise the image by removing some of the
image complexity. The low-frequency parts of the image do not need to be denoised because sometimes the challenge
is disentangling the (high-frequency) noise from the high-frequencies in the image. The scale parameter must be
chosen with care. The lesser the noise, the smaller the value. Values around 1 work well but must be tuned
depending on the image. If the scale parameter is too low, some noise might be left untouched. The best is to
keep the parameter as low as possible while still achieving good denoising performance. It is also possible to
apply median filtering when computing the low-pass image which helps reducing the impact of outlier voxel values,
for example salt&pepper noise. Note: when median filtering is on, larger values of sigma (e.g. >= 1) are
recommended, unless when the level of noise is very low in which case a sigma of 0 (no Gaussian blur) may be
advantageous. To recover the original denoised image the filtering is undone during post-processing. Note: this
is ideal for treating <a href='https://en.wikipedia.org/wiki/Colors_of_noise'>'blue' noise</a> that is
characterised by a high-frequency support.
"""
preprocess_description = (
"Remove low spatial frequencies" + ImageTransformBase.preprocess_description
)
postprocess_description = (
"Add back low spatial frequencies" + ImageTransformBase.postprocess_description
)
postprocess_supported = True
postprocess_recommended = True
def __init__(
self,
sigma: float = 1,
median_filtering: bool = True,
priority: float = 0.1,
**kwargs,
):
"""
Constructs a Highpass Transform
Parameters
----------
sigma : float
Sigma value of the Gaussian filter used for high-pass filtering.
median_filtering : bool
Adds robustness of the filtering in the presence of outliers.
priority : float
The priority is a value within [0,1] used to determine the order in
which to apply the pre- and post-processing transforms. Transforms
are sorted and applied in ascending order during preprocesing and in
the reverse, descending, order during post-processing.
"""
super().__init__(priority=priority, **kwargs)
self.sigma = sigma
self.median_filtering = median_filtering
self._low_pass_image = None
self._original_dtype = None
self._min = None
self._max = None
lprint(f"Instanciating: {self}")
# We exclude certain fields from saving:
def __getstate__(self):
state = self.__dict__.copy()
del state['_low_pass_image']
del state['_original_dtype']
del state['_min']
del state['_max']
return state
def __str__(self):
return (
f'{type(self).__name__}'
f' (sigma={self.sigma}, median_filtering={self.median_filtering})'
)
def __repr__(self):
return self.__str__()
def preprocess(self, array: ArrayLike):
with lsection(
f"Applies high-pass filter of sigma {self.sigma} {'and median filtering' if self.median_filtering else ''} to array of shape: {array.shape} and dtype: {array.dtype}"
):
# Remember min and max:
self._min = array.min()
self._max = array.max()
# remember original dtype:
self._original_dtype = array.dtype
# Cast to float if needed:
array = array.astype(numpy.float32, copy=False)
# Low-pass filtering:
self._low_pass_image = self._low_pass_filtering(array)
# High-pass filtering:
new_array = array - self._low_pass_image
return new_array
def postprocess(self, array: ArrayLike):
if not self.do_postprocess:
return array
with lsection(
f"Adds back low-pass frequencies to array of shape: {array.shape} and dtype: {array.dtype}"
):
array = array.astype(numpy.float32, copy=False)
# Bring back the low frequencies:
new_array = array + self._low_pass_image
# If integer type we ensure to stay within original bounds:
if issubclass(self._original_dtype.type, numbers.Integral):
new_array = numpy.clip(new_array, self._min, self._max)
# cast back to original dtype:
new_array = new_array.astype(self._original_dtype, copy=False)
# Free memory:
self._low_pass_image = None
return new_array
def _low_pass_filtering(self, array: ArrayLike):
lprint(f"Sigma for high-pass filter is: {self.sigma}")
# Median filtering if selected:
if self.median_filtering:
array = median_filter(array, size=3)
# Compute mean:
mean = numpy.mean(array).astype(numpy.float32)
# Low-pass filtering:
if self.sigma > 0:
array = gaussian_filter(array, sigma=self.sigma) - mean
return array
def _interpolation(image, x, y):
out = numpy.interp(image.flat, x, y)
return out.reshape(image.shape)