Fahimeh Orvati Nia commited on
Commit
6926cc6
·
1 Parent(s): 151802d

update the morphology for corn

Browse files
Files changed (2) hide show
  1. sorghum_pipeline/features/morphology.py +29 -25
  2. wrapper.py +6 -12
sorghum_pipeline/features/morphology.py CHANGED
@@ -56,6 +56,7 @@ class MorphologyExtractor:
56
 
57
  # Calculate height for each plant (skip background label 0)
58
  plant_heights = {}
 
59
  for plant_idx in range(1, num_labels):
60
  area = stats[plant_idx, cv2.CC_STAT_AREA]
61
  # Filter out very small components (noise)
@@ -69,17 +70,21 @@ class MorphologyExtractor:
69
  height_px = int(rows.max() - rows.min() + 1)
70
  height_cm = float(height_px * self.pixel_to_cm)
71
  plant_heights[f'plant_{plant_idx}'] = height_cm
 
 
 
 
 
 
 
72
 
73
  # Store individual plant heights
74
  features['traits']['plant_heights'] = plant_heights
75
- features['traits']['num_plants'] = len(plant_heights)
76
 
77
- # For backward compatibility, store total height if single plant
78
  if len(plant_heights) == 1:
79
  features['traits']['plant_height_cm'] = list(plant_heights.values())[0]
80
- elif len(plant_heights) > 1:
81
- # Store max height as overall height
82
- features['traits']['plant_height_cm'] = max(plant_heights.values())
83
  else:
84
  features['traits']['plant_height_cm'] = 0.0
85
 
@@ -160,30 +165,31 @@ class MorphologyExtractor:
160
  return arr
161
 
162
  def _simple_size_visual(self, rgb: np.ndarray, mask: np.ndarray) -> np.ndarray:
163
- """Draw contours and bbox for each plant on RGB image."""
164
  vis = rgb.copy()
165
 
166
  # Find connected components to identify individual plants
167
  num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)
168
 
169
- # Use different colors for different plants
170
- colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]
171
-
172
- plant_count = 0
173
- for plant_idx in range(1, num_labels): # Skip background (0)
174
  area = stats[plant_idx, cv2.CC_STAT_AREA]
175
- # Filter out very small components (noise)
176
- if area < 100:
177
- continue
178
-
179
- # Get individual plant mask
180
- plant_mask = ((labels == plant_idx).astype(np.uint8) * 255)
 
 
181
 
182
- # Find contours for this plant
183
  contours, _ = cv2.findContours(plant_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
184
 
185
- # Pick color for this plant
186
- color = colors[plant_count % len(colors)]
187
 
188
  # Draw contours
189
  cv2.drawContours(vis, contours, -1, color, 2)
@@ -191,13 +197,11 @@ class MorphologyExtractor:
191
  # Draw bounding box
192
  if contours:
193
  x, y, w, h = cv2.boundingRect(contours[0])
194
- cv2.rectangle(vis, (x, y), (x + w, y + h), color, 2)
195
 
196
- # Add plant number label
197
- cv2.putText(vis, f"P{plant_idx}", (x, y - 5),
198
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
199
-
200
- plant_count += 1
201
 
202
  return vis
203
 
 
56
 
57
  # Calculate height for each plant (skip background label 0)
58
  plant_heights = {}
59
+ plant_areas = {}
60
  for plant_idx in range(1, num_labels):
61
  area = stats[plant_idx, cv2.CC_STAT_AREA]
62
  # Filter out very small components (noise)
 
70
  height_px = int(rows.max() - rows.min() + 1)
71
  height_cm = float(height_px * self.pixel_to_cm)
72
  plant_heights[f'plant_{plant_idx}'] = height_cm
73
+ plant_areas[f'plant_{plant_idx}'] = area
74
+
75
+ # Keep only the largest plant (main plant) for single-plant datasets
76
+ if len(plant_heights) > 1:
77
+ # Find the largest plant by area
78
+ largest_plant = max(plant_areas.items(), key=lambda x: x[1])[0]
79
+ plant_heights = {largest_plant: plant_heights[largest_plant]}
80
 
81
  # Store individual plant heights
82
  features['traits']['plant_heights'] = plant_heights
83
+ features['traits']['num_plants'] = 1 if len(plant_heights) > 0 else 0
84
 
85
+ # Store single plant height
86
  if len(plant_heights) == 1:
87
  features['traits']['plant_height_cm'] = list(plant_heights.values())[0]
 
 
 
88
  else:
89
  features['traits']['plant_height_cm'] = 0.0
90
 
 
165
  return arr
166
 
167
  def _simple_size_visual(self, rgb: np.ndarray, mask: np.ndarray) -> np.ndarray:
168
+ """Draw contours and bbox for the largest plant on RGB image."""
169
  vis = rgb.copy()
170
 
171
  # Find connected components to identify individual plants
172
  num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)
173
 
174
+ # Find the largest plant (skip background 0)
175
+ largest_idx = -1
176
+ largest_area = 0
177
+ for plant_idx in range(1, num_labels):
 
178
  area = stats[plant_idx, cv2.CC_STAT_AREA]
179
+ if area > largest_area and area >= 100: # Filter noise
180
+ largest_area = area
181
+ largest_idx = plant_idx
182
+
183
+ # Draw only the largest plant
184
+ if largest_idx > 0:
185
+ # Get mask for largest plant
186
+ plant_mask = ((labels == largest_idx).astype(np.uint8) * 255)
187
 
188
+ # Find contours
189
  contours, _ = cv2.findContours(plant_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
190
 
191
+ # Use blue color for main plant
192
+ color = (255, 0, 0)
193
 
194
  # Draw contours
195
  cv2.drawContours(vis, contours, -1, color, 2)
 
197
  # Draw bounding box
198
  if contours:
199
  x, y, w, h = cv2.boundingRect(contours[0])
200
+ cv2.rectangle(vis, (x, y), (x + w, y + h), (0, 255, 0), 2)
201
 
202
+ # Add "Plant 1" label
203
+ cv2.putText(vis, "Plant 1", (x, y - 5),
204
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
 
 
205
 
206
  return vis
207
 
wrapper.py CHANGED
@@ -106,30 +106,24 @@ def _collect_outputs(work: Path, plants: Dict[str, Any]) -> Dict[str, str]:
106
  st = entry.get('statistics', {}) if isinstance(entry, dict) else {}
107
  if st:
108
  stats_lines.append(f"{name}: mean={st.get('mean', 0):.3f}, std={st.get('std', 0):.3f}")
109
- # Morphology stats (height for multiple plants)
110
  morph = pdata.get('morphology_features', {}) if isinstance(pdata, dict) else {}
111
  traits = morph.get('traits', {}) if isinstance(morph, dict) else {}
112
 
113
- # Check if we have multiple plants
114
  plant_heights = traits.get('plant_heights', {})
115
  num_plants = traits.get('num_plants', 0)
116
 
117
- if isinstance(plant_heights, dict) and len(plant_heights) > 1:
118
- # Multiple plants detected
119
- stats_lines.append(f"Number of plants: {num_plants}")
120
- # Sort by plant index for consistent display
121
- sorted_plants = sorted(plant_heights.items(), key=lambda x: int(x[0].split('_')[1]))
122
- for plant_name, height_cm in sorted_plants:
123
- plant_num = plant_name.split('_')[1]
124
- stats_lines.append(f" Plant {plant_num}: {height_cm:.2f} cm")
125
- elif isinstance(plant_heights, dict) and len(plant_heights) == 1:
126
- # Single plant
127
  height_cm = list(plant_heights.values())[0]
 
128
  stats_lines.append(f"Plant height: {height_cm:.2f} cm")
129
  else:
130
  # Fallback to old single height field
131
  height_cm = traits.get('plant_height_cm')
132
  if isinstance(height_cm, (int, float)) and height_cm > 0:
 
133
  stats_lines.append(f"Plant height: {height_cm:.2f} cm")
134
  if stats_lines:
135
  outputs['StatsText'] = "\n".join(stats_lines)
 
106
  st = entry.get('statistics', {}) if isinstance(entry, dict) else {}
107
  if st:
108
  stats_lines.append(f"{name}: mean={st.get('mean', 0):.3f}, std={st.get('std', 0):.3f}")
109
+ # Morphology stats (height - always show as single plant)
110
  morph = pdata.get('morphology_features', {}) if isinstance(pdata, dict) else {}
111
  traits = morph.get('traits', {}) if isinstance(morph, dict) else {}
112
 
113
+ # Get plant height (system now filters to largest plant only)
114
  plant_heights = traits.get('plant_heights', {})
115
  num_plants = traits.get('num_plants', 0)
116
 
117
+ # Always show as single plant (largest component)
118
+ if num_plants > 0 and isinstance(plant_heights, dict) and len(plant_heights) >= 1:
 
 
 
 
 
 
 
 
119
  height_cm = list(plant_heights.values())[0]
120
+ stats_lines.append(f"Number of plants: 1")
121
  stats_lines.append(f"Plant height: {height_cm:.2f} cm")
122
  else:
123
  # Fallback to old single height field
124
  height_cm = traits.get('plant_height_cm')
125
  if isinstance(height_cm, (int, float)) and height_cm > 0:
126
+ stats_lines.append(f"Number of plants: 1")
127
  stats_lines.append(f"Plant height: {height_cm:.2f} cm")
128
  if stats_lines:
129
  outputs['StatsText'] = "\n".join(stats_lines)