// How a measured imaging figure reads, decided in exactly one place. // // The figure is rendered twice for the same measurement and the two renderings are compared BY EYE: // one is what the viewer currently has on screen, the other is what the patient's chart holds for it. // That comparison is the whole reason a clinician can tell a caliper they nudged from one they did // not. Two copies of the rounding, or two spellings of the same unit, would make identical // measurements read as a discrepancy - and the action that invites is amending a finding that never // changed. // // Deliberately free of React, of Cornerstone and of the viewport directory, so both sides can use it: // a bundle-isolation test forbids anything outside src/components/DicomViewport/ from importing that // directory at all, which is what would otherwise pull the imaging packages into every page. // Cornerstone keeps display units; the chart keeps UCUM. Same physical unit, different spelling. // A unit not listed here is shown exactly as it arrived rather than blanked or guessed at: an // unfamiliar unit spelled out is honest, a figure with no unit at all is not. const DISPLAY_BY_UCUM: Readonly> = Object.freeze({ 'mm2': 'mm²', "[hnsf'U]": 'HU', // An angle is charted in UCUM 'deg' and drawn on the image with the degree sign. Left unmapped, // the drawn "37.2°" and the charted "37.2 deg" are the same measurement rendered two ways, which // reads on the row as a chart that disagrees with the image - on every charted angle, forever. 'deg': '°' }); export const measurementDisplayUnit = (unit: string): string => DISPLAY_BY_UCUM[unit] ?? unit; // UNITS THAT BIND DIRECTLY TO THE NUMBER. Every unit here is written with a space before it - // "24.6 mm" - except the degree sign, which is set tight against the figure in every clinical // display and in Cornerstone's own on-image label. Handled inside the formatter rather than by // letting the angle bypass it: a figure formatted anywhere else is a second spelling waiting to // happen, which is the one thing this module exists to prevent. const TIGHT_UNITS: readonly string[] = ['°']; // Two decimals at most, with no trailing zeros - close enough to the figure Cornerstone draws on the // image itself that a row and the drawing it names read as the same measurement. const round = (value: number): string => String(Math.round(value * 100) / 100); export const measurementFigure = (value: number, unit: string): string => { const display = measurementDisplayUnit(unit); return TIGHT_UNITS.includes(display) ? `${round(value)}${display}` : `${round(value)} ${display}`.trim(); };