Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions crates/ogar-fma-skeleton/src/guid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,56 @@ impl Guid {
pub fn same_family(&self, other: &Self) -> bool {
self.classid() == other.classid() && self.family_node() == other.family_node()
}

/// Build a **Located-mode** GUID (the bone case): `classid` · 3D-CRS
/// `HEEL/HIP` from the body position `(x, y, z)` · LEAF `familyNode:identity`.
/// HHTL is spatial — the address preserves location (Cesium / ArcGIS).
#[must_use]
pub fn located(classid: Tier, x: u8, y: u8, z: u8, leaf: LeafTile) -> Self {
let (heel, hip) = located_3d(x, y, z);
let mut g = Self::ZERO;
g.set_tier(TIER_CLASSID, classid);
g.set_tier(TIER_HEEL, heel);
g.set_tier(TIER_HIP, hip);
g.set_tier(
TIER_LEAF,
Tier {
container: leaf.family_node,
member: leaf.identity,
},
);
g
}

/// Build a **Cascade-mode** GUID (the soft-tissue case): `classid` · a named
/// ontology HHTL path (`heel` · `hip` · `twig`) · LEAF `familyNode:identity`.
/// HHTL is the self-speaking classification path — e.g. the papillary
/// muscle's `ANAT0001-CARD-HERT-LVNT-PAPMUS-…`. No spatial address; the
/// node is classified, then a splat projects onto it.
#[must_use]
pub fn ontological(classid: Tier, heel: Tier, hip: Tier, twig: Tier, leaf: LeafTile) -> Self {
let mut g = Self::ZERO;
g.set_tier(TIER_CLASSID, classid);
g.set_tier(TIER_HEEL, heel);
g.set_tier(TIER_HIP, hip);
g.set_tier(TIER_TWIG, twig);
g.set_tier(
TIER_LEAF,
Tier {
container: leaf.family_node,
member: leaf.identity,
},
);
g
}

/// The quantised `(x, y, z)` body position decoded from the Located HEEL/HIP
/// tiers (inverse of [`located`](Self::located)). Meaningful only for
/// Located-mode GUIDs.
#[must_use]
pub fn position(&self) -> (u8, u8, u8) {
position_3d(self.heel(), self.tier(TIER_HIP))
}
}

impl core::fmt::Debug for Guid {
Expand Down Expand Up @@ -223,6 +273,39 @@ pub fn located_heel_hip(x: u8, y: u8, z: u8) -> (Tier, Tier) {
(spatial_tier(x, y), spatial_tier(z, 0))
}

/// Encode a 3-axis body position as a **true 3D-octree** `(HEEL, HIP)` pair —
/// the ArcGIS / Cesium-grade Located CRS (supersedes the coronal-tile +
/// depth-slice [`located_heel_hip`] by using HIP's reserved odd bits).
///
/// The 24-bit 3D Morton code ([`morton3_encode`](crate::morton::morton3_encode))
/// is laid big-endian across three bytes — `HEEL.container` (coarsest octant),
/// `HEEL.member`, `HIP.container` — so prefix containment over `HEEL ++
/// HIP.container` is 3D spatial containment (the octree the GIS stack pages by
/// LOD). `HIP.member` (the 4th byte) is reserved for finer (>256³) refinement.
#[must_use]
pub fn located_3d(x: u8, y: u8, z: u8) -> (Tier, Tier) {
let code = crate::morton::morton3_encode(x, y, z); // 24-bit
(
Tier {
container: ((code >> 16) & 0xFF) as u8,
member: ((code >> 8) & 0xFF) as u8,
},
Tier {
container: (code & 0xFF) as u8,
member: 0, // reserved: finer octree levels (>256³)
},
)
}

/// Recover the quantised `(x, y, z)` body position from a Located 3D `(HEEL,
/// HIP)` pair built by [`located_3d`] — the inverse, for spatial queries.
#[must_use]
pub fn position_3d(heel: Tier, hip: Tier) -> (u8, u8, u8) {
let code =
((heel.container as u32) << 16) | ((heel.member as u32) << 8) | (hip.container as u32);
crate::morton::morton3_decode(code)
}

/// The **mode** of an HHTL tier — the operator's "heel" distinction
/// (2026-06-23).
///
Expand Down
96 changes: 70 additions & 26 deletions crates/ogar-fma-skeleton/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@

pub mod guid;
pub mod morton;
pub mod projection;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

pub use guid::{Guid, HhtlMode, LeafTile, Tier};
pub use morton::FamilyAddress;
pub use projection::{Modality, ModalityProjection, ProjectionSample, SampleKind};

/// Re-export of the `bone` concept id from the canonical OGAR codebook
/// (`ogar_vocab::class_ids::BONE` = `0x0A03`). It is the classid tier of every
Expand Down Expand Up @@ -257,6 +259,13 @@ impl Bone {
self.guid.identity()
}

/// The quantised `(x, y, z)` body position decoded from the Located 3D
/// HEEL/HIP tiers — `+X` anatomical-left, `+Y` superior, `+Z` anterior.
#[must_use]
pub fn position(&self) -> (u8, u8, u8) {
self.guid.position()
}

/// The full canonical 16-byte node key (the [`Guid`] bytes).
#[must_use]
pub const fn node_key(&self) -> [u8; morton::KEY_BYTES] {
Expand Down Expand Up @@ -353,21 +362,14 @@ impl Skeleton {
let family_node = (region_code(spec.region) << 4) | gi;

let p = spec.rest_pose.translation;
let (heel, hip) = guid::located_heel_hip(
let g = Guid::located(
classid,
q(p[0], min[0], max[0]),
q(p[1], min[1], max[1]),
q(p[2], min[2], max[2]),
);

let mut g = Guid::ZERO;
g.set_tier(guid::TIER_CLASSID, classid);
g.set_tier(guid::TIER_HEEL, heel);
g.set_tier(guid::TIER_HIP, hip);
g.set_tier(
guid::TIER_LEAF,
Tier {
container: family_node,
member: identity_of[pos],
LeafTile {
family_node,
identity: identity_of[pos],
},
);

Expand Down Expand Up @@ -866,30 +868,72 @@ mod tests {
}

#[test]
fn heel_encodes_laterality() {
// Located precondition: left/right twins differ in HEEL (the coronal X
// axis = laterality), so an X-ray / ultrasound sweep can tell sides apart
// from the spatial tier — while remaining the SAME categorical family.
fn crs_encodes_laterality_and_depth() {
// Located 3D CRS: the decoded position recovers anatomical axes.
// Laterality (X): left vs right femur. Depth (Z): sternum (anterior) vs
// a thoracic vertebra (posterior). Decoding through the 3D-octree HEEL/HIP
// is the inverse the GIS / projection layer uses.
let s = skel();
let left_femur = s.get(1021).unwrap();
let right_femur = s.get(2021).unwrap();
assert_ne!(left_femur.heel(), right_femur.heel(), "laterality in HEEL");
let (lx, _, _) = left_femur.position();
let (rx, _, _) = right_femur.position();
assert_ne!(lx, rx, "laterality recovered from the 3D CRS");
assert_ne!(left_femur.heel(), right_femur.heel(), "and visible in HEEL");
assert!(
left_femur.guid().same_family(&right_femur.guid()),
"both are lower-limb-long-bone family",
"yet the SAME categorical family (spatial is orthogonal)",
);
assert_ne!(left_femur.identity(), right_femur.identity());

let (_, _, sternum_z) = by_name(&s, "sternum").position();
let (_, _, vertebra_z) = by_name(&s, "vertebra T6").position();
assert!(
sternum_z > vertebra_z,
"sternum is anterior (+Z) of the vertebra: {sternum_z} vs {vertebra_z}",
);
}

#[test]
fn hip_encodes_depth() {
// HIP = the anterior-posterior depth slice: the sternum (anterior) and a
// thoracic vertebra (posterior) sit in different depth tiers.
let s = skel();
let sternum = by_name(&s, "sternum");
let vertebra = by_name(&s, "vertebra T6");
let hip = |b: &Bone| b.guid().tier(guid::TIER_HIP);
assert_ne!(hip(sternum), hip(vertebra), "anterior vs posterior depth");
fn cascade_ontology_guid_builds_the_self_speaking_path() {
// The soft-tissue (Cascade) mode: the papillary muscle's
// ANAT0001-CARD-HERT-LVNT-PAPMUS-<id>, no spatial address. Shares the
// tier algebra with the Located bones; only the HHTL reading differs.
let classid = Tier {
container: 0x0A,
member: 0x10,
}; // Anatomy : muscle (illustrative)
let g = Guid::ontological(
classid,
guid::ontology_tier(0x01, 0x02), // Cardiovascular : Heart
guid::ontology_tier(0x02, 0x03), // Heart : LeftVentricle
guid::ontology_tier(0x03, 0x07), // LeftVentricle : PapillaryMuscleFamily
LeafTile {
family_node: 0x07,
identity: 0x1D,
},
);
assert_eq!(g.classid(), 0x0A10);
assert_eq!(
g.leaf(),
LeafTile {
family_node: 0x07,
identity: 0x1D
}
);
// A sibling papillary muscle: same family, identity attached.
let sibling = Guid::ontological(
classid,
guid::ontology_tier(0x01, 0x02),
guid::ontology_tier(0x02, 0x03),
guid::ontology_tier(0x03, 0x07),
LeafTile {
family_node: 0x07,
identity: 0x1E,
},
);
assert!(g.same_family(&sibling));
assert_ne!(g.identity(), sibling.identity());
}

#[test]
Expand Down
50 changes: 50 additions & 0 deletions crates/ogar-fma-skeleton/src/morton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,56 @@ fn refine(
}
}

// ── 3D octree Morton (the Located CRS — true x:y:z, ArcGIS / Cesium-grade) ──

/// Spread an 8-bit value so each bit lands in every 3rd position (`bit k → 3k`),
/// the building block of a 24-bit 3D Morton (octree) code.
#[must_use]
pub const fn spread3(v: u8) -> u32 {
let mut out = 0u32;
let mut k = 0;
while k < 8 {
out |= ((v as u32 >> k) & 1) << (3 * k);
k += 1;
}
out
}

/// Gather every 3rd bit (`code bit 3k → bit k`) back into an 8-bit value —
/// inverse of [`spread3`].
#[must_use]
pub const fn gather3(code: u32) -> u8 {
let mut out = 0u8;
let mut k = 0;
while k < 8 {
out |= (((code >> (3 * k)) & 1) as u8) << k;
k += 1;
}
out
}

/// Encode `(x, y, z)` (8 bits each) into a **24-bit 3D Morton (octree) code**:
/// `x` in bit positions `0,3,6,…`, `y` in `1,4,7,…`, `z` in `2,5,8,…`. The top
/// three bits `(23,22,21) = (z7, y7, x7)` are the coarsest octant, so a shared
/// high-byte prefix is 3D spatial containment.
///
/// ```
/// use ogar_fma_skeleton::morton::{morton3_encode, morton3_decode};
/// let c = morton3_encode(0xC3, 0x55, 0xA0);
/// assert_eq!(morton3_decode(c), (0xC3, 0x55, 0xA0));
/// ```
#[must_use]
pub const fn morton3_encode(x: u8, y: u8, z: u8) -> u32 {
spread3(x) | (spread3(y) << 1) | (spread3(z) << 2)
}

/// Decode a 24-bit 3D Morton code back into `(x, y, z)`. Inverse of
/// [`morton3_encode`].
#[must_use]
pub const fn morton3_decode(code: u32) -> (u8, u8, u8) {
(gather3(code), gather3(code >> 1), gather3(code >> 2))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading