@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.
- package/dist/{1483.bundle.e208a19dd0ff9a11.js → 1483.bundle.5e93c978bd32d7de.js} +1 -1
- package/dist/{2343.bundle.e50c08b2df863e1a.js → 2343.bundle.3445fda02a002697.js} +12 -8
- package/dist/{2036.bundle.1b6c0d890aa6bf4e.js → 2493.bundle.f3b6a64d158365da.js} +113 -35
- package/dist/{3215.bundle.1fca83719c1b786b.js → 3330.bundle.b1297ef8c94e5440.js} +796 -180
- package/dist/{2690.bundle.f5dc28ac498de122.js → 3568.bundle.45dc681ef6890166.js} +2112 -76
- package/dist/{6495.bundle.84b83aa091c338cd.js → 6495.bundle.a6764fdb8c0c252f.js} +846 -219
- package/dist/{8668.bundle.93c88419e37dc076.js → 8668.bundle.0e47f7231aa07da2.js} +227 -3
- package/dist/{9402.bundle.0dd1b538f0708077.js → 9402.bundle.928437e2e48c12dd.js} +2 -2
- package/dist/{9560.bundle.bb39f946ea79cf0e.js → 9560.bundle.517f0ef7ee3a20bf.js} +159 -196
- package/dist/{app.bundle.bdeb02edea129b2c.js → app.bundle.81a823d1149be346.js} +99 -25
- package/dist/{compute.bundle.77e80c1775429bd6.js → compute.bundle.9cc3697d07fbb082.js} +3 -3
- package/dist/index.html +1 -1
- package/dist/{polySeg.bundle.5e350e0723aa59b6.js → polySeg.bundle.a526954fc2fed311.js} +3 -3
- package/dist/sw.js +1 -1
- package/package.json +6 -6
- /package/dist/{100.bundle.bd6735a86c215595.js → 100.bundle.84a35e9d087b403c.js} +0 -0
- /package/dist/{1180.bundle.4c5146471b4d3497.js → 1180.bundle.7f790d9fd6fb031c.js} +0 -0
- /package/dist/{1602.bundle.163e3da3a27b860c.js → 1602.bundle.254a392a6b5d2b85.js} +0 -0
- /package/dist/{2779.bundle.664d9d0a704f7b94.js → 2779.bundle.c879d6af5dc0c274.js} +0 -0
- /package/dist/{3500.bundle.18412afd9ee5d6a7.js → 3500.bundle.d4aaab15c4461cc4.js} +0 -0
- /package/dist/{3760.bundle.f69046888fc2b881.js → 3760.bundle.c95abd6ab662bd3b.js} +0 -0
- /package/dist/{4556.bundle.128d2abaea69dce3.js → 4556.bundle.c3ac23e680d677b2.js} +0 -0
- /package/dist/{4683.bundle.b02523a9d66f4d19.js → 4683.bundle.ed3f14fcb89fbad1.js} +0 -0
- /package/dist/{5189.bundle.2a0f740738759e32.js → 5189.bundle.ea191ea99047fd6f.js} +0 -0
- /package/dist/{5642.bundle.1622d255af1102d8.js → 5642.bundle.98e17a4413db6ef1.js} +0 -0
- /package/dist/{5696.bundle.2f47cb5ab5a7f983.js → 5696.bundle.e77041f7e2b08236.js} +0 -0
- /package/dist/{6749.bundle.9f0550714a4d5f8b.js → 6749.bundle.9dcda33e49485da4.js} +0 -0
- /package/dist/{7231.bundle.46f843b74a964f89.js → 7231.bundle.1063d727ad99108a.js} +0 -0
- /package/dist/{7248.bundle.bffd4e60f806e25d.js → 7248.bundle.ab6f276adad059da.js} +0 -0
- /package/dist/{7499.bundle.bb240e8754167cb2.js → 7499.bundle.832916b9b820c33f.js} +0 -0
- /package/dist/{781.bundle.f7411dabe0628911.js → 781.bundle.f06b6ab92ef4d695.js} +0 -0
- /package/dist/{8423.bundle.071901427fd3cd9e.js → 8423.bundle.ebf8d7d638d9616b.js} +0 -0
- /package/dist/{8492.bundle.9c17dcbab24b07c3.js → 8492.bundle.1ceeb18471c1c06b.js} +0 -0
- /package/dist/{8832.bundle.e413f00216682f40.js → 8832.bundle.1eb46bfa451c792f.js} +0 -0
- /package/dist/{9116.bundle.1cb5c393e3a152cd.js → 9116.bundle.1d0c6c18a4db51cc.js} +0 -0
- /package/dist/{9290.bundle.77a274271ce884a3.js → 9290.bundle.82e8dfc0f73c41bb.js} +0 -0
- /package/dist/{9383.bundle.9b1d03c71cc52f4f.js → 9383.bundle.ec44c452ccc23e93.js} +0 -0
- /package/dist/{9415.bundle.d3532cc872787744.js → 9415.bundle.69d74a2b03d5d6e4.js} +0 -0
- /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([[
|
|
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
|
-
|
|
6792
|
+
81537(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
|
|
6401
6793
|
__webpack_require__.d(__webpack_exports__, {
|
|
6402
|
-
|
|
6794
|
+
CN: () => (resolveAdaptiveBandAtTolerance),
|
|
6795
|
+
Ti: () => (probeAdaptiveRegion)
|
|
6403
6796
|
});
|
|
6404
6797
|
/* import */ var _cornerstonejs_core__rspack_import_0 = __webpack_require__(88479);
|
|
6405
|
-
/* import */ var
|
|
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
|
-
|
|
6409
|
-
const
|
|
6410
|
-
const
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
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
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
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
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
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
|
-
|
|
6443
|
-
|
|
6444
|
-
const
|
|
6445
|
-
const
|
|
6446
|
-
const
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
]);
|
|
6457
|
-
const
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
const
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
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
|
-
|
|
6483
|
-
|
|
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__, {
|