Spaces:
Runtime error
Runtime error
first commit of app
Browse files- README.md +4 -4
- app.py +96 -0
- line_fit.py +168 -0
- packages.txt +1 -0
- requirements.txt +5 -0
- test0000.png +0 -0
- test0001.png +0 -0
- test0002.png +0 -0
- test0003.png +0 -0
- test0004.png +0 -0
README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
---
|
2 |
title: Measure Fiber Diameter
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 3.0.26
|
8 |
app_file: app.py
|
9 |
-
pinned:
|
10 |
license: apache-2.0
|
11 |
---
|
12 |
|
|
|
1 |
---
|
2 |
title: Measure Fiber Diameter
|
3 |
+
emoji: π
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: blue
|
6 |
sdk: gradio
|
7 |
sdk_version: 3.0.26
|
8 |
app_file: app.py
|
9 |
+
pinned: true
|
10 |
license: apache-2.0
|
11 |
---
|
12 |
|
app.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import tensorflow as tf
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
from itertools import islice
|
6 |
+
from PIL import Image, ImageDraw, ImageColor
|
7 |
+
from line_fit import LineFit
|
8 |
+
import random
|
9 |
+
|
10 |
+
MAX_SELECTIONS = 8
|
11 |
+
input_shape = (MAX_SELECTIONS,256,256,2)
|
12 |
+
def load_model():
|
13 |
+
model = tf.keras.models.load_model('vivid-sweep-model-best.hdf5')
|
14 |
+
return model
|
15 |
+
|
16 |
+
model = load_model()
|
17 |
+
colors = list(ImageColor.colormap.keys())
|
18 |
+
linefit = LineFit(30, 0.3)
|
19 |
+
|
20 |
+
def get_blob_centroids(mask):
|
21 |
+
centers = []
|
22 |
+
print(mask.dtype)
|
23 |
+
print(mask.shape)
|
24 |
+
contours, hierarchies = cv2.findContours(
|
25 |
+
mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
26 |
+
for i in contours:
|
27 |
+
M = cv2.moments(i)
|
28 |
+
if M['m00'] != 0:
|
29 |
+
cx = int(M['m10']/M['m00'])
|
30 |
+
cy = int(M['m01']/M['m00'])
|
31 |
+
centers.append([cx,cy])
|
32 |
+
print(cx,cy)
|
33 |
+
return centers
|
34 |
+
|
35 |
+
def predict_mask(input_img, threshold):
|
36 |
+
# unpack and reshape input
|
37 |
+
im, mask = input_img["image"], input_img["mask"]
|
38 |
+
mask = mask[:,:,0].astype(np.uint8)
|
39 |
+
im = im.astype(np.float32)/256
|
40 |
+
|
41 |
+
# get centroids to measure the fibers
|
42 |
+
centers = get_blob_centroids(mask)
|
43 |
+
|
44 |
+
# create a batch of input for the model
|
45 |
+
batch = np.zeros([MAX_SELECTIONS,256,256,2], dtype=np.float32)
|
46 |
+
|
47 |
+
for i, (cx, cy) in enumerate(islice(centers, MAX_SELECTIONS)):
|
48 |
+
batch[i,:,:,0] = im
|
49 |
+
batch[i,cy,cx,1] = 1.0
|
50 |
+
|
51 |
+
pred = model.predict(batch, verbose=0).squeeze()
|
52 |
+
# create a single image with the background and the foreground
|
53 |
+
im = Image.fromarray(im*255).convert("RGBA")
|
54 |
+
# m = Image.fromarray(pred[0]>threshold).convert("RGBA")
|
55 |
+
# im = Image.blend(im, m, 0.5)
|
56 |
+
imgd = ImageDraw.Draw(im)
|
57 |
+
ds = []
|
58 |
+
for p in islice(pred, len(centers)):
|
59 |
+
d, lines = linefit.predict((p>threshold).astype(np.uint8)*255)
|
60 |
+
ds.append(d)
|
61 |
+
m = Image.fromarray(p>threshold)
|
62 |
+
imgd.bitmap([0,0], m, fill=random.choice(colors))
|
63 |
+
for line in lines:
|
64 |
+
imgd.line(line, fill ="blue", width = 1)
|
65 |
+
|
66 |
+
return im, ds
|
67 |
+
|
68 |
+
demo = gr.Blocks()
|
69 |
+
|
70 |
+
with demo:
|
71 |
+
with gr.Row():
|
72 |
+
with gr.Column():
|
73 |
+
img = gr.Image(
|
74 |
+
tool="sketch",
|
75 |
+
source="upload",
|
76 |
+
label="Mask",
|
77 |
+
image_mode='L',
|
78 |
+
shape=[256,256],
|
79 |
+
value='test0000.png'
|
80 |
+
)
|
81 |
+
threshold = gr.Slider(
|
82 |
+
label='Segmentation threshold', minimum=0, maximum=1, value=0.5)
|
83 |
+
|
84 |
+
with gr.Row():
|
85 |
+
btn = gr.Button("Run")
|
86 |
+
with gr.Column():
|
87 |
+
img2 = gr.Image()
|
88 |
+
text = gr.Text()
|
89 |
+
|
90 |
+
btn.click(fn=predict_mask, inputs=[img, threshold], outputs=[img2,text], )
|
91 |
+
examples = gr.Examples(examples=['test0001.png',
|
92 |
+
'test0002.png',
|
93 |
+
'test0003.png'],
|
94 |
+
inputs=img)
|
95 |
+
|
96 |
+
demo.launch()
|
line_fit.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import numpy as np
|
3 |
+
from scipy.optimize import curve_fit
|
4 |
+
from typing import Tuple, List
|
5 |
+
import cv2
|
6 |
+
|
7 |
+
point = Tuple[float,float]
|
8 |
+
line = Tuple[point, point]
|
9 |
+
measurements = List[line]
|
10 |
+
|
11 |
+
class LineFit:
|
12 |
+
def __init__(self, n: int, step_size:float) -> None:
|
13 |
+
"""Model that fits a line in a binary image and measures diameter of fibers
|
14 |
+
|
15 |
+
:param n: number of measurements to do along the fitted line.
|
16 |
+
:param step_size: step size of diameter measurement (in pixels). Can be fraction.
|
17 |
+
"""
|
18 |
+
self.n = n
|
19 |
+
self.step_size = step_size
|
20 |
+
|
21 |
+
def get_coordinates(self, im, value_for_mask):
|
22 |
+
#I = rgb2gray(I_orig) #we can delete this if we get binary images
|
23 |
+
mask = im > value_for_mask
|
24 |
+
fiber_coor = np.argwhere(mask)
|
25 |
+
x = fiber_coor[:, 1]
|
26 |
+
y = fiber_coor[:, 0]
|
27 |
+
return x, y
|
28 |
+
|
29 |
+
def func_line(self, x, a, b):
|
30 |
+
return a * x + b
|
31 |
+
|
32 |
+
def func_line_inv(self, y, a, b):
|
33 |
+
return (y - b)/a
|
34 |
+
|
35 |
+
def get_fited_line_x_y(self, im):
|
36 |
+
value_for_mask = (int(np.max(im))+int(np.min(im)))/2 # Pixels to mask in get_coordinate
|
37 |
+
x, y = self.get_coordinates(im, value_for_mask)
|
38 |
+
popt, pcov = curve_fit(self.func_line, x, y)
|
39 |
+
return x, y, popt, pcov
|
40 |
+
|
41 |
+
def get_fited_line_y_x(self, im):
|
42 |
+
value_for_mask = (int(np.max(im))+int(np.min(im)))/2 # Pixels to mask in get_coordinate
|
43 |
+
x, y = self.get_coordinates(im, value_for_mask)
|
44 |
+
popt, pcov = curve_fit(self.func_line, y, x)
|
45 |
+
return x, y, popt, pcov
|
46 |
+
|
47 |
+
def get_better_fit(self, x, y, popt, popt_inv, pcov, pcov_inv):
|
48 |
+
diagonal = np.diagonal(pcov)
|
49 |
+
diagonal_inv = np.diagonal(pcov_inv)
|
50 |
+
if np.less(diagonal, diagonal_inv).all() == True:
|
51 |
+
popt_fit = popt
|
52 |
+
x_line = np.arange(0, max(x), 1)
|
53 |
+
y_line = []
|
54 |
+
for i in x_line:
|
55 |
+
a = self.func_line(x_line[i], *popt)
|
56 |
+
y_line.append(a)
|
57 |
+
y_fit = y_line
|
58 |
+
x_fit = x_line
|
59 |
+
p1 = [x_fit[0],y_fit[0]]
|
60 |
+
p2 = [x_fit[-1],y_fit[-1]]
|
61 |
+
elif np.less(diagonal, diagonal_inv).all() == False:
|
62 |
+
popt_fit = [1/popt_inv[0], (-popt_inv[1])/popt_inv[0]]
|
63 |
+
y_line = np.arange(0, max(y), 1)
|
64 |
+
x_line = []
|
65 |
+
for i in y_line:
|
66 |
+
a = self.func_line(y_line[i], *popt_inv)
|
67 |
+
x_line.append(a)
|
68 |
+
y_fit = y_line
|
69 |
+
x_fit = x_line
|
70 |
+
p1 = [x_fit[0],y_fit[0]]
|
71 |
+
p2 = [x_fit[-1],y_fit[-1]]
|
72 |
+
else:
|
73 |
+
print("One of the pcov values is True and the rest are False")
|
74 |
+
return popt_fit, x_fit, y_fit, p1, p2
|
75 |
+
|
76 |
+
def get_point(self, t, p1, p2):
|
77 |
+
dx = p2[0]-p1[0]
|
78 |
+
dy = p2[1]-p1[1]
|
79 |
+
p = [(dx * t + p1[0]), (dy * t + p1[1])]
|
80 |
+
return p, dx, dy
|
81 |
+
|
82 |
+
def get_normal_vector(self, t, dx, dy, p3):
|
83 |
+
n_pos = [-dy, dx]
|
84 |
+
mag_pos = np.linalg.norm(n_pos)
|
85 |
+
nu_pos = n_pos/mag_pos
|
86 |
+
u_pos = [(nu_pos[0] * t + p3[0]), (nu_pos[1] * t + p3[1])]
|
87 |
+
return u_pos
|
88 |
+
|
89 |
+
def is_inside(self, im, pos):
|
90 |
+
if not (0 <= pos[0] < im.shape[0]):
|
91 |
+
return False
|
92 |
+
if not (0 <= pos[1] < im.shape[1]):
|
93 |
+
return False
|
94 |
+
return True
|
95 |
+
|
96 |
+
def get_pixels_half (self, pos_or_neg, im, dx, dy, p3):
|
97 |
+
color_threshold = (int(np.max(im))+int(np.min(im)))/2
|
98 |
+
for ts in (range(len(im[0]))):
|
99 |
+
u = self.get_normal_vector((pos_or_neg*(ts+(self.step_size))), dx, dy, p3)
|
100 |
+
test_point = round(u[1]),round(u[0])
|
101 |
+
if not self.is_inside(im, test_point):
|
102 |
+
return None, None
|
103 |
+
test = im[test_point[0], test_point[1]] > color_threshold
|
104 |
+
if test == False:
|
105 |
+
pixels = ts - 1
|
106 |
+
break
|
107 |
+
# plt.plot(u[0], u[1], 'c.', markersize=12)
|
108 |
+
return pixels, (u[0], u[1])
|
109 |
+
|
110 |
+
|
111 |
+
def get_calculated_diameter(self, im, p1, p2):
|
112 |
+
color_threshold = (int(np.max(im))+int(np.min(im)))/2
|
113 |
+
diameters = []
|
114 |
+
lines = []
|
115 |
+
#mask_meas_lines = np.zeros_like(im)
|
116 |
+
for n in range(1, self.n+1):
|
117 |
+
t = 1/(self.n+1)
|
118 |
+
p3, dx, dy = self.get_point((t * n), p1, p2)
|
119 |
+
test_point = round(p3[1]),round(p3[0])
|
120 |
+
if not self.is_inside(im, test_point):
|
121 |
+
continue
|
122 |
+
true_point = im[test_point[0], test_point[1]] > color_threshold
|
123 |
+
if true_point == False:
|
124 |
+
continue
|
125 |
+
if true_point == True:
|
126 |
+
radius_p, cp1 = self.get_pixels_half(1, im, dx, dy, p3)
|
127 |
+
radius_n, cp2 = self.get_pixels_half(-1, im, dx, dy, p3)
|
128 |
+
if (radius_p != None) and (radius_n != None):
|
129 |
+
max_val = max(radius_p, radius_n)
|
130 |
+
min_val = min(radius_p, radius_n)
|
131 |
+
equal = abs((max_val - min_val)/(max_val + 1e-5))
|
132 |
+
if equal < 0.1:
|
133 |
+
lines.append((cp1,cp2))
|
134 |
+
diameters.append(radius_p+radius_n)
|
135 |
+
# mask_meas_lines = self.mask_measured_lines(im, lines)
|
136 |
+
# plt.plot(p3[0], p3[1], 'r.', markersize=12)
|
137 |
+
calculated_diameter = np.array(diameters).mean()
|
138 |
+
return calculated_diameter, lines
|
139 |
+
|
140 |
+
def line_to_arrays(self, line):
|
141 |
+
return [line[0][0], line[1][0]], [line[0][1], line[1][1]]
|
142 |
+
|
143 |
+
def mask_measured_lines(self, im, lines):
|
144 |
+
mask = np.zeros_like(im)
|
145 |
+
for p1, p2 in lines:
|
146 |
+
if not (p1 == None or p2 == None):
|
147 |
+
cv2.line(mask, np.array(p1).astype(np.int32), np.array(p2).astype(np.int32), 1, 1)
|
148 |
+
return mask
|
149 |
+
|
150 |
+
def predict(self, im: np.ndarray):
|
151 |
+
x, y, popt, pcov = self.get_fited_line_x_y(im)
|
152 |
+
_, _, popt_inv, pcov_inv = self.get_fited_line_y_x(im)
|
153 |
+
popt_fit, x_fit, y_fit, p1, p2 = self.get_better_fit(x, y, popt, popt_inv, pcov, pcov_inv)
|
154 |
+
calculated_diameter, lines = self.get_calculated_diameter(im, p1, p2)
|
155 |
+
mask_meas_lines = self.mask_measured_lines(im, lines)
|
156 |
+
#for line in lines:
|
157 |
+
# plt.plot(*self.line_to_arrays(line), 'c-')
|
158 |
+
return calculated_diameter, mask_meas_lines
|
159 |
+
|
160 |
+
if __name__ == "__main__":
|
161 |
+
import os
|
162 |
+
|
163 |
+
model = LineFit(10, 0.5)
|
164 |
+
dataset_path = "/Users/carmenlopez/dev/diameterY/scratch/dataset_files"
|
165 |
+
example_path = os.path.join(dataset_path, "test_0005.npz")
|
166 |
+
example = np.load(example_path)
|
167 |
+
diameter_pred, mask_meas_lines = model.predict(example["x"])
|
168 |
+
print(diameter_pred, example["d"])
|
packages.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
python3-opencv
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
tensorflow
|
2 |
+
opencv-python
|
3 |
+
gradio
|
4 |
+
scipy
|
5 |
+
hugging_face_hub
|
test0000.png
ADDED
test0001.png
ADDED
test0002.png
ADDED
test0003.png
ADDED
test0004.png
ADDED