Skip to content

recon: handoff to the server team - #20

Merged
AidenJohnston merged 13 commits into
mainfrom
t3/recon
Sep 27, 2026
Merged

AidenJohnston merged 13 commits into
mainfrom
t3/recon

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What changed

Adds recon/, a reconstruction worker, and recon/HANDOFF.md for the server team, who now own photos to 3D model to criteria.

The worker takes the app's scan bundle (scene.json and keyframe photos with AR poses) or a Measure Lab session. It builds depth per keyframe: LiDAR when at least two keyframes carry it, otherwise MoGe-2 rescaled per photo by features triangulated with the AR poses. A keyframe that a LiDAR capture saved without depth gets no depth, so what only it saw stays unobserved instead of being filled in by a coarser source. The worker then fuses the depth into one volume and writes:

  • a colored mesh (model.glb, in the meter's frame);
  • the fitted wall and ground (geometry.json, feet);
  • occlusion-aware coverage (coverage.json);
  • an updated scene.json, posted to the placement server, with its answer and site plan saved. A refused site plan fails the run, and the previous run's result.json and site-plan.svg are deleted before posting.

make run BUNDLE=… OUT=… runs it. The manifest is untrusted: the worker refuses any image, depth or confidence file it names that lies outside the manifest's folder, whether by an absolute path, .. or a symlink. Keyframe ids name cache files, so an id must be a single file name, and each cache file must resolve inside the cache folder.

Coverage counts a cell as observed only when, from two positions, the frame's own depth puts a surface at the cell and the fused volume shows nothing in front of it. A depth that is merely farther, as through a window or an open gate, doesn't count. That is the depth test the evals found necessary: the app's coverage map claims wall no photo saw (PR #12, METHODS.md section 7). Facing gaps and clear distances are measured from the reconstructed wall's front, so in front of a pilaster they start at its face.

When the worker replaces the tapped wall with the reconstructed one, it labels the wall's source as mesh (LiDAR) or plane (photos only) and sends no plus_minus_ft. The server then applies that source's default error plus its drift per foot walked from the meter. An explicit bound would switch the drift off: a LiDAR wall 30 ft from the meter would keep 0.5 ft instead of 5.3 ft.

The ground is fitted as a plane and evaluated at the meter's foot. When RANSAC splits a wall at a doorway into collinear stretches, the worker takes the one nearest the meter.

The handoff collects what was measured, what to reuse with exact commands, the pitfalls, recommendations for the 3D path, the interfaces, and one known limit for the server team: ground is sampled only 10 ft out, while the public pool rule asks for at least 12.3 ft, so that check stays UNSURE. Its "Known defects" section lists six confirmed review findings that remain unfixed, each with a file and lines, a repro and what a fix needs. Issues #95 and #96 track two more.

Known defects: Unbounded ZIP processing and EXIF geometry. Both remain unfixed in this draft worker, which the app and demo do not use.

Verification

  • make accept on ETH3D electro against its laser scan (results). The 0.5 ft coverage bar was committed before the first acceptance run (b875f05).

    • Laser as LiDAR: 0.07 ft of the 22.5 ft claimed was unseen; wall pair error p90 1.8 in over 1–3 m spans (2.1 in at c6a8be3).
    • MoGe-2 photos only: 0.00 ft of 19.9 ft claimed was unseen; wall pair error p90 1.5 in (0.39 ft of 12.8 ft and 2.8 in at c6a8be3).

    Each laser point is now scored from the nearest photo in which the laser shows it unoccluded, not the nearest photo regardless. The laser path's wall did not move, so its 2.1 to 1.8 in change is that fix alone. Both paths fit the same wall. The MoGe-2 path now fits a 20.5 ft stretch of it instead of 15.7 ft, because fitting the ground as a plane moved RANSAC's split, so its row mixes both causes. Two runs gave identical numbers.

  • The server's own parser on t3/server (14dd051) accepts scene.json built by the worker from both ETH3D depth paths. It reads the LiDAR wall as mesh at 0.50 / 2.10 / 5.30 ft error at 0 / 10 / 30 ft, and the photos-only wall as plane at 0.75 / 2.35 / 5.55 ft.

  • At c6a8be3 the ADVIO replay and the app's Simulator bundle each ran end to end to a server result (manual_review); they were not rerun at this head. The Simulator bundle needs --move-meter, because its replayed wall has no reconstructed surface within 4 ft.

  • make test: 81 unit tests with ruff, run by the path-filtered Recon checks job. They cover the adapters, depth helpers, fusion geometry and normals, the coverage rule's surface requirement and 10 ft ground reach, the wall's source with no bound, facing distance from a proud face, per-frame LiDAR, the ground plane on asymmetric sloped support, the wall stretch past a doorway, the site-plan refusal, the acceptance view choice, manifest paths that escape the packet (absolute, ../ and symlink, for each file field in both formats), unsafe keyframe ids and cache files outside the cache folder, the content-keyed MoGe-2 cache and the GLB format. Each new test fails when its fix is reverted.

  • Peak memory at this head: 1.04 and 1.13 GB for the two acceptance runs, with MoGe-2's depth cached so the model did not run. MoGe-2 inference itself peaked at 2.95 GB at 640 px in an earlier run.

Risk

  • The handoff's "modern phone" pose numbers are synthetic: ETH3D's true poses degraded by a simulated 2% scale error plus 1 cm and 0.1° of noise, not a measured phone.
  • The LiDAR path reads Measure Lab's depth layout. The app's own LiDAR fields are not defined yet, so it has run only on the laser scan standing in for LiDAR.
  • The GLB's file type is registered to macOS's SceneKit Quick Look extension, but Quick Look was not opened on it here.
  • Which stretch of a MoGe-2 wall the worker picks is fragile: a 0.4° ground change moved it. The ground band is untested.
  • No dataset files, images or Base-private content are committed.

Checklist

  • The scope is focused: recon/, a path-filtered .github/workflows/recon.yml, and one licence row in docs/04-prior-art-and-codes.md
  • I explained what changed and why
  • N/A: before/after images. There is no app UI change.
  • N/A: video. There is no motion or interaction.

🤖 Generated with Claude Code

SamGu-NRX and others added 2 commits September 26, 2026 10:45
…riteria

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…o the server team

The worker turns a scan bundle or Measure Lab session into a GLB mesh, fitted wall
and ground, occlusion-aware coverage and an updated scene.json posted to the
placement server. On ETH3D electro it claims 0.46 ft (laser as LiDAR) and 0.39 ft
(MoGe-2 rescaled with the poses) of wall no photo saw, under the pre-registered
0.5 ft. HANDOFF.md collects everything measured overnight for the server team.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: daf7d4e3-2d3f-4bb6-9045-0d13171e6863

📥 Commits

Reviewing files that changed from the base of the PR and between a5e92f5 and 74f7a98.

⛔ Files ignored due to path filters (2)
  • recon/models/uv.lock is excluded by !**/*.lock
  • recon/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .github/workflows/recon.yml
  • docs/04-prior-art-and-codes.md
  • recon/.gitignore
  • recon/HANDOFF.md
  • recon/Makefile
  • recon/README.md
  • recon/models/.gitignore
  • recon/models/moge_depth.py
  • recon/models/pyproject.toml
  • recon/pyproject.toml
  • recon/recon/__init__.py
  • recon/recon/__main__.py
  • recon/recon/capture.py
  • recon/recon/coverage.py
  • recon/recon/depth.py
  • recon/recon/eth3d.py
  • recon/recon/fusion.py
  • recon/recon/geometry.py
  • recon/recon/glb.py
  • recon/recon/pipeline.py
  • recon/recon/scene.py
  • recon/recon/server.py
  • recon/recon/triangulate.py
  • recon/results/eth3d_electro.json
  • recon/results/eth3d_electro.md
  • recon/tests/test_triangulate.py
  • recon/tests/test_worker.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size:XXL 1,000+ effective changed lines. label Sep 26, 2026

@SamGu-NRX SamGu-NRX left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main-track review of c962e09. Verified findings below need writer fixes or a supported response. No datasets, model inference or heavy builds were run. This is not a merge-readiness approval.

Comment thread recon/recon/coverage.py
Comment thread recon/recon/scene.py
Comment thread recon/recon/eth3d.py Outdated
Comment thread recon/recon/pipeline.py Outdated
Comment thread recon/recon/coverage.py Outdated
SamGu-NRX and others added 4 commits September 26, 2026 19:41
…ts own error; content-keyed MoGe cache

- coverage.seen_by: a frame sees a wall or ground sample only when its depth there lies within
  the tolerance of the sample's depth. A farther depth only showed nothing occludes the sample,
  so an opening bridged by the wall line could export as observed wall.
- coverage.wall_and_ground: the ground grid ends at 3.048 m, so the 10 ft level is reachable.
- scene.build: the reconstructed wall is source "mesh" with the depth source's plus_minus_ft
  (MoGe-2 0.56 ft, LiDAR the server's 0.5 ft mesh default), replacing a tap's source and bound.
- eth3d.false_observed: a pixel with no laser return is unseen, not seen.
- depth.moge: the cache folder is a hash of every frame's image bytes, pose and intrinsics and
  the model script, lockfile and preprocessing; a stored key that differs is recomputed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ixes

Both rows still pass the 0.5 ft bar. Laser as LiDAR: claimed 22.5 of 31.9 ft (was 25.5),
false-observed 0.07 ft (was 0.46). MoGe-2: claimed 12.8 of 15.8 ft (was 14.8), false-observed
0.39 ft (unchanged). Geometry rows are unchanged. The oracle now counts a pixel with no laser
return as unseen, which can only raise false-observed for a fixed claim, so the laser row's drop
comes from the worker no longer claiming 10.15-10.48 ft once each sample needs a surface there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Walk-to-walk spread is disagreement between ARKit and the reference, not a
  bound on ARKit's error.
- The modern-phone pose setting is labelled synthetic (2% scale, 1 cm, 0.1
  degrees; evals/ar_poses.py) wherever it appears.
- Answer 3 and 4 numbers follow the evals rescoring on comparable seed groups.
- Acceptance lines follow the rerun after the coverage and oracle fixes.
- Section references point at experiments/evals/METHODS.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T08:16:43.879782Z 74f7a98 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6a8be3ef9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread recon/recon/coverage.py Outdated
Comment thread recon/recon/depth.py Outdated
Comment thread recon/recon/geometry.py Outdated
Comment thread recon/recon/coverage.py
CELL_M = 0.1524 # half a foot, the app's cell
WALL_TOP_M = 1.9812 # 6.5 ft, the headroom the export's wall band claims
WALL_ROWS = 9 # about 0.25 m apart, so a box mounted between rows cannot slip through
GROUND_MAX_M = 3.048 # 10 ft out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cover the required 13-foot ground range

Ground sampling stops at 10 ft, and observed() consequently cannot emit evidence beyond that distance, while docs/00-overview.md states that pool and driveway checks require up to 13 ft of photographed ground. Those checks therefore remain UNSURE even when the capture contains suitable views between 10 and 13 ft; obtain the required range from the rules rather than fixing this rule-dependent limit in reconstruction code.

AGENTS.md reference: AGENTS.md:L33-L33

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed completeness gap, with an important qualification: a fixed 13 ft cap is not a general repair. The server requests D + rule clearance + position error, so even the public pool rule exceeds 10 ft before drift. This currently produces UNSURE rather than a demonstrated unsafe pass. The manager must settle a rule/request-driven range contract with the server owner; do not substitute another unexplained reconstruction constant.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed here, by the manager's decision. The limit errs toward UNSURE, never toward a pass: ground past 10 ft is simply not reported as observed. A 13 ft constant would be another unexplained number, because the server's request is D + r + e, which already exceeds 12.3 ft for the public pool rule before drift. The reach should come from the server's request.

1c2686b records this in recon/HANDOFF.md, section 5, as a known limit for the server team, who own that request.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at1c2686b. Accepted as an explicit scope deferral, not a code fix. The manager directed this PR to retain10ft reach and document the conservative UNSURE outcome. HANDOFF now states the server needs D+r+e, explains the pool limitation, and assigns the future requested-range contract to the server team. Recon CI reports38 passing tests plus lint and formatting.

Comment thread recon/Makefile
Comment thread recon/recon/geometry.py Outdated
Comment thread recon/recon/eth3d.py Outdated
Comment thread recon/recon/server.py Outdated
Comment thread recon/models/moge_depth.py
…AR, visible-view scoring

- scene.json: the fitted wall carries source mesh (LiDAR) or plane (photos only) and no
  plus_minus_ft, so the server applies its per-source default plus drift. An explicit bound
  switched the drift off (0.5 ft at 30 ft instead of 5.3 ft).
- Facing gap and clear distance are measured from the reconstructed wall front, not the plane.
- A keyframe saved without LiDAR depth gets none; the other frames keep their LiDAR.
- The ground is kept as a plane and evaluated at the meter's foot.
- choose_wall picks the matching stretch whose extent is nearest the meter.
- ETH3D scores each laser point from the nearest view that sees it by the laser's visibility.
  Rerun: laser pair p90 2.1 -> 1.8 in, MoGe-2 2.8 -> 1.5 in (a different stretch of the same
  wall); false-observed 0.07 and 0.00 ft.
- A refused site plan raises, and stale result.json and site-plan.svg are removed first.
- HANDOFF: the 10 ft ground reach as a known limit, evals intervals after the seed-group repair.
- docs/04: MoGe-2 weights at the pinned revision are MIT per Hugging Face's metadata.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c2686b03f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread recon/recon/coverage.py
Comment thread recon/recon/coverage.py
GROUND_MAX_M = 3.048 # 10 ft out
FACING_MAX_M = 6.0
FACING_BAND_M = (0.3, 1.8)
OVERHEAD_OUT_M = (0.1, 0.56) # above the battery's footprint (1.83 ft deep)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read the overhead footprint from the selected battery rule

For any configured battery deeper than 1.83 ft, such as the older 3 × 3 ft units documented in docs/00-overview.md, the worker scans only the first 0.56 m and can report overhead clearance while an obstruction intersects the unsampled outer footprint; the initial 0.1 m next to the wall is also never tested. The server cannot detect this mismatch because the emitted clearance does not describe the sampled footprint, so pass the selected model's footprint from the sourced rules instead of hardcoding this range.

AGENTS.md reference: AGENTS.md:L33-L33

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed P1 with model dependence. Overhead samples omit the first0.10m and stop near0.56m, which does not cover a deeper selected battery. A low eave in an unsampled strip can be missed. The manager directed a Known defect entry for this draft handoff; no generic footprint repair is claimed.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recorded, not fixed, in d072729: recon/HANDOFF.md section 6, "Known defects", second entry. It gives recon/coverage.py:42, 231-242, a repro (an eave 0.05 m out, or 0.7 m out over a 3 ft deep battery, goes unfound), and what a fix needs: the footprint of the battery the chosen rule places, passed from the server's rules or request.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved as a documented draft-handoff deferral, not a code fix, under the manager's explicit decision. At d072729, HANDOFF.md section 6 lists this confirmed defect, its source locations, a reproduction and the needed future repair. It explicitly says none of these defects is fixed. This unused worker remains draft for Sam or Hunter to decide; the known behavior is not accepted for production placement decisions.

Comment thread recon/recon/coverage.py
Comment thread recon/recon/scene.py
Comment thread recon/recon/pipeline.py
# 1 to 3 m spans at an assumed 2% pose error, 6.7 in (experiments/evals README section 3 on
# t3/evals). LiDAR: no measured value exists here; 0.5 ft is the server's default for mesh
# measurements (errors.mesh_ft, a day-1 estimate).
PLUS_MINUS_FT = {"moge2-triangulated": 0.56, "lidar": 0.5}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use an edge-error bound for photo clearances

In the photos-only path, this explicit 0.56 ft bound is attached to facing gaps and overhead clearances, but it comes from the upper interval for wall pair error rather than the edge measurements used to locate those clearances. The repository's measured evidence says edges remain at 8 in or worse, while 0.56 ft is only 6.72 in; because the server treats plus_minus_ft as the uncertainty when deciding PASS, FAIL, or UNSURE, borderline photo-derived clearances can receive a definitive result with an understated error bar. Use the measured edge-error bound or leave the measurement unresolved until one is available.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed P1, but substituting an edge-pair percentile is not a justified numerical repair. The0.56ft comes from surface-interior pair-error p90 CI; neither that nor the edge-pair CI calibrates the fused-volume clearance estimator. Final server903d86f can PASS7.10±0.56ft against6.50ft. Omitting the bound defaults to0.5ft and does not solve this. The manager directed a Known defect entry until an accepted estimator uncertainty model or unresolved gate exists.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recorded, not fixed, in d072729: recon/HANDOFF.md section 6, "Known defects", fourth entry. It gives recon/pipeline.py:29-35 and states the bound's source: the surface pair-error p90 interval, not a calibration of the fused boundary. It quotes surface 5.64 in [4.63, 6.66] and edges 9.65 in [4.93, 15.01]. The repro is a photos-only overhead clearance of 7.10 ft passing the 6.5 ft headroom rule. A fix needs the clearance estimator's own measured uncertainty, or a gate that returns the check unresolved. No edge percentile was substituted.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved as a documented draft-handoff deferral, not a code fix, under the manager's explicit decision. At d072729, HANDOFF.md section 6 lists this confirmed defect, its source locations, a reproduction and the needed future repair. It explicitly says none of these defects is fixed. This unused worker remains draft for Sam or Hunter to decide; the known behavior is not accepted for production placement decisions.

Comment thread recon/recon/capture.py Outdated
Comment thread recon/recon/coverage.py
SamGu-NRX and others added 2 commits September 27, 2026 00:03
…efects

- capture.inside refuses an img, depth.file or depth.confidenceFile that is absolute, has a
  `..` component, or resolves outside the manifest's folder through a symlink (UnsafePath),
  in both the scan bundle and the Measure Lab adapter. Tests cover all three references in
  both formats.
- HANDOFF: a Known defects section for four confirmed review findings, links to issues
  #95 and #96, and LiDAR's reach cost corrected to 8.9 ft.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d072729bcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread recon/recon/depth.py Outdated
Comment thread recon/recon/coverage.py
Comment thread recon/recon/pipeline.py
Comment thread recon/recon/depth.py
SamGu-NRX and others added 2 commits September 27, 2026 00:24
- capture.frame_id refuses a keyframe id that is not one file-name component (letters, digits,
  `.`, `_`, `-`; not `.` or `..`) with BadFrameId, in the scan bundle and the Measure Lab
  adapter. The MoGe-2 cache names files after ids, so `/tmp/x` or `../../x` wrote outside it.
- HANDOFF known defects: partly occupied slabs counted as clear, and LiDAR without a
  confidence map fused unfiltered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ource lines

- depth.cache_file refuses an upright JPEG or depth NPZ path that does not resolve directly
  inside the cache folder (UnsafePath), a second check behind capture.frame_id for any Capture
  built without the readers.
- HANDOFF: the two new defect entries cite coverage.py:223,235 and depth.py:90-92.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 294c0fd88f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread recon/recon/capture.py
Depth maps and poses are keyed by id while coverage walks every frame
record, so two keyframes sharing an id let one depth view count from two
camera positions and turned two-view coverage from false to true.
Capture.__post_init__ now raises DuplicateFrameId naming the id, for both
readers and any capture built directly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74f7a98f52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread recon/recon/capture.py
Comment thread recon/recon/depth.py
Comment thread recon/recon/depth.py
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@greptileai

@greptile-apps

greptile-apps Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 0/5

[Medium risk] Adds a new 3D reconstruction worker module with CI.

The PR does not appear safe to merge while reconstructed clearance and coverage can settle checks on insufficient evidence, and EXIF-oriented captures can fail or be misaligned.

Fix All in CodexFindings

  1. P1 EXIF rotates sensor images ▶
  2. P1 Security Unbounded ZIP extraction ▶
  3. P1 One view claims clearance ▶
  4. P1 Overhead strip misses obstructions ▶
  5. P1 Old wall measurements survive ▶
  6. P1 Clearance error is uncalibrated ▶
  7. P1 Thin obstructions count as clear ▶
  8. P1 Missing confidence bypasses filtering ▶
  9. P1 Ground samples ignore slope ▶
  10. P1 Concurrent cache writes collide ▶
  11. P1 Old results survive reruns ▶
  12. P2 Observed cells silently disappear ▶
Fix with agent prompt
### Issue 1
recon/recon/depth.py:99
If a scan JPEG has EXIF orientation, this read can rotate its pixels even though the keyframe dimensions, intrinsics and LiDAR depth describe the unrotated sensor image. A portrait image can fail the dimension check; a 180° rotation can pass that check but misalign the image with its pose and depth. Read the sensor pixels without applying EXIF orientation.

```suggestion
    img = cv2.imread(str(frame.image), cv2.IMREAD_COLOR | cv2.IMREAD_IGNORE_ORIENTATION)
```

### Issue 2
recon/recon/capture.py:undefined-130
An untrusted scan ZIP reaches `extractall` after only its entry names are checked. A small archive with a very large decompressed member can fill the worker’s disk before manifest validation runs, stopping reconstruction. Bound the total expanded size and entry count before extraction.

**How this was verified:** The capture path reaches unrestricted extraction, and ZIP members’ expanded sizes are never checked.

### Issue 3
recon/recon/coverage.py:undefined-187
Wall and ground coverage require views from positions at least 0.25 m apart, but free-space coverage accepts voxels seen by just one frame. If only one position sees the space in front of a cell, the worker can send facing or overhead clearance that the two-position rule would reject, allowing the server to settle a check on insufficient evidence.

### Issue 4
recon/recon/coverage.py:undefined-231
The overhead test samples only 0.10–0.56 m in front of the wall, rather than the entire area a placed battery can occupy. An eave close to the wall or an obstruction farther out over the battery is missed, so the cell can be reported as having clear headroom despite that obstruction.

### Issue 5
recon/recon/scene.py:50-57
When reconstruction rotates a bundle’s wall around a meter that moves at most 5 cm, this branch keeps the phone’s old facing and overhead measurements. It then sends those old spans alongside measurements referenced to the new wall, so the server can evaluate stale gaps or clearances as current.

### Issue 6
recon/recon/pipeline.py:undefined-35
Photos-only facing and overhead measurements receive a ±0.56 ft bound derived from errors over surface-point spans, not from the accuracy of the reconstructed clearance boundary. A measured 7.10 ft overhead clearance is therefore treated as clearing a 6.5 ft minimum after subtracting 0.56 ft, although that metric does not establish the boundary’s error. Calibrate the clearance estimator or leave these checks unresolved.

### Issue 7
recon/recon/coverage.py:undefined-223
A slab must be at least 20% occupied for two consecutive steps before this loop reports an obstruction. At 5 cm voxels, a rail filling one of the facing band’s roughly 31 height rows stays below that threshold, so `facing_clear` continues past a reconstructed obstruction and the scene can claim unobstructed space.

### Issue 8
recon/recon/depth.py:90-92
The capture reader accepts a LiDAR depth file without `confidenceFile`, but this code filters low-confidence samples only when that map exists. Such a frame fuses every positive depth, including samples the worker’s LiDAR policy says to discard, and can turn unreliable surfaces into geometry or coverage evidence.

### Issue 9
recon/recon/coverage.py:165-166
The worker fits a ground plane, but these coverage samples all use the ground height at the meter. On sufficiently sloped terrain, the samples sit above or below the photographed surface; the depth test can then reject ground that was actually seen and report the wrong ground reach.

### Issue 10
recon/recon/depth.py:186-188
Two photos-only runs of the same capture use the same cache directory and can both see a depth archive as missing. They then write that `.npz` file directly while either run may read it, so a partial archive can fail to load; both runs also launch expensive model inference. Lock the cache entry or publish completed archives atomically.

### Issue 11
recon/recon/pipeline.py:127-128
Previous `result.json` and `site-plan.svg` files are removed only when `server.place` runs. Reusing an output directory with `--no-server`, or failing during reconstruction before the POST, leaves the previous answer and plan beside current or partial outputs. Clear them at the start of every run.

### Issue 12
recon/recon/scene.py:undefined-86
A long wall with fragmented half-foot coverage can produce more than 500 entries across the wall, ground, facing and overhead bands. This slice silently drops later entries from the scene sent to the server while `coverage.json` retains their cells, making coverage-based checks lose valid observations without any warning.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

The PR adds a scan-bundle and Measure Lab reconstruction worker, depth-model integration, geometry and coverage export, server placement, acceptance results, and a handoff for the server team.

  • The worker validates manifest file paths and cache names and adds unit and ETH3D acceptance coverage.
  • The handoff documents measured results, interfaces, limitations, and known defects.
  • EXIF-oriented sensor images need correction before the worker can reliably process them.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Scan bundle or Measure Lab session] --> B[Capture validation]
  B --> C{Depth source}
  C -->|LiDAR| D[Per-frame depth]
  C -->|Photos| E[MoGe-2 and pose-based scaling]
  D --> F[TSDF and mesh]
  E --> F
  F --> G[Wall, ground and coverage]
  G --> H[Updated scene]
  H --> I[Placement server and site plan]
Loading

Reviews (2) · Last reviewed commit: "recon: test that duplicate keyframe ids ..."

Comment thread recon/recon/capture.py
Comment thread recon/recon/coverage.py
Comment thread recon/recon/coverage.py
break
if hits == 0:
facing_clear[c] = out - front
oo = front + np.arange(OVERHEAD_OUT_M[0], OVERHEAD_OUT_M[1] + 1e-9, step)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Overhead strip misses obstructions The overhead test samples only 0.10–0.56 m in front of the wall, rather than the entire area a placed battery can occupy. An eave close to the wall or an obstruction farther out over the battery is missed, so the cell can be reported as having clear headroom despite that obstruction.

Prompt To Fix With AI
This is a comment left during a code review.
Path: recon/recon/coverage.py
Line: 231

Comment:
**Overhead strip misses obstructions** The overhead test samples only 0.10–0.56 m in front of the wall, rather than the entire area a placed battery can occupy. An eave close to the wall or an obstruction farther out over the battery is missed, so the cell can be reported as having clear headroom despite that obstruction.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Comment thread recon/recon/scene.py
Comment thread recon/recon/pipeline.py
# 1 to 3 m spans at an assumed 2% pose error, 6.7 in (experiments/evals README section 3 on
# t3/evals). LiDAR: no measured value exists here; 0.5 ft is the server's default for mesh
# measurements (errors.mesh_ft, a day-1 estimate).
PLUS_MINUS_FT = {"moge2-triangulated": 0.56, "lidar": 0.5}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Clearance error is uncalibrated Photos-only facing and overhead measurements receive a ±0.56 ft bound derived from errors over surface-point spans, not from the accuracy of the reconstructed clearance boundary. A measured 7.10 ft overhead clearance is therefore treated as clearing a 6.5 ft minimum after subtracting 0.56 ft, although that metric does not establish the boundary’s error. Calibrate the clearance estimator or leave these checks unresolved.

Prompt To Fix With AI
This is a comment left during a code review.
Path: recon/recon/pipeline.py
Line: 35

Comment:
**Clearance error is uncalibrated** Photos-only facing and overhead measurements receive a ±0.56 ft bound derived from errors over surface-point spans, not from the accuracy of the reconstructed clearance boundary. A measured 7.10 ft overhead clearance is therefore treated as clearing a 6.5 ft minimum after subtracting 0.56 ft, although that metric does not establish the boundary’s error. Calibrate the clearance estimator or leave these checks unresolved.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Comment thread recon/recon/depth.py
Comment thread recon/recon/coverage.py
Comment thread recon/recon/depth.py
Comment thread recon/recon/pipeline.py
Comment thread recon/recon/scene.py
@AidenJohnston
AidenJohnston marked this pull request as ready for review September 27, 2026 15:27
@AidenJohnston
AidenJohnston merged commit d9ccaf2 into main Sep 27, 2026
5 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@codemetrics-ai

Copy link
Copy Markdown

Note

Review skipped

Codemetrics is installed on this repository, but it hasn't been added to your team yet.

To enable AI code reviews for this repo, go to your dashboard and add house-scanning-master to your team's repositories.

Comment thread recon/recon/depth.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants