@cornerstonejs/ai 3.7.17 → 3.8.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.
@@ -60,6 +60,8 @@ export default class ONNXSegmentationController {
60
60
  private points;
61
61
  private labels;
62
62
  private worldPoints;
63
+ private randomPoints;
64
+ private _searchBreadth;
63
65
  private loadingAI;
64
66
  protected viewport: any;
65
67
  protected excludeTool: string;
@@ -81,6 +83,10 @@ export default class ONNXSegmentationController {
81
83
  protected maskImageData: any;
82
84
  protected promptAnnotationTypes: string[];
83
85
  protected _cachedPromptAnnotations: any;
86
+ protected _enabled: boolean;
87
+ protected _autoSegmentMode: boolean;
88
+ protected imageIdsRunAgainst: Map<any, any>;
89
+ protected numRandomPoints: number;
84
90
  protected islandFillOptions: {
85
91
  maxInternalRemove: number;
86
92
  fillInternalEdge: boolean;
@@ -93,7 +99,15 @@ export default class ONNXSegmentationController {
93
99
  models: any;
94
100
  modelName: any;
95
101
  islandFillOptions: any;
102
+ autoSegmentMode: boolean;
103
+ numRandomPoints: number;
104
+ searchBreadth: number;
96
105
  });
106
+ set enabled(enabled: boolean);
107
+ get enabled(): boolean;
108
+ set autoSegmentMode(enabled: boolean);
109
+ get autoSegmentMode(): boolean;
110
+ set numSamplePoints(num: number);
97
111
  initModel(): Promise<unknown>;
98
112
  setPCutoff(cutoff: number): void;
99
113
  initViewport(viewport: any): void;
@@ -1,4 +1,4 @@
1
- import { utilities, eventTarget, Enums, triggerEvent, } from '@cornerstonejs/core';
1
+ import { utilities, eventTarget, Enums, triggerEvent, cache, } from '@cornerstonejs/core';
2
2
  import * as cornerstoneTools from '@cornerstonejs/tools';
3
3
  import { segmentation as cstSegmentation, LabelmapBaseTool, } from '@cornerstonejs/tools';
4
4
  const { strategies } = cstSegmentation;
@@ -116,6 +116,9 @@ export default class ONNXSegmentationController {
116
116
  models: null,
117
117
  modelName: null,
118
118
  islandFillOptions: undefined,
119
+ autoSegmentMode: false,
120
+ numRandomPoints: 2,
121
+ searchBreadth: 3,
119
122
  }) {
120
123
  this.maxWidth = 1024;
121
124
  this.maxHeight = 1024;
@@ -127,6 +130,7 @@ export default class ONNXSegmentationController {
127
130
  this.points = [];
128
131
  this.labels = [];
129
132
  this.worldPoints = new Array();
133
+ this._searchBreadth = 3;
130
134
  this.excludeTool = ONNXSegmentationController.MarkerExclude;
131
135
  this.listeners = [console.log];
132
136
  this.desiredImage = {
@@ -145,6 +149,10 @@ export default class ONNXSegmentationController {
145
149
  ONNXSegmentationController.MarkerExclude,
146
150
  ONNXSegmentationController.BoxPrompt,
147
151
  ];
152
+ this._enabled = false;
153
+ this._autoSegmentMode = false;
154
+ this.imageIdsRunAgainst = new Map();
155
+ this.numRandomPoints = 25;
148
156
  this.islandFillOptions = {
149
157
  maxInternalRemove: 16,
150
158
  fillInternalEdge: true,
@@ -176,6 +184,73 @@ export default class ONNXSegmentationController {
176
184
  if (desiredImage.imageId === currentImage?.imageId) {
177
185
  return;
178
186
  }
187
+ if (this._enabled && this._autoSegmentMode) {
188
+ if ('isInAcquisitionPlane' in viewport &&
189
+ !viewport.isInAcquisitionPlane()) {
190
+ console.warn('Non acquisition plane viewports and auto segment mode is not yet supported');
191
+ return;
192
+ }
193
+ const segmentation = cornerstoneTools.segmentation.activeSegmentation.getActiveSegmentation(viewport.id);
194
+ const segmentIndex = cornerstoneTools.segmentation.segmentIndex.getActiveSegmentIndex(segmentation.segmentationId);
195
+ const imageIds = viewport.getImageIds();
196
+ const currentImageIdIndex = viewport.getCurrentImageIdIndex();
197
+ const pointLists = [];
198
+ let foundPrevious = false;
199
+ let foundNext = false;
200
+ for (let offset = 1; offset <= this._searchBreadth; offset++) {
201
+ if (!foundPrevious) {
202
+ const previousImageIdIndex = currentImageIdIndex - offset;
203
+ if (previousImageIdIndex >= 0) {
204
+ const previousImageId = imageIds[previousImageIdIndex];
205
+ const previousLabelmapImage = cache.getImageByReferencedImageId(previousImageId);
206
+ const previousLabelmapVoxelManager = previousLabelmapImage?.voxelManager;
207
+ if (previousLabelmapVoxelManager) {
208
+ let foundInThisSlice = false;
209
+ previousLabelmapVoxelManager.forEach(({ value, pointIJK }) => {
210
+ if (value === segmentIndex) {
211
+ const worldCoords = utilities.imageToWorldCoords(previousLabelmapImage.imageId, [pointIJK[0], pointIJK[1]]);
212
+ pointLists.push(worldCoords);
213
+ foundInThisSlice = true;
214
+ }
215
+ });
216
+ if (foundInThisSlice) {
217
+ foundPrevious = true;
218
+ }
219
+ }
220
+ }
221
+ }
222
+ if (!foundNext) {
223
+ const nextImageIdIndex = currentImageIdIndex + offset;
224
+ if (nextImageIdIndex < imageIds.length) {
225
+ const nextImageId = imageIds[nextImageIdIndex];
226
+ const nextLabelmapImage = cache.getImageByReferencedImageId(nextImageId);
227
+ const nextLabelmapVoxelManager = nextLabelmapImage?.voxelManager;
228
+ if (nextLabelmapVoxelManager) {
229
+ let foundInThisSlice = false;
230
+ nextLabelmapVoxelManager.forEach(({ value, pointIJK }) => {
231
+ if (value === segmentIndex) {
232
+ const worldCoords = utilities.imageToWorldCoords(nextLabelmapImage.imageId, [pointIJK[0], pointIJK[1]]);
233
+ pointLists.push(worldCoords);
234
+ foundInThisSlice = true;
235
+ }
236
+ });
237
+ if (foundInThisSlice) {
238
+ foundNext = true;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ if (foundPrevious && foundNext) {
244
+ break;
245
+ }
246
+ }
247
+ this.randomPoints =
248
+ pointLists.length > 0
249
+ ? pointLists
250
+ .sort(() => Math.random() - 0.5)
251
+ .slice(0, Math.min(pointLists.length, this.numRandomPoints))
252
+ : [];
253
+ }
179
254
  const { canvasMask } = this;
180
255
  const ctxMask = canvasMask.getContext('2d');
181
256
  ctxMask.clearRect(0, 0, canvasMask.width, canvasMask.height);
@@ -203,6 +278,27 @@ export default class ONNXSegmentationController {
203
278
  this.config = this.getConfig(options.modelName);
204
279
  this.islandFillOptions =
205
280
  options.islandFillOptions ?? this.islandFillOptions;
281
+ this._autoSegmentMode = options.autoSegmentMode || false;
282
+ this.numRandomPoints = options.numRandomPoints || this.numRandomPoints;
283
+ this._searchBreadth = options.searchBreadth || this._searchBreadth;
284
+ }
285
+ set enabled(enabled) {
286
+ this._enabled = enabled;
287
+ }
288
+ get enabled() {
289
+ return this._enabled;
290
+ }
291
+ set autoSegmentMode(enabled) {
292
+ this._autoSegmentMode = enabled;
293
+ if (enabled) {
294
+ this._enabled = true;
295
+ }
296
+ }
297
+ get autoSegmentMode() {
298
+ return this._autoSegmentMode;
299
+ }
300
+ set numSamplePoints(num) {
301
+ this.numRandomPoints = num;
206
302
  }
207
303
  initModel() {
208
304
  if (!this.loadingAI) {
@@ -521,33 +617,31 @@ export default class ONNXSegmentationController {
521
617
  if (this.currentImage !== session) {
522
618
  this.currentImage = session;
523
619
  }
524
- this.updateAnnotations();
620
+ if (this._enabled && this._autoSegmentMode) {
621
+ this.points = [];
622
+ this.labels = [];
623
+ if (this.randomPoints?.length) {
624
+ this.randomPoints.forEach((point) => {
625
+ const mappedPoint = this.mapAnnotationPoint(point, this.currentImage.canvasPosition);
626
+ this.points.push(...mappedPoint);
627
+ this.labels.push(1);
628
+ });
629
+ this.runDecode();
630
+ }
631
+ }
632
+ else {
633
+ this.updateAnnotations();
634
+ }
525
635
  return;
526
636
  }
527
637
  this.handleImage(desiredImage, session);
528
638
  }
529
639
  mapAnnotationPoint(worldPoint, canvasPosition) {
530
- const { viewport } = this;
531
- const canvasPoint = viewport.worldToCanvas(worldPoint);
532
- const { width, height } = viewport.canvas;
533
- const { width: destWidth, height: destHeight } = this.canvas;
534
- const x = Math.trunc((canvasPoint[0] * destWidth * devicePixelRatio) / width);
535
- const y = Math.trunc((canvasPoint[1] * destHeight * devicePixelRatio) / height);
536
- const { bottomLeft, topRight, origin } = canvasPosition;
537
- const yVector = vec3.sub([0, 0, 0], origin, bottomLeft);
538
- const xVector = vec3.sub([0, 0, 0], origin, topRight);
539
- const xLen = vec3.length(xVector);
540
- const yLen = vec3.length(yVector);
541
- vec3.scale(xVector, xVector, 1 / xLen);
542
- vec3.scale(yVector, yVector, 1 / yLen);
543
- const xDot = vec3.dot(worldPoint, xVector);
544
- const yDot = vec3.dot(worldPoint, yVector);
545
- const centerX = vec3.dot(origin, xVector);
546
- const centerY = vec3.dot(origin, yVector);
547
- const newX = Math.round(((centerX - xDot) * destWidth) / xLen);
548
- const newY = Math.round(((centerY - yDot) * destHeight) / yLen);
549
- console.log('Old/new X,Y', x, y, newX, newY, x - newX, y - newY);
550
- return [newX, newY];
640
+ const { origin, downVector, rightVector } = canvasPosition;
641
+ const deltaOrigin = vec3.sub([0, 0, 0], worldPoint, origin);
642
+ const x = Math.round(vec3.dot(deltaOrigin, rightVector) / vec3.sqrLen(rightVector));
643
+ const y = Math.round(vec3.dot(deltaOrigin, downVector) / vec3.sqrLen(downVector));
644
+ return [x, y];
551
645
  }
552
646
  updateAnnotations(useSession = this.currentImage) {
553
647
  if (this.isGpuInUse ||
@@ -647,22 +741,18 @@ export default class ONNXSegmentationController {
647
741
  createLabelmap(mask, canvasPosition, _points, _labels) {
648
742
  const { canvas, viewport } = this;
649
743
  const preview = this.tool.addPreview(viewport.element);
650
- const { previewSegmentIndex, memo, segmentationId, segmentIndex } = preview;
651
- const previewVoxelManager = memo?.voxelManager || preview.previewVoxelManager;
744
+ const { previewSegmentIndex, memo, segmentationId, segmentIndex, segmentationVoxelManager, } = preview;
745
+ const previewVoxelManager = memo?.voxelManager;
652
746
  const { dimensions } = previewVoxelManager;
653
747
  const { data } = mask;
654
- const { origin, topRight, bottomLeft } = canvasPosition;
655
- const downVec = vec3.subtract(vec3.create(), bottomLeft, origin);
656
- const rightVec = vec3.subtract(vec3.create(), topRight, origin);
657
- vec3.scale(downVec, downVec, 1 / canvas.height);
658
- vec3.scale(rightVec, rightVec, 1 / canvas.width);
748
+ const { origin, rightVector, downVector } = canvasPosition;
659
749
  const worldPointJ = vec3.create();
660
750
  const worldPoint = vec3.create();
661
751
  const imageData = viewport.getDefaultImageData();
662
752
  for (let j = 0; j < canvas.height; j++) {
663
- vec3.scaleAndAdd(worldPointJ, origin, downVec, j);
753
+ vec3.scaleAndAdd(worldPointJ, origin, downVector, j);
664
754
  for (let i = 0; i < canvas.width; i++) {
665
- vec3.scaleAndAdd(worldPoint, worldPointJ, rightVec, i);
755
+ vec3.scaleAndAdd(worldPoint, worldPointJ, rightVector, i);
666
756
  const ijkPoint = imageData.worldToIndex(worldPoint).map(Math.round);
667
757
  if (ijkPoint.findIndex((v, index) => v < 0 || v >= dimensions[index]) !==
668
758
  -1) {
@@ -670,6 +760,10 @@ export default class ONNXSegmentationController {
670
760
  }
671
761
  const maskIndex = 4 * (i + j * this.maxWidth);
672
762
  const v = data[maskIndex];
763
+ const segmentValue = segmentationVoxelManager.getAtIJKPoint(ijkPoint);
764
+ if (segmentValue !== 0) {
765
+ continue;
766
+ }
673
767
  if (v > this.pCutoff) {
674
768
  previewVoxelManager.setAtIJKPoint(ijkPoint, previewSegmentIndex);
675
769
  }
@@ -678,6 +772,8 @@ export default class ONNXSegmentationController {
678
772
  }
679
773
  }
680
774
  }
775
+ this.tool.doneEditMemo();
776
+ this.tool._previewData.isDrag = true;
681
777
  const voxelManager = previewVoxelManager.sourceVoxelManager || previewVoxelManager;
682
778
  if (this.islandFillOptions) {
683
779
  const islandRemoval = new IslandRemoval(this.islandFillOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/ai",
3
- "version": "3.7.17",
3
+ "version": "3.8.1",
4
4
  "description": "AI and ML Interfaces for Cornerstone3D",
5
5
  "files": [
6
6
  "dist"
@@ -56,8 +56,8 @@
56
56
  "onnxruntime-web": "1.17.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@cornerstonejs/core": "^3.7.17",
60
- "@cornerstonejs/tools": "^3.7.17"
59
+ "@cornerstonejs/core": "^3.8.1",
60
+ "@cornerstonejs/tools": "^3.8.1"
61
61
  },
62
- "gitHead": "9a5dc66176f5172fd3bb11ebcf37f9b8667ab3c4"
62
+ "gitHead": "1f45d352ecb6e239cb418f085cdabeca2a4520a9"
63
63
  }