Skip to content

fix CircleROI tool for wsi #1940

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

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b9c9f50
check viewport type for voxel stats
mbellehumeur Mar 26, 2025
ca3fdb8
check imageData.getScalar() instead of viewport type
mbellehumeur Mar 27, 2025
3c6e74a
This will be fixed in wsiviewport worldToCanvas func
mbellehumeur Mar 27, 2025
0356835
Fix for isInsideModule?
mbellehumeur Mar 27, 2025
c6db595
revert
mbellehumeur Mar 27, 2025
f8b94a0
Fix wsiviewport deviceScaling
mbellehumeur Apr 2, 2025
64a7b95
Refactor ROI tools to check for scalar data before processing points …
mbellehumeur Apr 16, 2025
7f07ac4
Refactor WSIViewport to return typed scalar data
mbellehumeur Apr 16, 2025
bebdbd6
Remove unnecessary initialization of scalarData in getScalarData method
mbellehumeur Apr 16, 2025
9f286a5
Refactor scalar data handling in ROI tools to use CanvasScalarData ty…
mbellehumeur Apr 17, 2025
57c88c5
Refactor PlanarFreehandROITool to use getScalarValueFromWorld for sca…
mbellehumeur Apr 17, 2025
63336f6
Remove commented-out code in WSIViewport for canvas position transfor…
mbellehumeur Apr 17, 2025
5ded038
Fixed closing bracket not to exclude stats calculation
mbellehumeur Apr 17, 2025
99f2d2b
Refactor WSIViewport to replace getScalarData with _getScalarData
mbellehumeur Apr 20, 2025
048f9dd
Refactor ROI tools to check voxelManager length instead of image.scal…
mbellehumeur Apr 21, 2025
00bf564
Refactor ROI tools to use imageData.getScalarValueFromWorld for point…
mbellehumeur Apr 21, 2025
65d8149
Refactor ROI tools to use voxelManager for pointsInShape calculation
mbellehumeur Apr 21, 2025
b8fb6b2
Refactor WSIViewport to replace getScalarData with _getScalarData method
mbellehumeur Apr 21, 2025
73a6d4b
Refactor WSI Annotation Tools example to include Circle, Rectangle, a…
mbellehumeur Apr 21, 2025
6d21c80
fix: Display/hide area for color images correctly
wayfarer3130 Apr 21, 2025
88b5bfd
Merge branch 'fix-circleroi-wsi' of https://github.com/mbellehumeur/c…
wayfarer3130 Apr 21, 2025
613279f
fix: Update number validation in ROI tools to use AnnotationTool.isNu…
mbellehumeur May 28, 2025
5fb2331
fix: Update canvasToIndex method to return Point3 instead of Point2
mbellehumeur May 28, 2025
1fb803a
fix: Initialize WSIUtilFunctions and update indexToCanvas transformation
mbellehumeur May 28, 2025
474fb89
fix: Remove unused CanvasScalarData type and WSIUtilFunctions declara…
mbellehumeur May 28, 2025
cd18632
fix: Define CanvasScalarData type and initialize with default values …
mbellehumeur May 28, 2025
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
20 changes: 15 additions & 5 deletions packages/core/src/RenderingEngine/WSIViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
ViewportInput,
BoundsIJK,
CPUImageData,
PixelDataTypedArray,
} from '../types';
import uuidv4 from '../utilities/uuidv4';
import * as metaData from '../metaData';
Expand Down Expand Up @@ -228,8 +229,11 @@ class WSIViewport extends Viewport {
this.setProperties({});
}

protected getScalarData() {
return null;
public getScalarData() {
const scalarData = null as PixelDataTypedArray;
//scalarData.length = 0;
//scarlarData = [];
return scalarData;
}

public getImageData(): CPUIImageData {
Expand Down Expand Up @@ -272,7 +276,7 @@ class WSIViewport extends Viewport {
preScale: {
scaled: false,
},
scalarData: this.getScalarData(),
scalarData: null,
imageData,
// It is for the annotations to work, since all of them work on voxelManager and not on scalarData now
voxelManager: {
Expand Down Expand Up @@ -615,12 +619,18 @@ class WSIViewport extends Viewport {
protected canvasToIndex = (canvasPos: Point2): Point2 => {
const transform = this.getTransform();
transform.invert();
return transform.transformPoint(canvasPos);
return transform.transformPoint(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you use Math.abs on the result here - I think that will get us close to the right value, or on initial setup, figure out which attribute is negative and store a negative multiplier for it.

canvasPos.map((it) => it * devicePixelRatio) as Point2
);
// return transform.transformPoint(canvasPos);
};

protected indexToCanvas = (indexPos: Point2): Point2 => {
const transform = this.getTransform();
return transform.transformPoint(indexPos);
return transform
Copy link
Collaborator

Choose a reason for hiding this comment

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

Will need to convert back to negative values, so if you on initial setup store an index of which values are negative and multiple by that vector you should get the right sign.

.transformPoint(indexPos)
.map((it) => it / devicePixelRatio) as Point2;
// return transform.transformPoint(indexPos);
};

/** This can be implemented later when multi-slice WSI is supported */
Expand Down
35 changes: 22 additions & 13 deletions packages/tools/src/tools/annotation/CircleROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,19 +982,20 @@ class CircleROITool extends AnnotationTool {
pixelUnitsOptions
);

const pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
isInObject: (pointLPS) =>
pointInEllipse(ellipseObj, pointLPS, { fast: true }),
boundsIJK,
imageData,
returnPoints: this.configuration.storePointData,
}
);

let pointsInShape;
if (imageData.getScalarData() !== null) {
pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
isInObject: (pointLPS) =>
pointInEllipse(ellipseObj, pointLPS, { fast: true }),
boundsIJK,
imageData,
returnPoints: this.configuration.storePointData,
}
);
}
const stats = this.configuration.statsCalculator.getStatistics();

cachedStats[targetId] = {
Modality: metadata.Modality,
area,
Expand Down Expand Up @@ -1028,6 +1029,14 @@ class CircleROITool extends AnnotationTool {
};

_isInsideVolume = (index1, index2, dimensions) => {
// for wsiViewport
if (index1[1] < 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you put this fix into the WSIViewport itself?

index1[1] = -index1[1];
}
if (index2[1] < 0) {
index2[1] = -index2[1];
Comment on lines +1033 to +1038
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look good. Could you please check if the viewport type is WSI before proceeding? I imagine this might disrupt other types of viewports.

}

return (
csUtils.indexWithinDimensions(index1, dimensions) &&
csUtils.indexWithinDimensions(index2, dimensions)
Expand Down Expand Up @@ -1135,7 +1144,7 @@ function defaultGetTextLines(data, targetId): string[] {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}

if (max) {
if (max && isFinite(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}

Expand Down
26 changes: 14 additions & 12 deletions packages/tools/src/tools/annotation/EllipticalROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,17 +1144,19 @@ class EllipticalROITool extends AnnotationTool {
pixelUnitsOptions
);

const pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
boundsIJK,
imageData,
isInObject: (pointLPS) =>
pointInEllipse(ellipseObj, pointLPS, { fast: true }),
returnPoints: this.configuration.storePointData,
}
);

let pointsInShape;
if (imageData.getScalarData() !== null) {
pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
boundsIJK,
imageData,
isInObject: (pointLPS) =>
pointInEllipse(ellipseObj, pointLPS, { fast: true }),
returnPoints: this.configuration.storePointData,
}
);
}
const stats = this.configuration.statsCalculator.getStatistics();
cachedStats[targetId] = {
Modality: metadata.Modality,
Expand Down Expand Up @@ -1248,7 +1250,7 @@ function defaultGetTextLines(data, targetId): string[] {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}

if (max) {
if (max && isFinite(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}

Expand Down
87 changes: 44 additions & 43 deletions packages/tools/src/tools/annotation/PlanarFreehandROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,49 +914,50 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
let curRow = 0;
let intersections = [];
let intersectionCounter = 0;
const pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
imageData,
isInObject: (pointLPS, _pointIJK) => {
let result = true;
const point = viewport.worldToCanvas(pointLPS);
if (point[1] != curRow) {
intersectionCounter = 0;
curRow = point[1];
intersections = getLineSegmentIntersectionsCoordinates(
canvasCoordinates,
point,
[canvasPosEnd[0], point[1]]
);
intersections.sort(
(function (index) {
return function (a, b) {
return a[index] === b[index]
? 0
: a[index] < b[index]
? -1
: 1;
};
})(0)
);
}
if (intersections.length && point[0] > intersections[0][0]) {
intersections.shift();
intersectionCounter++;
}
if (intersectionCounter % 2 === 0) {
result = false;
}
return result;
},
boundsIJK,
returnPoints: this.configuration.storePointData,
}
);

let pointsInShape;
if (imageData.getScalarData() !== null) {
pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
imageData,
isInObject: (pointLPS, _pointIJK) => {
let result = true;
const point = viewport.worldToCanvas(pointLPS);
if (point[1] != curRow) {
intersectionCounter = 0;
curRow = point[1];
intersections = getLineSegmentIntersectionsCoordinates(
canvasCoordinates,
point,
[canvasPosEnd[0], point[1]]
);
intersections.sort(
(function (index) {
return function (a, b) {
return a[index] === b[index]
? 0
: a[index] < b[index]
? -1
: 1;
};
})(0)
);
}
if (intersections.length && point[0] > intersections[0][0]) {
intersections.shift();
intersectionCounter++;
}
if (intersectionCounter % 2 === 0) {
result = false;
}
return result;
},
boundsIJK,
returnPoints: this.configuration.storePointData,
}
);
}
const stats = this.configuration.statsCalculator.getStatistics();

cachedStats[targetId] = {
Modality: metadata.Modality,
area,
Expand Down Expand Up @@ -1085,7 +1086,7 @@ function defaultGetTextLines(data, targetId): string[] {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}

if (Number.isFinite(max)) {
if (Number.isFinite(max) && !isNaN(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}

Expand Down
42 changes: 28 additions & 14 deletions packages/tools/src/tools/annotation/RectangleROITool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,16 +930,18 @@ class RectangleROITool extends AnnotationTool {
pixelUnitsOptions
);

const pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
boundsIJK,
imageData,
returnPoints: this.configuration.storePointData,
}
);
let pointsInShape;
if (imageData.getScalarData() !== null) {
pointsInShape = voxelManager.forEach(
this.configuration.statsCalculator.statsCallback,
{
boundsIJK,
imageData,
returnPoints: this.configuration.storePointData,
}
);
}
const stats = this.configuration.statsCalculator.getStatistics();

cachedStats[targetId] = {
Modality: metadata.Modality,
area,
Expand Down Expand Up @@ -968,6 +970,14 @@ class RectangleROITool extends AnnotationTool {
};

_isInsideVolume = (index1, index2, dimensions) => {
// for wsiViewport
if (index1[1] < 0) {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look good. Could you please check if the viewport type is WSI before proceeding? I imagine this might disrupt other types of viewports.

index1[1] = -index1[1];
}
if (index2[1] < 0) {
index2[1] = -index2[1];
}

return (
csUtils.indexWithinDimensions(index1, dimensions) &&
csUtils.indexWithinDimensions(index2, dimensions)
Expand Down Expand Up @@ -1048,12 +1058,16 @@ function defaultGetTextLines(data, targetId: string): string[] {
}

const textLines: string[] = [];

textLines.push(`Area: ${csUtils.roundNumber(area)} ${areaUnit}`);
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);

if (isFinite(mean) && !isNaN(mean)) {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}
if (isFinite(max) && !isNaN(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}
if (isFinite(stdDev) && !isNaN(stdDev)) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
}
return textLines;
}

Expand Down