qualia_client_core/wellfair/
anatomy_dyad.rs1use crate::wellfair::fetal_stages::CarnegieStage;
13use serde::{Deserialize, Serialize};
14use wellfare_core::anatomy::AnatomyModel;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct MaternalFetalDyad {
19 pub maternal_model: AnatomyModel,
20 pub host_structure: String,
22 pub carnegie_stage: u8,
23 pub gestational_age_days: u16,
24}
25
26impl MaternalFetalDyad {
27 pub fn at_stage(stage: &CarnegieStage) -> Self {
29 Self {
30 maternal_model: AnatomyModel::Female,
31 host_structure: "uterus".to_string(),
32 carnegie_stage: stage.stage,
33 gestational_age_days: stage.postfertilization_days,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
40pub struct DyadPlacement {
41 pub translate: [f32; 3],
43 pub scale: f32,
45}
46
47pub fn place_within(
51 host_min: [f32; 3],
52 host_max: [f32; 3],
53 fetal_min: [f32; 3],
54 fetal_max: [f32; 3],
55 fill: f32,
56) -> DyadPlacement {
57 let mid = |lo: f32, hi: f32| (lo + hi) * 0.5;
58 let translate = [
59 mid(host_min[0], host_max[0]),
60 mid(host_min[1], host_max[1]),
61 mid(host_min[2], host_max[2]),
62 ];
63 let host_min_ext = (host_max[0] - host_min[0])
64 .min(host_max[1] - host_min[1])
65 .min(host_max[2] - host_min[2])
66 .max(1e-6);
67 let fetal_max_ext = (fetal_max[0] - fetal_min[0])
68 .max(fetal_max[1] - fetal_min[1])
69 .max(fetal_max[2] - fetal_min[2])
70 .max(1e-6);
71 DyadPlacement {
72 translate,
73 scale: (fill.clamp(0.0, 1.0) * host_min_ext) / fetal_max_ext,
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn dyad_couples_female_uterus_and_stage_and_placement_fits() {
83 let stage = CarnegieStage {
84 stage: 18,
85 postfertilization_days: 44,
86 nih3d_entry: "3DPX-016952",
87 glb_file_id: 501993,
88 };
89 let dyad = MaternalFetalDyad::at_stage(&stage);
90 assert_eq!(dyad.maternal_model, AnatomyModel::Female);
91 assert_eq!(dyad.host_structure, "uterus");
92 assert_eq!(dyad.carnegie_stage, 18);
93 assert_eq!(dyad.gestational_age_days, 44);
94
95 let p = place_within(
98 [9.0, -1.0, -1.0],
99 [11.0, 1.0, 1.0],
100 [-2.0, -2.0, -2.0],
101 [2.0, 2.0, 2.0],
102 0.5,
103 );
104 assert_eq!(p.translate, [10.0, 0.0, 0.0]);
105 assert!((p.scale - 0.25).abs() < 1e-6, "scale {}", p.scale);
106 }
107
108 #[test]
111 #[ignore = "live network: places a Carnegie embryo within the maternal HRA uterus"]
112 fn place_embryo_in_maternal_uterus_from_real_assets() {
113 use crate::wellfair::ccf_resolver::{discover_ref_organs, fetch_glb, HRA_SPARQL_ENDPOINT};
114 use crate::wellfair::fetal_stages::carnegie_series;
115 use qualia_core_db::render::compile_10d::compile_asset;
116
117 let organs = discover_ref_organs(HRA_SPARQL_ENDPOINT).expect("discover HRA");
118 let uterus = organs
119 .iter()
120 .find(|o| o.filename.contains("uterus"))
121 .expect("uterus in the HRA female set");
122 let u_mesh = compile_asset(
123 &fetch_glb(&uterus.glb_url).expect("fetch uterus"),
124 Some("glb"),
125 "urn:host:uterus",
126 "glb",
127 )
128 .unwrap()
129 .mesh;
130
131 let stage = carnegie_series()
132 .into_iter()
133 .find(|s| s.stage == 18)
134 .unwrap();
135 let f_mesh = compile_asset(
136 &fetch_glb(&stage.glb_url()).expect("fetch embryo"),
137 Some("glb"),
138 "urn:fetal:s18",
139 "glb",
140 )
141 .unwrap()
142 .mesh;
143
144 let dyad = MaternalFetalDyad::at_stage(&stage);
145 let p = place_within(u_mesh.min, u_mesh.max, f_mesh.min, f_mesh.max, 0.6);
146 let ext = |m: &qualia_core_db::render::assets::Mesh| {
147 [
148 m.max[0] - m.min[0],
149 m.max[1] - m.min[1],
150 m.max[2] - m.min[2],
151 ]
152 };
153 eprintln!(
154 "DYAD {:?}: Carnegie {} (~{}d) seated in {} — translate {:?}, scale {:.5} · uterus ext {:?} · embryo ext {:?}",
155 dyad.maternal_model,
156 dyad.carnegie_stage,
157 dyad.gestational_age_days,
158 dyad.host_structure,
159 p.translate,
160 p.scale,
161 ext(&u_mesh),
162 ext(&f_mesh),
163 );
164 assert!(p.scale > 0.0);
165 }
166}