@cornerstonejs/tools 1.81.3 → 1.81.5
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/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/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/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/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.5",
|
|
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.5",
|
|
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": "e3e10fa13b4615e78e5cf08e252f2a9156700383"
|
|
63
63
|
}
|
|
@@ -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
|