phyloforfun commited on
Commit
b29f831
·
1 Parent(s): 16506ef
Files changed (1) hide show
  1. app.py +130 -136
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
- # 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 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(valid_quadrilaterals, index):
128
  # Extract the four points of the current quadrilateral
129
- centroids = get_points_from_contours(valid_quadrilaterals[index])
130
- return centroids
131
 
132
- def get_centroid(contour):
133
- # Compute the centroid for the contour
134
- M = cv2.moments(contour)
135
- if M["m00"] != 0:
136
- return (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
137
- return None
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
- # Generate all permutations of four points and filter valid quadrilaterals
220
- if 'valid_quadrilaterals' not in st.session_state or st.session_state.valid_quadrilaterals is None:
221
- st.session_state.valid_quadrilaterals = [perm for perm in itertools.permutations(significant_contours, 4) if is_valid_quadrilateral(get_points_from_contours(perm))]
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
- # Initialize or update the selected_quad_index and centroids
228
- if 'selected_quad_index' not in st.session_state:
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
- # Logic to handle cases where there are 4 or more significant contours
234
- if len(significant_contours) >= 4:
235
- with btn_back:
236
- if st.button('Previous'):
237
- decrement_index() # Call function to decrement index
 
238
 
239
- with btn_next:
240
- if st.button('Next'):
241
- increment_index() # Call function to increment index
 
 
 
 
242
 
243
- poly_mask = create_polygon_mask(st.session_state.centroids, flag_mask.shape)
 
 
 
244
 
245
- # Mask the plant_mask with poly_mask
246
- mask_plant_plot = cv2.bitwise_and(plant_mask, plant_mask, mask=poly_mask)
 
 
247
 
248
- # Count the number of black pixels inside the quadrilateral
249
- total_pixels_in_quad = np.prod(poly_mask.shape)
250
- white_pixels_in_quad = np.sum(poly_mask == 255)
251
- black_pixels_in_quad = total_pixels_in_quad - white_pixels_in_quad
252
-
253
- # Extract the RGB pixels from the original image using the mask_plant_plot
254
- plant_rgb = cv2.bitwise_and(img, img, mask=mask_plant_plot)
255
-
256
- # Draw the bounding quadrilateral
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.")