Spaces:
Runtime error
Runtime error
import numpy as np | |
from scipy.optimize import curve_fit | |
from typing import Tuple, List | |
import cv2 | |
point = Tuple[float,float] | |
line = Tuple[point, point] | |
measurements = List[line] | |
class LineFit: | |
def __init__(self, n: int, step_size:float) -> None: | |
"""Model that fits a line in a binary image and measures diameter of fibers | |
:param n: number of measurements to do along the fitted line. | |
:param step_size: step size of diameter measurement (in pixels). Can be fraction. | |
""" | |
self.n = n | |
self.step_size = step_size | |
def get_coordinates(self, im, value_for_mask): | |
#I = rgb2gray(I_orig) #we can delete this if we get binary images | |
mask = im > value_for_mask | |
fiber_coor = np.argwhere(mask) | |
x = fiber_coor[:, 1] | |
y = fiber_coor[:, 0] | |
return x, y | |
def func_line(self, x, a, b): | |
return a * x + b | |
def func_line_inv(self, y, a, b): | |
return (y - b)/a | |
def get_fited_line_x_y(self, im): | |
value_for_mask = (int(np.max(im))+int(np.min(im)))/2 # Pixels to mask in get_coordinate | |
x, y = self.get_coordinates(im, value_for_mask) | |
popt, pcov = curve_fit(self.func_line, x, y) | |
return x, y, popt, pcov | |
def get_fited_line_y_x(self, im): | |
value_for_mask = (int(np.max(im))+int(np.min(im)))/2 # Pixels to mask in get_coordinate | |
x, y = self.get_coordinates(im, value_for_mask) | |
popt, pcov = curve_fit(self.func_line, y, x) | |
return x, y, popt, pcov | |
def get_better_fit(self, x, y, popt, popt_inv, pcov, pcov_inv): | |
diagonal = np.diagonal(pcov) | |
diagonal_inv = np.diagonal(pcov_inv) | |
if np.less(diagonal, diagonal_inv).all() == True: | |
popt_fit = popt | |
x_line = np.arange(0, max(x), 1) | |
y_line = [] | |
for i in x_line: | |
a = self.func_line(x_line[i], *popt) | |
y_line.append(a) | |
y_fit = y_line | |
x_fit = x_line | |
p1 = [x_fit[0],y_fit[0]] | |
p2 = [x_fit[-1],y_fit[-1]] | |
elif np.less(diagonal, diagonal_inv).all() == False: | |
popt_fit = [1/popt_inv[0], (-popt_inv[1])/popt_inv[0]] | |
y_line = np.arange(0, max(y), 1) | |
x_line = [] | |
for i in y_line: | |
a = self.func_line(y_line[i], *popt_inv) | |
x_line.append(a) | |
y_fit = y_line | |
x_fit = x_line | |
p1 = [x_fit[0],y_fit[0]] | |
p2 = [x_fit[-1],y_fit[-1]] | |
else: | |
print("One of the pcov values is True and the rest are False") | |
return popt_fit, x_fit, y_fit, p1, p2 | |
def get_point(self, t, p1, p2): | |
dx = p2[0]-p1[0] | |
dy = p2[1]-p1[1] | |
p = [(dx * t + p1[0]), (dy * t + p1[1])] | |
return p, dx, dy | |
def get_normal_vector(self, t, dx, dy, p3): | |
n_pos = [-dy, dx] | |
mag_pos = np.linalg.norm(n_pos) | |
nu_pos = n_pos/mag_pos | |
u_pos = [(nu_pos[0] * t + p3[0]), (nu_pos[1] * t + p3[1])] | |
return u_pos | |
def is_inside(self, im, pos): | |
if not (0 <= pos[0] < im.shape[0]): | |
return False | |
if not (0 <= pos[1] < im.shape[1]): | |
return False | |
return True | |
def get_pixels_half (self, pos_or_neg, im, dx, dy, p3): | |
color_threshold = (int(np.max(im))+int(np.min(im)))/2 | |
for ts in (range(len(im[0]))): | |
u = self.get_normal_vector((pos_or_neg*(ts+(self.step_size))), dx, dy, p3) | |
test_point = round(u[1]),round(u[0]) | |
if not self.is_inside(im, test_point): | |
return None, None | |
test = im[test_point[0], test_point[1]] > color_threshold | |
if test == False: | |
pixels = ts - 1 | |
break | |
# plt.plot(u[0], u[1], 'c.', markersize=12) | |
return pixels, (u[0], u[1]) | |
def get_calculated_diameter(self, im, p1, p2): | |
color_threshold = (int(np.max(im))+int(np.min(im)))/2 | |
diameters = [] | |
lines = [] | |
#mask_meas_lines = np.zeros_like(im) | |
for n in range(1, self.n+1): | |
t = 1/(self.n+1) | |
p3, dx, dy = self.get_point((t * n), p1, p2) | |
test_point = round(p3[1]),round(p3[0]) | |
if not self.is_inside(im, test_point): | |
continue | |
true_point = im[test_point[0], test_point[1]] > color_threshold | |
if true_point == False: | |
continue | |
if true_point == True: | |
radius_p, cp1 = self.get_pixels_half(1, im, dx, dy, p3) | |
radius_n, cp2 = self.get_pixels_half(-1, im, dx, dy, p3) | |
if (radius_p != None) and (radius_n != None): | |
max_val = max(radius_p, radius_n) | |
min_val = min(radius_p, radius_n) | |
equal = abs((max_val - min_val)/(max_val + 1e-5)) | |
if equal < 0.1: | |
lines.append((cp1,cp2)) | |
diameters.append(radius_p+radius_n) | |
# mask_meas_lines = self.mask_measured_lines(im, lines) | |
# plt.plot(p3[0], p3[1], 'r.', markersize=12) | |
calculated_diameter = np.array(diameters).mean() | |
return calculated_diameter, lines | |
def line_to_arrays(self, line): | |
return [line[0][0], line[1][0]], [line[0][1], line[1][1]] | |
def mask_measured_lines(self, im, lines): | |
mask = np.zeros_like(im) | |
for p1, p2 in lines: | |
if not (p1 == None or p2 == None): | |
cv2.line(mask, np.array(p1).astype(np.int32), np.array(p2).astype(np.int32), 1, 1) | |
return mask | |
def predict(self, im: np.ndarray): | |
x, y, popt, pcov = self.get_fited_line_x_y(im) | |
_, _, popt_inv, pcov_inv = self.get_fited_line_y_x(im) | |
popt_fit, x_fit, y_fit, p1, p2 = self.get_better_fit(x, y, popt, popt_inv, pcov, pcov_inv) | |
calculated_diameter, lines = self.get_calculated_diameter(im, p1, p2) | |
mask_meas_lines = self.mask_measured_lines(im, lines) | |
#for line in lines: | |
# plt.plot(*self.line_to_arrays(line), 'c-') | |
return calculated_diameter, lines | |
if __name__ == "__main__": | |
import os | |
model = LineFit(10, 0.5) | |
dataset_path = "/Users/carmenlopez/dev/diameterY/scratch/dataset_files" | |
example_path = os.path.join(dataset_path, "test_0005.npz") | |
example = np.load(example_path) | |
diameter_pred, mask_meas_lines = model.predict(example["x"]) | |
print(diameter_pred, example["d"]) | |