Skip to content

Latest commit

 

History

History
361 lines (285 loc) · 22.8 KB

File metadata and controls

361 lines (285 loc) · 22.8 KB

Architecture

OpenSource Ortho is a Python-first planning and visualization safety playground for clear-aligner workflows. The current milestone is a clean, inspectable engine that can import dental meshes, represent staged movement, check configured movement caps, generate manufacturing-oriented exports, and feed an accurate UI. It is not a complete treatment-planning system. The roadmap has tiered review modes: STL-only surface review, enhanced-record review, CBCT-attached review, and root/bone-aware review only when registered, reviewed anatomy is trusted.

Full Treatment-System North Star

The product direction is to get as close as possible to the functional coverage of a modern commercial clear-aligner treatment system while staying open, auditable, privacy-preserving, and safety-boundary-first. Commercial systems are workflow references only; do not copy proprietary UI, source, assets, private workflows, or product naming. The open target is generic capability parity where lawful and clinically honest, not a clone or a medical-device claim.

Short version of the commercial reference workflow:

  1. Capture patient records with an intraoral scanner and related records such as photos, bite scans, prescription/Rx preferences, and optionally CBCT.
  2. Convert the scan into segmented dentition and a proposed setup using a cloud-backed planning system, doctor preferences, historical case data, and technician/CAD workflows.
  3. Let the doctor modify the setup with plan-editing tools: side-by-side setup comparison, direct 3D tooth controls, arch-form controls, attachment/cut placement, IPR/spacing controls, tooth locking, automatic same-arch response, and fast restaging after edits.
  4. Review roots/bone when CBCT-derived anatomy is available, then approve the plan in the commercial system so manufacturing can begin.
  5. Manufacture aligners using validated material/process controls, monitor progress against the planned stages, handle refinements, and retain the result.

OpenSource Ortho's matching architecture should therefore grow in these generic layers:

  • Data flywheel: privacy-preserving longitudinal case bundles: initial scans, target/final scans, refinements, intended movements, controls, and outcome notes. Single pre-treatment STL files help segmentation, but paired before/after and refinement data are what teach target setup and tracking.
  • Setup workbench: side-by-side comparison of original, generated, edited, and saved-version setups, all backed by plan hashes and deterministic checks.
  • 3D controls: direct authoring for translation, intrusion/extrusion, rotation, crown tip, crown torque, arch-form edits, attachments/cuts, IPR/spacing, and tooth locks. Controls may be edited from STL surface geometry, but root-sensitive controls must stay flagged until trusted anatomy exists.
  • Live restaging: after an edit, run target resolution, staging optimization, collision/IPR checks, fixed-tooth/exclusion checks, and timeline projection immediately, then show diffs rather than silently replacing the plan.
  • Automatic arch response: when a tooth or arch-form change is made, propose coupled same-arch adjustments to maintain contacts/spacing. The response is a visible proposal with provenance, not hidden clinical intent.
  • Manufacturing/monitoring loop: exported geometry, shell QA, progress scans, refinement records, and final scans should feed back into the case bundle for validation and model improvement.

The safety boundary is unchanged: a live-updated or data-informed plan can be internally consistent without being safe, clinically appropriate, approved, or complete.

Simple Flow

  1. The user uploads an intraoral-scan mesh, usually an STL file.
  2. The mesh is imported and labeled with provenance: patient-derived, imported, manual, model-generated, or synthetic.
  3. Teeth are segmented into individual tooth meshes. Supported paths: manual/imported per-tooth STLs, and an on-device hybrid geometric auto-segmenter (orthoplan/segmentation/, exposed as POST /api/segment) that proposes per-tooth regions for human review. It combines arch position, crown-height valleys, curvature, and face-normal changes into graph-cut-style boundaries, with optional Open3D mesh-processing support. When the request carries a plan with trusted, gate-passing registered CBCT anatomy, volume-derived interproximal boundary priors bias the cut placement and cut/prior agreement calibrates the per-tooth confidence (see the CBCT fidelity path below). A learned model (e.g. Teeth3DS) can replace the geometric proposal behind the SegmentationModel seam; it must run locally (scans are PHI).
  4. A TreatmentPlan stores each stage. Each Stage contains ToothDelta values for individual teeth.
  5. The planning layer checks each stage against user-configured MovementCaps.
  6. The visualization layer converts the plan into cumulative StageProgressFrame objects.
  7. The UI renders current, staged, and planned positions from those cumulative frames.
  8. Deterministic rules run first. Model providers such as OpenAI or Claude Code may add advisory findings, but all findings pass through lint_finding().
  9. Optional print-export settings record intended file format, delivery email, materials, acknowledgement, and export blockers; print-package can generate stage proxy STL files, a manifest, zip package, and email draft.

CBCT/DICOM is not required for every workflow. It is the higher-fidelity path for root/bone-aware checks when the app has local DICOM metadata, STL-to-CBCT registration, reviewed anatomy, and deterministic root/bone-aware findings. Local raw-volume sparse-mask anatomy proposals and automatic STL-to-CBCT registration proposals exist as safety-gated review packets; they are never trusted until deterministic checks and explicit human review/acceptance succeed. The canonical sample also includes a redacted CBCT metadata artifact and a safe root/bone engineering fixture so the UI/API can exercise the full gate sequence without committing raw DICOM bytes. See cbct-evaluation.md.

Language and Stack

The current scaffold is Python 3.11+.

Python is a good first language here because:

  • dental mesh and medical-imaging libraries are mature in Python
  • Pydantic gives us explicit, serializable plan objects
  • Open3D can support optional mesh processing; PyVista/VTK can support early visualization
  • later web UIs can consume the same JSON frame contract

The likely future split:

  • Python: data model, IO, planning, evaluation, CLI, test fixtures
  • Three.js or VTK/PyVista: interactive 3D visualization
  • Optional 3D Slicer extension or VTK-style tooling: heavyweight dental/CBCT workflow integration

The browser UI never reimplements the engine. orthoplan/api.py exposes pure entry points, and orthoplan/server.py serves them: POST /api/evaluate for findings/frames, POST /api/print-package (print_package_payload, reusing export_print_package) which returns a base64 zip of stage proxy STLs + manifest and an .eml draft for the guided Print / send step, and POST /api/segment (segment_payload) which runs the on-device segmenter on a server-local scan and returns a reviewable per-tooth proposal (confidence, linted advisory findings, and a ready-to-merge mesh_assets/tooth_meshes fragment). The UI sends plan-shaped JSON and renders the returned findings, data gaps, data-gap actions, timeline projection, and StageProgressFrame data verbatim - so there is exactly one source of truth for movement and policy. The 3D progress viewer (Three.js, vendored) renders exact whole-arch STL scans, schematic/marker movement when segmentation is not reviewed, and reviewed per-tooth STL fragments when render_meshes links are available; it draws rotation only where the engine marks it renderable. PCA tooth_frames are exposed as approximate metadata but do not make rotation renderable.

The UI is a static browser workspace under ui/. It opens by default into a guided, six-step wizard (ui/guided.js) for non-technical users; the dense Technician workspace is one toggle away. A self-contained Sample test case (ui/sample.js) reuses the guided wizard, pre-loaded with the bundled test-case STL scans and the canonical root/bone fixture, and snapshots/restores the user's working state (including any applied segmentation) so it stays isolated. On entry the sample loads accepted fixture registrations and reviewed derived landmarks, then runs the on-device auto-segmenter on the bundled scans and applies the per-tooth draft (sample-only pre-applying, clearly labeled). The sample therefore demonstrates the scan's own crowns moving across stages plus root/bone-aware review tier state, anatomical frames, root/bone context, and CBCT boundary priors; a user's own plan still requires explicit review + apply. The UI mirrors the Python data contract but does not run backend STL inspection itself.

The local development server can also serve registered per-tooth STL meshes from a local mesh workspace. Plan JSON still does not contain mesh bytes; it contains mesh_asset_id links. orthoplan register-mesh copies an STL into a local workspace registry, and /api/mesh/<mesh_asset_id> serves only registered files under that workspace. The UI uses render_meshes links from evaluate_plan() to load real tooth meshes when available and falls back to schematic proxies when a mesh is missing.

Core Objects

TreatmentPlan

  • plan ID and title
  • numbering system such as FDI, Universal, or Palmer
  • DataAvailability manifest
  • staged movement list
  • movement settings

Stage

  • one aligner-style step
  • list of per-tooth movement deltas

ToothDelta

  • tooth identity
  • arch
  • translation in millimeters
  • rotation in degrees
  • coordinate frame
  • movement source

ToothId

  • canonical FDI identity in Phase 1 (two-digit notation)
  • arch is derived from the FDI quadrant, never stored separately, so the two can never contradict
  • mixed numbering systems are rejected at plan creation rather than silently normalized

CoordinateFrame

  • typed, named frame replacing the former free-form string
  • declares axis semantics: z is the occlusogingival (vertical) axis; x/y span the horizontal plane
  • per-tooth mesiodistal/buccolingual axes are unresolved at the global scan level, so tip/torque/rotation are not renderable in Phase 1

MovementCaps

  • user-configurable per-stage caps held in an AxisCaps block (default)
  • per_tooth_overrides is reserved so tooth-class-specific caps can be added later without changing the rule interface
  • horizontal linear_mm caps the Euclidean magnitude of x/y movement; intrusion_extrusion_mm caps vertical movement separately
  • default values are literature/vendor heuristics, not medical clearance
  • caps are not evaluated until scan units are confirmed (see STL Upload)

StageProgressFrame

  • cumulative tooth positions at a stage
  • carries rotation_renderable and a crown-centroid pivot label
  • data gaps attached for the UI
  • designed so the UI does not recalculate treatment movement differently from the engine; transform composition lives in planning/transforms.py, and viz only consumes it

Safety-Review Tiers

Surface Review: STL Upload

The initial user upload can be an STL of an intraoral scan. STL contains surface geometry only. It does not contain roots, bone, periodontal status, occlusion dynamics, diagnosis, or CBCT anatomy.

That means the UI must show these data gaps. A surface scan can support crown-surface visualization, staged crown movement, arch-form proposals, crown-collision checks, and manufacturing-oriented handoff. It cannot prove root position, bone safety, periodontal suitability, or readiness for physical use.

STL upload is metadata-only in Phase 1 (orthoplan/io/stl_import.py):

  • mesh bytes are never stored in the serialized plan; only redacted metadata and an optional relative reference are kept
  • absolute paths and directory structure (which often carry patient names) are stripped
  • STL files carry no units, so units default to unverified and must be confirmed by the user before movement-cap evaluation runs
  • a bounding-box sanity check can warn about implausible scale, but never infers units

Root/Bone-Aware Review: CBCT/DICOM

CBCT/DICOM is an optional higher-fidelity tier rather than a universal requirement. It unlocks root/bone-aware behavior only behind these contracts:

  • local DICOM/CBCT record ingestion with PHI-aware metadata handling
  • on-device volume viewing or trusted local viewer integration
  • explicit STL-to-CBCT registration with quality metrics
  • reviewable derived anatomy such as roots, alveolar bone, and tooth axes
  • deterministic root/bone-aware findings that fail closed when registration or segmentation quality is missing

CBCT-derived data must enter planning through typed model contracts with provenance and review status. The planner must never silently assume that STL and CBCT coordinate spaces are aligned, and no review tier may imply clinical approval or complete treatment planning.

The CBCT fidelity path is sequenced fail-closed, each step gating the next:

  1. Registration quality gate (model/registration_gate.py): a numeric PASS/MARGINAL/FAIL judgement of each registration's recorded metrics (RMSE, fitness, inlier ratio). A reviewer's acceptance click can never unlock CBCT-derived behavior when the metrics contradict it; every CBCT consumer (anatomy proposals, boundary priors, axis frames, root/bone checks) consults the gate. Verdicts surface in the evaluate payload (registration.gate).
  2. Boundary priors + cross-modal confidence (segmentation/cbct_prior.py, segmentation/prior_blend.py): trusted per-tooth root/axis anatomy behind an open gate is mapped through the inverse registration into scan space; the midpoints between adjacent teeth become interproximal priors that BIAS (never replace) the hybrid segmenter's cut placement. Cut/prior agreement is then a measured per-tooth confidence calibration: disagreement always lowers confidence, and raising it above the surface-only score requires a PASS gate (capped below certainty). The /api/segment payload may carry the plan; the response reports the cbct_prior block.
  3. Trusted anatomical frames (planning/anatomical_frame.py): a trusted CBCT tooth axis (open gate, invertible registration, computable arch tangent from neighbouring crown centroids) rebuilds that tooth's local frame as non-approximate, which is exactly the validated frame source that makes tip/torque/rotation renderable (see below).
  4. Root-aware movement checks (evaluation/rules/root_bone.py, evaluation/rules/root_apex.py): root proximity, cortical-boundary proximity, and a geometric root-apex sweep estimate (root_length * sin(angulation) about the crown-centroid pivot) that documents every trusted tooth's estimate and warns past a stated review threshold. All of it emits cannot assess rather than guessing when readiness is missing.

How The Plan Moves Teeth

A plan does not deform a tooth mesh. It applies rigid transforms to segmented tooth objects:

  • translation along x/y/z in millimeters
  • tip, torque, and rotation in degrees
  • cumulative movement from stage 0 through the selected stage
  • the visualization pivot is the crown centroid, an explicit visualization assumption and not a biomechanical claim about root position

Translation accumulates as a true vector sum (translation is commutative), so cumulative translation is geometrically correct. For example, if tooth 11 moves 0.2 mm in stage 0 and 0.3 mm in stage 1, the stage-1 frame shows 0.5 mm.

Rotation is different. Summed Euler components (tip/torque/rotation) are not a composable rigid rotation, and converting them to a renderable transform requires per-tooth anatomical axes that the Phase 1 scan frame does not resolve. So frames report cumulative rotation values but flag rotation_renderable=False; a UI must not build a rotation matrix from a non-renderable pose. This is why build_stage_progress_frames() exists and why transform composition lives in planning/transforms.py.

Approximate crown-surface PCA frames are not enough to change that flag. The validated frame source is the trusted-CBCT-axis path (planning/anatomical_frame.py): when a plan carries a human-reviewed, in-field CBCT tooth axis behind a registration whose numeric quality gate is open, evaluate_plan() rebuilds that tooth's local frame as non-approximate (long axis from the registered CBCT axis, mesiodistal from the arch tangent through neighbouring crown centroids, buccolingual as their cross product) and rotation becomes renderable for that tooth only. Everything else keeps rotation_renderable=False.

For local real-mesh visualization, planning/mesh_transform.py can transform externally supplied per-tooth vertices by cumulative translation while preserving the same rotation honesty rule. The server/UI path uses the same stage frames; real STL meshes are visual geometry, not a different planning engine.

Plan Generation

planning/generate.py turns the best available target into cap-respecting staging by reusing the existing optimizer (planning/optimizer.py) - it never re-implements staging or caps. Target resolution is, in order: authored movement; a landmark-derived plan (per-tooth crown landmarks → real arch-form deviation targets + arch-length/space analysis in planning/arch_analysis.py, assembled with IPR, attachments, and approximate collision bounds in planning/landmark_plan.py); a geometry-derived arch-form fit over visible segmented crowns (planning/arch_form.py); or a labeled educational template. It has no model calls.

The top-level generation.py gateway composes the deterministic generator with run_rules validation, a deterministic correctness review, and an optional consent-gated model review reusing evaluation/advisory.py. The UI consumes the returned staged plan through the same stage-frame contract as everything else; it does not re-stage. See SAFETY.md for the boundary.

The correctness review emits explicit named checks, each with a pass/fail and a severity. Gate checks (caps-respected, fixed-teeth-unmoved, exclusions-respected, targets-reached, stages-contiguous) decide the verdict (CONSISTENT/ISSUES/NOT_APPLICABLE, never an approval). scale-confirmed is a warning check and collisions-checked is informational (it reports when the overlap check is vacuous because no segmented teeth exist). targets-reached regression-checks that the staged plan's cumulative movement actually reaches the requested target, so a staging bug cannot pass silently.

Plan Versions

cases.py stores a CaseStore of CaseRecords, each holding ordered PlanVersion snapshots (full plan JSON + content hash + engine version + note). A "case" groups versions of one plan; the default case id is the plan id. case_api.py wraps this in dict-in/dict-out functions used by both the server (POST /api/plan/version, GET /api/cases, GET /api/cases/<case_id>) and the CLI (case-save, case-list, case-versions). The store path defaults to .orthoplan-cases.json and is overridable via ORTHOPLAN_CASE_STORE. The UI's Versions panel saves snapshots and restores any version back into the editor.

Handoff Reports

orthoplan report PLAN.json emits a deterministic handoff artifact. The report includes the engine version, canonical plan hash, stable evaluation hash, input metadata, findings, data-gap actions, timeline, progress frames, review metadata, and a report hash. It can also include an optional HMAC-SHA256 signature. It does not include mesh bytes. The goal is reproducible review: the artifact ties inputs to engine behavior and makes provenance auditable without claiming approval.

Print Export Architecture

TreatmentSettings.print_export records export intent: format (stl/3mf), optional delivery email, model material, thermoforming material, post-processing notes, and a safety acknowledgement. orthoplan.printing.build_print_export_status() turns that intent into a readiness object with blockers and planned artifact filenames.

orthoplan print-package PLAN.json --out DIR generates stage proxy STL files from the current plan frames, a manifest, an optional deterministic zip package, and an optional .eml email draft. The manifest records the engine version, canonical plan hash, stage-frame hash, per-artifact SHA-256 hashes, byte sizes, blockers, and geometry-source metadata. When a tooth has linked segmented mesh bounds, the generated proxy is sized from those bounds; otherwise it is explicitly labeled as schematic proxy geometry. These are objective geometry outputs from the supplied plan data. Material choice, printing, post-processing, and any physical use are the user's own responsibility and risk. The package is not a certification, warranty, clearance, or authorization to wear or physically use an appliance.

The next print-geometry phase should package intentionally supplied mesh bytes and transform actual per-tooth vertices instead of bounded proxy solids.

Acquisition Advisor

orthoplan/evaluation/acquisition.py computes deterministic marginal data-acquisition impact. For each applicable missing modality, it builds a counterfactual plan with only that availability flag filled, reruns run_rules(), and diffs findings/data gaps. The output says which absence-of-data findings would clear, which suppressed checks would run, and which gap entries would close. It never predicts what the acquired data would show.

Parallel Agent Review

Parallel LLM/sub-agent review can be useful as an optional second-opinion layer: one agent may summarize geometric consistency, another may inspect data gaps, another may review print/export readiness, and another may produce handoff questions. This is not necessary for the core application to be correct, because deterministic rules remain the source of truth. Any agent-generated output must enter as advisory text, pass the same lint/quarantine boundary, and must never suppress, overwrite, or weaken deterministic findings.

Movement Settings

Movement settings are guardrails, not approval. Defaults are configurable.

Initial heuristic defaults (AxisCaps):

  • linear movement: 0.25 mm per stage (Euclidean magnitude of horizontal x/y movement)
  • angular movement: 1.0 degree per stage (tip and torque)
  • rotation: 2.0 degrees per stage
  • intrusion/extrusion: 0.10 mm per stage (vertical, evaluated separately)

These defaults are based on commonly cited clear-aligner staging ranges and should be treated as starting points for research tooling only. The software must never claim that a movement within these values is safe.

Timeline is an arithmetic projection, not an outcome estimate. Only inputs are stored (stage count comes from the plan; wear_interval_days defaults to 14). Duration is computed on demand in planning/timeline.py and always carries the caveat that the projection excludes refinements, compliance variation, pauses, and user-directed changes.