Skip to content

Commit

Permalink
fix(livewire): polyline and handles (winding direction)
Browse files Browse the repository at this point in the history
* fix(livewire): polyline and handles (winding direction)

* fixed lint issues

* update-api
  • Loading branch information
lscoder authored Jun 3, 2024
1 parent 2595cb6 commit 11a767d
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 46 deletions.
9 changes: 5 additions & 4 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3404,15 +3404,15 @@ export class LivewireContourTool extends ContourSegmentationBaseTool {
lastCanvasPoint?: Types_2.Point2;
confirmedPath?: LivewirePath;
currentPath?: LivewirePath;
confirmedPathRight?: LivewirePath;
confirmedPathNext?: LivewirePath;
closed?: boolean;
worldToSlice?: (point: Types_2.Point3) => Types_2.Point2;
sliceToWorld?: (point: Types_2.Point2) => Types_2.Point3;
originalPath?: Types_2.Point3[];
contourHoleProcessingEnabled?: boolean;
} | null;
// (undocumented)
editHandle(worldPos: Types_2.Point3, element: any, annotation: any, handleIndex: number): void;
editHandle(worldPos: Types_2.Point3, element: any, annotation: LivewireContourAnnotation, handleIndex: number): void;
// (undocumented)
_endCallback: (evt: EventTypes_2.InteractionEventType, clearAnnotation?: boolean) => void;
// (undocumented)
Expand Down Expand Up @@ -3440,9 +3440,9 @@ export class LivewireContourTool extends ContourSegmentationBaseTool {
// (undocumented)
protected scissors: LivewireScissors;
// (undocumented)
protected scissorsRight: LivewireScissors;
protected scissorsNext: LivewireScissors;
// (undocumented)
protected setupBaseEditData(worldPos: any, element: any, annotation: any, rightPos?: any, contourHoleProcessingEnabled?: any): void;
protected setupBaseEditData(worldPos: any, element: any, annotation: any, nextPos?: any, contourHoleProcessingEnabled?: any): void;
// (undocumented)
static toolName: string;
// (undocumented)
Expand Down Expand Up @@ -6074,6 +6074,7 @@ function updateContourPolyline(annotation: ContourAnnotation, polylineData: {
targetWindingDirection?: ContourWindingDirection;
}, transforms: {
canvasToWorld: (point: Types_2.Point2) => Types_2.Point3;
worldToCanvas: (point: Types_2.Point3) => Types_2.Point2;
}, options?: {
decimate?: {
enabled?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,17 +216,26 @@ export function createPolylineHole(
const { windingDirection: holeWindingDirection } =
holeAnnotation.data.contour;

// Check if both normals are pointing to the same direction because the
// polyline for the hole needs to be in a different direction
// if (glMatrix.equals(1, dotNormals)) {
if (targetWindingDirection === holeWindingDirection) {
holeAnnotation.data.contour.polyline.reverse();
holeAnnotation.data.contour.windingDirection = targetWindingDirection * -1;
}

addChildAnnotation(targetAnnotation, holeAnnotation);
contourSegUtils.removeContourSegmentationAnnotation(holeAnnotation);

const { contour: holeContour } = holeAnnotation.data;
const holePolyline = convertContourPolylineToCanvasSpace(
holeContour.polyline,
viewport
);

// Calling `updateContourPolyline` method instead of reversing the polyline
// locally because it is also responsible for checking/fixing the winding direction.
contourUtils.updateContourPolyline(
holeAnnotation,
{
points: holePolyline,
closed: holeContour.closed,
},
viewport
);

const { element } = viewport;
const enabledElement = getEnabledElement(element);
const { renderingEngine } = enabledElement;
Expand Down Expand Up @@ -393,7 +402,7 @@ function combinePolylines(
};

// Calling `updateContourPolyline` method instead of setting it locally
// because it is also responsible for checking/setting the winding direction.
// because it is also responsible for checking/fixing the winding direction.
contourUtils.updateContourPolyline(
newAnnotation,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class LivewireContourSegmentationTool extends LivewireContourTool {
// Now, update the rendering
this.updateAnnotation(acceptedPath);
this.scissors = null;
this.scissorsRight = null;
this.scissorsNext = null;
this.editData = null;
annotation.data.handles.interpolationSources = null;

Expand Down
53 changes: 28 additions & 25 deletions packages/tools/src/tools/annotation/LivewireContourTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const CLICK_CLOSE_CURVE_SQR_DIST = 10 ** 2; // px
class LivewireContourTool extends ContourSegmentationBaseTool {
public static toolName: string;
protected scissors: LivewireScissors;
/** The scissors from the right handle, used for editing */
protected scissorsRight: LivewireScissors;
/** The scissors from the next handle, used for editing */
protected scissorsNext: LivewireScissors;

touchDragCallback: any;
mouseDragCallback: any;
Expand All @@ -53,7 +53,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
confirmedPath?: LivewirePath;
currentPath?: LivewirePath;
/** The next path segment, on the other side of the handle */
confirmedPathRight?: LivewirePath;
confirmedPathNext?: LivewirePath;
closed?: boolean;
worldToSlice?: (point: Types.Point3) => Types.Point2;
sliceToWorld?: (point: Types.Point2) => Types.Point3;
Expand Down Expand Up @@ -141,7 +141,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
worldPos,
element,
annotation,
rightPos?,
nextPos?,
contourHoleProcessingEnabled?
) {
const enabledElement = getEnabledElement(element);
Expand Down Expand Up @@ -217,21 +217,21 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
height,
voiRange
);
if (rightPos) {
this.scissorsRight = LivewireScissors.createInstanceFromRawPixelData(
if (nextPos) {
this.scissorsNext = LivewireScissors.createInstanceFromRawPixelData(
scalarData as Float32Array,
width,
height,
voiRange
);
this.scissorsRight.startSearch(worldToSlice(rightPos));
this.scissorsNext.startSearch(worldToSlice(nextPos));
}

// Scissors always start at the startPos for both editing handles and
// for initial rendering
this.scissors.startSearch(startPos);

const newAnnotation = !rightPos;
const newAnnotation = !nextPos;

const confirmedPath = new LivewirePath();
const currentPath = new LivewirePath();
Expand All @@ -255,7 +255,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
lastCanvasPoint,
confirmedPath,
currentPath,
confirmedPathRight: currentPathNext,
confirmedPathNext: currentPathNext,
closed: false,
handleIndex:
this.editData?.handleIndex ?? annotation.handles?.activeHandleIndex,
Expand Down Expand Up @@ -467,7 +467,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
protected clearEditData() {
this.editData = null;
this.scissors = null;
this.scissorsRight = null;
this.scissorsNext = null;
this.isDrawing = false;
}

Expand Down Expand Up @@ -628,7 +628,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
public editHandle(
worldPos: Types.Point3,
element,
annotation,
annotation: LivewireContourAnnotation,
handleIndex: number
) {
const { data } = annotation;
Expand All @@ -638,11 +638,11 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
handlePoints[(handleIndex - 1 + numHandles) % numHandles];
const nextHandle = handlePoints[(handleIndex + 1) % numHandles];

if (!this.editData?.confirmedPathRight) {
if (!this.editData?.confirmedPathNext) {
this.setupBaseEditData(previousHandle, element, annotation, nextHandle);
const { polyline } = data.contour;
const confirmedPath = new LivewirePath();
const confirmedPathRight = new LivewirePath();
const confirmedPathNext = new LivewirePath();
const { worldToSlice } = this.editData;
const previousIndex = findHandlePolylineIndex(
annotation,
Expand All @@ -660,23 +660,19 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
// For this case, the next/previous indices are swapped, and the
// path data gets inserted in between the newly generated data, so
// handle this case specially
confirmedPathRight.addPoints(
confirmedPathNext.addPoints(
polyline.slice(nextIndex + 1, previousIndex).map(worldToSlice)
);
} else if (nextIndex < previousIndex) {
throw new Error(
`Expected right index after left index, but were: ${previousIndex} ${nextIndex}`
);
} else {
confirmedPath.addPoints(
polyline.slice(0, previousIndex + 1).map(worldToSlice)
);
confirmedPathRight.addPoints(
confirmedPathNext.addPoints(
polyline.slice(nextIndex, polyline.length).map(worldToSlice)
);
}
this.editData.confirmedPath = confirmedPath;
this.editData.confirmedPathRight = confirmedPathRight;
this.editData.confirmedPathNext = confirmedPathNext;
}
const { editData, scissors } = this;
const { worldToSlice, sliceToWorld } = editData;
Expand All @@ -702,7 +698,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
handlePoints[handleIndex] = sliceToWorld(slicePos);

const pathPointsLeft = scissors.findPathToPoint(slicePos);
const pathPointsRight = this.scissorsRight.findPathToPoint(slicePos);
const pathPointsRight = this.scissorsNext.findPathToPoint(slicePos);
const currentPath = new LivewirePath();

// Merge the "confirmed" path that goes from the first control point to the
Expand All @@ -713,7 +709,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
currentPath.addPoints(pathPointsLeft);
}
currentPath.addPoints(pathPointsRight.reverse());
currentPath.appendPath(editData.confirmedPathRight);
currentPath.appendPath(editData.confirmedPathNext);
if (handleIndex === 0) {
currentPath.addPoints(pathPointsLeft);
}
Expand Down Expand Up @@ -928,22 +924,29 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
return;
}

const { annotation, sliceToWorld } = this.editData;
const { annotation, sliceToWorld, worldToSlice, closed, newAnnotation } =
this.editData;
let { pointArray: imagePoints } = livewirePath;

if (imagePoints.length > 1) {
imagePoints = [...imagePoints, imagePoints[0]];
}

// Save the annotation in clockwise winding direction only after closing it
// because reversing the handle points may cause some weird issues
const targetWindingDirection =
newAnnotation && closed ? ContourWindingDirection.Clockwise : undefined;

this.updateContourPolyline(
annotation,
{
points: imagePoints,
closed: annotation.data.contour.closed,
targetWindingDirection: ContourWindingDirection.Clockwise,
closed,
targetWindingDirection,
},
{
canvasToWorld: sliceToWorld,
worldToCanvas: worldToSlice,
}
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/tools/src/tools/base/ContourBaseTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ abstract class ContourBaseTool extends AnnotationTool {
},
transforms: {
canvasToWorld: (point: Types.Point2) => Types.Point3;
worldToCanvas: (point: Types.Point3) => Types.Point2;
}
) {
const decimateConfig = this.configuration?.decimate || {};
Expand Down
23 changes: 19 additions & 4 deletions packages/tools/src/utilities/contours/updateContourPolyline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default function updateContourPolyline(
},
transforms: {
canvasToWorld: (point: Types.Point2) => Types.Point3;
worldToCanvas: (point: Types.Point3) => Types.Point2;
},
options?: {
decimate?: {
Expand All @@ -38,7 +39,7 @@ export default function updateContourPolyline(
};
}
) {
const { canvasToWorld } = transforms;
const { canvasToWorld, worldToCanvas } = transforms;
const { data } = annotation;
const { targetWindingDirection } = polylineData;
let { points: polyline } = polylineData;
Expand All @@ -54,7 +55,8 @@ export default function updateContourPolyline(
let { closed } = polylineData;
const numPoints = polyline.length;
const polylineWorldPoints = new Array(numPoints);
const currentWindingDirection = math.polyline.getWindingDirection(polyline);
const currentPolylineWindingDirection =
math.polyline.getWindingDirection(polyline);
const parentAnnotation = getParentAnnotation(annotation) as ContourAnnotation;

if (closed === undefined) {
Expand All @@ -79,11 +81,24 @@ export default function updateContourPolyline(
: targetWindingDirection;

if (windingDirection === undefined) {
windingDirection = currentWindingDirection;
} else if (windingDirection !== currentWindingDirection) {
windingDirection = currentPolylineWindingDirection;
}

if (windingDirection !== currentPolylineWindingDirection) {
polyline.reverse();
}

const handlePoints = data.handles.points.map((p) => worldToCanvas(p));

if (handlePoints.length > 2) {
const currentHandlesWindingDirection =
math.polyline.getWindingDirection(handlePoints);

if (currentHandlesWindingDirection !== windingDirection) {
data.handles.points.reverse();
}
}

for (let i = 0; i < numPoints; i++) {
polylineWorldPoints[i] = canvasToWorld(polyline[i]);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/tools/src/utilities/math/polyline/getSignedArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import type { Types } from '@cornerstonejs/core';
* https://www.youtube.com/watch?v=GpsKrAipXm8&t=1900s
*
* This functions has a runtime very close to `getArea` and it is recommended to
* be called only if you need the area signal (eg: calculate polygon normal). If
* you do not need the area signal you should always call `getArea`.
*
* be called only if you need the area signal (eg: calculate polygon normal or
* winding direction). If you do not need the area signal you should always call
* `getArea`.
*
* @param polyline - Polyline points (2D)
* @returns Area of the polyline (with signal)
Expand Down

0 comments on commit 11a767d

Please sign in to comment.