| """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, |
| }, |
| } |
|
|