Skip to content

Integrate ECG Tool, Smart Paint segmentation and flatfoot measurement tool #5915

Open
onkar76 wants to merge 3 commits intoOHIF:release/3.12from
onkar76:feature/clinical-measurement-3.12
Open

Integrate ECG Tool, Smart Paint segmentation and flatfoot measurement tool #5915
onkar76 wants to merge 3 commits intoOHIF:release/3.12from
onkar76:feature/clinical-measurement-3.12

Conversation

@onkar76
Copy link
Copy Markdown

@onkar76 onkar76 commented Mar 24, 2026

Context

Changes & Results

The following tools have been added to the Basic Viewer:

ECG QTc Tool — Enables QT interval measurement and QTc calculation directly on ECG images.

Smart Paint Suite — A comprehensive set of segmentation tools (Brush, Eraser, Threshold, AI-assisted segmentation, etc.) for labeling anatomical structures.

ABC Split Angle Tool (Flatfoot Measurement) — Provides geometric analysis for foot alignment assessment, including area and angle calculations.

Testing

Checklist

PR

  • [] My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • [] My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • [] The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • [] OS:
  • [] Node version:
  • [] Browser:

Greptile Summary

This PR adds three new clinical measurement features to the Basic Viewer: an ECG QTc Tool (ECGBidirectionalTool) for on-image QT/RR/QTc annotation, a Smart Paint Suite (Brush/Eraser/segmentation toolbox) for labelmap segmentation, and an ABC Split Angle Tool for flatfoot geometric analysis. It also introduces a sizeable new ECGViewerPanel (973 lines) that provides off-image ECG interval, QTc, HRV, and QRS-axis calculations, wires the panel into the basic, longitudinal, and preclinical-4D modes, and extends the D3 LineChart component with point-click selection.

Key concerns found during review:

  • Hardcoded ECG Viewer button in WorkList — an unconditional "ECG Viewer" button is rendered for every study row regardless of modality, navigating to the default viewer without any mode parameter. This should be removed or at minimum gated on ECG/Waveform modality.
  • ECGBidirectionalTool interval calculation — QT and RR measurements use raw 3D world-coordinate distances converted directly to milliseconds via a fixed 25 mm/s paper speed, producing silently incorrect values for any image without valid pixel-spacing calibration or recorded at 50 mm/s.
  • HRV SDNN formulaECGViewerPanel divides by n (population SD) instead of n − 1 (sample SD); the standard Task Force SDNN definition requires sample SD, so reported values will underestimate the reference ranges.
  • QTC_THRESHOLDS borderline zone unreachable — the borderline field equals prolonged for both genders and is never read by qtcStatusLabel, so the BORDERLINE risk category is never returned correctly.
  • i18n regressionsi18next-browser-languagedetector is downgraded from 3.1.1 → 2.2.4 without documented justification; lngWhitelist (deprecated) is used instead of supportedLngs; and multiple UI labels are constructed by splicing two translated tokens in a fixed English word order, breaking non-English locales.

Confidence Score: 1/5

  • Not safe to merge — multiple clinical accuracy bugs in the ECG tooling, a UI regression affecting every study row in the WorkList, and an unexplained i18n dependency downgrade.
  • Several P1-level issues prevent a clean merge: (1) the unconditional ECG Viewer WorkList button is a visible regression for all users; (2) the QT/RR calculation in ECGBidirectionalTool will silently produce wrong clinical values on uncalibrated images; (3) the SDNN formula is clinically non-standard; (4) the QTc borderline zone logic is broken. The i18n downgrade adds further risk without documented rationale.
  • platform/app/src/routes/WorkList/WorkList.tsx, extensions/cornerstone/src/tools/ECGBidirectionalTool.js, extensions/cornerstone-dynamic-volume/src/panels/ECGViewerPanel.tsx, platform/app/package.json

Important Files Changed

Filename Overview
platform/app/src/routes/WorkList/WorkList.tsx Adds a hardcoded "ECG Viewer" button to every study row in the WorkList with no modality filtering, duplicating the mode-based navigation already present in the row.
extensions/cornerstone/src/tools/ECGBidirectionalTool.js New ECG tool that calculates QT/RR/QTc intervals by treating raw 3D world-coordinate distances as millimeters and applying a hardcoded 25 mm/s ECG paper speed; calculation correctness depends on image calibration and actual paper speed.
extensions/cornerstone-dynamic-volume/src/panels/ECGViewerPanel.tsx Large new ECG analysis panel with QTc, HRV, and QRS axis computation; contains a clinical accuracy issue in the HRV SDNN formula (uses population SD / n instead of sample SD / n-1) and unused dead-code fields in QTC_THRESHOLDS.
extensions/cornerstone/src/tools/ABCSplitAngleTool.js New tool for flatfoot/foot alignment measurement using ABC triangle split angle and area calculation. Logic appears sound for world-space geometry.
platform/i18n/src/index.js Adds deprecated lngWhitelist option (replaced by supportedLngs in i18next v21+) and is paired with a downgrade of i18next-browser-languagedetector from 3.1.1 to 2.2.4.
platform/ui-next/src/components/LineChart/d3LineChart/chart.ts Adds selected-point highlighting and click-handler support to the D3 line chart; clean, well-scoped changes.
platform/ui-next/src/components/SegmentationTable/AddSegmentationRow.tsx Constructs "Add Labelmap Segmentation" label by splicing two translated tokens in a template literal, which bypasses proper i18n and will break for non-English locales with different word order.
extensions/cornerstone/src/commandsModule.ts activateSelectedSegmentationOfType now auto-creates a labelmap when none exists, avoiding a silent no-op. Change looks correct.
modes/basic/src/index.tsx Adds ECGViewerPanel to rightPanels and new toolbar sections; adds cornerstone-dynamic-volume as a required dependency. Longitudinal mode is also updated to include the ECG panel, coupling it to an extension it didn't previously depend on.
platform/app/package.json Downgrades i18next-browser-languagedetector from 3.1.1 to 2.2.4 — a version that is over 4 years older and may lack bug fixes or security patches.

Sequence Diagram

sequenceDiagram
    participant User
    participant WorkList
    participant BasicMode
    participant ECGViewerPanel
    participant ECGBidirectionalTool
    participant LineChart

    User->>WorkList: Expand study row
    WorkList-->>User: Render mode buttons + (new) hardcoded ECG Viewer button

    User->>BasicMode: Open study via Basic mode
    BasicMode->>ECGViewerPanel: Mount in right panel
    BasicMode->>ECGBidirectionalTool: Register tool in tool group

    User->>LineChart: Click point on ECG chart
    LineChart-->>ECGViewerPanel: onPointClick(seriesIndex, pointIndex, x, y)
    ECGViewerPanel->>ECGViewerPanel: Add to selectedPoints (max 4)
    ECGViewerPanel->>ECGViewerPanel: buildInterval(A→B, B→C, C→D, B→D)
    ECGViewerPanel->>ECGViewerPanel: qtcBazett(qtSec, rrSec)
    ECGViewerPanel-->>User: Display QTc / HRV / QRS-axis results

    User->>ECGBidirectionalTool: Place 3 points (A, B, C) on ECG image
    ECGBidirectionalTool->>ECGBidirectionalTool: distance3(A,B) → mm → mmToMs → QT ms
    ECGBidirectionalTool->>ECGBidirectionalTool: distance3(A,C) → mm → mmToMs → RR ms
    ECGBidirectionalTool->>ECGBidirectionalTool: qtcBazett / qtcFridericia
    ECGBidirectionalTool-->>User: Overlay QT, RR, QTc labels on viewport
Loading

Reviews (1): Last reviewed commit: "Added clinical measurement changes" | Re-trigger Greptile

Greptile also left 7 inline comments on this PR.

(4/5) You can add custom instructions or style guidelines for the agent here!

@onkar76 onkar76 changed the title Added clinical measurement changes Add ECG QTc Tool, Smart Paint segmentation suite, and ABC Split Angle flatfoot measurement tool Mar 24, 2026
@onkar76 onkar76 changed the title Add ECG QTc Tool, Smart Paint segmentation suite, and ABC Split Angle flatfoot measurement tool Add ECG Tool, Smart Paint segmentation and flatfoot measurement tool Mar 24, 2026
@onkar76 onkar76 changed the title Add ECG Tool, Smart Paint segmentation and flatfoot measurement tool Integrate ECG Tool, Smart Paint segmentation and flatfoot measurement tool Mar 24, 2026
Comment on lines +466 to +489
{(() => {
const query = new URLSearchParams();
if (filterValues.configUrl) {
query.append('configUrl', filterValues.configUrl);
}
query.append('StudyInstanceUIDs', studyInstanceUid);
preserveQueryParameters(query);
return (
<Link
key="ecg-viewer"
to={`viewer${dataPath || ''}?${query.toString()}`}
>
<Button
type={ButtonEnums.type.primary}
size={ButtonEnums.size.smallTall}
startIcon={<Icons.LaunchArrow className="!h-[20px] !w-[20px] text-black" />}
onClick={() => {}}
dataCY={`mode-ecg-viewer-${studyInstanceUid}`}
>
ECG Viewer
</Button>
</Link>
);
})()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hardcoded ECG Viewer button appears on every study

This IIFE renders an "ECG Viewer" <Button> inside StudyListExpandedRow unconditionally — it will appear for every study in the WorkList regardless of modality (CT, MR, US, etc.). The generated URL viewer?StudyInstanceUIDs=... also carries no mode identifier, so it simply opens the default viewer rather than any ECG-specific mode.

The existing per-mode buttons above this block (rendered by the modes.map(...)) already handle mode-based navigation. This addition appears to be a leftover debug/development shortcut that was not intended for production. It should be removed, or — if an ECG shortcut is truly needed — conditionally rendered when the study contains Waveform (ECG) series and routed to the correct mode.

Comment on lines +113 to +133
}

if (result.QT && result.RR) {
const qtSec = result.QT.ms / 1000;
const rrSec = result.RR.ms / 1000;
if (rrSec > 0) {
result.QTcB = { ms: (qtSec / Math.sqrt(rrSec)) * 1000 };
result.QTcF = { ms: (qtSec / Math.cbrt(rrSec)) * 1000 };
}
}

return result;
}

// ── Tool class ────────────────────────────────────────────────────────────────

class ECGBidirectionalTool extends AngleTool {
static toolName = 'ECGBidirectional';

constructor(toolProps = {}, defaultToolProps) {
super(toolProps, defaultToolProps);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 QT/RR interval calculation assumes fixed paper speed and perfect calibration

computeStats converts the Euclidean world-coordinate distance directly to milliseconds via a hardcoded 25 mm/s ECG paper speed:

function mmToMs(mm) {
  return (mm / ECG_PAPER_SPEED_MM_S) * 1000;
}

This means the calculation is only accurate when:

  1. The DICOM image has correct pixel spacing metadata so that world coordinates are in true physical millimetres.
  2. The ECG was actually recorded and printed at exactly 25 mm/s — many systems also use 50 mm/s.

For scanned paper ECGs or screenshots there is typically no valid pixel spacing, so world distances bear no relation to physical millimetres and the QT/RR/QTc results will be silently wrong. Consider validating pixel spacing before presenting clinical values, and at minimum exposing a paper-speed selector (25/50 mm/s) so users can correct the assumption.

Comment on lines +176 to +185
function getIntervalStatus(key: string, ms: number) {
const def = ECG_INTERVAL_DEFS[key];
if (!def || def.min === null) return null;
if (ms < def.min) return { label: '↓ Short', note: def.shortNote, color: 'text-yellow-400' };
if (ms > def.max) return { label: '↑ Long', note: def.longNote, color: 'text-red-400' };
return { label: '✓ Normal', note: `Normal range: ${def.min}–${def.max} ms`, color: 'text-green-400' };
}

function qtcBazett(qtSec: number, rrSec: number): number | null {
if (!Number.isFinite(qtSec) || !Number.isFinite(rrSec) || rrSec <= 0) return null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 SDNN uses population standard deviation (÷ n) instead of sample SD (÷ n−1)

The standard HRV metric SDNN is defined as the sample standard deviation of NN intervals (divides by n − 1). The current code divides by n:

const sdnn = Math.sqrt(valid.reduce((s, v) => s + (v - meanRR) ** 2, 0) / n);

This underestimates SDNN and will produce values that don't match established HRV reference ranges (e.g., Task Force of the European Society of Cardiology, 1996). Suggested fix:

Suggested change
function getIntervalStatus(key: string, ms: number) {
const def = ECG_INTERVAL_DEFS[key];
if (!def || def.min === null) return null;
if (ms < def.min) return { label: '↓ Short', note: def.shortNote, color: 'text-yellow-400' };
if (ms > def.max) return { label: '↑ Long', note: def.longNote, color: 'text-red-400' };
return { label: '✓ Normal', note: `Normal range: ${def.min}${def.max} ms`, color: 'text-green-400' };
}
function qtcBazett(qtSec: number, rrSec: number): number | null {
if (!Number.isFinite(qtSec) || !Number.isFinite(rrSec) || rrSec <= 0) return null;
const sdnn = Math.sqrt(valid.reduce((s, v) => s + (v - meanRR) ** 2, 0) / (n - 1));

Comment on lines +89 to +93
female: { normal: 460, borderline: 470, prolonged: 470 },
};

const PROLONGED_CAUSES = [
'Amiodarone', 'Haloperidol', 'Sotalol', 'Azithromycin',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 QTC_THRESHOLDS.borderline is dead code — its value is never read

The borderline field is declared in QTC_THRESHOLDS but qtcStatusLabel never references t.borderline; it only uses t.normal and t.prolonged:

if (ms > t.prolonged)  // uses prolonged
  ...
if (ms > t.normal)     // uses normal

Because borderline === prolonged for both genders the field has no effect on any output. Either the qtcStatusLabel function should be updated to use t.borderline as the upper boundary of the borderline zone (separating it from the fully prolonged zone), or the field should be removed. As written, male QTc 461–500 ms is labelled "PROLONGED" where clinically it should be "BORDERLINE".

Comment on lines +25 to +26
const isLabelMap = segmentationRepresentationTypes?.[0] === 'Labelmap';
const addLabel = isLabelMap ? `Add ${t('Labelmap')} ${t('Segmentation')}` : t('Add segmentation');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Template-literal i18n pattern will break non-English locales

Constructing labels by concatenating two independently translated tokens in a fixed English word order:

const addLabel = isLabelMap ? `Add ${t('Labelmap')} ${t('Segmentation')}` : t('Add segmentation');

won't translate correctly for languages with different noun/adjective order or compound-word rules. The same pattern is repeated in CustomDropdownMenuContent.tsx lines 53–58 (Create New ${t('Labelmap')} ${t('Segmentation')}, Manage Current …) and in SegmentationCollapsed.tsx line 80 (Select a ${t('Labelmap').toLowerCase()}).

All three cases should use a single translation key (e.g., 'Add Labelmap Segmentation') so translators can produce a grammatically correct phrase for each locale.

"history": "5.3.0",
"i18next": "17.3.1",
"i18next-browser-languagedetector": "3.1.1",
"i18next-browser-languagedetector": "2.2.4",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 i18next-browser-languagedetector downgraded from 3.1.1 → 2.2.4

This is a significant downgrade (4+ major patch versions). Version 2.2.4 predates several bug fixes and the same change is mirrored in platform/i18n/package.json. If the intention was to resolve a compatibility issue with the existing [email protected], please document why, as this may silently reintroduce old language-detection bugs. If it was accidental, revert to 3.1.1.

// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: DEFAULT_LANGUAGE,
lngWhitelist: Object.keys(locales),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 lngWhitelist is deprecated — use supportedLngs instead

lngWhitelist was deprecated in i18next v21 in favour of supportedLngs. While i18next 17.x still accepts it, aligning with the recommended API avoids a migration burden if i18next is ever upgraded. The same key is added on line 117.

Suggested change
lngWhitelist: Object.keys(locales),
supportedLngs: Object.keys(locales),

@sedghi
Copy link
Copy Markdown
Member

sedghi commented Mar 24, 2026

is this correct base? you want it in 3.12? we don't usually add features after release

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.

2 participants