AccessPath / accessibilityamodal /geometry_analysis.py
anonymous-accesspath's picture
Audited anonymous-review AccessPath release
2f382c4 verified
Raw
History Blame Contribute Delete
24 kB
"""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,
},
}