@ohif/app 3.13.0-beta.119 → 3.13.0-beta.120

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 (39) hide show
  1. package/dist/{1483.bundle.e208a19dd0ff9a11.js → 1483.bundle.5e93c978bd32d7de.js} +1 -1
  2. package/dist/{2343.bundle.e50c08b2df863e1a.js → 2343.bundle.3445fda02a002697.js} +12 -8
  3. package/dist/{2036.bundle.1b6c0d890aa6bf4e.js → 2493.bundle.f3b6a64d158365da.js} +113 -35
  4. package/dist/{3215.bundle.1fca83719c1b786b.js → 3330.bundle.b1297ef8c94e5440.js} +796 -180
  5. package/dist/{2690.bundle.f5dc28ac498de122.js → 3568.bundle.45dc681ef6890166.js} +2112 -76
  6. package/dist/{6495.bundle.84b83aa091c338cd.js → 6495.bundle.a6764fdb8c0c252f.js} +846 -219
  7. package/dist/{8668.bundle.93c88419e37dc076.js → 8668.bundle.0e47f7231aa07da2.js} +227 -3
  8. package/dist/{9402.bundle.0dd1b538f0708077.js → 9402.bundle.928437e2e48c12dd.js} +2 -2
  9. package/dist/{9560.bundle.bb39f946ea79cf0e.js → 9560.bundle.517f0ef7ee3a20bf.js} +159 -196
  10. package/dist/{app.bundle.bdeb02edea129b2c.js → app.bundle.81a823d1149be346.js} +99 -25
  11. package/dist/{compute.bundle.77e80c1775429bd6.js → compute.bundle.9cc3697d07fbb082.js} +3 -3
  12. package/dist/index.html +1 -1
  13. package/dist/{polySeg.bundle.5e350e0723aa59b6.js → polySeg.bundle.a526954fc2fed311.js} +3 -3
  14. package/dist/sw.js +1 -1
  15. package/package.json +6 -6
  16. /package/dist/{100.bundle.bd6735a86c215595.js → 100.bundle.84a35e9d087b403c.js} +0 -0
  17. /package/dist/{1180.bundle.4c5146471b4d3497.js → 1180.bundle.7f790d9fd6fb031c.js} +0 -0
  18. /package/dist/{1602.bundle.163e3da3a27b860c.js → 1602.bundle.254a392a6b5d2b85.js} +0 -0
  19. /package/dist/{2779.bundle.664d9d0a704f7b94.js → 2779.bundle.c879d6af5dc0c274.js} +0 -0
  20. /package/dist/{3500.bundle.18412afd9ee5d6a7.js → 3500.bundle.d4aaab15c4461cc4.js} +0 -0
  21. /package/dist/{3760.bundle.f69046888fc2b881.js → 3760.bundle.c95abd6ab662bd3b.js} +0 -0
  22. /package/dist/{4556.bundle.128d2abaea69dce3.js → 4556.bundle.c3ac23e680d677b2.js} +0 -0
  23. /package/dist/{4683.bundle.b02523a9d66f4d19.js → 4683.bundle.ed3f14fcb89fbad1.js} +0 -0
  24. /package/dist/{5189.bundle.2a0f740738759e32.js → 5189.bundle.ea191ea99047fd6f.js} +0 -0
  25. /package/dist/{5642.bundle.1622d255af1102d8.js → 5642.bundle.98e17a4413db6ef1.js} +0 -0
  26. /package/dist/{5696.bundle.2f47cb5ab5a7f983.js → 5696.bundle.e77041f7e2b08236.js} +0 -0
  27. /package/dist/{6749.bundle.9f0550714a4d5f8b.js → 6749.bundle.9dcda33e49485da4.js} +0 -0
  28. /package/dist/{7231.bundle.46f843b74a964f89.js → 7231.bundle.1063d727ad99108a.js} +0 -0
  29. /package/dist/{7248.bundle.bffd4e60f806e25d.js → 7248.bundle.ab6f276adad059da.js} +0 -0
  30. /package/dist/{7499.bundle.bb240e8754167cb2.js → 7499.bundle.832916b9b820c33f.js} +0 -0
  31. /package/dist/{781.bundle.f7411dabe0628911.js → 781.bundle.f06b6ab92ef4d695.js} +0 -0
  32. /package/dist/{8423.bundle.071901427fd3cd9e.js → 8423.bundle.ebf8d7d638d9616b.js} +0 -0
  33. /package/dist/{8492.bundle.9c17dcbab24b07c3.js → 8492.bundle.1ceeb18471c1c06b.js} +0 -0
  34. /package/dist/{8832.bundle.e413f00216682f40.js → 8832.bundle.1eb46bfa451c792f.js} +0 -0
  35. /package/dist/{9116.bundle.1cb5c393e3a152cd.js → 9116.bundle.1d0c6c18a4db51cc.js} +0 -0
  36. /package/dist/{9290.bundle.77a274271ce884a3.js → 9290.bundle.82e8dfc0f73c41bb.js} +0 -0
  37. /package/dist/{9383.bundle.9b1d03c71cc52f4f.js → 9383.bundle.ec44c452ccc23e93.js} +0 -0
  38. /package/dist/{9415.bundle.d3532cc872787744.js → 9415.bundle.69d74a2b03d5d6e4.js} +0 -0
  39. /package/dist/{9437.bundle.a18085ed31658c82.js → 9437.bundle.ca5db89624e35827.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- (globalThis["rspackChunk"] = globalThis["rspackChunk"] || []).push([[2690], {
2
+ (globalThis["rspackChunk"] = globalThis["rspackChunk"] || []).push([[3568], {
3
3
  69501(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
4
4
  __webpack_require__.d(__webpack_exports__, {
5
5
  A: () => (__rspack_default_export)
@@ -855,6 +855,182 @@ function getVolumeViewportLabelmapImageMapperState(viewport) {
855
855
 
856
856
 
857
857
 
858
+ },
859
+ 88392(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
860
+ __webpack_require__.d(__webpack_exports__, {
861
+ $n: () => (removeLabelmap),
862
+ AD: () => (registerLabelmap),
863
+ BS: () => (getLabelmapForVolumeId),
864
+ Hs: () => (getLabelmap),
865
+ M4: () => (getLabelmapForImageId),
866
+ kL: () => (getOrCreateLabelmapVolume),
867
+ m: () => (getLabelmaps)
868
+ });
869
+ /* import */ var _cornerstonejs_core__rspack_import_0 = __webpack_require__(88479);
870
+ /* import */ var _normalizeLabelmapSegmentationData_js__rspack_import_1 = __webpack_require__(209);
871
+ /* import */ var _labelmapImageIdMapping_js__rspack_import_2 = __webpack_require__(26372);
872
+
873
+
874
+
875
+ function getLabelmap(segmentation, labelmapId) {
876
+ return (0,_normalizeLabelmapSegmentationData_js__rspack_import_1/* .ensureLabelmapState */.uk)(segmentation)?.labelmaps?.[labelmapId];
877
+ }
878
+ function getLabelmaps(segmentation) {
879
+ const labelmapState = (0,_normalizeLabelmapSegmentationData_js__rspack_import_1/* .ensureLabelmapState */.uk)(segmentation);
880
+ if (!labelmapState) {
881
+ return [];
882
+ }
883
+ return Object.values(labelmapState.labelmaps);
884
+ }
885
+ function registerLabelmap(segmentation, layer) {
886
+ const labelmapState = (0,_normalizeLabelmapSegmentationData_js__rspack_import_1/* .ensureLabelmapState */.uk)(segmentation);
887
+ if (!labelmapState) {
888
+ return;
889
+ }
890
+ labelmapState.labelmaps[layer.labelmapId] = layer;
891
+ }
892
+ function removeLabelmap(segmentation, labelmapId) {
893
+ const labelmapState = (0,_normalizeLabelmapSegmentationData_js__rspack_import_1/* .ensureLabelmapState */.uk)(segmentation);
894
+ if (!labelmapState) {
895
+ return;
896
+ }
897
+ const layer = labelmapState.labelmaps[labelmapId];
898
+ if (layer?.geometryVolumeId && _cornerstonejs_core__rspack_import_0.cache.getVolume(layer.geometryVolumeId)) {
899
+ _cornerstonejs_core__rspack_import_0.cache.removeVolumeLoadObject(layer.geometryVolumeId);
900
+ }
901
+ delete labelmapState.labelmaps[labelmapId];
902
+ }
903
+ function getOrCreateLabelmapVolume(layer) {
904
+ const mergedVolume = getOrCreateMergedStackLabelmapVolume(layer);
905
+ if (mergedVolume) {
906
+ return mergedVolume;
907
+ }
908
+ const existingVolumeId = layer.volumeId ?? layer.geometryVolumeId;
909
+ if (existingVolumeId) {
910
+ const cachedVolume = _cornerstonejs_core__rspack_import_0.cache.getVolume(existingVolumeId);
911
+ if (cachedVolume) {
912
+ return cachedVolume;
913
+ }
914
+ }
915
+ const imageIds = layer.imageIds ?? [];
916
+ if (!imageIds.length) {
917
+ return;
918
+ }
919
+ const volumeId = layer.volumeId ?? layer.geometryVolumeId ?? `${layer.labelmapId}-geometry`;
920
+ if (!layer.volumeId) {
921
+ layer.geometryVolumeId = volumeId;
922
+ }
923
+ const cachedVolume = _cornerstonejs_core__rspack_import_0.cache.getVolume(volumeId);
924
+ if (cachedVolume) {
925
+ return cachedVolume;
926
+ }
927
+ return _cornerstonejs_core__rspack_import_0.volumeLoader.createAndCacheVolumeFromImagesSync(volumeId, imageIds);
928
+ }
929
+ function getOrCreateMergedStackLabelmapVolume(layer) {
930
+ if (layer.volumeId || !hasDuplicateReferencedImageIds(layer)) {
931
+ return;
932
+ }
933
+ const volumeId = layer.geometryVolumeId ?? `${layer.labelmapId}-geometry`;
934
+ const cachedVolume = _cornerstonejs_core__rspack_import_0.cache.getVolume(volumeId);
935
+ if (cachedVolume) {
936
+ return cachedVolume;
937
+ }
938
+ const imageIdsByReferencedImageId = new Map();
939
+ (0,_labelmapImageIdMapping_js__rspack_import_2/* .forEachLabelmapImageReference */.Zd)(layer, (referencedImageId, imageId) => {
940
+ const imageIdsForReference = imageIdsByReferencedImageId.get(referencedImageId) ?? [];
941
+ imageIdsForReference.push(imageId);
942
+ imageIdsByReferencedImageId.set(referencedImageId, imageIdsForReference);
943
+ });
944
+ const mergedReferencedImageIds = Array.from(imageIdsByReferencedImageId.keys());
945
+ if (!mergedReferencedImageIds.length) {
946
+ return;
947
+ }
948
+ let mergedImageIndex = 0;
949
+ const mergedImages = _cornerstonejs_core__rspack_import_0.imageLoader.createAndCacheDerivedImages(mergedReferencedImageIds, {
950
+ getDerivedImageId: () => `${volumeId}-image-${mergedImageIndex++}`,
951
+ targetBuffer: { type: 'Uint8Array' },
952
+ });
953
+ mergedImages.forEach((mergedImage) => {
954
+ const sourceImageIds = imageIdsByReferencedImageId.get(mergedImage.referencedImageId);
955
+ if (!sourceImageIds?.length) {
956
+ return;
957
+ }
958
+ const targetVoxelManager = mergedImage.voxelManager;
959
+ const scalarDataLength = targetVoxelManager.getScalarDataLength();
960
+ sourceImageIds.forEach((sourceImageId) => {
961
+ const sourceImage = _cornerstonejs_core__rspack_import_0.cache.getImage(sourceImageId);
962
+ const sourceVoxelManager = sourceImage?.voxelManager;
963
+ if (!sourceVoxelManager) {
964
+ return;
965
+ }
966
+ const sourceScalarDataLength = sourceVoxelManager.getScalarDataLength();
967
+ const length = Math.min(scalarDataLength, sourceScalarDataLength);
968
+ for (let index = 0; index < length; index++) {
969
+ const value = Number(sourceVoxelManager.getAtIndex(index));
970
+ if (value !== 0) {
971
+ targetVoxelManager.setAtIndex(index, value);
972
+ }
973
+ }
974
+ });
975
+ });
976
+ layer.geometryVolumeId = volumeId;
977
+ return _cornerstonejs_core__rspack_import_0.volumeLoader.createAndCacheVolumeFromImagesSync(volumeId, mergedImages.map((image) => image.imageId));
978
+ }
979
+ function hasDuplicateReferencedImageIds(layer) {
980
+ return (0,_labelmapImageIdMapping_js__rspack_import_2/* .hasMultipleLabelmapImagesPerReferencedImageId */.dm)(layer);
981
+ }
982
+ function getLabelmapIds(segmentation) {
983
+ return getLabelmaps(segmentation).map((layer) => layer.labelmapId);
984
+ }
985
+ function getLabelmapDataById(segmentation, labelmapId) {
986
+ const labelmapState = ensureLabelmapState(segmentation);
987
+ const layer = labelmapState?.labelmaps?.[labelmapId];
988
+ if (!labelmapState || !layer) {
989
+ return;
990
+ }
991
+ return {
992
+ volumeId: layer.volumeId,
993
+ referencedVolumeId: layer.referencedVolumeId,
994
+ imageIds: layer.imageIds,
995
+ referencedImageIds: layer.referencedImageIds,
996
+ sourceRepresentationName: labelmapState.sourceRepresentationName,
997
+ primaryLabelmapId: labelmapId,
998
+ labelmaps: {
999
+ [labelmapId]: layer,
1000
+ },
1001
+ segmentBindings: Object.fromEntries(Object.entries(labelmapState.segmentBindings).filter(([, binding]) => binding.labelmapId === labelmapId)),
1002
+ };
1003
+ }
1004
+ function getScalarArrayLengthFromLabelmap(layer) {
1005
+ if (layer.volumeId) {
1006
+ return cache.getVolume(layer.volumeId)?.voxelManager?.getScalarDataLength();
1007
+ }
1008
+ const firstImageId = layer.imageIds?.[0];
1009
+ const firstImage = firstImageId ? cache.getImage(firstImageId) : null;
1010
+ if (!firstImage || !layer.imageIds?.length) {
1011
+ return 0;
1012
+ }
1013
+ return firstImage.voxelManager.getScalarDataLength() * layer.imageIds.length;
1014
+ }
1015
+ function getConstructorNameForLabelmap(layer) {
1016
+ if (layer.volumeId) {
1017
+ return cache.getVolume(layer.volumeId)?.voxelManager?.getConstructor()
1018
+ ?.name;
1019
+ }
1020
+ const imageId = layer.imageIds?.[0];
1021
+ return imageId
1022
+ ? cache.getImage(imageId)?.voxelManager?.getConstructor()?.name
1023
+ : undefined;
1024
+ }
1025
+ function getLabelmapForImageId(segmentation, imageId) {
1026
+ return getLabelmaps(segmentation).find((layer) => layer.imageIds?.includes(imageId));
1027
+ }
1028
+ function getLabelmapForVolumeId(segmentation, volumeId) {
1029
+ return getLabelmaps(segmentation).find((layer) => layer.volumeId === volumeId || layer.geometryVolumeId === volumeId);
1030
+ }
1031
+
1032
+
1033
+
858
1034
  },
859
1035
  9484(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
860
1036
  __webpack_require__.d(__webpack_exports__, {
@@ -1707,6 +1883,19 @@ function defaultGetTextLines(data, targetId) {
1707
1883
  /* export default */ const __rspack_default_export = (BidirectionalTool);
1708
1884
 
1709
1885
 
1886
+ },
1887
+ 58690(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
1888
+ __webpack_require__.d(__webpack_exports__, {
1889
+ kC: () => (BLOCKED_CURSOR),
1890
+ t5: () => (PENDING_CURSOR),
1891
+ ys: () => (PLUS_CURSOR)
1892
+ });
1893
+ const CIRCLE_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3C/svg%3E\") 12 12, crosshair";
1894
+ const PLUS_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3Cpath%20d='M12%208v8M8%2012h8'%20stroke='%2300dc82'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, copy";
1895
+ const BLOCKED_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23ff5a5a'%20stroke-width='2'/%3E%3Cpath%20d='M5.7%205.7L18.3%2018.3'%20stroke='%23ff5a5a'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, not-allowed";
1896
+ const PENDING_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23a0a6ad'%20stroke-width='2'%20stroke-dasharray='4%203'/%3E%3C/svg%3E\") 12 12, progress";
1897
+
1898
+
1710
1899
  },
1711
1900
  47844(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
1712
1901
  __webpack_require__.d(__webpack_exports__, {
@@ -4432,6 +4621,7 @@ class BrushTool extends _LabelmapBaseTool_js__rspack_import_9/* ["default"] */.A
4432
4621
  };
4433
4622
  this._hoverData = this.createHoverData(element, canvasPoint);
4434
4623
  if (!this._hoverData) {
4624
+ this._editData = null;
4435
4625
  return false;
4436
4626
  }
4437
4627
  this._calculateCursor(element, canvasPoint);
@@ -5964,6 +6154,180 @@ function lpad(string, character, length) {
5964
6154
  /* export default */ const __rspack_default_export = (floodFill);
5965
6155
 
5966
6156
 
6157
+ },
6158
+ 10621(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
6159
+ __webpack_require__.d(__webpack_exports__, {
6160
+ x: () => (floodFill3dSliceLazy)
6161
+ });
6162
+ const FLOOD_SLICE_FLAG_VISITED = 1;
6163
+ async function floodFill3dSliceLazy(getter, seed, options) {
6164
+ const { width: w, height: h, depth: d, equals, ensureSliceLoaded, yieldEvery = 500, planar = false, maxDeltaK, maxDeltaIJ, isCancelled, maxVoxels, shouldContinue, validateEvery = 2048, } = options;
6165
+ const [sx, sy, sz] = seed;
6166
+ if (sx < 0 || sx >= w || sy < 0 || sy >= h || sz < 0 || sz >= d) {
6167
+ return {
6168
+ sliceMasks: new Map(),
6169
+ voxelCount: 0,
6170
+ truncated: false,
6171
+ bbox: null,
6172
+ };
6173
+ }
6174
+ if (ensureSliceLoaded) {
6175
+ await ensureSliceLoaded(sz);
6176
+ }
6177
+ const startNode = getter(sx, sy, sz);
6178
+ if (!equals(startNode, startNode)) {
6179
+ return {
6180
+ sliceMasks: new Map(),
6181
+ voxelCount: 0,
6182
+ truncated: false,
6183
+ bbox: null,
6184
+ };
6185
+ }
6186
+ const frameSize = w * h;
6187
+ const sliceMasks = new Map();
6188
+ function sliceFlags(z) {
6189
+ let a = sliceMasks.get(z);
6190
+ if (!a) {
6191
+ a = new Uint8Array(frameSize);
6192
+ sliceMasks.set(z, a);
6193
+ }
6194
+ return a;
6195
+ }
6196
+ function isVisited(z, x, y) {
6197
+ const a = sliceMasks.get(z);
6198
+ if (!a) {
6199
+ return false;
6200
+ }
6201
+ return (a[y * w + x] & FLOOD_SLICE_FLAG_VISITED) !== 0;
6202
+ }
6203
+ function setVisited(z, x, y) {
6204
+ sliceFlags(z)[y * w + x] |= FLOOD_SLICE_FLAG_VISITED;
6205
+ }
6206
+ function pack(x, y, z) {
6207
+ return z * frameSize + y * w + x;
6208
+ }
6209
+ function unpack(p) {
6210
+ const x = p % w;
6211
+ const t1 = Math.floor(p / w);
6212
+ const y = t1 % h;
6213
+ const z = Math.floor(t1 / h);
6214
+ return [x, y, z];
6215
+ }
6216
+ const dirs = planar
6217
+ ? [
6218
+ [1, 0, 0],
6219
+ [-1, 0, 0],
6220
+ [0, 1, 0],
6221
+ [0, -1, 0],
6222
+ ]
6223
+ : [
6224
+ [1, 0, 0],
6225
+ [-1, 0, 0],
6226
+ [0, 1, 0],
6227
+ [0, -1, 0],
6228
+ [0, 0, 1],
6229
+ [0, 0, -1],
6230
+ ];
6231
+ const queue = [];
6232
+ let qh = 0;
6233
+ queue.push(pack(sx, sy, sz));
6234
+ setVisited(sz, sx, sy);
6235
+ let minX = sx;
6236
+ let maxX = sx;
6237
+ let minY = sy;
6238
+ let maxY = sy;
6239
+ let minZ = sz;
6240
+ let maxZ = sz;
6241
+ const currentBBox = () => ({
6242
+ min: [minX, minY, minZ],
6243
+ max: [maxX, maxY, maxZ],
6244
+ });
6245
+ let steps = 0;
6246
+ let truncated = false;
6247
+ let nextValidateAt = validateEvery;
6248
+ while (qh < queue.length) {
6249
+ if (isCancelled?.()) {
6250
+ break;
6251
+ }
6252
+ if (maxVoxels !== undefined && queue.length > maxVoxels) {
6253
+ truncated = true;
6254
+ break;
6255
+ }
6256
+ if (shouldContinue && queue.length >= nextValidateAt) {
6257
+ nextValidateAt += validateEvery;
6258
+ if (!shouldContinue({ voxelCount: queue.length, bbox: currentBBox() })) {
6259
+ truncated = true;
6260
+ break;
6261
+ }
6262
+ }
6263
+ steps++;
6264
+ if (yieldEvery > 0 && steps % yieldEvery === 0) {
6265
+ await new Promise((r) => setTimeout(r, 0));
6266
+ }
6267
+ const p = queue[qh++];
6268
+ const [x, y, z] = unpack(p);
6269
+ for (let di = 0; di < dirs.length; di++) {
6270
+ const nx = x + dirs[di][0];
6271
+ const ny = y + dirs[di][1];
6272
+ const nz = z + dirs[di][2];
6273
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h || nz < 0 || nz >= d) {
6274
+ continue;
6275
+ }
6276
+ if (maxDeltaK >= 0 && Math.abs(nz - sz) > maxDeltaK) {
6277
+ continue;
6278
+ }
6279
+ if (maxDeltaIJ >= 0 &&
6280
+ (Math.abs(nx - sx) > maxDeltaIJ || Math.abs(ny - sy) > maxDeltaIJ)) {
6281
+ continue;
6282
+ }
6283
+ if (planar && nz !== sz) {
6284
+ continue;
6285
+ }
6286
+ if (isVisited(nz, nx, ny)) {
6287
+ continue;
6288
+ }
6289
+ if (ensureSliceLoaded) {
6290
+ await ensureSliceLoaded(nz);
6291
+ if (isCancelled?.()) {
6292
+ break;
6293
+ }
6294
+ }
6295
+ const nv = getter(nx, ny, nz);
6296
+ if (!equals(nv, startNode)) {
6297
+ continue;
6298
+ }
6299
+ setVisited(nz, nx, ny);
6300
+ queue.push(pack(nx, ny, nz));
6301
+ if (nx < minX) {
6302
+ minX = nx;
6303
+ }
6304
+ else if (nx > maxX) {
6305
+ maxX = nx;
6306
+ }
6307
+ if (ny < minY) {
6308
+ minY = ny;
6309
+ }
6310
+ else if (ny > maxY) {
6311
+ maxY = ny;
6312
+ }
6313
+ if (nz < minZ) {
6314
+ minZ = nz;
6315
+ }
6316
+ else if (nz > maxZ) {
6317
+ maxZ = nz;
6318
+ }
6319
+ }
6320
+ }
6321
+ const voxelCount = queue.length;
6322
+ return {
6323
+ sliceMasks,
6324
+ voxelCount,
6325
+ truncated,
6326
+ bbox: voxelCount > 0 ? currentBBox() : null,
6327
+ };
6328
+ }
6329
+
6330
+
5967
6331
  },
5968
6332
  91662(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
5969
6333
  __webpack_require__.d(__webpack_exports__, {
@@ -6242,6 +6606,34 @@ const DEFAULT_NEGATIVE_SEEDS_COUNT = 70;
6242
6606
  const MAX_NEGATIVE_SEED_ATTEMPTS_MULTIPLIER = 50;
6243
6607
 
6244
6608
 
6609
+ },
6610
+ 1967(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
6611
+ __webpack_require__.d(__webpack_exports__, {
6612
+ K: () => (getViewportVoiMappingForVolume)
6613
+ });
6614
+ function getViewportVoiMappingForVolume(viewport, volumeId) {
6615
+ const getProps = viewport.getProperties;
6616
+ if (typeof getProps !== 'function') {
6617
+ return null;
6618
+ }
6619
+ const props = volumeId
6620
+ ? getProps.call(viewport, volumeId)
6621
+ : getProps.call(viewport);
6622
+ if (!props?.voiRange) {
6623
+ return null;
6624
+ }
6625
+ const { lower, upper } = props.voiRange;
6626
+ if (typeof lower !== 'number' || typeof upper !== 'number') {
6627
+ return null;
6628
+ }
6629
+ return {
6630
+ voiRange: { lower, upper },
6631
+ VOILUTFunction: props.VOILUTFunction,
6632
+ invert: props.invert === true,
6633
+ };
6634
+ }
6635
+
6636
+
6245
6637
  },
6246
6638
  20326(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
6247
6639
  __webpack_require__.d(__webpack_exports__, {
@@ -6397,90 +6789,1540 @@ fn main(
6397
6789
 
6398
6790
 
6399
6791
  },
6400
- 14386(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
6792
+ 81537(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
6401
6793
  __webpack_require__.d(__webpack_exports__, {
6402
- e: () => (runGrowCut)
6794
+ CN: () => (resolveAdaptiveBandAtTolerance),
6795
+ Ti: () => (probeAdaptiveRegion)
6403
6796
  });
6404
6797
  /* import */ var _cornerstonejs_core__rspack_import_0 = __webpack_require__(88479);
6405
- /* import */ var _growCutShader_js__rspack_import_1 = __webpack_require__(20326);
6798
+ /* import */ var _getViewportVoiMappingForVolume_js__rspack_import_1 = __webpack_require__(1967);
6799
+ /* import */ var _constants_js__rspack_import_2 = __webpack_require__(29735);
6406
6800
 
6407
6801
 
6408
- const GB = 1024 * 1024 * 1024;
6409
- const WEBGPU_MEMORY_LIMIT = 1.99 * GB;
6410
- const DEFAULT_GROWCUT_OPTIONS = {
6411
- windowSize: 3,
6412
- maxProcessingTime: 30000,
6413
- inspection: {
6414
- numCyclesInterval: 5,
6415
- numCyclesBelowThreshold: 3,
6416
- threshold: 1e-4,
6417
- },
6418
- };
6419
- async function runGrowCut(referenceVolumeId, labelmapVolumeId, options = DEFAULT_GROWCUT_OPTIONS) {
6420
- const workGroupSize = [8, 8, 4];
6421
- const { windowSize, maxProcessingTime } = Object.assign({}, DEFAULT_GROWCUT_OPTIONS, options);
6422
- const inspection = Object.assign({}, DEFAULT_GROWCUT_OPTIONS.inspection, options.inspection);
6423
- const volume = _cornerstonejs_core__rspack_import_0.cache.getVolume(referenceVolumeId);
6424
- const labelmap = _cornerstonejs_core__rspack_import_0.cache.getVolume(labelmapVolumeId);
6425
- const [columns, rows, numSlices] = volume.dimensions;
6426
- if (labelmap.dimensions[0] !== columns ||
6427
- labelmap.dimensions[1] !== rows ||
6428
- labelmap.dimensions[2] !== numSlices) {
6429
- throw new Error('Volume and labelmap must have the same size');
6802
+
6803
+ const { transformWorldToIndex, mapScalarToViewportVoiIntensity, mapViewportVoiIntensityToScalar, getVolumeDirectionVectors, } = _cornerstonejs_core__rspack_import_0.utilities;
6804
+ const { growCutLog: log } = _cornerstonejs_core__rspack_import_0.utilities.logger;
6805
+ const WINDOW_RADIUS_PX = 96;
6806
+ const SEED_SNAP_RADIUS_PX = 4;
6807
+ const MIN_REGION_PX = 6;
6808
+ const MIN_REGION_MM2 = 8;
6809
+ const MAX_REGION_WINDOW_FRACTION = 0.6;
6810
+ const FLAT_CONTRAST_BYTES = 10;
6811
+ const MIN_TOLERANCE_BYTES = 2;
6812
+ const MIN_PLATEAU_WIDTH_BYTES = 4;
6813
+ const QUIET_GAIN_ABS_PX = 3;
6814
+ const QUIET_GAIN_FRACTION = 0.02;
6815
+ function medianOf(values) {
6816
+ if (!values.length) {
6817
+ return 0;
6430
6818
  }
6431
- let numIterations = Math.floor(Math.sqrt(rows ** 2 + columns ** 2 + numSlices ** 2) / 2);
6432
- numIterations = Math.min(numIterations, 500);
6433
- const labelmapData = labelmap.voxelManager.getCompleteScalarDataArray();
6434
- let volumePixelData = volume.voxelManager.getCompleteScalarDataArray();
6435
- if (!(volumePixelData instanceof Float32Array)) {
6436
- volumePixelData = new Float32Array(volumePixelData);
6819
+ const sorted = values.slice().sort((a, b) => a - b);
6820
+ return sorted[Math.floor(sorted.length / 2)];
6821
+ }
6822
+ function rawBandForTolerance(context, toleranceBytes) {
6823
+ const { seedByte, polarity, voiMapping, rawWindow, seedScalar } = context;
6824
+ const thresholdByte = Math.max(0, Math.min(255, polarity > 0 ? seedByte - toleranceBytes : seedByte + toleranceBytes));
6825
+ const includedExtremeByte = polarity > 0 ? 255 : 0;
6826
+ const rawFromByte = (byte) => voiMapping
6827
+ ? mapViewportVoiIntensityToScalar(byte / 255, voiMapping)
6828
+ : rawWindow.min + (byte / 255) * rawWindow.span;
6829
+ const rawThreshold = rawFromByte(thresholdByte);
6830
+ const rawExtreme = rawFromByte(includedExtremeByte);
6831
+ let min;
6832
+ let max;
6833
+ if (rawExtreme >= rawThreshold) {
6834
+ min = rawThreshold;
6835
+ max = Infinity;
6437
6836
  }
6438
- const requiredLimits = {
6439
- maxStorageBufferBindingSize: WEBGPU_MEMORY_LIMIT,
6440
- maxBufferSize: WEBGPU_MEMORY_LIMIT,
6837
+ else {
6838
+ min = -Infinity;
6839
+ max = rawThreshold;
6840
+ }
6841
+ if (Number.isFinite(seedScalar)) {
6842
+ min = Math.min(min, seedScalar);
6843
+ max = Math.max(max, seedScalar);
6844
+ }
6845
+ return { min, max };
6846
+ }
6847
+ function resolveAdaptiveBandAtTolerance(context, toleranceBytes) {
6848
+ const tolerance = Math.max(0, Math.min(255, Math.round(toleranceBytes)));
6849
+ const { min, max } = rawBandForTolerance(context, tolerance);
6850
+ return {
6851
+ min,
6852
+ max,
6853
+ ijkStart: [...context.seedIjk],
6854
+ diagnostics: {
6855
+ neighborhoodMean: Number.isFinite(context.seedScalar)
6856
+ ? context.seedScalar
6857
+ : 0,
6858
+ neighborhoodStdDev: 0,
6859
+ clickedVoxelValue: Number.isFinite(context.seedScalar)
6860
+ ? context.seedScalar
6861
+ : 0,
6862
+ positiveStdDevMultiplier: 1,
6863
+ neighborhoodRadius: SEED_SNAP_RADIUS_PX,
6864
+ strategy: 'adaptiveRegion:atTolerance',
6865
+ adaptive: {
6866
+ toleranceBytes: tolerance,
6867
+ regionSizePx: -1,
6868
+ backgroundByte: -1,
6869
+ seedByte: context.seedByte,
6870
+ seedSnapped: false,
6871
+ windowSize: [0, 0],
6872
+ polarity: context.polarity,
6873
+ },
6874
+ },
6441
6875
  };
6442
- const adapter = await navigator.gpu?.requestAdapter();
6443
- const device = await adapter.requestDevice({ requiredLimits });
6444
- const BUFFER_SIZE = volumePixelData.byteLength;
6445
- const UPDATED_VOXELS_COUNTER_BUFFER_SIZE = numIterations * Uint32Array.BYTES_PER_ELEMENT;
6446
- const BOUNDS_BUFFER_SIZE = 6 * Int32Array.BYTES_PER_ELEMENT;
6447
- const shaderModule = device.createShaderModule({
6448
- code: _growCutShader_js__rspack_import_1/* ["default"] */.A,
6449
- });
6450
- const numIterationIndex = 3;
6451
- const paramsArrayValues = new Uint32Array([
6452
- columns,
6453
- rows,
6454
- numSlices,
6455
- 0,
6456
- ]);
6457
- const gpuParamsBuffer = device.createBuffer({
6458
- size: paramsArrayValues.byteLength,
6459
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
6460
- });
6461
- const gpuVolumePixelDataBuffer = device.createBuffer({
6462
- size: BUFFER_SIZE,
6463
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
6464
- });
6465
- device.queue.writeBuffer(gpuVolumePixelDataBuffer, 0, volumePixelData);
6466
- const gpuLabelmapBuffers = [0, 1].map(() => device.createBuffer({
6467
- size: BUFFER_SIZE,
6468
- usage: GPUBufferUsage.STORAGE |
6469
- GPUBufferUsage.COPY_SRC |
6470
- GPUBufferUsage.COPY_DST,
6471
- }));
6472
- device.queue.writeBuffer(gpuLabelmapBuffers[0], 0, new Uint32Array(labelmapData));
6473
- const gpuStrengthBuffers = [0, 1].map(() => {
6474
- const strengthBuffer = device.createBuffer({
6475
- size: BUFFER_SIZE,
6476
- usage: GPUBufferUsage.STORAGE |
6477
- GPUBufferUsage.COPY_SRC |
6478
- GPUBufferUsage.COPY_DST,
6479
- });
6480
- return strengthBuffer;
6876
+ }
6877
+ function probeAdaptiveRegionCore(input) {
6878
+ const { dimensions, getScalar, ijkClick, voiMapping } = input;
6879
+ const inPlaneAxes = input.inPlaneAxes ?? [0, 1];
6880
+ const inPlaneSpacing = input.inPlaneSpacing ?? [1, 1];
6881
+ for (let axis = 0; axis < 3; axis++) {
6882
+ if (ijkClick[axis] < 0 || ijkClick[axis] >= dimensions[axis]) {
6883
+ return { viable: false, reason: 'outside-volume', range: null };
6884
+ }
6885
+ }
6886
+ const [axisA, axisB] = inPlaneAxes;
6887
+ const a0 = Math.max(0, ijkClick[axisA] - WINDOW_RADIUS_PX);
6888
+ const a1 = Math.min(dimensions[axisA] - 1, ijkClick[axisA] + WINDOW_RADIUS_PX);
6889
+ const b0 = Math.max(0, ijkClick[axisB] - WINDOW_RADIUS_PX);
6890
+ const b1 = Math.min(dimensions[axisB] - 1, ijkClick[axisB] + WINDOW_RADIUS_PX);
6891
+ const wA = a1 - a0 + 1;
6892
+ const wB = b1 - b0 + 1;
6893
+ const windowArea = wA * wB;
6894
+ const spacingA = Number.isFinite(inPlaneSpacing[0]) && inPlaneSpacing[0] > 0
6895
+ ? inPlaneSpacing[0]
6896
+ : 1;
6897
+ const spacingB = Number.isFinite(inPlaneSpacing[1]) && inPlaneSpacing[1] > 0
6898
+ ? inPlaneSpacing[1]
6899
+ : 1;
6900
+ const pxAreaMm2 = spacingA * spacingB;
6901
+ const minRegionPx = Math.max(MIN_REGION_PX, Math.ceil(MIN_REGION_MM2 / pxAreaMm2));
6902
+ const maxRegionPx = Math.max(minRegionPx + 1, Math.floor(windowArea * MAX_REGION_WINDOW_FRACTION));
6903
+ const scalarAt = (x, y) => {
6904
+ const ijk = [...ijkClick];
6905
+ ijk[axisA] = a0 + x;
6906
+ ijk[axisB] = b0 + y;
6907
+ return Number(getScalar(ijk[0], ijk[1], ijk[2]));
6908
+ };
6909
+ const rawValues = new Float64Array(windowArea);
6910
+ let rawMinInWindow = Infinity;
6911
+ let rawMaxInWindow = -Infinity;
6912
+ for (let y = 0; y < wB; y++) {
6913
+ for (let x = 0; x < wA; x++) {
6914
+ const v = scalarAt(x, y);
6915
+ rawValues[y * wA + x] = v;
6916
+ if (Number.isFinite(v)) {
6917
+ if (v < rawMinInWindow) {
6918
+ rawMinInWindow = v;
6919
+ }
6920
+ if (v > rawMaxInWindow) {
6921
+ rawMaxInWindow = v;
6922
+ }
6923
+ }
6924
+ }
6925
+ }
6926
+ if (!Number.isFinite(rawMinInWindow)) {
6927
+ return { viable: false, reason: 'outside-volume', range: null };
6928
+ }
6929
+ const rawSpan = rawMaxInWindow - rawMinInWindow || 1;
6930
+ const bytes = new Int16Array(windowArea);
6931
+ for (let idx = 0; idx < windowArea; idx++) {
6932
+ const v = rawValues[idx];
6933
+ if (!Number.isFinite(v)) {
6934
+ bytes[idx] = -1;
6935
+ continue;
6936
+ }
6937
+ const mapped = voiMapping
6938
+ ? mapScalarToViewportVoiIntensity(v, voiMapping)
6939
+ : (v - rawMinInWindow) / rawSpan;
6940
+ bytes[idx] = Math.max(0, Math.min(255, Math.round(mapped * 255)));
6941
+ }
6942
+ const ringBytes = [];
6943
+ for (let x = 0; x < wA; x++) {
6944
+ if (bytes[x] >= 0) {
6945
+ ringBytes.push(bytes[x]);
6946
+ }
6947
+ const bottom = (wB - 1) * wA + x;
6948
+ if (wB > 1 && bytes[bottom] >= 0) {
6949
+ ringBytes.push(bytes[bottom]);
6950
+ }
6951
+ }
6952
+ for (let y = 1; y < wB - 1; y++) {
6953
+ const left = y * wA;
6954
+ const right = y * wA + (wA - 1);
6955
+ if (bytes[left] >= 0) {
6956
+ ringBytes.push(bytes[left]);
6957
+ }
6958
+ if (wA > 1 && bytes[right] >= 0) {
6959
+ ringBytes.push(bytes[right]);
6960
+ }
6961
+ }
6962
+ const backgroundByte = medianOf(ringBytes);
6963
+ const clickX = ijkClick[axisA] - a0;
6964
+ const clickY = ijkClick[axisB] - b0;
6965
+ let seedX = clickX;
6966
+ let seedY = clickY;
6967
+ let bestContrast = -1;
6968
+ let bestDist = Infinity;
6969
+ const snapR2 = SEED_SNAP_RADIUS_PX * SEED_SNAP_RADIUS_PX;
6970
+ for (let dy = -SEED_SNAP_RADIUS_PX; dy <= SEED_SNAP_RADIUS_PX; dy++) {
6971
+ for (let dx = -SEED_SNAP_RADIUS_PX; dx <= SEED_SNAP_RADIUS_PX; dx++) {
6972
+ if (dx * dx + dy * dy > snapR2) {
6973
+ continue;
6974
+ }
6975
+ const x = clickX + dx;
6976
+ const y = clickY + dy;
6977
+ if (x < 0 || x >= wA || y < 0 || y >= wB) {
6978
+ continue;
6979
+ }
6980
+ const byte = bytes[y * wA + x];
6981
+ if (byte < 0) {
6982
+ continue;
6983
+ }
6984
+ const contrast = Math.abs(byte - backgroundByte);
6985
+ const dist = dx * dx + dy * dy;
6986
+ if (contrast > bestContrast ||
6987
+ (contrast === bestContrast && dist < bestDist)) {
6988
+ bestContrast = contrast;
6989
+ bestDist = dist;
6990
+ seedX = x;
6991
+ seedY = y;
6992
+ }
6993
+ }
6994
+ }
6995
+ if (bestContrast < 0) {
6996
+ return { viable: false, reason: 'outside-volume', range: null };
6997
+ }
6998
+ if (bestContrast < FLAT_CONTRAST_BYTES) {
6999
+ return { viable: false, reason: 'flat-region', range: null };
7000
+ }
7001
+ const seedPixelByte = bytes[seedY * wA + seedX];
7002
+ const polarity = seedPixelByte >= backgroundByte ? 1 : -1;
7003
+ const elevation = (byte) => polarity > 0 ? byte : 255 - byte;
7004
+ const seedNeighborhood = [];
7005
+ for (let dy = -1; dy <= 1; dy++) {
7006
+ for (let dx = -1; dx <= 1; dx++) {
7007
+ const x = seedX + dx;
7008
+ const y = seedY + dy;
7009
+ if (x < 0 || x >= wA || y < 0 || y >= wB) {
7010
+ continue;
7011
+ }
7012
+ const byte = bytes[y * wA + x];
7013
+ if (byte >= 0 &&
7014
+ Math.abs(byte - seedPixelByte) <= Math.abs(byte - backgroundByte)) {
7015
+ seedNeighborhood.push(byte);
7016
+ }
7017
+ }
7018
+ }
7019
+ const v0 = seedNeighborhood.length
7020
+ ? medianOf(seedNeighborhood)
7021
+ : seedPixelByte;
7022
+ const h0 = elevation(v0);
7023
+ const cost = (idx) => {
7024
+ const byte = bytes[idx];
7025
+ if (byte < 0) {
7026
+ return -1;
7027
+ }
7028
+ return Math.max(0, h0 - elevation(byte));
7029
+ };
7030
+ const buckets = new Array(256);
7031
+ const pushToBucket = (level, idx) => {
7032
+ let bucket = buckets[level];
7033
+ if (!bucket) {
7034
+ bucket = [];
7035
+ buckets[level] = bucket;
7036
+ }
7037
+ bucket.push(idx);
7038
+ };
7039
+ const visited = new Uint8Array(windowArea);
7040
+ const seedIdx = seedY * wA + seedX;
7041
+ const seedCost = cost(seedIdx);
7042
+ if (seedCost < 0) {
7043
+ return { viable: false, reason: 'flat-region', range: null };
7044
+ }
7045
+ pushToBucket(seedCost, seedIdx);
7046
+ visited[seedIdx] = 1;
7047
+ const curve = [];
7048
+ const clickIdx = clickY * wA + clickX;
7049
+ let clickJoinLevel = -1;
7050
+ const neighborOffsets = [
7051
+ [-1, 0],
7052
+ [1, 0],
7053
+ [0, -1],
7054
+ [0, 1],
7055
+ ];
7056
+ const clickNeighborJoinLevels = [];
7057
+ const neighborWatch = new Map();
7058
+ neighborOffsets.forEach(([dx, dy], slot) => {
7059
+ const x = clickX + dx;
7060
+ const y = clickY + dy;
7061
+ if (x < 0 || x >= wA || y < 0 || y >= wB) {
7062
+ clickNeighborJoinLevels.push(null);
7063
+ return;
7064
+ }
7065
+ clickNeighborJoinLevels.push(-1);
7066
+ neighborWatch.set(y * wA + x, slot);
6481
7067
  });
6482
- const gpuCounterBuffer = device.createBuffer({
6483
- size: UPDATED_VOXELS_COUNTER_BUFFER_SIZE,
7068
+ let size = 0;
7069
+ let borderTouchLevel = -1;
7070
+ let explosionLevel = -1;
7071
+ outer: for (let level = 0; level < 256; level++) {
7072
+ const bucket = buckets[level];
7073
+ if (bucket?.length) {
7074
+ for (let bi = 0; bi < bucket.length; bi++) {
7075
+ const idx = bucket[bi];
7076
+ if (visited[idx] === 2) {
7077
+ continue;
7078
+ }
7079
+ visited[idx] = 2;
7080
+ size++;
7081
+ if (idx === clickIdx) {
7082
+ clickJoinLevel = level;
7083
+ }
7084
+ const watchSlot = neighborWatch.get(idx);
7085
+ if (watchSlot !== undefined) {
7086
+ clickNeighborJoinLevels[watchSlot] = level;
7087
+ }
7088
+ const x = idx % wA;
7089
+ const y = Math.floor(idx / wA);
7090
+ if (borderTouchLevel < 0 &&
7091
+ (x === 0 || x === wA - 1 || y === 0 || y === wB - 1)) {
7092
+ borderTouchLevel = level;
7093
+ }
7094
+ if (size > maxRegionPx) {
7095
+ explosionLevel = level;
7096
+ break outer;
7097
+ }
7098
+ const neighbors = [idx - 1, idx + 1, idx - wA, idx + wA];
7099
+ const canLeft = x > 0;
7100
+ const canRight = x < wA - 1;
7101
+ const canUp = y > 0;
7102
+ const canDown = y < wB - 1;
7103
+ const allowed = [canLeft, canRight, canUp, canDown];
7104
+ for (let n = 0; n < 4; n++) {
7105
+ if (!allowed[n]) {
7106
+ continue;
7107
+ }
7108
+ const nIdx = neighbors[n];
7109
+ if (visited[nIdx] !== 0) {
7110
+ continue;
7111
+ }
7112
+ const nCost = cost(nIdx);
7113
+ if (nCost < 0) {
7114
+ continue;
7115
+ }
7116
+ visited[nIdx] = 1;
7117
+ pushToBucket(Math.max(level, nCost), nIdx);
7118
+ }
7119
+ }
7120
+ bucket.length = 0;
7121
+ }
7122
+ const lastSize = curve.length ? curve[curve.length - 1].size : 0;
7123
+ if (size > lastSize) {
7124
+ curve.push({ level, size });
7125
+ }
7126
+ }
7127
+ const growthEndLevel = explosionLevel >= 0 ? explosionLevel - 1 : 255;
7128
+ const runs = [];
7129
+ {
7130
+ let runStart = -1;
7131
+ let currentSize = 0;
7132
+ let ci = 0;
7133
+ const closeRun = (endLevel, sizeAtEnd) => {
7134
+ if (runStart >= 0 && endLevel >= runStart) {
7135
+ runs.push({
7136
+ startLevel: runStart,
7137
+ endLevel,
7138
+ width: endLevel - runStart,
7139
+ size: sizeAtEnd,
7140
+ });
7141
+ }
7142
+ runStart = -1;
7143
+ };
7144
+ for (let level = 0; level <= growthEndLevel; level++) {
7145
+ const sizeBefore = currentSize;
7146
+ let gain = 0;
7147
+ if (ci < curve.length && curve[ci].level === level) {
7148
+ gain = curve[ci].size - currentSize;
7149
+ currentSize = curve[ci].size;
7150
+ ci++;
7151
+ }
7152
+ const quietThreshold = Math.max(QUIET_GAIN_ABS_PX, Math.floor(sizeBefore * QUIET_GAIN_FRACTION));
7153
+ if (currentSize > 0 && gain <= quietThreshold) {
7154
+ if (runStart < 0) {
7155
+ runStart = level;
7156
+ }
7157
+ }
7158
+ else {
7159
+ closeRun(level - 1, sizeBefore);
7160
+ }
7161
+ }
7162
+ closeRun(growthEndLevel, currentSize);
7163
+ }
7164
+ let best = null;
7165
+ for (const run of runs) {
7166
+ const endLevel = borderTouchLevel >= 0
7167
+ ? Math.min(run.endLevel, borderTouchLevel - 1)
7168
+ : run.endLevel;
7169
+ if (endLevel < run.startLevel) {
7170
+ continue;
7171
+ }
7172
+ const clipped = {
7173
+ ...run,
7174
+ endLevel,
7175
+ width: endLevel - run.startLevel,
7176
+ };
7177
+ if (clipped.size < minRegionPx || clipped.size > maxRegionPx) {
7178
+ continue;
7179
+ }
7180
+ if (!best || clipped.width > best.width) {
7181
+ best = clipped;
7182
+ }
7183
+ }
7184
+ const selectionDebug = () => ({
7185
+ growthCurve: curve.map((p) => [p.level, p.size]),
7186
+ quietRuns: runs.map((run) => [
7187
+ run.startLevel,
7188
+ run.endLevel,
7189
+ run.size,
7190
+ ]),
7191
+ seedByte: v0,
7192
+ backgroundByte,
7193
+ polarity,
7194
+ borderTouchLevel,
7195
+ explosionLevel,
7196
+ clickJoinLevel,
7197
+ minRegionPx,
7198
+ maxRegionPx,
7199
+ pxAreaMm2,
7200
+ });
7201
+ if (!best || best.width < MIN_PLATEAU_WIDTH_BYTES) {
7202
+ const stableButSmall = runs.find((run) => run.size < minRegionPx &&
7203
+ run.width >= MIN_PLATEAU_WIDTH_BYTES &&
7204
+ (borderTouchLevel < 0 || run.startLevel < borderTouchLevel));
7205
+ const failSize = best?.size ?? stableButSmall?.size ?? size;
7206
+ return {
7207
+ viable: false,
7208
+ reason: stableButSmall ? 'too-small' : 'unbounded',
7209
+ range: null,
7210
+ regionSizePx: failSize,
7211
+ regionAreaMm2: failSize * pxAreaMm2,
7212
+ debug: selectionDebug(),
7213
+ };
7214
+ }
7215
+ let toleranceBytes = best.startLevel + Math.floor(best.width / 2);
7216
+ toleranceBytes = Math.min(Math.max(toleranceBytes, MIN_TOLERANCE_BYTES), best.endLevel);
7217
+ const toleranceScale = input.toleranceScale && input.toleranceScale > 0 ? input.toleranceScale : 1;
7218
+ toleranceBytes = Math.max(1, Math.min(255, Math.round(toleranceBytes * toleranceScale)));
7219
+ const chosen = best;
7220
+ if (clickJoinLevel < 0 || clickJoinLevel > toleranceBytes) {
7221
+ return {
7222
+ viable: false,
7223
+ reason: 'off-target',
7224
+ range: null,
7225
+ regionSizePx: chosen.size,
7226
+ regionAreaMm2: chosen.size * pxAreaMm2,
7227
+ debug: selectionDebug(),
7228
+ };
7229
+ }
7230
+ const seedIjk = [...ijkClick];
7231
+ seedIjk[axisA] = a0 + seedX;
7232
+ seedIjk[axisB] = b0 + seedY;
7233
+ const seedScalar = rawValues[seedIdx];
7234
+ const clickedScalar = rawValues[clickY * wA + clickX];
7235
+ const expandContext = {
7236
+ seedByte: v0,
7237
+ polarity,
7238
+ voiMapping: voiMapping ?? null,
7239
+ rawWindow: { min: rawMinInWindow, span: rawSpan },
7240
+ seedIjk,
7241
+ seedScalar: Number.isFinite(seedScalar) ? seedScalar : 0,
7242
+ growthCurve: curve.map((p) => [p.level, p.size]),
7243
+ chosenToleranceBytes: toleranceBytes,
7244
+ growthEndLevel,
7245
+ pxAreaMm2,
7246
+ };
7247
+ const { min: rawLo, max: rawHi } = rawBandForTolerance(expandContext, toleranceBytes);
7248
+ return {
7249
+ viable: true,
7250
+ reason: 'ok',
7251
+ regionSizePx: chosen.size,
7252
+ regionAreaMm2: chosen.size * pxAreaMm2,
7253
+ toleranceBytes,
7254
+ clickNeighborJoinLevels,
7255
+ expandContext,
7256
+ range: {
7257
+ min: rawLo,
7258
+ max: rawHi,
7259
+ ijkStart: seedIjk,
7260
+ diagnostics: {
7261
+ neighborhoodMean: Number.isFinite(seedScalar) ? seedScalar : 0,
7262
+ neighborhoodStdDev: 0,
7263
+ clickedVoxelValue: Number.isFinite(clickedScalar) ? clickedScalar : 0,
7264
+ positiveStdDevMultiplier: toleranceScale,
7265
+ neighborhoodRadius: SEED_SNAP_RADIUS_PX,
7266
+ strategy: 'adaptiveRegion',
7267
+ adaptive: {
7268
+ toleranceBytes,
7269
+ regionSizePx: chosen.size,
7270
+ regionAreaMm2: chosen.size * pxAreaMm2,
7271
+ backgroundByte,
7272
+ seedByte: v0,
7273
+ polarity,
7274
+ seedSnapped: seedX !== clickX || seedY !== clickY,
7275
+ windowSize: [wA, wB],
7276
+ growthCurve: curve.map((p) => [p.level, p.size]),
7277
+ explosionLevel,
7278
+ borderTouchLevel,
7279
+ },
7280
+ },
7281
+ },
7282
+ };
7283
+ }
7284
+ function resolveInPlaneAxes(referencedVolume, viewport) {
7285
+ if (!viewport) {
7286
+ return [0, 1];
7287
+ }
7288
+ try {
7289
+ const camera = viewport.getCamera();
7290
+ const { ijkVecSliceDir } = getVolumeDirectionVectors(referencedVolume.imageData, camera);
7291
+ const abs = ijkVecSliceDir.map(Math.abs);
7292
+ let sliceAxis = 0;
7293
+ if (abs[1] >= abs[0] && abs[1] >= abs[2]) {
7294
+ sliceAxis = 1;
7295
+ }
7296
+ else if (abs[2] >= abs[0] && abs[2] >= abs[1]) {
7297
+ sliceAxis = 2;
7298
+ }
7299
+ const axes = [0, 1, 2].filter((axis) => axis !== sliceAxis);
7300
+ return [axes[0], axes[1]];
7301
+ }
7302
+ catch {
7303
+ return [0, 1];
7304
+ }
7305
+ }
7306
+ function probeAdaptiveRegion(referencedVolume, worldPosition, options) {
7307
+ const { dimensions, imageData, spacing } = referencedVolume;
7308
+ const voxelManager = referencedVolume.voxelManager;
7309
+ const [width, height] = dimensions;
7310
+ const pixelsPerSlice = width * height;
7311
+ const ijkClick = transformWorldToIndex(imageData, worldPosition).map(Math.round);
7312
+ const voiMapping = options?.voiMapping ??
7313
+ (options?.viewport && options?.referencedVolumeId
7314
+ ? (0,_getViewportVoiMappingForVolume_js__rspack_import_1/* .getViewportVoiMappingForVolume */.K)(options.viewport, options.referencedVolumeId)
7315
+ : null);
7316
+ const toleranceScale = options?.positiveStdDevMultiplier
7317
+ ? options.positiveStdDevMultiplier / (/* inlined export .DEFAULT_POSITIVE_STD_DEV_MULTIPLIER */1.8)
7318
+ : 1;
7319
+ const inPlaneAxes = resolveInPlaneAxes(referencedVolume, options?.viewport);
7320
+ const inPlaneSpacing = [
7321
+ spacing?.[inPlaneAxes[0]] ?? 1,
7322
+ spacing?.[inPlaneAxes[1]] ?? 1,
7323
+ ];
7324
+ const result = probeAdaptiveRegionCore({
7325
+ dimensions,
7326
+ getScalar: (i, j, k) => Number(voxelManager.getAtIndex(k * pixelsPerSlice + j * width + i)),
7327
+ ijkClick,
7328
+ inPlaneAxes,
7329
+ inPlaneSpacing,
7330
+ voiMapping,
7331
+ toleranceScale,
7332
+ });
7333
+ log.debug('adaptiveRegion probe', {
7334
+ ijkClick,
7335
+ viable: result.viable,
7336
+ reason: result.reason,
7337
+ regionSizePx: result.regionSizePx,
7338
+ regionAreaMm2: result.regionAreaMm2,
7339
+ toleranceBytes: result.toleranceBytes,
7340
+ band: result.range
7341
+ ? { min: result.range.min, max: result.range.max }
7342
+ : null,
7343
+ });
7344
+ return result;
7345
+ }
7346
+ const getAdaptiveRegionIntensityRange = (referencedVolume, worldPosition, options) => {
7347
+ const probe = probeAdaptiveRegion(referencedVolume, worldPosition, options);
7348
+ if (!probe.viable) {
7349
+ log.info('adaptiveRegion: no proper region at click', {
7350
+ reason: probe.reason,
7351
+ regionSizePx: probe.regionSizePx,
7352
+ regionAreaMm2: probe.regionAreaMm2,
7353
+ debug: probe.debug,
7354
+ });
7355
+ return null;
7356
+ }
7357
+ return probe.range;
7358
+ };
7359
+
7360
+
7361
+ },
7362
+ 55600(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
7363
+
7364
+ // EXPORTS
7365
+ __webpack_require__.d(__webpack_exports__, {
7366
+ ZC: () => (/* binding */ runFloodFillSegmentation)
7367
+ });
7368
+
7369
+ // UNUSED EXPORTS: default, getDisplayVoiSnapshot, getPositiveIntensityRange, getPositiveIntensityRangeRaw, getPositiveIntensityRangeVoiMapped
7370
+
7371
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 1 modules
7372
+ var esm = __webpack_require__(88479);
7373
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/islandRemoval.js
7374
+ var segmentation_islandRemoval = __webpack_require__(29827);
7375
+ ;// CONCATENATED MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/floodFillIslandRemoval.js
7376
+
7377
+
7378
+ const { logger } = esm.utilities;
7379
+ const { growCutLog: islandRemovalLog } = logger;
7380
+
7381
+ class FloodFillIslandRemoval extends segmentation_islandRemoval/* ["default"] */.A {
7382
+ constructor(options) {
7383
+ super(options);
7384
+ this.verboseLogging = false;
7385
+ this.usingPreviewLayer = false;
7386
+ this.internalFilledPoints = [];
7387
+ this.verboseLogging = options?.verboseLogging ?? this.verboseLogging;
7388
+ }
7389
+ initialize(viewport, segmentationVoxels, options) {
7390
+ this.usingPreviewLayer = !!segmentationVoxels.sourceVoxelManager;
7391
+ const initialized = super.initialize(viewport, segmentationVoxels, options);
7392
+ if (initialized && this.verboseLogging) {
7393
+ const { segmentSet } = this;
7394
+ islandRemovalLog.info('islandRemoval: initialize', {
7395
+ segmentIndex: this.segmentIndex,
7396
+ previewSegmentIndex: this.previewSegmentIndex,
7397
+ segmentSetDimensions: {
7398
+ width: segmentSet.width,
7399
+ height: segmentSet.height,
7400
+ depth: segmentSet.depth,
7401
+ },
7402
+ boundsIJKPrime: segmentSet.normalizer.boundsIJKPrime,
7403
+ clickedPoints: this.selectedPoints,
7404
+ segmentVoxelsInPlaneGrid: FloodFillIslandRemoval.countRleValueVolume(segmentSet, segmentation_islandRemoval/* .SegmentationEnum.SEGMENT */.a.SEGMENT),
7405
+ });
7406
+ }
7407
+ return initialized;
7408
+ }
7409
+ static countRleValueVolume(segmentSet, value) {
7410
+ let n = 0;
7411
+ segmentSet.forEach((_baseIndex, rle) => {
7412
+ if (rle.value === value) {
7413
+ n += rle.end - rle.start;
7414
+ }
7415
+ });
7416
+ return n;
7417
+ }
7418
+ floodFillSegmentIsland() {
7419
+ if (this.verboseLogging) {
7420
+ const { selectedPoints, segmentSet } = this;
7421
+ const { fromIJK } = segmentSet.normalizer;
7422
+ for (const clickedPoint of selectedPoints) {
7423
+ const ijkPrime = fromIJK(clickedPoint);
7424
+ const atClick = segmentSet.get(segmentSet.toIndex(ijkPrime));
7425
+ if (atClick !== segmentation_islandRemoval/* .SegmentationEnum.SEGMENT */.a.SEGMENT) {
7426
+ islandRemovalLog.info('islandRemoval: floodFillSegmentIsland click skipped (not SEGMENT)', {
7427
+ clickedPointVolumeIJK: clickedPoint,
7428
+ ijkPrime,
7429
+ segmentSetAtIndex: atClick,
7430
+ });
7431
+ }
7432
+ }
7433
+ }
7434
+ const floodedCount = super.floodFillSegmentIsland();
7435
+ if (this.verboseLogging) {
7436
+ islandRemovalLog.info('islandRemoval: floodFillSegmentIsland done', {
7437
+ totalIslandVoxels: floodedCount,
7438
+ islandVoxelsAfterFlood: FloodFillIslandRemoval.countRleValueVolume(this.segmentSet, segmentation_islandRemoval/* .SegmentationEnum.ISLAND */.a.ISLAND),
7439
+ });
7440
+ }
7441
+ return floodedCount;
7442
+ }
7443
+ removeExternalIslands() {
7444
+ const { previewVoxelManager, segmentSet } = this;
7445
+ const { toIJK } = segmentSet.normalizer;
7446
+ const sourceVoxelManager = previewVoxelManager.sourceVoxelManager ?? previewVoxelManager;
7447
+ if (!this.usingPreviewLayer) {
7448
+ islandRemovalLog.warn('islandRemoval: removeExternalIslands has no preview layer; ' +
7449
+ 'external island cleanup of accepted voxels is skipped. ' +
7450
+ 'Run island removal through a preview layer to clear external islands.');
7451
+ return 0;
7452
+ }
7453
+ let clearedVoxels = 0;
7454
+ const callback = (index, rle) => {
7455
+ const [, jPrime, kPrime] = segmentSet.toIJK(index);
7456
+ if (rle.value !== segmentation_islandRemoval/* .SegmentationEnum.ISLAND */.a.ISLAND) {
7457
+ for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
7458
+ const clearPoint = toIJK([iPrime, jPrime, kPrime]);
7459
+ const sourceVal = sourceVoxelManager.getAtIJKPoint(clearPoint);
7460
+ if (sourceVal === this.previewSegmentIndex) {
7461
+ previewVoxelManager.setAtIJKPoint(clearPoint, null);
7462
+ clearedVoxels += 1;
7463
+ }
7464
+ }
7465
+ }
7466
+ };
7467
+ segmentSet.forEach(callback, { rowModified: true });
7468
+ if (this.verboseLogging) {
7469
+ islandRemovalLog.info('islandRemoval: removeExternalIslands', {
7470
+ clearedVoxels,
7471
+ });
7472
+ }
7473
+ return clearedVoxels;
7474
+ }
7475
+ removeInternalIslands() {
7476
+ this.internalFilledPoints = [];
7477
+ const modifiedSlices = super.removeInternalIslands();
7478
+ if (this.verboseLogging) {
7479
+ islandRemovalLog.info('islandRemoval: removeInternalIslands', {
7480
+ modifiedSliceCount: modifiedSlices?.length,
7481
+ internalFilledPoints: this.internalFilledPoints.length,
7482
+ maxInternalRemove: this.maxInternalRemove,
7483
+ });
7484
+ }
7485
+ return modifiedSlices;
7486
+ }
7487
+ onInternalPointFilled(point) {
7488
+ this.internalFilledPoints.push(point);
7489
+ }
7490
+ getInternalFilledPoints() {
7491
+ return this.internalFilledPoints;
7492
+ }
7493
+ }
7494
+
7495
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/floodFillSliceLazy.js
7496
+ var floodFillSliceLazy = __webpack_require__(10621);
7497
+ ;// CONCATENATED MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/commitSliceMasksToLabelmap.js
7498
+
7499
+
7500
+ const { VoxelManager } = esm.utilities;
7501
+ function commitSliceMasksToLabelmapVolume({ labelmapVolume, sliceMasks, width: w, height: h, paintIndex, historyVoxelManager, }) {
7502
+ const floodedPoints = [];
7503
+ let voxelCount = 0;
7504
+ const vm = labelmapVolume.voxelManager;
7505
+ const historyWriter = historyVoxelManager;
7506
+ const [labelmapWidth, labelmapHeight, depth] = labelmapVolume.dimensions;
7507
+ if (labelmapWidth !== w || labelmapHeight !== h) {
7508
+ throw new Error(`commitSliceMasksToLabelmapVolume: labelmap in-plane dimensions ` +
7509
+ `${labelmapWidth}x${labelmapHeight} do not match mask dimensions ${w}x${h}`);
7510
+ }
7511
+ const frameSize = w * h;
7512
+ const expectedLen = frameSize * depth;
7513
+ const zs = Array.from(sliceMasks.keys()).sort((a, b) => a - b);
7514
+ for (let zi = 0; zi < zs.length; zi++) {
7515
+ const z = zs[zi];
7516
+ if (z < 0 || z >= depth) {
7517
+ continue;
7518
+ }
7519
+ const flags = sliceMasks.get(z);
7520
+ if (!flags || flags.length !== frameSize) {
7521
+ continue;
7522
+ }
7523
+ let sliceTouched = false;
7524
+ let minX = Infinity;
7525
+ let minY = Infinity;
7526
+ let maxX = -Infinity;
7527
+ let maxY = -Infinity;
7528
+ const appendRunFlooded = (y, x0, x1) => {
7529
+ for (let xi = x0; xi < x1; xi++) {
7530
+ floodedPoints.push([xi, y, z]);
7531
+ }
7532
+ voxelCount += x1 - x0;
7533
+ sliceTouched = true;
7534
+ minX = Math.min(minX, x0);
7535
+ maxX = Math.max(maxX, x1 - 1);
7536
+ minY = Math.min(minY, y);
7537
+ maxY = Math.max(maxY, y);
7538
+ };
7539
+ const denseScalar = !historyWriter &&
7540
+ vm.scalarData &&
7541
+ vm.scalarData.length >= expectedLen &&
7542
+ typeof vm.scalarData.fill === 'function';
7543
+ if (denseScalar) {
7544
+ const base = z * frameSize;
7545
+ const data = vm.scalarData;
7546
+ for (let y = 0; y < h; y++) {
7547
+ const row = y * w;
7548
+ for (let x = 0; x < w;) {
7549
+ const li = row + x;
7550
+ if (!(flags[li] & (/* inlined export .FLOOD_SLICE_FLAG_VISITED */1))) {
7551
+ x++;
7552
+ continue;
7553
+ }
7554
+ const x0 = x;
7555
+ while (x < w && flags[row + x] & (/* inlined export .FLOOD_SLICE_FLAG_VISITED */1)) {
7556
+ x++;
7557
+ }
7558
+ data.fill(paintIndex, base + row + x0, base + row + x);
7559
+ appendRunFlooded(y, x0, x);
7560
+ }
7561
+ }
7562
+ }
7563
+ else {
7564
+ const imageIds = labelmapVolume.imageIds;
7565
+ const image = imageIds?.length && z < imageIds.length
7566
+ ? esm.cache.getImage(imageIds[z])
7567
+ : null;
7568
+ const svm = image?.voxelManager;
7569
+ if (!historyWriter &&
7570
+ svm?.scalarData &&
7571
+ svm.scalarData.length >= frameSize &&
7572
+ typeof svm.scalarData.fill === 'function') {
7573
+ const data = svm.scalarData;
7574
+ for (let y = 0; y < h; y++) {
7575
+ const row = y * w;
7576
+ for (let x = 0; x < w;) {
7577
+ const li = row + x;
7578
+ if (!(flags[li] & (/* inlined export .FLOOD_SLICE_FLAG_VISITED */1))) {
7579
+ x++;
7580
+ continue;
7581
+ }
7582
+ const x0 = x;
7583
+ while (x < w && flags[row + x] & (/* inlined export .FLOOD_SLICE_FLAG_VISITED */1)) {
7584
+ x++;
7585
+ }
7586
+ data.fill(paintIndex, row + x0, row + x);
7587
+ appendRunFlooded(y, x0, x);
7588
+ }
7589
+ }
7590
+ svm.modifiedSlices.add(z);
7591
+ }
7592
+ else {
7593
+ const writer = historyWriter ?? vm;
7594
+ for (let y = 0; y < h; y++) {
7595
+ const row = y * w;
7596
+ for (let x = 0; x < w; x++) {
7597
+ if (!(flags[row + x] & (/* inlined export .FLOOD_SLICE_FLAG_VISITED */1))) {
7598
+ continue;
7599
+ }
7600
+ const index = z * frameSize + row + x;
7601
+ writer.setAtIndex(index, paintIndex);
7602
+ floodedPoints.push([x, y, z]);
7603
+ voxelCount++;
7604
+ sliceTouched = true;
7605
+ minX = Math.min(minX, x);
7606
+ maxX = Math.max(maxX, x);
7607
+ minY = Math.min(minY, y);
7608
+ maxY = Math.max(maxY, y);
7609
+ }
7610
+ }
7611
+ }
7612
+ }
7613
+ if (sliceTouched) {
7614
+ vm.modifiedSlices.add(z);
7615
+ if (Number.isFinite(minX) &&
7616
+ Number.isFinite(minY) &&
7617
+ Number.isFinite(maxX) &&
7618
+ Number.isFinite(maxY)) {
7619
+ VoxelManager.addBounds(vm.boundsIJK, [minX, minY, z]);
7620
+ VoxelManager.addBounds(vm.boundsIJK, [maxX, maxY, z]);
7621
+ }
7622
+ }
7623
+ }
7624
+ return { floodedPoints, voxelCount };
7625
+ }
7626
+
7627
+ ;// CONCATENATED MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/createEnsureSliceLoadedForVolume.js
7628
+
7629
+ function createEnsureSliceLoadedForVolume(volume) {
7630
+ const numSlices = volume.dimensions[2];
7631
+ const imageIds = volume.imageIds;
7632
+ if (!imageIds?.length) {
7633
+ return async () => undefined;
7634
+ }
7635
+ const loadedSlices = new Set();
7636
+ const inFlight = new Map();
7637
+ return async function ensureSliceLoaded(z) {
7638
+ if (!Number.isFinite(z) || z < 0 || z >= numSlices) {
7639
+ return;
7640
+ }
7641
+ if (loadedSlices.has(z)) {
7642
+ return;
7643
+ }
7644
+ const existing = inFlight.get(z);
7645
+ if (existing) {
7646
+ return existing;
7647
+ }
7648
+ const imageId = imageIds[z];
7649
+ if (!imageId) {
7650
+ return;
7651
+ }
7652
+ const promise = esm.imageLoader.loadImage(imageId)
7653
+ .then(() => {
7654
+ loadedSlices.add(z);
7655
+ })
7656
+ .finally(() => {
7657
+ inFlight.delete(z);
7658
+ });
7659
+ inFlight.set(z, promise);
7660
+ return promise;
7661
+ };
7662
+ }
7663
+
7664
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/growCut/constants.js
7665
+ var constants = __webpack_require__(29735);
7666
+ ;// CONCATENATED MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/growCut/neighborhoodStats.js
7667
+ function calculateNeighborhoodStatsVM(voxelManager, dimensions, centerIjk, radius, mapValue) {
7668
+ const [width, height, numSlices] = dimensions;
7669
+ let sum = 0;
7670
+ let sumSq = 0;
7671
+ let count = 0;
7672
+ const [cx, cy, cz] = centerIjk.map(Math.round);
7673
+ for (let z = cz - radius; z <= cz + radius; z++) {
7674
+ if (z < 0 || z >= numSlices) {
7675
+ continue;
7676
+ }
7677
+ for (let y = cy - radius; y <= cy + radius; y++) {
7678
+ if (y < 0 || y >= height) {
7679
+ continue;
7680
+ }
7681
+ for (let x = cx - radius; x <= cx + radius; x++) {
7682
+ if (x < 0 || x >= width) {
7683
+ continue;
7684
+ }
7685
+ const raw = Number(voxelManager.getAtIJK(x, y, z));
7686
+ const value = mapValue ? mapValue(raw) : raw;
7687
+ sum += value;
7688
+ sumSq += value * value;
7689
+ count++;
7690
+ }
7691
+ }
7692
+ }
7693
+ if (count === 0) {
7694
+ if (cx >= 0 &&
7695
+ cx < width &&
7696
+ cy >= 0 &&
7697
+ cy < height &&
7698
+ cz >= 0 &&
7699
+ cz < numSlices) {
7700
+ const raw = Number(voxelManager.getAtIJK(cx, cy, cz));
7701
+ const centerValue = mapValue ? mapValue(raw) : raw;
7702
+ return { mean: centerValue, stdDev: 0, count: 1 };
7703
+ }
7704
+ return { mean: 0, stdDev: 0, count: 0 };
7705
+ }
7706
+ const mean = sum / count;
7707
+ const variance = sumSq / count - mean * mean;
7708
+ const stdDev = Math.sqrt(Math.max(0, variance));
7709
+ return { mean, stdDev, count };
7710
+ }
7711
+
7712
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/growCut/getViewportVoiMappingForVolume.js
7713
+ var getViewportVoiMappingForVolume = __webpack_require__(1967);
7714
+ ;// CONCATENATED MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/utilities/segmentation/growCut/runFloodFillSegmentation.js
7715
+
7716
+
7717
+
7718
+
7719
+
7720
+
7721
+
7722
+
7723
+ const { transformWorldToIndex, mapScalarToViewportVoiIntensity, mapMappedBandToRawRange, } = esm.utilities;
7724
+ const { growCutLog: log } = esm.utilities.logger;
7725
+ const ENABLE_VERBOSE_FLOOD_FILL_LOGS = false;
7726
+ const timeStart = (label) => {
7727
+ if (ENABLE_VERBOSE_FLOOD_FILL_LOGS) {
7728
+ console.time(label);
7729
+ }
7730
+ };
7731
+ const timeEnd = (label) => {
7732
+ if (ENABLE_VERBOSE_FLOOD_FILL_LOGS) {
7733
+ console.timeEnd(label);
7734
+ }
7735
+ };
7736
+ const FLOOD_FILL_PREP_TIMING_LABEL = 'cornerstone.tools: floodFill: prep';
7737
+ const FLOOD_FILL_PREP_REF_META = 'cornerstone.tools: floodFill: prep: ref_volume_meta';
7738
+ const FLOOD_FILL_RANGE_TIMING_LABEL = 'cornerstone.tools: floodFill: intensityRange';
7739
+ const FLOOD_FILL_RUN_TIMING_LABEL = 'cornerstone.tools: floodFill: fillAndIslandRemoval';
7740
+ const FLOOD_FILL_ISLAND_EXTERNAL_TIMING_LABEL = 'cornerstone.tools: floodFill: islandRemoval:external';
7741
+ const FLOOD_FILL_ISLAND_INTERNAL_TIMING_LABEL = 'cornerstone.tools: floodFill: islandRemoval:internal';
7742
+ function getDisplayVoiSnapshot(viewport, referencedVolumeId) {
7743
+ const getProps = viewport.getProperties;
7744
+ if (typeof getProps !== 'function') {
7745
+ return null;
7746
+ }
7747
+ const props = referencedVolumeId
7748
+ ? getProps.call(viewport, referencedVolumeId)
7749
+ : getProps.call(viewport);
7750
+ const voiRange = props?.voiRange;
7751
+ if (!voiRange ||
7752
+ typeof voiRange.lower !== 'number' ||
7753
+ typeof voiRange.upper !== 'number') {
7754
+ return null;
7755
+ }
7756
+ const { lower, upper } = voiRange;
7757
+ return {
7758
+ lower,
7759
+ upper,
7760
+ windowWidth: upper - lower,
7761
+ windowCenter: (lower + upper) / 2,
7762
+ };
7763
+ }
7764
+ function getPositiveIntensityRangeRaw(referencedVolume, worldPosition, options) {
7765
+ const { dimensions, imageData: refImageData } = referencedVolume;
7766
+ const [width, height, numSlices] = dimensions;
7767
+ const referenceVolumeVoxelManager = referencedVolume.voxelManager;
7768
+ const neighborhoodRadius = options?.initialNeighborhoodRadius ?? (/* inlined export .DEFAULT_NEIGHBORHOOD_RADIUS */1);
7769
+ const positiveK = options?.positiveStdDevMultiplier ?? (/* inlined export .DEFAULT_POSITIVE_STD_DEV_MULTIPLIER */1.8);
7770
+ const ijkStart = transformWorldToIndex(refImageData, worldPosition).map(Math.round);
7771
+ if (ijkStart[0] < 0 ||
7772
+ ijkStart[0] >= width ||
7773
+ ijkStart[1] < 0 ||
7774
+ ijkStart[1] >= height ||
7775
+ ijkStart[2] < 0 ||
7776
+ ijkStart[2] >= numSlices) {
7777
+ log.warn('intensity range: click outside volume', {
7778
+ ijkStart,
7779
+ dimensions: [width, height, numSlices],
7780
+ });
7781
+ return null;
7782
+ }
7783
+ const initialStats = calculateNeighborhoodStatsVM(referenceVolumeVoxelManager, dimensions, ijkStart, neighborhoodRadius);
7784
+ if (initialStats.count === 0) {
7785
+ const seedScalar = Number(referenceVolumeVoxelManager.getAtIJKPoint(ijkStart));
7786
+ initialStats.mean = seedScalar;
7787
+ initialStats.stdDev = 0;
7788
+ }
7789
+ const min = initialStats.mean - positiveK * initialStats.stdDev;
7790
+ const max = initialStats.mean + positiveK * initialStats.stdDev;
7791
+ const startValue = referenceVolumeVoxelManager.getAtIJKPoint(ijkStart);
7792
+ if (startValue < min || startValue > max) {
7793
+ log.warn('intensity range: clicked voxel intensity is outside the calculated positive range');
7794
+ return null;
7795
+ }
7796
+ return {
7797
+ min,
7798
+ max,
7799
+ ijkStart,
7800
+ diagnostics: {
7801
+ neighborhoodMean: initialStats.mean,
7802
+ neighborhoodStdDev: initialStats.stdDev,
7803
+ clickedVoxelValue: startValue,
7804
+ positiveStdDevMultiplier: positiveK,
7805
+ neighborhoodRadius,
7806
+ strategy: 'meanStdRaw',
7807
+ },
7808
+ };
7809
+ }
7810
+ function getPositiveIntensityRangeVoiMapped(referencedVolume, worldPosition, voiMapping, options) {
7811
+ const { dimensions, imageData: refImageData } = referencedVolume;
7812
+ const [width, height, numSlices] = dimensions;
7813
+ const referenceVolumeVoxelManager = referencedVolume.voxelManager;
7814
+ const neighborhoodRadius = options?.initialNeighborhoodRadius ?? (/* inlined export .DEFAULT_NEIGHBORHOOD_RADIUS */1);
7815
+ const positiveK = options?.positiveStdDevMultiplier ?? (/* inlined export .DEFAULT_POSITIVE_STD_DEV_MULTIPLIER */1.8);
7816
+ const ijkStart = transformWorldToIndex(refImageData, worldPosition).map(Math.round);
7817
+ if (ijkStart[0] < 0 ||
7818
+ ijkStart[0] >= width ||
7819
+ ijkStart[1] < 0 ||
7820
+ ijkStart[1] >= height ||
7821
+ ijkStart[2] < 0 ||
7822
+ ijkStart[2] >= numSlices) {
7823
+ log.info('intensity range (VOI-mapped): click outside volume', {
7824
+ ijkStart,
7825
+ dimensions: [width, height, numSlices],
7826
+ });
7827
+ return null;
7828
+ }
7829
+ const mapFn = (v) => mapScalarToViewportVoiIntensity(v, voiMapping);
7830
+ const initialStats = calculateNeighborhoodStatsVM(referenceVolumeVoxelManager, dimensions, ijkStart, neighborhoodRadius, mapFn);
7831
+ if (initialStats.count === 0) {
7832
+ const seedScalar = Number(referenceVolumeVoxelManager.getAtIJKPoint(ijkStart));
7833
+ initialStats.mean = mapFn(seedScalar);
7834
+ initialStats.stdDev = 0;
7835
+ }
7836
+ let yMin = initialStats.mean - positiveK * initialStats.stdDev;
7837
+ let yMax = initialStats.mean + positiveK * initialStats.stdDev;
7838
+ yMin = Math.max(0, Math.min(1, yMin));
7839
+ yMax = Math.max(0, Math.min(1, yMax));
7840
+ if (yMin > yMax) {
7841
+ const t = yMin;
7842
+ yMin = yMax;
7843
+ yMax = t;
7844
+ }
7845
+ const { rawMin, rawMax } = mapMappedBandToRawRange(yMin, yMax, voiMapping);
7846
+ const startValue = Number(referenceVolumeVoxelManager.getAtIJKPoint(ijkStart));
7847
+ const bandLo = Math.min(rawMin, rawMax);
7848
+ const bandHi = Math.max(rawMin, rawMax);
7849
+ const min = Math.min(bandLo, startValue);
7850
+ const max = Math.max(bandHi, startValue);
7851
+ return {
7852
+ min,
7853
+ max,
7854
+ ijkStart,
7855
+ diagnostics: {
7856
+ neighborhoodMean: initialStats.mean,
7857
+ neighborhoodStdDev: initialStats.stdDev,
7858
+ clickedVoxelValue: startValue,
7859
+ positiveStdDevMultiplier: positiveK,
7860
+ neighborhoodRadius,
7861
+ strategy: 'meanStdVoiMapped',
7862
+ mappedBand: { min: yMin, max: yMax },
7863
+ },
7864
+ };
7865
+ }
7866
+ function getPositiveIntensityRange(referencedVolume, worldPosition, options) {
7867
+ if (options?.voiMapping) {
7868
+ return getPositiveIntensityRangeVoiMapped(referencedVolume, worldPosition, options.voiMapping, options);
7869
+ }
7870
+ return getPositiveIntensityRangeRaw(referencedVolume, worldPosition, options);
7871
+ }
7872
+ function resolveIntensityRange(referencedVolume, worldPosition, viewport, referencedVolumeId, options, rangeContext) {
7873
+ if (options.getIntensityRange) {
7874
+ const fromGetter = options.getIntensityRange(referencedVolume, worldPosition, rangeContext);
7875
+ if (!fromGetter) {
7876
+ log.warn('flood fill intensity range: strategy/custom getIntensityRange returned null (e.g. missing canvasPoint/VOI for canvas-disk, click outside volume, or fixed-% rejection)', { referencedVolumeId, worldPosition });
7877
+ }
7878
+ return fromGetter;
7879
+ }
7880
+ const voiFromViewport = (0,getViewportVoiMappingForVolume/* .getViewportVoiMappingForVolume */.K)(viewport, referencedVolumeId);
7881
+ const merged = {
7882
+ ...rangeContext,
7883
+ voiMapping: voiFromViewport ?? undefined,
7884
+ };
7885
+ let result = getPositiveIntensityRange(referencedVolume, worldPosition, merged);
7886
+ if (!result && merged.voiMapping) {
7887
+ log.info('flood fill intensity range: VOI-mapped mean±σ failed; falling back to raw mean±σ', { referencedVolumeId });
7888
+ result = getPositiveIntensityRange(referencedVolume, worldPosition, {
7889
+ ...merged,
7890
+ voiMapping: undefined,
7891
+ });
7892
+ }
7893
+ if (!result) {
7894
+ log.warn('flood fill intensity range: default mean±σ (VOI + raw fallback) could not resolve a band', { referencedVolumeId, worldPosition });
7895
+ }
7896
+ return result;
7897
+ }
7898
+ function assertFloodFillLabelmapMatchesRef(referencedVolume, labelmapVolume) {
7899
+ const [rw, rh, rd] = referencedVolume.dimensions;
7900
+ const [lw, lh, ld] = labelmapVolume.dimensions;
7901
+ if (rw !== lw || rh !== lh || rd !== ld) {
7902
+ throw new Error(`runFloodFillSegmentation: labelmap dimensions [${lw},${lh},${ld}] must match referenced volume [${rw},${rh},${rd}]`);
7903
+ }
7904
+ }
7905
+ function resolveFloodPaintIndices(segmentIndex, explicitPreview) {
7906
+ if (explicitPreview !== undefined) {
7907
+ return {
7908
+ paintIndex: explicitPreview,
7909
+ usePreview: explicitPreview !== segmentIndex,
7910
+ };
7911
+ }
7912
+ if (segmentIndex === 255) {
7913
+ return { paintIndex: 255, usePreview: false };
7914
+ }
7915
+ return { paintIndex: 255, usePreview: true };
7916
+ }
7917
+ function promotePreviewSegmentToFinal(readVoxelManager, writeVoxelManager, preview, final, points, numPixelsPerSlice, width) {
7918
+ for (let i = 0; i < points.length; i++) {
7919
+ const [x, y, z] = points[i];
7920
+ const index = z * numPixelsPerSlice + y * width + x;
7921
+ if (readVoxelManager.getAtIndex(index) === preview) {
7922
+ writeVoxelManager.setAtIndex(index, final);
7923
+ }
7924
+ }
7925
+ }
7926
+ async function runFloodFillSegmentation({ referencedVolumeId, worldPosition, viewport, labelmapVolume, options = {}, }) {
7927
+ timeStart(FLOOD_FILL_PREP_TIMING_LABEL);
7928
+ const referencedVolume = esm.cache.getVolume(referencedVolumeId);
7929
+ assertFloodFillLabelmapMatchesRef(referencedVolume, labelmapVolume);
7930
+ const labelmap = labelmapVolume;
7931
+ const segmentIndex = options.segmentIndex ?? 1;
7932
+ const { paintIndex, usePreview } = resolveFloodPaintIndices(segmentIndex, options.floodPreviewSegmentIndex);
7933
+ timeStart(FLOOD_FILL_PREP_REF_META);
7934
+ const [volMin, volMax] = referencedVolume.voxelManager.getRange();
7935
+ const displayVoi = getDisplayVoiSnapshot(viewport, referencedVolumeId);
7936
+ log.info('segmentation path: flood fill (floodfill_full)', {
7937
+ referencedVolumeId,
7938
+ volumeScalarRange: { min: volMin, max: volMax },
7939
+ displayVoi,
7940
+ floodPreview: usePreview ? paintIndex : null,
7941
+ segmentIndex,
7942
+ });
7943
+ timeEnd(FLOOD_FILL_PREP_REF_META);
7944
+ timeEnd(FLOOD_FILL_PREP_TIMING_LABEL);
7945
+ const voiMapping = (0,getViewportVoiMappingForVolume/* .getViewportVoiMappingForVolume */.K)(viewport, referencedVolumeId);
7946
+ const rangeContext = {
7947
+ positiveStdDevMultiplier: options.positiveStdDevMultiplier,
7948
+ initialNeighborhoodRadius: options.initialNeighborhoodRadius,
7949
+ viewport,
7950
+ element: options.element,
7951
+ referencedVolumeId,
7952
+ canvasPoint: options.canvasPoint,
7953
+ canvasDiskRadiusPx: options.intensitySamplingDiskRadiusCanvasPx,
7954
+ voiMapping: voiMapping ?? undefined,
7955
+ };
7956
+ timeStart(FLOOD_FILL_RANGE_TIMING_LABEL);
7957
+ const rangeResult = resolveIntensityRange(referencedVolume, worldPosition, viewport, referencedVolumeId, options, rangeContext);
7958
+ timeEnd(FLOOD_FILL_RANGE_TIMING_LABEL);
7959
+ if (!rangeResult) {
7960
+ log.warn('flood fill: aborted before fill (no intensity range)', {
7961
+ referencedVolumeId,
7962
+ worldPosition,
7963
+ });
7964
+ return null;
7965
+ }
7966
+ const { min: rangeMin, max: rangeMax, ijkStart, diagnostics } = rangeResult;
7967
+ let clampedRangeMin = rangeMin;
7968
+ let clampedRangeMax = rangeMax;
7969
+ const MAX_WL_BAND_FRACTION = 0.2;
7970
+ if (voiMapping && diagnostics?.mappedBand) {
7971
+ const mapped = diagnostics.mappedBand;
7972
+ const mappedLo = Math.max(0, Math.min(1, Math.min(mapped.min, mapped.max)));
7973
+ const mappedHi = Math.max(0, Math.min(1, Math.max(mapped.min, mapped.max)));
7974
+ const mappedWidth = mappedHi - mappedLo;
7975
+ if (mappedWidth > MAX_WL_BAND_FRACTION) {
7976
+ const seedScalarPreClamp = Number(referencedVolume.voxelManager.getAtIJKPoint(ijkStart));
7977
+ const seedMapped = Math.max(0, Math.min(1, mapScalarToViewportVoiIntensity(seedScalarPreClamp, voiMapping)));
7978
+ const half = MAX_WL_BAND_FRACTION / 2;
7979
+ const clippedMappedLo = Math.max(0, seedMapped - half);
7980
+ const clippedMappedHi = Math.min(1, seedMapped + half);
7981
+ const { rawMin: clipRawMin, rawMax: clipRawMax } = mapMappedBandToRawRange(clippedMappedLo, clippedMappedHi, voiMapping);
7982
+ clampedRangeMin = Math.min(clipRawMin, clipRawMax, seedScalarPreClamp);
7983
+ clampedRangeMax = Math.max(clipRawMin, clipRawMax, seedScalarPreClamp);
7984
+ log.info('flood fill: clipped wide WL band around seed', {
7985
+ maxWlBandFraction: MAX_WL_BAND_FRACTION,
7986
+ originalMappedBand: {
7987
+ min: mappedLo,
7988
+ max: mappedHi,
7989
+ width: mappedWidth,
7990
+ },
7991
+ clippedMappedBand: {
7992
+ min: clippedMappedLo,
7993
+ max: clippedMappedHi,
7994
+ width: clippedMappedHi - clippedMappedLo,
7995
+ },
7996
+ });
7997
+ }
7998
+ }
7999
+ log.info('intensity tolerance band', {
8000
+ toleranceMin: clampedRangeMin,
8001
+ toleranceMax: clampedRangeMax,
8002
+ width: clampedRangeMax - clampedRangeMin,
8003
+ ...diagnostics,
8004
+ });
8005
+ if (ENABLE_VERBOSE_FLOOD_FILL_LOGS) {
8006
+ console.info('[cornerstone-tools] flood fill intensity range', {
8007
+ rawMin: clampedRangeMin,
8008
+ rawMax: clampedRangeMax,
8009
+ strategy: diagnostics.strategy,
8010
+ mappedBand: diagnostics.mappedBand,
8011
+ neighborhoodRadius: diagnostics.neighborhoodRadius,
8012
+ });
8013
+ }
8014
+ timeStart(FLOOD_FILL_RUN_TIMING_LABEL);
8015
+ try {
8016
+ const { dimensions } = referencedVolume;
8017
+ const [width, height, numSlices] = dimensions;
8018
+ const refVoxelManager = referencedVolume.voxelManager;
8019
+ const numPixelsPerSlice = width * height;
8020
+ const labelmapReadVm = labelmap.voxelManager;
8021
+ const labelmapWriteVm = (options.historyVoxelManager ??
8022
+ labelmap.voxelManager);
8023
+ let positiveMin = clampedRangeMin;
8024
+ let positiveMax = clampedRangeMax;
8025
+ const seedScalar = Number(refVoxelManager.getAtIJKPoint(ijkStart));
8026
+ if (Number.isFinite(seedScalar)) {
8027
+ if (seedScalar < positiveMin) {
8028
+ log.info('flood fill: expanded tolerance min to include seed voxel', {
8029
+ seedScalar,
8030
+ previousMin: positiveMin,
8031
+ });
8032
+ positiveMin = seedScalar;
8033
+ }
8034
+ if (seedScalar > positiveMax) {
8035
+ log.info('flood fill: expanded tolerance max to include seed voxel', {
8036
+ seedScalar,
8037
+ previousMax: positiveMax,
8038
+ });
8039
+ positiveMax = seedScalar;
8040
+ }
8041
+ }
8042
+ if (ENABLE_VERBOSE_FLOOD_FILL_LOGS) {
8043
+ console.info('[cornerstone-tools] flood fill seed + effective tolerance', {
8044
+ seedScalar,
8045
+ effectiveMin: positiveMin,
8046
+ effectiveMax: positiveMax,
8047
+ });
8048
+ }
8049
+ const intensityGetter = (x, y, z) => {
8050
+ if (x < 0 ||
8051
+ x >= width ||
8052
+ y < 0 ||
8053
+ y >= height ||
8054
+ z < 0 ||
8055
+ z >= numSlices) {
8056
+ return undefined;
8057
+ }
8058
+ const idx = z * numPixelsPerSlice + y * width + x;
8059
+ return refVoxelManager.getAtIndex(idx);
8060
+ };
8061
+ const inRange = (val) => val >= positiveMin && val <= positiveMax;
8062
+ const ensureSliceLoaded = options.ensureSliceLoaded ??
8063
+ createEnsureSliceLoadedForVolume(referencedVolume);
8064
+ const planar = options.planar === true;
8065
+ if (planar) {
8066
+ log.info('flood fill: planar mode (fixed slice index k)', { ijkStart });
8067
+ }
8068
+ const { sliceMasks, voxelCount: filledVoxelCount, truncated, bbox, } = await (0,floodFillSliceLazy/* .floodFill3dSliceLazy */.x)(intensityGetter, ijkStart, {
8069
+ width,
8070
+ height,
8071
+ depth: numSlices,
8072
+ equals: (val, _startVal) => val !== undefined && typeof val === 'number' && inRange(val),
8073
+ ensureSliceLoaded,
8074
+ yieldEvery: options.yieldEvery ?? 500,
8075
+ planar,
8076
+ maxDeltaK: options.maxDeltaK,
8077
+ maxDeltaIJ: options.maxDeltaIJ,
8078
+ isCancelled: options.isCancelled,
8079
+ maxVoxels: options.maxVoxels,
8080
+ shouldContinue: options.shouldContinueRegion,
8081
+ });
8082
+ const finalShapeRejected = !truncated &&
8083
+ bbox &&
8084
+ options.shouldContinueRegion &&
8085
+ !options.shouldContinueRegion({ voxelCount: filledVoxelCount, bbox });
8086
+ if (truncated || finalShapeRejected) {
8087
+ log.warn('flood fill: rejected — region stopped by budget or shape gate; nothing committed', {
8088
+ maxVoxels: options.maxVoxels,
8089
+ voxelCountAtStop: filledVoxelCount,
8090
+ bbox,
8091
+ ijkStart,
8092
+ });
8093
+ options.onRejected?.({ voxelCount: filledVoxelCount, bbox });
8094
+ return null;
8095
+ }
8096
+ if (filledVoxelCount === 0) {
8097
+ log.info('flood fill: zero voxels (range may be too tight or seed isolated)', {
8098
+ ijkStart,
8099
+ toleranceMin: positiveMin,
8100
+ toleranceMax: positiveMax,
8101
+ });
8102
+ return labelmap;
8103
+ }
8104
+ if (options.isCancelled?.() === true) {
8105
+ log.info('flood fill: cancellation requested; nothing committed', {
8106
+ ijkStart,
8107
+ voxelCountAtCancel: filledVoxelCount,
8108
+ });
8109
+ return null;
8110
+ }
8111
+ const { floodedPoints, voxelCount: committedVoxels } = commitSliceMasksToLabelmapVolume({
8112
+ labelmapVolume: labelmap,
8113
+ sliceMasks,
8114
+ width,
8115
+ height,
8116
+ paintIndex,
8117
+ historyVoxelManager: options.historyVoxelManager,
8118
+ });
8119
+ if (committedVoxels === 0 || floodedPoints.length === 0) {
8120
+ log.warn('flood fill: commit produced no labelmap voxels', {
8121
+ filledVoxelCount,
8122
+ committedVoxels,
8123
+ ijkStart,
8124
+ });
8125
+ return labelmap;
8126
+ }
8127
+ let committedPoints = floodedPoints;
8128
+ log.info('flood fill: complete', {
8129
+ voxelCount: floodedPoints.length,
8130
+ ijkStart,
8131
+ paintIndex,
8132
+ usePreview,
8133
+ });
8134
+ const applyExternal = options.applyExternalIslandRemoval !== false;
8135
+ const applyInternal = options.applyInternalIslandRemoval !== false && applyExternal;
8136
+ if (!applyExternal && !applyInternal) {
8137
+ if (usePreview) {
8138
+ promotePreviewSegmentToFinal(labelmapReadVm, labelmapWriteVm, paintIndex, segmentIndex, floodedPoints, numPixelsPerSlice, width);
8139
+ }
8140
+ options.onCommitted?.(committedPoints);
8141
+ return labelmap;
8142
+ }
8143
+ const islandVerbose = options.islandRemovalVerboseLogging === true;
8144
+ const islandRemoval = new FloodFillIslandRemoval({
8145
+ maxInternalRemove: options.maxInternalRemove ?? 128,
8146
+ fillInternalEdge: false,
8147
+ verboseLogging: islandVerbose,
8148
+ });
8149
+ const ijkPoints = [ijkStart];
8150
+ const initialized = islandRemoval.initialize(viewport, options.historyVoxelManager ?? labelmap.voxelManager, {
8151
+ points: ijkPoints,
8152
+ segmentIndex,
8153
+ previewSegmentIndex: usePreview ? paintIndex : segmentIndex,
8154
+ });
8155
+ if (!initialized) {
8156
+ log.warn('island removal: initialize failed', { segmentIndex, ijkStart });
8157
+ if (usePreview) {
8158
+ promotePreviewSegmentToFinal(labelmapReadVm, labelmapWriteVm, paintIndex, segmentIndex, floodedPoints, numPixelsPerSlice, width);
8159
+ }
8160
+ options.onCommitted?.(committedPoints);
8161
+ return labelmap;
8162
+ }
8163
+ let islandFloodVoxels = 0;
8164
+ let externalClearedVoxels = 0;
8165
+ let internalSliceCount;
8166
+ if (applyExternal) {
8167
+ timeStart(FLOOD_FILL_ISLAND_EXTERNAL_TIMING_LABEL);
8168
+ islandFloodVoxels = islandRemoval.floodFillSegmentIsland();
8169
+ externalClearedVoxels = islandRemoval.removeExternalIslands();
8170
+ timeEnd(FLOOD_FILL_ISLAND_EXTERNAL_TIMING_LABEL);
8171
+ if (applyInternal) {
8172
+ timeStart(FLOOD_FILL_ISLAND_INTERNAL_TIMING_LABEL);
8173
+ const modifiedSlices = islandRemoval.removeInternalIslands();
8174
+ internalSliceCount = modifiedSlices?.length;
8175
+ timeEnd(FLOOD_FILL_ISLAND_INTERNAL_TIMING_LABEL);
8176
+ }
8177
+ }
8178
+ log.info('island removal: complete', {
8179
+ segmentIndex,
8180
+ applyExternalIslandRemoval: applyExternal,
8181
+ applyInternalIslandRemoval: applyInternal,
8182
+ floodedPointsBeforeIsland: floodedPoints.length,
8183
+ islandFloodVoxelsFromSegmentSet: islandFloodVoxels,
8184
+ externalIslandClearVoxels: externalClearedVoxels,
8185
+ internalRemovalModifiedSlices: internalSliceCount,
8186
+ islandRemovalVerboseLogging: islandVerbose,
8187
+ });
8188
+ const internalFilledPoints = applyInternal
8189
+ ? islandRemoval.getInternalFilledPoints()
8190
+ : [];
8191
+ committedPoints =
8192
+ internalFilledPoints.length > 0
8193
+ ? floodedPoints.concat(internalFilledPoints)
8194
+ : floodedPoints;
8195
+ if (usePreview) {
8196
+ promotePreviewSegmentToFinal(labelmapReadVm, labelmapWriteVm, paintIndex, segmentIndex, committedPoints, numPixelsPerSlice, width);
8197
+ }
8198
+ options.onCommitted?.(committedPoints);
8199
+ if (ENABLE_VERBOSE_FLOOD_FILL_LOGS && floodedPoints.length > 0) {
8200
+ let finalSegmentCount = 0;
8201
+ let previewCount = 0;
8202
+ let zeroCount = 0;
8203
+ let otherCount = 0;
8204
+ for (let i = 0; i < floodedPoints.length; i++) {
8205
+ const [x, y, z] = floodedPoints[i];
8206
+ const index = z * numPixelsPerSlice + y * width + x;
8207
+ const value = Number(labelmap.voxelManager.getAtIndex(index) ?? 0);
8208
+ if (value === segmentIndex) {
8209
+ finalSegmentCount += 1;
8210
+ }
8211
+ else if (value === paintIndex) {
8212
+ previewCount += 1;
8213
+ }
8214
+ else if (value === 0) {
8215
+ zeroCount += 1;
8216
+ }
8217
+ else {
8218
+ otherCount += 1;
8219
+ }
8220
+ }
8221
+ console.info('[cornerstone-tools] flood fill final voxel check', {
8222
+ floodedPoints: floodedPoints.length,
8223
+ segmentIndex,
8224
+ paintIndex,
8225
+ usePreview,
8226
+ finalSegmentCount,
8227
+ previewCount,
8228
+ zeroCount,
8229
+ otherCount,
8230
+ });
8231
+ }
8232
+ return labelmap;
8233
+ }
8234
+ finally {
8235
+ timeEnd(FLOOD_FILL_RUN_TIMING_LABEL);
8236
+ }
8237
+ }
8238
+
8239
+
8240
+
8241
+ },
8242
+ 14386(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
8243
+ __webpack_require__.d(__webpack_exports__, {
8244
+ e: () => (runGrowCut)
8245
+ });
8246
+ /* import */ var _cornerstonejs_core__rspack_import_0 = __webpack_require__(88479);
8247
+ /* import */ var _growCutShader_js__rspack_import_1 = __webpack_require__(20326);
8248
+
8249
+
8250
+ const GB = 1024 * 1024 * 1024;
8251
+ const WEBGPU_MEMORY_LIMIT = 1.99 * GB;
8252
+ const DEFAULT_GROWCUT_OPTIONS = {
8253
+ windowSize: 3,
8254
+ maxProcessingTime: 30000,
8255
+ inspection: {
8256
+ numCyclesInterval: 5,
8257
+ numCyclesBelowThreshold: 3,
8258
+ threshold: 1e-4,
8259
+ },
8260
+ };
8261
+ async function runGrowCut(referenceVolumeId, labelmapVolumeId, options = DEFAULT_GROWCUT_OPTIONS) {
8262
+ const workGroupSize = [8, 8, 4];
8263
+ const { windowSize, maxProcessingTime } = Object.assign({}, DEFAULT_GROWCUT_OPTIONS, options);
8264
+ const inspection = Object.assign({}, DEFAULT_GROWCUT_OPTIONS.inspection, options.inspection);
8265
+ const volume = _cornerstonejs_core__rspack_import_0.cache.getVolume(referenceVolumeId);
8266
+ const labelmap = _cornerstonejs_core__rspack_import_0.cache.getVolume(labelmapVolumeId);
8267
+ const [columns, rows, numSlices] = volume.dimensions;
8268
+ if (labelmap.dimensions[0] !== columns ||
8269
+ labelmap.dimensions[1] !== rows ||
8270
+ labelmap.dimensions[2] !== numSlices) {
8271
+ throw new Error('Volume and labelmap must have the same size');
8272
+ }
8273
+ let numIterations = Math.floor(Math.sqrt(rows ** 2 + columns ** 2 + numSlices ** 2) / 2);
8274
+ numIterations = Math.min(numIterations, 500);
8275
+ const labelmapData = labelmap.voxelManager.getCompleteScalarDataArray();
8276
+ let volumePixelData = volume.voxelManager.getCompleteScalarDataArray();
8277
+ if (!(volumePixelData instanceof Float32Array)) {
8278
+ volumePixelData = new Float32Array(volumePixelData);
8279
+ }
8280
+ const requiredLimits = {
8281
+ maxStorageBufferBindingSize: WEBGPU_MEMORY_LIMIT,
8282
+ maxBufferSize: WEBGPU_MEMORY_LIMIT,
8283
+ };
8284
+ const adapter = await navigator.gpu?.requestAdapter();
8285
+ const device = await adapter.requestDevice({ requiredLimits });
8286
+ const BUFFER_SIZE = volumePixelData.byteLength;
8287
+ const UPDATED_VOXELS_COUNTER_BUFFER_SIZE = numIterations * Uint32Array.BYTES_PER_ELEMENT;
8288
+ const BOUNDS_BUFFER_SIZE = 6 * Int32Array.BYTES_PER_ELEMENT;
8289
+ const shaderModule = device.createShaderModule({
8290
+ code: _growCutShader_js__rspack_import_1/* ["default"] */.A,
8291
+ });
8292
+ const numIterationIndex = 3;
8293
+ const paramsArrayValues = new Uint32Array([
8294
+ columns,
8295
+ rows,
8296
+ numSlices,
8297
+ 0,
8298
+ ]);
8299
+ const gpuParamsBuffer = device.createBuffer({
8300
+ size: paramsArrayValues.byteLength,
8301
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
8302
+ });
8303
+ const gpuVolumePixelDataBuffer = device.createBuffer({
8304
+ size: BUFFER_SIZE,
8305
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
8306
+ });
8307
+ device.queue.writeBuffer(gpuVolumePixelDataBuffer, 0, volumePixelData);
8308
+ const gpuLabelmapBuffers = [0, 1].map(() => device.createBuffer({
8309
+ size: BUFFER_SIZE,
8310
+ usage: GPUBufferUsage.STORAGE |
8311
+ GPUBufferUsage.COPY_SRC |
8312
+ GPUBufferUsage.COPY_DST,
8313
+ }));
8314
+ device.queue.writeBuffer(gpuLabelmapBuffers[0], 0, new Uint32Array(labelmapData));
8315
+ const gpuStrengthBuffers = [0, 1].map(() => {
8316
+ const strengthBuffer = device.createBuffer({
8317
+ size: BUFFER_SIZE,
8318
+ usage: GPUBufferUsage.STORAGE |
8319
+ GPUBufferUsage.COPY_SRC |
8320
+ GPUBufferUsage.COPY_DST,
8321
+ });
8322
+ return strengthBuffer;
8323
+ });
8324
+ const gpuCounterBuffer = device.createBuffer({
8325
+ size: UPDATED_VOXELS_COUNTER_BUFFER_SIZE,
6484
8326
  usage: GPUBufferUsage.STORAGE |
6485
8327
  GPUBufferUsage.COPY_SRC |
6486
8328
  GPUBufferUsage.COPY_DST,
@@ -7259,6 +9101,200 @@ function invalidateBrushCursor(toolGroupId) {
7259
9101
  }
7260
9102
 
7261
9103
 
9104
+ },
9105
+ 29827(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
9106
+ __webpack_require__.d(__webpack_exports__, {
9107
+ A: () => (IslandRemoval),
9108
+ a: () => (SegmentationEnum)
9109
+ });
9110
+ /* import */ var _cornerstonejs_core__rspack_import_0 = __webpack_require__(88479);
9111
+ /* import */ var _normalizeViewportPlane_js__rspack_import_1 = __webpack_require__(95972);
9112
+
9113
+
9114
+ const { RLEVoxelMap, VoxelManager } = _cornerstonejs_core__rspack_import_0.utilities;
9115
+ const MAX_IMAGE_SIZE = 65535;
9116
+ var SegmentationEnum;
9117
+ (function (SegmentationEnum) {
9118
+ SegmentationEnum[SegmentationEnum["SEGMENT"] = -1] = "SEGMENT";
9119
+ SegmentationEnum[SegmentationEnum["ISLAND"] = -2] = "ISLAND";
9120
+ SegmentationEnum[SegmentationEnum["INTERIOR"] = -3] = "INTERIOR";
9121
+ SegmentationEnum[SegmentationEnum["EXTERIOR"] = -4] = "EXTERIOR";
9122
+ SegmentationEnum[SegmentationEnum["INTERIOR_SMALL"] = -5] = "INTERIOR_SMALL";
9123
+ SegmentationEnum[SegmentationEnum["INTERIOR_TEST"] = -6] = "INTERIOR_TEST";
9124
+ })(SegmentationEnum || (SegmentationEnum = {}));
9125
+ class IslandRemoval {
9126
+ constructor(options) {
9127
+ this.fillInternalEdge = false;
9128
+ this.maxInternalRemove = 128;
9129
+ this.maxInternalRemove =
9130
+ options?.maxInternalRemove ?? this.maxInternalRemove;
9131
+ this.fillInternalEdge = options?.fillInternalEdge ?? this.fillInternalEdge;
9132
+ }
9133
+ initialize(viewport, segmentationVoxels, options) {
9134
+ const hasSource = !!segmentationVoxels.sourceVoxelManager;
9135
+ const segmentationVoxelManager = hasSource
9136
+ ? segmentationVoxels.sourceVoxelManager
9137
+ : segmentationVoxels;
9138
+ const previewVoxelManager = hasSource
9139
+ ? segmentationVoxels
9140
+ : VoxelManager.createRLEHistoryVoxelManager(segmentationVoxelManager);
9141
+ const { segmentIndex = 1, previewSegmentIndex = 1 } = options;
9142
+ const clickedPoints = options.points || segmentationVoxelManager.getPoints();
9143
+ if (!clickedPoints?.length) {
9144
+ return;
9145
+ }
9146
+ const boundsIJK = segmentationVoxelManager
9147
+ .getBoundsIJK()
9148
+ .map((bound, i) => [
9149
+ Math.min(bound[0], ...clickedPoints.map((point) => point[i])),
9150
+ Math.max(bound[1], ...clickedPoints.map((point) => point[i])),
9151
+ ]);
9152
+ if (boundsIJK.find((it) => it[0] < 0 || it[1] > MAX_IMAGE_SIZE)) {
9153
+ return;
9154
+ }
9155
+ const { toIJK, fromIJK, boundsIJKPrime, error } = (0,_normalizeViewportPlane_js__rspack_import_1/* ["default"] */.A)(viewport, boundsIJK);
9156
+ if (error) {
9157
+ console.warn('Not performing island removal for planes not orthogonal to acquisition plane', error);
9158
+ return;
9159
+ }
9160
+ const [width, height, depth] = fromIJK(segmentationVoxelManager.dimensions);
9161
+ const segmentSet = new RLEVoxelMap(width, height, depth);
9162
+ const getter = (i, j, k) => {
9163
+ const index = segmentationVoxelManager.toIndex(toIJK([i, j, k]));
9164
+ const oldVal = segmentationVoxelManager.getAtIndex(index);
9165
+ if (oldVal === previewSegmentIndex || oldVal === segmentIndex) {
9166
+ return SegmentationEnum.SEGMENT;
9167
+ }
9168
+ };
9169
+ segmentSet.fillFrom(getter, boundsIJKPrime);
9170
+ segmentSet.normalizer = { toIJK, fromIJK, boundsIJKPrime };
9171
+ this.segmentSet = segmentSet;
9172
+ this.previewVoxelManager = previewVoxelManager;
9173
+ this.segmentIndex = segmentIndex;
9174
+ this.previewSegmentIndex = previewSegmentIndex ?? segmentIndex;
9175
+ this.selectedPoints = clickedPoints;
9176
+ return true;
9177
+ }
9178
+ floodFillSegmentIsland() {
9179
+ const { selectedPoints: clickedPoints, segmentSet } = this;
9180
+ let floodedCount = 0;
9181
+ const { fromIJK } = segmentSet.normalizer;
9182
+ clickedPoints.forEach((clickedPoint) => {
9183
+ const ijkPrime = fromIJK(clickedPoint);
9184
+ const index = segmentSet.toIndex(ijkPrime);
9185
+ const [iPrime, jPrime, kPrime] = ijkPrime;
9186
+ if (segmentSet.get(index) === SegmentationEnum.SEGMENT) {
9187
+ floodedCount += segmentSet.floodFill(iPrime, jPrime, kPrime, SegmentationEnum.ISLAND);
9188
+ }
9189
+ });
9190
+ return floodedCount;
9191
+ }
9192
+ removeExternalIslands() {
9193
+ const { previewVoxelManager, segmentSet } = this;
9194
+ const { toIJK } = segmentSet.normalizer;
9195
+ const callback = (index, rle) => {
9196
+ const [, jPrime, kPrime] = segmentSet.toIJK(index);
9197
+ if (rle.value !== SegmentationEnum.ISLAND) {
9198
+ for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
9199
+ const clearPoint = toIJK([iPrime, jPrime, kPrime]);
9200
+ const v = previewVoxelManager.getAtIJKPoint(clearPoint);
9201
+ previewVoxelManager.setAtIJKPoint(clearPoint, v === undefined ? 0 : null);
9202
+ }
9203
+ }
9204
+ };
9205
+ segmentSet.forEach(callback, { rowModified: true });
9206
+ }
9207
+ removeInternalIslands() {
9208
+ const { segmentSet, previewVoxelManager, previewSegmentIndex } = this;
9209
+ const { height, normalizer, width } = segmentSet;
9210
+ const { toIJK } = normalizer;
9211
+ segmentSet.forEachRow((baseIndex, row) => {
9212
+ let lastRle;
9213
+ for (const rle of [...row]) {
9214
+ if (rle.value !== SegmentationEnum.ISLAND) {
9215
+ continue;
9216
+ }
9217
+ if (!lastRle) {
9218
+ if (this.fillInternalEdge && rle.start > 0) {
9219
+ for (let iPrime = 0; iPrime < rle.start; iPrime++) {
9220
+ segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
9221
+ }
9222
+ }
9223
+ lastRle = rle;
9224
+ continue;
9225
+ }
9226
+ for (let iPrime = lastRle.end; iPrime < rle.start; iPrime++) {
9227
+ segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
9228
+ }
9229
+ lastRle = rle;
9230
+ }
9231
+ if (this.fillInternalEdge && lastRle?.end < width) {
9232
+ for (let iPrime = lastRle.end; iPrime < width; iPrime++) {
9233
+ segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
9234
+ }
9235
+ }
9236
+ });
9237
+ segmentSet.forEach((baseIndex, rle) => {
9238
+ if (rle.value !== SegmentationEnum.INTERIOR) {
9239
+ return;
9240
+ }
9241
+ const [, jPrime, kPrime] = segmentSet.toIJK(baseIndex);
9242
+ const rowPrev = jPrime > 0 ? segmentSet.getRun(jPrime - 1, kPrime) : null;
9243
+ const rowNext = jPrime + 1 < height ? segmentSet.getRun(jPrime + 1, kPrime) : null;
9244
+ const isLast = jPrime === height - 1;
9245
+ const isFirst = jPrime === 0;
9246
+ const prevCovers = IslandRemoval.covers(rle, rowPrev) ||
9247
+ (isFirst && this.fillInternalEdge);
9248
+ const nextCovers = IslandRemoval.covers(rle, rowNext) || (isLast && this.fillInternalEdge);
9249
+ if (rle.end - rle.start > 2 && (!prevCovers || !nextCovers)) {
9250
+ segmentSet.floodFill(rle.start, jPrime, kPrime, SegmentationEnum.EXTERIOR, { singlePlane: true });
9251
+ }
9252
+ });
9253
+ segmentSet.forEach((baseIndex, rle) => {
9254
+ if (rle.value !== SegmentationEnum.INTERIOR) {
9255
+ return;
9256
+ }
9257
+ const [, jPrime, kPrime] = segmentSet.toIJK(baseIndex);
9258
+ const size = segmentSet.floodFill(rle.start, jPrime, kPrime, SegmentationEnum.INTERIOR_TEST);
9259
+ const isBig = size > this.maxInternalRemove;
9260
+ const newType = isBig
9261
+ ? SegmentationEnum.EXTERIOR
9262
+ : SegmentationEnum.INTERIOR_SMALL;
9263
+ segmentSet.floodFill(rle.start, jPrime, kPrime, newType);
9264
+ });
9265
+ segmentSet.forEach((baseIndex, rle) => {
9266
+ if (rle.value !== SegmentationEnum.INTERIOR_SMALL) {
9267
+ return;
9268
+ }
9269
+ for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
9270
+ const clearPoint = toIJK(segmentSet.toIJK(baseIndex + iPrime));
9271
+ previewVoxelManager.setAtIJKPoint(clearPoint, previewSegmentIndex);
9272
+ this.onInternalPointFilled(clearPoint);
9273
+ }
9274
+ });
9275
+ return previewVoxelManager.getArrayOfModifiedSlices();
9276
+ }
9277
+ onInternalPointFilled(_point) {
9278
+ }
9279
+ static covers(rle, row) {
9280
+ if (!row) {
9281
+ return false;
9282
+ }
9283
+ let { start } = rle;
9284
+ const { end } = rle;
9285
+ for (const rowRle of row) {
9286
+ if (start >= rowRle.start && start < rowRle.end) {
9287
+ start = rowRle.end;
9288
+ if (start >= end) {
9289
+ return true;
9290
+ }
9291
+ }
9292
+ }
9293
+ return false;
9294
+ }
9295
+ }
9296
+
9297
+
7262
9298
  },
7263
9299
  70736(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
7264
9300
  __webpack_require__.d(__webpack_exports__, {