|
import numpy as np |
|
import time |
|
import sklearn.datasets |
|
import skimage.transform |
|
import scipy.stats |
|
|
|
from . import python_utils |
|
from . import image_utils |
|
|
|
|
|
|
|
|
|
CV2 = False |
|
if python_utils.module_exists("cv2"): |
|
import cv2 |
|
|
|
CV2 = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AverageMeter(object): |
|
"""Computes and stores the average and current value""" |
|
|
|
def __init__(self, name="", init_val=0, fmt=':f'): |
|
self.name = name |
|
self.init_val = init_val |
|
self.fmt = fmt |
|
self.val = self.avg = self.init_val |
|
self.sum = self.count = 0 |
|
|
|
def reset(self): |
|
self.val = self.avg = self.init_val |
|
self.sum = self.count = 0 |
|
|
|
def update(self, val, n=1): |
|
self.val = val |
|
self.sum += val * n |
|
self.count += n |
|
self.avg = self.sum / self.count |
|
|
|
def get_avg(self): |
|
return self.avg |
|
|
|
def __str__(self): |
|
fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})' |
|
return fmtstr.format(**self.__dict__) |
|
|
|
|
|
class RunningDecayingAverage(object): |
|
""" |
|
Updates average with val*(1 - decay) + avg*decay |
|
""" |
|
def __init__(self, decay, init_val=0): |
|
assert 0 < decay < 1 |
|
self.decay = decay |
|
self.init_val = init_val |
|
self.val = self.avg = self.init_val |
|
|
|
def reset(self): |
|
self.val = self.avg = self.init_val |
|
|
|
def update(self, val): |
|
self.val = val |
|
self.avg = (1 - self.decay)*val + self.decay*self.avg |
|
|
|
def get_avg(self): |
|
return self.avg |
|
|
|
|
|
class DispFieldMapsPatchCreator: |
|
def __init__(self, global_shape, patch_res, map_count, modes, gauss_mu_range, gauss_sig_scaling): |
|
self.global_shape = global_shape |
|
self.patch_res = patch_res |
|
self.map_count = map_count |
|
self.modes = modes |
|
self.gauss_mu_range = gauss_mu_range |
|
self.gauss_sig_scaling = gauss_sig_scaling |
|
|
|
self.current_patch_index = -1 |
|
self.patch_boundingboxes = image_utils.compute_patch_boundingboxes(self.global_shape, stride=self.patch_res, |
|
patch_res=self.patch_res) |
|
self.disp_maps = None |
|
self.create_new_disp_maps() |
|
|
|
def create_new_disp_maps(self): |
|
print("DispFieldMapsPatchCreator.create_new_disp_maps()") |
|
self.disp_maps = create_displacement_field_maps(self.global_shape, self.map_count, self.modes, |
|
self.gauss_mu_range, self.gauss_sig_scaling) |
|
|
|
def get_patch(self): |
|
self.current_patch_index += 1 |
|
|
|
if len(self.patch_boundingboxes) <= self.current_patch_index: |
|
self.current_patch_index = 0 |
|
self.create_new_disp_maps() |
|
|
|
patch_boundingbox = self.patch_boundingboxes[self.current_patch_index] |
|
patch_disp_maps = self.disp_maps[:, patch_boundingbox[0]:patch_boundingbox[2], |
|
patch_boundingbox[1]:patch_boundingbox[3], :] |
|
return patch_disp_maps |
|
|
|
|
|
|
|
|
|
def compute_crossfield_c0c2(u, v): |
|
c0 = np.power(u, 2) * np.power(v, 2) |
|
c2 = - (np.power(u, 2) + np.power(v, 2)) |
|
crossfield = np.stack([c0.real, c0.imag, c2.real, c2.imag], axis=-1) |
|
return crossfield |
|
|
|
|
|
def compute_crossfield_uv(c0c2): |
|
c0 = c0c2[..., 0] + 1j * c0c2[..., 1] |
|
c2 = c0c2[..., 2] + 1j * c0c2[..., 3] |
|
sqrt_c2_squared_minus_4c0 = np.sqrt(np.power(c2, 2) - 4 * c0) |
|
u_squared = (c2 + sqrt_c2_squared_minus_4c0) / 2 |
|
v_squared = (c2 - sqrt_c2_squared_minus_4c0) / 2 |
|
u = np.sqrt(u_squared) |
|
v = np.sqrt(v_squared) |
|
return u, v |
|
|
|
|
|
def to_homogeneous(array): |
|
new_array = np.ones((array.shape[0], array.shape[1] + 1), dtype=array.dtype) |
|
new_array[..., :-1] = array |
|
return new_array |
|
|
|
|
|
def to_euclidian(array_homogeneous): |
|
array = array_homogeneous[:, 0:2] / array_homogeneous[:, 2:3] |
|
return array |
|
|
|
|
|
def stretch(array): |
|
mini = np.min(array) |
|
maxi = np.max(array) |
|
if maxi - mini: |
|
array -= mini |
|
array *= 2 / (maxi - mini) |
|
array -= 1 |
|
return array |
|
|
|
|
|
def crop_center(array, out_shape): |
|
assert len(out_shape) == 2, "out_shape should be of length 2" |
|
in_shape = np.array(array.shape[:2]) |
|
start = in_shape // 2 - (out_shape // 2) |
|
out_array = array[start[0]:start[0] + out_shape[0], start[1]:start[1] + out_shape[1], ...] |
|
return out_array |
|
|
|
|
|
def multivariate_gaussian(pos, mu, sigma): |
|
"""Return the multivariate Gaussian distribution on array pos. |
|
|
|
pos is an array constructed by packing the meshed arrays of variables |
|
x_1, x_2, x_3, ..., x_k into its _last_ dimension. |
|
|
|
""" |
|
|
|
n = mu.shape[0] |
|
sigma_det = np.linalg.det(sigma) |
|
sigma_inv = np.linalg.inv(sigma) |
|
N = np.sqrt((2 * np.pi) ** n * sigma_det) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try: |
|
fac = np.einsum('...k,kl,...l->...', pos - mu, sigma_inv, pos - mu, optimize=True) |
|
except: |
|
fac = np.einsum('...k,kl,...l->...', pos - mu, sigma_inv, pos - mu) |
|
|
|
|
|
|
|
|
|
|
|
return np.exp(-fac / 2) / N |
|
|
|
|
|
def create_multivariate_gaussian_mixture_map(shape, mode_count, mu_range, sig_scaling): |
|
shape = np.array(shape) |
|
|
|
|
|
|
|
dim_count = 2 |
|
downsample_factor = 4 |
|
dtype = np.float32 |
|
|
|
mu_scale = mu_range[1] - mu_range[0] |
|
row = np.linspace(mu_range[0], mu_range[1], mu_scale * shape[0] / downsample_factor, dtype=dtype) |
|
col = np.linspace(mu_range[0], mu_range[1], mu_scale * shape[1] / downsample_factor, dtype=dtype) |
|
rr, cc = np.meshgrid(row, col, indexing='ij') |
|
grid = np.stack([rr, cc], axis=2) |
|
|
|
mus = np.random.uniform(mu_range[0], mu_range[1], (mode_count, dim_count, 2)).astype(dtype) |
|
|
|
signs = np.random.choice([1, -1], size=(mode_count, dim_count)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gaussian_mixture = np.zeros_like(grid) |
|
for mode_index in range(mode_count): |
|
for dim in range(dim_count): |
|
sig = (sig_scaling[1] - sig_scaling[0]) * sklearn.datasets.make_spd_matrix(2) + sig_scaling[0] |
|
|
|
sig = sig.astype(dtype) |
|
multivariate_gaussian_grid = signs[mode_index, dim] * multivariate_gaussian(grid, mus[mode_index, dim], sig) |
|
gaussian_mixture[:, :, dim] += multivariate_gaussian_grid |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gaussian_mixture[:, :, 0] = stretch(gaussian_mixture[:, :, 0]) |
|
gaussian_mixture[:, :, 1] = stretch(gaussian_mixture[:, :, 1]) |
|
|
|
|
|
gaussian_mixture = crop_center(gaussian_mixture, shape // downsample_factor) |
|
|
|
|
|
|
|
|
|
|
|
gaussian_mixture = skimage.transform.resize(gaussian_mixture, shape) |
|
|
|
main_end = time.time() |
|
|
|
|
|
return gaussian_mixture |
|
|
|
|
|
def create_displacement_field_maps(shape, map_count, modes, gauss_mu_range, gauss_sig_scaling, seed=None): |
|
if seed is not None: |
|
np.random.seed(seed) |
|
disp_field_maps_list = [] |
|
for disp_field_map_index in range(map_count): |
|
disp_field_map_normed = create_multivariate_gaussian_mixture_map(shape, |
|
modes, |
|
gauss_mu_range, |
|
gauss_sig_scaling) |
|
disp_field_maps_list.append(disp_field_map_normed) |
|
disp_field_maps = np.stack(disp_field_maps_list, axis=0) |
|
|
|
return disp_field_maps |
|
|
|
|
|
def get_h_mat(t, theta, scale_offset, shear, p): |
|
""" |
|
Computes the homography matrix given the parameters |
|
See https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4 |
|
(fixed mistake in H_a) |
|
|
|
:param t: 2D translation vector |
|
:param theta: Scalar angle |
|
:param scale_offset: 2D scaling vector |
|
:param shear: 2D shearing vector |
|
:param p: 2D projection vector |
|
:return: h_mat: shape (3, 3) |
|
""" |
|
cos_theta = np.cos(theta) |
|
sin_theta = np.sin(theta) |
|
h_e = np.array([ |
|
[cos_theta, -sin_theta, t[0]], |
|
[sin_theta, cos_theta, t[1]], |
|
[0, 0, 1], |
|
]) |
|
h_a = np.array([ |
|
[1 + scale_offset[0], shear[1], 0], |
|
[shear[0], 1 + scale_offset[1], 0], |
|
[0, 0, 1], |
|
]) |
|
h_p = np.array([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[p[0], p[1], 1], |
|
]) |
|
h_mat = h_e @ h_a @ h_p |
|
return h_mat |
|
|
|
|
|
if CV2: |
|
def find_homography_4pt(src, dst): |
|
""" |
|
Estimates the homography that transforms src points into dst points. |
|
Then converts the matrix representation into the 4 points representation. |
|
|
|
:param src: |
|
:param dst: |
|
:return: |
|
""" |
|
h_mat, _ = cv2.findHomography(src, dst) |
|
h_4pt = convert_h_mat_to_4pt(h_mat) |
|
return h_4pt |
|
|
|
|
|
def convert_h_mat_to_4pt(h_mat): |
|
src_4pt = np.array([[ |
|
[-1, -1], |
|
[1, -1], |
|
[1, 1], |
|
[-1, 1], |
|
]], dtype=np.float64) |
|
h_4pt = cv2.perspectiveTransform(src_4pt, h_mat) |
|
return h_4pt |
|
|
|
|
|
def convert_h_4pt_to_mat(h_4pt): |
|
src_4pt = np.array([ |
|
[-1, -1], |
|
[1, -1], |
|
[1, 1], |
|
[-1, 1], |
|
], dtype=np.float32) |
|
h_4pt = h_4pt.astype(np.float32) |
|
h_mat = cv2.getPerspectiveTransform(src_4pt, h_4pt) |
|
return h_mat |
|
|
|
|
|
def field_map_to_image(field_map): |
|
mag, ang = cv2.cartToPolar(field_map[..., 0], field_map[..., 1]) |
|
hsv = np.zeros((field_map.shape[0], field_map.shape[1], 3)) |
|
hsv[..., 0] = ang * 180 / np.pi / 2 |
|
hsv[..., 1] = 255 |
|
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX) |
|
hsv = hsv.astype(np.uint8) |
|
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) |
|
return rgb |
|
else: |
|
def find_homography_4pt(src, dst): |
|
print("cv2 is not available, the find_homography_4pt(src, dst) function cannot work!") |
|
|
|
|
|
def convert_h_mat_to_4pt(h_mat): |
|
print("cv2 is not available, the convert_h_mat_to_4pt(h_mat) function cannot work!") |
|
|
|
|
|
def convert_h_4pt_to_mat(h_4pt): |
|
print("cv2 is not available, the convert_h_4pt_to_mat(h_4pt) function cannot work!") |
|
|
|
|
|
def field_map_to_image(field_map): |
|
print("cv2 is not available, the field_map_to_image(field_map) function cannot work!") |
|
|
|
|
|
def circular_diff(a1, a2, range_max): |
|
""" |
|
Compute difference between a1 and a2 belonging to the circular interval [0, range_max). |
|
For example to compute angle difference, use range_max=2*PI. |
|
a1 and a2 must be between range_min and range_max! |
|
Thus difference between 0 and range_max is 0. |
|
:param a1: numpy array |
|
:param a2: numpy array |
|
:param range_max: |
|
:return: |
|
""" |
|
d = range_max / 2 - np.abs(np.abs(a1 - a2) - range_max / 2) |
|
return d |
|
|
|
|
|
def invert_permutation(p): |
|
'''The argument p is assumed to be some permutation of 0, 1, ..., len(p)-1. |
|
Returns an array s, where s[i] gives the index of i in p. |
|
''' |
|
s = np.empty(p.size, p.dtype) |
|
s[p] = np.arange(p.size) |
|
return s |
|
|
|
|
|
def region_growing_1d(array, max_range, max_skew): |
|
""" |
|
:param array: |
|
:param max_var: |
|
:param max_mean_median_diff: |
|
:return: |
|
""" |
|
|
|
def verify_predicate(region): |
|
""" |
|
Region is sorted |
|
:param region: |
|
:return: |
|
""" |
|
skew = scipy.stats.skew(region) |
|
return region[-1] - region[0] < max_range and abs(skew) < max_skew |
|
|
|
assert len(array.shape) == 1, "array should be 1d, not {}".format(array.shape) |
|
p = np.argsort(array) |
|
sorted_array = array[p] |
|
|
|
labels = np.zeros(len(sorted_array), dtype=np.long) |
|
region_start = 0 |
|
region_label = 1 |
|
labels[region_start] = region_label |
|
centers = [] |
|
for i in range(1, len(sorted_array)): |
|
region = sorted_array[region_start:i + 1] |
|
if not verify_predicate(region): |
|
|
|
median = region[len(region) // 2] |
|
centers.append(median) |
|
|
|
region_start = i |
|
region_label += 1 |
|
labels[i] = region_label |
|
centers.append(median) |
|
|
|
return labels[invert_permutation(p)], centers |
|
|
|
|
|
def bilinear_interpolate(im, pos): |
|
|
|
x = pos[..., 1] |
|
y = pos[..., 0] |
|
|
|
x0 = np.floor(x).astype(int) |
|
x1 = x0 + 1 |
|
y0 = np.floor(y).astype(int) |
|
y1 = y0 + 1 |
|
|
|
x0_clipped = np.clip(x0, 0, im.shape[1] - 1) |
|
x1_clipped = np.clip(x1, 0, im.shape[1] - 1) |
|
y0_clipped = np.clip(y0, 0, im.shape[0] - 1) |
|
y1_clipped = np.clip(y1, 0, im.shape[0] - 1) |
|
|
|
Ia = im[y0_clipped, x0_clipped] |
|
Ib = im[y1_clipped, x0_clipped] |
|
Ic = im[y0_clipped, x1_clipped] |
|
Id = im[y1_clipped, x1_clipped] |
|
|
|
wa = (x1 - x) * (y1 - y) |
|
wb = (x1 - x) * (y - y0) |
|
wc = (x - x0) * (y1 - y) |
|
wd = (x - x0) * (y - y0) |
|
|
|
value = (Ia.T * wa).T + (Ib.T * wb).T + (Ic.T * wc).T + (Id.T * wd).T |
|
|
|
return value |
|
|
|
|
|
def main(): |
|
import matplotlib.pyplot as plt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
array = np.concatenate([np.arange(1, 1.01, 0.001), np.arange(0, np.pi / 2, np.pi / 100)]) |
|
print(array) |
|
labels = region_growing_1d(array, max_range=np.pi / 10, max_skew=1) |
|
print(labels) |
|
|
|
plt.plot(array, labels, ".") |
|
plt.show() |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|