Spaces:
Runtime error
Runtime error
Auto-scale thresholds with resolution
Browse files- ARCHITECTURE.md +3 -2
- app/safety.py +13 -5
- app/ui.py +1 -0
ARCHITECTURE.md
CHANGED
|
@@ -14,6 +14,7 @@ This document describes the flow in the current Gradio app (`app/ui.py`), from i
|
|
| 14 |
- Coverage strictness: default 0.95 (fraction of the footprint that must be safe).
|
| 15 |
- Depth smoothing: `depth_smoothing_base` scaled by resolution to reduce speckle before scoring.
|
| 16 |
- Roof mask: depth-based, with large components (>20% of the map) discarded to avoid masking whole fields.
|
|
|
|
| 17 |
|
| 18 |
## Per-Image Processing Pipeline
|
| 19 |
1. **Load and crop** the selected image (RGB, 5% border removed).
|
|
@@ -28,9 +29,9 @@ This document describes the flow in the current Gradio app (`app/ui.py`), from i
|
|
| 28 |
- Roof via depth MAD threshold + grad gate; components smaller than `area_thresh` and not exceeding `max_area_frac` (20%) are kept; also dilated to the footprint for blocking.
|
| 29 |
6. **Flat region search (`pick_flat_patch`)**:
|
| 30 |
- Normalize depth to [0,1], compute `std_map` via box mean/mean_sq, and `grad_norm` via `np.gradient` normalized at the 95th percentile.
|
| 31 |
-
- Landing mask starts from `grad_norm <
|
| 32 |
7. **Safe mask construction**:
|
| 33 |
-
- Base safe mask: `(std_map <
|
| 34 |
- Apply segmentation blocks (expanded masks) to remove water/road/roof regions.
|
| 35 |
- Clearance: dilate hazards by `clearance_factor * patch_px` (default 0, so off unless raised).
|
| 36 |
- Coverage: box filter with `patch_px` window; keep pixels meeting `coverage_strictness` (default 0.95).
|
|
|
|
| 14 |
- Coverage strictness: default 0.95 (fraction of the footprint that must be safe).
|
| 15 |
- Depth smoothing: `depth_smoothing_base` scaled by resolution to reduce speckle before scoring.
|
| 16 |
- Roof mask: depth-based, with large components (>20% of the map) discarded to avoid masking whole fields.
|
| 17 |
+
- Resolution-aware thresholds: flatness (`std_thresh`) and gradient (`grad_thresh`) are auto-scaled with the actual depth resolution; the UI sliders act as base values.
|
| 18 |
|
| 19 |
## Per-Image Processing Pipeline
|
| 20 |
1. **Load and crop** the selected image (RGB, 5% border removed).
|
|
|
|
| 29 |
- Roof via depth MAD threshold + grad gate; components smaller than `area_thresh` and not exceeding `max_area_frac` (20%) are kept; also dilated to the footprint for blocking.
|
| 30 |
6. **Flat region search (`pick_flat_patch`)**:
|
| 31 |
- Normalize depth to [0,1], compute `std_map` via box mean/mean_sq, and `grad_norm` via `np.gradient` normalized at the 95th percentile.
|
| 32 |
+
- Landing mask starts from `grad_norm < grad_thresh_eff` (resolution-adjusted), excludes water/road if present, and keeps the lowest-variance patch as a fallback box.
|
| 33 |
7. **Safe mask construction**:
|
| 34 |
+
- Base safe mask: `(std_map < std_thresh_eff) & (grad_norm < grad_thresh_eff) & landing_mask & texture_mask`.
|
| 35 |
- Apply segmentation blocks (expanded masks) to remove water/road/roof regions.
|
| 36 |
- Clearance: dilate hazards by `clearance_factor * patch_px` (default 0, so off unless raised).
|
| 37 |
- Coverage: box filter with `patch_px` window; keep pixels meeting `coverage_strictness` (default 0.95).
|
app/safety.py
CHANGED
|
@@ -60,6 +60,8 @@ class AnalysisSummary:
|
|
| 60 |
roof_mask_enabled: bool
|
| 61 |
used_valid_center: bool
|
| 62 |
warnings: list[str]
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
@dataclass
|
|
@@ -213,11 +215,15 @@ class SafetyAnalyzer:
|
|
| 213 |
road_mask_resized = np.array(road_mask_resized) > 0
|
| 214 |
road_mask_block = expand_mask_for_footprint(road_mask_resized)
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
box, std_map, grad_norm, grad_mask, landing_mask = pick_flat_patch(
|
| 217 |
depth,
|
| 218 |
patch=patch_px,
|
| 219 |
-
std_thresh=
|
| 220 |
-
grad_thresh=
|
| 221 |
water_mask=water_mask_block if water_mask_block is not None else water_mask_resized,
|
| 222 |
)
|
| 223 |
if request.use_roof_mask:
|
|
@@ -251,7 +257,7 @@ class SafetyAnalyzer:
|
|
| 251 |
interior_mask = np.ones_like(landing_mask, dtype=bool)
|
| 252 |
landing_mask = landing_mask & interior_mask
|
| 253 |
texture_mask = texture_norm <= max(0.0, min(1.0, request.texture_threshold))
|
| 254 |
-
safe_mask = (std_map <
|
| 255 |
|
| 256 |
try:
|
| 257 |
clearance_px = max(1, int(round(request.clearance_factor * patch_px)))
|
|
@@ -289,8 +295,8 @@ class SafetyAnalyzer:
|
|
| 289 |
keep |= labels == i
|
| 290 |
safe_mask = keep
|
| 291 |
|
| 292 |
-
risk_std = np.clip((std_map -
|
| 293 |
-
risk_grad = np.clip((grad_norm -
|
| 294 |
risk_map = np.maximum(risk_std, risk_grad) * (~safe_mask)
|
| 295 |
|
| 296 |
safe_fit = safe_mask.astype(np.float32)
|
|
@@ -443,6 +449,8 @@ class SafetyAnalyzer:
|
|
| 443 |
roof_mask_enabled=request.use_roof_mask,
|
| 444 |
used_valid_center=used_valid_center,
|
| 445 |
warnings=warnings,
|
|
|
|
|
|
|
| 446 |
)
|
| 447 |
return AnalysisResult(images=layers, summary=summary)
|
| 448 |
|
|
|
|
| 60 |
roof_mask_enabled: bool
|
| 61 |
used_valid_center: bool
|
| 62 |
warnings: list[str]
|
| 63 |
+
std_thresh_applied: float
|
| 64 |
+
grad_thresh_applied: float
|
| 65 |
|
| 66 |
|
| 67 |
@dataclass
|
|
|
|
| 215 |
road_mask_resized = np.array(road_mask_resized) > 0
|
| 216 |
road_mask_block = expand_mask_for_footprint(road_mask_resized)
|
| 217 |
|
| 218 |
+
# Autoscale sensitivity with resolution: stricter when resolution is low
|
| 219 |
+
std_thresh_eff = max(1e-6, float(request.std_thresh)) * (res_scale ** -0.5)
|
| 220 |
+
grad_thresh_eff = max(1e-6, float(request.grad_thresh)) * (res_scale ** -0.3)
|
| 221 |
+
|
| 222 |
box, std_map, grad_norm, grad_mask, landing_mask = pick_flat_patch(
|
| 223 |
depth,
|
| 224 |
patch=patch_px,
|
| 225 |
+
std_thresh=std_thresh_eff,
|
| 226 |
+
grad_thresh=grad_thresh_eff,
|
| 227 |
water_mask=water_mask_block if water_mask_block is not None else water_mask_resized,
|
| 228 |
)
|
| 229 |
if request.use_roof_mask:
|
|
|
|
| 257 |
interior_mask = np.ones_like(landing_mask, dtype=bool)
|
| 258 |
landing_mask = landing_mask & interior_mask
|
| 259 |
texture_mask = texture_norm <= max(0.0, min(1.0, request.texture_threshold))
|
| 260 |
+
safe_mask = (std_map < std_thresh_eff) & (grad_norm < grad_thresh_eff) & landing_mask & texture_mask
|
| 261 |
|
| 262 |
try:
|
| 263 |
clearance_px = max(1, int(round(request.clearance_factor * patch_px)))
|
|
|
|
| 295 |
keep |= labels == i
|
| 296 |
safe_mask = keep
|
| 297 |
|
| 298 |
+
risk_std = np.clip((std_map - std_thresh_eff) / (std_thresh_eff + 1e-6), 0.0, 1.0)
|
| 299 |
+
risk_grad = np.clip((grad_norm - grad_thresh_eff) / (grad_thresh_eff + 1e-6), 0.0, 1.0)
|
| 300 |
risk_map = np.maximum(risk_std, risk_grad) * (~safe_mask)
|
| 301 |
|
| 302 |
safe_fit = safe_mask.astype(np.float32)
|
|
|
|
| 449 |
roof_mask_enabled=request.use_roof_mask,
|
| 450 |
used_valid_center=used_valid_center,
|
| 451 |
warnings=warnings,
|
| 452 |
+
std_thresh_applied=std_thresh_eff,
|
| 453 |
+
grad_thresh_applied=grad_thresh_eff,
|
| 454 |
)
|
| 455 |
return AnalysisResult(images=layers, summary=summary)
|
| 456 |
|
app/ui.py
CHANGED
|
@@ -90,6 +90,7 @@ def _format_metrics(summary: AnalysisSummary | None) -> str:
|
|
| 90 |
f"**Hazard coverage:** {summary.hazard_pct:.1f}%",
|
| 91 |
f"**Landing center (px):** {summary.landing_center_image[0]}, {summary.landing_center_image[1]}",
|
| 92 |
f"**Footprint size:** {summary.footprint_m:.1f} m ≈ {summary.footprint_image_px}px",
|
|
|
|
| 93 |
]
|
| 94 |
if not summary.used_valid_center:
|
| 95 |
lines.append("Warning: No fully safe footprint; showing lowest-variance patch.")
|
|
|
|
| 90 |
f"**Hazard coverage:** {summary.hazard_pct:.1f}%",
|
| 91 |
f"**Landing center (px):** {summary.landing_center_image[0]}, {summary.landing_center_image[1]}",
|
| 92 |
f"**Footprint size:** {summary.footprint_m:.1f} m ≈ {summary.footprint_image_px}px",
|
| 93 |
+
f"**Effective thresholds:** std ≤ {summary.std_thresh_applied:.4f}, grad ≤ {summary.grad_thresh_applied:.3f}",
|
| 94 |
]
|
| 95 |
if not summary.used_valid_center:
|
| 96 |
lines.append("Warning: No fully safe footprint; showing lowest-variance patch.")
|