Spaces:
Runtime error
Runtime error
Commit
·
b29f831
1
Parent(s):
16506ef
update
Browse files
app.py
CHANGED
@@ -89,97 +89,33 @@ def get_points_from_contours(contours):
|
|
89 |
return centroids
|
90 |
|
91 |
# Function to display the image with the selected quadrilateral superimposed
|
92 |
-
|
93 |
-
#
|
94 |
-
|
95 |
|
96 |
-
#
|
97 |
-
|
98 |
|
99 |
-
#
|
100 |
-
|
101 |
-
|
102 |
-
# # Function to update displayed quadrilateral based on selected index
|
103 |
-
# def update_displayed_quadrilateral(index, point_combinations, base_image_path):
|
104 |
-
# # Extract the four points of the current quadrilateral
|
105 |
-
# quad_points = get_points_from_contours(point_combinations[index])
|
106 |
-
|
107 |
-
# # Read the base image
|
108 |
-
# base_image = cv2.imread(base_image_path)
|
109 |
-
|
110 |
-
# # If the image is not found, handle the error appropriately
|
111 |
-
# if base_image is None:
|
112 |
-
# st.error("Failed to load image.")
|
113 |
-
# return
|
114 |
-
|
115 |
-
# # Display the image with the selected quadrilateral
|
116 |
-
# display_image_with_quadrilateral(base_image, quad_points)
|
117 |
-
|
118 |
-
def increment_index():
|
119 |
-
st.session_state.selected_quad_index = (st.session_state.selected_quad_index + 1) % len(st.session_state.valid_quadrilaterals)
|
120 |
-
st.session_state.centroids = update_displayed_quadrilateral(st.session_state.selected_quad_index)
|
121 |
-
|
122 |
-
def decrement_index():
|
123 |
-
st.session_state.selected_quad_index = (st.session_state.selected_quad_index - 1) % len(st.session_state.valid_quadrilaterals)
|
124 |
-
st.session_state.centroids = update_displayed_quadrilateral(st.session_state.selected_quad_index)
|
125 |
|
126 |
# Function to update displayed quadrilateral based on selected index
|
127 |
-
def update_displayed_quadrilateral(
|
128 |
# Extract the four points of the current quadrilateral
|
129 |
-
|
130 |
-
return centroids
|
131 |
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
def get_points_from_contours(contours):
|
140 |
-
centroids = [get_centroid(contour) for contour in contours if get_centroid(contour) is not None]
|
141 |
-
return centroids
|
142 |
-
|
143 |
-
# Function to check if a quadrilateral is valid (i.e., no sides intersect)
|
144 |
-
def is_valid_quadrilateral(pts):
|
145 |
-
def ccw(A, B, C):
|
146 |
-
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
|
147 |
-
|
148 |
-
def intersect(A, B, C, D):
|
149 |
-
return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
|
150 |
-
|
151 |
-
A, B, C, D = pts
|
152 |
-
return not (intersect(A, B, C, D) or intersect(A, D, B, C))
|
153 |
-
|
154 |
-
def create_polygon_mask(centroids, flag_mask_shape):
|
155 |
-
# Create a polygon mask using the sorted centroids
|
156 |
-
poly_mask = np.zeros(flag_mask_shape, dtype=np.uint8)
|
157 |
-
cv2.fillPoly(poly_mask, [np.array(centroids)], 255)
|
158 |
-
return poly_mask
|
159 |
-
|
160 |
-
def warp_and_display_images(img, centroids, base_name, flag_mask_rgb, plant_mask_rgb, mask_plant_plot_rgb):
|
161 |
-
# Warp the images
|
162 |
-
plant_rgb_warp = warp_image(img, centroids)
|
163 |
-
plant_mask_warp = warp_image(mask_plant_plot_rgb, centroids)
|
164 |
-
|
165 |
-
# Extract the RGB pixels from the original image using the mask_plant_plot
|
166 |
-
plant_rgb = cv2.bitwise_and(img, img, mask=create_polygon_mask(centroids, img.shape[:2]))
|
167 |
-
|
168 |
-
# Draw the bounding quadrilateral
|
169 |
-
plot_rgb = plant_rgb.copy()
|
170 |
-
for i in range(4):
|
171 |
-
cv2.line(plot_rgb, centroids[i], centroids[(i+1)%4], (0, 0, 255), 3)
|
172 |
-
|
173 |
-
# Display the images
|
174 |
-
st.image([flag_mask_rgb, plant_mask_rgb, mask_plant_plot_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp],
|
175 |
-
caption=["Flag Mask", "Plant Mask", "Mask Plant Plot", "Plot RGB", "Plant RGB Warp", "Plant Mask Warp"],
|
176 |
-
use_column_width=True)
|
177 |
-
|
178 |
-
return plant_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp
|
179 |
-
|
180 |
-
def process_image(image_path, flag_lower, flag_upper, plant_lower, plant_upper, loc):
|
181 |
|
|
|
|
|
182 |
|
|
|
183 |
with loc:
|
184 |
btn_back, btn_next = st.columns([2,2])
|
185 |
|
@@ -212,66 +148,127 @@ def process_image(image_path, flag_lower, flag_upper, plant_lower, plant_upper,
|
|
212 |
# return None, None, None, None, None, None, None, None, None, None
|
213 |
|
214 |
|
|
|
215 |
contours, _ = cv2.findContours(flag_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
|
216 |
sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)
|
|
|
|
|
217 |
significant_contours = [cnt for cnt in sorted_contours if cv2.contourArea(cnt) > MIN_AREA]
|
218 |
|
219 |
-
#
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
if not st.session_state.valid_quadrilaterals:
|
224 |
-
st.warning("No valid quadrilaterals found.")
|
225 |
return None, None, None, None, None, None, None, None, None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
|
227 |
-
|
228 |
-
|
229 |
-
st.session_state.selected_quad_index = 0
|
230 |
-
if 'centroids' not in st.session_state:
|
231 |
-
st.session_state.centroids = get_points_from_contours(st.session_state.valid_quadrilaterals[st.session_state.selected_quad_index])
|
232 |
|
233 |
-
#
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
|
|
|
|
|
|
|
|
242 |
|
243 |
-
|
|
|
|
|
|
|
244 |
|
245 |
-
|
246 |
-
|
|
|
|
|
247 |
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
plot_rgb = plant_rgb.copy()
|
258 |
-
for i in range(4):
|
259 |
-
cv2.line(plot_rgb, st.session_state.centroids[i], st.session_state.centroids[(i+1)%4], (0, 0, 255), 3)
|
260 |
-
|
261 |
-
# Convert the masks to RGB for visualization
|
262 |
-
flag_mask_rgb = cv2.cvtColor(flag_mask, cv2.COLOR_GRAY2RGB)
|
263 |
-
orange_color = [255, 165, 0] # RGB value for orange
|
264 |
-
flag_mask_rgb[np.any(flag_mask_rgb != [0, 0, 0], axis=-1)] = orange_color
|
265 |
-
|
266 |
-
plant_mask_rgb = cv2.cvtColor(plant_mask, cv2.COLOR_GRAY2RGB)
|
267 |
-
mask_plant_plot_rgb = cv2.cvtColor(mask_plant_plot, cv2.COLOR_GRAY2RGB)
|
268 |
-
bright_green_color = [0, 255, 0]
|
269 |
-
plant_mask_rgb[np.any(plant_mask_rgb != [0, 0, 0], axis=-1)] = bright_green_color
|
270 |
-
mask_plant_plot_rgb[np.any(mask_plant_plot_rgb != [0, 0, 0], axis=-1)] = bright_green_color
|
271 |
-
|
272 |
-
# Warp the images
|
273 |
-
plant_rgb_warp = warp_image(plant_rgb, st.session_state.centroids)
|
274 |
-
plant_mask_warp = warp_image(mask_plant_plot_rgb, st.session_state.centroids)
|
275 |
|
276 |
return flag_mask_rgb, plant_mask_rgb, mask_plant_plot_rgb, plant_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp, plant_mask, mask_plant_plot, black_pixels_in_quad
|
277 |
|
@@ -464,7 +461,7 @@ def main():
|
|
464 |
|
465 |
flag_lower_bound, flag_upper_bound, plant_lower_bound, plant_upper_bound = get_color_parameters()
|
466 |
|
467 |
-
flag_mask, plant_mask, mask_plant_plot, plant_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp, plant_mask_bi, mask_plant_plot_bi, black_pixels_in_quad = process_image(selected_img, flag_lower_bound, flag_upper_bound, plant_lower_bound, plant_upper_bound, R_save)
|
468 |
|
469 |
if plant_mask_warp is not None:
|
470 |
plot_coverage, warp_coverage, plot_area_cm2 = calculate_coverage(mask_plant_plot_bi, plant_mask_warp, black_pixels_in_quad)
|
@@ -558,9 +555,6 @@ if 'dir_uploaded_images_small' not in st.session_state:
|
|
558 |
st.session_state['dir_uploaded_images_small'] = os.path.join(st.session_state['dir_home'],'uploads_small')
|
559 |
validate_dir(os.path.join(st.session_state['dir_home'],'uploads_small'))
|
560 |
|
561 |
-
if 'selected_quad_index' not in st.session_state:
|
562 |
-
st.session_state['selected_quad_index'] = 0
|
563 |
-
|
564 |
|
565 |
st.title("GreenSight")
|
566 |
st.write("Simple color segmentation app to estimate the vegetation coverage in a plot. Corners of the plot need to be marked with solid, uniforly colored flags.")
|
|
|
89 |
return centroids
|
90 |
|
91 |
# Function to display the image with the selected quadrilateral superimposed
|
92 |
+
def display_image_with_quadrilateral(image, points):
|
93 |
+
# Make a copy of the image to draw on
|
94 |
+
overlay_image = image.copy()
|
95 |
|
96 |
+
# Draw the quadrilateral
|
97 |
+
cv2.polylines(overlay_image, [np.array(points)], isClosed=True, color=(0, 255, 0), thickness=3)
|
98 |
|
99 |
+
# Display the image with the quadrilateral
|
100 |
+
st.image(overlay_image, caption="Quadrilateral on Image", use_column_width='auto')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
# Function to update displayed quadrilateral based on selected index
|
103 |
+
def update_displayed_quadrilateral(index, point_combinations, base_image_path):
|
104 |
# Extract the four points of the current quadrilateral
|
105 |
+
quad_points = get_points_from_contours(point_combinations[index])
|
|
|
106 |
|
107 |
+
# Read the base image
|
108 |
+
base_image = cv2.imread(base_image_path)
|
109 |
+
|
110 |
+
# If the image is not found, handle the error appropriately
|
111 |
+
if base_image is None:
|
112 |
+
st.error("Failed to load image.")
|
113 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
|
115 |
+
# Display the image with the selected quadrilateral
|
116 |
+
display_image_with_quadrilateral(base_image, quad_points)
|
117 |
|
118 |
+
def process_image(image_path, flag_lower, flag_upper, plant_lower, plant_upper, loc, file_name, file_exists, selected_img, headers, base_name):
|
119 |
with loc:
|
120 |
btn_back, btn_next = st.columns([2,2])
|
121 |
|
|
|
148 |
# return None, None, None, None, None, None, None, None, None, None
|
149 |
|
150 |
|
151 |
+
# Find contours
|
152 |
contours, _ = cv2.findContours(flag_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
153 |
+
|
154 |
+
# Sort contours by area and keep a significant number, assuming noise has much smaller area
|
155 |
sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)
|
156 |
+
|
157 |
+
# Filter out noise based on a predefined area threshold
|
158 |
significant_contours = [cnt for cnt in sorted_contours if cv2.contourArea(cnt) > MIN_AREA]
|
159 |
|
160 |
+
# Logic to handle cases where there are more than 4 significant contours
|
161 |
+
centroids = []
|
162 |
+
if len(significant_contours) < 4:
|
|
|
|
|
|
|
163 |
return None, None, None, None, None, None, None, None, None, None
|
164 |
+
elif len(significant_contours) > 4:
|
165 |
+
while not st.session_state['keep_quad']:
|
166 |
+
with loc:
|
167 |
+
st.warning("Cycle until correct plot bounds are found")
|
168 |
+
# Create all possible combinations of four points
|
169 |
+
point_combinations = list(itertools.combinations(significant_contours, 4))
|
170 |
+
|
171 |
+
# Placeholder for quadrilateral indices
|
172 |
+
selected_quad_index = 0
|
173 |
+
|
174 |
+
# Function to update displayed quadrilateral based on selected index
|
175 |
+
def update_displayed_quadrilateral(index):
|
176 |
+
# Extract the four points of the current quadrilateral
|
177 |
+
centroids = get_points_from_contours(point_combinations[index])
|
178 |
+
return centroids
|
179 |
+
|
180 |
+
# Show initial quadrilateral
|
181 |
+
centroids = update_displayed_quadrilateral(selected_quad_index)
|
182 |
+
|
183 |
+
with btn_back:
|
184 |
+
# Button to go to the previous quadrilateral
|
185 |
+
if st.button('Previous'):
|
186 |
+
selected_quad_index = max(selected_quad_index - 1, 0)
|
187 |
+
centroids = update_displayed_quadrilateral(selected_quad_index)
|
188 |
+
|
189 |
+
with btn_next:
|
190 |
+
# Button to go to the next quadrilateral
|
191 |
+
if st.button('Next'):
|
192 |
+
selected_quad_index = min(selected_quad_index + 1, len(point_combinations) - 1)
|
193 |
+
centroids = update_displayed_quadrilateral(selected_quad_index)
|
194 |
+
|
195 |
+
with loc:
|
196 |
+
if st.button('Keep Plot Bounds'):
|
197 |
+
st.session_state['keep_quad'] = True
|
198 |
+
if st.button('Save as Failure'):
|
199 |
+
st.session_state['keep_quad'] = True
|
200 |
+
# Append the data to the CSV file
|
201 |
+
with open(file_name, mode='a', newline='') as file:
|
202 |
+
writer = csv.writer(file)
|
203 |
+
|
204 |
+
# If the file doesn't exist, write the headers
|
205 |
+
if not file_exists:
|
206 |
+
writer.writerow(headers)
|
207 |
+
|
208 |
+
# Write the data
|
209 |
+
writer.writerow([f"{base_name}",f"NA", f"NA", f"NA"])
|
210 |
+
|
211 |
+
# Remove processed image from the list
|
212 |
+
st.session_state['input_list'].remove(selected_img)
|
213 |
+
st.rerun()
|
214 |
+
|
215 |
+
# If there are exactly 4 largest contours, proceed with existing logic
|
216 |
+
elif len(significant_contours) == 4:
|
217 |
+
# Create a new mask with only the largest 4 contours
|
218 |
+
largest_4_flag_mask = np.zeros_like(flag_mask)
|
219 |
+
cv2.drawContours(largest_4_flag_mask, sorted_contours, -1, (255), thickness=cv2.FILLED)
|
220 |
+
|
221 |
+
# Compute the centroid for each contour
|
222 |
+
for contour in sorted_contours:
|
223 |
+
M = cv2.moments(contour)
|
224 |
+
if M["m00"] != 0:
|
225 |
+
cx = int(M["m10"] / M["m00"])
|
226 |
+
cy = int(M["m01"] / M["m00"])
|
227 |
+
else:
|
228 |
+
cx, cy = 0, 0
|
229 |
+
centroids.append((cx, cy))
|
230 |
+
|
231 |
+
# Compute the centroid of the centroids
|
232 |
+
centroid_x = sum(x for x, y in centroids) / 4
|
233 |
+
centroid_y = sum(y for x, y in centroids) / 4
|
234 |
|
235 |
+
# Sort the centroids
|
236 |
+
centroids.sort(key=lambda point: (-math.atan2(point[1] - centroid_y, point[0] - centroid_x)) % (2 * np.pi))
|
|
|
|
|
|
|
237 |
|
238 |
+
# Create a polygon mask using the sorted centroids
|
239 |
+
poly_mask = np.zeros_like(flag_mask)
|
240 |
+
cv2.fillPoly(poly_mask, [np.array(centroids)], 255)
|
241 |
+
|
242 |
+
# Mask the plant_mask with poly_mask
|
243 |
+
mask_plant_plot = cv2.bitwise_and(plant_mask, plant_mask, mask=poly_mask)
|
244 |
|
245 |
+
# Count the number of black pixels inside the quadrilateral
|
246 |
+
total_pixels_in_quad = np.prod(poly_mask.shape)
|
247 |
+
white_pixels_in_quad = np.sum(poly_mask == 255)
|
248 |
+
black_pixels_in_quad = total_pixels_in_quad - white_pixels_in_quad
|
249 |
+
|
250 |
+
# Extract the RGB pixels from the original image using the mask_plant_plot
|
251 |
+
plant_rgb = cv2.bitwise_and(img, img, mask=mask_plant_plot)
|
252 |
|
253 |
+
# Draw the bounding quadrilateral
|
254 |
+
plot_rgb = plant_rgb.copy()
|
255 |
+
for i in range(4):
|
256 |
+
cv2.line(plot_rgb, centroids[i], centroids[(i+1)%4], (0, 0, 255), 3)
|
257 |
|
258 |
+
# Convert the masks to RGB for visualization
|
259 |
+
flag_mask_rgb = cv2.cvtColor(flag_mask, cv2.COLOR_GRAY2RGB)
|
260 |
+
orange_color = [255, 165, 0] # RGB value for orange
|
261 |
+
flag_mask_rgb[np.any(flag_mask_rgb != [0, 0, 0], axis=-1)] = orange_color
|
262 |
|
263 |
+
plant_mask_rgb = cv2.cvtColor(plant_mask, cv2.COLOR_GRAY2RGB)
|
264 |
+
mask_plant_plot_rgb = cv2.cvtColor(mask_plant_plot, cv2.COLOR_GRAY2RGB)
|
265 |
+
bright_green_color = [0, 255, 0]
|
266 |
+
plant_mask_rgb[np.any(plant_mask_rgb != [0, 0, 0], axis=-1)] = bright_green_color
|
267 |
+
mask_plant_plot_rgb[np.any(mask_plant_plot_rgb != [0, 0, 0], axis=-1)] = bright_green_color
|
268 |
+
|
269 |
+
# Warp the images
|
270 |
+
plant_rgb_warp = warp_image(plant_rgb, centroids)
|
271 |
+
plant_mask_warp = warp_image(mask_plant_plot_rgb, centroids)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
|
273 |
return flag_mask_rgb, plant_mask_rgb, mask_plant_plot_rgb, plant_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp, plant_mask, mask_plant_plot, black_pixels_in_quad
|
274 |
|
|
|
461 |
|
462 |
flag_lower_bound, flag_upper_bound, plant_lower_bound, plant_upper_bound = get_color_parameters()
|
463 |
|
464 |
+
flag_mask, plant_mask, mask_plant_plot, plant_rgb, plot_rgb, plant_rgb_warp, plant_mask_warp, plant_mask_bi, mask_plant_plot_bi, black_pixels_in_quad = process_image(selected_img, flag_lower_bound, flag_upper_bound, plant_lower_bound, plant_upper_bound, R_save, file_name, file_exists, selected_img, headers, base_name)
|
465 |
|
466 |
if plant_mask_warp is not None:
|
467 |
plot_coverage, warp_coverage, plot_area_cm2 = calculate_coverage(mask_plant_plot_bi, plant_mask_warp, black_pixels_in_quad)
|
|
|
555 |
st.session_state['dir_uploaded_images_small'] = os.path.join(st.session_state['dir_home'],'uploads_small')
|
556 |
validate_dir(os.path.join(st.session_state['dir_home'],'uploads_small'))
|
557 |
|
|
|
|
|
|
|
558 |
|
559 |
st.title("GreenSight")
|
560 |
st.write("Simple color segmentation app to estimate the vegetation coverage in a plot. Corners of the plot need to be marked with solid, uniforly colored flags.")
|