import cv2
import numpy as np
import xarray as xr
from skimage import img_as_float
from ..array._image_operations import _VectorizedImageOperationArray
GRAY_VECTOR_BT709 = np.array([0.2126, 0.7152, 0.0722], dtype='float32')
GRAY_VECTOR_BT601 = np.array([0.299, 0.587, 0.114], dtype='float32')
[docs]
def rgb2gray(
data, *,
preserve_range=False,
output_array=None,
casting='same_kind',
gray_vector=None,
):
"""Convert ND images from rgb color space to grayscale color space.
Conversion from RGB color space to grayscale color space is done with
the following coefficients:
[0.299, 0.587, 0.114]
Parameters
----------
data: ndarray [..., 3]
Multi-dimensional image where the last dimension has a dimension of 3.
The color coordinates correspond to the red, green and blue channels
respectively.
preserve_range:
If True, integer images will not be scaled between 0 and 1 prior to
color space conversion. If False, integer images will be converted
to floating point numbers between 0 and 1 following to scikit-image
conventions.
.. versionchanged :: 0.14.0
In Version 0.14.0 the default value of preserved_range was
changed to ``False``.
output_array: ArrayLike[N, M] or None
The output array of the operation. If provided, this should be a
C contiguous array.
casting: {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
gray_vector: array_like
The coefficients to convert RGB to grayscale. By default, it is
[0.299, 0.587, 0.114] but can be changed to another set of
coefficients.
"""
# Fastpath for the common case of converting to BT601 grayscale
# converting to floating point and using numpy in general is much slower
# than optimized opencv code
if (
preserve_range and
data.dtype in (np.uint8, np.uint16, np.uint32) and
((output_array is None) or (output_array.dtype == data.dtype)) and
# BT601 is the default from opencv
((gray_vector is None) or np.allclose(gray_vector, GRAY_VECTOR_BT601))
):
return cv2.cvtColor(data, cv2.COLOR_RGB2GRAY, dst=output_array)
if not preserve_range:
data = img_as_float(data)
# Mostly the same as the scikit-image function but supports ND arrays.
if gray_vector is None:
dtype = data.dtype if isinstance(data.dtype, np.floating) else 'float32'
gray_vector = GRAY_VECTOR_BT601.astype(dtype)
return np.matmul(
data,
gray_vector,
out=output_array,
casting=casting,
)
[docs]
def rgba2gray(data, *, preserve_range=False):
"""Convert ND images from RGBA color space to grayscale color space.
Conversion from RGBA color space to grayscale color space is done with
the following coefficients:
[0.299, 0.587, 0.114, 0]
Parameters
----------
data: ndarray [..., 4]
Multi-dimensional image where the last dimension has a dimension of 4.
The color coordinates correspond to the red, green, blue and alpha
channels respectively.
preserve_range:
If True, integer images will not be scaled between 0 and 1 prior to
color space conversion. If False, integer images will be converted
to floating point numbers between 0 and 1 following to scikit-image
conventions.
.. versionchanged :: 0.14.0
In Version 0.14.0 the default value of preserved_range was
changed to ``False``.
"""
if not preserve_range:
data = img_as_float(data)
dtype = data.dtype if isinstance(data.dtype, np.floating) else 'float32'
coefficients = np.array([
GRAY_VECTOR_BT601[0],
GRAY_VECTOR_BT601[1],
GRAY_VECTOR_BT601[2],
0.,
], dtype=dtype)
return data @ coefficients
def _apply_conversion_matrix(image, conversion_matrix, output_array):
# User may pass a lazy array, cast it for opencv to use it
image = np.ascontiguousarray(image)
# image must have 3 dimensions to work correctly with cv2.transform
if image.ndim == 2:
image = image[..., np.newaxis]
conversion_matrix = conversion_matrix[:image.shape[-1], :]
cv2.transform(
image,
conversion_matrix,
dst=output_array,
)
[docs]
def get_converted_data(dataset, conversion_matrix):
if not np.issubdtype(np.float64, dataset.images):
# Cast down to float32 unless the original images are of 64 bit float
conversion_matrix = conversion_matrix.astype('float32')
# We want to broadcast the image corrections on the
# 'image_y', 'image_x' and stack dims
to_broadcast_dims = tuple(
dim
for dim in dataset.images.dims
if dim not in ('y', 'x', 'rgb', 'rgba')
)
to_broadcast_shape = tuple(
dataset.sizes[dim]
if dim in ('image_y', 'image_x') else 1
for dim in to_broadcast_dims
)
funcs = np.empty(
(dataset.sizes['image_y'], dataset.sizes['image_x']),
dtype=object
)
import functools
for i in np.ndindex(funcs.shape):
funcs[i] = functools.partial(
_apply_conversion_matrix,
conversion_matrix=conversion_matrix[i]
)
funcs = funcs.reshape(to_broadcast_shape)
array = _VectorizedImageOperationArray(
dataset.images.variable,
dtype=dataset.images.dtype,
broadcast_dims=len(to_broadcast_shape),
func=funcs
)
return array
[docs]
def get_converted_dataset(dataset, conversion_matrix):
"""Apply photometric response to the dataset images variable
Parameters
----------
dataset : xarray Dataset
MCAM data containing image data as well as additional metadata. Image data should be RGB.
conversion_matrix : numpy Array
NxMx3x3 Matrix to be applied to the image data. NxM should match the shape image_y and
image_x dimension of the dataset.
Returns
-------
converted_dataset : xarray Dataset
"""
from xarray.core.indexing import LazilyIndexedArray
converted_data = LazilyIndexedArray(
get_converted_data(dataset, conversion_matrix=conversion_matrix)
)
dims = dataset.images.dims
images = xr.Variable(dims, converted_data)
converted_dataset = dataset.copy()
converted_dataset['images'] = images
return converted_dataset