@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.81.3",
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.3",
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": "945fb26ade3946d7beccd8a4684c1ca20552d647"
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
- const stats = this.configuration.statsCalculator.getStatistics();
919
-
920
- cachedStats[targetId] = {
921
- Modality: metadata.Modality,
922
- area,
923
- perimeter: calculatePerimeter(canvasCoordinates, closed),
924
- mean: stats.mean?.value,
925
- max: stats.max?.value,
926
- stdDev: stats.stdDev?.value,
927
- statsArray: stats.array,
928
- pointsInShape: pointsInShape,
929
- areaUnit: areaUnits,
930
- modalityUnit,
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)} ${modalityUnit}`);
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 / (physicalDeltaX * 10);
121
+ scale = 1 / physicalDeltaX;
113
122
  calibrationType = 'US Region';
114
- units = 'mm';
115
- areaUnits = 'mm' + SQUARE;
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