Deagin Claude Opus 4.6 commited on
Commit
dda24a3
·
1 Parent(s): b0559d0

Tune segmentation: reduce over-splitting and clean polygon shapes

Browse files

- Fusion: raise split threshold from 15% to 30% of plane (RADIO roof
prompts are too similar to split on small differences)
- Fusion: raise min component size from 200 to 500 px, fraction from
10% to 20% of plane
- Merge: raise small fragment threshold from 3% to 5% of building area
- GeoJSON: increase Douglas-Peucker epsilon from 1% to 2.5% of perimeter
for cleaner 4-6 vertex polygons instead of jagged 10-16

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (3) hide show
  1. fusion.py +7 -5
  2. geo_export.py +2 -2
  3. pipeline.py +1 -1
fusion.py CHANGED
@@ -66,11 +66,13 @@ def fuse_segmentations(
66
  continue
67
 
68
  fraction = count / plane_pixels
69
- if fraction < 0.15:
70
- # Too small to be a real sub-division
 
 
71
  continue
72
 
73
- # This class represents >15% of the plane — check spatial coherence
74
  sub_mask = plane_mask & (radio_roof_class == cls)
75
  labeled_sub, n_components = label(sub_mask)
76
 
@@ -79,7 +81,7 @@ def fuse_segmentations(
79
  comp_size = comp_mask.sum()
80
 
81
  # Only split if the sub-region is spatially coherent and large enough
82
- if comp_size > plane_pixels * 0.1 and comp_size > 200:
83
  fused[comp_mask] = next_id
84
  next_id += 1
85
 
@@ -108,7 +110,7 @@ def split_disconnected_regions(label_map: np.ndarray) -> np.ndarray:
108
  def merge_small_fragments(
109
  label_map: np.ndarray,
110
  building_mask: np.ndarray,
111
- min_fraction: float = 0.03,
112
  ) -> np.ndarray:
113
  """Merge fragments smaller than min_fraction of building area.
114
 
 
66
  continue
67
 
68
  fraction = count / plane_pixels
69
+ if fraction < 0.30:
70
+ # Must represent >30% of the plane to justify a split.
71
+ # The RADIO roof-type prompts are similar ("flat/pitched/hip/gable
72
+ # roof plane"), so smaller fractions are noise, not real sub-features.
73
  continue
74
 
75
+ # This class represents >30% of the plane — check spatial coherence
76
  sub_mask = plane_mask & (radio_roof_class == cls)
77
  labeled_sub, n_components = label(sub_mask)
78
 
 
81
  comp_size = comp_mask.sum()
82
 
83
  # Only split if the sub-region is spatially coherent and large enough
84
+ if comp_size > plane_pixels * 0.20 and comp_size > 500:
85
  fused[comp_mask] = next_id
86
  next_id += 1
87
 
 
110
  def merge_small_fragments(
111
  label_map: np.ndarray,
112
  building_mask: np.ndarray,
113
+ min_fraction: float = 0.05,
114
  ) -> np.ndarray:
115
  """Merge fragments smaller than min_fraction of building area.
116
 
geo_export.py CHANGED
@@ -101,9 +101,9 @@ def labels_to_geojson(
101
  if area_sqft < min_area_sqft:
102
  continue
103
 
104
- # Simplify polygon (1% of perimeter)
105
  perimeter = cv2.arcLength(contour, True)
106
- epsilon = 0.01 * perimeter
107
  simplified = cv2.approxPolyDP(contour, epsilon, True)
108
 
109
  coords = [pixel_to_geo(pt[0][0], pt[0][1], w, h, bounds) for pt in simplified]
 
101
  if area_sqft < min_area_sqft:
102
  continue
103
 
104
+ # Simplify polygon (2.5% of perimeter — clean roof plane shapes)
105
  perimeter = cv2.arcLength(contour, True)
106
+ epsilon = 0.025 * perimeter
107
  simplified = cv2.approxPolyDP(contour, epsilon, True)
108
 
109
  coords = [pixel_to_geo(pt[0][0], pt[0][1], w, h, bounds) for pt in simplified]
pipeline.py CHANGED
@@ -158,7 +158,7 @@ def run(
158
  result.status.append("Fusing geometry + appearance...")
159
  fused = fuse_segmentations(ransac_labels, score_map, cropped_mask)
160
  fused = split_disconnected_regions(fused)
161
- fused = merge_small_fragments(fused, cropped_mask, min_fraction=0.03)
162
  result.fused_labels = fused
163
 
164
  n_final = len(set(np.unique(fused)) - {0})
 
158
  result.status.append("Fusing geometry + appearance...")
159
  fused = fuse_segmentations(ransac_labels, score_map, cropped_mask)
160
  fused = split_disconnected_regions(fused)
161
+ fused = merge_small_fragments(fused, cropped_mask, min_fraction=0.05)
162
  result.fused_labels = fused
163
 
164
  n_final = len(set(np.unique(fused)) - {0})