crabbly commited on
Commit
d11d730
·
verified ·
1 Parent(s): 475bba9

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +43 -16
main.py CHANGED
@@ -123,6 +123,12 @@ class ProcessResult:
123
  sm_height: Optional[float] = None
124
  raw_perimeter: Optional[float] = None
125
  sm_perimeter: Optional[float] = None
 
 
 
 
 
 
126
  raw_rind_thick: Optional[float] = None
127
  sm_rind_thick: Optional[float] = None
128
  raw_rind_ratio: Optional[float] = None
@@ -343,19 +349,23 @@ class WatermelonProcessor:
343
 
344
  @staticmethod
345
  def split_asymmetry(region_mask, midline, thickness=5):
346
- if midline is None or len(midline) < 2 or cv2.countNonZero(region_mask) == 0:
347
- return None
348
-
349
  split_mask = region_mask.copy()
350
- cv2.polylines(split_mask, [midline.astype(np.int32)], False, 0, thickness)
 
 
 
 
 
 
 
 
 
351
  n_labels, _, stats, _ = cv2.connectedComponentsWithStats((split_mask > 0).astype(np.uint8), connectivity=8)
352
- if n_labels <= 2:
353
- return None
354
 
355
  areas = sorted([int(stats[i, cv2.CC_STAT_AREA]) for i in range(1, n_labels)], reverse=True)
356
- if len(areas) < 2 or areas[0] + areas[1] == 0:
357
- return None
358
-
359
  return float(abs(areas[0] - areas[1]) / (areas[0] + areas[1]))
360
 
361
  @staticmethod
@@ -422,13 +432,14 @@ class WatermelonProcessor:
422
 
423
  pt_top, pt_bot, height_px = get_intersections(phi + np.pi/2, rind_mask)
424
  pt_right, pt_left, width_px = get_intersections(phi, rind_mask)
425
- _, _, flesh_width_px = get_intersections(phi, flesh_mask)
 
426
 
427
  rind_thick_px = None
428
  if width_px > 0 and flesh_width_px > 0:
429
  rind_thick_px = float(max(0.0, (width_px - flesh_width_px) / 2.0))
430
 
431
- return height_px, width_px, rind_thick_px, (pt_top, pt_bot), (pt_left, pt_right)
432
 
433
  @staticmethod
434
  def get_dual_mask_midline(f_left, f_right, rind_cnt, pred_cnt, cx, cy):
@@ -561,7 +572,13 @@ class WatermelonProcessor:
561
  t_r, raw_r_r, (cx, cy), raw_rind_cnt = rind_data
562
  pts_r_raw = raw_rind_cnt.reshape(-1, 2).astype(np.float32)
563
 
564
- # --- NEW SCAN-LINE FLESH GAP FILLING (Preserves V-shape) ---
 
 
 
 
 
 
565
  _, (ma, Ma), angle = cv2.fitEllipse(raw_rind_cnt) if len(raw_rind_cnt) > 5 else (None, (0,0), 0)
566
  rot_angle = angle if ma < Ma else angle + 90
567
  M_rot = cv2.getRotationMatrix2D((cx, cy), rot_angle, 1.0)
@@ -603,7 +620,8 @@ class WatermelonProcessor:
603
  _, _, r_angle = cv2.fitEllipse(raw_rind_cnt)
604
  raw_phi = np.deg2rad(180 - r_angle) if r_angle > 90 else np.deg2rad(-r_angle)
605
 
606
- raw_h, raw_w, raw_rt, raw_h_line, raw_w_line = self.calculate_axis_metrics(cx, cy, raw_phi, target_rind_mask, flesh_combined)
 
607
 
608
  # Midline is found using flesh_combined (with gap) and clipped to the raw rind contour
609
  midline = self.get_dual_mask_midline(flesh_l_m, flesh_r_m, raw_rind_cnt, pts_r_raw, cx, cy)
@@ -632,7 +650,11 @@ class WatermelonProcessor:
632
  bounds=([0.5, 0.5, -0.4, 0.0, 0.1, 0.0, 0.1, -1.5, -0.2, -0.2], [2.0, 2.0, 0.4, 0.5, 50.0, 0.5, 50.0, 1.5, 0.2, 0.2]), max_nfev=3000)
633
  d_r = np.sum((raw_r_r/scale_r - 1)**2)
634
  r2_rind = 1 - (np.sum((raw_r_r/scale_r - self.watermelon_model(t_r, *popt_r))**2) / d_r) if d_r != 0 else None
635
-
 
 
 
 
636
  t_fit = np.linspace(-np.pi, np.pi, 500)
637
  fit_r = self.watermelon_model(t_fit, *popt_r) * scale_r
638
  sm_rind_pts = np.array([[r*np.cos(t)+cx, cy-r*np.sin(t)] for t, r in zip(t_fit, fit_r)], dtype=np.float32)
@@ -667,11 +689,13 @@ class WatermelonProcessor:
667
 
668
  sm_f_a = self.contour_area_px(sm_flesh_pts)
669
  sm_flesh_asym = self.split_asymmetry(sm_flesh_mask, midline, thickness=3)
 
670
  else:
671
  sm_flesh_mask = np.zeros_like(target_rind_mask)
 
672
 
673
  # Smooth axes (using the filled smooth masks)
674
- sm_h, sm_w, sm_rt, sm_h_line, sm_w_line = self.calculate_axis_metrics(cx, cy, sm_phi, sm_rind_mask, sm_flesh_mask)
675
  if sm_tot_a > 0 and sm_f_a is not None:
676
  sm_f_rat = float(sm_f_a / sm_tot_a)
677
 
@@ -699,6 +723,9 @@ class WatermelonProcessor:
699
 
700
  raw_width=s(raw_w), sm_width=s(sm_w), raw_height=s(raw_h), sm_height=s(sm_h),
701
  raw_perimeter=s(raw_perim), sm_perimeter=s(sm_perim),
 
 
 
702
  raw_rind_thick=s(raw_rt), sm_rind_thick=s(sm_rt),
703
  raw_rind_ratio=rt_rat(raw_rt, raw_w), sm_rind_ratio=rt_rat(sm_rt, sm_w),
704
  raw_total_area=a(raw_tot_a), sm_total_area=a(sm_tot_a),
@@ -717,7 +744,7 @@ class WatermelonProcessor:
717
 
718
  def draw_base(r_m, f_m):
719
  out = image.copy().astype(np.float32)
720
- alpha = 0.15 # Transparency
721
 
722
  # --- THE FIX: Only paint the rind where there is NO flesh ---
723
  rind_only = (r_m > 0) & (f_m == 0)
 
123
  sm_height: Optional[float] = None
124
  raw_perimeter: Optional[float] = None
125
  sm_perimeter: Optional[float] = None
126
+ raw_flesh_width: Optional[float] = None
127
+ sm_flesh_width: Optional[float] = None
128
+ raw_flesh_height: Optional[float] = None
129
+ sm_flesh_height: Optional[float] = None
130
+ raw_flesh_perimeter: Optional[float] = None
131
+ sm_flesh_perimeter: Optional[float] = None
132
  raw_rind_thick: Optional[float] = None
133
  sm_rind_thick: Optional[float] = None
134
  raw_rind_ratio: Optional[float] = None
 
349
 
350
  @staticmethod
351
  def split_asymmetry(region_mask, midline, thickness=5):
352
+ if midline is None or len(midline) < 2 or cv2.countNonZero(region_mask) == 0: return None
 
 
353
  split_mask = region_mask.copy()
354
+
355
+ # FIX: Extrapolate the line 100 pixels in both directions to guarantee it completely bisects smoothed/expanded masks
356
+ m = midline.astype(np.float32)
357
+ p0, p1, pn, pn_1 = m[0], m[1], m[-1], m[-2]
358
+ n0, n1 = np.linalg.norm(p0 - p1), np.linalg.norm(pn - pn_1)
359
+ ext_start = p0 + (p0 - p1) / n0 * 100 if n0 > 1e-5 else p0
360
+ ext_end = pn + (pn - pn_1) / n1 * 100 if n1 > 1e-5 else pn
361
+ ext_midline = np.vstack([ext_start, m, ext_end]).astype(np.int32)
362
+
363
+ cv2.polylines(split_mask, [ext_midline], False, 0, thickness)
364
  n_labels, _, stats, _ = cv2.connectedComponentsWithStats((split_mask > 0).astype(np.uint8), connectivity=8)
365
+ if n_labels <= 2: return None
 
366
 
367
  areas = sorted([int(stats[i, cv2.CC_STAT_AREA]) for i in range(1, n_labels)], reverse=True)
368
+ if len(areas) < 2 or areas[0] + areas[1] == 0: return None
 
 
369
  return float(abs(areas[0] - areas[1]) / (areas[0] + areas[1]))
370
 
371
  @staticmethod
 
432
 
433
  pt_top, pt_bot, height_px = get_intersections(phi + np.pi/2, rind_mask)
434
  pt_right, pt_left, width_px = get_intersections(phi, rind_mask)
435
+ _, _, f_height_px = get_intersections(phi + np.pi/2, flesh_mask) # NEW
436
+ _, _, f_width_px = get_intersections(phi, flesh_mask)
437
 
438
  rind_thick_px = None
439
  if width_px > 0 and flesh_width_px > 0:
440
  rind_thick_px = float(max(0.0, (width_px - flesh_width_px) / 2.0))
441
 
442
+ return height_px, width_px, rind_thick_px, (pt_top, pt_bot), (pt_left, pt_right), f_height_px, f_width_px
443
 
444
  @staticmethod
445
  def get_dual_mask_midline(f_left, f_right, rind_cnt, pred_cnt, cx, cy):
 
572
  t_r, raw_r_r, (cx, cy), raw_rind_cnt = rind_data
573
  pts_r_raw = raw_rind_cnt.reshape(-1, 2).astype(np.float32)
574
 
575
+ # Raw Flesh perimeter length
576
+ raw_flesh_perim = None
577
+ if flesh_data:
578
+ _, _, _, raw_flesh_cnt = flesh_data
579
+ raw_flesh_perim = float(cv2.arcLength(raw_flesh_cnt, True))
580
+
581
+ # --- SCAN-LINE FLESH GAP FILLING (Preserves V-shape) ---
582
  _, (ma, Ma), angle = cv2.fitEllipse(raw_rind_cnt) if len(raw_rind_cnt) > 5 else (None, (0,0), 0)
583
  rot_angle = angle if ma < Ma else angle + 90
584
  M_rot = cv2.getRotationMatrix2D((cx, cy), rot_angle, 1.0)
 
620
  _, _, r_angle = cv2.fitEllipse(raw_rind_cnt)
621
  raw_phi = np.deg2rad(180 - r_angle) if r_angle > 90 else np.deg2rad(-r_angle)
622
 
623
+ # Capture raw_f_h and raw_f_w
624
+ raw_h, raw_w, raw_rt, raw_h_line, raw_w_line, raw_f_h, raw_f_w = self.calculate_axis_metrics(cx, cy, raw_phi, target_rind_mask, flesh_closed)
625
 
626
  # Midline is found using flesh_combined (with gap) and clipped to the raw rind contour
627
  midline = self.get_dual_mask_midline(flesh_l_m, flesh_r_m, raw_rind_cnt, pts_r_raw, cx, cy)
 
650
  bounds=([0.5, 0.5, -0.4, 0.0, 0.1, 0.0, 0.1, -1.5, -0.2, -0.2], [2.0, 2.0, 0.4, 0.5, 50.0, 0.5, 50.0, 1.5, 0.2, 0.2]), max_nfev=3000)
651
  d_r = np.sum((raw_r_r/scale_r - 1)**2)
652
  r2_rind = 1 - (np.sum((raw_r_r/scale_r - self.watermelon_model(t_r, *popt_r))**2) / d_r) if d_r != 0 else None
653
+
654
+ # R² Warning Flag
655
+ if r2_rind is not None and r2_rind < 0.85:
656
+ warnings.append(f"R² below 0.85 ({r2_rind:.2f}). Fruit may be damaged or irregular.")
657
+
658
  t_fit = np.linspace(-np.pi, np.pi, 500)
659
  fit_r = self.watermelon_model(t_fit, *popt_r) * scale_r
660
  sm_rind_pts = np.array([[r*np.cos(t)+cx, cy-r*np.sin(t)] for t, r in zip(t_fit, fit_r)], dtype=np.float32)
 
689
 
690
  sm_f_a = self.contour_area_px(sm_flesh_pts)
691
  sm_flesh_asym = self.split_asymmetry(sm_flesh_mask, midline, thickness=3)
692
+ sm_flesh_perim = float(np.sum(np.linalg.norm(np.diff(sm_flesh_pts, axis=0), axis=1)) + np.linalg.norm(sm_flesh_pts[-1]-sm_flesh_pts[0]))
693
  else:
694
  sm_flesh_mask = np.zeros_like(target_rind_mask)
695
+ sm_flesh_perim = None
696
 
697
  # Smooth axes (using the filled smooth masks)
698
+ sm_h, sm_w, sm_rt, sm_h_line, sm_w_line, sm_f_h, sm_f_w = self.calculate_axis_metrics(cx, cy, sm_phi, sm_rind_mask, sm_flesh_mask)
699
  if sm_tot_a > 0 and sm_f_a is not None:
700
  sm_f_rat = float(sm_f_a / sm_tot_a)
701
 
 
723
 
724
  raw_width=s(raw_w), sm_width=s(sm_w), raw_height=s(raw_h), sm_height=s(sm_h),
725
  raw_perimeter=s(raw_perim), sm_perimeter=s(sm_perim),
726
+ raw_flesh_width=s(raw_f_w), sm_flesh_width=s(sm_f_w),
727
+ raw_flesh_height=s(raw_f_h), sm_flesh_height=s(sm_f_h),
728
+ raw_flesh_perimeter=s(raw_flesh_perim), sm_flesh_perimeter=s(sm_flesh_perim),
729
  raw_rind_thick=s(raw_rt), sm_rind_thick=s(sm_rt),
730
  raw_rind_ratio=rt_rat(raw_rt, raw_w), sm_rind_ratio=rt_rat(sm_rt, sm_w),
731
  raw_total_area=a(raw_tot_a), sm_total_area=a(sm_tot_a),
 
744
 
745
  def draw_base(r_m, f_m):
746
  out = image.copy().astype(np.float32)
747
+ alpha = 0.10 # Transparency
748
 
749
  # --- THE FIX: Only paint the rind where there is NO flesh ---
750
  rind_only = (r_m > 0) & (f_m == 0)