From f3804dde2ad41f89ead3c941b76066046784d451 Mon Sep 17 00:00:00 2001 From: onkar mali Date: Sat, 21 Mar 2026 23:31:20 +0530 Subject: [PATCH 1/5] Clinical Viewer ecg viewer, smart paint flatfoot analysis --- FLATFOOT_TOOLS_GUIDE.md | 466 + HACKATHON_CHANGES.md | 226 + PROJECT_GUIDE.md | 336 + TECHNICAL_DOCUMENTATION.md | 336 + extensions/default/src/ViewerLayout/index.tsx | 135 +- .../ecg-tools/ECG_VIEWER_DOCUMENTATION.md | 635 ++ extensions/ecg-tools/package.json | 12 + .../components/ClinicalViewersDropdown.tsx | 65 + .../components/EmbeddedViewerToggleButton.tsx | 47 + extensions/ecg-tools/src/index.tsx | 85 + .../ecg-tools/src/panels/PanelEcgViewer.tsx | 932 ++ .../ecg-tools/src/utils/ecgCalculations.ts | 189 + extensions/flatfoot/FLATFOOT_DOCUMENTATION.md | 531 + extensions/flatfoot/package.json | 11 + extensions/flatfoot/src/index.tsx | 52 + .../flatfoot/src/panels/PanelFlatfoot.tsx | 989 ++ .../src/utils/flatfootCalculations.ts | 99 + .../smart-paint/SMART_PAINT_DOCUMENTATION.md | 405 + extensions/smart-paint/package.json | 11 + extensions/smart-paint/src/index.tsx | 74 + .../src/panels/PanelSmartPaint.tsx | 382 + .../smart-paint/src/tools/SmartPaintTool.ts | 210 + modes/basic/src/index.tsx | 10 +- modes/basic/src/toolbarButtons.ts | 38 + modes/ecg/package.json | 12 + modes/ecg/src/id.ts | 1 + modes/ecg/src/index.tsx | 73 + modes/longitudinal/src/index.ts | 72 +- package.json | 4 +- platform/app/pluginConfig.json | 3 + .../app/src/components/SegmentLabelPanel.tsx | 154 + .../app/src/routes/EcgViewer/EcgViewer.tsx | 38 + platform/app/src/routes/EcgViewer/index.js | 1 + .../app/src/routes/Flatfoot/FlatfootRoute.tsx | 27 + platform/app/src/routes/Flatfoot/index.js | 1 + .../src/routes/SmartPaint/SmartPaintRoute.tsx | 27 + platform/app/src/routes/SmartPaint/index.js | 1 + .../src/routes/ViewerHub/ViewerHubRoute.tsx | 117 + platform/app/src/routes/ViewerHub/index.js | 1 + platform/app/src/routes/WorkList/WorkList.tsx | 45 +- platform/app/src/routes/index.tsx | 23 +- platform/i18n/src/index.js | 114 +- yarn.lock | 9235 ++++++++--------- 43 files changed, 11405 insertions(+), 4820 deletions(-) create mode 100644 FLATFOOT_TOOLS_GUIDE.md create mode 100644 HACKATHON_CHANGES.md create mode 100644 PROJECT_GUIDE.md create mode 100644 TECHNICAL_DOCUMENTATION.md create mode 100644 extensions/ecg-tools/ECG_VIEWER_DOCUMENTATION.md create mode 100644 extensions/ecg-tools/package.json create mode 100644 extensions/ecg-tools/src/components/ClinicalViewersDropdown.tsx create mode 100644 extensions/ecg-tools/src/components/EmbeddedViewerToggleButton.tsx create mode 100644 extensions/ecg-tools/src/index.tsx create mode 100644 extensions/ecg-tools/src/panels/PanelEcgViewer.tsx create mode 100644 extensions/ecg-tools/src/utils/ecgCalculations.ts create mode 100644 extensions/flatfoot/FLATFOOT_DOCUMENTATION.md create mode 100644 extensions/flatfoot/package.json create mode 100644 extensions/flatfoot/src/index.tsx create mode 100644 extensions/flatfoot/src/panels/PanelFlatfoot.tsx create mode 100644 extensions/flatfoot/src/utils/flatfootCalculations.ts create mode 100644 extensions/smart-paint/SMART_PAINT_DOCUMENTATION.md create mode 100644 extensions/smart-paint/package.json create mode 100644 extensions/smart-paint/src/index.tsx create mode 100644 extensions/smart-paint/src/panels/PanelSmartPaint.tsx create mode 100644 extensions/smart-paint/src/tools/SmartPaintTool.ts create mode 100644 modes/ecg/package.json create mode 100644 modes/ecg/src/id.ts create mode 100644 modes/ecg/src/index.tsx create mode 100644 platform/app/src/components/SegmentLabelPanel.tsx create mode 100644 platform/app/src/routes/EcgViewer/EcgViewer.tsx create mode 100644 platform/app/src/routes/EcgViewer/index.js create mode 100644 platform/app/src/routes/Flatfoot/FlatfootRoute.tsx create mode 100644 platform/app/src/routes/Flatfoot/index.js create mode 100644 platform/app/src/routes/SmartPaint/SmartPaintRoute.tsx create mode 100644 platform/app/src/routes/SmartPaint/index.js create mode 100644 platform/app/src/routes/ViewerHub/ViewerHubRoute.tsx create mode 100644 platform/app/src/routes/ViewerHub/index.js diff --git a/FLATFOOT_TOOLS_GUIDE.md b/FLATFOOT_TOOLS_GUIDE.md new file mode 100644 index 00000000000..90559bbb784 --- /dev/null +++ b/FLATFOOT_TOOLS_GUIDE.md @@ -0,0 +1,466 @@ +# Flatfoot Measurement — Tool Reference Guide + +## Overview + +The Flatfoot Measurement module analyzes foot X-rays (lateral view) to detect and classify +flat foot (Pes Planus), high arch (Pes Cavus), and normal arch conditions. +All measurements are performed by clicking on anatomical landmarks directly on the uploaded X-ray image. + +**All placed points are draggable** — after placing a measurement you can click and drag any dot +to reposition it. Angles and distances update live as you drag. + +--- + +## How to Start + +1. Open the app → click **Flatfoot Analysis** button on the WorkList +2. Click **Upload X-Ray** → select a lateral foot X-ray (PNG / JPG / BMP) +3. (Optional) Run **Calibrate** first for accurate mm measurements +4. Select a tool → click points on the image → results appear in the right panel +5. To move a point: hover over any dot (cursor changes to hand) → click-drag to new position + +--- + +## Tools Reference + +--- + +### 1. Cursor +**Button:** `Cursor` +**Color:** Blue + +**Purpose:** Default inactive mode. No measurements are placed. +Use this to inspect the image without accidentally adding points. + +**How to use:** +- Select Cursor → freely move mouse over the image +- No clicks register any measurement + +--- + +### 2. ⬡ Area (Polygon) +**Button:** `⬡ Area` +**Color:** Yellow + +**Purpose:** Measure the enclosed area of any region on the X-ray — e.g., the contact area of the foot, heel pad area, or any anatomical zone of interest. + +**Formula — Shoelace (Gauss Area) Formula:** +``` +Area = |Σ (xᵢ · yᵢ₊₁ − xᵢ₊₁ · yᵢ)| / 2 + +Then convert: + Area (mm²) = Area (px²) / (px_per_mm)² +``` + +**How to use:** +1. Select `⬡ Area` tool +2. Click anywhere on the image → numbered dot appears (●1) +3. Click again → dot ●2 appears, connected to ●1 with a line +4. Keep clicking → each dot connects to the previous one +5. After 3+ dots, the polygon auto-fills with semi-transparent color +6. Live area in mm² shows in the hint bar as you build the polygon +7. Click **✓ Finish & Save Area** to save the result + +**Reading the result:** +- `245.3 mm²` → area enclosed by your polygon in square millimetres + +**Clinical use:** Measure plantar contact area, heel pad, or any region of interest. + +--- + +### 3. Distance +**Button:** `Distance` +**Color:** Green + +**Purpose:** Measure straight-line distance between any two points on the image. + +**Formula — Euclidean Distance:** +``` +Distance (px) = √[(x₂ − x₁)² + (y₂ − y₁)²] + +Distance (mm) = Distance (px) / px_per_mm +``` + +**How to use:** +1. Select `Distance` +2. Click **Point 1** (start) +3. Click **Point 2** (end) +4. Result shows in mm in the right panel + +**Example:** Measure foot length, heel width, or any bone segment. + +--- + +### 4. Calcaneal Pitch +**Button:** `Calcaneal Pitch` +**Color:** Amber/Orange + +**Purpose:** Measures the angle of the calcaneus (heel bone) relative to the ground (horizontal). +Indicates the inclination of the heel — reduced angle = flat foot tendency. + +**Formula — Angle from Horizontal:** +``` +Δy = y₂ − y₁ +Δx = x₂ − x₁ + +Calcaneal Pitch (°) = atan2(Δy, Δx) × (180 / π) +``` + +**Normal Range:** 17° – 32° + +| Result | Interpretation | +|---|---| +| < 17° | Decreased pitch — possible Pes Planus (flat foot) | +| 17° – 32° | Normal calcaneal pitch | +| > 32° | Increased pitch — possible Pes Cavus (high arch) | + +**How to use:** +1. Select `Calcaneal Pitch` +2. Click **Point 1** → Posterior-inferior border of calcaneus (back bottom of heel bone) +3. Click **Point 2** → Anterior-inferior border of calcaneus (front bottom of heel bone) +4. Draw the line along the inferior surface of the calcaneus + +``` + Point 2 (anterior) + / + / ← calcaneal line + / + Point 1 (posterior) + ━━━━━━━━━━━━━━━━━━━━ (ground line / horizontal) +``` + +--- + +### 5. Clarke's Angle +**Button:** `Clarke's Angle` +**Color:** Purple + +**Purpose:** Measures the angle of the medial longitudinal arch. +Uses 3 points to describe how raised or collapsed the arch is. + +**Formula — Cosine Rule (angle at middle point):** +``` +Given 3 points: A (heel), B (arch apex), C (ball of foot) + +Vector BA = A − B +Vector BC = C − B + +cos(θ) = (BA · BC) / (|BA| × |BC|) + +Clarke's Angle (°) = acos(cos(θ)) × (180 / π) +``` + +**Normal Range:** ≥ 42° + +| Result | Interpretation | +|---|---| +| < 42° | Flat arch — Pes Planus | +| 42° – 54° | Normal arch | +| > 54° | High arch — Pes Cavus | + +**How to use:** +1. Select `Clarke's Angle` +2. Click **Point 1** → Heel (most posterior-inferior point of calcaneus) +3. Click **Point 2** → Arch Apex (highest point of medial longitudinal arch / navicular area) +4. Click **Point 3** → 1st Metatarsal Head (ball of foot) + +``` + ●2 (arch apex) + / \ + / \ + / \ + ●1 ──────────── ●3 + (heel) (ball) + ↑ angle measured at ●2 +``` + +--- + +### 6. Arch Index +**Button:** `Arch Index` +**Color:** Orange + +**Purpose:** Quantifies flat foot severity using foot contact area proportions. +Based on the **Cavanagh & Rodgers (1987)** method. + +**Formula — Arch Index (AI):** +``` +The foot is divided into 3 equal thirds (anterior, middle, posterior): + +AI = Mid-foot contact area / Total foot contact area + +Simplified from 2 click points: + Total foot length (px) = Distance(Point 1 → Point 2) + Mid-foot length (px) = Total / 3 (middle third) + +AI = Mid-foot px / Total px +``` + +**Classification (Cavanagh & Rodgers):** + +| Arch Index | Classification | +|---|---| +| < 0.21 | Pes Cavus (high arch) | +| 0.21 – 0.26 | Normal arch | +| > 0.26 | Pes Planus (flat foot) | + +**How to use:** +1. Select `Arch Index` +2. Click **Point 1** → Posterior edge of heel (back of heel) +3. Click **Point 2** → Tip of the longest toe (front of foot) +4. The system divides the foot into 3 equal thirds and calculates AI + +``` + |←── Total Foot Length ──────────────────→| + |← Posterior →|←── Mid-foot ──→|← Ant. →| + ●1 (heel) ●2 (toe) + + AI = middle third / total length +``` + +> **Note:** For clinical accuracy, the Arch Index should ideally be measured +> from a pressure plate footprint, not an X-ray. This tool gives a linear approximation. + +--- + +### 7. Meary's Angle (Talo-First Metatarsal Angle) +**Button:** `Meary's Angle` +**Color:** Pink + +**Purpose:** Measures the angle between the long axis of the talus and the long axis +of the first metatarsal. One of the most reliable indicators of flat foot on lateral X-ray. + +**Formula — Angle Between Two Lines:** +``` +Line 1: Talus axis → defined by Points 1 & 2 +Line 2: 1st Metatarsal axis → defined by Points 3 & 4 + +Vector V1 = P2 − P1 (talus direction) +Vector V2 = P4 − P3 (metatarsal direction) + +dot = V1.x·V2.x + V1.y·V2.y +|V1| = √(V1.x² + V1.y²) +|V2| = √(V2.x² + V2.y²) + +Meary's Angle (°) = acos(dot / (|V1| × |V2|)) × (180 / π) +``` + +**Normal Range:** < 4° + +| Result | Interpretation | +|---|---| +| < 4° | Normal alignment | +| 4° – 15° | Mild Pes Planus | +| 15° – 30° | Moderate Pes Planus | +| > 30° | Severe Pes Planus | + +**How to use:** +1. Select `Meary's Angle` +2. Click **Point 1** → Center of talus head (anterior) +3. Click **Point 2** → Center of talus body (posterior) +4. Click **Point 3** → Base of 1st metatarsal +5. Click **Point 4** → Head of 1st metatarsal + +``` + ●1────●2 ← Talus axis + \ + \ ← angle here + ●3────────●4 + (metatarsal axis) +``` + +--- + +### 8. △ Triangle +**Button:** `△ Triangle` +**Color:** Cyan + +**Purpose:** Drop 3 corner points to form a triangle. Calculates all 3 interior angles, +marks the midpoint of each side, and shows the enclosed area. Useful for measuring +any triangular bone or joint region. + +**Formula — Law of Cosines (all 3 angles):** +``` +Given 3 corners: A, B, C +Side lengths: + ab = |B − A|, bc = |C − B|, ca = |A − C| + +Angle at A = acos((ab² + ca² − bc²) / (2 · ab · ca)) +Angle at B = acos((ab² + bc² − ca²) / (2 · ab · bc)) +Angle at C = 180° − ∠A − ∠B + +Area (px²) = |( (B.x−A.x)·(C.y−A.y) ) − ( (C.x−A.x)·(B.y−A.y) )| / 2 +Area (mm²) = Area (px²) / (px_per_mm)² +``` + +**How to use:** +1. Select `△ Triangle` +2. Click **Point 1** (A) → first corner +3. Click **Point 2** (B) → second corner +4. Click **Point 3** (C) → third corner +5. Triangle draws automatically with: + - Angle labels at each corner (∠1, ∠2, ∠3) + - White circle markers at each side midpoint + - Area displayed at the centroid + +**Reading the result:** +- `∠1=45° ∠2=90° ∠3=45° | 312.4 mm²` +- All 3 angles sum to exactly 180° + +**Dragging:** All 3 corner points can be dragged — angles and area update live. + +``` + ●A + / \ + /∠A \ + / \ + ●B─────●C + ∠B ∠C + mid-points shown as ○ on each side +``` + +--- + +### 9. ⊿⊿ Split Triangle +**Button:** `⊿⊿ Split △` +**Color:** Orange + +**Purpose:** Draw a baseline (like Arch Index) and then drop an apex point. +A perpendicular is dropped from the apex to the baseline, splitting it into +two sub-triangles. All 6 angles (3 per triangle) are calculated. + +**Formula:** +``` +Points: A (line start), B (line end), C (apex) + +Foot of perpendicular D on line AB: + t = ((C − A) · (B − A)) / |B − A|² + D = A + t · (B − A) ← clamped to [0,1] + +Triangle 1: A, D, C → 3 angles via law of cosines +Triangle 2: D, B, C → 3 angles via law of cosines + +Area of each triangle = cross-product / 2 +``` + +**How to use:** +1. Select `⊿⊿ Split △` +2. Click **Point 1** (A) → start of baseline +3. Click **Point 2** (B) → end of baseline +4. Click **Point 3** (C) → apex above the line +5. Point D (foot of perpendicular) appears automatically on the baseline +6. Two filled triangles draw (orange = T1, purple = T2) +7. A dashed line shows the C→D perpendicular; right-angle mark appears at D + +**Reading the result:** +- `T1: ∠A=60° ∠D=90° ∠C=30° (145mm²) | T2: ∠D=90° ∠B=45° ∠C=45° (145mm²)` + +**Dragging:** All 4 points (A, B, C, D) can be dragged independently. +- Dragging **A or B** moves the baseline endpoints +- Dragging **C** moves the apex (D recomputes to stay on the A-B line... unless you also drag D) +- Dragging **D** slides the split point freely along (or off) the baseline + +``` + ●C (apex) + /|\ + / | \ + / | \ + / ∟ | \ + ●A───────●D────●B + T1 (orange) T2 (purple) +``` + +--- + +### 10. Calibrate +**Button:** `Calibrate` +**Color:** Yellow (when active) + +**Purpose:** Set the scale so measurements are accurate in real-world millimetres. +Without calibration, the tool uses a default estimate (96 DPI screen assumption). + +**Formula:** +``` +px_per_mm = pixel_distance_of_known_object / known_length_in_mm +``` + +**How to use:** +1. Find a known reference object in the X-ray — e.g., a ruler, implant, or standard marker +2. Click **Calibrate** +3. Enter the known real-world length (mm) in the input box +4. Click **Point 1** → one end of the known object +5. Click **Point 2** → other end of the known object +6. Calibration is saved — all subsequent measurements use this scale + +**Example:** If a 10mm marker spans 38 pixels → px/mm = 38/10 = 3.8 + +> Always calibrate before taking clinical measurements for accurate results. + +--- + +## Summary Table + +| Tool | Points | Formula | Normal Range | Key Use | +|---|---|---|---|---| +| Cursor | 0 | — | — | Inspect only | +| ⬡ Area | 3+ | Shoelace | — | Contact / region area (mm²) | +| Distance | 2 | Euclidean | — | Any length (mm) | +| Calcaneal Pitch | 2 | atan2 | 17°–32° | Heel bone inclination | +| Clarke's Angle | 3 | Cosine rule | ≥ 42° | Medial arch angle | +| Arch Index | 2 | AI = mid/total | 0.21–0.26 | Arch area ratio | +| Meary's Angle | 4 | Vector dot product | < 4° | Talo-metatarsal alignment | +| △ Triangle | 3 | Law of cosines + cross-product | — | Triangle angles + area | +| ⊿⊿ Split △ | 3 | Perpendicular foot + law of cosines | — | Split baseline into 2 triangles | +| Calibrate | 2 | px/mm ratio | — | Set real-world scale | + +--- + +## Dragging Points + +Every measurement point placed on the canvas is draggable: + +- **Hover** over any dot → cursor changes to a hand (↕) +- **Click and drag** → point moves, measurement updates live +- **Release** → final value saved + +This is especially useful for the Triangle and Split Triangle tools where small adjustments +to corner positions significantly affect the calculated angles. + +--- + +## Recommended Workflow + +``` +1. Upload lateral foot X-ray + ↓ +2. Calibrate (using a known marker on the X-ray) + ↓ +3. Calcaneal Pitch ← quick overall arch check + ↓ +4. Meary's Angle ← most clinically reliable flat foot indicator + ↓ +5. Clarke's Angle ← medial arch assessment + ↓ +6. Arch Index ← load distribution estimate + ↓ +7. Triangle / Split △ ← detailed angular analysis of any bone region + ↓ +8. Distance / Area ← any additional measurements needed + ↓ +9. Review results panel → compare against Normal Ranges +``` + +--- + +## Classification Summary + +| Condition | Calcaneal Pitch | Clarke's | Arch Index | Meary's | +|---|---|---|---|---| +| Pes Cavus (high arch) | > 32° | > 54° | < 0.21 | < 4° | +| Normal | 17°–32° | 42°–54° | 0.21–0.26 | < 4° | +| Pes Planus (flat foot) | < 17° | < 42° | > 0.26 | > 4° | + +--- + +*Reference: Cavanagh PR, Rodgers MM. The arch index: a useful measure from footprints. +J Biomech. 1987;20(5):547-51.* diff --git a/HACKATHON_CHANGES.md b/HACKATHON_CHANGES.md new file mode 100644 index 00000000000..814f9a7364e --- /dev/null +++ b/HACKATHON_CHANGES.md @@ -0,0 +1,226 @@ +# Hackathon Changes Documentation + +## Overview + +Three clinical modules were added to the OHIF Viewer as standalone extensions, plus a runtime bug fix for the i18n library. +Subsequently, several enhancements were made to all three modules based on user feedback. + +--- + +## 1. Bug Fix — i18n Runtime Crash + +**File:** `platform/i18n/src/index.js` + +**Problem:** App crashed on load with: +``` +TypeError: _this.services.languageUtils.isWhitelisted is not a function +``` + +**Root Cause:** Version mismatch — `i18next` (newer) removed `isWhitelisted()`, but `i18next-browser-languagedetector` (older) still calls it during language detection. + +**Fix:** Monkey-patched `LanguageDetector.prototype.detect` at module-load time so the missing method is injected right before it is called. + +--- + +## 2. New Extensions + +### 2.1 ECG Tools — `extensions/ecg-tools/` + +``` +extensions/ecg-tools/ +├── package.json ← @custom/extension-ecg-tools +└── src/ + ├── index.tsx ← OHIF extension entry point + ├── utils/ + │ └── ecgCalculations.ts ← Clinical math + └── panels/ + └── PanelEcgViewer.tsx ← Main UI +``` + +**Clinical Calculations (`ecgCalculations.ts`):** + +| Function | Formula | Description | +|---|---|---| +| `bazettQTc(qtMs, rrMs)` | `QTc = QT / √RR` (seconds) | QT interval corrected for heart rate | +| `heartRate(rrMs)` | `HR = 60000 / RR` | Beats per minute from RR interval | +| `qrsAxis(leadI, aVF)` | `atan2(aVF, leadI) × 180/π` | Electrical axis in degrees | +| `rrVariance(intervals[])` | std deviation formula | RR interval variability | +| `standardCalibration(px/mm)` | 25 mm/s, 10 mm/mV | Convert pixels to clinical units | + +**QRS Axis Interpretation:** +- `-30° to +90°` → Normal +- `-30° to -90°` → Left Axis Deviation (LAD) +- `+90° to +180°` → Right Axis Deviation (RAD) +- `> ±180°` → Extreme Axis Deviation + +**Tools available in the UI:** +- `CURSOR` — Pan/inspect +- `CALIBRATE_H` / `CALIBRATE_V` — Set pixel-to-mm scale +- `TIME` — Measure time intervals (ms) +- `AMPLITUDE` — Measure voltage (mV) +- `RR_INTERVAL` — Measure R-to-R distance → HR +- `QT_INTERVAL` — Measure QT → compute QTc +- `QRS_AXIS` — Enter Lead I & aVF amplitudes +- `COMPARE` — Side-by-side study comparison + +**ECG Improvements (post-initial):** +- Measurement annotation font size reduced to ~12px for cleaner readout +- Image rendering quality improved (`imageSmoothingQuality = 'high'`) for sharper ECG waveforms +- Canvas max size raised (1800×1200) to support higher-resolution ECG images + +--- + +### 2.2 Smart Paint — `extensions/smart-paint/` + +``` +extensions/smart-paint/ +├── package.json ← @custom/extension-smart-paint +└── src/ + ├── index.tsx ← OHIF extension entry point + ├── tools/ + │ └── SmartPaintTool.ts ← Paint engine + └── panels/ + └── PanelSmartPaint.tsx ← Interactive UI +``` + +**Paint Engine (`SmartPaintTool.ts`):** + +| Function | Description | +|---|---| +| `paintBrush(state, cx, cy, radius, sensitivity, pixelData, erase)` | Paint or erase pixels in a circle; optional intensity-based region grow | +| `commitHistory(state)` | Save current mask to undo stack (max 50 steps) | +| `undo(state)` | Restore previous mask | +| `redo(state)` | Re-apply undone mask | +| `clearMask(key)` | Reset all painted pixels | +| `maskToContour(state)` | Marching-squares algorithm → array of `[x,y]` boundary points | +| `renderMaskOverlay(ctx, state, color)` | Draw mask as semi-transparent color on canvas | +| `countPaintedPixels(state)` | Count total painted pixels → used for area display | + +**UI Features:** +- Upload any image file +- Brush tool with adjustable radius and sensitivity +- Custom SVG cursor that scales with brush size +- Erase mode toggle +- 2D / 3D mode toggle +- Undo / Redo buttons +- "Extract Contour" button → traces mask boundary +- **Painted area display** — shown as `px²` below the canvas and in status bar (live, updates on every stroke) +- **Panel icon** changed to segmentation icon (`tab-segmentation`) + +--- + +### 2.3 Flatfoot Measurement — `extensions/flatfoot/` + +``` +extensions/flatfoot/ +├── package.json ← @custom/extension-flatfoot +└── src/ + ├── index.tsx ← OHIF extension entry point + ├── utils/ + │ └── flatfootCalculations.ts ← Clinical foot math + └── panels/ + └── PanelFlatfoot.tsx ← Measurement UI +``` + +**Measurement Tools:** +- `DISTANCE` — Ruler between two points +- `CALCANEAL_PITCH` — 2-point angle from horizontal +- `CLARKE_ANGLE` — 3-point angle at arch apex +- `ARCH_INDEX` — 2-point span (total foot length + mid-foot) +- `MEARYS_ANGLE` — 4 points defining talus and first metatarsal axes +- `TRIANGLE` — 3 corner points → all 3 angles + side midpoints + area +- `TRIANGLE_SPLIT` — Baseline (2 pts) + apex → perpendicular creates 2 sub-triangles with all 6 angles + +**Flatfoot Improvements (post-initial):** +- **All measurement points are now draggable** — click and drag any placed dot to reposition it; values recalculate live +- **Triangle tool added** — 3-corner measurement with law-of-cosines angles, midpoints, area +- **Split Triangle tool added** — Arch-Index-style baseline + apex, perpendicular drop creates 2 triangles; D point (foot of perpendicular) is also independently draggable + +--- + +## 3. New Routes (Standalone Pages) + +| URL | Component | Description | +|---|---|---| +| `/ecg-viewer` | `EcgViewer` | Full ECG analysis page | +| `/smart-paint` | `SmartPaint` | Smart Paint ROI page | +| `/flatfoot` | `Flatfoot` | Flatfoot measurement page | + +**Files changed:** +- `platform/app/src/routes/index.tsx` — Added 3 route entries +- `platform/app/src/routes/EcgViewer/EcgViewer.tsx` — Lazy-loads extension panel +- `platform/app/src/routes/SmartPaint/SmartPaintRoute.tsx` — New file +- `platform/app/src/routes/Flatfoot/FlatfootRoute.tsx` — New file + +--- + +## 4. WorkList Navigation Buttons + +**File:** `platform/app/src/routes/WorkList/WorkList.tsx` + +Three buttons added to the toolbar area above the study list: + +| Button | Color | Navigates To | +|---|---|---| +| ECG Viewer | Green | `/ecg-viewer` | +| Smart Paint ROI | Cyan | `/smart-paint` | +| Flatfoot Analysis | Amber | `/flatfoot` | + +--- + +## 5. OHIF Plugin Registration + +**File:** `platform/app/src/pluginImports.js` + +Added dynamic imports for all new extensions and mode: + +```javascript +extensions.push("@custom/extension-ecg-tools"); +extensions.push("@custom/extension-smart-paint"); +extensions.push("@custom/extension-flatfoot"); +modes.push("@custom/mode-ecg"); +``` + +--- + +## 6. ECG Mode + +**Files:** `modes/ecg/src/index.tsx`, `modes/ecg/src/id.ts` + +- Mode ID: `@custom/mode-ecg` +- Route: `viewer/ecg` +- Panels: ECG Tools, Smart Paint, Flatfoot in right-side panel layout + +--- + +## 7. Quick Start Checklist + +``` +□ cd /home/artem/Desktop/project/frontend/Viewers +□ yarn install (install any new deps) +□ yarn dev (start dev server) +□ Open http://localhost:3000 +□ Check browser console — no red errors +□ See 3 buttons on WorkList page +□ Test each clinical tool with a sample image +``` + +--- + +## 8. File Change Summary + +| File | Type | What Changed | +|---|---|---| +| `platform/i18n/src/index.js` | Fix | Monkey-patch for `isWhitelisted` crash | +| `platform/app/src/pluginImports.js` | Feature | Register 3 extensions + 1 mode | +| `platform/app/src/routes/index.tsx` | Feature | Add 3 new routes | +| `platform/app/src/routes/WorkList/WorkList.tsx` | Feature | Add 3 toolbar nav buttons | +| `extensions/ecg-tools/src/panels/PanelEcgViewer.tsx` | Update | Smaller font, better image quality, larger canvas | +| `extensions/smart-paint/src/index.tsx` | Update | Icon changed to `tab-segmentation` | +| `extensions/smart-paint/src/tools/SmartPaintTool.ts` | Update | Added `countPaintedPixels()` | +| `extensions/smart-paint/src/panels/PanelSmartPaint.tsx` | Update | Painted area display below canvas | +| `extensions/flatfoot/src/panels/PanelFlatfoot.tsx` | Update | Triangle tool, Split Triangle tool, drag for all points | +| `extensions/ecg-tools/**` | New | Full ECG Tools extension | +| `extensions/smart-paint/**` | New | Full Smart Paint extension | +| `extensions/flatfoot/**` | New | Full Flatfoot extension | +| `modes/ecg/**` | New | OHIF ECG mode | diff --git a/PROJECT_GUIDE.md b/PROJECT_GUIDE.md new file mode 100644 index 00000000000..af9de9ae42b --- /dev/null +++ b/PROJECT_GUIDE.md @@ -0,0 +1,336 @@ +# Clinical Viewer — Complete Project Guide + +This is the full guide for the three clinical tools we built on top of the OHIF medical viewer. +Written in plain language so anyone on the team can understand what was built, how it works, and how to use it. + +--- + +## What Did We Build? + +We added three new clinical analysis tools to the existing OHIF medical image viewer: + +1. **ECG Viewer** — analyze heart ECG strips, measure QT intervals, calculate heart rate +2. **Smart Paint** — paint/highlight regions on any medical image, measure the painted area +3. **Flatfoot Analysis** — measure foot arch angles on X-rays to detect flat foot or high arch + +All three tools open as their own pages inside the app. +You reach them from the main patient list by clicking the colored buttons at the top. + +**No new software libraries were installed.** Everything was built using tools already in the project (React, TypeScript, Tailwind CSS, and the browser's built-in Canvas drawing). + +--- + +## How to Run the App + +``` +1. Open a terminal in the project folder: + cd /home/artem/Desktop/project/frontend/Viewers + +2. Install dependencies (only needed first time): + yarn install + +3. Start the development server: + yarn dev + +4. Open your browser: + http://localhost:3000 +``` + +If you see a patient/study list on screen, everything is working correctly. + +--- + +## The Three Tools + +--- + +### 1. ECG Viewer + +**What it's for:** Analyzing ECG (electrocardiogram) heart tracing images. +You upload a photo or screenshot of an ECG strip, then click on it to measure things like how long the QT interval is, what the heart rate is, and what the electrical axis of the heart is. + +**How to open it:** Click the green **ECG Viewer** button on the patient list page. + +**What you can measure:** + +| What | How many clicks | What you get | +|---|---|---| +| Time interval | 2 clicks | Duration in milliseconds | +| Voltage (amplitude) | 2 clicks | Height in millivolts | +| R-to-R interval | 2 clicks | Heart rate in BPM | +| QT interval + QTc | 2 clicks | QT duration + Bazett-corrected QTc | +| QRS axis | Enter 2 numbers | Heart electrical axis in degrees | +| Side-by-side compare | Upload 2 images | Two ECGs shown together | + +**Before measuring:** Use the Calibrate tools first. Calibrate H sets how many pixels = 1 millisecond. Calibrate V sets how many pixels = 1 millivolt. Without calibration the measurements will be off. + +**Normal values to know:** +- QTc: normal is under 440ms for men, under 460ms for women +- Heart rate: normal resting is 60–100 BPM +- QRS axis: normal is between -30° and +90° + +**Recent improvements:** +- Measurement labels are now smaller and less cluttered (font reduced to ~12px) +- ECG images display sharper and clearer (high-quality image smoothing enabled) +- Supports higher-resolution images (canvas now up to 1800×1200 pixels) + +--- + +### 2. Smart Paint + +**What it's for:** Painting / highlighting a region on any medical image. +Imagine using a digital brush to color over a specific area — the tool then tells you exactly how big that painted area is in pixels. + +**How to open it:** Click the cyan **Smart Paint ROI** button on the patient list page. + +**How to use it:** +1. Click **Upload Image** — select any PNG, JPG, or BMP medical image +2. Choose **Paint** or **Erase** mode +3. Adjust **Brush size** (how big your brush is) and **Sensitivity** (how selective the brush is — higher sensitivity paints only similar-looking pixels) +4. Click and drag over the image to paint +5. The **painted area** shows automatically below the canvas +6. Press **Undo** / **Redo** to fix mistakes +7. Press **Extract Contour** to trace the outline of the painted region + +**Area measurement:** +As you paint, a badge appears below the image showing: +``` +Painted Area: 12,345 px² +``` +This updates live with every brush stroke. + +**Segment colors:** +On the right side panel you can create named segments (like "Region A", "Region B") each with its own color. Selecting a segment changes the brush color. This is useful when you need to mark multiple different regions on the same image. + +**Recent improvements:** +- Live area display added — shows painted pixel count below the canvas and in the status bar +- Panel icon changed to a segmentation icon to better represent what the tool does + +--- + +### 3. Flatfoot Analysis + +**What it's for:** Measuring the foot arch from a lateral (side-view) X-ray. +Doctors use this to determine if a patient has flat feet (Pes Planus), a normal arch, or a high arch (Pes Cavus). + +**How to open it:** Click the amber **Flatfoot Analysis** button on the patient list page. + +**Important:** Before measuring, click **Calibrate** and mark a known distance on the X-ray (like a ruler or implant). This tells the tool how many pixels equal 1 millimeter, making all measurements accurate. + +**All available tools:** + +--- + +#### Cursor +Just for looking at the image. No measurements are placed when this is active. + +--- + +#### Distance +Click 2 points → get the straight-line distance in millimeters. +Use this to measure foot length, bone lengths, or any segment. + +--- + +#### Calcaneal Pitch +Click 2 points along the bottom of the heel bone → get the heel angle. + +| Angle | Meaning | +|---|---| +| Less than 17° | Possibly flat foot | +| 17° to 32° | Normal | +| More than 32° | Possibly high arch | + +--- + +#### Clarke's Angle +Click 3 points: heel → arch top → ball of foot → get the arch angle. + +| Angle | Meaning | +|---|---| +| Less than 42° | Flat arch | +| 42° to 54° | Normal | +| More than 54° | High arch | + +--- + +#### Arch Index +Click 2 points: back of heel → tip of longest toe → get the Arch Index ratio. + +| Index | Meaning | +|---|---| +| Less than 0.21 | High arch | +| 0.21 to 0.26 | Normal | +| More than 0.26 | Flat foot | + +--- + +#### Meary's Angle +Click 4 points: 2 along the talus bone → 2 along the first metatarsal → get the angle between them. +This is one of the most reliable measurements for diagnosing flat foot. + +| Angle | Meaning | +|---|---| +| Less than 4° | Normal alignment | +| 4° to 15° | Mild flat foot | +| 15° to 30° | Moderate flat foot | +| More than 30° | Severe flat foot | + +--- + +#### Triangle (NEW) +Click 3 corner points to form a triangle. +The tool automatically calculates: +- All 3 interior angles (they always add up to 180°) +- A midpoint marker on each side of the triangle +- The area of the triangle in mm² + +This is useful for measuring any triangular bone region or joint space. + +After placing the 3 corners you can **drag any point** to fine-tune the triangle — all angles and area update instantly. + +Example result: `∠1=45° ∠2=90° ∠3=45° | 312.4 mm²` + +--- + +#### Split Triangle (NEW) +This works like a combination of Arch Index (a baseline) and a triangle. + +1. Click **Point 1** → start of your baseline +2. Click **Point 2** → end of your baseline +3. Click **Point 3** → the apex (the top point above the line) + +The tool drops a perpendicular line from the apex down to the baseline. This creates two smaller triangles side by side. All 6 angles (3 per triangle) are shown. + +A right-angle symbol (∟) appears where the perpendicular meets the baseline. + +**Why this is useful:** You can see exactly how a triangular region is divided by a vertical reference line — useful for analyzing how weight or force is distributed across a bone region. + +After placing the 3 points, **all 4 dots are draggable**: +- Drag A or B to move the baseline +- Drag C to move the apex +- Drag D (the perpendicular foot) to shift the split point independently + +Example result: +`T1: ∠A=60° ∠D=90° ∠C=30° (145mm²) | T2: ∠D=90° ∠B=45° ∠C=45° (145mm²)` + +--- + +#### Dragging Points (ALL tools) + +Every dot placed on the canvas — for any tool — can be dragged after placing it. + +- **Hover** over a dot → your cursor changes to a hand symbol +- **Click and hold** → drag to the new position +- **Release** → the measurement recalculates instantly + +This means you don't have to redo a measurement if you placed a point slightly off. Just drag it to the right spot. + +--- + +### Summary of Flatfoot Normal Ranges + +| Measurement | Flat Foot | Normal | High Arch | +|---|---|---|---| +| Calcaneal Pitch | < 17° | 17°–32° | > 32° | +| Clarke's Angle | < 42° | 42°–54° | > 54° | +| Arch Index | > 0.26 | 0.21–0.26 | < 0.21 | +| Meary's Angle | > 4° | < 4° | < 4° | + +--- + +## How Each Tool Page is Structured + +Each of the three tools opens as its own page with: +- A **Back to Worklist** button at the top left to return to the patient list +- A **toolbar** across the top with all the tool buttons +- A **canvas area** in the middle where you work on the image +- A **results panel** on the right (for ECG and Flatfoot) showing all measurements + +--- + +## How the App Was Extended (For Developers) + +### The Three New Extensions + +Each tool is built as a separate "extension" — a self-contained module that plugs into the OHIF viewer. + +``` +extensions/ecg-tools/ ← ECG Viewer extension +extensions/smart-paint/ ← Smart Paint extension +extensions/flatfoot/ ← Flatfoot Analysis extension +``` + +Each extension has: +- A panel component (the main UI you see) +- A utility/tool file (the math and logic) +- An `index.tsx` that registers the extension with OHIF + +### How Pages Are Wired Up + +Three new pages (routes) were added to the app: + +| Web address | What it shows | +|---|---| +| `/ecg-viewer` | ECG Viewer page | +| `/smart-paint` | Smart Paint page | +| `/flatfoot` | Flatfoot Analysis page | + +### How Measurements Work on Canvas + +All three tools draw on an HTML Canvas element. The key technical point: + +When a canvas is displayed smaller than its actual resolution (because of CSS), click positions need to be converted. If you click at CSS position (100, 50) but the canvas is twice as big internally, the actual canvas position is (200, 100). All three tools apply this correction. + +### How Smart Paint's Brush Works + +The brush stores a "mask" — a grid of 1s and 0s the same size as the image. When you paint, pixels within the brush radius get set to 1. When you erase, they go back to 0. The `countPaintedPixels` function simply counts all the 1s in this grid to give you the area. + +The tool keeps a history of up to 50 mask snapshots so Undo and Redo work reliably. + +### How the Triangle Math Works + +**Regular Triangle:** Uses the Law of Cosines to find each angle. +Given sides a, b, c opposite to corners A, B, C: +- Angle at A = arccos((b² + c² − a²) / (2bc)) +- Repeat for B and C +- Area = half the cross-product of two edge vectors + +**Split Triangle:** Finds the point D on line AB that is closest to C (the foot of the perpendicular). This is a standard dot-product projection: move along AB by exactly the amount that brings you directly below C. +- D = A + t × (B − A), where t = dot(C−A, B−A) / dot(B−A, B−A) +- D is then stored as a fourth point so it can be dragged freely + +### How Dragging Works + +Each measurement stores its points in a list. When the user holds the mouse button near a point (within 12 pixels), the app records which measurement and which point index is being dragged. As the mouse moves, that point's position updates and the measurement value recalculates. When the mouse is released, dragging stops. + +A separate flag (`hasDraggedRef`) prevents the app from accidentally placing a new point when the user releases after a drag. + +--- + +## Bug That Was Fixed at the Start + +When the app first ran, it crashed with this error: + +``` +TypeError: isWhitelisted is not a function +``` + +This happened because two language-related libraries were out of sync — one had removed a function the other still expected. The fix was to inject that missing function back in, right before it was needed. This is called a "monkey patch" — a temporary bridge between two incompatible versions. + +--- + +## Quick Reference — What Goes Where + +| I want to... | Open this page | +|---|---| +| Measure QT interval / heart rate | ECG Viewer (`/ecg-viewer`) | +| Paint a region and measure its size | Smart Paint (`/smart-paint`) | +| Check for flat foot on an X-ray | Flatfoot Analysis (`/flatfoot`) | +| Measure a triangle of bone | Flatfoot → Triangle tool | +| Split a region with a perpendicular | Flatfoot → Split Triangle tool | +| Move a measurement point I placed | Hover over it and drag | + +--- + +*Built on OHIF Viewer v3 · Branch: release/3.12 · No new npm packages required* diff --git a/TECHNICAL_DOCUMENTATION.md b/TECHNICAL_DOCUMENTATION.md new file mode 100644 index 00000000000..bbd9e8bacaa --- /dev/null +++ b/TECHNICAL_DOCUMENTATION.md @@ -0,0 +1,336 @@ +# Technical Documentation — Custom Clinical Extensions + +> **Project:** OHIF Viewer v3 (monorepo) +> **Branch:** `release/3.12` +> **Last updated:** 2026-03-21 + +--- + +## 1. Libraries & Technologies Used + +### No New npm Packages Were Installed + +All 3 extensions were built **exclusively with libraries already present** in the OHIF monorepo. +No `npm install` or `yarn add` was run. + +| Technology | Version (from existing repo) | Used For | +|---|---|---| +| **React** | ^18 | All UI components | +| **TypeScript** | ^5 | All `.tsx` / `.ts` files | +| **Tailwind CSS** | ^3 | All styling (utility classes) | +| **React Router v6** | `useNavigate`, `Link` | Route navigation between pages | +| **HTML5 Canvas 2D API** | Browser built-in | ECG waveform rendering, flatfoot measurement overlays, Smart Paint mask | +| **`@ohif/ui`** | (workspace) | `Button`, `ButtonEnums`, `StudyListExpandedRow` etc. | +| **`@ohif/ui-next`** | (workspace) | `Header`, `Icons`, `ScrollArea`, `Tooltip`, `Onboarding` | +| **`@ohif/core`** | (workspace) | Peer dependency declared in extension package.json | +| **React.lazy + Suspense** | React 18 built-in | Lazy-loading each extension panel into its route | + +--- + +## 2. Folder Structure — New Files Created + +``` +Viewers/ +├── extensions/ +│ ├── ecg-tools/ ← NEW extension +│ │ ├── package.json +│ │ └── src/ +│ │ ├── index.tsx +│ │ ├── panels/ +│ │ │ └── PanelEcgViewer.tsx ← Main ECG panel +│ │ └── utils/ +│ │ └── ecgCalculations.ts ← ECG math functions +│ │ +│ ├── smart-paint/ ← NEW extension +│ │ ├── package.json +│ │ └── src/ +│ │ ├── index.tsx +│ │ ├── panels/ +│ │ │ └── PanelSmartPaint.tsx ← Smart Paint panel +│ │ └── tools/ +│ │ └── SmartPaintTool.ts ← Brush engine + mask store +│ │ +│ └── flatfoot/ ← NEW extension +│ ├── package.json +│ └── src/ +│ ├── index.tsx +│ ├── panels/ +│ │ └── PanelFlatfoot.tsx ← Flatfoot measurement panel +│ └── utils/ +│ └── flatfootCalculations.ts ← Clinical math functions +│ +├── platform/app/src/routes/ +│ ├── EcgViewer/ ← NEW route folder +│ │ ├── index.js ← Re-export +│ │ └── EcgViewer.tsx ← Route wrapper + Back button +│ │ +│ ├── SmartPaint/ ← NEW route folder +│ │ ├── index.js +│ │ └── SmartPaintRoute.tsx ← Route wrapper + Back button +│ │ +│ └── Flatfoot/ ← NEW route folder +│ ├── index.js +│ └── FlatfootRoute.tsx ← Route wrapper + Back button +│ +├── HACKATHON_CHANGES.md +├── FLATFOOT_TOOLS_GUIDE.md +└── TECHNICAL_DOCUMENTATION.md ← This file +``` + +--- + +## 3. Existing Files Modified + +| File | What Changed | +|---|---| +| `platform/app/src/routes/index.tsx` | Imported 3 new route components; added 3 entries to `bakedInRoutes` array | +| `platform/app/src/routes/WorkList/WorkList.tsx` | Added 3 clinical tool buttons above the study list | +| `platform/i18n/src/index.js` | Bug fix: monkey-patch for missing `isWhitelisted` method | +| `extensions/ecg-tools/src/panels/PanelEcgViewer.tsx` | Smaller annotation font, higher image quality, larger canvas cap | +| `extensions/smart-paint/src/index.tsx` | Panel icon changed from `tool-freehand-roi` to `tab-segmentation` | +| `extensions/smart-paint/src/tools/SmartPaintTool.ts` | Added `countPaintedPixels()` export | +| `extensions/smart-paint/src/panels/PanelSmartPaint.tsx` | Imports `countPaintedPixels`; shows live area in px² below canvas | +| `extensions/flatfoot/src/panels/PanelFlatfoot.tsx` | Added Triangle tool, Split Triangle tool, drag-to-reposition for all points | + +--- + +## 4. Extension Detail + +--- + +### 4.1 ECG Viewer (`extensions/ecg-tools/`) + +**Route:** `/ecg-viewer` +**Panel file:** `extensions/ecg-tools/src/panels/PanelEcgViewer.tsx` +**Math file:** `extensions/ecg-tools/src/utils/ecgCalculations.ts` + +#### What it does +Full-featured ECG analysis tool that loads ECG strip images (PNG/JPG) and lets the user place measurement points directly on the canvas. + +#### Tools available +| Tool | Color | Formula | Output | +|---|---|---|---| +| Cursor | Blue | — | Inspect only | +| Calibrate H | Amber | `px/ms = pixel_dist / known_ms` | Sets time scale | +| Calibrate V | Green | `px/mV = pixel_dist / known_mV` | Sets amplitude scale | +| Time | Blue | Euclidean distance → ms | Duration in ms | +| Amplitude | Purple | Euclidean distance → mV | Voltage in mV | +| RR Interval | Green | `HR = 60000 / RR_ms` | Heart rate (BPM) | +| QT Interval | Red | distance → ms | QT duration | +| QTc | Orange | Bazett: `QTc = QT / √(RR/1000)` | Corrected QT | +| QRS Axis | Pink | `atan2(aVF_amp, LeadI_amp)` | Axis in degrees | +| Compare | Blue | — | Side-by-side two ECGs | + +#### Post-initial improvements +- **Font size:** annotation labels reduced from `14 * √zoom` to `11 * √zoom` (~12px at zoom=1) +- **Image quality:** `ctx.imageSmoothingQuality = 'high'` added before `drawImage` for sharper waveforms +- **Canvas max size:** raised from `MAX_W=1100, MAX_H=700` to `MAX_W=1800, MAX_H=1200` + +#### Key technical decisions +- **Canvas coordinate scaling:** `getBoundingClientRect()` returns CSS display size; click coords multiplied by `canvas.width / rect.width` to get actual pixel position. +- State managed with `useReducer` for complex tool/measurement state. + +--- + +### 4.2 Smart Paint (`extensions/smart-paint/`) + +**Route:** `/smart-paint` +**Panel file:** `extensions/smart-paint/src/panels/PanelSmartPaint.tsx` +**Engine file:** `extensions/smart-paint/src/tools/SmartPaintTool.ts` + +#### What it does +Freehand brush ROI drawing tool. User paints a mask over an uploaded image. Supports undo/redo, sensitivity control, erase mode, contour extraction, and live area measurement. + +#### Architecture +``` +PanelSmartPaint (React) + ├── canvasRef ← displays the loaded image + ├── overlayRef ← transparent canvas on top for mask/contour + └── SmartPaintTool ← stateful engine (singleton per imageKey) + ├── paintBrush() ← fills pixels within brush radius + ├── commitHistory() ← saves snapshot to undo stack + ├── undo() / redo() ← restores from history stack + ├── clearMask() ← resets all painted pixels + ├── renderMaskOverlay() ← draws mask pixels onto overlay canvas + ├── maskToContour() ← traces boundary of painted region + └── countPaintedPixels() ← counts 1-bits in mask for area display +``` + +#### Area Measurement +After every paint stroke, undo, redo, or clear, `countPaintedPixels(state)` is called inside `renderOverlay`. The pixel count is stored in `paintedPx` state and shown: +- **Below the canvas:** a cyan badge `Painted Area: 12,345 px²` with a color swatch +- **In the status bar:** appended as `Area: 12,345 px²` in cyan + +#### Icon +Changed from `tool-freehand-roi` to `tab-segmentation` in `extensions/smart-paint/src/index.tsx`. + +--- + +### 4.3 Flatfoot Measurement (`extensions/flatfoot/`) + +**Route:** `/flatfoot` +**Panel file:** `extensions/flatfoot/src/panels/PanelFlatfoot.tsx` +**Math file:** `extensions/flatfoot/src/utils/flatfootCalculations.ts` + +#### What it does +Clinical foot X-ray measurement tool for diagnosing flat foot (Pes Planus), high arch (Pes Cavus), and normal arch conditions. + +#### Tools & Formulas +| Tool | Points | Formula | Normal Range | +|---|---|---|---| +| ⬡ Area Polygon | 3+ | Shoelace: `A = |Σ(xᵢyᵢ₊₁ − xᵢ₊₁yᵢ)| / 2` | — | +| Distance | 2 | Euclidean: `√((x₂−x₁)² + (y₂−y₁)²)` | — | +| Calcaneal Pitch | 2 | `atan2(Δy, Δx) × 180/π` | 17°–32° | +| Clarke's Angle | 3 | Cosine rule at middle point | ≥ 42° | +| Arch Index | 2 | `AI = mid_third / total_length` | 0.21–0.26 | +| Meary's Angle | 4 | `acos(V1·V2 / (|V1||V2|))` | < 4° | +| Triangle | 3 | Law of cosines (all 3 angles) + cross-product area | — | +| Split Triangle | 3+D | Perpendicular foot projection + law of cosines | — | + +#### Triangle Tool (new) +``` +Points: A, B, C (3 corners) +ab = |B−A|, bc = |C−B|, ca = |A−C| +∠A = acos((ab²+ca²−bc²)/(2·ab·ca)) +∠B = acos((ab²+bc²−ca²)/(2·ab·bc)) +∠C = 180 − ∠A − ∠B +area = |cross(B−A, C−A)| / 2 +``` +Draws: filled triangle, midpoint markers on each side, angle labels pushed outward from centroid, area at centroid. + +#### Split Triangle Tool (new) +``` +Points: A (line start), B (line end), C (apex) +t = ((C−A)·(B−A)) / |B−A|² ← clamped [0,1] +D = A + t·(B−A) ← foot of perpendicular + +Triangle 1 = A, D, C → 3 angles +Triangle 2 = D, B, C → 3 angles +``` +D is stored as `pts[3]` so it can be dragged independently. +Draws: two colored triangles (orange T1, purple T2), dashed C→D line, right-angle mark at D. + +#### Drag-to-reposition (new) +All placed points on all tools are draggable: +``` +dragRef = useRef(null) // { measurementId, pointIdx } while dragging +hasDraggedRef = useRef(false) // prevents click firing after drag +HIT_RADIUS = 12 // canvas-px radius for hit detection + +onMouseDown → find nearest point within HIT_RADIUS → set dragRef +onMouseMove → if dragRef set: update point coords, call recomputeValue() +onMouseUp → clear dragRef +onClick → skip if hasDraggedRef.current is true +``` + +`recomputeValue(tool, points)` re-runs the formula for that tool and returns a new value string without creating a new measurement. + +#### Classification +| Condition | Calcaneal Pitch | Clarke's | Arch Index | Meary's | +|---|---|---|---|---| +| Pes Cavus (high arch) | > 32° | > 54° | < 0.21 | < 4° | +| Normal | 17°–32° | 42°–54° | 0.21–0.26 | < 4° | +| Pes Planus (flat foot) | < 17° | < 42° | > 0.26 | > 4° | + +--- + +## 5. Routing Architecture + +Routes registered in `platform/app/src/routes/index.tsx`: + +```typescript +const bakedInRoutes = [ + // ... existing routes ... + { path: '/ecg-viewer', children: EcgViewer }, + { path: '/smart-paint', children: SmartPaint }, + { path: '/flatfoot', children: Flatfoot }, +]; +``` + +Each route wrapper pattern: +```tsx +export default function XRoute() { + const navigate = useNavigate(); + return ( +
+
+ +
+
+ + + +
+
+ ); +} +``` + +--- + +## 6. WorkList Integration + +The 3 tool buttons appear in the toolbar above the study list. + +``` +[ ▶ ECG Viewer ] [ ▶ Smart Paint ] [ ▶ Flatfoot Analysis ] │ study list below +``` + +--- + +## 7. Key Bug Fixes Applied + +### Bug 1 — Webpack `Module not found` (relative path depth) +**Fix:** Changed relative import depth from `../../../../` to `../../../../../` to correctly reach the repo root. + +### Bug 2 — Canvas click coordinate offset (ECG + Flatfoot) +**Fix:** Scale click coordinates by `canvas.width / rect.width` and `canvas.height / rect.height`. + +### Bug 3 — Linter reverting file edits +**Fix:** Used full-file Write instead of incremental Edit when the formatter reverted partial changes. + +### Bug 4 — D point not draggable in Split Triangle +**Problem:** D was computed each frame from A/B/C, so there was no stored point to drag. +**Fix:** During measurement commit, store 4 points: `[A, B, C, D]`. Both `recomputeValue` and `drawMeasure` check `pts.length === 4` to use stored D; otherwise compute it fresh. + +### Bug 5 — Click fires after drag +**Problem:** `onClick` fired after a drag-release, placing a new measurement point. +**Fix:** `hasDraggedRef.current` is set on first `mousemove` during drag; `onClick` skips if this is true, then resets it. + +--- + +## 8. Verification Checklist + +``` +Start the dev server: + yarn dev (from repo root) + +Open: http://localhost:3000 + +✓ WorkList loads +✓ See: [ECG Viewer] [Smart Paint] [Flatfoot Analysis] buttons +✓ ECG Viewer → upload PNG, calibrate, measure QT → QTc shown +✓ Smart Paint → upload image, paint, area px² updates live, undo/redo works +✓ Flatfoot → upload X-ray, use Triangle tool → 3 angles + area shown +✓ Flatfoot → use Split Triangle → 2 triangles with all angles; drag D point +✓ Flatfoot → drag any measurement point → values recalculate live +``` + +--- + +## 9. Summary Table + +| Item | Count | +|---|---| +| New extensions created | 3 | +| New route folders created | 3 | +| New `.tsx` panel files | 6 (3 panels + 3 route wrappers) | +| New utility/tool `.ts` files | 3 | +| New `package.json` files | 3 | +| Existing files modified | 8 | +| New documentation files | 3 | +| npm packages installed | **0** | + +--- + +*All extensions use only React, TypeScript, Tailwind CSS, HTML5 Canvas, and libraries already present in the OHIF v3 monorepo.* diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index c1bbb42cfe1..2053ee89da5 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -1,4 +1,6 @@ import React, { useEffect, useState, useCallback } from 'react'; + +const EMBED_VIEWER_EVENT = 'ohif:embedViewer'; import PropTypes from 'prop-types'; import { InvestigationalUseDialog } from '@ohif/ui-next'; @@ -147,6 +149,22 @@ function ViewerLayout({ }; }, [panelService, hasPanels]); + const [embeddedViewerUrl, setEmbeddedViewerUrl] = useState(null); + + useEffect(() => { + const handler = (e: Event) => { + const url = (e as CustomEvent<{ url: string | null }>).detail?.url; + setEmbeddedViewerUrl(url ?? null); + }; + window.addEventListener(EMBED_VIEWER_EVENT, handler); + return () => window.removeEventListener(EMBED_VIEWER_EVENT, handler); + }, []); + + const closeEmbedded = useCallback(() => { + (window as any)._ohifEmbeddedViewer = null; + window.dispatchEvent(new CustomEvent(EMBED_VIEWER_EVENT, { detail: { url: null } })); + }, []); + const viewportComponents = viewports.map(getViewportComponentData); return ( @@ -163,58 +181,79 @@ function ViewerLayout({ > {showLoadingIndicator && } - - {/* LEFT SIDEPANELS */} - {hasLeftPanels ? ( - <> - - - - - - ) : null} - {/* TOOLBAR + GRID */} - -
-
+
+ Embedded Viewer +
+