import numpy as np
from scipy import interpolate
from scipy.ndimage import convolve
from scipy.interpolate import CubicSpline
from .geometryFunctions import *
from .hausdorffDistanceCalc import hausdorffDistanceFor2Dpoints
[docs]class ActiveContour:
"""Class that represents the snake, the contour that deforms until reaching a state of equilibrium through an
optimization process. This class contains the methods needed to do that.
Methods
-------
adjustContour(perimeter_factor=None, f_close=None, plot_contour=None, fix_point_count=None,
fix_point_indices=None, f_keep_point_count=None, f_compute_convergence=None,
convergence_thresh=None, convergence_metric_type=None,
convergence_metric_value=None)
Runs the GGVF Active Contour code to completion. It deforms iteratively the initial contour until it
reaches a sate of equilibrium.
"""
# TODO: Aqui recibimos los parametros para inicializar el algoritmo
def __init__(self, image: list, x_coords: list = None, y_coords: list = None, alpha: float = 0.1, beta: float = 0.25,
gamma: float = 1.0, kappa: float = 0.0, mu: float = 0.1, vf_iterations: int = 100,
contour_iterations: int = 200) -> None:
"""Initializes ActiveContour with the parameters given by the user, or
the default parameter values if not given.
Parameters
----------
image : list
Image file for which the active contour (snake) will be applied.
This argument must be 2D.
x_coords : list
The initial X points of the active contour of the snake. Optional.
Must be used with Y.
y_coords : list
The initial Y points of the active contour of the snake. Optional.
Must be used with X.
alpha : float
The elasticity parameter of the active contour. It reflects the
contour's ability to stretch along its length. Default: 0.10.
beta : float
The rigidity parameter of the active contour. It reflects the
contour's ability to bend, as, for example, around corners.
Default: 0.25.
gamma : float
The viscosity parameter. Larger values make it harder to deform
the active contour in space. Default: 1.0.
kappa : float
The external force weight. Default: 1.25.
mu : float
The regularization parameter. this should be set according
to the amount of noise in the image.
Use a larger value for noisier images. Default: 0.10.
vf_iterations : int
The number of iterations for calculation the Gradient Vector
Flow (GVF). Default: 100.
contour_iterations : int
The number of iterations to use in calculating the snake
positions. Default: 200.
"""
#para asegurar tipo de arrays, poner el tipo (d_type=float64? o double)
self.image = np.array(image, dtype=np.float64)
self.x = x_coords
self.y = y_coords
self.alpha = max(alpha, 0.001)
self.beta = max(beta, 0.001)
self.gamma = max(gamma, 0.1)
self.kappa = max(kappa, 0.0)
self.mu = max(min(mu, 0.25), 0.001)
self.vf_iterations = max(int(vf_iterations), 1)
self.contour_iterations = max(int(contour_iterations), 1)
# pU (edgeMap)
# pV (edgeMap)
self.u = None
self.v = None
# Manejar errores
# Revisa que se haya entregado una imagen
# Chequea que sea un array 2-dimensional
# Puntero a la imagen
# Puntero a coordenadas X e Y
# Calcular npts igual a largo de X o 0 si es None/si es invalido
self.npts = len(self.x) if self.x is not None else 0
pass
[docs] def get_x_coords(self):
"""It returns the array of coordinates x of the contour.
Returns
-------
np.ndarray
X coordinates of the contour.
"""
try:
if len(self.x) <= 0:
return -1
self.x = np.array(self.x, dtype=np.float64)
return self.x
except ValueError:
return -1
[docs] def get_y_coords(self):
"""It returns the array of coordinates y of the contour.
Returns
-------
np.ndarray
Y coordinates of the contour.
"""
try:
if len(self.y) <= 0:
return -1
self.y = np.array(self.y, dtype=np.float64)
return self.y
except ValueError:
return -1
[docs] def get_GGVF(self):
"""It returns the values of the GGVF, with its coordinates u and v.
Returns
-------
np.ndarray
It returns an array with the value of the GGVF.
"""
try:
if len(self.u) <= 0:
return -1
if len(self.v) <= 0:
return -1
self.u = np.array(self.u, dtype=np.float64)
self.v = np.array(self.v, dtype=np.float64)
return np.array([self.u, self.v], dtype=np.float64)
except ValueError:
return -1
[docs] def laplacian(self, image: np.ndarray) -> np.ndarray:
"""Computes the laplacian of an image.
Parameters
----------
image : np.ndarray
Image to apply the laplacian.
Returns
-------
np.ndarray
Returns the laplacian of the image, computing the convolution of it with a kernel.
"""
kernel = np.zeros((5, 5))
kernel[0, 2] = 0.0833333
kernel[1, 1:4] = 0.0833333
kernel[2, :] = 0.0833333
kernel[3, 1:4] = 0.0833333
kernel[4, 2] = 0.0833333
kernel[2, 2] = -1.0
# return convol(image, kernel, center=1, /edge_truncate)
return convolve(image, kernel, mode='nearest')
# TODO: Computar el campo GGVF para el contorno activo
[docs] def calcGGVF(self) -> None:
"""It computes the GGVF, and sets the values of self.u and self.v.
"""
# Calculate gradients [fx, fy] to initialize the vector field [u, v].
self.edgeMap()
# Original version for the GGVF by Xu99
# b = np.square(self.u) + np.square(self.v)
b = np.abs(self.u) + np.abs(self.v)
# This pair of functions act as an "enhancer/de-enhancer" of high gradient neighbors the choice of the functions
# must satisfy some convergence restrictions (see reference) TODO: agregar referencia
g = np.exp(-b / self.mu)
c1 = self.u * (np.ones(g.shape) - g)
c2 = self.v * (np.ones(g.shape) - g)
# Solve iteratively for the GGVF [u, v]
# delta_x = delta_y = delta_t = 1
for _ in range(1, self.vf_iterations + 1):
u_lap = self.laplacian(self.u)
v_lap = self.laplacian(self.v)
# Original iteration scheme
# self.u += g * u_lap - h * (self.u - fx)
# self.v += g * v_lap - h * (self.v - fy)
# Optimized iteration scheme
self.u = g * (self.u + u_lap) + c1
self.v = g * (self.v + v_lap) + c2
return
# TODO: Plotear el campo GVF
[docs] def plotGVF(self) -> None:
#revisar plt.streamploat
return
[docs] def getCoords(self, xyRes = np.array([1.,1.], dtype=np.float64)) -> np.ndarray:
"""It returns the coordinates x and y of the image.
Parameters
----------
xyRes : np.ndarray of floats, optional
Resolution of the image, by default np.array([1.,1.])
Returns
-------
np.ndarray
Coordinates x and y of the image
note:: in case xCoords is an invalid value, it returns -1.
"""
self.x = np.array(self.x)
self.y = np.array(self.y)
return np.array([xyRes[0] * self.x, xyRes[1] * self.y], dtype=np.float64)
[docs] def setContour(self, x: list, y: list) -> None:
"""Set the [x, y] coordinates for the active contour.
Parameters
----------
x : list
`x` coordinate array of the contour to be set.
y : list
`y` coordinate array of the contour to be set.
"""
self.x = np.array(x, dtype=np.float64)
self.y = np.array(y, dtype=np.float64)
self.npts = len(self.x)
return
[docs] def getPerimeter(self,xyRes = np.array([1.,1.], dtype=np.float64)) -> float:
"""This method calculates the perimeter of a contour.
Parameters:
-----------
xyRes: np.ndarray of floats, optional.
Resolution of the image. Default: np.array([1,1]).
Returns:
-------
float
Value of the perimeter of a contour.
note:: in case xCoords is an invalid value, it returns -1.
"""
p = self.getDistance(xyRes)
return np.sum(p)
[docs] def getDistance(self, xyRes = np.array([1.,1.], dtype=np.float64)) -> np.ndarray:
"""This method calculates the distance between consecutive points.
Parameters:
-----------
xyRes: np.ndarray of floats, optional.
Resolution of the image, by default np.array([1,1]).
Returns:
-------
np.ndarray
Array of floats with the euclidean distance between the consecutive points of a segment.
note:: in case xCoords is an invalid value, it returns -1.
"""
dx = np.square((np.roll(self.x,-1)-self.x)*xyRes[0])
dy = np.square((np.roll(self.y,-1)-self.y)*xyRes[1])
return np.power(dx + dy, 0.5)
[docs] def arcSample(self, points = 50, f_close = None) -> None:
"""It takes a closed curve and re-samples it in equal arc lengths.
Parameters
----------
points : int, optional
The number of points in the output vectors, by default 50.
f_close : bool, optional
Set this keyword to True to specify the contour curve, by default None.
"""
#if size(*self.pX,/n_dimensions) eq 2 then begin, x_in = reform(*self.pX) ... ya lo hace python
x_in = np.copy(self.x)
y_in = np.copy(self.y)
npts = len(x_in)
#Make sure the curve is closed (first point same as last point).
if bool(f_close):
if (x_in[0] != x_in[npts - 1]) or (y_in[0] != y_in[npts - 1]):
x_in = np.concatenate((x_in, np.array([x_in[0]], dtype=np.float64)))
y_in = np.concatenate((y_in, np.array([y_in[0]], dtype=np.float64)))
# print, "Active contour interpolation warning: adding 1 point to close the contour,
# according to the specified input"
npts += 1
else:
points -= 1
#Interpolate very finely
nc = (npts - 1) * 100
t = np.arange(npts, dtype=np.float64)
t1 = np.arange(nc + 1, dtype=np.float64) / 100
csx = CubicSpline(t, x_in)
x1 = csx(t1)
csy = CubicSpline(t, y_in)
y1 = csy(t1)
if bool(f_close):
#computes the boundary condition for the cubic spline: derivatives at the beggining and end points are the same
avg_slopeX = (x1[1] - x1[0] + x1[nc] - x1[nc - 1]) / (t1[1] - t1[0]) * 0.5
avg_slopeY = (y1[1] - y1[0] + y1[nc] - y1[nc - 1]) / (t1[1] - t1[0]) * 0.5
dx1 = CubicSpline(t, x_in, bc_type = ((1, avg_slopeX), (1, avg_slopeX)))
dy1 = CubicSpline(t, y_in, bc_type = ((1, avg_slopeY), (1, avg_slopeY)))
else:
#computes the boundary condition for the cubic spline: derivatives at the beggining and end points
avg_slopeX0 = (x1[1] - x1[0]) / (t1[1] - t1[0])
avg_slopeX1 = (x1[nc] - x1[nc - 1]) / (t1[nc] - t1[nc - 1])
avg_slopeY0 = (y1[1] - y1[0]) / (t1[1] - t1[0])
avg_slopeY1 = (y1[nc] - y1[nc - 1]) / (t1[nc] - t1[nc - 1])
dx1 = CubicSpline(t, x_in, bc_type = ((1, avg_slopeX0), (1, avg_slopeX1)))
dy1 = CubicSpline(t, y_in, bc_type = ((1, avg_slopeY0), (1, avg_slopeY1)))
x1 = dx1(t1)
y1 = dy1(t1)
#compute cumulative path length.
ds = np.sqrt(np.square((x1[1:] - x1[:len(x1)-1])) + np.square((y1[1:] - y1[:len(y1)-1])))
ss = np.concatenate((np.array([0], dtype=np.float64), np.cumsum(ds, dtype=np.float64)), axis = None, dtype=np.float64)
#Invert this curve, solve for TX, which should be evenly sampled in the arc length space.
sx = np.arange(points) * (ss[nc] / points)
cstx = CubicSpline(ss, t1)
tx = cstx(sx)
#Reinterpolate the original points using the new values of TX and optionally close the contour.
if bool(f_close):
x_out = dx1(tx)
y_out = dy1(tx)
self.x = np.concatenate((x_out, np.array([x_out[0]], dtype=np.float64)), axis = None)
self.y = np.concatenate((y_out, np.array([y_out[0]], dtype=np.float64)), axis = None)
else:
x_out = dx1(tx)
y_out = dy1(tx)
self.x = np.concatenate((x_out, np.array([x_in[npts - 1]], dtype=np.float64)), axis = None)
self.y = np.concatenate((y_out, np.array([y_in[npts - 1]], dtype=np.float64)), axis = None)
self.npts = len(self.x)
[docs] def adjustContour(self, perimeter_factor=None, f_close=None, plot_contour=None, fix_point_count=None,
fix_point_indices=None, f_keep_point_count=None, f_compute_convergence=None,
convergence_thresh=None, convergence_metric_type=None,
convergence_metric_value=None) -> np.ndarray:
"""Runs the GGVF Active Contour code to completion. It deforms iteratively the initial contour until it
reaches a sate of equilibrium.
Parameters
----------
perimeter_factor: float, optional
It indicates the factor to compute the number of points to use for a given contour, by default None.
f_close: bool, optional
Flag to indicate if a contour must be closed, adding an extra point, by default None.
plot_contour: int, optional
Flag to indicate if a contour must be closed, adding an extra point, by default None.
fix_point_count: int, optional
Number of points to fix in the contour, by default None.
fix_point_indices: np.ndarray, optional
Array of indices of the points to be fixed.
f_keep_point_count: bool, optional
Flag to indicate that the number of points in the contour must be kept, and if False, it re-interpolates them.
f_compute_convergence: bool, optional
Flaf to indicate that the convergence must be computed.
convergence_thresh: float, optional
Value of the convergence limit.
convergence_metric_type: string, optional
It indicates the type of metric to compute the variation between the contour in the current
iteration and the last iteration, until reaching convergence.
convergence_metric_value: float, optional
It indicates that the convergence must be computed, in case the flags f_compute_convergence or
f_use_convergence_threshold where not set.
Returns
-------
np.ndarray
It returns an array with the coordinates x and y of the contour after the adjustment, when the state of
equilibrium is reached.
"""
if plot_contour is None: plot_contour = 0
# checkear si x e y son validos sino return -1
if not isinstance(self.get_x_coords(), np.ndarray):
return -1
if not isinstance(self.get_y_coords(), np.ndarray):
return -1
try:
if len(fix_point_indices) > 0:
fix_point_count = len(fix_point_indices)
elif len(fix_point_count) > 0: # TODO: Esto deberia chequear si se fijo la variable fix_point_count
fix_point_count = max(fix_point_count, 0)
else:
fix_point_count = 0
except TypeError:
fix_point_count = 0
try:
if fix_point_count == 0:
npts_iter = max(round(polygon_perimeter(self.x, self.y) * max(perimeter_factor, 0.1)), 5)
else:
npts_iter = self.npts
# En caso de que perimeter_factor no este definido
except NameError:
print("perimeter_factor no esta definido")
npts_iter = self.npts
# En caso de que perimeter_factor este definido pero tenga el tipo
# incorrecto
except TypeError:
npts_iter = self.npts
if npts_iter != self.npts and not bool(f_keep_point_count):
self.arcSample(points=npts_iter, f_close=f_close)
# self.x, self.y = polygon_line_sample(np.copy(self.x), np.copy(self.y), n_points_per_pix = npts_iter)
perimeter_it_0 = polygon_perimeter(self.x, self.y)
alpha = np.full(npts_iter, self.alpha)
beta = np.full(npts_iter, self.beta)
a = beta
b = -alpha - 4*beta
c = 2*alpha + 6*beta
vfx = 0.0
vfy = 0.0
abc_matrix = np.diag(a[0:npts_iter-2], 2) + np.diag(a[npts_iter-2:npts_iter], -(npts_iter-2)) \
+ np.diag(b[0:npts_iter-1], 1) + np.diag([b[npts_iter-1]], -(npts_iter-1)) \
+ np.diag(c + self.gamma) \
+ np.diag(b[0:npts_iter-1], -1) + np.diag([b[npts_iter-1]], (npts_iter-1)) \
+ np.diag(a[0:npts_iter-2], -2) + np.diag(a[npts_iter-2:npts_iter], (npts_iter-2))
inv_array = np.linalg.inv(abc_matrix)
f_use_convergence_threshold = bool(convergence_thresh) or (convergence_thresh == 0)
f_compute_convergence = bool(f_compute_convergence) \
or bool(convergence_metric_value) \
or f_use_convergence_threshold
if f_compute_convergence:
if bool(convergence_metric_type):
var_metric = convergence_metric_type
else:
var_metric = 'LinfNorm'
if self.contour_iterations >= 1:
for j in range(self.contour_iterations):
if f_compute_convergence:
last_iter_x = np.copy(self.x)
last_iter_y = np.copy(self.y)
if self.kappa > 0:
points = (np.arange(self.image.shape[0]), np.arange(self.image.shape[1]))
xi = np.transpose(np.vstack((self.x, self.y)))
vfx = interpolate.interpn(points, self.v, xi, method='cubic')
vfy = interpolate.interpn(points, self.u, xi, method='cubic')
n_elem_inv_array = inv_array.shape[0]
n_elem_contour = len(self.x)
if n_elem_inv_array != n_elem_contour:
npts_iter = n_elem_contour
alpha = np.full(npts_iter, self.alpha)
beta = np.full(npts_iter, self.beta)
a = beta
b = -alpha - 4*beta
c = 2*alpha + 6*beta
abc_matrix = np.diag(a[0:npts_iter-2], 2) + np.diag(a[npts_iter-2:npts_iter], -(npts_iter-2)) \
+ np.diag(b[0:npts_iter-1], 1) + np.diag([b[npts_iter-1]], -(npts_iter-1)) \
+ np.diag(c + self.gamma) \
+ np.diag(b[0:npts_iter-1], -1) + np.diag([b[npts_iter-1]], (npts_iter-1)) \
+ np.diag(a[0:npts_iter-2], -2) + np.diag(a[npts_iter-2:npts_iter], (npts_iter-2))
inv_array = np.linalg.inv(abc_matrix)
# Deform the snake.
if (fix_point_count > 0) and not bool(f_close):
x_tmp = np.matmul(inv_array, (self.gamma * self.x + self.kappa * vfx))
y_tmp = np.matmul(inv_array, (self.gamma * self.y + self.kappa * vfy))
if len(fix_point_indices) > 0:
x_tmp[fix_point_indices] = self.x[fix_point_indices]
y_tmp[fix_point_indices] = self.y[fix_point_indices]
if f_compute_convergence:
x_delta = np.abs(x_tmp - self.x)
y_delta = np.abs(y_tmp - self.y)
# Re-interpolate the snake points.
if bool(perimeter_factor):
poly_line_length = 0.0
for k in range(len(x_tmp) - 1):
poly_line_length += np.sqrt(np.square(x_tmp[k+1] - x_tmp[k])
+ np.square(y_tmp[k+1] - y_tmp[k]))
npts_iter = max((round(poly_line_length) * max(perimeter_factor, 0.1)), 5)
if not bool(f_keep_point_count):
self.arcSample(points=npts_iter)
# self.x, self.y = polygon_line_sample(np.copy(self.x), np.copy(self.y), n_points_per_pix = npts_iter)
self.x = x_tmp
self.y = y_tmp
else:
x_fix_vec_1 = self.x[0 : fix_point_count]
x_fix_vec_2 = self.x[npts_iter - fix_point_count :]
y_fix_vec_1 = self.y[0 : fix_point_count]
y_fix_vec_2 = self.y[npts_iter - fix_point_count :]
x_tmp_2 = np.concatenate((x_fix_vec_1, x_tmp[1 : n_elem_contour - fix_point_count + 1], x_fix_vec_2))
y_tmp_2 = np.concatenate((y_fix_vec_1, y_tmp[1 : n_elem_contour - fix_point_count + 1], y_fix_vec_2))
if f_compute_convergence:
x_delta = np.abs(x_tmp_2 - self.x)
y_delta = np.abs(y_tmp_2 - self.y)
# Re-interpolate the snake points.
if bool(perimeter_factor):
poly_line_length = 0.0
for k in range(len(self.x) - 1):
poly_line_length += np.sqrt(np.square(x_tmp_2[k+1] - x_tmp_2[k])
+ np.square(y_tmp_2[k+1] - y_tmp_2[k]))
npts_iter = max((round(poly_line_length) * max(perimeter_factor, 0.1)), 5)
if not bool(f_keep_point_count):
self.arcSample(points=npts_iter)
# self.x, self.y = polygon_line_sample(np.copy(self.x), np.copy(self.y), n_points_per_pix = npts_iter)
# Put back the fixed points
x_tmp_2[0:fix_point_count] = x_fix_vec_1
x_tmp_2[npts_iter - fix_point_count + 1 :] = x_fix_vec_2
y_tmp_2[0:fix_point_count] = y_fix_vec_1
y_tmp_2[npts_iter - fix_point_count + 1 :] = y_fix_vec_2
self.x = x_tmp_2
self.y = y_tmp_2
else: # Non-fixed points, i.e. all the contour pointscan be displaced
if f_compute_convergence:
x_tmp_3 = np.matmul(inv_array, (self.gamma * self.x + self.kappa * vfx))
y_tmp_3 = np.matmul(inv_array, (self.gamma * self.y + self.kappa * vfy))
x_delta = np.abs(x_tmp_3 - self.x)
y_delta = np.abs(y_tmp_3 - self.y)
self.x = x_tmp_3
self.y = y_tmp_3
else:
self.x = np.matmul(inv_array, (self.gamma * self.x + self.kappa * vfx))
self.y = np.matmul(inv_array, (self.gamma * self.y + self.kappa * vfy))
# Re-interpolate the snake points.
if bool(perimeter_factor):
npts_iter = max((round(polygon_perimeter(self.x, self.y) * max(perimeter_factor, 0.1))), 5)
f_close = 1
if not bool(f_keep_point_count):
# self.x, self.y = polygon_line_sample(np.copy(self.x), np.copy(self.y), n_points_per_pix = (npts_iter-1 if bool(f_close) else npts_iter), f_close_output=f_close)
self.arcSample(points=((npts_iter-1) if f_close else npts_iter), f_close=f_close)
if plot_contour > 0:
if j == 1: pass # TODO: oPlot, [*self.pX, (*self.pX)[0]], [*self.pY, (*self.pY)[0]], color = 255, linestyle = 1, thick = 3
elif j == self.contour_iterations: pass # TODO: oPlot, [*self.pX, (*self.pX)[0]], [*self.pY, (*self.pY)[0]], color = 255, thick = 3
else: pass # TODO: oPlot, [*self.pX, (*self.pX)[0]], [*self.pY, (*self.pY)[0]], color = (255 - (self.contour_iterations - j) * 30) > 100
if f_compute_convergence:
delta_mag = np.sqrt(np.square(x_delta) + np.square(y_delta))
if var_metric == 'Hausdorff': variation = hausdorffDistanceFor2Dpoints(self.x, self.y, last_iter_x, last_iter_y)
elif var_metric == 'L1norm' : variation = calcNorm_L1ForVector(delta_mag)
elif var_metric == 'L2norm' : variation = calcNorm_L2ForVector(delta_mag)
elif var_metric == 'LinfNorm' : variation = calcNorm_LInfiniteForVector(delta_mag)
elif var_metric == 'average' : variation = np.mean(delta_mag)
elif var_metric == 'avgFracPerimeter': variation = np.mean(delta_mag) / polygon_perimeter(self.x, self.y)
elif var_metric == 'avgFracPerimeter0': variation = np.mean(delta_mag) / perimeter_it_0
else: variation = calcNorm_LInfiniteForVector(delta_mag)
f_log = 0
f_log_all = 0
if f_log:
log_file_path = 'D:\\tmp\\snakeLog.txt'
msg = f"{var_metric} convergence criterion value = {variation} at iteration {j}"
if f_log_all:
hd = hausdorffDistanceFor2Dpoints(self.x, self.y, last_iter_x, last_iter_y)
l1 = calcNorm_L1ForVector(delta_mag)
l2 = calcNorm_L2ForVector(delta_mag)
li = calcNorm_LInfiniteForVector(delta_mag)
avg = np.mean(delta_mag)
avg_norm_perim_it = np.mean(delta_mag) / polygon_perimeter(self.x, self.y)
avg_norm_perim_0 = np.mean(delta_mag) / perimeter_it_0
msg = ";".join(list(map(str, [hd, l1, l2, li, avg, avg_norm_perim_it, avg_norm_perim_0, j])))
# TODO: file_logger(msg, log_file_path)
if f_use_convergence_threshold:
if variation <= convergence_thresh:
break
if f_compute_convergence:
if f_log_all:
print('Hausdorff', ' L1norm', ' L2norm', ' LinfNorm', ' average', ' avgFracPerimeter', ' avgFracPerimeter0', ' Iteration')
elif f_log:
print(msg)
convergence_metric_value = variation
return np.array([self.x, self.y], dtype=np.float64)
# TODO: este metodo puede ser estatico
[docs] def gradient(self, image: np.ndarray, direction: int) -> np.ndarray:
"""Computes the gradient of the image's intensity.
Parameters
----------
image : np.ndarray
Array with the intensity values per pixel of the image.
direction : int
It indicates the direction in which to calculate the gradient.
Returns
-------
np.ndarray
Returns the result of computing the gradient of the image.
"""
# TODO: si la direccion admite vectores entonces
# Revisa la cantidad de elementos de la direccion
# if n_elements(direction) == 0 -> direction = 0
# Chequeo de la dimension de la imagen
# Si la dimension no es 2 retornar -1
# IDL: shift
# Python: np.roll. Reference: https://numpy.org/doc/stable/reference/generated/numpy.roll.html
# np matrix accessors reference:
# https://stackoverflow.com/questions/4455076/how-do-i-access-the-ith-column-of-a-numpy-multidimensional-array
if direction == 0:
theGradient = (np.roll(image, -1, axis=1) - np.roll(image, 1, axis=1)) * 0.5
theGradient[:, 0] = theGradient[:, 1]
theGradient[:, theGradient.shape[1] - 1] = theGradient[:, theGradient.shape[1] - 2]
elif direction == 1:
theGradient = (np.roll(image, -1, axis=0) - np.roll(image, 1, axis=0)) * 0.5
theGradient[0, :] = theGradient[1, :]
theGradient[theGradient.shape[0] - 1, :] = theGradient[theGradient.shape[0] - 2, :]
else:
return -1 # Reemplazar este valor por algo mas indicativo. Una excepcion si solo se recibe direccion {0, 1}
return np.array(theGradient, dtype=np.float64)
[docs] def edgeMap(self) -> None:
"""Computes the edge map of a given image, and sets the values of the coordinates u and v of the GGVF.
"""
edge_map = np.sqrt(np.square(self.gradient(self.image, 0)) + np.square(self.gradient(self.image, 1)))
min_val = np.min(edge_map) # TODO: este valor por defecto podia ser 0, hablar con Jorge
max_val = np.max(edge_map)
if max_val != min_val:
edge_map = np.array([(i - min_val)/(max_val - min_val) for i in edge_map], dtype=np.float64)
self.u = self.gradient(edge_map, 0)
self.v = self.gradient(edge_map, 1)