457918434a
删除未被读取的 expected 配置,避免误认为拓扑已受保护。配置检查、manifest 测试及文档检查通过。
147 lines
8.7 KiB
Markdown
147 lines
8.7 KiB
Markdown
# 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.
|