Source code for pyVHR.extraction.sig_extraction_methods
from numba import njit, prange, float32
import math
import numpy as np
from numba import prange, njit
"""
This module defines classes or methods used for signal extraction.
"""
[docs]class SignalProcessingParams():
"""
This class contains usefull parameters used by this module.
RGB_LOW_TH (numpy.int32): RGB low-threshold value.
RGB_HIGH_TH (numpy.int32): RGB high-threshold value.
"""
RGB_LOW_TH = np.int32(55)
RGB_HIGH_TH = np.int32(200)
[docs]@njit(['float32[:,:](uint8[:,:,:], int32, int32)', ], parallel=True, fastmath=True, nogil=True)
def holistic_mean(im, RGB_LOW_TH, RGB_HIGH_TH):
"""
This method computes the RGB-Mean Signal excluding 'im' pixels
that are outside the RGB range [RGB_LOW_TH, RGB_HIGH_TH] (extremes are included).
Args:
im (uint8 ndarray): ndarray with shape [rows, columns, rgb_channels].
RGB_LOW_TH (numpy.int32): RGB low threshold value.
RGB_HIGH_TH (numpy.int32): RGB high threshold value.
Returns:
RGB-Mean Signal as float32 ndarray with shape [1,3], where 1 is the single estimator,
and 3 are r-mean, g-mean and b-mean.
"""
mean = np.zeros((1, 3), dtype=np.float32)
mean_r = np.float32(0.0)
mean_g = np.float32(0.0)
mean_b = np.float32(0.0)
num_elems = np.float32(0.0)
for x in prange(im.shape[0]):
for y in prange(im.shape[1]):
if not((im[x, y, 0] <= RGB_LOW_TH and im[x, y, 1] <= RGB_LOW_TH and im[x, y, 2] <= RGB_LOW_TH)
or (im[x, y, 0] >= RGB_HIGH_TH and im[x, y, 1] >= RGB_HIGH_TH and im[x, y, 2] >= RGB_HIGH_TH)):
mean_r += im[x, y, 0]
mean_g += im[x, y, 1]
mean_b += im[x, y, 2]
num_elems += 1.0
if num_elems > 1.0:
mean[0, 0] = mean_r / num_elems
mean[0, 1] = mean_g / num_elems
mean[0, 2] = mean_b / num_elems
else:
mean[0, 0] = mean_r
mean[0, 1] = mean_g
mean[0, 2] = mean_b
return mean
[docs]@njit(['float32[:,:](float32[:,:],uint8[:,:,:],float32, int32, int32)', ], parallel=True, fastmath=True, nogil=True)
def landmarks_mean(ldmks, im, square, RGB_LOW_TH, RGB_HIGH_TH):
"""
This method computes the RGB-Mean Signal excluding 'im' pixels
that are outside the RGB range [RGB_LOW_TH, RGB_HIGH_TH] (extremes are included).
Args:
ldmks (float32 ndarray): landmakrs as ndarray with shape [num_landmarks, 5],
where the second dimension contains y-coord, x-coord, r-mean (value is not important), g-mean (value is not important), b-mean (value is not important).
im (uint8 ndarray): ndarray with shape [rows, columns, rgb_channels].
square (numpy.float32): side size of square patches.
RGB_LOW_TH (numpy.int32): RGB low threshold value.
RGB_HIGH_TH (numpy.int32): RGB high threshold value.
Returns:
RGB-Mean Signal as float32 ndarray with shape [num_landmarks, 5], where the second dimension contains y-coord, x-coord, r-mean, g-mean, b-mean.
"""
r_ldmks = ldmks.astype(np.float32)
width = im.shape[1]
height = im.shape[0]
S = math.floor(square/2)
lds_mean = np.zeros((ldmks.shape[0], 3), dtype=np.float32)
num_elems = np.zeros((ldmks.shape[0], ), dtype=np.float32)
for ld_id in prange(0, r_ldmks.shape[0]):
if r_ldmks[ld_id, 0] >= 0.0:
for x in prange(int(r_ldmks[ld_id, 0] - S), int(r_ldmks[ld_id, 0] + S + 1)):
for y in prange(int(r_ldmks[ld_id, 1] - S), int(r_ldmks[ld_id, 1] + S + 1)):
if x >= 0 and x < height and y >= 0 and y < width:
if not((im[x, y, 0] <= RGB_LOW_TH and im[x, y, 1] <= RGB_LOW_TH and im[x, y, 2] <= RGB_LOW_TH) or
(im[x, y, 0] >= RGB_HIGH_TH and im[x, y, 1] >= RGB_HIGH_TH and im[x, y, 2] >= RGB_HIGH_TH)):
lds_mean[ld_id, 0] += np.float32(im[x, y, 0])
lds_mean[ld_id, 1] += np.float32(im[x, y, 1])
lds_mean[ld_id, 2] += np.float32(im[x, y, 2])
num_elems[ld_id] += 1.0
if num_elems[ld_id] > 1.0:
r_ldmks[ld_id, 2] = lds_mean[ld_id, 0] / num_elems[ld_id]
r_ldmks[ld_id, 3] = lds_mean[ld_id, 1] / num_elems[ld_id]
r_ldmks[ld_id, 4] = lds_mean[ld_id, 2] / num_elems[ld_id]
return r_ldmks
[docs]@njit(['float32[:,:](float32[:,:],uint8[:,:,:],float32, int32, int32)', ], parallel=True, fastmath=True, nogil=True)
def landmarks_median(ldmks, im, square, RGB_LOW_TH, RGB_HIGH_TH):
"""
This method computes the RGB-Median Signal excluding 'im' pixels
that are outside the RGB range [RGB_LOW_TH, RGB_HIGH_TH] (extremes are included).
Args:
ldmks (float32 ndarray): landmakrs as ndarray with shape [num_landmarks, 5],
where the second dimension contains y-coord, x-coord, r-mean (value is not important), g-mean (value is not important), b-mean (value is not important).
im (uint8 ndarray): ndarray with shape [rows, columns, rgb_channels].
square (numpy.float32): side size of square patches.
RGB_LOW_TH (numpy.int32): RGB low threshold value.
RGB_HIGH_TH (numpy.int32): RGB high threshold value.
Returns:
RGB-Median Signal as float32 ndarray with shape [num_landmarks, 5], where the second dimension contains y-coord, x-coord, r-mean, g-mean, b-mean.
"""
r_ldmks = ldmks.astype(np.float32)
width = float32(im.shape[1])
height = float32(im.shape[0])
S = math.floor(square/2.0)
for ld_id in prange(r_ldmks.shape[0]):
if r_ldmks[ld_id, 0] >= 0.0:
x_s = r_ldmks[ld_id, 0] - S
if x_s < 0.0:
x_s = 0
x_e = r_ldmks[ld_id, 0] + S
if x_e >= height:
x_e = height
y_s = r_ldmks[ld_id, 1] - S
if y_s < 0.0:
y_s = 0
y_e = r_ldmks[ld_id, 1] + S
if y_e >= width:
y_e = width
ar = np.copy(im[int(x_s):int(x_e), int(y_s):int(y_e), :])
f_ar = ar.flatten()
r = ar[:, :, 0].flatten()
g = ar[:, :, 1].flatten()
b = ar[:, :, 2].flatten()
goodidx = np.ones((ar.shape[0]*ar.shape[1],), dtype=np.int32)
targets = np.arange(3, f_ar.shape[0], 3)
for idx in prange(targets.shape[0]):
i = targets[idx]
if ((f_ar[i-2] <= RGB_LOW_TH and f_ar[i-1] <= RGB_LOW_TH and f_ar[i] <= RGB_LOW_TH) or
(f_ar[i-2] >= RGB_HIGH_TH and f_ar[i-1] >= RGB_HIGH_TH and f_ar[i] >= RGB_HIGH_TH)):
goodidx[i % 3] = 0
goodidx = np.argwhere(goodidx).flatten()
if goodidx.size < 1 or r.size < 1:
r_ldmks[ld_id, 2] = np.float32(0.0)
r_ldmks[ld_id, 3] = np.float32(0.0)
r_ldmks[ld_id, 4] = np.float32(0.0)
else:
r_ldmks[ld_id, 2] = np.float32(np.median(r[goodidx]))
r_ldmks[ld_id, 3] = np.float32(np.median(g[goodidx]))
r_ldmks[ld_id, 4] = np.float32(np.median(b[goodidx]))
return r_ldmks
[docs]@njit(['float32[:,:](float32[:,:],uint8[:,:,:],float32[:,:], int32, int32)', ], parallel=True, fastmath=True, nogil=True)
def landmarks_mean_custom_rect(ldmks, im, rects, RGB_LOW_TH, RGB_HIGH_TH):
"""
This method computes the RGB-Mean Signal excluding 'im' pixels
that are outside the RGB range [RGB_LOW_TH, RGB_HIGH_TH] (extremes are included).
Args:
ldmks (float32 ndarray): landmakrs as ndarray with shape [num_landmarks, 5],
where the second dimension contains y-coord, x-coord, r-mean (value is not important), g-mean (value is not important), b-mean (value is not important).
im (uint8 ndarray): ndarray with shape [rows, columns, rgb_channels].
rects (float32 ndarray): positive float32 np.ndarray of shape [num_landmarks, 2]. If the list of used landmarks is [1,2,3]
and rects_dim is [[10,20],[12,13],[40,40]] then the landmark number 2 will have a rectangular patch of xy-dimension 12x13.
RGB_LOW_TH (numpy.int32): RGB low threshold value.
RGB_HIGH_TH (numpy.int32): RGB high threshold value.
Returns:
RGB-Mean Signal as float32 ndarray with shape [num_landmarks, 5], where the second dimension contains y-coord, x-coord, r-mean, g-mean, b-mean.
"""
r_ldmks = ldmks.astype(np.float32)
width = im.shape[1]
height = im.shape[0]
lds_mean = np.zeros((ldmks.shape[0], 3), dtype=np.float32)
num_elems = np.zeros((ldmks.shape[0], ), dtype=np.float32)
for ld_id in prange(0, r_ldmks.shape[0]):
if r_ldmks[ld_id, 0] >= 0:
Sx = math.floor(rects[ld_id, 1]/2)
Sy = math.floor(rects[ld_id, 0]/2)
for x in prange(r_ldmks[ld_id, 0] - Sx, r_ldmks[ld_id, 0] + Sx + 1):
for y in prange(r_ldmks[ld_id, 1] - Sy, r_ldmks[ld_id, 1] + Sy + 1):
if x >= 0 and x < height and y >= 0 and y < width:
if not((im[x, y, 0] <= RGB_LOW_TH and im[x, y, 1] <= RGB_LOW_TH and im[x, y, 2] <= RGB_LOW_TH) or
(im[x, y, 0] >= RGB_HIGH_TH and im[x, y, 1] >= RGB_HIGH_TH and im[x, y, 2] >= RGB_HIGH_TH)):
lds_mean[ld_id, 0] += im[x, y, 0]
lds_mean[ld_id, 1] += im[x, y, 1]
lds_mean[ld_id, 2] += im[x, y, 2]
num_elems[ld_id] += 1.0
if num_elems[ld_id] > 1.0:
r_ldmks[ld_id, 2] = lds_mean[ld_id, 0] / num_elems[ld_id]
r_ldmks[ld_id, 3] = lds_mean[ld_id, 1] / num_elems[ld_id]
r_ldmks[ld_id, 4] = lds_mean[ld_id, 2] / num_elems[ld_id]
return r_ldmks
[docs]@njit(['float32[:,:](float32[:,:],uint8[:,:,:],float32[:,:], int32, int32)', ], parallel=True, fastmath=True, nogil=True)
def landmarks_median_custom_rect(ldmks, im, rects, RGB_LOW_TH, RGB_HIGH_TH):
"""
This method computes the RGB-Median Signal excluding 'im' pixels
that are outside the RGB range [RGB_LOW_TH, RGB_HIGH_TH] (extremes are included).
Args:
ldmks (float32 ndarray): landmakrs as ndarray with shape [num_landmarks, 5],
where the second dimension contains y-coord, x-coord, r-mean (value is not important), g-mean (value is not important), b-mean (value is not important).
im (uint8 ndarray): ndarray with shape [rows, columns, rgb_channels].
rects (float32 ndarray): positive float32 np.ndarray of shape [num_landmarks, 2]. If the list of used landmarks is [1,2,3]
and rects_dim is [[10,20],[12,13],[40,40]] then the landmark number 2 will have a rectangular patch of xy-dimension 12x13.
RGB_LOW_TH (numpy.int32): RGB low threshold value.
RGB_HIGH_TH (numpy.int32): RGB high threshold value.
Returns:
RGB-Median Signal as float32 ndarray with shape [num_landmarks, 5], where the second dimension contains y-coord, x-coord, r-mean, g-mean, b-mean.
"""
r_ldmks = ldmks.astype(np.float32)
width = float32(im.shape[1])
height = float32(im.shape[0])
for ld_id in prange(ldmks.shape[0]):
if r_ldmks[ld_id, 0] >= 0.0:
Sx = math.floor(rects[ld_id, 1]/2)
Sy = math.floor(rects[ld_id, 0]/2)
x_s = r_ldmks[ld_id, 0] - Sx
if x_s < 0.0:
x_s = 0
x_e = r_ldmks[ld_id, 0] + Sx
if x_e >= height:
x_e = height
y_s = r_ldmks[ld_id, 1] - Sy
if y_s < 0.0:
y_s = 0
y_e = r_ldmks[ld_id, 1] + Sy
if y_e >= width:
y_e = width
ar = np.copy(im[int(x_s):int(x_e), int(y_s):int(y_e), :])
f_ar = ar.flatten()
r = ar[:, :, 0].flatten()
g = ar[:, :, 1].flatten()
b = ar[:, :, 2].flatten()
goodidx = np.ones((ar.shape[0]*ar.shape[1],), dtype=np.int32)
targets = np.arange(3, f_ar.shape[0], 3)
for idx in prange(targets.shape[0]):
i = targets[idx]
if ((f_ar[i-2] <= RGB_LOW_TH and f_ar[i-1] <= RGB_LOW_TH and f_ar[i] <= RGB_LOW_TH) or
(f_ar[i-2] >= RGB_HIGH_TH and f_ar[i-1] >= RGB_HIGH_TH and f_ar[i] >= RGB_HIGH_TH)):
goodidx[i % 3] = 0
goodidx = np.argwhere(goodidx).flatten()
if goodidx.size < 1 or r.size < 1:
r_ldmks[ld_id, 2] = np.int32(0)
r_ldmks[ld_id, 3] = np.int32(0)
r_ldmks[ld_id, 4] = np.int32(0)
else:
r_ldmks[ld_id, 2] = np.int32(np.median(r[goodidx]))
r_ldmks[ld_id, 3] = np.int32(np.median(g[goodidx]))
r_ldmks[ld_id, 4] = np.int32(np.median(b[goodidx]))
return r_ldmks