Skip to content

Commit

Permalink
fix(cornerstone-dicom-sr): Freehand SR hydration support (#1160)
Browse files Browse the repository at this point in the history
* Fix freehand sr annotation load

* Add perimeter and cached stats

* Update import

* Add perimeter

* Update docs
  • Loading branch information
igoroctaviano authored Apr 5, 2024
1 parent 05f4cce commit 5e778a1
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 23 deletions.
7 changes: 5 additions & 2 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,9 @@ export class BrushTool extends BaseTool {
protected updateCursor(evt: EventTypes_2.InteractionEventType): void;
}

// @public (undocumented)
function calculatePerimeter(polyline: number[][], closed: boolean): number;

// @public (undocumented)
abstract class Calculator {
// (undocumented)
Expand Down Expand Up @@ -1552,7 +1555,8 @@ declare namespace contours {
updateContourPolyline,
interpolation,
acceptAutogeneratedInterpolations,
findHandlePolylineIndex
findHandlePolylineIndex,
calculatePerimeter
}
}

Expand Down Expand Up @@ -3771,7 +3775,6 @@ export class PlanarFreehandContourSegmentationTool extends PlanarFreehandROITool
type PlanarFreehandROIAnnotation = ContourAnnotation & {
data: {
label?: string;
isOpenContour?: boolean;
isOpenUShapeContour?: boolean;
openUShapeContourVectorToPeak?: Types_2.Point3[];
cachedStats?: ROICachedStats;
Expand Down
27 changes: 21 additions & 6 deletions packages/adapters/src/adapters/Cornerstone3D/PlanarFreehandROI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PlanarFreehandROI {
imageToWorldCoords,
metadata
) {
const { defaultState, SCOORDGroup, ReferencedFrameNumber } =
const { defaultState, NUMGroup, SCOORDGroup, ReferencedFrameNumber } =
MeasurementReport.getSetupMeasurementData(
MeasurementGroup,
sopInstanceUIDToImageIdMap,
Expand Down Expand Up @@ -79,15 +79,21 @@ class PlanarFreehandROI {
const state = defaultState;

state.annotation.data = {
polyline: worldCoords,
isOpenContour,
contour: { polyline: worldCoords, closed: !isOpenContour },
handles: {
points,
activeHandleIndex: null,
textBox: {
hasMoved: false
}
},
cachedStats: {
[`imageId:${referencedImageId}`]: {
area: NUMGroup
? NUMGroup.MeasuredValueSequence.NumericValue
: null
}
},
frameNumber: ReferencedFrameNumber
};

Expand All @@ -96,7 +102,9 @@ class PlanarFreehandROI {

static getTID300RepresentationArguments(tool, worldToImageCoords) {
const { data, finding, findingSites, metadata } = tool;
const { isOpenContour, polyline } = data;

const { polyline, closed } = data.contour;
const isOpenContour = closed !== true;

const { referencedImageId } = metadata;

Expand All @@ -118,13 +126,20 @@ class PlanarFreehandROI {
points.push([firstPoint[0], firstPoint[1]]);
}

const area = 0; // TODO -> The tool doesn't have these stats yet.
const perimeter = 0;
const { area, areaUnit, modalityUnit, perimeter, mean, max, stdDev } =
data.cachedStats[`imageId:${referencedImageId}`] || {};

return {
/** From cachedStats */
points,
area,
areaUnit,
perimeter,
modalityUnit,
mean,
max,
stdDev,
/** Other */
trackingIdentifierTextValue,
finding,
findingSites: findingSites || []
Expand Down
50 changes: 37 additions & 13 deletions packages/tools/src/tools/annotation/PlanarFreehandROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ import pointInShapeCallback from '../../utilities/pointInShapeCallback';
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
import { getModalityUnit } from '../../utilities/getModalityUnit';
import { BasicStatsCalculator } from '../../utilities/math/basic';
import calculatePerimeter from '../../utilities/contours/calculatePerimeter';
import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
import { KeyboardBindings, ChangeTypes } from '../../enums';

const { pointCanProjectOnLine } = polyline;
const { EPSILON } = CONSTANTS;

const PARALLEL_THRESHOLD = 1 - EPSILON;

/**
* PlanarFreehandROITool lets you draw annotations that define an arbitrarily drawn region.
* You can use the PlanarFreehandROITool in all perpendicular views (axial, sagittal, coronal),
Expand Down Expand Up @@ -243,7 +245,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
*/
epsilon: 0.1,
},
calculateStats: false,
calculateStats: true,
getTextLines: defaultGetTextLines,
statsCalculator: BasicStatsCalculator,
},
Expand Down Expand Up @@ -294,7 +296,9 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
);

this.activateDraw(evt, annotation, viewportIdsToRender);

evt.preventDefault();

triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);

return annotation;
Expand Down Expand Up @@ -350,6 +354,8 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
} else {
this.activateOpenContourEdit(evt, annotation, viewportIdsToRender);
}

evt.preventDefault();
};

/**
Expand Down Expand Up @@ -536,16 +542,21 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
annotation.data.handles.points.length = 0;
};

return <PlanarFreehandROIAnnotation>csUtils.deepMerge(contourAnnotation, {
data: {
contour: {
polyline: [<Types.Point3>[...worldPos]],
const annotation = <PlanarFreehandROIAnnotation>csUtils.deepMerge(
contourAnnotation,
{
data: {
contour: {
polyline: [<Types.Point3>[...worldPos]],
},
label: '',
cachedStats: {},
},
label: '',
cachedStats: {},
},
onInterpolationComplete,
});
onInterpolationComplete,
}
);

return annotation;
}

protected getAnnotationStyle(context) {
Expand Down Expand Up @@ -682,7 +693,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
) => {
const { data } = annotation;
const { cachedStats } = data;
const { polyline: points } = data.contour;
const { polyline: points, closed } = data.contour;

const targetIds = Object.keys(cachedStats);

Expand Down Expand Up @@ -835,6 +846,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
cachedStats[targetId] = {
Modality: metadata.Modality,
area,
perimeter: calculatePerimeter(canvasCoordinates, closed),
mean: stats.mean?.value,
max: stats.max?.value,
stdDev: stats.stdDev?.value,
Expand Down Expand Up @@ -920,8 +932,16 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {

function defaultGetTextLines(data, targetId): string[] {
const cachedVolumeStats = data.cachedStats[targetId];
const { area, mean, stdDev, max, isEmptyArea, areaUnit, modalityUnit } =
cachedVolumeStats || {};
const {
area,
mean,
stdDev,
perimeter,
max,
isEmptyArea,
areaUnit,
modalityUnit,
} = cachedVolumeStats || {};

const textLines: string[] = [];

Expand All @@ -944,6 +964,10 @@ function defaultGetTextLines(data, targetId): string[] {
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${modalityUnit}`);
}

if (perimeter) {
textLines.push(`Perimeter: ${roundNumber(perimeter)} ${modalityUnit}`);
}

return textLines;
}

Expand Down
1 change: 0 additions & 1 deletion packages/tools/src/types/ToolSpecificAnnotationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ export interface CircleROIStartEndThresholdAnnotation extends Annotation {
export type PlanarFreehandROIAnnotation = ContourAnnotation & {
data: {
label?: string;
isOpenContour?: boolean;
isOpenUShapeContour?: boolean;
// Present if isOpenUShapeContour is true:
openUShapeContourVectorToPeak?: Types.Point3[];
Expand Down
31 changes: 31 additions & 0 deletions packages/tools/src/utilities/contours/calculatePerimeter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Calculates the perimeter of a polyline.
*
* @param polyline - The polyline represented as an array of points.
* @param closed - Indicates whether the polyline is closed or not.
* @returns The perimeter of the polyline.
*/
function calculatePerimeter(polyline: number[][], closed: boolean): number {
let perimeter = 0;

for (let i = 0; i < polyline.length - 1; i++) {
const point1 = polyline[i];
const point2 = polyline[i + 1];
perimeter += Math.sqrt(
Math.pow(point2[0] - point1[0], 2) + Math.pow(point2[1] - point1[1], 2)
);
}

if (closed) {
const firstPoint = polyline[0];
const lastPoint = polyline[polyline.length - 1];
perimeter += Math.sqrt(
Math.pow(lastPoint[0] - firstPoint[0], 2) +
Math.pow(lastPoint[1] - firstPoint[1], 2)
);
}

return perimeter;
}

export default calculatePerimeter;
2 changes: 2 additions & 0 deletions packages/tools/src/utilities/contours/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import updateContourPolyline from './updateContourPolyline';
import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
import * as interpolation from './interpolation';
import findHandlePolylineIndex from './findHandlePolylineIndex';
import calculatePerimeter from './calculatePerimeter';

export {
areCoplanarContours,
Expand All @@ -24,4 +25,5 @@ export {
interpolation,
acceptAutogeneratedInterpolations,
findHandlePolylineIndex,
calculatePerimeter,
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
getToolGroupForViewport,
} from '../../store/ToolGroupManager';

import SegmentationDisplayTool from '../../tools/displayTools/SegmentationDisplayTool';
import { SegmentationDisplayTool } from '../../tools';
import { SegmentationRenderedEventDetail } from '../../types/EventTypes';

/**
Expand Down

0 comments on commit 5e778a1

Please sign in to comment.