Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: segmentSelect issue + segmentation modified event #1065

Merged
merged 3 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ async function run() {
mouseButton: MouseBindings.Primary, // Left Click
},
{
mouseButton: MouseBindings.Primary, // Left Click
mouseButton: MouseBindings.Primary, // Shift + Left Click
modifierKey: KeyboardBindings.Shift,
},
],
Expand Down
2 changes: 2 additions & 0 deletions packages/tools/examples/segmentSelect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function setupTools(toolGroupId, isContour = false) {

if (isContour) {
toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName);
toolGroup.setToolPassive(PlanarFreehandContourSegmentationTool.toolName);
}

addManipulationBindings(toolGroup);
Expand Down Expand Up @@ -223,6 +224,7 @@ async function run() {

const config = segmentation.config.getGlobalConfig();
config.representations.LABELMAP.activeSegmentOutlineWidthDelta = 3;
config.representations.CONTOUR.activeSegmentOutlineWidthDelta = 3;
}

run();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AnnotationRemovedEventType } from '../../types/EventTypes';
import * as contourSegUtils from '../../utilities/contourSegmentation';
import { contourSegmentationRemoved } from './contourSegmentation';

export default function annotationRemovedListener(
evt: AnnotationRemovedEventType
) {
const annotation = evt.detail.annotation;

if (contourSegUtils.isContourSegmentationAnnotation(annotation)) {
contourSegmentationRemoved(evt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
Types,
} from '@cornerstonejs/core';
import { ContourSegmentationAnnotation } from '../../../types/ContourSegmentationAnnotation';
import getViewportsForAnnotation from '../../../utilities/getViewportsForAnnotation';
import {
getViewportForAnnotation,
math,
triggerAnnotationRenderForViewportIds,
} from '../../../utilities';
Expand All @@ -26,6 +26,7 @@ import * as contourUtils from '../../../utilities/contours';
import * as contourSegUtils from '../../../utilities/contourSegmentation';
import { ToolGroupManager, hasTool as cstHasTool } from '../../../store';
import { PlanarFreehandContourSegmentationTool } from '../../../tools';
import type { Annotation } from '../../../types';
import type { ContourAnnotation } from '../../../types/ContourAnnotation';
import { ContourWindingDirection } from '../../../types/ContourAnnotation';

Expand All @@ -41,7 +42,7 @@ export default async function contourSegmentationCompletedListener(
return;
}

const viewport = getViewportForAnnotation(sourceAnnotation);
const viewport = getViewport(sourceAnnotation);
const contourSegmentationAnnotations =
getValidContourSegmentationAnnotations(sourceAnnotation);

Expand All @@ -64,10 +65,6 @@ export default async function contourSegmentationCompletedListener(
return;
}

if (!isFreehandContourSegToolRegistered(viewport)) {
return;
}

const { targetAnnotation, targetPolyline, isContourHole } =
targetAnnotationInfo;

Expand All @@ -92,30 +89,43 @@ export default async function contourSegmentationCompletedListener(
}
}

function isFreehandContourSegToolRegistered(viewport: Types.IViewport) {
function isFreehandContourSegToolRegisteredForViewport(
viewport: Types.IViewport,
silent = false
) {
const { toolName } = PlanarFreehandContourSegmentationTool;

if (!cstHasTool(PlanarFreehandContourSegmentationTool)) {
console.warn(`${toolName} is not registered in cornerstone`);
return false;
}

const toolGroup = ToolGroupManager.getToolGroupForViewport(
viewport.id,
viewport.renderingEngineId
);

let errorMessage;

if (!toolGroup.hasTool(toolName)) {
console.warn(`Tool ${toolName} not added to ${toolGroup.id} toolGroup`);
return false;
errorMessage = `Tool ${toolName} not added to ${toolGroup.id} toolGroup`;
} else if (!toolGroup.getToolOptions(toolName)) {
errorMessage = `Tool ${toolName} must be in active/passive state`;
}

if (!toolGroup.getToolOptions(toolName)) {
console.warn(`Tool ${toolName} must be in active/passive state`);
return false;
if (errorMessage && !silent) {
console.warn(errorMessage);
}

return true;
return !errorMessage;
}

function getViewport(annotation: Annotation) {
const viewports = getViewportsForAnnotation(annotation);
const viewportWithToolRegistered = viewports.find((viewport) =>
isFreehandContourSegToolRegisteredForViewport(viewport, true)
);

// Returns the first viewport even if freehand contour segmentation is not
// registered because it can be used to project the polyline to create holes.
// Another verification is done before appending/removing contours which is
// possible only when the tool is registered.
return viewportWithToolRegistered ?? viewports[0];
}

function convertContourPolylineToCanvasSpace(
Expand All @@ -136,11 +146,6 @@ function getValidContourSegmentationAnnotations(
sourceAnnotation: ContourSegmentationAnnotation
): ContourSegmentationAnnotation[] {
const { annotationUID: sourceAnnotationUID } = sourceAnnotation;
const { FrameOfReferenceUID } = sourceAnnotation.metadata;

if (!FrameOfReferenceUID) {
return [];
}

// Get all annotations and filter all contour segmentations locally
const allAnnotations = getAllAnnotations();
Expand Down Expand Up @@ -242,13 +247,25 @@ function getContourHolesData(
});
}

async function combinePolylines(
function combinePolylines(
viewport: Types.IViewport,
targetAnnotation: ContourSegmentationAnnotation,
targetPolyline: Types.Point2[],
sourceAnnotation: ContourSegmentationAnnotation,
sourcePolyline: Types.Point2[]
) {
if (!cstHasTool(PlanarFreehandContourSegmentationTool)) {
console.warn(
`${PlanarFreehandContourSegmentationTool.toolName} is not registered in cornerstone`
);
return;
}

// Cannot append/remove an annotation if it will not be available on any viewport
if (!isFreehandContourSegToolRegisteredForViewport(viewport)) {
return;
}

const sourceStartPoint = sourcePolyline[0];
const mergePolylines = math.polyline.containsPoint(
targetPolyline,
Expand Down Expand Up @@ -329,7 +346,7 @@ async function combinePolylines(
const polyline = newPolylines[i];
const startPoint = viewport.canvasToWorld(polyline[0]);
const endPoint = viewport.canvasToWorld(polyline[polyline.length - 1]);
const newAnnotation: ContourAnnotation = {
const newAnnotation: ContourSegmentationAnnotation = {
metadata: {
...metadata,
toolName: DEFAULT_CONTOUR_SEG_TOOLNAME,
Expand Down Expand Up @@ -368,6 +385,7 @@ async function combinePolylines(
);

addAnnotation(newAnnotation, element);
contourSegUtils.addContourSegmentationAnnotation(newAnnotation);

reassignedContourHolesMap
.get(polyline)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { AnnotationRemovedEventType } from '../../../types/EventTypes';
import type { ContourSegmentationAnnotation } from '../../../types/ContourSegmentationAnnotation';
import { removeContourSegmentationAnnotation } from '../../../utilities/contourSegmentation';

export default function contourSegmentationRemovedListener(
evt: AnnotationRemovedEventType
) {
const annotation = evt.detail.annotation as ContourSegmentationAnnotation;

removeContourSegmentationAnnotation(annotation);
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as contourSegmentationCompleted } from './contourSegmentationCompleted';
export { default as contourSegmentationRemoved } from './contourSegmentationRemoved';
2 changes: 2 additions & 0 deletions packages/tools/src/eventListeners/annotations/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import annotationCompletedListener from './annotationCompletedListener';
import annotationSelectionListener from './annotationSelectionListener';
import annotationModifiedListener from './annotationModifiedListener';
import annotationRemovedListener from './annotationRemovedListener';

export {
annotationCompletedListener,
annotationSelectionListener,
annotationModifiedListener,
annotationRemovedListener,
};
2 changes: 2 additions & 0 deletions packages/tools/src/eventListeners/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
annotationCompletedListener,
annotationSelectionListener,
annotationModifiedListener,
annotationRemovedListener,
} from './annotations';
//import touchEventListeners from './touchEventListeners';

Expand All @@ -29,4 +30,5 @@ export {
annotationCompletedListener,
annotationSelectionListener,
annotationModifiedListener,
annotationRemovedListener,
};
6 changes: 6 additions & 0 deletions packages/tools/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { addEnabledElement, removeEnabledElement } from './store';
import { resetCornerstoneToolsState } from './store/state';
import {
annotationCompletedListener,
annotationRemovedListener,
annotationSelectionListener,
annotationModifiedListener,
segmentationDataModifiedEventListener,
Expand Down Expand Up @@ -124,6 +125,11 @@ function _addCornerstoneToolsEventListeners() {
annotationSelectionListener
);

eventTarget.addEventListener(
TOOLS_EVENTS.ANNOTATION_REMOVED,
annotationRemovedListener
);

/**
* Segmentation
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { utilities } from '@cornerstonejs/core';
import type { PublicToolProps } from '../../types';
import type { AnnotationRenderContext } from '../../types';
import { PlanarFreehandContourSegmentationAnnotation } from '../../types/ToolSpecificAnnotationTypes';
import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
import PlanarFreehandROITool from './PlanarFreehandROITool';

class PlanarFreehandContourSegmentationTool extends PlanarFreehandROITool {
Expand Down Expand Up @@ -28,6 +31,28 @@ class PlanarFreehandContourSegmentationTool extends PlanarFreehandROITool {
// Re-enable contour segmentation behavior disabled by PlanarFreehandROITool
return true;
}

protected renderAnnotationInstance(
renderContext: AnnotationRenderContext
): boolean {
const annotation =
renderContext.annotation as PlanarFreehandContourSegmentationAnnotation;
const { invalidated } = annotation;

// Render the annotation before triggering events
const renderResult = super.renderAnnotationInstance(renderContext);

if (invalidated) {
const { segmentationId } = annotation.data.segmentation;

// This event is trigged by ContourSegmentationBaseTool but PlanarFreehandROITool
// is the only contour class that does not call `renderAnnotationInstace` from
// its base class.
triggerSegmentationDataModified(segmentationId);
}

return renderResult;
}
}

PlanarFreehandContourSegmentationTool.toolName =
Expand Down
2 changes: 1 addition & 1 deletion packages/tools/src/types/ContourSegmentationAnnotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type ContourSegmentationAnnotationData = {
originalPolyline?: Types.Point3[];
};
};
handles: {
handles?: {
/**
* Segmentation contours can be interpolated between slices to produce
* intermediate data. The interpolation sources are source data for
Expand Down
31 changes: 31 additions & 0 deletions packages/tools/src/utilities/getViewportsForAnnotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getEnabledElements, utilities as csUtils } from '@cornerstonejs/core';
import type { Annotation } from '../types';

const { isEqual } = csUtils;

/**
* Finds a all matching viewports in terms of the orientation of the annotation data
* and the frame of reference. This doesn't mean the annotation IS being displayed
* on these viewports, just that it could be by navigating the slice, and/or pan/zoom,
* without changing the orientation.
*
* @param annotation - Annotation to find the viewports that it could display in
* @returns All viewports to display in
*/
export default function getViewportsForAnnotation(annotation: Annotation) {
Copy link
Member

Choose a reason for hiding this comment

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

we have this already

packages/tools/src/utilities/getViewportForAnnotation.ts

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ViewportS (plural)
Later it looks for the viewport that is in a toolGroup with freehand tool

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Pushed a change to make getViewportForAnnotation call getViewportsForAnnotation removing duplicated code

const { metadata } = annotation;

return getEnabledElements()
.filter((enabledElement) => {
if (enabledElement.FrameOfReferenceUID === metadata.FrameOfReferenceUID) {
const viewport = enabledElement.viewport;
const { viewPlaneNormal, viewUp } = viewport.getCamera();
return (
isEqual(viewPlaneNormal, metadata.viewPlaneNormal) &&
(!metadata.viewUp || isEqual(viewUp, metadata.viewUp))
);
}
return;
})
.map((enabledElement) => enabledElement.viewport);
}