A surface plan maps values on a moving mesh to vertices of a reference mesh. Build the geometry once, then apply it to vectors or matrices whose rows are moving vertices. This example uses an octahedron and two query directions.
vertices <- rbind(c(1,0,0), c(-1,0,0), c(0,1,0), c(0,-1,0),
c(0,0,1), c(0,0,-1))
faces <- rbind(c(1,3,5), c(3,2,5), c(2,4,5), c(4,1,5),
c(3,1,6), c(2,3,6), c(4,2,6), c(1,4,6))
moving <- surface_mesh(vertices, faces)
query <- rbind(c(1,1,1)/sqrt(3), c(1,1,0)/sqrt(2))
reference <- surface_mesh(query)
plan <- surface_resampling_plan(reference, moving)
z <- vertices[,3]
apply_surface_resampling(plan, z)
#> [1] 0.3333333 0.0000000The first result is one third: the positive-octant face has equal weights on its three corners. The second query lies on an edge whose endpoints both have value zero. Reordering faces leaves the interpolation unchanged.
Spherical plans normalize both geometries to a common radius about
the origin and select the closest point on the triangular mesh,
including edges and vertices. They require a closed, connected spherical
source with an unambiguous radial mapping.
validate_surface_mesh() reports defects without repairing
input. A reference consisting only of query directions may omit
faces.
For open, nonspherical surfaces, set spherical=FALSE.
Barycentric plans then use interior orthogonal projections;
outside="error", "missing", and
"nearest" choose how uncovered queries are handled. The
nonspherical default is nearest-vertex fallback, which is identified
separately in coverage details. Direct surface_sampler()
calls expose projection="interior" or
"closest".
By default, a nonfinite value with positive weight makes that output unavailable. A zero-weight missing value has no effect. Omission explicitly renormalizes the remaining finite weights and reports how much raw weight remained.
with_missing <- z
with_missing[5] <- NA_real_
apply_surface_resampling(plan, with_missing)
#> [1] NA 0
omitted <- apply_surface_resampling(plan, with_missing,
na_policy="omit", details=TRUE)
data.frame(value=omitted$values,
retained_weight=omitted$finite_weight_mass,
available=omitted$available)
#> value retained_weight available
#> 1 0 0.6666667 TRUE
#> 2 0 1.0000000 TRUEOmission returns zero at the first query, based only on the two
equatorial vertices; its retained weight is two thirds. An all-missing
neighborhood returns NA, not zero. With matrix data these
diagnostics are column specific. Use na_policy="error" to
reject contributing nonfinite values. Omission requires row
normalization (normalize="element").
Source and target ROIs have different roles. Positive source-mask entries include vertices before row normalization; a target mask excludes outputs after correspondence and normalization have been computed.
masked_plan <- surface_resampling_plan(
reference, moving,
source_mask=c(1,1,1,1,0,0), target_mask=c(TRUE,FALSE)
)
coverage <- apply_surface_resampling(masked_plan, z, details=TRUE)
data.frame(value=coverage$values, geometry=coverage$geometric_support,
source_weight=coverage$source_weight_mass, status=coverage$status)
#> value geometry source_weight status
#> 1 0 TRUE 0.6666667 available
#> 2 NA TRUE 1.0000000 masked_targetBoth queries have geometric support. The first keeps two thirds of its original source weight and produces a supported zero. The second is unavailable because the target mask excludes it. A topological hole in a spherical mesh is a geometry error, not an ROI.
Labels use integer keys and an optional table of names or colors. Aggregate voting sums weights for each key; largest voting chooses a single source vertex. Neither policy averages the numeric IDs.
keys <- c(10L,20L,10L,20L,30L,40L)
label_table <- data.frame(key=c(0L,10L,20L,30L,40L),
name=c("unassigned","A","B","C","D"))
apply_surface_resampling(plan, keys, data_type="label",
label_table=label_table, unassigned=0L)
#> [1] 10 10
#> attr(,"label_table")
#> key name
#> 1 0 unassigned
#> 2 10 A
#> 3 20 B
#> 4 30 C
#> 5 40 DAggregate ties choose the smallest label key. Largest-weight ties
choose the smallest source vertex index. These are deterministic library
conventions; Workbench can resolve ties differently. Set
unassigned explicitly when another application uses a
particular missing-label key, and include it in the label table. The
default is NA_integer_.
For a fixed forward matrix W,
apply_surface_adjoint() applies its Euclidean transpose,
including the forward normalization and fixed masks. It does not
renormalize the transpose.
y <- c(2,1)
c(forward_inner_product=sum(apply_surface_resampling(plan,z)*y),
adjoint_inner_product=sum(z*apply_surface_adjoint(plan,y)))
#> forward_inner_product adjoint_inner_product
#> 0.6666667 0.6666667An unsupported source column has algebraic adjoint value zero;
details=TRUE identifies those columns. Missing-data
omission changes weights with the data and is not this fixed adjoint.
normalize="sum" conserves represented discrete source mass
before target exclusion; it does not perform anatomical area
correction.
Reverse geometric interpolation requires faces on the new source. Supply both original geometries so their recorded identities can be checked.
angle <- .23
rotation <- rbind(c(cos(angle),-sin(angle),0),
c(sin(angle),cos(angle),0),c(0,0,1))
rotated_vertices <- vertices %*% rotation
rotated <- surface_mesh(rotated_vertices, faces)
forward_plan <- surface_resampling_plan(rotated, moving)
reverse_plan <- reverse_surface_resampling_plan(forward_plan, rotated, moving)
reverse_plan
#> <SurfaceResamplingPlan | method=barycentric | moving=6 -> reference=6 | nnz=10>A rebuilt reverse plan generally differs from a transpose and cannot
recover information lost through downsampling. inverse=TRUE
is deprecated and retains its legacy transpose-then-normalize behavior;
it rejects new policies and fixed masks rather than silently changing
their interpretation.
method="adaptive_bary_area" implements forward/reverse
support selection and anatomical area correction as a separate method.
It requires experimental=TRUE: the full-template Workbench
parity gate has not passed. There is no substitution of ordinary
barycentric interpolation when it fails.
Bind areas to the exact registration geometry using
surface_vertex_areas(). Provide positive measured vertex
areas with explicit provenance, or a corresponding anatomical mesh with
the same ordered faces. The following ellipsoids are synthetic
illustrations, not anatomical reference data.
source_anatomy <- surface_mesh(vertices %*% diag(c(20,30,40)), faces)
target_anatomy <- surface_mesh(rotated_vertices %*% diag(c(20,30,40)), faces)
source_areas <- surface_vertex_areas(moving, anatomical=source_anatomy)
target_areas <- surface_vertex_areas(rotated, anatomical=target_anatomy)
adaptive <- surface_resampling_plan(
rotated, moving, method="adaptive_bary_area",
source_areas=source_areas, target_areas=target_areas, experimental=TRUE
)
result <- apply_surface_resampling(adaptive, 1+z, details=TRUE)
c(source_integral=sum(source_areas$values*(1+z)),
effective_target_integral=sum(result$effective_target_area[result$available] *
result$values[result$available]))
#> source_integral effective_target_integral
#> 6248.2 6248.2Areas must be finite and strictly positive, with matching units on both sides. Zero areas are rejected. Normalized spherical areas are never substituted implicitly for anatomical areas.
The area identity uses an effective target measure,
not generally the supplied target vertex areas. With row normalization
and no target exclusion, it preserves the integral over included,
represented source values. Under omission, the effective measure is
column specific and the identity includes only finite source values;
filter unavailable rows before multiplying by their zero measure.
effective_target_area is unavailable for
normalize="sum" or "none".
source_roi_target_area remains a diagnostic before data
omission or target exclusion. No conservation of numeric label IDs is
claimed.
| Capability | Evidence and current boundary |
|---|---|
| Ordinary continuous interpolation | Analytic opposite-side, boundary and permutation tests; independent small Workbench basis fixtures; exact indexed/exhaustive comparisons. Full-template strict Workbench parity remains incomplete. |
| ROI, missing data and categorical voting | Independent policy fixtures and small Workbench mask/non-tied-label comparisons. Tie rules and missing-label keys must be mapped explicitly. |
| Reverse plans and Euclidean adjoints | Independent dense-matrix identities and rebuilt geometric reverse tests. Neither recovers discarded information. |
| Adaptive area interpolation | Analytic area/ROI identities and unequal-area Workbench basis fixtures. Experimental until full-template gates pass. |
The full ordinary-template run found rare near-edge differences above
its predeclared 5e-5 maximum-error threshold. Independent
closest-point calculations support the native result at those queries;
Workbench selects an edge and omits a small positive third weight. The
failed threshold is retained. Passing a constant field or a small
fixture does not qualify every production route.
Versioned receipts and the method capability matrix are in the
repository’s output/surface-completion/ directory.
Workbench is an optional comparator, not a runtime dependency. Its metric
and label
commands define the external comparison methods.