Skip to content

fix: clipper2 intersection definition#2742

Merged
sedghi merged 22 commits into
mainfrom
fix/clipper2-intersection-definition
Jul 2, 2026
Merged

fix: clipper2 intersection definition#2742
sedghi merged 22 commits into
mainfrom
fix/clipper2-intersection-definition

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Context

Intersections with holes are not working consistently. This PR builds on top of changes from:
#2739
to see if we can figure out a reasonable solution for this.

Changes & Results

Testing

Checklist

PR

  • [] My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • [] My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • [] The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • [] "OS:
  • [] "Node version:
  • [] "Browser:

Summary by CodeRabbit

Release Notes

  • New Features

    • Stroke-based contour editing with add and remove operations
    • Support for complex contours with holes
    • Enhanced polygon boolean operations (Union, Difference, Intersection, XOR)
  • Documentation

    • Added detailed behavioral specifications for contour editing and topology handling
  • Tests

    • Added comprehensive test coverage for boolean operations and contour interactions
  • Chores

    • Added geometry processing library dependency

GhadeerAlbattarni and others added 7 commits May 21, 2026 10:44
…t intersection

The intersectPolylinesSets function returned empty results when one
polyline was entirely contained inside the other (no edge crossings).

- When A is fully inside B (isContourHole), the intersection is A
- When B is fully inside A, the intersection is B
…lly enclosed in minuend

Subtracting a shape that was fully enclosed by another (e.g. small sphere
inside big sphere) had no effect and returned the original shape unchanged.
The subtract operation now correctly handles all spatial relationships and
preserves holes through chained operations.

Changes:
- polylineInfoTypes: add optional holePolylines field to both world and
  canvas polyline info types
- polylineSubtract: handle B-inside-A by cutting a hole, A-inside-B by
  removing entirely, and skip adding a hole when B is already inside an existing
  hole (no-op). Propagate existing holes through edge-crossing subtractions
- logicalOperators: load hole polylines from child annotations when reading
  a segment, propagate them through canvas/world space conversions, and
  carry them back to world space in the operation result
- addPolylinesToSegmentation: save hole polylines as proper child
  annotations with correct winding direction
Added an isTargetInsideSource flag to checkIntersection so that both
containment directions (source inside target, target inside source) are
detected in one place, rather than scattered containsPoints calls across
each caller.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@wayfarer3130

Copy link
Copy Markdown
Collaborator Author

@dan-rukas and @sedghi
CC: @GhadeerAlbattarni and @jbocce

I was looking at Ghadeer's changes for union/intersection and running into some usability and performance issues with them, and I realized that the area isn't sufficiently well defined to really know what all we are doing, so with clod, I wrote a semantic behaviour document around the contours:
https://github.com/cornerstonejs/cornerstone3D/blob/da64bcc24d955c9cf1dae5c6c505db498f8e3e41/packages/tools/src/utilities/contourSegmentation/SEMANTICS.md

As well, I had it change to use clipper2-ts instead of a custom implementation to try to address some of the performance concerns.

I think the semantics changes are mostly reasonable - it basically says that only the source/basis contour gets affected, and only when there is an overlap to start with. I'm not completely comfortable with the definition on that yet, since I think that a union with a contour that is entirely within a hole SHOULD get included. I'm going to update that.

There is also some question about the user interface in OHIF - I think the union/intersect/difference should be three buttons with a cancel operation, and the whole dialog should have a help button explaining that it applies to the CURRENT view only, and there should be a progress dialog/indication that something has been applied.

Then, the current self-intersect behaviour is very confusion - I have updated the definitions for that to be derived from the union and intersection definitions, and added specific add/remove behaviour that is independent of holes/location of start.

@jbocce

jbocce commented May 25, 2026

Copy link
Copy Markdown
Collaborator

@dan-rukas and @sedghi CC: @GhadeerAlbattarni and @jbocce

I was looking at Ghadeer's changes for union/intersection and running into some usability and performance issues with them, and I realized that the area isn't sufficiently well defined to really know what all we are doing, so with clod, I wrote a semantic behaviour document around the contours: https://github.com/cornerstonejs/cornerstone3D/blob/da64bcc24d955c9cf1dae5c6c505db498f8e3e41/packages/tools/src/utilities/contourSegmentation/SEMANTICS.md

As well, I had it change to use clipper2-ts instead of a custom implementation to try to address some of the performance concerns.

I think the semantics changes are mostly reasonable - it basically says that only the source/basis contour gets affected, and only when there is an overlap to start with. I'm not completely comfortable with the definition on that yet, since I think that a union with a contour that is entirely within a hole SHOULD get included. I'm going to update that.

There is also some question about the user interface in OHIF - I think the union/intersect/difference should be three buttons with a cancel operation, and the whole dialog should have a help button explaining that it applies to the CURRENT view only, and there should be a progress dialog/indication that something has been applied.

Then, the current self-intersect behaviour is very confusion - I have updated the definitions for that to be derived from the union and intersection definitions, and added specific add/remove behaviour that is independent of holes/location of start.

@dan-rukas, @sedghi and @wayfarer3130
CC: @GhadeerAlbattarni

Thanks for this info @wayfarer3130. It sounds to me then that we should discuss this further, but it very much sounds like for 3.13 we should stay the course as far as contour logical operations are concerned and revisit this in the next release.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Replaces all manual polyline merge/subtract/intersect/XOR algorithms with a clipper2-ts-backed boolean-operations pipeline. A new PolygonWithHoles/BooleanOp abstraction wraps Clipper2, a runBooleanOpByView orchestration layer enforces view-reference isolation, hole polylines propagate through all annotation pipelines, and new addContourStroke/removeContourStroke entry points drive cursor gestures. A formal SEMANTICS.md specification is added.

Changes

Clipper2 Boolean Operations Overhaul

Layer / File(s) Summary
Clipper2 dependency and pure-geometry boolean ops
packages/tools/package.json, packages/tools/src/utilities/contourSegmentation/clipperBooleanOps.ts, packages/tools/src/utilities/contourSegmentation/clipperBooleanOps.spec.ts
Adds clipper2-ts v2.0.1. Defines PolygonWithHoles, BooleanOp enum, Clipper path converters, PolyTreeD walker, empty-input fast paths, and applyBoolean. Tests cover all four operations, hole topology, edge guarantees, and deep-nesting flattening.
Hole-aware data types and view-grouped boolean orchestration
packages/tools/src/utilities/contourSegmentation/polylineInfoTypes.ts, packages/tools/src/utilities/contourSegmentation/polylineSetOps.ts, packages/tools/src/utilities/contourSegmentation/polylineSetOps.spec.ts
Extends PolylineInfoWorld and PolylineInfoCanvas with optional holePolylines. Introduces runBooleanOpByView, which groups by viewReference, runs applyBoolean per group, and applies per-op inclusion rules. Tests cover cross-view isolation, passthrough, commutativity, hole survival, and empty-input cases.
Set operations migrated to runBooleanOpByView
packages/tools/src/utilities/contourSegmentation/polylineSubtract.ts, polylineUnify.ts, polylineXor.ts, polylineIntersect.ts
Rewrites subtractPolylineSets, unifyPolylineSets, xorPolylinesSets, and intersectPolylinesSets to delegate to runBooleanOpByView. Removes manual pairwise loops, identity checks, and geometry-helper cleanup chains.
Hole propagation in sharedOperations, logicalOperators, and mergeMultipleAnnotations
packages/tools/src/utilities/contourSegmentation/sharedOperations.ts, logicalOperators.ts, mergeMultipleAnnotations.ts
Extends checkIntersection with isTargetInsideSource. Rewrites combinePolylines to build PolygonWithHoles subjects/clips, run applyBoolean, remove old annotations, and recreate parent+child hole annotations. Adds windingDirection to createNewAnnotationFromPolyline. Propagates holePolylines through canvas/world conversions. Rewrites processSequentialIntersections with the same pipeline.
Cursor-driven stroke add/remove and hole annotation creation
packages/tools/src/utilities/contourSegmentation/applyContourStroke.ts, addPolylinesToSegmentation.ts
Adds addContourStroke (Union) and removeContourStroke (Difference): validates tool registration, collects same-plane/segment parent targets, runs applyBoolean, removes replaced annotations, and recreates parent+hole annotations. Refactors addPolylinesToSegmentation to create hole annotations with counterclockwise winding via a new createContourSegmentationAnnotation factory.
Event listener simplification and behavioral specification
packages/tools/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts, packages/tools/src/utilities/contourSegmentation/SEMANTICS.md
Simplifies the completion listener to dispatch to addContourStroke/removeContourStroke based on contourHoleProcessingEnabled. Adds SEMANTICS.md with a formal model, universal invariants, cursor interaction semantics, toolbar operation semantics, a 10-case operation matrix, and an implementation-notes section.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200, 220, 255, 0.5)
    Note over contourSegmentationCompleted,applyStroke: Cursor gesture (add or remove stroke)
  end
  participant contourSegmentationCompleted
  participant addContourStroke
  participant removeContourStroke
  participant applyStroke
  participant collectSamePlaneSegmentTargets
  participant applyBoolean
  participant removeAnnotationCompletely
  participant createNewAnnotationFromPolyline

  contourSegmentationCompleted->>contourSegmentationCompleted: read contourHoleProcessingEnabled
  alt contourHoleProcessingEnabled
    contourSegmentationCompleted->>removeContourStroke: viewport, sourceAnnotation
    removeContourStroke->>applyStroke: viewport, sourceAnnotation, BooleanOp.Difference
  else
    contourSegmentationCompleted->>addContourStroke: viewport, sourceAnnotation
    addContourStroke->>applyStroke: viewport, sourceAnnotation, BooleanOp.Union
  end
  applyStroke->>applyStroke: convert stroke to canvas space
  applyStroke->>collectSamePlaneSegmentTargets: viewport, sourceAnnotation
  collectSamePlaneSegmentTargets-->>applyStroke: target annotations[]
  applyStroke->>applyBoolean: subjects[targets+holes], clips[stroke], op
  applyBoolean-->>applyStroke: PolygonWithHoles[]
  applyStroke->>removeAnnotationCompletely: source + all replaced targets + holes
  loop each result polygon
    applyStroke->>createNewAnnotationFromPolyline: outer ring (Clockwise)
    loop each hole
      applyStroke->>createNewAnnotationFromPolyline: hole ring (CounterClockwise)
    end
  end
  applyStroke->>contourSegmentationCompleted: ANNOTATION_CUT_MERGE_PROCESS_COMPLETED event
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Hop hop, the rabbit clips and snips,
Old hand-rolled loops now take their dips.
Clipper2 draws the boolean art,
Holes preserved in every part.
Union, Diff, XOR, Intersect—
Each polygon emerges correct!
A warren of shapes, all neatly checked. ✂️

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. It provides only a brief context referencing PR #2739 and mentions 'intersections with holes' but lacks details on changes made, results/effects, testing instructions, and missing all completed checklist items as required by the template. Complete all required sections: provide a detailed list of changes and their effects, explain how to test the changes, and mark all checklist items as completed or explicitly note which items cannot be satisfied.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: clipper2 intersection definition' is vague and does not clearly describe the primary changes. While the PR does involve clipper2 and intersections, the title omits the major refactoring of contour segmentation operations including holes, semantic definitions, and multiple utility functions. Consider using a more descriptive title such as 'fix: refactor contour segmentation with clipper2-ts and hole support' or 'fix: implement clipper2-based boolean operations for contour segmentation with holes' to better reflect the scope of changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clipper2-intersection-definition
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/clipper2-intersection-definition

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/tools/src/utilities/contourSegmentation/SEMANTICS.md (1)

22-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Missing language specifier on code fence.

The code fence lacks a language identifier. While this doesn't affect correctness, adding typescript or ts after the opening backticks improves syntax highlighting and tooling support.

📝 Suggested fix
-```
+```typescript
 type Polygon = {
   outer: Point2[];       // simple closed polyline, CW, ≥ 3 vertices
   holes?: Point2[][];    // each simple closed, CCW, fully inside outer,
                          // pairwise non-overlapping
 };
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @packages/tools/src/utilities/contourSegmentation/SEMANTICS.md around lines
22 - 28, The code fence in the SEMANTICS.md file containing the Polygon type
definition lacks a language identifier. Add the language specifier typescript
immediately after the opening triple backticks ( ) at the beginning of
the code block that defines the Polygon type with outer and holes properties to
enable proper syntax highlighting and tooling support.


</details>

<!-- cr-comment:v1:bd1369eb6fa2d34baf3520c5 -->

_Source: Linters/SAST tools_

</blockquote></details>
<details>
<summary>packages/tools/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts (1)</summary><blockquote>

`19-57`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_

**Simplified listener delegates correctly to boolean operations.**

The refactoring successfully removes inline intersection detection and delegates to `removeContourStroke`/`addContourStroke`, which map to Difference and Union operations per the context snippets. The documentation clearly explains the polarity model and references the formal specification.

One observation: the stroke application calls (`removeContourStroke`/`addContourStroke`) have no explicit error handling. If those functions throw during boolean operations, this listener will propagate the exception, and `ANNOTATION_CUT_MERGE_PROCESS_COMPLETED` won't fire. This may be acceptable if the caller is expected to handle errors, but consider whether defensive error handling would improve resilience.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/tools/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts`
around lines 19 - 57, The calls to removeContourStroke and addContourStroke in
the contourSegmentationCompletedListener function lack error handling, which
means any exceptions thrown during boolean operations will propagate and prevent
the ANNOTATION_CUT_MERGE_PROCESS_COMPLETED event from being triggered. Add a
try-catch block around the conditional logic that calls removeContourStroke or
addContourStroke to catch any potential errors, log them appropriately, and
determine whether the triggerEvent call should still execute (e.g., ensure
resilience by still notifying listeners even if the operation partially fails,
or handle the error more explicitly depending on your application's
requirements).
```

</details>

<!-- cr-comment:v1:ab07bc4166b976181e06b0f1 -->

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @packages/tools/src/utilities/contourSegmentation/applyContourStroke.ts:

  • Around line 123-140: The triggerAnnotationModified(parent, element) call is
    firing before the holes are processed and linked to the parent annotation. Move
    the triggerAnnotationModified(parent, element) statement from its current
    position (before the polygon.holes?.forEach loop) to after the loop completes,
    so that listeners observe the parent with all its child holes already attached
    via addChildAnnotation(parent, hole). This ensures the annotation modified event
    is emitted only after the parent structure is fully updated.
  • Around line 63-68: When the guard condition fails and the function returns
    early from removeContourStroke because DEFAULT_CONTOUR_SEG_TOOL_NAME is not
    registered, the source annotation from the failed subtract operation remains in
    state, leaving invalid additive geometry. Before the early return statement in
    this guard block, remove or discard the source annotation that was created for
    the Difference path to ensure proper cleanup and prevent the incomplete cut
    annotation from persisting in the state.

In @packages/tools/src/utilities/contourSegmentation/polylineSubtract.ts:

  • Around line 47-56: The toInfo function is only converting the parent contour
    polyline and ignoring the child holes from the annotation. Modify the toInfo
    function to also extract and convert any child holes from the
    annotation.data.contour, convert each hole's polyline to canvas space using
    convertContourPolylineToCanvasSpace (similar to how the parent polyline is
    converted), and populate the holePolylines array field in the returned
    PolylineInfoCanvas object so that annotations with holes are properly
    represented when passed to subtractPolylineSets.

In @packages/tools/src/utilities/contourSegmentation/polylineUnify.ts:

  • Around line 48-56: The toInfo function inside unifyAnnotationPolylines is not
    preserving child holes from the annotation when building the PolylineInfoCanvas
    object. To fix this, extract the holes array from annotation.data.contour (in
    addition to the main polyline), convert each hole to canvas space using
    convertContourPolylineToCanvasSpace with the viewport parameter, and include the
    converted holes in the returned PolylineInfoCanvas object so that
    BooleanOp.Union can preserve them during the union operation.

In @packages/tools/src/utilities/contourSegmentation/sharedOperations.ts:

  • Around line 101-109: The hasIntersection return value on line 107 does not
    account for the case where the target polyline is fully contained inside the
    source polyline. Update the hasIntersection assignment to also include the
    isTargetInsideSource condition so that it returns true when there is a
    containment overlap, not just when there are line segment intersections or
    contour holes. This ensures callers checking hasIntersection will properly
    detect all types of overlaps between the source and target polylines.

Nitpick comments:
In
@packages/tools/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts:

  • Around line 19-57: The calls to removeContourStroke and addContourStroke in
    the contourSegmentationCompletedListener function lack error handling, which
    means any exceptions thrown during boolean operations will propagate and prevent
    the ANNOTATION_CUT_MERGE_PROCESS_COMPLETED event from being triggered. Add a
    try-catch block around the conditional logic that calls removeContourStroke or
    addContourStroke to catch any potential errors, log them appropriately, and
    determine whether the triggerEvent call should still execute (e.g., ensure
    resilience by still notifying listeners even if the operation partially fails,
    or handle the error more explicitly depending on your application's
    requirements).

In @packages/tools/src/utilities/contourSegmentation/SEMANTICS.md:

  • Around line 22-28: The code fence in the SEMANTICS.md file containing the
    Polygon type definition lacks a language identifier. Add the language specifier
    typescript immediately after the opening triple backticks ( ) at the
    beginning of the code block that defines the Polygon type with outer and holes
    properties to enable proper syntax highlighting and tooling support.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `373be3c4-a334-4788-93ff-7219b3a4d1a1`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9cc703acb396d12b37fdc8af4727218574494caa and c8bf188ccbe7ac253d7c6ea543fa874204c11cb5.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `bun.lock` is excluded by `!**/*.lock`
* `yarn.lock` is excluded by `!**/yarn.lock`, `!**/*.lock`

</details>

<details>
<summary>📒 Files selected for processing (17)</summary>

* `packages/tools/package.json`
* `packages/tools/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts`
* `packages/tools/src/utilities/contourSegmentation/SEMANTICS.md`
* `packages/tools/src/utilities/contourSegmentation/addPolylinesToSegmentation.ts`
* `packages/tools/src/utilities/contourSegmentation/applyContourStroke.ts`
* `packages/tools/src/utilities/contourSegmentation/clipperBooleanOps.spec.ts`
* `packages/tools/src/utilities/contourSegmentation/clipperBooleanOps.ts`
* `packages/tools/src/utilities/contourSegmentation/logicalOperators.ts`
* `packages/tools/src/utilities/contourSegmentation/mergeMultipleAnnotations.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineInfoTypes.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineIntersect.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineSetOps.spec.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineSetOps.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineSubtract.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineUnify.ts`
* `packages/tools/src/utilities/contourSegmentation/polylineXor.ts`
* `packages/tools/src/utilities/contourSegmentation/sharedOperations.ts`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread packages/tools/src/utilities/contourSegmentation/polylineUnify.ts Outdated
@wayfarer3130 wayfarer3130 requested a review from sedghi June 24, 2026 17:01
Comment thread packages/tools/src/utilities/contourSegmentation/SEMANTICS.md
Comment thread packages/tools/package.json
sedghi and others added 4 commits June 30, 2026 15:37
@sedghi sedghi merged commit 48539da into main Jul 2, 2026
14 checks passed
@sedghi sedghi deleted the fix/clipper2-intersection-definition branch July 2, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants