Files
orisys-sensor-design/docs/geometry-measurements.md
T
ykcheungab 457918434a 移除无效拓扑字段并精简设计参数说明
删除未被读取的 expected 配置,避免误认为拓扑已受保护。配置检查、manifest 测试及文档检查通过。
2026-10-07 16:04:31 +08:00

147 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Measured geometry, camera and runtime facts (`sensors/finger/r01`)
Reference measurements of the finger `r01` sensing surface, the sensing camera, and the runtime
unroll domain. These are properties of the physical part, not of any one design, so they live
here rather than in a decision record and are reused by the multi-view ownership work, the probe
bench and the optics model.
Every row states how it was obtained. Anything not re-derivable is marked as such.
## 1. Method
Read-only, never saved. Blender 5.0.1, invoked as `blender -b -P <script>` against
`sensors/finger/r01/source/geometry.blend`. The probe scripts lived in
`orisys-sensor-design/.scratch/multiview-arch/`, which is scratch and has been removed — so the
numbers below are **not re-derivable by re-running a stored script**. Anything you need to trust
today must be re-measured from the mesh, from the runtime asset named in each row, or from the
recorded blend hash.
Recorded blend hash for the 2026-09-27 measurement set: `61093B1A…`.
## 2. Sensing surface
| Fact | Value | Note |
|---|---|---|
| Topology | 1032 verts / 1974 polys, **all triangles**, 5922 loops, no modifiers | The mesh is pre-triangulated, which is what makes the loop-triangle index a valid stable key |
| Dimensions | 15.81 × 15.0 × 7.5 mm bbox | |
| Surface area | **355.77 mm²** | 1.5× the bbox footprint |
| Area distribution | **38.1 % near-vertical wall, 0 % flat top** | The cap has a 15 mm-deep circular end, so almost half the surface faces sideways |
| Position | x ∈ [−15.81, −1.77] mm; area-weighted centroid ≈ **(−8.13, 0.00, 3.14) mm** | **The surface is not centred on the origin.** Any view aimed at the origin misses it |
| Face normals | **94.5 % inward-facing** (`normal.z < 0`), `normal.z ∈ [−0.9999, +0.6755]` | Winding is not trustworthy on this mesh; see §5 |
## 3. UV layouts
| Fact | Value |
|---|---|
| Maps | exactly two: `produce` and `compute`, 5922 loops each; `compute` is active |
| `compute` extent | u ∈ [0.00009, 1.0], v ∈ [0.1425, 0.85742] — the empty v-band is the shipped map's domain shape, not a defect |
| Texel density uniformity | per-polygon (UV area / world area): `compute` min 2.5e-4, p05 1.1e-3, **median 1.391e-3**, p95 1.872e-3, max 4.061e-3 → **spread 2.92×** |
| `produce` density | min **0.0** — **degenerate zero-area UV faces exist**, median 2.131e-3, max 4.216e-3 |
The unwrap's non-uniformity across the surface (2.92×) is **larger** than the axis-aligned
anisotropy `docs/xynova-texture-investigation.md` measured (1.17×) — that earlier figure was taken
at the centre point, which is the good case.
`compute` is the **runtime contract**: the shipped `unroll_map_uv_s0.4.npz` was built from this
mesh and this UV layout, so changing `compute` invalidates that asset and is a release event, not
a build side effect.
## 4. Sensing camera (`cam_sensor`)
| Fact | Value | Note |
|---|---|---|
| Type and lens | PERSP, effective lens 3.5 mm | |
| Sensor width | `sensor_width` 1.794 × object scale **6.009** = 10.78 mm | The 6.009 object scale must be divided out by anything that derives a view from this camera, or the pitch is wrong by 6× |
| Position | origin 4.62 mm above the surface | |
| H-FOV | ≈ **114°** | Extremely wide and very close: foreshortening is unavoidable |
| Coverage | 1887/1974 triangles inside the frustum (**95.6 %**), **0 occluded**, **0 triangles beyond 75° incidence** | The camera is not edge-on-limited |
| Incidence over the surface | p50 **42.1°** | Intrinsic to a 355.8 mm² cap with 38 % near-vertical wall |
## 5. Printed cell pitch (measured on the surface)
| Fact | Value | Note |
|---|---|---|
| Adjacent camera-cell pitch | median **0.516 mm horizontal × 0.714 mm vertical**; horizontal spread 0.449–0.568 mm | The cell pitch is *finer vertically than horizontally on the surface* |
| `design.yaml` nominal cell | `vector.width_mm/nx = 0.205`, `vector.height_mm/ny = 0.3556` | **Wrong as a physical cell size.** The vector page (16.4 × 16.0 mm) was sized to the bbox, not to the camera's 80×45 grid |
| Consumers of the wrong value | `cli.py:521` (`cmd_inspect`'s `nominal_cell_mm`), `cli.py:751` (`pitch_mm = vector_width_mm / nx`) | `pitch_mm` feeds `sample_stack`, and therefore the thickness/contact numbers in the optics model |
| Grid vs surface | 80 cells × 0.516 mm = 41 mm of surface pitch across a 14.0 mm-wide surface | A cell spans ~13 surface fragments; the patch colour table is indexed by camera cell, so colour varies much faster than the cell pitch suggests |
| Page scale | `3280/3200 = 1.025 = 16.4/16.0`, exactly 200 px/mm both axes | Any change to `nx`/`ny` or the page must preserve this, or `_warn_if_page_aspect_differs` fires and `analyse_map` returns `inconclusive` |
The optics model currently derives the printed cell from the page arithmetic above. Replacing it
with the measured 0.516 mm changes the reported thickness/contact numbers, so the two must move
together.
## 6. Runtime unroll domain
From `orisys-sdk/sensors/finger/assets/30fps-0703/unroll_map_uv_s0.4.npz`:
| Fact | Value |
|---|---|
| Grid | 280 × 300 |
| Valid ratio | 0.408 — **34270 valid pixels** |
| Keys | includes `world_xyz`, `projection_K`, `source_scale`, `edge_band`, `edge_expand`, `method` |
| Surface coverage | ≈ **268.8 mm² = 75.6 %** of the 355.77 mm² surface (nearest-centroid association, max 1.0 mm error) |
**Incidence over the runtime region** (the part that actually matters, not the whole surface):
| Measure | Front ortho | Best of 2 views |
|---|---|---|
| p50 | **55.6°** | **55.6°** |
| p90 | 89.5° | 83.2° |
| > 60° | 44.97 % | — |
| > 75° | 30.4 % | — |
Two findings govern the whole multi-view design:
1. **Adding views improves the tail, not the median.** p90 moves; p50 does not move at all.
2. **Azimuthal rotation about the cap axis is a measurable no-op** — identical statistics at
az = 0/±26/±45°. The cap is a surface of revolution, so rotating about its axis changes
nothing.
## 7. Merge-mode agreement (2026-09-27, blend sha256 `61093B1A…`)
Measured on this revision, and the reason `merge.incidence_limit_deg: 62.0` rather than 60:
| Quantity | Value |
|---|---|
| Authored seam | the **y = 0 plane** |
| `authored` vs `computed` agreement | within **0.0014** of coverage at a 60° incidence limit; **identical from 62° up** |
| Hole fraction at 60° | **0.0218** — fails the `max_hole_fraction: 0.02` gate |
| Hole fraction at 62° | **0.0000** |
So neither mode is a compromise at this revision, and the 62° default is a measured threshold,
not a preference.
> **This window is camera-specific and expires when a camera moves.** The y = 0 coincidence holds
> because the two region cameras are mirrored about XZ at azimuth 308.26° / 231.74°, elevation
> −24.46°, distance 32.62 mm. `incidence_limit_deg` describes a *camera-pose and surface*
> combination, not a property of the sensor model, so moving a region camera invalidates it and
> the scan must be re-run.
>
> It has already happened once: on the Xynova surface, **62° produces 4.8 % hole**, which is why
> that revision sets `incidence_limit_deg: 70.0` explicitly and the YAML carries a warning not to
> delete it. The in-code defaults (`texture_views.MERGE_DEFAULTS` and `texture_merge.py`) stay at
> 62.0 with a test requiring the two to agree, so removing the override silently restores a value
> that is wrong for that surface.
>
> A previous revision of `design.yaml` also recorded "hole 0.100 mm² / fraction 0.0002 at 62°" and
> "4960/5007 triangles owned, 20454 + 21483 fragments". Those figures were valid before the
> cameras moved and are **retracted**; they are recorded here only to show why camera-dependent
> numbers must state their re-measurement trigger.
**A trap worth keeping:** assigning a component by *count of visible triangles* does not work
here — both views see ≈100 % of each component's triangles, so the count ties and a naive rule
silently hands both components to the first view. Component assignment uses **qualifying
footprint**, which folds in both the incidence limit and the footprint floor.
## 8. What these numbers imply for any view set
- The sensing surface is offset from the origin, so a frame centred on the origin misses it;
derived frames must be built from the surface's own extent.
- The incidence p50 over the runtime region is intrinsic to the cap's shape. No finite view set
removes it; it can only shorten the tail.
- The dominant print-distortion term is the UV unwrap's density spread (2.92×), not projection.
- 94.5 % inward normals means anything that trusts winding — a back-face cull, a renderer, a
"which side is the surface" heuristic — will get this mesh backwards. Either fix the winding in
the authored blend, or keep taking `abs()` and record why.