CofeehousePy/deps/scikit-image/skimage/io/_plugins/_colormixer.pyx

552 lines
13 KiB
Cython

#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
"""Color Mixer
NumPy does not do overflow checking when adding or multiplying
integers, so currently the only way to clip results efficiently
(without making copies of the data) is with an extension such as this
one.
"""
cimport numpy as cnp
from libc.math cimport exp, pow
cnp.import_array()
def add(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
Py_ssize_t channel, Py_ssize_t amount):
"""Add a given amount to a color channel of `stateimg`, and
store the result in `img`. Overflow is clipped.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
channel : int
Channel (0 for "red", 1 for "green", 2 for "blue").
amount : int
Value to add.
"""
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef Py_ssize_t k = channel
cdef Py_ssize_t n = amount
cdef cnp.int16_t op_result
cdef cnp.uint8_t lut[256]
cdef Py_ssize_t i, j, l
with nogil:
for l from 0 <= l < 256:
op_result = <cnp.int16_t>(l + n)
if op_result > 255:
op_result = 255
elif op_result < 0:
op_result = 0
else:
pass
lut[l] = <cnp.uint8_t>op_result
for i from 0 <= i < height:
for j from 0 <= j < width:
img[i, j, k] = lut[stateimg[i,j,k]]
def multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
Py_ssize_t channel, float amount):
"""Multiply a color channel of `stateimg` by a certain amount, and
store the result in `img`. Overflow is clipped.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
channel : int
Channel (0 for "red", 1 for "green", 2 for "blue").
amount : float
Multiplication factor.
"""
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef Py_ssize_t k = channel
cdef float n = amount
cdef float op_result
cdef cnp.uint8_t lut[256]
cdef Py_ssize_t i, j, l
with nogil:
for l from 0 <= l < 256:
op_result = l * n
if op_result > 255:
op_result = 255
elif op_result < 0:
op_result = 0
else:
pass
lut[l] = <cnp.uint8_t>op_result
for i from 0 <= i < height:
for j from 0 <= j < width:
img[i,j,k] = lut[stateimg[i,j,k]]
def brightness(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
float factor, Py_ssize_t offset):
"""Modify the brightness of an image.
'factor' is multiplied to all channels, which are
then added by 'amount'. Overflow is clipped.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
factor : float
Multiplication factor.
offset : int
Ammount to add to each channel.
"""
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef float op_result
cdef cnp.uint8_t lut[256]
cdef Py_ssize_t i, j, k
with nogil:
for k from 0 <= k < 256:
op_result = k * factor + offset
if op_result > 255:
op_result = 255
elif op_result < 0:
op_result = 0
else:
pass
lut[k] = <cnp.uint8_t>op_result
for i from 0 <= i < height:
for j from 0 <= j < width:
img[i,j,0] = lut[stateimg[i,j,0]]
img[i,j,1] = lut[stateimg[i,j,1]]
img[i,j,2] = lut[stateimg[i,j,2]]
def sigmoid_gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
float alpha, float beta):
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef Py_ssize_t i, j, k
cdef float c1 = 1 / (1 + exp(beta))
cdef float c2 = 1 / (1 + exp(beta - alpha)) - c1
cdef cnp.uint8_t lut[256]
with nogil:
# compute the lut
for k from 0 <= k < 256:
lut[k] = <cnp.uint8_t>(((1 / (1 + exp(beta - (k / 255.) * alpha)))
- c1) * 255 / c2)
for i from 0 <= i < height:
for j from 0 <= j < width:
img[i,j,0] = lut[stateimg[i,j,0]]
img[i,j,1] = lut[stateimg[i,j,1]]
img[i,j,2] = lut[stateimg[i,j,2]]
def gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
float gamma):
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef cnp.uint8_t lut[256]
cdef Py_ssize_t i, j, k
if gamma == 0:
gamma = 0.00000000000000000001
gamma = 1./gamma
with nogil:
# compute the lut
for k from 0 <= k < 256:
lut[k] = <cnp.uint8_t>((pow((k / 255.), gamma) * 255))
for i from 0 <= i < height:
for j from 0 <= j < width:
img[i,j,0] = lut[stateimg[i,j,0]]
img[i,j,1] = lut[stateimg[i,j,1]]
img[i,j,2] = lut[stateimg[i,j,2]]
cdef void rgb_2_hsv(float* RGB, float* HSV) nogil:
cdef float R, G, B, H, S, V, MAX, MIN
R = RGB[0]
G = RGB[1]
B = RGB[2]
if R > 255:
R = 255
elif R < 0:
R = 0
else:
pass
if G > 255:
G = 255
elif G < 0:
G = 0
else:
pass
if B > 255:
B = 255
elif B < 0:
B = 0
else:
pass
if R < G:
MIN = R
MAX = G
else:
MIN = G
MAX = R
if B < MIN:
MIN = B
elif B > MAX:
MAX = B
else:
pass
V = MAX / 255.
if MAX == MIN:
H = 0.
elif MAX == R:
H = (60 * (G - B) / (MAX - MIN) + 360) % 360
elif MAX == G:
H = 60 * (B - R) / (MAX - MIN) + 120
else:
H = 60 * (R - G) / (MAX - MIN) + 240
if MAX == 0:
S = 0
else:
S = 1 - MIN / MAX
HSV[0] = H
HSV[1] = S
HSV[2] = V
cdef void hsv_2_rgb(float* HSV, float* RGB) nogil:
cdef float H, S, V
cdef float f, p, q, t, r, g, b
cdef Py_ssize_t hi
H = HSV[0]
S = HSV[1]
V = HSV[2]
if H > 360:
H = H % 360
elif H < 0:
H = 360 - ((-1 * H) % 360)
else:
pass
if S > 1:
S = 1
elif S < 0:
S = 0
else:
pass
if V > 1:
V = 1
elif V < 0:
V = 0
else:
pass
hi = (<int>(H / 60.)) % 6
f = (H / 60.) - (<int>(H / 60.))
p = V * (1 - S)
q = V * (1 - f * S)
t = V * (1 - (1 -f) * S)
if hi == 0:
r = V
g = t
b = p
elif hi == 1:
r = q
g = V
b = p
elif hi == 2:
r = p
g = V
b = t
elif hi == 3:
r = p
g = q
b = V
elif hi == 4:
r = t
g = p
b = V
else:
r = V
g = p
b = q
RGB[0] = r
RGB[1] = g
RGB[2] = b
def py_hsv_2_rgb(H, S, V):
'''Convert an HSV value to RGB.
Automatic clipping.
Parameters
----------
H : float
From 0. - 360.
S : float
From 0. - 1.
V : float
From 0. - 1.
Returns
-------
out : (R, G, B) ints
Each from 0 - 255
conversion convention from here:
https://en.wikipedia.org/wiki/HSL_and_HSV
'''
cdef float HSV[3]
cdef float RGB[3]
HSV[0] = H
HSV[1] = S
HSV[2] = V
hsv_2_rgb(HSV, RGB)
R = int(RGB[0] * 255)
G = int(RGB[1] * 255)
B = int(RGB[2] * 255)
return (R, G, B)
def py_rgb_2_hsv(R, G, B):
'''Convert an HSV value to RGB.
Automatic clipping.
Parameters
----------
R : int
From 0. - 255.
G : int
From 0. - 255.
B : int
From 0. - 255.
Returns
-------
out : (H, S, V) floats
Ranges (0...360), (0...1), (0...1)
conversion convention from here:
https://en.wikipedia.org/wiki/HSL_and_HSV
'''
cdef float HSV[3]
cdef float RGB[3]
RGB[0] = R
RGB[1] = G
RGB[2] = B
rgb_2_hsv(RGB, HSV)
H = HSV[0]
S = HSV[1]
V = HSV[2]
return (H, S, V)
def hsv_add(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
float h_amt, float s_amt, float v_amt):
"""Modify the image color by specifying additive HSV Values.
Since the underlying images are RGB, all three values HSV
must be specified at the same time.
The RGB triplet in the image is converted to HSV, the operation
is applied, and then the HSV triplet is converted back to RGB
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
then the operation is performed and any overflow is clipped, then the
reverse transform is performed. Those are the ranges to keep in mind,
when passing in values.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
h_amt : float
Ammount to add to H channel.
s_amt : float
Ammount to add to S channel.
v_amt : float
Ammount to add to V channel.
"""
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef float HSV[3]
cdef float RGB[3]
cdef Py_ssize_t i, j
with nogil:
for i from 0 <= i < height:
for j from 0 <= j < width:
RGB[0] = stateimg[i, j, 0]
RGB[1] = stateimg[i, j, 1]
RGB[2] = stateimg[i, j, 2]
rgb_2_hsv(RGB, HSV)
# Add operation
HSV[0] += h_amt
HSV[1] += s_amt
HSV[2] += v_amt
hsv_2_rgb(HSV, RGB)
RGB[0] *= 255
RGB[1] *= 255
RGB[2] *= 255
img[i, j, 0] = <cnp.uint8_t>RGB[0]
img[i, j, 1] = <cnp.uint8_t>RGB[1]
img[i, j, 2] = <cnp.uint8_t>RGB[2]
def hsv_multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img,
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
float h_amt, float s_amt, float v_amt):
"""Modify the image color by specifying multiplicative HSV Values.
Since the underlying images are RGB, all three values HSV
must be specified at the same time.
The RGB triplet in the image is converted to HSV, the operation
is applied, and then the HSV triplet is converted back to RGB
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
then the operation is performed and any overflow is clipped, then the
reverse transform is performed. Those are the ranges to keep in mind,
when passing in values.
Note that since hue is in degrees, it makes no sense to multiply
that channel, thus an add operation is performed on the hue. And the
values given for h_amt, should be the same as for hsv_add
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
h_amt : float
Ammount to add to H channel.
s_amt : float
Ammount by which to multiply S channel.
v_amt : float
Ammount by which to multiply V channel.
"""
cdef Py_ssize_t height = img.shape[0]
cdef Py_ssize_t width = img.shape[1]
cdef float HSV[3]
cdef float RGB[3]
cdef Py_ssize_t i, j
with nogil:
for i from 0 <= i < height:
for j from 0 <= j < width:
RGB[0] = stateimg[i, j, 0]
RGB[1] = stateimg[i, j, 1]
RGB[2] = stateimg[i, j, 2]
rgb_2_hsv(RGB, HSV)
# Multiply operation
HSV[0] += h_amt
HSV[1] *= s_amt
HSV[2] *= v_amt
hsv_2_rgb(HSV, RGB)
RGB[0] *= 255
RGB[1] *= 255
RGB[2] *= 255
img[i, j, 0] = <cnp.uint8_t>RGB[0]
img[i, j, 1] = <cnp.uint8_t>RGB[1]
img[i, j, 2] = <cnp.uint8_t>RGB[2]