@bettermedicine/imeasure 0.0.1

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.
Files changed (125) hide show
  1. package/.github/workflows/test-build-imeasure-package.yml +30 -0
  2. package/Makefile +21 -0
  3. package/README.md +31 -0
  4. package/dist/affines/from-cs-volume.d.ts +2 -0
  5. package/dist/affines/from-cs-volume.js +12 -0
  6. package/dist/affines/from-primitives.d.ts +8 -0
  7. package/dist/affines/from-primitives.js +24 -0
  8. package/dist/affines/from-vtk-image-data.d.ts +2 -0
  9. package/dist/affines/from-vtk-image-data.js +13 -0
  10. package/dist/affines/index.d.ts +5 -0
  11. package/dist/affines/index.js +5 -0
  12. package/dist/affines/invert.d.ts +2 -0
  13. package/dist/affines/invert.js +6 -0
  14. package/dist/affines/serialize.d.ts +2 -0
  15. package/dist/affines/serialize.js +12 -0
  16. package/dist/affines/translate.d.ts +3 -0
  17. package/dist/affines/translate.js +10 -0
  18. package/dist/constants.d.ts +10 -0
  19. package/dist/constants.js +10 -0
  20. package/dist/cropping/index.d.ts +24 -0
  21. package/dist/cropping/index.js +52 -0
  22. package/dist/formatting/index.d.ts +3 -0
  23. package/dist/formatting/index.js +3 -0
  24. package/dist/formatting/interactive.d.ts +7 -0
  25. package/dist/formatting/interactive.js +39 -0
  26. package/dist/formatting/static.d.ts +7 -0
  27. package/dist/formatting/static.js +33 -0
  28. package/dist/formatting/utils.d.ts +4 -0
  29. package/dist/formatting/utils.js +20 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +15 -0
  32. package/dist/math/affines/from-cs-volume.d.ts +8 -0
  33. package/dist/math/affines/from-cs-volume.js +18 -0
  34. package/dist/math/affines/from-primitives.d.ts +12 -0
  35. package/dist/math/affines/from-primitives.js +28 -0
  36. package/dist/math/affines/from-vtk-image-data.d.ts +5 -0
  37. package/dist/math/affines/from-vtk-image-data.js +8 -0
  38. package/dist/math/affines/from-vtk-image-data.test.d.ts +1 -0
  39. package/dist/math/affines/from-vtk-image-data.test.js +15 -0
  40. package/dist/math/affines/index.d.ts +6 -0
  41. package/dist/math/affines/index.js +6 -0
  42. package/dist/math/affines/invert.d.ts +2 -0
  43. package/dist/math/affines/invert.js +6 -0
  44. package/dist/math/affines/serialize.d.ts +2 -0
  45. package/dist/math/affines/serialize.js +12 -0
  46. package/dist/math/affines/translate-and-invert.test.d.ts +1 -0
  47. package/dist/math/affines/translate-and-invert.test.js +40 -0
  48. package/dist/math/affines/translate.d.ts +3 -0
  49. package/dist/math/affines/translate.js +10 -0
  50. package/dist/math/cropping.d.ts +23 -0
  51. package/dist/math/cropping.js +142 -0
  52. package/dist/math/index.d.ts +4 -0
  53. package/dist/math/index.js +4 -0
  54. package/dist/math/utility.d.ts +4 -0
  55. package/dist/math/utility.js +19 -0
  56. package/dist/metadata/world.d.ts +10 -0
  57. package/dist/metadata/world.js +25 -0
  58. package/dist/test/testdata.d.ts +2 -0
  59. package/dist/test/testdata.js +2 -0
  60. package/dist/types/arrays.d.ts +1 -0
  61. package/dist/types/arrays.js +1 -0
  62. package/dist/types/cornerstone.d.ts +3 -0
  63. package/dist/types/cornerstone.js +1 -0
  64. package/dist/types/crop.d.ts +16 -0
  65. package/dist/types/crop.js +1 -0
  66. package/dist/types/index.d.ts +3 -0
  67. package/dist/types/index.js +3 -0
  68. package/dist/types/io.d.ts +52 -0
  69. package/dist/types/io.js +1 -0
  70. package/dist/types/metadata.d.ts +19 -0
  71. package/dist/types/metadata.js +1 -0
  72. package/dist/types/ohif.d.ts +10 -0
  73. package/dist/types/ohif.js +1 -0
  74. package/dist/types/series-metadata.d.ts +19 -0
  75. package/dist/types/series-metadata.js +1 -0
  76. package/dist/volumes/crop.d.ts +24 -0
  77. package/dist/volumes/crop.js +52 -0
  78. package/docs/README.md +26 -0
  79. package/docs/docs/api.md +5 -0
  80. package/docs/docusaurus.config.ts +134 -0
  81. package/docs/package-lock.json +17698 -0
  82. package/docs/package.json +54 -0
  83. package/docs/sidebars.ts +33 -0
  84. package/docs/src/components/HomepageFeatures/index.tsx +71 -0
  85. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  86. package/docs/src/css/custom.css +30 -0
  87. package/docs/src/pages/index.module.css +23 -0
  88. package/docs/src/pages/index.tsx +44 -0
  89. package/docs/src/pages/markdown-page.md +7 -0
  90. package/docs/static/.nojekyll +0 -0
  91. package/docs/static/img/favicon.ico +0 -0
  92. package/docs/static/img/logo.png +0 -0
  93. package/docs/tsconfig.json +8 -0
  94. package/eslint.config.mjs +11 -0
  95. package/package.json +34 -0
  96. package/src/constants.ts +12 -0
  97. package/src/cropping/index.ts +82 -0
  98. package/src/formatting/index.ts +3 -0
  99. package/src/formatting/interactive.ts +60 -0
  100. package/src/formatting/static.ts +49 -0
  101. package/src/formatting/utils.ts +31 -0
  102. package/src/index.ts +17 -0
  103. package/src/math/affines/from-cs-volume.ts +24 -0
  104. package/src/math/affines/from-primitives.ts +43 -0
  105. package/src/math/affines/from-vtk-image-data.test.ts +17 -0
  106. package/src/math/affines/from-vtk-image-data.ts +13 -0
  107. package/src/math/affines/index.ts +6 -0
  108. package/src/math/affines/invert.ts +7 -0
  109. package/src/math/affines/serialize.ts +14 -0
  110. package/src/math/affines/translate-and-invert.test.ts +78 -0
  111. package/src/math/affines/translate.ts +21 -0
  112. package/src/math/cropping.ts +219 -0
  113. package/src/math/index.ts +4 -0
  114. package/src/math/utility.ts +30 -0
  115. package/src/metadata/world.ts +35 -0
  116. package/src/test/testdata.ts +20 -0
  117. package/src/types/arrays.ts +1 -0
  118. package/src/types/cornerstone.ts +4 -0
  119. package/src/types/crop.ts +16 -0
  120. package/src/types/index.ts +3 -0
  121. package/src/types/io.ts +62 -0
  122. package/src/types/metadata.ts +26 -0
  123. package/src/types/ohif.ts +14 -0
  124. package/tsconfig.json +15 -0
  125. package/vitest.config.ts +7 -0
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { affineTranslateVec3 } from "./translate";
3
+ import { mat4 } from "gl-matrix";
4
+ import { invertAffineMatrix } from "./invert";
5
+ import { sampleFFSMatrix } from "../../test/testdata";
6
+ describe("translate and invert", () => {
7
+ const identityAffine = mat4.fromValues(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
8
+ describe("translate", () => {
9
+ it("correctly translates a point using an identity affine", () => {
10
+ const point = [1, 2, 3];
11
+ const translatedPoint = affineTranslateVec3(point, identityAffine);
12
+ expect(translatedPoint).toEqual([1, 2, 3]);
13
+ });
14
+ it("predictably translates given coordinates with known expected result", () => {
15
+ const points = [
16
+ [246, 185, 212],
17
+ [357, 297, 310],
18
+ ];
19
+ const expectedResults = [
20
+ [-7.3681640625, -234.610595703125, -299.3999938964844],
21
+ [90.4072265625, -135.954345703125, -201.39999389648438],
22
+ ];
23
+ points.forEach((point, index) => {
24
+ const translatedPoint = affineTranslateVec3(point, sampleFFSMatrix);
25
+ expect(translatedPoint).toEqual(expectedResults[index]);
26
+ });
27
+ });
28
+ });
29
+ describe("invert", () => {
30
+ it("correctly inverts an identity matrix", () => {
31
+ const invertedMatrix = invertAffineMatrix(identityAffine);
32
+ expect(invertedMatrix).toEqual(identityAffine);
33
+ });
34
+ it("correctly inverts a sample matrix", () => {
35
+ const invertedMatrix = invertAffineMatrix(sampleFFSMatrix);
36
+ const expectedInvertedMatrix = mat4.fromValues(1.135254979133606, 0, 0, 0, 0, 1.135254979133606, 0, 0, 0, 0, 1, 0, 254.36474609375, 451.34283447265625, 511.3999938964844, 1);
37
+ expect(invertedMatrix).toEqual(expectedInvertedMatrix);
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,3 @@
1
+ import { mat4, vec3 } from "gl-matrix";
2
+ import { Point3D } from "../../types/cornerstone";
3
+ export declare const affineTranslateVec3: (voxelCoord: vec3 | Point3D, affine: mat4) => vec3;
@@ -0,0 +1,10 @@
1
+ import { vec4 } from "gl-matrix";
2
+ // We use affines to convert between voxel-space and world-space coordinates.
3
+ // Takes a coordinate and an affine matrix, returns the translated coordinate.
4
+ // This is useful for converting between voxel-space and world-space coordinates.
5
+ export const affineTranslateVec3 = (voxelCoord, affine) => {
6
+ const homogeneousVoxel = vec4.fromValues(voxelCoord[0], voxelCoord[1], voxelCoord[2], 1);
7
+ const worldCoord = vec4.create(); // Output container for the translated coordinate
8
+ vec4.transformMat4(worldCoord, homogeneousVoxel, affine);
9
+ return [worldCoord[0], worldCoord[1], worldCoord[2]];
10
+ };
@@ -0,0 +1,23 @@
1
+ import { mat4 } from "gl-matrix";
2
+ import { Point3D } from "../types/cornerstone";
3
+ import { TIMeasureCropRegion } from "../types/crop";
4
+ export declare const getInteractiveImeasureCropCuboidDimensions: (measurement: [Point3D, Point3D], worldToVoxelAffine: mat4) => number[];
5
+ export declare const calculateInteractiveImeasureCropRegion: (worldBasePoints: Point3D[], voxelToWorldAffine: mat4) => TIMeasureCropRegion;
6
+ /**
7
+ * Calculates a bounding box for iMeasure cropping based on user clicks and world affines.
8
+ *
9
+ * Expands the crop region if extra clicks are detected outside the initial box, ensuring the region includes all relevant points.
10
+ * Handles conversion between world and voxel space, and adjusts the crop box dimensions accordingly.
11
+ *
12
+ * The function maintains the center and cropping planes, and updates edge lengths if necessary.
13
+ */
14
+ export declare const calculateInteractiveImeasureBoundingBox: (firstClicks: [Point3D, Point3D] | [Point3D], extraClicks: Point3D[] | undefined, voxelToWorldAffine: mat4) => TIMeasureCropRegion;
15
+ /**
16
+ * Calculates a bounding box for iMeasure cropping based on user clicks and world affines.
17
+ *
18
+ * Expands the crop region if extra clicks are detected outside the initial box, ensuring the region includes all relevant points.
19
+ * Handles conversion between world and voxel space, and adjusts the crop box dimensions accordingly.
20
+ *
21
+ * The function maintains the center and cropping planes, and updates edge lengths if necessary.
22
+ */
23
+ export declare const calculateStaticImeasureBoundingBox: (clicks: [Point3D, Point3D], voxelToWorldAffine: mat4) => TIMeasureCropRegion;
@@ -0,0 +1,142 @@
1
+ import { affineTranslateVec3, invertAffineMatrix } from "./affines";
2
+ import { INTERACTIVE_IMEASURE_BASE } from "../constants";
3
+ import { calculateZAxisLength, roundPoint3 } from "./utility";
4
+ // TODO: should expand scale based on z-axis input point diff.
5
+ // Tests first.
6
+ export const getInteractiveImeasureCropCuboidDimensions = (measurement, worldToVoxelAffine) => {
7
+ const [voxelStart, voxelEnd] = measurement.map((pt) => roundPoint3(affineTranslateVec3(pt, worldToVoxelAffine)));
8
+ const x_size = Math.abs(voxelStart[0] - voxelEnd[0]) +
9
+ INTERACTIVE_IMEASURE_BASE.PADDING * 2;
10
+ const y_size = Math.abs(voxelStart[1] - voxelEnd[1]) +
11
+ INTERACTIVE_IMEASURE_BASE.PADDING * 2;
12
+ let xyCubeSize = Math.max(x_size, y_size, INTERACTIVE_IMEASURE_BASE.X);
13
+ // the model always expects an even number of voxels on each axis
14
+ if (xyCubeSize % 2 !== 0) {
15
+ xyCubeSize += 1;
16
+ }
17
+ const voxelToWorldAffine = invertAffineMatrix(worldToVoxelAffine);
18
+ const maxDistanceBetweenPoints = Math.max(x_size, y_size);
19
+ const zLength = calculateZAxisLength(maxDistanceBetweenPoints, voxelToWorldAffine);
20
+ return [xyCubeSize, xyCubeSize, zLength];
21
+ };
22
+ const getCropCuboidFromCenterAndEdgeLengths = (voxelSpaceCenter, edgeLengths) => {
23
+ const minCropCoordinates = [
24
+ voxelSpaceCenter[0] - edgeLengths.x / 2,
25
+ voxelSpaceCenter[1] - edgeLengths.y / 2,
26
+ voxelSpaceCenter[2] - edgeLengths.z / 2,
27
+ ];
28
+ const maxCropCoordinates = [
29
+ minCropCoordinates[0] + edgeLengths.x - 1,
30
+ minCropCoordinates[1] + edgeLengths.y - 1,
31
+ minCropCoordinates[2] + edgeLengths.z - 1,
32
+ ];
33
+ const croppingPlanes = [
34
+ minCropCoordinates[0], // minX
35
+ maxCropCoordinates[0], // maxX
36
+ minCropCoordinates[1], // minY
37
+ maxCropCoordinates[1], // maxY
38
+ minCropCoordinates[2], // minZ
39
+ maxCropCoordinates[2], // maxZ
40
+ ];
41
+ return {
42
+ cropCubePoints: [minCropCoordinates, maxCropCoordinates],
43
+ croppingPlanes,
44
+ voxelSpaceCenter,
45
+ };
46
+ };
47
+ export const calculateInteractiveImeasureCropRegion = (worldBasePoints, voxelToWorldAffine) => {
48
+ let center;
49
+ const singlePoint = worldBasePoints.length === 1;
50
+ if (singlePoint) {
51
+ center = worldBasePoints[0];
52
+ }
53
+ else {
54
+ center = [
55
+ (worldBasePoints[0][0] + worldBasePoints[1][0]) / 2,
56
+ (worldBasePoints[0][1] + worldBasePoints[1][1]) / 2,
57
+ (worldBasePoints[0][2] + worldBasePoints[1][2]) / 2,
58
+ ];
59
+ }
60
+ // we want to convert (and round) this to voxel coordinates
61
+ const worldToVoxelAffine = invertAffineMatrix(voxelToWorldAffine);
62
+ const voxelSpaceCenter = roundPoint3(affineTranslateVec3(center, worldToVoxelAffine));
63
+ // if we only have a single click, use constants for edge lengths.
64
+ let x_edge = INTERACTIVE_IMEASURE_BASE.X;
65
+ let y_edge = INTERACTIVE_IMEASURE_BASE.Y;
66
+ let z_edge = INTERACTIVE_IMEASURE_BASE.Z;
67
+ if (!singlePoint) {
68
+ // if we have two clicks, calculate the edges based on the points
69
+ const tempProposedCuboidEdges = getInteractiveImeasureCropCuboidDimensions(worldBasePoints, worldToVoxelAffine);
70
+ [x_edge, y_edge, z_edge] = tempProposedCuboidEdges;
71
+ }
72
+ const { cropCubePoints, croppingPlanes } = getCropCuboidFromCenterAndEdgeLengths(voxelSpaceCenter, {
73
+ x: x_edge,
74
+ y: y_edge,
75
+ z: z_edge,
76
+ });
77
+ return {
78
+ center,
79
+ voxelSpaceCenter,
80
+ cropCubePoints,
81
+ croppingPlanes,
82
+ edgeLengths: {
83
+ x: x_edge,
84
+ y: y_edge,
85
+ z: z_edge,
86
+ },
87
+ };
88
+ };
89
+ /**
90
+ * Calculates a bounding box for iMeasure cropping based on user clicks and world affines.
91
+ *
92
+ * Expands the crop region if extra clicks are detected outside the initial box, ensuring the region includes all relevant points.
93
+ * Handles conversion between world and voxel space, and adjusts the crop box dimensions accordingly.
94
+ *
95
+ * The function maintains the center and cropping planes, and updates edge lengths if necessary.
96
+ */
97
+ export const calculateInteractiveImeasureBoundingBox = (firstClicks, extraClicks = [], voxelToWorldAffine) => {
98
+ const worldToVoxelAffine = invertAffineMatrix(voxelToWorldAffine);
99
+ const { center, voxelSpaceCenter, cropCubePoints, croppingPlanes, edgeLengths, } = calculateInteractiveImeasureCropRegion(firstClicks, voxelToWorldAffine);
100
+ let maxDistanceFromCenter = 0;
101
+ const extraClicksInVoxelSpace = extraClicks.map((pt) => roundPoint3(affineTranslateVec3(pt, worldToVoxelAffine)));
102
+ extraClicksInVoxelSpace.forEach((point) => {
103
+ maxDistanceFromCenter = Math.max(Math.abs(point[0] - voxelSpaceCenter[0]), Math.abs(point[1] - voxelSpaceCenter[1]), maxDistanceFromCenter);
104
+ // TODO: handle z-axis differences
105
+ });
106
+ const halfEdgeLength = maxDistanceFromCenter + INTERACTIVE_IMEASURE_BASE.PADDING;
107
+ // check if we need to expand the box at all?
108
+ if (halfEdgeLength * 2 < edgeLengths.x) {
109
+ return {
110
+ center,
111
+ voxelSpaceCenter,
112
+ cropCubePoints,
113
+ croppingPlanes,
114
+ edgeLengths,
115
+ };
116
+ }
117
+ const zLength = calculateZAxisLength(halfEdgeLength * 2, voxelToWorldAffine);
118
+ const newEdgeLengths = {
119
+ x: halfEdgeLength * 2,
120
+ y: halfEdgeLength * 2,
121
+ z: zLength,
122
+ };
123
+ const { cropCubePoints: newCropCubePoints, croppingPlanes: newCroppingPlanes, } = getCropCuboidFromCenterAndEdgeLengths(voxelSpaceCenter, newEdgeLengths);
124
+ return {
125
+ center,
126
+ voxelSpaceCenter,
127
+ cropCubePoints: newCropCubePoints,
128
+ croppingPlanes: newCroppingPlanes,
129
+ edgeLengths: newEdgeLengths,
130
+ };
131
+ };
132
+ /**
133
+ * Calculates a bounding box for iMeasure cropping based on user clicks and world affines.
134
+ *
135
+ * Expands the crop region if extra clicks are detected outside the initial box, ensuring the region includes all relevant points.
136
+ * Handles conversion between world and voxel space, and adjusts the crop box dimensions accordingly.
137
+ *
138
+ * The function maintains the center and cropping planes, and updates edge lengths if necessary.
139
+ */
140
+ export const calculateStaticImeasureBoundingBox = (clicks, voxelToWorldAffine) => {
141
+ return calculateInteractiveImeasureBoundingBox(clicks, [], voxelToWorldAffine);
142
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./cropping";
2
+ export * as affines from "./affines";
3
+ export * as utility from "./utility";
4
+ export * as cropping from "./cropping";
@@ -0,0 +1,4 @@
1
+ export * from "./cropping";
2
+ export * as affines from "./affines";
3
+ export * as utility from "./utility";
4
+ export * as cropping from "./cropping";
@@ -0,0 +1,4 @@
1
+ import { mat4, vec3 } from "gl-matrix";
2
+ import { Point3D } from "../types/cornerstone";
3
+ export declare function roundPoint3(m: Point3D | vec3): Point3D;
4
+ export declare const calculateZAxisLength: (xyLength: number, voxelToWorldAffine: mat4) => number;
@@ -0,0 +1,19 @@
1
+ import { INTERACTIVE_IMEASURE_BASE } from "../constants";
2
+ export function roundPoint3(m) {
3
+ if (!Array.isArray(m) || m.length !== 3) {
4
+ throw new Error("Input must be an array of three numbers.");
5
+ }
6
+ return m.map((coord) => Math.round(coord));
7
+ }
8
+ // an utility used to calculate the proportional z-axis length based on
9
+ // a cuboid's XY length, based on the relationship between x/y pixel spacing and
10
+ // series slice thickness.
11
+ export const calculateZAxisLength = (xyLength, voxelToWorldAffine) => {
12
+ const pixelSpacing = voxelToWorldAffine[0];
13
+ const sliceThickness = voxelToWorldAffine[10];
14
+ let zLength = Math.max(Math.round((xyLength * pixelSpacing) / sliceThickness), INTERACTIVE_IMEASURE_BASE.Z);
15
+ if (zLength % 2 !== 0) {
16
+ zLength += 1; // ensure even number
17
+ }
18
+ return zLength;
19
+ };
@@ -0,0 +1,10 @@
1
+ import vtkImageData from "@kitware/vtk.js/Common/DataModel/ImageData";
2
+ import { TWorldMetadata } from "../types/metadata";
3
+ import { TOhifDisplaySetStub } from "../types/ohif";
4
+ /**
5
+ * Extracts world metadata from a vtkImageData object and an OHIF DisplaySet.
6
+ * Combines spatial information from the image data and OHIF display set to produce
7
+ * metadata describing the world origin, shape, spacing, bounding image positions,
8
+ * rescale parameters, and image orientation.
9
+ */
10
+ export declare const extractWorldMetaFromImageDataAndDisplaySet: (imageData: vtkImageData, displaySet: TOhifDisplaySetStub) => TWorldMetadata;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Extracts world metadata from a vtkImageData object and an OHIF DisplaySet.
3
+ * Combines spatial information from the image data and OHIF display set to produce
4
+ * metadata describing the world origin, shape, spacing, bounding image positions,
5
+ * rescale parameters, and image orientation.
6
+ */
7
+ export const extractWorldMetaFromImageDataAndDisplaySet = (imageData, displaySet) => {
8
+ const firstInstance = displaySet.instances[0];
9
+ const rescaleIntercept = firstInstance.RescaleIntercept;
10
+ const rescaleSlope = firstInstance.RescaleSlope;
11
+ const imageOrientationPatient = firstInstance.ImageOrientationPatient;
12
+ const imageCount = displaySet.instances.length;
13
+ const firstDsPosition = displaySet.instances[0].ImagePositionPatient;
14
+ const lastDsPosition = displaySet.instances[imageCount - 1].ImagePositionPatient;
15
+ return {
16
+ worldOrigin: imageData.getOrigin(),
17
+ worldShape: imageData.getDimensions(),
18
+ worldSpacing: imageData.getSpacing(),
19
+ imageCount,
20
+ worldBoundingImagePositions: [firstDsPosition, lastDsPosition],
21
+ rescaleIntercept,
22
+ rescaleSlope,
23
+ imageOrientationPatient,
24
+ };
25
+ };
@@ -0,0 +1,2 @@
1
+ import { mat4 } from "gl-matrix";
2
+ export declare const sampleFFSMatrix: mat4;
@@ -0,0 +1,2 @@
1
+ import { mat4 } from "gl-matrix";
2
+ export const sampleFFSMatrix = mat4.fromValues(0.880859375, 0, 0, 0, 0, 0.880859375, 0, 0, 0, 0, 1, 0, -224.0595703125, -397.569580078125, -511.3999938964844, 1);
@@ -0,0 +1 @@
1
+ export type TypedArray = Uint8Array | Int16Array | Float32Array;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Types } from "@cornerstonejs/core";
2
+ export type Point3D = Types.Point3;
3
+ export type { ImageVolume } from "@cornerstonejs/core";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { Point3D } from "./cornerstone";
2
+ export type TCroppingPlanes = [number, number, number, number, number, number];
3
+ export type TCropCubePoints = [Point3D, Point3D];
4
+ export type TCropCuboid = {
5
+ cropCubePoints: TCropCubePoints;
6
+ croppingPlanes: TCroppingPlanes;
7
+ voxelSpaceCenter: Point3D;
8
+ };
9
+ export type TIMeasureCropRegion = TCropCuboid & {
10
+ center: Point3D;
11
+ edgeLengths: {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ };
16
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * as IO from "./io";
2
+ export * as Cornerstone from "./cornerstone";
3
+ export * as Metadata from "./metadata";
@@ -0,0 +1,3 @@
1
+ export * as IO from "./io";
2
+ export * as Cornerstone from "./cornerstone";
3
+ export * as Metadata from "./metadata";
@@ -0,0 +1,52 @@
1
+ import { Point3D } from "./cornerstone";
2
+ export type TRawMeasurement = {
3
+ coords_voxel: [Point3D, Point3D];
4
+ coords_world: [Point3D, Point3D];
5
+ length_voxel: number;
6
+ length_world: number;
7
+ };
8
+ export type TCrossMeasurement = {
9
+ short: TRawMeasurement;
10
+ long: TRawMeasurement;
11
+ };
12
+ export type TIMeasure3DResponse = {
13
+ mask: string;
14
+ measurements: {
15
+ cross: {
16
+ 0: TCrossMeasurement;
17
+ 1: TCrossMeasurement;
18
+ 2: TCrossMeasurement;
19
+ };
20
+ diagonal: TRawMeasurement;
21
+ height: TRawMeasurement;
22
+ };
23
+ };
24
+ export type TIMeasure3DGenericPayload = {
25
+ id: string;
26
+ world_origin: Point3D;
27
+ world_shape: Point3D;
28
+ world_spacing: Point3D;
29
+ rescale_intercept: number;
30
+ rescale_slope: number;
31
+ image_orientation_patient: [number, number, number, number, number, number];
32
+ crop_boundaries_world: [Point3D, Point3D];
33
+ crop_boundaries_voxel: [Point3D, Point3D];
34
+ crop_boundaries_unclipped_voxel: [Point3D, Point3D];
35
+ crop_cuboid_center_voxel: Point3D;
36
+ shape: Point3D;
37
+ world_bounding_image_positions: [Point3D, Point3D];
38
+ image_count: number;
39
+ };
40
+ export type TStaticImeasurePayload = TIMeasure3DGenericPayload & {
41
+ clicks: [Point3D, Point3D];
42
+ };
43
+ export type TInteractiveIMeasureMode = "positive" | "negative";
44
+ export type TInteractiveIMeasureModeClicks = {
45
+ mode: TInteractiveIMeasureMode;
46
+ points: Point3D[];
47
+ firstClick?: true;
48
+ };
49
+ export type TInteractiveImeasurePayload = TIMeasure3DGenericPayload & {
50
+ finding_id?: string;
51
+ clicks: TInteractiveIMeasureModeClicks[];
52
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { vec3 } from "gl-matrix";
2
+ import { Point3D } from "./cornerstone";
3
+ export type TWorldMetadata = {
4
+ worldOrigin: vec3;
5
+ worldShape: vec3;
6
+ worldSpacing: vec3;
7
+ rescaleIntercept: number;
8
+ rescaleSlope: number;
9
+ imageOrientationPatient: [number, number, number, number, number, number];
10
+ worldBoundingImagePositions: [vec3, vec3];
11
+ imageCount: number;
12
+ };
13
+ export type TCropMetadata = {
14
+ cropCuboidCenterVoxel: Point3D;
15
+ cropBoundariesUnclippedVoxel: [Point3D, Point3D];
16
+ cropBoundariesVoxel: [Point3D, Point3D];
17
+ cropBoundariesWorld: [Point3D, Point3D];
18
+ shape: [number, number, number];
19
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { Point3D } from "./cornerstone";
2
+ export type TOhifDisplaySetInstanceStub = {
3
+ RescaleIntercept: number;
4
+ RescaleSlope: number;
5
+ ImageOrientationPatient: [number, number, number, number, number, number];
6
+ ImagePositionPatient: Point3D;
7
+ };
8
+ export type TOhifDisplaySetStub = {
9
+ instances: TOhifDisplaySetInstanceStub[];
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { vec3 } from "gl-matrix";
2
+ import { Point3D } from "./cornerstone";
3
+ export type TWorldMetadata = {
4
+ worldOrigin: vec3;
5
+ worldShape: vec3;
6
+ worldSpacing: vec3;
7
+ rescaleIntercept: number;
8
+ rescaleSlope: number;
9
+ imageOrientationPatient: [number, number, number, number, number, number];
10
+ worldBoundingImagePositions: [vec3, vec3];
11
+ imageCount: number;
12
+ };
13
+ export type TCropMetadata = {
14
+ cropCuboidCenterVoxel: Point3D;
15
+ cropBoundariesUnclippedVoxel: [Point3D, Point3D];
16
+ cropBoundariesVoxel: [Point3D, Point3D];
17
+ cropBoundariesWorld: [Point3D, Point3D];
18
+ shape: [number, number, number];
19
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import vtkImageData from "@kitware/vtk.js/Common/DataModel/ImageData";
2
+ import { TCropMetadata } from "../types/metadata";
3
+ import { TCroppingPlanes, TIMeasureCropRegion } from "../types/crop";
4
+ export type TInteractiveImeasureCropResult = {
5
+ voxelValues: Uint8Array | Int16Array | Float32Array;
6
+ volume: vtkImageData;
7
+ cropMeta: TCropMetadata;
8
+ };
9
+ /**
10
+ * Crops a VTK volume using specified crop boundaries and returns the cropped volume data along with metadata.
11
+ *
12
+ * This function takes a `vtkImageData` volume and a crop region definition, applies cropping planes,
13
+ * and computes the effective cropped boundaries in both voxel and world coordinates. The result includes
14
+ * the cropped volume, its scalar voxel values, and metadata about the crop operation.
15
+ */
16
+ export declare const cropFromVTKVolumeByCropBoundaries: (vtkVolume: vtkImageData, cropRegion: TIMeasureCropRegion) => TInteractiveImeasureCropResult;
17
+ /**
18
+ * Crops a VTK volume using specified cropping planes.
19
+ *
20
+ * This function applies cropping planes to a `vtkImageData` volume and returns the cropped volume.
21
+ * The cropping planes define the boundaries of the crop operation in voxel coordinates, ordered as:
22
+ * [minX, maxX, minY, maxY, minZ, maxZ].
23
+ */
24
+ export declare const cropVolumeByCroppingPlanes: (vtkVolume: vtkImageData, croppingPlanes: TCroppingPlanes) => vtkImageData;
@@ -0,0 +1,52 @@
1
+ import cropFilter from "@kitware/vtk.js/Filters/General/ImageCropFilter";
2
+ import { affineTranslateVec3, getAffineMatrixFromVtkImageData, } from "../math/affines";
3
+ import { vec3ToPoint3D } from "../formatting";
4
+ /**
5
+ * Crops a VTK volume using specified crop boundaries and returns the cropped volume data along with metadata.
6
+ *
7
+ * This function takes a `vtkImageData` volume and a crop region definition, applies cropping planes,
8
+ * and computes the effective cropped boundaries in both voxel and world coordinates. The result includes
9
+ * the cropped volume, its scalar voxel values, and metadata about the crop operation.
10
+ */
11
+ export const cropFromVTKVolumeByCropBoundaries = (vtkVolume, cropRegion) => {
12
+ const voxelToWorldAffine = getAffineMatrixFromVtkImageData(vtkVolume);
13
+ const croppedVolume = cropVolumeByCroppingPlanes(vtkVolume, cropRegion.croppingPlanes);
14
+ // Our resulting crop may differ from the crop cube we passed in in case the
15
+ // crop cube exceeded the bounds of the original volume. To get its exact, effective
16
+ // coordinates, we get its extent...
17
+ const [minX, maxX, minY, maxY, minZ, maxZ] = croppedVolume.getExtent();
18
+ const actualCroppedBounds = [
19
+ [minX, minY, minZ],
20
+ [maxX, maxY, maxZ],
21
+ ];
22
+ // ...and translate it to world coordinates
23
+ const cropBoundariesWorld = actualCroppedBounds.map((pt) => vec3ToPoint3D(affineTranslateVec3(pt, voxelToWorldAffine)));
24
+ return {
25
+ voxelValues: croppedVolume
26
+ .getPointData()
27
+ .getScalars()
28
+ .getData(),
29
+ volume: croppedVolume,
30
+ cropMeta: {
31
+ cropCuboidCenterVoxel: cropRegion.voxelSpaceCenter,
32
+ cropBoundariesUnclippedVoxel: cropRegion.cropCubePoints,
33
+ cropBoundariesVoxel: actualCroppedBounds,
34
+ cropBoundariesWorld: cropBoundariesWorld,
35
+ shape: croppedVolume.getDimensions(),
36
+ },
37
+ };
38
+ };
39
+ /**
40
+ * Crops a VTK volume using specified cropping planes.
41
+ *
42
+ * This function applies cropping planes to a `vtkImageData` volume and returns the cropped volume.
43
+ * The cropping planes define the boundaries of the crop operation in voxel coordinates, ordered as:
44
+ * [minX, maxX, minY, maxY, minZ, maxZ].
45
+ */
46
+ export const cropVolumeByCroppingPlanes = (vtkVolume, croppingPlanes) => {
47
+ const cropper = cropFilter.newInstance();
48
+ cropper.setInputData(vtkVolume);
49
+ cropper.setCroppingPlanes(croppingPlanes);
50
+ cropper.update();
51
+ return cropper.getOutputData();
52
+ };
package/docs/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Website
2
+
3
+ This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
4
+ > :bulb: Note: `docs/sdk` is automatically generated using Typedoc
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm i
10
+ ```
11
+
12
+ ## Local Development
13
+
14
+ ```bash
15
+ npm run dev
16
+ ```
17
+
18
+ This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
19
+
20
+ ## Build
21
+
22
+ ```bash
23
+ npm run build
24
+ ```
25
+
26
+ This command generates static content into the `build` directory and can be served using any static contents hosting service.
@@ -0,0 +1,5 @@
1
+ ---
2
+ sidebar_position: 1
3
+ ---
4
+
5
+ # IMeasure API documentation