@cornerstonejs/tools 1.81.2 → 1.81.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/tools/CrosshairsTool.js +5 -5
- package/dist/cjs/tools/CrosshairsTool.js.map +1 -1
- package/dist/cjs/tools/annotation/PlanarFreehandROITool.d.ts +19 -0
- package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +143 -111
- package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
- package/dist/cjs/utilities/getCalibratedUnits.js +12 -3
- package/dist/cjs/utilities/getCalibratedUnits.js.map +1 -1
- package/dist/esm/tools/CrosshairsTool.js +5 -5
- package/dist/esm/tools/CrosshairsTool.js.map +1 -1
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js +142 -110
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
- package/dist/esm/utilities/getCalibratedUnits.js +12 -3
- package/dist/esm/utilities/getCalibratedUnits.js.map +1 -1
- package/dist/types/tools/CrosshairsTool.d.ts.map +1 -1
- package/dist/types/tools/annotation/PlanarFreehandROITool.d.ts +19 -0
- package/dist/types/tools/annotation/PlanarFreehandROITool.d.ts.map +1 -1
- package/dist/types/utilities/getCalibratedUnits.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/tools/CrosshairsTool.ts +5 -5
- package/src/tools/annotation/PlanarFreehandROITool.ts +229 -178
- package/src/utilities/getCalibratedUnits.ts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "1.81.
|
|
3
|
+
"version": "1.81.4",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@cornerstonejs/core": "^1.81.
|
|
32
|
+
"@cornerstonejs/core": "^1.81.4",
|
|
33
33
|
"@icr/polyseg-wasm": "0.4.0",
|
|
34
34
|
"@types/offscreencanvas": "2019.7.3",
|
|
35
35
|
"comlink": "^4.4.1",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"type": "individual",
|
|
60
60
|
"url": "https://ohif.org/donate"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "53b48392fab3f763c665d403b1f9fc3091c9510f"
|
|
63
63
|
}
|
|
@@ -298,7 +298,8 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
298
298
|
|
|
299
299
|
resetCrosshairs = () => {
|
|
300
300
|
const viewportsInfo = this._getViewportsInfo();
|
|
301
|
-
|
|
301
|
+
for (const viewportInfo of viewportsInfo) {
|
|
302
|
+
const { viewportId, renderingEngineId } = viewportInfo;
|
|
302
303
|
const enabledElement = getEnabledElementByIds(
|
|
303
304
|
viewportId,
|
|
304
305
|
renderingEngineId
|
|
@@ -308,13 +309,13 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
308
309
|
const resetZoom = true;
|
|
309
310
|
const resetToCenter = true;
|
|
310
311
|
const resetRotation = true;
|
|
311
|
-
const
|
|
312
|
+
const suppressEvents = true;
|
|
312
313
|
viewport.resetCamera(
|
|
313
314
|
resetPan,
|
|
314
315
|
resetZoom,
|
|
315
316
|
resetToCenter,
|
|
316
317
|
resetRotation,
|
|
317
|
-
|
|
318
|
+
suppressEvents
|
|
318
319
|
);
|
|
319
320
|
(viewport as Types.IVolumeViewport).resetSlabThickness();
|
|
320
321
|
const { element } = viewport;
|
|
@@ -327,7 +328,7 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
327
328
|
removeAnnotation(annotations[0].annotationUID);
|
|
328
329
|
}
|
|
329
330
|
viewport.render();
|
|
330
|
-
}
|
|
331
|
+
}
|
|
331
332
|
|
|
332
333
|
this.computeToolCenter(viewportsInfo);
|
|
333
334
|
};
|
|
@@ -1433,7 +1434,6 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
1433
1434
|
data.handles.rotationPoints = newRtpoints;
|
|
1434
1435
|
data.handles.slabThicknessPoints = newStpoints;
|
|
1435
1436
|
|
|
1436
|
-
debugger;
|
|
1437
1437
|
if (this.configuration.viewportIndicators) {
|
|
1438
1438
|
const { viewportIndicatorsConfig } = this.configuration;
|
|
1439
1439
|
|
|
@@ -737,169 +737,6 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
737
737
|
const { imageData, metadata } = image;
|
|
738
738
|
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
739
739
|
|
|
740
|
-
// Using an arbitrary start point (canvasPoint), calculate the
|
|
741
|
-
// mm spacing for the canvas in the X and Y directions.
|
|
742
|
-
const canvasPoint = canvasCoordinates[0];
|
|
743
|
-
const originalWorldPoint = viewport.canvasToWorld(canvasPoint);
|
|
744
|
-
const deltaXPoint = viewport.canvasToWorld([
|
|
745
|
-
canvasPoint[0] + 1,
|
|
746
|
-
canvasPoint[1],
|
|
747
|
-
]);
|
|
748
|
-
const deltaYPoint = viewport.canvasToWorld([
|
|
749
|
-
canvasPoint[0],
|
|
750
|
-
canvasPoint[1] + 1,
|
|
751
|
-
]);
|
|
752
|
-
|
|
753
|
-
const deltaInX = vec3.distance(originalWorldPoint, deltaXPoint);
|
|
754
|
-
const deltaInY = vec3.distance(originalWorldPoint, deltaYPoint);
|
|
755
|
-
|
|
756
|
-
const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[0]);
|
|
757
|
-
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
758
|
-
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
759
|
-
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
760
|
-
|
|
761
|
-
let iMin = worldPosIndex[0];
|
|
762
|
-
let iMax = worldPosIndex[0];
|
|
763
|
-
|
|
764
|
-
let jMin = worldPosIndex[1];
|
|
765
|
-
let jMax = worldPosIndex[1];
|
|
766
|
-
|
|
767
|
-
let kMin = worldPosIndex[2];
|
|
768
|
-
let kMax = worldPosIndex[2];
|
|
769
|
-
|
|
770
|
-
for (let j = 1; j < points.length; j++) {
|
|
771
|
-
const worldPosIndex = csUtils.transformWorldToIndex(
|
|
772
|
-
imageData,
|
|
773
|
-
points[j]
|
|
774
|
-
);
|
|
775
|
-
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
776
|
-
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
777
|
-
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
778
|
-
iMin = Math.min(iMin, worldPosIndex[0]);
|
|
779
|
-
iMax = Math.max(iMax, worldPosIndex[0]);
|
|
780
|
-
|
|
781
|
-
jMin = Math.min(jMin, worldPosIndex[1]);
|
|
782
|
-
jMax = Math.max(jMax, worldPosIndex[1]);
|
|
783
|
-
|
|
784
|
-
kMin = Math.min(kMin, worldPosIndex[2]);
|
|
785
|
-
kMax = Math.max(kMax, worldPosIndex[2]);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const worldPosIndex2 = csUtils.transformWorldToIndex(
|
|
789
|
-
imageData,
|
|
790
|
-
points[1]
|
|
791
|
-
);
|
|
792
|
-
worldPosIndex2[0] = Math.floor(worldPosIndex2[0]);
|
|
793
|
-
worldPosIndex2[1] = Math.floor(worldPosIndex2[1]);
|
|
794
|
-
worldPosIndex2[2] = Math.floor(worldPosIndex2[2]);
|
|
795
|
-
|
|
796
|
-
const { scale, areaUnits } = getCalibratedLengthUnitsAndScale(
|
|
797
|
-
image,
|
|
798
|
-
() => {
|
|
799
|
-
const polyline = data.contour.polyline;
|
|
800
|
-
const numPoints = polyline.length;
|
|
801
|
-
const projectedPolyline = new Array(numPoints);
|
|
802
|
-
|
|
803
|
-
for (let i = 0; i < numPoints; i++) {
|
|
804
|
-
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const {
|
|
808
|
-
maxX: canvasMaxX,
|
|
809
|
-
maxY: canvasMaxY,
|
|
810
|
-
minX: canvasMinX,
|
|
811
|
-
minY: canvasMinY,
|
|
812
|
-
} = math.polyline.getAABB(projectedPolyline);
|
|
813
|
-
|
|
814
|
-
const topLeftBBWorld = viewport.canvasToWorld([
|
|
815
|
-
canvasMinX,
|
|
816
|
-
canvasMinY,
|
|
817
|
-
]);
|
|
818
|
-
|
|
819
|
-
const topLeftBBIndex = csUtils.transformWorldToIndex(
|
|
820
|
-
imageData,
|
|
821
|
-
topLeftBBWorld
|
|
822
|
-
);
|
|
823
|
-
|
|
824
|
-
const bottomRightBBWorld = viewport.canvasToWorld([
|
|
825
|
-
canvasMaxX,
|
|
826
|
-
canvasMaxY,
|
|
827
|
-
]);
|
|
828
|
-
|
|
829
|
-
const bottomRightBBIndex = csUtils.transformWorldToIndex(
|
|
830
|
-
imageData,
|
|
831
|
-
bottomRightBBWorld
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
return [topLeftBBIndex, bottomRightBBIndex];
|
|
835
|
-
}
|
|
836
|
-
);
|
|
837
|
-
let area = polyline.getArea(canvasCoordinates) / scale / scale;
|
|
838
|
-
// Convert from canvas_pixels ^2 to mm^2
|
|
839
|
-
area *= deltaInX * deltaInY;
|
|
840
|
-
|
|
841
|
-
// Expand bounding box
|
|
842
|
-
const iDelta = 0.01 * (iMax - iMin);
|
|
843
|
-
const jDelta = 0.01 * (jMax - jMin);
|
|
844
|
-
const kDelta = 0.01 * (kMax - kMin);
|
|
845
|
-
|
|
846
|
-
iMin = Math.floor(iMin - iDelta);
|
|
847
|
-
iMax = Math.ceil(iMax + iDelta);
|
|
848
|
-
jMin = Math.floor(jMin - jDelta);
|
|
849
|
-
jMax = Math.ceil(jMax + jDelta);
|
|
850
|
-
kMin = Math.floor(kMin - kDelta);
|
|
851
|
-
kMax = Math.ceil(kMax + kDelta);
|
|
852
|
-
|
|
853
|
-
const boundsIJK = [
|
|
854
|
-
[iMin, iMax],
|
|
855
|
-
[jMin, jMax],
|
|
856
|
-
[kMin, kMax],
|
|
857
|
-
] as [Types.Point2, Types.Point2, Types.Point2];
|
|
858
|
-
|
|
859
|
-
const worldPosEnd = imageData.indexToWorld([iMax, jMax, kMax]);
|
|
860
|
-
const canvasPosEnd = viewport.worldToCanvas(worldPosEnd);
|
|
861
|
-
|
|
862
|
-
let curRow = 0;
|
|
863
|
-
let intersections = [];
|
|
864
|
-
let intersectionCounter = 0;
|
|
865
|
-
const pointsInShape = pointInShapeCallback(
|
|
866
|
-
imageData,
|
|
867
|
-
(pointLPS, pointIJK) => {
|
|
868
|
-
let result = true;
|
|
869
|
-
const point = viewport.worldToCanvas(pointLPS);
|
|
870
|
-
if (point[1] != curRow) {
|
|
871
|
-
intersectionCounter = 0;
|
|
872
|
-
curRow = point[1];
|
|
873
|
-
intersections = getLineSegmentIntersectionsCoordinates(
|
|
874
|
-
canvasCoordinates,
|
|
875
|
-
point,
|
|
876
|
-
[canvasPosEnd[0], point[1]]
|
|
877
|
-
);
|
|
878
|
-
intersections.sort(
|
|
879
|
-
(function (index) {
|
|
880
|
-
return function (a, b) {
|
|
881
|
-
return a[index] === b[index]
|
|
882
|
-
? 0
|
|
883
|
-
: a[index] < b[index]
|
|
884
|
-
? -1
|
|
885
|
-
: 1;
|
|
886
|
-
};
|
|
887
|
-
})(0)
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
|
-
if (intersections.length && point[0] > intersections[0][0]) {
|
|
891
|
-
intersections.shift();
|
|
892
|
-
intersectionCounter++;
|
|
893
|
-
}
|
|
894
|
-
if (intersectionCounter % 2 === 0) {
|
|
895
|
-
result = false;
|
|
896
|
-
}
|
|
897
|
-
return result;
|
|
898
|
-
},
|
|
899
|
-
this.configuration.statsCalculator.statsCallback,
|
|
900
|
-
boundsIJK
|
|
901
|
-
);
|
|
902
|
-
|
|
903
740
|
const modalityUnitOptions = {
|
|
904
741
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
905
742
|
isSuvScaled: this.isSuvScaled(
|
|
@@ -914,21 +751,64 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
914
751
|
annotation.metadata.referencedImageId,
|
|
915
752
|
modalityUnitOptions
|
|
916
753
|
);
|
|
754
|
+
const calibratedScale = getCalibratedLengthUnitsAndScale(image, () => {
|
|
755
|
+
const polyline = data.contour.polyline;
|
|
756
|
+
const numPoints = polyline.length;
|
|
757
|
+
const projectedPolyline = new Array(numPoints);
|
|
917
758
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
759
|
+
for (let i = 0; i < numPoints; i++) {
|
|
760
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const {
|
|
764
|
+
maxX: canvasMaxX,
|
|
765
|
+
maxY: canvasMaxY,
|
|
766
|
+
minX: canvasMinX,
|
|
767
|
+
minY: canvasMinY,
|
|
768
|
+
} = math.polyline.getAABB(projectedPolyline);
|
|
769
|
+
|
|
770
|
+
const topLeftBBWorld = viewport.canvasToWorld([canvasMinX, canvasMinY]);
|
|
771
|
+
|
|
772
|
+
const topLeftBBIndex = csUtils.transformWorldToIndex(
|
|
773
|
+
imageData,
|
|
774
|
+
topLeftBBWorld
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
const bottomRightBBWorld = viewport.canvasToWorld([
|
|
778
|
+
canvasMaxX,
|
|
779
|
+
canvasMaxY,
|
|
780
|
+
]);
|
|
781
|
+
|
|
782
|
+
const bottomRightBBIndex = csUtils.transformWorldToIndex(
|
|
783
|
+
imageData,
|
|
784
|
+
bottomRightBBWorld
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
return [topLeftBBIndex, bottomRightBBIndex];
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
if (closed) {
|
|
791
|
+
this.updateClosedCachedStats({
|
|
792
|
+
targetId,
|
|
793
|
+
viewport,
|
|
794
|
+
canvasCoordinates,
|
|
795
|
+
points,
|
|
796
|
+
imageData,
|
|
797
|
+
metadata,
|
|
798
|
+
cachedStats,
|
|
799
|
+
modalityUnit,
|
|
800
|
+
calibratedScale,
|
|
801
|
+
});
|
|
802
|
+
} else {
|
|
803
|
+
this.updateOpenCachedStats({
|
|
804
|
+
metadata,
|
|
805
|
+
canvasCoordinates,
|
|
806
|
+
targetId,
|
|
807
|
+
cachedStats,
|
|
808
|
+
modalityUnit,
|
|
809
|
+
calibratedScale,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
932
812
|
}
|
|
933
813
|
|
|
934
814
|
triggerAnnotationModified(
|
|
@@ -942,6 +822,170 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
942
822
|
return cachedStats;
|
|
943
823
|
};
|
|
944
824
|
|
|
825
|
+
protected updateClosedCachedStats({
|
|
826
|
+
viewport,
|
|
827
|
+
points,
|
|
828
|
+
imageData,
|
|
829
|
+
metadata,
|
|
830
|
+
cachedStats,
|
|
831
|
+
targetId,
|
|
832
|
+
modalityUnit,
|
|
833
|
+
canvasCoordinates,
|
|
834
|
+
calibratedScale,
|
|
835
|
+
}) {
|
|
836
|
+
const { scale, areaUnits, units } = calibratedScale;
|
|
837
|
+
|
|
838
|
+
// Using an arbitrary start point (canvasPoint), calculate the
|
|
839
|
+
// mm spacing for the canvas in the X and Y directions.
|
|
840
|
+
const canvasPoint = canvasCoordinates[0];
|
|
841
|
+
const originalWorldPoint = viewport.canvasToWorld(canvasPoint);
|
|
842
|
+
const deltaXPoint = viewport.canvasToWorld([
|
|
843
|
+
canvasPoint[0] + 1,
|
|
844
|
+
canvasPoint[1],
|
|
845
|
+
]);
|
|
846
|
+
const deltaYPoint = viewport.canvasToWorld([
|
|
847
|
+
canvasPoint[0],
|
|
848
|
+
canvasPoint[1] + 1,
|
|
849
|
+
]);
|
|
850
|
+
|
|
851
|
+
const deltaInX = vec3.distance(originalWorldPoint, deltaXPoint);
|
|
852
|
+
const deltaInY = vec3.distance(originalWorldPoint, deltaYPoint);
|
|
853
|
+
|
|
854
|
+
const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[0]);
|
|
855
|
+
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
856
|
+
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
857
|
+
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
858
|
+
|
|
859
|
+
let iMin = worldPosIndex[0];
|
|
860
|
+
let iMax = worldPosIndex[0];
|
|
861
|
+
|
|
862
|
+
let jMin = worldPosIndex[1];
|
|
863
|
+
let jMax = worldPosIndex[1];
|
|
864
|
+
|
|
865
|
+
let kMin = worldPosIndex[2];
|
|
866
|
+
let kMax = worldPosIndex[2];
|
|
867
|
+
|
|
868
|
+
for (let j = 1; j < points.length; j++) {
|
|
869
|
+
const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[j]);
|
|
870
|
+
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
871
|
+
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
872
|
+
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
873
|
+
iMin = Math.min(iMin, worldPosIndex[0]);
|
|
874
|
+
iMax = Math.max(iMax, worldPosIndex[0]);
|
|
875
|
+
|
|
876
|
+
jMin = Math.min(jMin, worldPosIndex[1]);
|
|
877
|
+
jMax = Math.max(jMax, worldPosIndex[1]);
|
|
878
|
+
|
|
879
|
+
kMin = Math.min(kMin, worldPosIndex[2]);
|
|
880
|
+
kMax = Math.max(kMax, worldPosIndex[2]);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const worldPosIndex2 = csUtils.transformWorldToIndex(imageData, points[1]);
|
|
884
|
+
worldPosIndex2[0] = Math.floor(worldPosIndex2[0]);
|
|
885
|
+
worldPosIndex2[1] = Math.floor(worldPosIndex2[1]);
|
|
886
|
+
worldPosIndex2[2] = Math.floor(worldPosIndex2[2]);
|
|
887
|
+
|
|
888
|
+
let area = polyline.getArea(canvasCoordinates) / scale / scale;
|
|
889
|
+
// Convert from canvas_pixels ^2 to mm^2
|
|
890
|
+
area *= deltaInX * deltaInY;
|
|
891
|
+
|
|
892
|
+
// Expand bounding box
|
|
893
|
+
const iDelta = 0.01 * (iMax - iMin);
|
|
894
|
+
const jDelta = 0.01 * (jMax - jMin);
|
|
895
|
+
const kDelta = 0.01 * (kMax - kMin);
|
|
896
|
+
|
|
897
|
+
iMin = Math.floor(iMin - iDelta);
|
|
898
|
+
iMax = Math.ceil(iMax + iDelta);
|
|
899
|
+
jMin = Math.floor(jMin - jDelta);
|
|
900
|
+
jMax = Math.ceil(jMax + jDelta);
|
|
901
|
+
kMin = Math.floor(kMin - kDelta);
|
|
902
|
+
kMax = Math.ceil(kMax + kDelta);
|
|
903
|
+
|
|
904
|
+
const boundsIJK = [
|
|
905
|
+
[iMin, iMax],
|
|
906
|
+
[jMin, jMax],
|
|
907
|
+
[kMin, kMax],
|
|
908
|
+
] as [Types.Point2, Types.Point2, Types.Point2];
|
|
909
|
+
|
|
910
|
+
const worldPosEnd = imageData.indexToWorld([iMax, jMax, kMax]);
|
|
911
|
+
const canvasPosEnd = viewport.worldToCanvas(worldPosEnd);
|
|
912
|
+
|
|
913
|
+
let curRow = 0;
|
|
914
|
+
let intersections = [];
|
|
915
|
+
let intersectionCounter = 0;
|
|
916
|
+
const pointsInShape = pointInShapeCallback(
|
|
917
|
+
imageData,
|
|
918
|
+
(pointLPS, _pointIJK) => {
|
|
919
|
+
let result = true;
|
|
920
|
+
const point = viewport.worldToCanvas(pointLPS);
|
|
921
|
+
if (point[1] != curRow) {
|
|
922
|
+
intersectionCounter = 0;
|
|
923
|
+
curRow = point[1];
|
|
924
|
+
intersections = getLineSegmentIntersectionsCoordinates(
|
|
925
|
+
canvasCoordinates,
|
|
926
|
+
point,
|
|
927
|
+
[canvasPosEnd[0], point[1]]
|
|
928
|
+
);
|
|
929
|
+
intersections.sort(
|
|
930
|
+
(function (index) {
|
|
931
|
+
return function (a, b) {
|
|
932
|
+
return a[index] === b[index] ? 0 : a[index] < b[index] ? -1 : 1;
|
|
933
|
+
};
|
|
934
|
+
})(0)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
if (intersections.length && point[0] > intersections[0][0]) {
|
|
938
|
+
intersections.shift();
|
|
939
|
+
intersectionCounter++;
|
|
940
|
+
}
|
|
941
|
+
if (intersectionCounter % 2 === 0) {
|
|
942
|
+
result = false;
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
},
|
|
946
|
+
this.configuration.statsCalculator.statsCallback,
|
|
947
|
+
boundsIJK
|
|
948
|
+
);
|
|
949
|
+
const stats = this.configuration.statsCalculator.getStatistics();
|
|
950
|
+
|
|
951
|
+
cachedStats[targetId] = {
|
|
952
|
+
Modality: metadata.Modality,
|
|
953
|
+
area,
|
|
954
|
+
perimeter: calculatePerimeter(canvasCoordinates, closed) / scale,
|
|
955
|
+
mean: stats.mean?.value,
|
|
956
|
+
max: stats.max?.value,
|
|
957
|
+
stdDev: stats.stdDev?.value,
|
|
958
|
+
statsArray: stats.array,
|
|
959
|
+
pointsInShape: pointsInShape,
|
|
960
|
+
/**
|
|
961
|
+
* areaUnits are sizing, eg mm^2 typically
|
|
962
|
+
* modality units are pixel value units, eg HU or other
|
|
963
|
+
* unit is linear measurement unit, eg mm
|
|
964
|
+
*/
|
|
965
|
+
areaUnit: areaUnits,
|
|
966
|
+
modalityUnit,
|
|
967
|
+
unit: units,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
protected updateOpenCachedStats({
|
|
972
|
+
targetId,
|
|
973
|
+
metadata,
|
|
974
|
+
canvasCoordinates,
|
|
975
|
+
cachedStats,
|
|
976
|
+
modalityUnit,
|
|
977
|
+
calibratedScale,
|
|
978
|
+
}) {
|
|
979
|
+
const { scale, units } = calibratedScale;
|
|
980
|
+
|
|
981
|
+
cachedStats[targetId] = {
|
|
982
|
+
Modality: metadata.Modality,
|
|
983
|
+
length: calculatePerimeter(canvasCoordinates, false) / scale,
|
|
984
|
+
modalityUnit,
|
|
985
|
+
unit: units,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
945
989
|
private _renderStats = (
|
|
946
990
|
annotation,
|
|
947
991
|
viewport,
|
|
@@ -1010,11 +1054,13 @@ function defaultGetTextLines(data, targetId): string[] {
|
|
|
1010
1054
|
area,
|
|
1011
1055
|
mean,
|
|
1012
1056
|
stdDev,
|
|
1057
|
+
length,
|
|
1013
1058
|
perimeter,
|
|
1014
1059
|
max,
|
|
1015
1060
|
isEmptyArea,
|
|
1016
1061
|
areaUnit,
|
|
1017
1062
|
modalityUnit,
|
|
1063
|
+
unit,
|
|
1018
1064
|
} = cachedVolumeStats || {};
|
|
1019
1065
|
|
|
1020
1066
|
const textLines: string[] = [];
|
|
@@ -1039,7 +1085,12 @@ function defaultGetTextLines(data, targetId): string[] {
|
|
|
1039
1085
|
}
|
|
1040
1086
|
|
|
1041
1087
|
if (perimeter) {
|
|
1042
|
-
textLines.push(`Perimeter: ${roundNumber(perimeter)} ${
|
|
1088
|
+
textLines.push(`Perimeter: ${roundNumber(perimeter)} ${unit}`);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (length) {
|
|
1092
|
+
// No need to show length prefix as there is just the single value
|
|
1093
|
+
textLines.push(`${roundNumber(length)} ${unit}`);
|
|
1043
1094
|
}
|
|
1044
1095
|
|
|
1045
1096
|
return textLines;
|
|
@@ -16,8 +16,17 @@ const SUPPORTED_PROBE_VARIANT = [
|
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
const UNIT_MAPPING = {
|
|
19
|
+
0: 'px',
|
|
20
|
+
1: 'percent',
|
|
21
|
+
2: 'dB',
|
|
19
22
|
3: 'cm',
|
|
20
23
|
4: 'seconds',
|
|
24
|
+
5: 'hertz',
|
|
25
|
+
6: 'dB/seconds',
|
|
26
|
+
7: 'cm/sec',
|
|
27
|
+
8: 'cm\xb2',
|
|
28
|
+
9: 'cm\xb2/s',
|
|
29
|
+
0xc: 'degrees',
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
const EPS = 1e-3;
|
|
@@ -109,10 +118,10 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
109
118
|
|
|
110
119
|
if (isSamePhysicalDelta) {
|
|
111
120
|
// 1 to 1 aspect ratio, we use just one of them
|
|
112
|
-
scale = 1 /
|
|
121
|
+
scale = 1 / physicalDeltaX;
|
|
113
122
|
calibrationType = 'US Region';
|
|
114
|
-
units = '
|
|
115
|
-
areaUnits =
|
|
123
|
+
units = UNIT_MAPPING[region.physicalUnitsXDirection] || 'unknown';
|
|
124
|
+
areaUnits = units + SQUARE;
|
|
116
125
|
} else {
|
|
117
126
|
// here we are showing at the aspect ratio of the physical delta
|
|
118
127
|
// if they are not the same, then we should show px, but the correct solution
|