Skip to content

feat(dental-cpr): world's first open-source OHIF dental panoramic CPR extension#12

Merged
Ambientwork merged 26 commits intomainfrom
Ambientwork/dental-tools-extension
Mar 24, 2026
Merged

feat(dental-cpr): world's first open-source OHIF dental panoramic CPR extension#12
Ambientwork merged 26 commits intomainfrom
Ambientwork/dental-tools-extension

Conversation

@Ambientwork
Copy link
Copy Markdown
Owner

@Ambientwork Ambientwork commented Mar 23, 2026

Summary

  • World's first open-source OHIF dental panoramic CPR extension@ambientwork/ohif-extension-dental-cpr
  • Dental tools extension@ambientwork/ohif-extension-dental-tools (nerve canal, tooth annotation, bone thickness, implant planning)
  • Custom OHIF Docker buildDockerfile.ohif multi-stage: node:22-slim builder + nginx:1.27-alpine runner
  • 3-panel CBCT layout — axial (draw arch) | panoramic CPR | cross-section ⊥ to arch
  • Click-to-cross-section — click on panoramic CPR fires ARCH_CROSS_SECTION_POSITIONDentalCrossSectionViewport updates via vtkImageReslice

Docker Build

webpack 5.94.0 compiled with 14 warnings, 0 errors
Image: ambientct/ohif:latest — 194MB nginx

Build fixes resolved across 9 iterations:

  1. SSL: ca-certificates in apt-get
  2. Script paths: process.cwd() instead of __dirname (scripts run from /tmp)
  3. register-plugins.js: Step B (App.tsx patch) non-fatal when pluginConfig.json succeeded
  4. node-gyp: python3 make g++ for native addon compilation
  5. JSX/TSX transpilation: scripts/ohif/compile-extensions.js pre-builds extensions with @babel/core from OHIF's node_modules (modules: false preserves ESM for @cornerstonejs exports fields)

Test Coverage

  • extensions/dental-cpr/tests/buildCenterline.test.ts — 6 tests (catmullRomSpline, mat3ToQuat, all 4 Shepperd branches)
  • extensions/dental-tools/tests/boneThickness.test.js — 4 tests (including empty array guard)
  • extensions/dental-tools/tests/fdi.test.js — 4 tests (including boundary: fdi(0)=null, fdi(99)=null)
  • 17 tests passing total

Architecture

extensions/
  dental-cpr/src/
    tools/DentalArchSplineTool.ts   — Catmull-Rom spline + Frenet frames
    viewports/DentalCPRViewport.tsx — vtkImageCPRMapper panoramic reconstruction
    viewports/DentalCrossSectionViewport.tsx — vtkImageReslice ⊥ cross-section
    utils/buildCenterline.ts        — CenterlinePoint[], Frenet-Serret frames
    hanging-protocols/cbctDentalHP.ts — 3-panel 2×2 CBCT layout
  dental-tools/src/
    tools/NerveCanalTool.js         — SplineROI + safety margin
    tools/ToothAnnotationTool.js    — ArrowAnnotate + FDI picker
    tools/BoneThicknessTool.js      — LengthTool + HU sampling
    tools/ImplantPlanningTool.js    — implant cylinder scaffold
    panels/DentalToolsPanel.jsx     — React FDI grid + findings
scripts/ohif/
  register-packages.js             — adds file: deps to platform/app/package.json
  register-plugins.js              — updates pluginConfig.json (primary)
  compile-extensions.js            — pre-compiles JSX/TSX → ESM via @babel/core

Known Open Points (Runtime Verification Needed)

  • vtkImageReslice import path @kitware/vtk.js/Imaging/Core/ImageReslice — webpack OK, vtk.js runtime not yet tested with real CBCT data
  • vtkImageCPRMapper panoramic rendering — webpack OK, runtime binding to cornerstone3D viewport not yet verified
  • triggerAnnotationModified/triggerAnnotationCompleted API — these are now at annotation.triggerAnnotationModified in this @cornerstonejs/tools version (14 webpack warnings document this)
  • Hanging protocol 3-panel layout — webpack OK, OHIF layout engine runtime to verify

See docs/DENTAL-FEATURES-ROADMAP.md for full status table.

Test plan

  • 17 unit tests pass (npm test in extensions)
  • docker build -f Dockerfile.ohif -t ambientct/ohif:latest . — 0 errors
  • docker compose up -d and verify viewer loads at http://localhost:3000
  • Upload a CBCT DICOM series → verify dental mode activates
  • Draw arch spline → verify panoramic CPR renders
  • Click CPR viewport → verify cross-section updates

🤖 Generated with Claude Code

Ambientwork and others added 26 commits March 23, 2026 21:05
- Stats bar: "50+ practices", 0€ fees, 5min setup, 100% local
- Demo section: YouTube embed placeholder with play button + live demo placeholder
- Testimonials: 3 placeholder testimonials with star ratings
- FAQ accordion: 6 questions (FDA cert, server requirements, DICOM import,
  data security, device compatibility, support/commercial options)
- Newsletter: Brevo embed placeholder with functional fallback form
- Nav: added Demo + FAQ links
- OG URL updated to ct.ambientwork.ai

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docker-compose: read_only + tmpfs, no-new-privileges, cap_drop ALL,
  bind all ports to 127.0.0.1 (including DICOM port 4242)
- orthanc.json: disable Lua execution, REST filesystem writes; empty
  AllowedOrigins for same-origin-only CORS
- setup.sh: interactive custom password with min-16-chars + blocklist
  enforcement, password confirmation, CORS var in generated .env,
  security notes printed post-setup
- .env.example: CORS_ALLOWED_ORIGINS var + HTTP-in-production warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Research-backed analysis of OHIF/Cornerstone3D dental imaging capabilities:
- Documents what is already available (MPR, measurements, volume rendering)
- CPR/panoramic viewport: vtkImageCPRMapper available via CS3D PR #1689
  but no first-class implementation exists (Issue #2609, open Feb 2026)
- No public OHIF dental panoramic extension found — greenfield opportunity
- Phase 2: hanging protocol, nerve canal spline, bone thickness (days)
- Phase 3: panoramic CPR viewport (4-8h CC, first open-source impl)
- Phase 4: implant overlay, auto-segmentation, cephalometrics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… extension

Implements CS3D Issue #2609 — Curved Planar Reformation for dental CBCT.

## Extension: @ambientwork/ohif-extension-dental-cpr

- DentalArchSplineTool: AnnotationTool, user clicks control points on axial
  CBCT slice; double-click fires ARCH_SPLINE_COMPLETED event
- buildCenterline: Catmull-Rom spline → vtkPolyData with quaternion orientation
  array (Frenet-Serret frames, mat3→quat via Shepperd's method)
- DentalCPRViewport: custom OHIF viewport using vtkImageCPRMapper
  - setImageData() + setCenterlineData() (correct vtk.js API per docs)
  - useStraightenedMode() — classic panoramic equivalent
  - MIP slab slider (1–20 mm, live update)
  - Handles resize, loading states, error display
- cbctDentalHP: hanging protocol — CT study → 2-panel layout (axial + CPR)

## Mode: @ambientwork/ohif-mode-dental-cpr

- Routes to 'dentalCPR' path
- Creates 'dentalCPRToolGroup', registers DentalArchSplineTool with
  primary mouse button binding
- isValidMode: CT modality only

## Docker

- Dockerfile.ohif: multi-stage build (node:20 + OHIF v3.9.2 clone + yarn build
  + nginx:1.27); deterministic yarn@4.0.2; App.tsx patch fails loudly (exit 1)
- docker-compose.yml: viewer-custom service (commented), drop-in replacement
  for ohif/app when dental CPR is needed

## Technical notes

- vtkImageCPRMapper wiring follows PR #1689 fix (factory registration)
- Quaternion orientation (4 components) used for PointData array
- Camera at (0, 0, arcLength×2) with parallel projection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NerveCanalTool: add z-slice proximity guard — skip safety margin line
  when canal and implant annotations are on different slices (prevents
  misleading 3D distances in 2D clinical display)
- DentalArchSplineTool: reset isDrawing/currentAnnotationUID on
  onSetToolPassive() to prevent state leak when switching tools mid-draw
- DentalCPRViewport: add TODO comment for multi-instance window event scope
- buildCenterline: export catmullRomSpline() and mat3ToQuat() for testing;
  add Jest setup + 6 unit tests covering spline correctness and all 4
  Shepperd quaternion branches
- huSampling: guard estimateBoneThickness() against empty array (was NaN)
- tests: add 5 missing coverage cases (empty HU, all-above-threshold,
  getToothInfo null, 2 new buildCenterline tests)

Tests: 8 → 17 (+9 new), all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dockerfile.ohif (Schritt 1):
- Upgrade builder to node:22-slim
- Extract registration logic to scripts/ohif/register-packages.js
  and scripts/ohif/register-plugins.js — no more inline heredocs
- Two-path registration: pluginConfig.json (official OHIF v3 mechanism)
  + App.tsx anchor-patch as fallback
- Dedicated nginx config: config/nginx/ohif.conf (SPA routing + cache headers)

Cross-Section Viewport (Schritt 2):
- DentalCrossSectionViewport.tsx — vtkImageReslice cuts CBCT perpendicular
  to arch tangent at a user-selected position
- Direction cosines: N=output-x, B=output-y, T=slice-normal (arch tangent)
- 40×40 mm at 0.3 mm/px; bone window W=2000 L=400
- Fires/listens to ARCH_CROSS_SECTION_POSITION events

3-Panel Layout (Schritt 3):
- cbctDentalHP: 2×2 grid — axial (top-left), CPR (top-right),
  cross-section (bottom full-width)
- modes/dental-cpr-mode: adds cross-section viewport + dental-tools panel
- DentalCPRViewport: click handler maps canvas X → spline index →
  fires ARCH_CROSS_SECTION_POSITION with Frenet frame data

buildCenterline:
- Export buildCenterlinePoints() — returns pure-JS Frenet frames
  (no vtk.js dep) for use in click-handler event payload

docker-compose.yml (Schritt 4):
- viewer service now uses ambientct/ohif:latest (custom build, port 3000)
- viewer-stock: pre-built ohif/app:v3.9.2 on port 3001 for quick testing

Docker build (Schritt 5): Running in background → /tmp/docker-build.log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Build failures fixed in order:
1. SSL: add ca-certificates to apt-get (git clone failed)
2. register-packages.js: use process.cwd() instead of __dirname
   (scripts copied to /tmp, __dirname pointed to wrong path)
3. register-plugins.js: use process.cwd(); App.tsx Step B now
   non-fatal when pluginConfig.json registration succeeded
4. node-gyp: add python3 make g++ for native addon compilation
5. JSX transpilation: add compile-extensions.js pre-build step
   that uses @babel/core from OHIF's node_modules to transpile
   dental-cpr (TSX) and dental-tools (JSX) before webpack runs.
   OHIF's webpack babel-loader excluded workspace symlinks.
   modules:false preserves ESM import syntax so @cornerstonejs
   exports fields resolve correctly.

Result: webpack 5.94.0 compiled with 14 warnings, 0 errors.
Image: ambientct/ohif:latest — 194MB nginx serving OHIF dist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mark Docker build as ✅ verified (2026-03-24)
- webpack 5.94.0 compiled 0 errors, 14 warnings
- Image: ambientct/ohif:latest (194MB)
- Document build fixes: ca-certificates, process.cwd(), compile-extensions.js
- Mark DentalCPRViewport, DentalCrossSectionViewport as
  build-OK but runtime-unverified (needs real CBCT data test)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FROM node:22-bookworm AS builder (was node:22-slim)
- Clone target: /ohif (was /build/ohif)
- WORKDIR /ohif (was /build/ohif)
- FROM nginx:alpine (was nginx:1.27-alpine)
- Remove python3/make/g++ from apt-get (bookworm has them built-in)
- Remove corepack prepare — let corepack auto-detect yarn version from OHIF repo
- Remove generate-plugin-config optional step (not needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Ambientwork Ambientwork merged commit b793858 into main Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant