Reconstruct a surface from registered spherical views¶
Use this route when camera poses and physical scale are already known. It does not estimate registration. Start with at least three central 2:1 ERPs for the candidate reviewed profile.
1. Build the registered scene¶
import numpy as np
from panorai import EquirectangularImage
from panorai.reconstruction import (
RegisteredSphericalScene,
RegisteredSphericalView,
SphericalCameraPose,
)
views = []
for panorama_id, path, R_world_to_camera, center_world in camera_records:
views.append(
RegisteredSphericalView(
image=EquirectangularImage.open(path),
pose=SphericalCameraPose(
panorama_id=panorama_id,
rotation_world_to_panorama=np.asarray(R_world_to_camera),
center_world=np.asarray(center_world),
),
)
)
scene = RegisteredSphericalScene(views=tuple(views), units="m")
validity= can add a boolean HW mask to a view. It is intersected with the
image container’s own support and validity. Do not encode missing range or
image samples as zero.
2. Run the candidate reviewed profile¶
from panorai.applications import SphericalDenseReconstruction
method = SphericalDenseReconstruction.reviewed_profile(
voxel_size=0.05,
bounds=None,
)
result = method(scene)
if result.accepted:
mesh = result.require_success().surface
print(mesh.vertices_xyz.shape, mesh.faces.shape)
else:
print(result.reasons)
With bounds=None, bounds come from accepted range points and expand by the
TSDF truncation distance. The method fails rather than silently increasing the
voxel size when the derived volume exceeds max_voxels.
The name reviewed_profile identifies frozen parameters for future promotion.
The application still has Experimental maturity until its external scientific
and installed-wheel gates pass.
3. Inspect or alter the composition¶
pipeline = method.as_pipeline()
print(pipeline.describe())
run = pipeline.run(
scene,
retain=("estimate_multi_view_ranges", "fuse_tsdf"),
)
surface = run.output.require_success().surface
The stages are public:
from panorai.reconstruction import (
BuildRegisteredReferenceSets,
EstimateMultiViewRanges,
FuseTSDF,
ExtractSurface,
ColorizeSurface,
ValidateDenseReconstruction,
)
FuseTSDF also accepts an external MultiViewRangeSet. That is the extension
point for another range estimator; it does not change the TSDF, frame, support
or validity contracts.
4. Evaluate against a reference surface¶
from panorai.evaluation import evaluate_surface_reconstruction
metrics = evaluate_surface_reconstruction(
result.require_success().surface,
reference_mesh,
distance_threshold=2 * method.voxel_size,
)
print(metrics.accuracy_median, metrics.recall, metrics.fscore)
The evaluator uses bidirectional vertex-to-triangle distances. Freeze the distance threshold and held-out scene list before inference. Dataset download, split selection and experiment orchestration stay outside the library.
Aggregate only after every scene has been evaluated with that same frozen threshold:
from panorai.evaluation import aggregate_surface_reconstruction
report = aggregate_surface_reconstruction(
{"scene-001": metrics_001, "scene-002": metrics_002}
)
print(report.per_scene["scene-001"].fscore, report.aggregate["fscore"])
The aggregate is the unweighted median of scene-level values, so a finely tessellated scene cannot dominate a corpus result.
5. Optional Open3D conversion¶
from panorai.pcd import surface_mesh_to_open3d
o3d_mesh = surface_mesh_to_open3d(mesh)
Open3D is imported only by this adapter. It is not the canonical surface contract and is not required to import reconstruction or geometry.