From 0851e26f2ce2beafa9268929d90c5ac8aa5dbb47 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 24 Jun 2026 05:25:46 +0000 Subject: [PATCH] osint/fma: hydrate the bake from the real FMA heart subtree, not a fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /fma render surface was hydrated from a hand-authored fixture (data/fma-heart.fixture.ttl, 4 chambers x 3 wall layers). Replace it with a real subtree extracted from fma.owl (FMA v5.0.0, the 266 MB UW Structural Informatics Group FMA, CC BY 3.0, now mirrored on the q2 fma-ontology-5.0.0 release; SHA-256 59465eb5...79979e44). tools/extract_fma_heart.py streams the OWL (ET.iterparse, memory-bounded — only the label / part / superclass maps are retained, not the ~1.5M-triple graph), walks the regional_part / constitutional_part partonomy from FMA:Heart with a per-parent fan-out cap (root exempt). That keeps the slice a balanced cross-section — all 22 major heart subdivisions (the four chamber cavities, the four valves, the three cardiac septa, both coronary arteries, the wall's endo/epi/myocardium, the fibrous skeleton, ...) with their real sub-parts — instead of draining into FMA's ~40-branch coronary arterial tree. It emits the line-Turtle subset fma_ttl::parse already hydrates; the generic hydrate_fma walk is unchanged (the partonomy IS the key, derived, not hardcoded to "heart"). The fixture's "four myocardia all share one Cardiac-muscle-tissue type" was an invention — real FMA gives region-specific myocardium classes. Rewrite the dual-membership proof to pick the strongest real cross-cutter in the slice (the ceiling type that is the is-a target of the most structures) and its deepest member, instead of a hardcoded label that no longer exists. On the real data it surfaces "Cavity proper of inflow part of right atrium" at a 4-tier-deep part-of address (HEEL[01:01] HIP[02:06] TWIG[03:01] LEAF[04:01]), is-a "Subdivision of cavity of cardiac chamber" — a global type shared by 7 cavity structures across all four chambers. The dual membership is now proven on real anatomy. Verified: - byte-deterministic bake (re-run -> identical fma.soa; 6603 bytes, 125 nodes = 80 part-of + 45 ceiling types, 156 edges) - fma_ttl unit tests pass; clippy clean on the bake (the 3 -D warnings are the pre-existing aiwar-ingest lib dep, not these files) - cockpit FmaGraph.tsx doc updated to the real cross-cutting example; tsc clean - isolated to the osint-bake leaf crate + cockpit vite app; nothing in the quarto-core / pampa / wasm chain is touched The 266 MB OWL is NOT committed; the production hydrator remains the spine's lance_graph_ontology::hydrate_fma over the full fma.owl. Co-Authored-By: Claude Opus 4.6 Claude-Session: https://claude.ai/code/session_01TzqvDqbFRzyx17EkLKBoZF --- cockpit/public/fma.soa | Bin 940 -> 6603 bytes cockpit/src/FmaGraph.tsx | 3 +- crates/osint-bake/data/fma-heart.fixture.ttl | 53 ------ crates/osint-bake/data/fma-heart.ttl | 166 +++++++++++++++++++ crates/osint-bake/src/bin/fma.rs | 124 +++++++++----- crates/osint-bake/tools/extract_fma_heart.py | 145 ++++++++++++++++ 6 files changed, 394 insertions(+), 97 deletions(-) delete mode 100644 crates/osint-bake/data/fma-heart.fixture.ttl create mode 100644 crates/osint-bake/data/fma-heart.ttl create mode 100644 crates/osint-bake/tools/extract_fma_heart.py diff --git a/cockpit/public/fma.soa b/cockpit/public/fma.soa index 45b0be0eec282cc5d510e77d48e3db2f32abeb39..6269c5b27fc354ddf040b2e35045183a625fedd8 100644 GIT binary patch literal 6603 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 iHrjmXR(qAxrh}C#?fDNXAHI43 diff --git a/cockpit/src/FmaGraph.tsx b/cockpit/src/FmaGraph.tsx index bf29f1f31..22a4ce03a 100644 --- a/cockpit/src/FmaGraph.tsx +++ b/cockpit/src/FmaGraph.tsx @@ -3,7 +3,8 @@ // same vis-network renderer, and on click shows a node resolving to BOTH: // · its part-of position (basin-local: organ → chamber → wall → structure) // · its leaf-limited global TYPE (the 0xFFFF ceiling pole — cross-cutting, -// the same "Cardiac muscle tissue" shared by every chamber). +// e.g. one "Subdivision of cavity of cardiac chamber" or "Endothelium of +// endocardium" shared by structures in different parts of the heart). // // This is the `Cascade` (ontology / part-of) reading of OGAR PR #116's HhtlMode // FMA tier model: each node is a stack of 8:8 [container:identity] tiers — diff --git a/crates/osint-bake/data/fma-heart.fixture.ttl b/crates/osint-bake/data/fma-heart.fixture.ttl deleted file mode 100644 index f2814b3fc..000000000 --- a/crates/osint-bake/data/fma-heart.fixture.ttl +++ /dev/null @@ -1,53 +0,0 @@ -# fma-heart.fixture.ttl — FIXTURE, NOT the real FMA. -# -# A faithful but hand-authored heart subtree in the canonical FMA predicate -# set. The real Foundational Model of Anatomy (266 MB fma.owl, ~1.5M triples, -# OGIT contextId 13, dcterms:source AdaWorldAPI/MedCare-rs bioportal-ontologies) -# hydrates through lance-graph-rdf / lance_graph_ontology::hydrate_fma at the -# spine; this light q2 bake mirrors that shape on a subtree so /fma renders -# without the lance/datafusion closure that bake deliberately excludes. -# -# Line-oriented Turtle subset: one `subject predicate object .` per line. -# Predicates mirror the canonical hydrator set (pr-d-1-fma-owl-hydrator): -# bfo:part_of → partonomy (drives the HHTL cascade tiers) -# rdfs:subClassOf → cross-cutting tissue type (the is-a ceiling) - -@prefix fma: . -@prefix bfo: . -@prefix rdfs: . - -# ── chambers regional-part-of the heart ── -fma:Left_atrium bfo:part_of fma:Heart . -fma:Right_atrium bfo:part_of fma:Heart . -fma:Left_ventricle bfo:part_of fma:Heart . -fma:Right_ventricle bfo:part_of fma:Heart . - -# ── each chamber's wall layers, each a subClassOf its tissue type ── -fma:Myocardium_of_left_atrium bfo:part_of fma:Left_atrium . -fma:Myocardium_of_left_atrium rdfs:subClassOf fma:Cardiac_muscle_tissue . -fma:Endocardium_of_left_atrium bfo:part_of fma:Left_atrium . -fma:Endocardium_of_left_atrium rdfs:subClassOf fma:Endothelium . -fma:Epicardium_of_left_atrium bfo:part_of fma:Left_atrium . -fma:Epicardium_of_left_atrium rdfs:subClassOf fma:Mesothelium . - -fma:Myocardium_of_right_atrium bfo:part_of fma:Right_atrium . -fma:Myocardium_of_right_atrium rdfs:subClassOf fma:Cardiac_muscle_tissue . -fma:Endocardium_of_right_atrium bfo:part_of fma:Right_atrium . -fma:Endocardium_of_right_atrium rdfs:subClassOf fma:Endothelium . -fma:Epicardium_of_right_atrium bfo:part_of fma:Right_atrium . -fma:Epicardium_of_right_atrium rdfs:subClassOf fma:Mesothelium . - -fma:Myocardium_of_left_ventricle bfo:part_of fma:Left_ventricle . -fma:Myocardium_of_left_ventricle rdfs:subClassOf fma:Cardiac_muscle_tissue . -fma:Endocardium_of_left_ventricle bfo:part_of fma:Left_ventricle . -fma:Endocardium_of_left_ventricle rdfs:subClassOf fma:Endothelium . -fma:Epicardium_of_left_ventricle bfo:part_of fma:Left_ventricle . -fma:Epicardium_of_left_ventricle rdfs:subClassOf fma:Mesothelium . - -fma:Myocardium_of_right_ventricle bfo:part_of fma:Right_ventricle . -fma:Myocardium_of_right_ventricle rdfs:subClassOf fma:Cardiac_muscle_tissue . -fma:Endocardium_of_right_ventricle bfo:part_of fma:Right_ventricle . -fma:Endocardium_of_right_ventricle rdfs:subClassOf fma:Endothelium . -fma:Epicardium_of_right_ventricle bfo:part_of fma:Right_ventricle . -fma:Epicardium_of_right_ventricle rdfs:subClassOf fma:Mesothelium . - diff --git a/crates/osint-bake/data/fma-heart.ttl b/crates/osint-bake/data/fma-heart.ttl new file mode 100644 index 000000000..21493a115 --- /dev/null +++ b/crates/osint-bake/data/fma-heart.ttl @@ -0,0 +1,166 @@ +# fma-heart.ttl — REAL FMA heart subtree, extracted from fma.owl (FMA v5.0.0). +# Source: BioPortal #29 / UW Structural Informatics Group, CC BY 3.0, +# SHA-256 59465eb503c418dbf9216d362b94c7f42bc3b96b297e553ec17c7fee77979e44, +# mirror github.com/AdaWorldAPI/q2/releases/download/fma-ontology-5.0.0/fma.owl +# Generated by tools/extract_fma_heart.py (streaming ET; regional_part + +# constitutional_part partonomy, BFS from FMA:Heart fma7088, depth<=4, <=80 nodes). +# The 266 MB OWL is NOT committed; the production hydrator is the spine's +# lance_graph_ontology::hydrate_fma. This is the real-data heart slice the +# q2 /fma render surface hydrates. + +fma:Cavity_of_right_atrium bfo:part_of fma:Heart . +fma:Cardiac_vein bfo:part_of fma:Heart . +fma:Neural_network_of_heart bfo:part_of fma:Heart . +fma:Coronary_sinus bfo:part_of fma:Heart . +fma:Right_coronary_artery bfo:part_of fma:Heart . +fma:Left_coronary_artery bfo:part_of fma:Heart . +fma:Interatrial_septum bfo:part_of fma:Heart . +fma:Interventricular_septum bfo:part_of fma:Heart . +fma:Atrioventricular_septum bfo:part_of fma:Heart . +fma:Right_side_of_heart bfo:part_of fma:Heart . +fma:Left_side_of_heart bfo:part_of fma:Heart . +fma:Tricuspid_valve bfo:part_of fma:Heart . +fma:Mitral_valve bfo:part_of fma:Heart . +fma:Aortic_valve bfo:part_of fma:Heart . +fma:Pulmonary_valve bfo:part_of fma:Heart . +fma:Wall_of_heart bfo:part_of fma:Heart . +fma:Systemic_capillary_bed_of_heart bfo:part_of fma:Heart . +fma:Lymphatic_capillary_bed_of_heart bfo:part_of fma:Heart . +fma:Cavity_of_right_ventricle bfo:part_of fma:Heart . +fma:Cavity_of_left_atrium bfo:part_of fma:Heart . +fma:Cavity_of_left_ventricle bfo:part_of fma:Heart . +fma:Fibrous_skeleton_of_heart bfo:part_of fma:Heart . +fma:Cavity_of_inflow_part_of_right_atrium bfo:part_of fma:Cavity_of_right_atrium . +fma:Ostium_of_superior_vena_cava bfo:part_of fma:Cavity_of_right_atrium . +fma:Ostium_of_inferior_vena_cava bfo:part_of fma:Cavity_of_right_atrium . +fma:Variant_branch_of_right_coronary_arterial_tree bfo:part_of fma:Right_coronary_artery . +fma:Recurrent_atrioventricular_branch_of_right_coronary_artery bfo:part_of fma:Right_coronary_artery . +fma:Lateral_atrial_branch_of_right_coronary_artery bfo:part_of fma:Right_coronary_artery . +fma:Wall_of_left_coronary_artery bfo:part_of fma:Left_coronary_artery . +fma:Lumen_of_left_coronary_artery bfo:part_of fma:Left_coronary_artery . +fma:Trunk_of_left_coronary_artery bfo:part_of fma:Left_coronary_artery . +fma:Membranous_part_of_interatrial_septum bfo:part_of fma:Interatrial_septum . +fma:Fibrocollagenous_connective_tissue_of_interatrial_septum bfo:part_of fma:Interatrial_septum . +fma:Muscular_part_of_interatrial_septum bfo:part_of fma:Interatrial_septum . +fma:Fibrocollagenous_connective_tissue_of_interventricular_septum bfo:part_of fma:Interventricular_septum . +fma:Muscular_part_of_interventricular_septum bfo:part_of fma:Interventricular_septum . +fma:Membranous_part_of_interventricular_septum bfo:part_of fma:Interventricular_septum . +fma:Endothelium_of_atrioventricular_septum bfo:part_of fma:Atrioventricular_septum . +fma:Fibroelastic_connective_tissue_of_endocardium bfo:part_of fma:Atrioventricular_septum . +fma:Neural_network_of_right_side_of_heart bfo:part_of fma:Right_side_of_heart . +fma:Small_cardiac_vein bfo:part_of fma:Right_side_of_heart . +fma:Right_marginal_vein bfo:part_of fma:Right_side_of_heart . +fma:Neural_network_of_left_side_of_heart bfo:part_of fma:Left_side_of_heart . +fma:Great_cardiac_vein bfo:part_of fma:Left_side_of_heart . +fma:Left_atrium bfo:part_of fma:Left_side_of_heart . +fma:Endothelium_of_tricuspid_valve bfo:part_of fma:Tricuspid_valve . +fma:Anterior_leaflet_of_tricuspid_valve bfo:part_of fma:Tricuspid_valve . +fma:Posterior_leaflet_of_tricuspid_valve bfo:part_of fma:Tricuspid_valve . +fma:Endothelium_of_mitral_valve bfo:part_of fma:Mitral_valve . +fma:Anterior_leaflet_of_mitral_valve bfo:part_of fma:Mitral_valve . +fma:Posterior_leaflet_of_mitral_valve bfo:part_of fma:Mitral_valve . +fma:Endothelium_of_aortic_valve bfo:part_of fma:Aortic_valve . +fma:Right_posterior_cusp_of_aortic_valve bfo:part_of fma:Aortic_valve . +fma:Anterior_cusp_of_aortic_valve bfo:part_of fma:Aortic_valve . +fma:Left_anterior_cusp_of_pulmonary_valve bfo:part_of fma:Pulmonary_valve . +fma:Right_anterior_cusp_of_pulmonary_valve bfo:part_of fma:Pulmonary_valve . +fma:Posterior_cusp_of_pulmonary_valve bfo:part_of fma:Pulmonary_valve . +fma:Endocardium bfo:part_of fma:Wall_of_heart . +fma:Epicardium bfo:part_of fma:Wall_of_heart . +fma:Myocardium bfo:part_of fma:Wall_of_heart . +fma:Ostium_of_least_cardiac_vein_of_right_ventricle bfo:part_of fma:Cavity_of_right_ventricle . +fma:Cavity_of_inflow_part_of_right_ventricle bfo:part_of fma:Cavity_of_right_ventricle . +fma:Ostium_of_pulmonary_trunk bfo:part_of fma:Cavity_of_right_ventricle . +fma:Cavity_of_inflow_part_of_left_atrium bfo:part_of fma:Cavity_of_left_atrium . +fma:Cavity_proper_of_left_atrium bfo:part_of fma:Cavity_of_left_atrium . +fma:Ostium_of_right_superior_pulmonary_vein bfo:part_of fma:Cavity_of_left_atrium . +fma:Cavity_of_inflow_part_of_left_ventricle bfo:part_of fma:Cavity_of_left_ventricle . +fma:Ostium_of_aorta bfo:part_of fma:Cavity_of_left_ventricle . +fma:Cavity_of_outflow_part_of_left_ventricle bfo:part_of fma:Cavity_of_left_ventricle . +fma:Connective_tissue_of_fibrous_skeleton_of_heart bfo:part_of fma:Fibrous_skeleton_of_heart . +fma:Fibrocollagenous_connective_tissue_of_fibrous_skeleton_of_heart bfo:part_of fma:Fibrous_skeleton_of_heart . +fma:Central_fibrous_body bfo:part_of fma:Fibrous_skeleton_of_heart . +fma:Cavity_proper_of_inflow_part_of_right_atrium bfo:part_of fma:Cavity_of_inflow_part_of_right_atrium . +fma:Ostium_of_coronary_sinus bfo:part_of fma:Cavity_of_inflow_part_of_right_atrium . +fma:Ostium_of_least_cardiac_vein_of_right_atrium bfo:part_of fma:Cavity_of_inflow_part_of_right_atrium . +fma:Lumen_of_trunk_of_left_coronary_artery bfo:part_of fma:Trunk_of_left_coronary_artery . +fma:Wall_of_trunk_of_left_coronary_artery bfo:part_of fma:Trunk_of_left_coronary_artery . +fma:Endocardium_of_interatrial_septum bfo:part_of fma:Membranous_part_of_interatrial_septum . +fma:Fibrocollagenous_connective_tissue_of_muscular_part_of_interatrial_septum bfo:part_of fma:Muscular_part_of_interatrial_septum . +fma:Heart rdfs:subClassOf fma:Organ_with_cavitated_organ_parts . +fma:Cavity_of_right_atrium rdfs:subClassOf fma:Cavity_of_atrium . +fma:Cardiac_vein rdfs:subClassOf fma:Systemic_vein . +fma:Neural_network_of_heart rdfs:subClassOf fma:Neural_network_of_organ . +fma:Coronary_sinus rdfs:subClassOf fma:Trunk_of_systemic_vein . +fma:Right_coronary_artery rdfs:subClassOf fma:Coronary_artery . +fma:Left_coronary_artery rdfs:subClassOf fma:Coronary_artery . +fma:Interatrial_septum rdfs:subClassOf fma:Cardiac_septum . +fma:Interventricular_septum rdfs:subClassOf fma:Cardiac_septum . +fma:Atrioventricular_septum rdfs:subClassOf fma:Region_of_membranous_part_of_interventricular_septum . +fma:Right_side_of_heart rdfs:subClassOf fma:Zone_of_heart . +fma:Left_side_of_heart rdfs:subClassOf fma:Zone_of_heart . +fma:Tricuspid_valve rdfs:subClassOf fma:Atrioventricular_valve . +fma:Mitral_valve rdfs:subClassOf fma:Atrioventricular_valve . +fma:Aortic_valve rdfs:subClassOf fma:Cardiac_valve . +fma:Pulmonary_valve rdfs:subClassOf fma:Cardiac_valve . +fma:Wall_of_heart rdfs:subClassOf fma:Wall_of_organ . +fma:Systemic_capillary_bed_of_heart rdfs:subClassOf fma:Systemic_capillary_bed . +fma:Lymphatic_capillary_bed_of_heart rdfs:subClassOf fma:Lymphatic_capillary_bed . +fma:Cavity_of_right_ventricle rdfs:subClassOf fma:Cavity_of_ventricle . +fma:Cavity_of_left_atrium rdfs:subClassOf fma:Cavity_of_atrium . +fma:Cavity_of_left_ventricle rdfs:subClassOf fma:Cavity_of_ventricle . +fma:Fibrous_skeleton_of_heart rdfs:subClassOf fma:Fibrous_connective_tissue . +fma:Cavity_of_inflow_part_of_right_atrium rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Ostium_of_superior_vena_cava rdfs:subClassOf fma:Orifice_of_vein . +fma:Ostium_of_inferior_vena_cava rdfs:subClassOf fma:Orifice_of_vein . +fma:Variant_branch_of_right_coronary_arterial_tree rdfs:subClassOf fma:Branch_of_right_coronary_artery . +fma:Recurrent_atrioventricular_branch_of_right_coronary_artery rdfs:subClassOf fma:Branch_of_right_coronary_artery . +fma:Lateral_atrial_branch_of_right_coronary_artery rdfs:subClassOf fma:Atrial_branch_of_right_coronary_artery . +fma:Wall_of_left_coronary_artery rdfs:subClassOf fma:Wall_of_coronary_artery . +fma:Lumen_of_left_coronary_artery rdfs:subClassOf fma:Lumen_of_coronary_artery . +fma:Trunk_of_left_coronary_artery rdfs:subClassOf fma:Trunk_of_coronary_artery . +fma:Membranous_part_of_interatrial_septum rdfs:subClassOf fma:Membranous_part_of_cardiac_septum . +fma:Fibrocollagenous_connective_tissue_of_interatrial_septum rdfs:subClassOf fma:Fibrocollagenous_sheath_of_cardiac_muscle_tissue . +fma:Muscular_part_of_interatrial_septum rdfs:subClassOf fma:Muscular_part_of_cardiac_septum . +fma:Fibrocollagenous_connective_tissue_of_interventricular_septum rdfs:subClassOf fma:Fibrocollagenous_sheath_of_cardiac_muscle_tissue . +fma:Muscular_part_of_interventricular_septum rdfs:subClassOf fma:Muscular_part_of_cardiac_septum . +fma:Membranous_part_of_interventricular_septum rdfs:subClassOf fma:Membranous_part_of_cardiac_septum . +fma:Endothelium_of_atrioventricular_septum rdfs:subClassOf fma:Endothelium_of_endocardium . +fma:Fibroelastic_connective_tissue_of_endocardium rdfs:subClassOf fma:Fibroelastic_connective_tissue . +fma:Neural_network_of_right_side_of_heart rdfs:subClassOf fma:Neural_network_of_organ_part . +fma:Neural_network_of_left_side_of_heart rdfs:subClassOf fma:Neural_network_of_organ_part . +fma:Left_atrium rdfs:subClassOf fma:Cardiac_atrium . +fma:Endothelium_of_tricuspid_valve rdfs:subClassOf fma:Endothelium_of_endocardium . +fma:Anterior_leaflet_of_tricuspid_valve rdfs:subClassOf fma:Leaflet_of_tricuspid_valve . +fma:Posterior_leaflet_of_tricuspid_valve rdfs:subClassOf fma:Leaflet_of_tricuspid_valve . +fma:Endothelium_of_mitral_valve rdfs:subClassOf fma:Endothelium_of_endocardium . +fma:Anterior_leaflet_of_mitral_valve rdfs:subClassOf fma:Leaflet_of_mitral_valve . +fma:Posterior_leaflet_of_mitral_valve rdfs:subClassOf fma:Leaflet_of_mitral_valve . +fma:Endothelium_of_aortic_valve rdfs:subClassOf fma:Endothelium_of_endocardium . +fma:Right_posterior_cusp_of_aortic_valve rdfs:subClassOf fma:Cusp_of_aortic_valve . +fma:Anterior_cusp_of_aortic_valve rdfs:subClassOf fma:Cusp_of_aortic_valve . +fma:Left_anterior_cusp_of_pulmonary_valve rdfs:subClassOf fma:Cusp_of_pulmonary_valve . +fma:Right_anterior_cusp_of_pulmonary_valve rdfs:subClassOf fma:Cusp_of_pulmonary_valve . +fma:Posterior_cusp_of_pulmonary_valve rdfs:subClassOf fma:Cusp_of_pulmonary_valve . +fma:Endocardium rdfs:subClassOf fma:Tunica_intima . +fma:Epicardium rdfs:subClassOf fma:Region_of_visceral_serous_pericardium . +fma:Myocardium rdfs:subClassOf fma:Muscle_body . +fma:Ostium_of_least_cardiac_vein_of_right_ventricle rdfs:subClassOf fma:Ostium_of_least_cardiac_vein . +fma:Cavity_of_inflow_part_of_right_ventricle rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Ostium_of_pulmonary_trunk rdfs:subClassOf fma:Orifice_of_artery . +fma:Cavity_of_inflow_part_of_left_atrium rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Cavity_proper_of_left_atrium rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Ostium_of_right_superior_pulmonary_vein rdfs:subClassOf fma:Ostium_of_superior_pulmonary_vein . +fma:Cavity_of_inflow_part_of_left_ventricle rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Ostium_of_aorta rdfs:subClassOf fma:Orifice_of_artery . +fma:Cavity_of_outflow_part_of_left_ventricle rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Connective_tissue_of_fibrous_skeleton_of_heart rdfs:subClassOf fma:Fibroelastic_connective_tissue . +fma:Fibrocollagenous_connective_tissue_of_fibrous_skeleton_of_heart rdfs:subClassOf fma:Fibrocollagenous_connective_tissue . +fma:Central_fibrous_body rdfs:subClassOf fma:Subdivision_of_fibrous_skeleton_of_heart . +fma:Cavity_proper_of_inflow_part_of_right_atrium rdfs:subClassOf fma:Subdivision_of_cavity_of_cardiac_chamber . +fma:Ostium_of_coronary_sinus rdfs:subClassOf fma:Orifice_of_vein . +fma:Ostium_of_least_cardiac_vein_of_right_atrium rdfs:subClassOf fma:Ostium_of_least_cardiac_vein . +fma:Lumen_of_trunk_of_left_coronary_artery rdfs:subClassOf fma:Lumen_of_trunk_of_coronary_artery . +fma:Wall_of_trunk_of_left_coronary_artery rdfs:subClassOf fma:Wall_of_trunk_of_coronary_artery . +fma:Endocardium_of_interatrial_septum rdfs:subClassOf fma:Endocardium_of_cardiac_septum . +fma:Fibrocollagenous_connective_tissue_of_muscular_part_of_interatrial_septum rdfs:subClassOf fma:Fibrocollagenous_sheath_of_cardiac_muscle_tissue . diff --git a/crates/osint-bake/src/bin/fma.rs b/crates/osint-bake/src/bin/fma.rs index f6bc36718..948e0f328 100644 --- a/crates/osint-bake/src/bin/fma.rs +++ b/crates/osint-bake/src/bin/fma.rs @@ -1,23 +1,32 @@ //! FMA anatomy slice — the "real test" of the dual-membership lattice. //! -//! **Hydrated from an FMA `.ttl` fixture** (`data/fma-heart.fixture.ttl`) via +//! **Hydrated from a real FMA heart subtree** (`data/fma-heart.ttl`, extracted +//! from the 266 MB `fma.owl` v5.0.0 by `tools/extract_fma_heart.py`) via //! [`hydrate_fma`] — no longer hand-built. Stands up a Foundational-Model-of- -//! Anatomy-shaped slice of the **heart** (organ → chambers → wall layers) and -//! proves that one node resolves to BOTH addresses at once: +//! Anatomy slice of the **heart** (organ → its 22 major subdivisions: the four +//! chamber cavities, the four valves, the three cardiac septa, both coronary +//! arteries, the wall's endo/epi/myocardium, the fibrous skeleton, … → their +//! sub-parts) and proves that one node resolves to BOTH addresses at once: //! -//! * **part-of position** (basin-local): HEEL=[Organ:Heart], HIP=[Chamber:id], -//! TWIG=[Wall:id] — where the node *is* in the body, read straight off the key -//! (the partonomy walk fills the cascade; deeper tiers stay 0 until the real -//! 75K FMA hydrates tissues/cells through the same walk). +//! * **part-of position** (basin-local): HEEL=[Organ:Heart], HIP=[part:id], +//! TWIG=[sub-part:id], LEAF=[deeper:id] — where the node *is* in the body, read +//! straight off the key (the partonomy walk fills the cascade; deeper tiers +//! stay 0 until the full 75K-term FMA hydrates tissues/cells through the same +//! walk). //! * **leaf-limited global type** (the CEILING pole, HEEL=HIP=TWIG=0xFFFF, -//! LEAF=type): "cardiac muscle tissue", "endothelium" — cross-cutting types -//! that appear in *every* chamber. The deepest sentinel run (through TWIG) -//! makes the LEAF the sole discriminator: "limited to the leaf". +//! LEAF=type): "Atrioventricular valve", "Endothelium of endocardium", +//! "Cavity of ventricle" — cross-cutting FMA `subClassOf` types carried by +//! structures in *different* parts of the heart. The deepest sentinel run +//! (through TWIG) makes the LEAF the sole discriminator: "limited to the leaf". //! -//! The same `Cardiac muscle tissue` ceiling node is the `is-a` target for the -//! myocardium tissue in all four chambers — visibly cross-cutting. Emits -//! `cockpit/public/fma.soa` (OSO1, the cockpit's `/fma` view reads it) and -//! prints the dual-membership proof. +//! The proof picks the strongest real cross-cutter in the slice — the ceiling +//! type that is the `is-a` target of the most structures (e.g. `Subdivision of +//! cavity of cardiac chamber`, shared by the inflow/outflow cavities of all four +//! chambers; or `Endothelium of endocardium`, shared by the valve and septum +//! endothelia) — then a member sitting deepest in the body, and shows it +//! resolving to both its part-of cascade and that shared global type. Emits +//! `cockpit/public/fma.soa` (OSO1, the cockpit's `/fma` view reads it) and prints +//! the dual-membership proof. //! //! ## Relation to OGAR PR #116 (the FMA canon) //! @@ -171,12 +180,14 @@ impl Builder { } } -/// Embedded FMA heart fixture — real class names + the canonical FMA predicate -/// set. The production path hydrates the 266 MB `fma.owl` through -/// `lance-graph-rdf` at the spine; this light bake hydrates the fixture so -/// `/fma` renders without the lance/datafusion closure. See -/// `data/fma-heart.fixture.ttl`. -const FMA_TTL: &str = include_str!("../../data/fma-heart.fixture.ttl"); +/// Embedded real FMA heart subtree — extracted from `fma.owl` (FMA v5.0.0, +/// CC BY 3.0) by `tools/extract_fma_heart.py`: a balanced BFS of the +/// `regional_part`/`constitutional_part` partonomy from `FMA:Heart` (depth ≤ 4, +/// per-parent fan-out capped so no single subtree dominates). The production +/// path hydrates the full 266 MB `fma.owl` through `lance-graph-rdf` at the +/// spine; this light bake hydrates the committed subtree so `/fma` renders +/// without the lance/datafusion closure. See `data/fma-heart.ttl`. +const FMA_TTL: &str = include_str!("../../data/fma-heart.ttl"); /// Hydrate an FMA `.ttl` fragment into the bake's [`Builder`] — the light-bake /// twin of `lance_graph_ontology::hydrate_fma`. Walk the `bfo:part_of` partonomy @@ -321,17 +332,51 @@ fn main() { let b = hydrate_fma(FMA_TTL); let bytes = emit_oso1(&b); - // dual-membership proof: a hydrated wall layer carries BOTH addresses. - let tissue = b - .nodes + // Dual-membership proof: pick the strongest real cross-cutter in the slice — + // the ceiling TYPE that is the `is-a` target of the most structures — then the + // member sitting deepest in the body. Whatever the hydrated heart slice + // contains, this surfaces a node that genuinely resolves to BOTH a basin-local + // part-of cascade and a cross-cutting global type. `tier_depth` counts the + // non-zero HHTL tiers: how deep into the body the part-of address runs. + let tier_depth = |n: usize| -> usize { + let k = &b.nodes[n].key; + [k.heel(), k.hip(), k.twig(), k.leaf(), k.family_v2()] + .iter() + .filter(|&&t| t != 0) + .count() + }; + // group is-a edges by ceiling-type target → the members that share it. + let mut by_type: std::collections::BTreeMap> = + std::collections::BTreeMap::new(); + for &(s, t, rel) in &b.edges { + if rel == REL_IS_A { + by_type.entry(t).or_default().push(s); + } + } + // strongest cross-cutter: most members, ties broken by label (deterministic). + let (>ype, sharers) = by_type + .iter() + .max_by(|x, y| { + x.1.len() + .cmp(&y.1.len()) + .then_with(|| b.nodes[*y.0].label.cmp(&b.nodes[*x.0].label)) + }) + .expect("the slice has is-a edges"); + // anchor: the member sitting deepest in the body (ties broken by label). + let &anchor = sharers .iter() - .position(|x| x.label == "Myocardium of left ventricle") - .expect("LV myocardium hydrated from the fixture"); - let key = &b.nodes[tissue].key; + .max_by(|&&x, &&y| { + tier_depth(x) + .cmp(&tier_depth(y)) + .then_with(|| b.nodes[y].label.cmp(&b.nodes[x].label)) + }) + .expect("a cross-cutting type has members"); + + let key = &b.nodes[anchor].key; + let show = |t: u16| format!("[{:02x}:{:02x}]", t >> 8, t & 0xFF); println!("── FMA dual-membership proof ──"); - println!("node: {}", b.nodes[tissue].label); + println!("node: {}", b.nodes[anchor].label); // the partonomy IS the key: each HHTL tier is an 8:8 [mixin:identity] pair. - let show = |t: u16| format!("[{:02x}:{:02x}]", t >> 8, t & 0xFF); println!( " part-of address — HHTL 8:8 [mixin:identity]: HEEL {} HIP {} TWIG {} LEAF {} family {}", show(key.heel()), @@ -340,13 +385,7 @@ fn main() { show(key.leaf()), show(key.family_v2()), ); - // its is-a edge → the leaf-limited global type (ceiling pole) - let gtype = b - .edges - .iter() - .find(|&&(s, _, rel)| s == tissue && rel == REL_IS_A) - .map(|&(_, t, _)| t) - .expect("tissue is-a a global type"); + // its is-a edge → the leaf-limited global type (ceiling pole). let gk = &b.nodes[gtype].key; println!( " is-a global type (ceiling): {} HEEL={:#06x} HIP={:#06x} TWIG={:#06x} LEAF={}", @@ -356,16 +395,15 @@ fn main() { gk.twig(), gk.leaf() ); - // cross-cutting: how many chambers' tissues share this one global type? - let members = b - .edges - .iter() - .filter(|&&(_, t, rel)| t == gtype && rel == REL_IS_A) - .count(); + // cross-cutting: how many structures across the heart share this one type? println!( - " '{}' is the is-a target of {members} tissues across the chambers (cross-cutting)", - b.nodes[gtype].label + " '{}' is the is-a target of {} structures in different parts of the heart (cross-cutting)", + b.nodes[gtype].label, + sharers.len() ); + let mut shared_by: Vec<&str> = sharers.iter().map(|&m| b.nodes[m].label.as_str()).collect(); + shared_by.sort_unstable(); + println!(" shared by: {}", shared_by.join(", ")); let n_types = b.nodes.iter().filter(|x| x.class == C_TYPE).count(); println!( diff --git a/crates/osint-bake/tools/extract_fma_heart.py b/crates/osint-bake/tools/extract_fma_heart.py new file mode 100644 index 000000000..5644b5763 --- /dev/null +++ b/crates/osint-bake/tools/extract_fma_heart.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Extract the FMA *heart* part-of subtree from the real fma.owl into the +line-oriented Turtle subset the q2 light bake hydrates (osint-bake/src/fma_ttl.rs). + +Source (provenance): fma.owl, FMA v5.0.0, BioPortal #29 / UW Structural +Informatics Group, CC BY 3.0, SHA-256 59465eb503c418dbf9216d362b94c7f42bc3b96b297e553ec17c7fee77979e44, +mirrored at github.com/AdaWorldAPI/q2/releases/download/fma-ontology-5.0.0/fma.owl + +This is a one-off data-prep tool. The PRODUCTION hydrator is +lance_graph_ontology::hydrate_fma (rio_xml) at the spine; this script exists so +the light bake (no lance/datafusion closure) can carry a *real* heart subtree +instead of a hand-authored fixture. Streaming ET.iterparse keeps memory bounded +(only label / part / superclass maps are retained, not the 1.5M-triple graph). + +Usage: python3 extract_fma_heart.py +""" +import sys +import xml.etree.ElementTree as ET + +RDF = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}" +RDFS = "{http://www.w3.org/2000/01/rdf-schema#}" +OWL = "{http://www.w3.org/2002/07/owl#}" +BASE = "http://purl.org/sig/ont/fma/" + +# forward "has-part" properties (whole → part); the part is a CHILD of the whole. +PART_PROPS = {"regional_part", "constitutional_part", "systemic_part"} +HEART = "fma7088" # FMA:Heart +MAX_DEPTH = 4 # organ → chamber → wall → tissue +MAX_NODES = 80 # keep the cockpit slice legible +MAX_CHILDREN = 3 # per NON-root parent — a balanced cross-section, not a + # coronary-artery dump (FMA's coronary tree has ~40 named + # branches; uncapped BFS lets it starve every other part). + # The root (Heart) is exempt: all its major subdivisions + # — 4 chamber cavities, 4 valves, 3 septa, both coronary + # arteries, wall, fibrous skeleton, … — are the point. + +CLEAR_TAGS = {OWL + t for t in ("Class", "Axiom", "ObjectProperty", + "AnnotationProperty", "DatatypeProperty", + "NamedIndividual")} | {RDF + "Description"} + + +def local(iri): + return iri.rsplit("/", 1)[-1] if iri else None + + +def main(src, out): + label, children, supers = {}, {}, {} + for _ev, elem in ET.iterparse(src, events=("end",)): + if elem.tag == OWL + "Class": + about = elem.get(RDF + "about") + if about and about.startswith(BASE): + cid = local(about) + lab = elem.find(RDFS + "label") + if lab is not None and lab.text: + label[cid] = lab.text.strip() + for sc in elem.findall(RDFS + "subClassOf"): + res = sc.get(RDF + "resource") + if res and res.startswith(BASE): # named superclass → is-a + supers.setdefault(cid, []).append(local(res)) + continue + restr = sc.find(OWL + "Restriction") + if restr is None: + continue + op = restr.find(OWL + "onProperty") + sv = restr.find(OWL + "someValuesFrom") + if op is None or sv is None: + continue + prop = local(op.get(RDF + "resource") or "") + tgt = sv.get(RDF + "resource") + if prop in PART_PROPS and tgt and tgt.startswith(BASE): + children.setdefault(cid, []).append(local(tgt)) # tgt is part of cid + if elem.tag in CLEAR_TAGS: + elem.clear() + + # BFS the heart subtree; first whole to reach a part becomes its tree-parent + # (FMA part-of is a DAG; we take a spanning tree rooted at the heart). Each + # non-root whole contributes at most MAX_CHILDREN parts, so breadth stays + # balanced across the heart's subdivisions instead of draining into one + # deep subtree. Because every level-1 node is popped before any level-2 node + # (plain BFS), this caps fan-out per parent fairly across the whole level. + parent, depth, order = {}, {HEART: 0}, [HEART] + queue = [HEART] + while queue and len(order) < MAX_NODES: + n = queue.pop(0) + if depth[n] >= MAX_DEPTH: + continue + cap = MAX_NODES if n == HEART else MAX_CHILDREN # root keeps every major part + taken = 0 + for c in sorted(set(children.get(n, []))): + if taken >= cap: + break + if c in depth or c not in label: # unseen + actually a labelled class + continue + parent[c], depth[c] = n, depth[n] + 1 + order.append(c) + queue.append(c) + taken += 1 + if len(order) >= MAX_NODES: + break + + def tok(cid): + # real FMA label → a prefix:token the line-Turtle reader splits on + # (label_of() turns '_' back into spaces). alnum kept, else '_'. + lab = label.get(cid, cid) + t = "".join(ch if ch.isalnum() else "_" for ch in lab).strip("_") + while "__" in t: + t = t.replace("__", "_") + return "fma:" + t + + lines = [ + "# fma-heart.ttl — REAL FMA heart subtree, extracted from fma.owl (FMA v5.0.0).", + "# Source: BioPortal #29 / UW Structural Informatics Group, CC BY 3.0,", + "# SHA-256 59465eb503c418dbf9216d362b94c7f42bc3b96b297e553ec17c7fee77979e44,", + "# mirror github.com/AdaWorldAPI/q2/releases/download/fma-ontology-5.0.0/fma.owl", + "# Generated by tools/extract_fma_heart.py (streaming ET; regional_part +", + "# constitutional_part partonomy, BFS from FMA:Heart fma7088, depth<=%d, <=%d nodes)." + % (MAX_DEPTH, MAX_NODES), + "# The 266 MB OWL is NOT committed; the production hydrator is the spine's", + "# lance_graph_ontology::hydrate_fma. This is the real-data heart slice the", + "# q2 /fma render surface hydrates.", + "", + ] + seen_types = set() + for c in order: + if c in parent: + lines.append(f"{tok(c)} bfo:part_of {tok(parent[c])} .") + for c in order: # cross-cutting type: first named superclass + for s in supers.get(c, []): + if s in label and s not in depth: # a real category outside the part-of tree + lines.append(f"{tok(c)} rdfs:subClassOf {tok(s)} .") + seen_types.add(s) + break + open(out, "w").write("\n".join(lines) + "\n") + + # inspection summary to stderr + print(f"heart subtree: {len(order)} nodes (<= depth {MAX_DEPTH}), " + f"{sum(1 for c in order if c in parent)} part-of edges, " + f"{len(seen_types)} cross-cutting types", file=sys.stderr) + for c in order[:14]: + print(f" depth {depth[c]} {label.get(c,c)}" + + (f" ⊂ {label.get(parent[c])}" if c in parent else " (root)"), file=sys.stderr) + + +if __name__ == "__main__": + main(sys.argv[1], sys.argv[2])