@cornerstonejs/ai 3.7.17 → 3.8.0
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.
|
|
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 {
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
|
|
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
|
|
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,
|
|
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,
|
|
753
|
+
vec3.scaleAndAdd(worldPointJ, origin, downVector, j);
|
|
664
754
|
for (let i = 0; i < canvas.width; i++) {
|
|
665
|
-
vec3.scaleAndAdd(worldPoint, worldPointJ,
|
|
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.
|
|
3
|
+
"version": "3.8.0",
|
|
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.
|
|
60
|
-
"@cornerstonejs/tools": "^3.
|
|
59
|
+
"@cornerstonejs/core": "^3.8.0",
|
|
60
|
+
"@cornerstonejs/tools": "^3.8.0"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "bc351a2bae5a5b2f93691a384222b71b26d5c263"
|
|
63
63
|
}
|