Skip to content

Commit

Permalink
fix(PT measurement units): Non-SUV scaled, but pre-scaled PTs should …
Browse files Browse the repository at this point in the history
…show proper units (#686)

* fix(PT measurement units): SUV scaled PTs show SUV units.
Non-SUV scaled, but pre-scaled PTs show the units from the DICOM (0054,1001)/units tag.

* PR feedback:
- introduced ModalityUnitOptions to pass around isSuvScale and isPreScaled
- refactored getModalityUnit function for readability

* Updated the API docs.
  • Loading branch information
jbocce committed Jul 13, 2023
1 parent 41a6ff5 commit e9190df
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 220 deletions.
22 changes: 10 additions & 12 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export class CircleROITool extends AnnotationTool {
// (undocumented)
addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => CircleROIAnnotation;
// (undocumented)
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any) => any;
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any, modalityUnitOptions: ModalityUnitOptions) => any;
// (undocumented)
cancel: (element: HTMLDivElement) => any;
// (undocumented)
Expand All @@ -700,7 +700,7 @@ export class CircleROITool extends AnnotationTool {
// (undocumented)
_endCallback: (evt: EventTypes_2.InteractionEventType) => void;
// (undocumented)
_getTextLines: (data: any, targetId: string, isPreScaled: boolean, isSuvScaled: boolean) => string[];
_getTextLines: (data: any, targetId: string) => string[];
// (undocumented)
handleSelectedCallback: (evt: EventTypes_2.InteractionEventType, annotation: CircleROIAnnotation, handle: ToolHandle) => void;
// (undocumented)
Expand Down Expand Up @@ -1652,7 +1652,7 @@ export class EllipticalROITool extends AnnotationTool {
// (undocumented)
addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => EllipticalROIAnnotation;
// (undocumented)
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any) => any;
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any, modalityUnitOptions: ModalityUnitOptions) => any;
// (undocumented)
cancel: (element: HTMLDivElement) => any;
// (undocumented)
Expand Down Expand Up @@ -1683,7 +1683,7 @@ export class EllipticalROITool extends AnnotationTool {
// (undocumented)
_getCanvasEllipseCenter(ellipseCanvasPoints: Types_2.Point2[]): Types_2.Point2;
// (undocumented)
_getTextLines: (data: any, targetId: string, isPreScaled: boolean, isSuvScaled: boolean) => string[];
_getTextLines: (data: any, targetId: string) => string[];
// (undocumented)
handleSelectedCallback: (evt: EventTypes_2.InteractionEventType, annotation: EllipticalROIAnnotation, handle: ToolHandle) => void;
// (undocumented)
Expand Down Expand Up @@ -3670,13 +3670,13 @@ export class PlanarFreehandROITool extends AnnotationTool {
// (undocumented)
addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => PlanarFreehandROIAnnotation;
// (undocumented)
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any) => any;
_calculateCachedStats: (annotation: any, viewport: any, renderingEngine: any, enabledElement: any, modalityUnitOptions: ModalityUnitOptions) => any;
// (undocumented)
cancel: (element: HTMLDivElement) => void;
// (undocumented)
filterInteractableAnnotationsForElement(element: HTMLDivElement, annotations: Annotations): Annotations | undefined;
// (undocumented)
_getTextLines: (data: any, targetId: string, isPreScaled: boolean, isSuvScaled: boolean) => string[];
_getTextLines: (data: any, targetId: string) => string[];
// (undocumented)
handleSelectedCallback: (evt: EventTypes_2.InteractionEventType, annotation: PlanarFreehandROIAnnotation, handle: ToolHandle) => void;
// (undocumented)
Expand Down Expand Up @@ -3822,7 +3822,7 @@ export class ProbeTool extends AnnotationTool {
// (undocumented)
addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => ProbeAnnotation;
// (undocumented)
_calculateCachedStats(annotation: any, renderingEngine: any, enabledElement: any): any;
_calculateCachedStats(annotation: any, renderingEngine: any, enabledElement: any, modalityUnitOptions: ModalityUnitOptions): any;
// (undocumented)
cancel: (element: HTMLDivElement) => any;
// (undocumented)
Expand All @@ -3845,9 +3845,7 @@ export class ProbeTool extends AnnotationTool {
// (undocumented)
getHandleNearImagePoint(element: HTMLDivElement, annotation: ProbeAnnotation, canvasCoords: Types_2.Point2, proximity: number): ToolHandle | undefined;
// (undocumented)
_getTextLines(data: any, targetId: string, isPreScaled: boolean, isSuvScaled: boolean): string[] | undefined;
// (undocumented)
_getValueForModality(value: any, imageVolume: any, modality: any): {};
_getTextLines(data: any, targetId: string): string[] | undefined;
// (undocumented)
handleSelectedCallback(evt: EventTypes_2.InteractionEventType, annotation: ProbeAnnotation): void;
// (undocumented)
Expand Down Expand Up @@ -4108,7 +4106,7 @@ export class RectangleROITool extends AnnotationTool {
// (undocumented)
addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => RectangleROIAnnotation;
// (undocumented)
_calculateCachedStats: (annotation: any, viewPlaneNormal: any, viewUp: any, renderingEngine: any, enabledElement: any) => any;
_calculateCachedStats: (annotation: any, viewPlaneNormal: any, viewUp: any, renderingEngine: any, enabledElement: any, modalityUnitOptions: any) => any;
// (undocumented)
cancel: (element: HTMLDivElement) => any;
// (undocumented)
Expand Down Expand Up @@ -4136,7 +4134,7 @@ export class RectangleROITool extends AnnotationTool {
height: number;
};
// (undocumented)
_getTextLines: (data: any, targetId: string, isPreScaled: boolean, isSuvScaled: boolean) => string[] | undefined;
_getTextLines: (data: any, targetId: string) => string[] | undefined;
// (undocumented)
handleSelectedCallback: (evt: EventTypes_2.InteractionEventType, annotation: RectangleROIAnnotation, handle: ToolHandle) => void;
// (undocumented)
Expand Down
61 changes: 33 additions & 28 deletions packages/tools/src/tools/annotation/CircleROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ import {
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
import { pointInShapeCallback } from '../../utilities';
import { StyleSpecifier } from '../../types/AnnotationStyle';
import { getModalityUnit } from '../../utilities/getModalityUnit';
import {
ModalityUnitOptions,
getModalityUnit,
} from '../../utilities/getModalityUnit';
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
import {
getCanvasCircleCorners,
Expand Down Expand Up @@ -661,6 +664,16 @@ class CircleROITool extends AnnotationTool {

const { centerPointRadius } = this.configuration;

const modalityUnitOptions = {
isPreScaled: isViewportPreScaled(viewport, targetId),

isSuvScaled: this.isSuvScaled(
viewport,
targetId,
annotation.metadata.referencedImageId
),
};

// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
// force to recalculate the stats from the points
if (
Expand All @@ -683,14 +696,16 @@ class CircleROITool extends AnnotationTool {
annotation,
viewport,
renderingEngine,
enabledElement
enabledElement,
modalityUnitOptions
);
} else if (annotation.invalidated) {
this._throttledCalculateCachedStats(
annotation,
viewport,
renderingEngine,
enabledElement
enabledElement,
modalityUnitOptions
);
// If the invalidated data is as a result of volumeViewport manipulation
// of the tools, we need to invalidate the related viewports data, so that
Expand Down Expand Up @@ -797,20 +812,7 @@ class CircleROITool extends AnnotationTool {

renderStatus = true;

const isPreScaled = isViewportPreScaled(viewport, targetId);

const isSuvScaled = this.isSuvScaled(
viewport,
targetId,
annotation.metadata.referencedImageId
);

const textLines = this._getTextLines(
data,
targetId,
isPreScaled,
isSuvScaled
);
const textLines = this._getTextLines(data, targetId);
if (!textLines || textLines.length === 0) {
continue;
}
Expand Down Expand Up @@ -854,12 +856,7 @@ class CircleROITool extends AnnotationTool {
return renderStatus;
};

_getTextLines = (
data,
targetId: string,
isPreScaled: boolean,
isSuvScaled: boolean
): string[] => {
_getTextLines = (data, targetId: string): string[] => {
const cachedVolumeStats = data.cachedStats[targetId];
const {
radius,
Expand All @@ -871,10 +868,10 @@ class CircleROITool extends AnnotationTool {
isEmptyArea,
Modality,
areaUnit,
modalityUnit,
} = cachedVolumeStats;

const textLines: string[] = [];
const unit = getModalityUnit(Modality, isPreScaled, isSuvScaled);

if (radius) {
const radiusLine = isEmptyArea
Expand All @@ -891,15 +888,15 @@ class CircleROITool extends AnnotationTool {
}

if (mean) {
textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`);
textLines.push(`Mean: ${mean.toFixed(2)} ${modalityUnit}`);
}

if (max) {
textLines.push(`Max: ${max.toFixed(2)} ${unit}`);
textLines.push(`Max: ${max.toFixed(2)} ${modalityUnit}`);
}

if (stdDev) {
textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`);
textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${modalityUnit}`);
}

return textLines;
Expand All @@ -909,7 +906,8 @@ class CircleROITool extends AnnotationTool {
annotation,
viewport,
renderingEngine,
enabledElement
enabledElement,
modalityUnitOptions: ModalityUnitOptions
) => {
const data = annotation.data;
const { viewportId, renderingEngineId } = enabledElement;
Expand Down Expand Up @@ -1037,6 +1035,12 @@ class CircleROITool extends AnnotationTool {
stdDev /= count;
stdDev = Math.sqrt(stdDev);

const modalityUnit = getModalityUnit(
metadata.Modality,
annotation.metadata.referencedImageId,
modalityUnitOptions
);

cachedStats[targetId] = {
Modality: metadata.Modality,
area,
Expand All @@ -1048,6 +1052,7 @@ class CircleROITool extends AnnotationTool {
radius: worldWidth / 2,
radiusUnit: hasPixelSpacing ? 'mm' : 'px',
perimeter: 2 * Math.PI * (worldWidth / 2),
modalityUnit,
};
} else {
this.isHandleOutsideImage = true;
Expand Down
39 changes: 23 additions & 16 deletions packages/tools/src/tools/annotation/DragProbeTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,36 @@ class DragProbeTool extends ProbeTool {

const color = this.getStyle('color', styleSpecifier, annotation);

const modalityUnitOptions = {
isPreScaled: isViewportPreScaled(viewport, targetId),

isSuvScaled: this.isSuvScaled(
viewport,
targetId,
annotation.metadata.referencedImageId
),
};

if (!data.cachedStats[targetId]) {
data.cachedStats[targetId] = {
Modality: null,
index: null,
value: null,
};

this._calculateCachedStats(annotation, renderingEngine, enabledElement);
this._calculateCachedStats(
annotation,
renderingEngine,
enabledElement,
modalityUnitOptions
);
} else if (annotation.invalidated) {
this._calculateCachedStats(annotation, renderingEngine, enabledElement);
this._calculateCachedStats(
annotation,
renderingEngine,
enabledElement,
modalityUnitOptions
);
}

// If rendering engine has been destroyed while rendering
Expand All @@ -185,20 +205,7 @@ class DragProbeTool extends ProbeTool {

renderStatus = true;

const isPreScaled = isViewportPreScaled(viewport, targetId);

const isSuvScaled = this.isSuvScaled(
viewport,
targetId,
annotation.metadata.referencedImageId
);

const textLines = this._getTextLines(
data,
targetId,
isPreScaled,
isSuvScaled
);
const textLines = this._getTextLines(data, targetId);
if (textLines) {
const textCanvasCoordinates = [
canvasCoordinates[0] + 6,
Expand Down
Loading

0 comments on commit e9190df

Please sign in to comment.