# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "14/04/2020"
import numpy
import silx.math
from silx.utils.enum import Enum as _Enum
[docs]class Method(_Enum):
"""
Methods available to compute the background.
"""
mean = "mean"
median = "median"
[docs]def background_subtraction(data, bg_frames, method='median'):
"""Function that computes the median between a series of dark images from a dataset
and subtracts it to each frame of the raw data to remove the noise.
:param ndarray data: The raw data
:param array_like bg_frames: List of dark frames
:param method: Method used to determine the background image.
:type method: Union['mean', 'median', None]
:returns: ndarray
:raises: ValueError
"""
assert bg_frames is not None, "Background frames must be given"
background = numpy.zeros(data[0].shape, data.dtype)
method = Method.from_value(method)
if len(bg_frames):
if method == Method.mean:
numpy.mean(bg_frames, out=background, axis=0)
elif method == Method.median:
numpy.median(bg_frames, out=background, axis=0)
else:
raise ValueError("Invalid method specified. Please use `mean`, "
"or `median`.")
if data.dtype.kind == 'i' or data.dtype.kind == 'u':
new_data = numpy.subtract(data, background, dtype=numpy.int64)
else:
new_data = numpy.subtract(data, background)
new_data[new_data < 0] = 0
return new_data.astype(data.dtype)
[docs]def img2img_mean(img, mean=None, n=0):
"""
Update mean from stack of images, given a new image and its index in
the stack.
:param array_like img: img to add to the mean
:param array_like mean: mean img
:param int n: index of the last image in the stack
:return: Image with new mean
"""
if not numpy.any(mean):
mean = img
else:
mean = (mean * n + img) / (n + 1)
return mean
[docs]def background_subtraction_2D(img, bg):
"""
Compute background subtraction.
:param array_like img: Raw image
:param array_like bg: Background image
:return: Image with subtracted background
"""
if img.dtype.kind == 'i' or img.dtype.kind == 'u':
img = numpy.subtract(img, bg.astype(img.dtype), dtype=numpy.int64)
else:
img = numpy.subtract(img, bg.astype(img.dtype))
img[img < 0] = 0
return img
def _create_circular_mask(shape, center=None, radius=None):
"""
Function that given a height and a width returns a circular mask image.
:param int h: Height
:param int w: Width
:param center: Center of the circle
:type center: Union[[int, int], None]
:param radius: Radius of the circle
:type radius: Union[int, None]
:returns: ndarray
"""
h, w = shape
if center is None: # use the middle of the image
center = [int(h / 2), int(w / 2)]
if radius is None: # use the smallest distance between the center and image walls
radius = min(center[0], center[1], h - center[0], w - center[1])
X, Y = numpy.ogrid[:h, :w]
dist_from_center = numpy.sqrt((X - center[0]) ** 2 + (Y - center[1]) ** 2)
mask = dist_from_center <= radius
return mask
def _create_n_sphere_mask(shape, center=None, radius=None):
"""
Function that given a list of dimensions returns a n-dimensional sphere mask.
:param shape: Dimensions of the mask
:type shape: array_like
:param center: Center of the sphere
:type center: Union[array_like, None]
:param radius: Radius of the sphere
:type radius: Union[int, None]
:returns: ndarray
"""
assert shape or radius, "If dimensions are not entered radius must be given"
dimensions = numpy.array(shape)
if center is None: # use the middle of the image
center = (dimensions / 2).astype(int)
if radius is None: # use the smallest distance between the center and image walls
radius = min(numpy.concatenate([center, dimensions - center]))
C = numpy.ogrid[[slice(0, dim) for dim in dimensions]]
dist_from_center = numpy.sqrt(numpy.sum((C - center) ** 2))
mask = dist_from_center <= radius
return mask
[docs]def chunk_image(img, start, chunk_shape):
"""
Return a chunk of an image.
:param array_like img: Raw image
:param tuple start: Start of the chunk in the image
:param tuple shape: Shape of the chunk
"""
return img[start[0]: start[0] + chunk_shape[0], start[1]:start[1] + chunk_shape[1]]
[docs]def hot_pixel_removal_3D(data, ksize=3):
"""
Function to remove hot pixels of the data using median filter.
:param array_like data: Input data.
:param int ksize: Size of the mask to apply.
:returns: ndarray
"""
corrected_data = numpy.empty(data.shape, dtype=data.dtype)
for i, frame in enumerate(data):
corrected_data[i] = hot_pixel_removal_2D(frame, ksize)
return corrected_data
[docs]def hot_pixel_removal_2D(image, ksize=3):
"""
Function to remove hot pixels of the data using median filter.
:param array_like data: Input data.
:param int ksize: Size of the mask to apply.
:returns: ndarray
"""
if image.dtype == numpy.int or image.dtype == numpy.uint:
# Cast to int and not uint to later subtract
image = image.astype(numpy.int16)
elif image.dtype == numpy.float:
image = image.astype(numpy.float32)
corrected_image = numpy.array(image)
median = silx.math.medfilt(corrected_image, ksize)
if image.dtype.kind == 'i' or image.dtype.kind == 'u':
hot_pixels = numpy.subtract(corrected_image, median, dtype=numpy.int64)
else:
hot_pixels = numpy.subtract(corrected_image, median)
threshold = numpy.std(hot_pixels)
corrected_image[hot_pixels > threshold] = median[hot_pixels > threshold]
return corrected_image
[docs]def threshold_removal(data, bottom=None, top=None):
"""
Set bottom and top threshold to the images in the dataset.
:param Dataset dataset: Dataset with the data
:param int bottom: Bottom threshold
:param int top: Top threshold
:returns: ndarray
"""
frames = []
for frame in data:
median = numpy.median(frame)
if bottom is not None:
frame[frame < bottom] = median
if top is not None:
frame[frame > top] = median
frames.append(frame)
return numpy.asarray(frames)