@cornerstonejs/ai 3.7.1 → 3.7.3

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.
@@ -80,6 +80,7 @@ export default class ONNXSegmentationController {
80
80
  protected annotationsNeedUpdating: boolean;
81
81
  protected maskImageData: any;
82
82
  protected promptAnnotationTypes: string[];
83
+ protected _cachedPromptAnnotations: any;
83
84
  protected islandFillOptions: {
84
85
  maxInternalRemove: number;
85
86
  fillInternalEdge: boolean;
@@ -98,11 +99,13 @@ export default class ONNXSegmentationController {
98
99
  initViewport(viewport: any): void;
99
100
  acceptPreview(element: any): void;
100
101
  rejectPreview(element: any): void;
102
+ restoreCachedPromptAnnotations(viewport: Types.IViewport): any[];
103
+ removePromptAnnotationsWithCache(viewport: Types.IViewport): void;
101
104
  interpolateScroll(viewport?: any, dir?: number): Promise<void>;
102
105
  protected log(logger: Loggers, ...args: any[]): void;
103
106
  protected getPromptAnnotations: (viewport?: any) => cornerstoneTools.Types.Annotations;
104
107
  protected viewportRenderedListener: (_event: any) => void;
105
- protected annotationModifiedListener: (_event?: any) => void;
108
+ protected annotationModifiedListener: Function;
106
109
  disconnectViewport(viewport: any): void;
107
110
  protected load(): Promise<void>;
108
111
  clear(viewport: any): void;
@@ -115,8 +118,8 @@ export default class ONNXSegmentationController {
115
118
  tryLoad(options?: {
116
119
  resetImage: boolean;
117
120
  }): void;
118
- mapAnnotationPoint(worldPoint: any): number[];
119
- updateAnnotations(): void;
121
+ mapAnnotationPoint(worldPoint: any, canvasPosition: any): number[];
122
+ updateAnnotations(useSession?: any): void;
120
123
  restoreImageEncoding(session: any, imageId: any): Promise<any>;
121
124
  loadStorageImageEncoding(session: any, imageId: any, index?: any): Promise<any>;
122
125
  storeImageEncoding(session: any, imageId: any, data: any): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { utilities, eventTarget, Enums } from '@cornerstonejs/core';
1
+ import { utilities, eventTarget, Enums, triggerEvent, } 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;
@@ -13,6 +13,11 @@ const { segmentation } = cornerstoneTools;
13
13
  const { filterAnnotationsForDisplay } = cornerstoneTools.utilities.planar;
14
14
  const { IslandRemoval } = cornerstoneTools.utilities;
15
15
  const { triggerSegmentationDataModified } = segmentation.triggerSegmentationEvents;
16
+ const ONNX_EVENTS = {
17
+ MODEL_LOADING_STARTED: 'ONNX_MODEL_LOADING_STARTED',
18
+ MODEL_LOADING_COMPLETED: 'ONNX_MODEL_LOADING_COMPLETED',
19
+ MODEL_COMPONENT_LOADED: 'ONNX_MODEL_COMPONENT_LOADED',
20
+ };
16
21
  function cloneTensor(t) {
17
22
  return new ort.Tensor(t.type, Float32Array.from(t.data), t.dims);
18
23
  }
@@ -176,14 +181,14 @@ export default class ONNXSegmentationController {
176
181
  ctxMask.clearRect(0, 0, canvasMask.width, canvasMask.height);
177
182
  this.tryLoad({ resetImage: true });
178
183
  };
179
- this.annotationModifiedListener = (_event) => {
184
+ this.annotationModifiedListener = cornerstoneTools.utilities.debounce((_event) => {
180
185
  const currentAnnotations = this.getPromptAnnotations();
181
186
  if (!currentAnnotations.length) {
182
187
  return;
183
188
  }
184
189
  this.annotationsNeedUpdating = true;
185
190
  this.tryLoad();
186
- };
191
+ }, 300);
187
192
  if (options.listeners) {
188
193
  this.listeners = [...options.listeners];
189
194
  }
@@ -225,11 +230,6 @@ export default class ONNXSegmentationController {
225
230
  activeStrategy: 'FILL_INSIDE_CIRCLE',
226
231
  preview: {
227
232
  enabled: true,
228
- previewColors: {
229
- 0: [255, 255, 255, 128],
230
- 1: [0, 255, 255, 192],
231
- 2: [255, 0, 255, 255],
232
- },
233
233
  },
234
234
  },
235
235
  });
@@ -257,15 +257,56 @@ export default class ONNXSegmentationController {
257
257
  rejectPreview(element) {
258
258
  this.tool.rejectPreview(element);
259
259
  }
260
+ restoreCachedPromptAnnotations(viewport) {
261
+ if (!this._cachedPromptAnnotations) {
262
+ return [];
263
+ }
264
+ const annotations = [];
265
+ const { include, exclude } = this._cachedPromptAnnotations;
266
+ if (include) {
267
+ const newInclude = cornerstoneTools.utilities.moveAnnotationToViewPlane(include, viewport);
268
+ annotations.push(newInclude);
269
+ }
270
+ if (exclude) {
271
+ const newExclude = cornerstoneTools.utilities.moveAnnotationToViewPlane(exclude, viewport);
272
+ annotations.push(newExclude);
273
+ }
274
+ return annotations;
275
+ }
276
+ removePromptAnnotationsWithCache(viewport) {
277
+ const toolNames = [
278
+ ONNXSegmentationController.MarkerInclude,
279
+ ONNXSegmentationController.MarkerExclude,
280
+ ONNXSegmentationController.BoxPrompt,
281
+ ];
282
+ let cachedInclude = null;
283
+ let cachedExclude = null;
284
+ const allAnnotations = cornerstoneTools.annotation.state.getAllAnnotations();
285
+ for (const annotation of allAnnotations) {
286
+ const toolName = annotation.metadata.toolName;
287
+ if (toolNames.includes(toolName)) {
288
+ if (toolName === ONNXSegmentationController.MarkerInclude &&
289
+ !cachedInclude) {
290
+ cachedInclude = annotation;
291
+ }
292
+ else if (toolName === ONNXSegmentationController.MarkerExclude &&
293
+ !cachedExclude) {
294
+ cachedExclude = annotation;
295
+ }
296
+ cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID);
297
+ }
298
+ }
299
+ this._cachedPromptAnnotations = {
300
+ include: cachedInclude,
301
+ exclude: cachedExclude,
302
+ };
303
+ viewport.render();
304
+ }
260
305
  async interpolateScroll(viewport = this.viewport, dir = 1) {
261
306
  const { element } = viewport;
262
307
  this.tool.acceptPreview(element);
263
308
  const promptAnnotations = this.getPromptAnnotations(viewport);
264
- if (!promptAnnotations.length) {
265
- return;
266
- }
267
309
  const currentSliceIndex = viewport.getCurrentImageIdIndex();
268
- const { focalPoint } = viewport.getCamera();
269
310
  const viewRef = viewport.getViewReference({
270
311
  sliceIndex: currentSliceIndex + dir,
271
312
  });
@@ -275,22 +316,21 @@ export default class ONNXSegmentationController {
275
316
  }
276
317
  viewport.scroll(dir);
277
318
  await new Promise((resolve) => window.setTimeout(resolve, 250));
278
- const nextAnnotations = this.getPromptAnnotations(viewport);
279
- if (nextAnnotations.length > 0) {
280
- return;
319
+ let annotations = [];
320
+ if (!promptAnnotations.length) {
321
+ annotations = this.restoreCachedPromptAnnotations(viewport);
281
322
  }
282
- const { focalPoint: newFocal } = viewport.getCamera();
283
- const newDelta = vec3.sub(vec3.create(), newFocal, focalPoint);
284
- for (const annotation of promptAnnotations) {
285
- annotation.interpolationUID ||= crypto.randomUUID();
286
- const newAnnotation = structuredClone(annotation);
287
- newAnnotation.annotationUID = undefined;
288
- Object.assign(newAnnotation.metadata, viewRef);
289
- newAnnotation.cachedStats = {};
290
- for (const handle of newAnnotation.data.handles.points) {
291
- vec3.add(handle, handle, newDelta);
323
+ else {
324
+ for (const annotation of promptAnnotations) {
325
+ const newAnnotation = structuredClone(annotation);
326
+ cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID);
327
+ Object.assign(newAnnotation.metadata, viewRef);
328
+ cornerstoneTools.utilities.moveAnnotationToViewPlane(newAnnotation, viewport);
329
+ annotations.push(newAnnotation);
292
330
  }
293
- annotationState.addAnnotation(newAnnotation, viewport.element);
331
+ }
332
+ for (const annotation of annotations) {
333
+ annotationState.addAnnotation(annotation, viewport.element);
294
334
  }
295
335
  viewport.render();
296
336
  }
@@ -486,16 +526,30 @@ export default class ONNXSegmentationController {
486
526
  }
487
527
  this.handleImage(desiredImage, session);
488
528
  }
489
- mapAnnotationPoint(worldPoint) {
529
+ mapAnnotationPoint(worldPoint, canvasPosition) {
490
530
  const { viewport } = this;
491
531
  const canvasPoint = viewport.worldToCanvas(worldPoint);
492
532
  const { width, height } = viewport.canvas;
493
533
  const { width: destWidth, height: destHeight } = this.canvas;
494
534
  const x = Math.trunc((canvasPoint[0] * destWidth * devicePixelRatio) / width);
495
535
  const y = Math.trunc((canvasPoint[1] * destHeight * devicePixelRatio) / height);
496
- return [x, y];
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];
497
551
  }
498
- updateAnnotations() {
552
+ updateAnnotations(useSession = this.currentImage) {
499
553
  if (this.isGpuInUse ||
500
554
  !this.annotationsNeedUpdating ||
501
555
  !this.currentImage) {
@@ -506,16 +560,16 @@ export default class ONNXSegmentationController {
506
560
  this.points = [];
507
561
  this.labels = [];
508
562
  this.worldPoints = [];
509
- if (!promptAnnotations?.length) {
563
+ if (!promptAnnotations?.length || !useSession?.canvasPosition) {
510
564
  return;
511
565
  }
512
566
  for (const annotation of promptAnnotations) {
513
567
  const handle = annotation.data.handles.points[0];
514
- const point = this.mapAnnotationPoint(handle);
568
+ const point = this.mapAnnotationPoint(handle, useSession.canvasPosition);
515
569
  this.points.push(...point);
516
570
  if (annotation.metadata.toolName === ONNXSegmentationController.BoxPrompt) {
517
571
  this.labels.push(2, 3);
518
- this.points.push(...this.mapAnnotationPoint(annotation.data.handles.points[3]));
572
+ this.points.push(...this.mapAnnotationPoint(annotation.data.handles.points[3], useSession.canvasPosition));
519
573
  }
520
574
  else {
521
575
  const label = annotation.metadata.toolName === this.excludeTool ? 0 : 1;
@@ -698,11 +752,23 @@ export default class ONNXSegmentationController {
698
752
  const cache = await caches.open('onnx');
699
753
  let cachedResponse = await cache.match(url);
700
754
  if (cachedResponse == undefined) {
755
+ triggerEvent(eventTarget, ONNX_EVENTS.MODEL_COMPONENT_LOADED, {
756
+ name,
757
+ url,
758
+ status: 'loading',
759
+ source: 'network',
760
+ });
701
761
  await cache.add(url);
702
762
  cachedResponse = await cache.match(url);
703
763
  this.log(Loggers.Log, `${name} (network)`);
704
764
  }
705
765
  else {
766
+ triggerEvent(eventTarget, ONNX_EVENTS.MODEL_COMPONENT_LOADED, {
767
+ name,
768
+ url,
769
+ status: 'loaded',
770
+ source: 'cache',
771
+ });
706
772
  this.log(Loggers.Log, `${name} (cached)`);
707
773
  }
708
774
  const data = await cachedResponse.arrayBuffer();
@@ -710,6 +776,12 @@ export default class ONNXSegmentationController {
710
776
  }
711
777
  catch (error) {
712
778
  this.log(Loggers.Log, `${name} (network)`);
779
+ triggerEvent(eventTarget, ONNX_EVENTS.MODEL_COMPONENT_LOADED, {
780
+ name,
781
+ url,
782
+ status: 'error',
783
+ error,
784
+ });
713
785
  return await fetch(url).then((response) => response.arrayBuffer());
714
786
  }
715
787
  }
@@ -724,6 +796,11 @@ export default class ONNXSegmentationController {
724
796
  }
725
797
  urls.push(model.url);
726
798
  }
799
+ triggerEvent(eventTarget, ONNX_EVENTS.MODEL_LOADING_STARTED, {
800
+ modelConfig: this.config.model,
801
+ totalSize: missing,
802
+ urls,
803
+ });
727
804
  if (missing > 0) {
728
805
  this.log(Loggers.Log, `downloading ${missing} MB from network ... it might take a while`);
729
806
  }
@@ -754,7 +831,13 @@ export default class ONNXSegmentationController {
754
831
  imageSession[model.key] = await ort.InferenceSession.create(model_bytes, sessionOptions);
755
832
  }
756
833
  const stop = performance.now();
757
- this.log(Loggers.Log, `ready, ${(stop - start).toFixed(1)}ms`, urls.join(', '));
834
+ const loadTime = stop - start;
835
+ triggerEvent(eventTarget, ONNX_EVENTS.MODEL_LOADING_COMPLETED, {
836
+ modelConfig: this.config.model,
837
+ loadTimeMs: loadTime,
838
+ urls,
839
+ });
840
+ this.log(Loggers.Log, `ready, ${loadTime.toFixed(1)}ms`, urls.join(', '));
758
841
  }
759
842
  async getDirectoryForImageId(session, imageId) {
760
843
  if (imageId.indexOf('/studies/') === -1 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/ai",
3
- "version": "3.7.1",
3
+ "version": "3.7.3",
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.1",
60
- "@cornerstonejs/tools": "^3.7.1"
59
+ "@cornerstonejs/core": "^3.7.3",
60
+ "@cornerstonejs/tools": "^3.7.3"
61
61
  },
62
- "gitHead": "a4fe414ad0c938b9f842127dea4b0d12e334ac25"
62
+ "gitHead": "319a7741f4757201093d6b234a92dc6278d26f66"
63
63
  }