"""Structured accessibility geometry constraints for 3D amodal completion. The functions in this module convert the deterministic outputs of ``accessibilityamodal.reconstruct`` into a JSON-ready report. The report is intended for navigation-risk review and downstream reconstruction code, not for visual plausibility scoring. """ from __future__ import annotations from typing import Any import cv2 import numpy as np VALID_CATEGORIES = { "stairs", "ramp", "walkway", "curb_cut", "raised_curb", "tactile_paving", "unknown", } CONTINUOUS_CATEGORIES = {"ramp", "walkway", "curb_cut", "raised_curb", "tactile_paving"} def _round(value: float | int | None, digits: int = 6) -> float | None: if value is None: return None return round(float(value), digits) def _ratio(numerator: int | float, denominator: int | float) -> float: if denominator <= 0: return 0.0 return float(numerator) / float(denominator) def normalize_category(category: str | None, geometry_mode: str) -> str: value = (category or "").strip().lower() if value == "walkable": value = "walkway" if value in VALID_CATEGORIES: return value if geometry_mode == "stairs": return "stairs" if geometry_mode == "ramp": return "ramp" if geometry_mode == "walkable": return "walkway" return "unknown" def _step_interval_consistency(edges_y: list[int]) -> str: if len(edges_y) < 3: return "unknown" gaps = np.diff(np.array(sorted(edges_y), dtype=np.float32)) mean_gap = float(np.mean(gaps)) if mean_gap <= 1e-6: return "unknown" variation = float(np.std(gaps) / mean_gap) if variation < 0.18: return "high" if variation < 0.35: return "medium" return "low" def _slope_direction_from_depth(depth: np.ndarray, target: np.ndarray) -> str: if not np.any(target): return "unknown" valid = target & np.isfinite(depth) & (depth > 0) if int(valid.sum()) < 32: return "unknown" ys = np.where(valid)[0] middle = float(np.median(ys)) upper = valid & (np.indices(valid.shape)[0] <= middle) lower = valid & (np.indices(valid.shape)[0] > middle) if int(upper.sum()) < 16 or int(lower.sum()) < 16: return "unknown" upper_depth = float(np.median(depth[upper])) lower_depth = float(np.median(depth[lower])) scale = max(abs(upper_depth), abs(lower_depth), 1e-6) if abs(upper_depth - lower_depth) / scale < 0.02: return "unknown" return "away_from_camera" if upper_depth > lower_depth else "toward_camera" def _visible_evidence( category: str, visible_mask: np.ndarray, amodal_mask: np.ndarray, hidden_mask: np.ndarray, obstacle_mask: np.ndarray, depth: np.ndarray, edges_y: list[int], stair_edge_confidence: float, stair_edge_coverage: float, completion_method: str, ) -> list[dict[str, Any]]: evidence: list[dict[str, Any]] = [] amodal_area = int(amodal_mask.sum()) visible_area = int(visible_mask.sum()) hidden_area = int(hidden_mask.sum()) obstacle_overlap = int((obstacle_mask & amodal_mask).sum()) if visible_area > 0: evidence.append( { "type": "boundary", "description": "visible target mask defines the observed support boundary", "confidence": _round(min(0.95, 0.35 + 0.60 * _ratio(visible_area, max(amodal_area, 1)))), } ) if hidden_area > 0 or obstacle_overlap > 0: evidence.append( { "type": "occlusion", "description": "hidden target support is constrained by amodal-minus-visible and obstacle overlap", "confidence": _round(min(0.95, 0.30 + 0.60 * _ratio(obstacle_overlap, max(hidden_area, 1)))), } ) if category == "stairs" and edges_y: evidence.append( { "type": "repeated_step", "description": f"{len(edges_y)} candidate stair tread/riser boundary rows detected", "confidence": _round(max(stair_edge_confidence, min(stair_edge_coverage, 1.0) * 0.75)), } ) if category in CONTINUOUS_CATEGORIES and "plane" in completion_method: evidence.append( { "type": ( "slope" if category == "ramp" else "curb_boundary" if category == "raised_curb" else "boundary" ), "description": ( "visible curb support is completed as one continuous surface without repeated steps" if category == "raised_curb" else "visible support is completed with a robust continuous-surface prior" ), "confidence": 0.70, } ) valid_depth = amodal_mask & np.isfinite(depth) & (depth > 0) if int(valid_depth.sum()) >= 128: rows = np.where(valid_depth)[0] row_span = max(int(rows.max() - rows.min()), 1) depth_values = depth[valid_depth] depth_span = float(np.percentile(depth_values, 90) - np.percentile(depth_values, 10)) scale = max(float(np.median(depth_values)), 1e-6) if depth_span / scale > 0.02 and row_span > 8: evidence.append( { "type": "depth_gradient", "description": "target depth varies coherently across the support region", "confidence": _round(min(0.80, depth_span / scale)), } ) return evidence def _connected_hidden_regions( category: str, hidden_mask: np.ndarray, completion_method: str, mean_hidden_confidence: float, edges_y: list[int], ) -> list[dict[str, Any]]: if not np.any(hidden_mask): return [] labels_count, labels, stats, _ = cv2.connectedComponentsWithStats( hidden_mask.astype(np.uint8), connectivity=8 ) regions: list[dict[str, Any]] = [] areas = [ (idx, int(stats[idx, cv2.CC_STAT_AREA])) for idx in range(1, labels_count) if int(stats[idx, cv2.CC_STAT_AREA]) > 0 ] areas.sort(key=lambda item: item[1], reverse=True) total = max(int(hidden_mask.sum()), 1) for serial, (idx, area) in enumerate(areas[:12], start=1): if category == "stairs" and len(edges_y) >= 2: basis = "repeated_steps" description = "complete the hidden support by repeating the visible tread/riser pattern" elif category in {"ramp", "walkway", "curb_cut", "raised_curb"} and "plane" in completion_method: basis = "plane_continuity" description = ( "continue one curb top/face support through the hidden region without stair bands" if category == "raised_curb" else "project hidden pixels onto the fitted visible support plane" ) elif category == "tactile_paving": basis = "boundary_continuity" description = "preserve the narrow tactile strip footprint through the hidden region" elif "inpaint" in completion_method: basis = "depth_interpolation" description = "use local depth interpolation because stronger structural evidence is unavailable" else: basis = "uncertain" description = "insufficient structural evidence for confident hidden completion" regions.append( { "region_id": f"hidden_{serial}", "completion_basis": basis, "description": description, "confidence": _round(min(0.95, mean_hidden_confidence * (0.50 + 0.50 * area / total))), } ) return regions def _amodal_quality( target_present: bool, visible_mask: np.ndarray, amodal_mask: np.ndarray, hidden_mask: np.ndarray, mean_hidden_confidence: float, used_regular_fallback_edges: bool, ) -> str: if not target_present: return "bad" amodal_area = int(amodal_mask.sum()) visible_ratio = _ratio(int(visible_mask.sum()), amodal_area) hidden_ratio = _ratio(int(hidden_mask.sum()), amodal_area) if visible_ratio < 0.02: return "bad" if used_regular_fallback_edges or mean_hidden_confidence < 0.30: return "uncertain" if visible_ratio >= 0.20 and (hidden_ratio == 0.0 or mean_hidden_confidence >= 0.50): return "good" if visible_ratio >= 0.05: return "partial" return "uncertain" def _geometry_model_type(category: str, geometry_mode: str) -> str: if category == "raised_curb": return "raised_curb_prism" if category == "stairs" or geometry_mode == "stairs": return "stair_steps" if category == "curb_cut": return "curb_cut_planes" if category == "tactile_paving": return "tactile_strip" if category in {"ramp", "walkway"}: return "single_plane" return "uncertain" def _hidden_depth_rule(category: str, completion_method: str) -> str: if category == "stairs": return "repeat_stair_geometry" if category in {"ramp", "walkway", "curb_cut", "raised_curb", "tactile_paving"} and "plane" in completion_method: return "fit_to_plane" if "inpaint" in completion_method: return "interpolate_between_boundaries" return "uncertain" def _recommended_action(category: str, geometry_confidence: float) -> tuple[str, str]: if geometry_confidence < 0.30: return "manual_review", "Evidence is weak; do not generate a navigation mesh without review." if category == "stairs": return ( "stair_regularization", "Fit repeated tread/riser bands and complete hidden depth by stair periodicity.", ) if category == "curb_cut": return ( "curb_cut_plane_decomposition", "Decompose sidewalk, road, and sloped transition planes before meshing.", ) if category == "raised_curb": return ( "raised_curb_prism_fit", "Fit one continuous curb top and vertical face, then extrude a solid prism without repeated stair bands.", ) if category == "tactile_paving": return ( "tactile_centerline_completion", "Keep a narrow tactile strip, continue its centerline, and avoid expanding to the full sidewalk.", ) if category in {"ramp", "walkway"}: return ( "plane_fit", "Fit visible support only, reject obstacle/depth outliers, and project hidden pixels to the plane.", ) return "manual_review", "Unknown category; keep the sample out of automatic 3D completion." def build_accessibility_geometry_analysis( *, sample_id: str | None, category: str | None, geometry_mode: str, visible_mask: np.ndarray, amodal_mask: np.ndarray, hidden_mask: np.ndarray, obstacle_mask: np.ndarray, depth: np.ndarray, completed_depth: np.ndarray, confidence: np.ndarray, completion_method: str, edges_y: list[int], stair_edge_slope: float, edge_source: str, stair_edge_confidence: float, stair_edge_coverage: float, used_regular_fallback_edges: bool, visible_depth_unchanged: bool, completed_target_depth_finite: bool, point_cloud_vertices: int, mesh_vertices: int, mesh_faces: int, ) -> dict[str, Any]: """Build the JSON-ready geometry constraint report.""" normalized_category = normalize_category(category, geometry_mode) target_present = bool(np.any(amodal_mask) or np.any(visible_mask)) hidden_nonempty = bool(np.any(hidden_mask)) hidden_confidence_values = confidence[hidden_mask] if hidden_nonempty else np.array([1.0], dtype=np.float32) mean_hidden_confidence = float(np.mean(hidden_confidence_values)) if hidden_confidence_values.size else 0.0 visible_area = int(visible_mask.sum()) amodal_area = int(amodal_mask.sum()) hidden_area = int(hidden_mask.sum()) obstacle_area = int(obstacle_mask.sum()) obstacle_target_overlap = int((obstacle_mask & amodal_mask).sum()) obstacle_visible_overlap_ratio = _ratio(int((obstacle_mask & visible_mask).sum()), max(visible_area, 1)) if normalized_category == "stairs": structure_confidence = float(stair_edge_confidence) if used_regular_fallback_edges: structure_confidence = min(structure_confidence, 0.30) elif normalized_category in CONTINUOUS_CATEGORIES: structure_confidence = 0.72 if "plane" in completion_method else 0.38 else: structure_confidence = 0.20 target_evidence_score = min(1.0, 0.25 + 0.75 * _ratio(visible_area, max(amodal_area, 1))) mesh_score = 1.0 if point_cloud_vertices > 0 and mesh_vertices > 0 and mesh_faces > 0 else 0.25 geometry_confidence = min( 0.99, max( 0.0, 0.35 * structure_confidence + 0.25 * mean_hidden_confidence + 0.20 * target_evidence_score + 0.20 * mesh_score, ), ) if not target_present: geometry_confidence = 0.0 if not completed_target_depth_finite: geometry_confidence *= 0.50 amodal_quality = _amodal_quality( target_present, visible_mask, amodal_mask, hidden_mask, mean_hidden_confidence, used_regular_fallback_edges, ) if amodal_quality in {"uncertain", "bad"}: geometry_confidence = min(geometry_confidence, 0.55 if amodal_quality == "uncertain" else 0.20) visible_evidence = _visible_evidence( normalized_category, visible_mask, amodal_mask, hidden_mask, obstacle_mask, depth, edges_y, stair_edge_confidence, stair_edge_coverage, completion_method, ) occluders = [] if obstacle_area > 0: occluders.append( { "class": "other", "overlaps_target": bool(obstacle_target_overlap > 0), "should_exclude_from_mesh": True, "description": ( "aggregate obstacle mask overlaps the target support" if obstacle_target_overlap > 0 else "aggregate obstacle mask is outside the target support" ), } ) hidden_regions = _connected_hidden_regions( normalized_category, hidden_mask, completion_method, mean_hidden_confidence, edges_y, ) plane_applies = normalized_category in CONTINUOUS_CATEGORIES stair_applies = normalized_category == "stairs" model_type = _geometry_model_type(normalized_category, geometry_mode) slope_direction = _slope_direction_from_depth(completed_depth, amodal_mask) expected_surface = ( "segmented" if normalized_category in {"stairs", "curb_cut"} else "continuous_top_with_vertical_face" if normalized_category == "raised_curb" else "sloped" if normalized_category == "ramp" else "flat" if normalized_category in {"walkway", "tactile_paving"} else "segmented" ) hidden_depth_rule = _hidden_depth_rule(normalized_category, completion_method) step_lines = [ { "y": int(y), "slope": _round(stair_edge_slope), "source": edge_source, } for y in edges_y ] if normalized_category == "stairs": passable = False risk_level = "high" if geometry_confidence >= 0.30 else "unknown" risks = ["stairs present", "wheeled passability is blocked or requires alternate route"] if used_regular_fallback_edges: risks.append("stair geometry relies on fallback edge positions") reason_short = "Stair geometry is detected; treat as high risk for wheeled accessibility." elif normalized_category == "raised_curb": passable = False risk_level = "high" if geometry_confidence >= 0.30 else "unknown" risks = [ "raised curb is a non-walkable level-change barrier", "wheeled passability is blocked or requires a curb cut or alternate route", ] reason_short = "A raised curb is present; model it as a continuous high obstacle, not as stairs." elif geometry_confidence < 0.30 or amodal_quality in {"uncertain", "bad"}: passable = False risk_level = "unknown" risks = ["hidden support geometry is uncertain"] reason_short = "Evidence is insufficient for an automatic passability decision." else: obstacle_intrusion = _ratio(obstacle_target_overlap, max(amodal_area, 1)) passable = obstacle_intrusion < 0.25 and visible_depth_unchanged risk_level = "medium" if hidden_nonempty or obstacle_intrusion > 0.10 else "low" risks = [] if hidden_nonempty: risks.append("hidden region may contain unresolved hazards") if obstacle_intrusion > 0.10: risks.append("obstacle intrudes into the target support") if not risks: risks.append("no major geometry risk from available masks") reason_short = "Continuous support appears geometrically consistent, but monocular depth is not metric truth." triangle_fan_passed = True if normalized_category in CONTINUOUS_CATEGORIES: triangle_fan_passed = bool(mesh_faces > 0 and ("plane" in completion_method or "continuous" in completion_method)) stair_not_smoothed_passed = True if normalized_category == "stairs": stair_not_smoothed_passed = bool( "stair" in completion_method and len(edges_y) >= 2 and stair_edge_confidence > 0.0 ) raised_curb_not_stepped_passed = True if normalized_category == "raised_curb": raised_curb_not_stepped_passed = bool( geometry_mode != "stairs" and "stair" not in completion_method and not edges_y ) obstacle_exclusion_passed = obstacle_visible_overlap_ratio <= 0.02 hidden_follows_amodal = bool(np.array_equal(hidden_mask, amodal_mask & ~visible_mask)) next_step, instruction = _recommended_action(normalized_category, geometry_confidence) return { "sample_id": sample_id or "", "category": normalized_category, "target_present": target_present, "geometry_confidence": _round(geometry_confidence), "visible_evidence": visible_evidence, "occluders": occluders, "amodal_completion": { "target_amodal_region_quality": amodal_quality, "hidden_regions": hidden_regions, "do_not_complete_regions": [ { "description": "obstacle mask outside the hidden target support", "reason": "foreground occluder geometry is not part of the accessible support surface", }, { "description": "outside target_amodal mask", "reason": "completion must be clipped to reviewed or inferred target support", }, { "description": "thin mask boundary band, shadows, reflections, and wall/railing regions", "reason": "these pixels are unstable depth or non-walkable geometry", }, ], }, "geometry_prior": { "model_type": model_type, "plane_prior": { "applies": plane_applies, "slope_direction_image": slope_direction, "expected_surface": expected_surface, "fit_visible_only_then_extend_to_hidden": True, "reject_depth_outliers": True, }, "stairs_prior": { "applies": stair_applies, "step_direction_image": slope_direction, "visible_step_lines": step_lines, "estimated_step_count": len(edges_y) + 1 if edges_y else None, "step_interval_consistency": _step_interval_consistency(edges_y), "hidden_completion_rule": "repeat_visible_tread_riser_pattern", }, "curb_cut_prior": { "applies": normalized_category == "curb_cut", "has_sidewalk_plane": None if normalized_category != "curb_cut" else "plane" in completion_method, "has_road_plane": None, "has_sloped_transition": None if normalized_category != "curb_cut" else "plane" in completion_method, "boundary_or_hinge_lines": [], }, "raised_curb_prior": { "applies": normalized_category == "raised_curb", "is_walkable_surface": False if normalized_category == "raised_curb" else None, "hazard_class": "high_obstacle" if normalized_category == "raised_curb" else None, "has_continuous_top_surface": ( None if normalized_category != "raised_curb" else "plane" in completion_method ), "vertical_face_required": True if normalized_category == "raised_curb" else None, "repeated_step_profile_allowed": False if normalized_category == "raised_curb" else None, "boundary_or_hinge_lines": [], }, }, "depth_completion_constraints": { "trusted_depth_regions": [ "visible target surface excluding obstacle and mask boundary noise", ], "untrusted_depth_regions": [ "obstacle mask", "hidden mask raw depth", "thin railings", "mask boundary band", "specular/shadow regions", ], "hidden_depth_rule": hidden_depth_rule, "mesh_generation_rule": ( "fit one continuous curb top, add a vertical face, and forbid repeated stair bands" if normalized_category == "raised_curb" else "clip mesh by target_amodal mask, remove obstacle geometry, regularize hidden region" ), }, "passability_assessment": { "is_likely_passable": passable, "risk_level": risk_level, "risks": risks, "reason_short": reason_short, }, "failure_checks": [ { "check": "ramp_or_walkway_should_not_collapse_into_triangle_fan", "passed": triangle_fan_passed, "fix_if_failed": "use RANSAC plane fitting on visible target and project hidden mask to fitted plane", }, { "check": "stairs_should_not_be_smoothed_into_single_ramp", "passed": stair_not_smoothed_passed, "fix_if_failed": "fit repeated tread/riser geometry", }, { "check": "raised_curb_should_not_use_repeated_stair_bands", "passed": raised_curb_not_stepped_passed, "fix_if_failed": "switch to continuous-surface completion and fit one raised curb prism", }, { "check": "obstacles_should_not_be_included_in_accessible_mesh", "passed": obstacle_exclusion_passed, "fix_if_failed": "subtract obstacle mask before point cloud and mesh creation", }, { "check": "hidden_region_should_follow_amodal_mask_not_visible_mask_only", "passed": hidden_follows_amodal, "fix_if_failed": "use target_amodal and hidden masks as reconstruction constraints", }, ], "recommended_3d_action": { "next_step": next_step, "short_instruction_for_reconstruction_code": instruction, }, }