@ohif/app 3.8.0-beta.8 → 3.8.0-beta.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/{220.bundle.f7e1c96c94245e70f2be.js → 109.bundle.b4fee2a22b622839baf5.js} +4466 -3715
  2. package/dist/{471.bundle.49c8d281adbae4a2c4df.js → 121.bundle.47f05840a5b3cdf75543.js} +94 -113
  3. package/dist/141.bundle.556b4c1e4cab770417ac.js +8620 -0
  4. package/dist/{687.bundle.9065db35c01823286f08.js → 164.bundle.fadc7c5d634402c73b5f.js} +22 -38
  5. package/dist/17dd54813d5acc10bf8f.wasm +0 -0
  6. package/dist/183.bundle.a3e238998be71c4b2af8.js +30410 -0
  7. package/dist/{506.bundle.5731bb4349e266491225.js → 188.bundle.51dc4b37920f45594393.js} +23 -28
  8. package/dist/{342.bundle.e7c3d500f86fdfcc62b5.js → 206.bundle.fcaa081a0d1f68095c31.js} +1991 -1145
  9. package/dist/20fc4c659b85ccd2a9c0.wasm +0 -0
  10. package/dist/217.bundle.d44bbaa50b6fa563fe15.js +115126 -0
  11. package/dist/{451.bundle.57c21db5d003c75e9d61.js → 295.bundle.5ace95771ced62bdcab8.js} +111 -128
  12. package/dist/{125.bundle.253395f320b72180da63.js → 297.bundle.194d8985ab974839b5b6.js} +7 -8
  13. package/dist/{19.bundle.f77c5787b6d8ac0b638b.js → 325.bundle.fd8e0c18db4708d03a91.js} +477 -373
  14. package/dist/335.bundle.8400aa5a88697a6b9d53.js +2590 -0
  15. package/dist/{202.bundle.d3490836f71e001dd30f.js → 342.bundle.e6d0bba29351b5650a8c.js} +566 -868
  16. package/dist/{776.bundle.a2dedb405a12ffd7699b.js → 41.bundle.0905b258a90a7c6437bb.js} +7453 -3624
  17. package/dist/422.bundle.c6fd037b075dd54f1ba7.js +865 -0
  18. package/dist/{957.bundle.9ea4506963ef8b2d84ba.js → 433.bundle.e0018820758f5a86fa7f.js} +14797 -27561
  19. package/dist/445.bundle.38c6d2af64e41cd7c614.js +7835 -0
  20. package/dist/{126.bundle.6e7111d58bcc937ffd80.js → 448.bundle.5e6da31477887bf53016.js} +356 -430
  21. package/dist/487.bundle.89d973049defb3ba6cb7.js +1876 -0
  22. package/dist/{886.bundle.c8dd3ecc42a4253de278.js → 530.bundle.207b38c15c4c01e4db0e.js} +104 -121
  23. package/dist/{250.bundle.aea3335667054bdefe36.js → 544.bundle.1c1f57118560046649c1.js} +37 -62
  24. package/dist/574.bundle.d648fea691d6709bf2b4.js +2652 -0
  25. package/dist/{181.css → 574.css} +1 -1
  26. package/dist/{410.bundle.15c855b0ff4a1a674fb8.js → 594.bundle.84076375b127b9c7f673.js} +183 -221
  27. package/dist/{221.bundle.aef554202c58483cb34e.js → 633.bundle.acab89baaa06a299d679.js} +365 -553
  28. package/dist/{774.bundle.4b2dc46a35012b898e1a.js → 644.bundle.1e77691d2eeb96a423b0.js} +1852 -8945
  29. package/dist/{663.bundle.d7be28450db14266cdd0.js → 669.bundle.b17e8a621e38d92c653f.js} +310 -265
  30. package/dist/699.bundle.9367d7ef9f7615b2e733.js +772 -0
  31. package/dist/702.bundle.963481fbf871984b646f.js +8426 -0
  32. package/dist/722.bundle.afab1fe6bfcd569130ac.js +1083 -0
  33. package/dist/{359.bundle.45ecb3d28e8c22142606.js → 724.bundle.55f9f49816de931af91a.js} +165 -260
  34. package/dist/{757.bundle.ec8301d8e70d2b990f65.js → 726.bundle.0b3d9277d22fe7e15b89.js} +512 -879
  35. package/dist/{530.bundle.a03b6f942ace3e1baa1e.js → 835.bundle.15aff0b7433bb0dd6d6d.js} +37 -30
  36. package/dist/{822.bundle.82cdc418f8f56da6060b.js → 862.bundle.d32ab08e64806b2e964d.js} +81 -97
  37. package/dist/{236.bundle.4e9924934a747afac132.js → 889.bundle.8ef8b723d0163d5d135c.js} +207 -199
  38. package/dist/{281.bundle.deb7492d143e7768d8bf.js → 905.bundle.8a96e1a75b7cfe5ec093.js} +157 -124
  39. package/dist/{814.bundle.c8c951d20039b63b865a.js → 907.bundle.5c88ed911bed18582da4.js} +16 -30
  40. package/dist/{417.bundle.af0a207c29b109f84159.js → 931.bundle.d270a1fda9a2836c3cc5.js} +26 -26
  41. package/dist/{686.bundle.dccef1f36e4bc79bcc48.js → 939.bundle.9d93b2e47c52338747a2.js} +7 -8
  42. package/dist/94.bundle.f5f2479c214180d05d42.js +778 -0
  43. package/dist/{12.bundle.b5ca13e5363f170ecb3b.js → 961.bundle.f4e52bc76d3044d05372.js} +20 -33
  44. package/dist/app-config.js +1 -0
  45. package/dist/app.bundle.css +16 -13
  46. package/dist/{app.bundle.a978edc59b9d82f2eb22.js → app.bundle.ed937512f7d19d61c411.js} +183396 -87682
  47. package/dist/assets/images/CT-AAA.png +0 -0
  48. package/dist/assets/images/CT-AAA2.png +0 -0
  49. package/dist/assets/images/CT-Air.png +0 -0
  50. package/dist/assets/images/CT-Bone.png +0 -0
  51. package/dist/assets/images/CT-Bones.png +0 -0
  52. package/dist/assets/images/CT-Cardiac.png +0 -0
  53. package/dist/assets/images/CT-Cardiac2.png +0 -0
  54. package/dist/assets/images/CT-Cardiac3.png +0 -0
  55. package/dist/assets/images/CT-Chest-Contrast-Enhanced.png +0 -0
  56. package/dist/assets/images/CT-Chest-Vessels.png +0 -0
  57. package/dist/assets/images/CT-Coronary-Arteries-2.png +0 -0
  58. package/dist/assets/images/CT-Coronary-Arteries-3.png +0 -0
  59. package/dist/assets/images/CT-Coronary-Arteries.png +0 -0
  60. package/dist/assets/images/CT-Cropped-Volume-Bone.png +0 -0
  61. package/dist/assets/images/CT-Fat.png +0 -0
  62. package/dist/assets/images/CT-Liver-Vasculature.png +0 -0
  63. package/dist/assets/images/CT-Lung.png +0 -0
  64. package/dist/assets/images/CT-MIP.png +0 -0
  65. package/dist/assets/images/CT-Muscle.png +0 -0
  66. package/dist/assets/images/CT-Pulmonary-Arteries.png +0 -0
  67. package/dist/assets/images/CT-Soft-Tissue.png +0 -0
  68. package/dist/assets/images/DTI-FA-Brain.png +0 -0
  69. package/dist/assets/images/MR-Angio.png +0 -0
  70. package/dist/assets/images/MR-Default.png +0 -0
  71. package/dist/assets/images/MR-MIP.png +0 -0
  72. package/dist/assets/images/MR-T2-Brain.png +0 -0
  73. package/dist/assets/images/VolumeRendering.png +0 -0
  74. package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
  75. package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
  76. package/dist/{dicom-microscopy-viewer.bundle.2c146384eb9466d02ff8.js → dicom-microscopy-viewer.bundle.d3a56dc9f62df5e11019.js} +3 -3
  77. package/dist/histogram-worker.bundle.829e14ec12c2b41a4323.js +359 -0
  78. package/dist/index.html +1 -1
  79. package/dist/{index.worker.e62ecca63f1a2e124230.worker.js → index.worker.64c896c4316fcd506666.worker.js} +2 -2
  80. package/dist/index.worker.64c896c4316fcd506666.worker.js.map +1 -0
  81. package/dist/polySeg.bundle.f1a6ece1396dc1385155.js +249 -0
  82. package/dist/serve.json +12 -0
  83. package/dist/sw.js +1 -1
  84. package/package.json +26 -22
  85. package/dist/181.bundle.a62b9f0ec692299acb35.js +0 -1527
  86. package/dist/23.bundle.e008ad788170f2ed5569.js +0 -900
  87. package/dist/604.bundle.a51f83e64004bca5f497.js +0 -1848
  88. package/dist/613.bundle.9e7072e5b575354fe51e.js +0 -532
  89. package/dist/743.bundle.489f7df3a089d4d374e1.js +0 -78007
  90. package/dist/75788f12450d4c5ed494.wasm +0 -0
  91. package/dist/775.bundle.2285e7e0e67878948c0d.js +0 -1009
  92. package/dist/788.bundle.207ac23c0dfa70cbe3fb.js +0 -2682
  93. package/dist/82.bundle.d6fdcca0f67540bb226a.js +0 -1049
  94. package/dist/index.worker.e62ecca63f1a2e124230.worker.js.map +0 -1
  95. /package/dist/{19.css → 325.css} +0 -0
  96. /package/dist/{776.css → 41.css} +0 -0
  97. /package/dist/{579.css → 481.css} +0 -0
  98. /package/dist/{250.css → 544.css} +0 -0
  99. /package/dist/{221.css → 633.css} +0 -0
@@ -0,0 +1,1876 @@
1
+ "use strict";
2
+ (globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[487],{
3
+
4
+ /***/ 26487:
5
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6
+
7
+ // ESM COMPAT FLAG
8
+ __webpack_require__.r(__webpack_exports__);
9
+
10
+ // EXPORTS
11
+ __webpack_require__.d(__webpack_exports__, {
12
+ "default": () => (/* binding */ dynamicVolumeExtension)
13
+ });
14
+
15
+ // NAMESPACE OBJECT: ../../../extensions/cornerstone-dynamic-volume/src/actions/index.ts
16
+ var actions_namespaceObject = {};
17
+ __webpack_require__.r(actions_namespaceObject);
18
+ __webpack_require__.d(actions_namespaceObject, {
19
+ updateSegmentationsChartDisplaySet: () => (updateSegmentationsChartDisplaySet)
20
+ });
21
+
22
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/package.json
23
+ const package_namespaceObject = /*#__PURE__*/JSON.parse('{"UU":"@ohif/extension-cornerstone-dynamic-volume"}');
24
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/id.js
25
+
26
+ const id = package_namespaceObject.UU;
27
+ const SOPClassHandlerName = 'dynamic-volume';
28
+
29
+ // EXTERNAL MODULE: ../../core/src/index.ts + 70 modules
30
+ var src = __webpack_require__(55411);
31
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 327 modules
32
+ var esm = __webpack_require__(44656);
33
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 18 modules
34
+ var dist_esm = __webpack_require__(24542);
35
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
36
+
37
+
38
+
39
+ const CHART_MODALITY = 'CHT';
40
+ const SEG_CHART_INSTANCE_UID = src.utils.guid();
41
+
42
+ // Private SOPClassUid for chart data
43
+ const ChartDataSOPClassUid = '1.9.451.13215.7.3.2.7.6.1';
44
+ const {
45
+ utilities: csToolsUtils
46
+ } = dist_esm;
47
+ function _getDateTimeStr() {
48
+ const now = new Date();
49
+ const date = now.getFullYear() + ('0' + now.getUTCMonth()).slice(-2) + ('0' + now.getUTCDate()).slice(-2);
50
+ const time = ('0' + now.getUTCHours()).slice(-2) + ('0' + now.getUTCMinutes()).slice(-2) + ('0' + now.getUTCSeconds()).slice(-2);
51
+ return {
52
+ date,
53
+ time
54
+ };
55
+ }
56
+ function _getTimePointsDataByTagName(volume, timePointsTag) {
57
+ const uniqueTimePoints = volume.imageIds.reduce((timePoints, imageId) => {
58
+ const instance = src.DicomMetadataStore.getInstanceByImageId(imageId);
59
+ const timePointValue = instance[timePointsTag];
60
+ if (timePointValue !== undefined) {
61
+ timePoints.add(timePointValue);
62
+ }
63
+ return timePoints;
64
+ }, new Set());
65
+ return Array.from(uniqueTimePoints).sort((a, b) => a - b);
66
+ }
67
+ function _convertTimePointsUnit(timePoints, timePointsUnit) {
68
+ const validUnits = ['ms', 's', 'm', 'h'];
69
+ const divisors = [1000, 60, 60];
70
+ const currentUnitIndex = validUnits.indexOf(timePointsUnit);
71
+ let divisor = 1;
72
+ if (currentUnitIndex !== -1) {
73
+ for (let i = currentUnitIndex; i < validUnits.length - 1; i++) {
74
+ const newDivisor = divisor * divisors[i];
75
+ const greaterThanDivisorCount = timePoints.filter(timePoint => timePoint > newDivisor).length;
76
+
77
+ // Change the scale only if more than 50% of the time points are
78
+ // greater than the new divisor.
79
+ if (greaterThanDivisorCount <= timePoints.length / 2) {
80
+ break;
81
+ }
82
+ divisor = newDivisor;
83
+ timePointsUnit = validUnits[i + 1];
84
+ }
85
+ if (divisor > 1) {
86
+ timePoints = timePoints.map(timePoint => timePoint / divisor);
87
+ }
88
+ }
89
+ return {
90
+ timePoints,
91
+ timePointsUnit
92
+ };
93
+ }
94
+
95
+ // It currently supports only one tag but a few other will be added soon
96
+ // Supported 4D Tags
97
+ // (0018,1060) Trigger Time [NOK]
98
+ // (0018,0081) Echo Time [NOK]
99
+ // (0018,0086) Echo Number [NOK]
100
+ // (0020,0100) Temporal Position Identifier [NOK]
101
+ // (0054,1300) FrameReferenceTime [OK]
102
+ function _getTimePointsData(volume) {
103
+ const timePointsTags = {
104
+ FrameReferenceTime: {
105
+ unit: 'ms'
106
+ }
107
+ };
108
+ const timePointsTagNames = Object.keys(timePointsTags);
109
+ let timePoints;
110
+ let timePointsUnit;
111
+ for (let i = 0; i < timePointsTagNames.length; i++) {
112
+ const tagName = timePointsTagNames[i];
113
+ const curTimePoints = _getTimePointsDataByTagName(volume, tagName);
114
+ if (curTimePoints.length) {
115
+ timePoints = curTimePoints;
116
+ timePointsUnit = timePointsTags[tagName].unit;
117
+ break;
118
+ }
119
+ }
120
+ if (!timePoints.length) {
121
+ const concatTagNames = timePointsTagNames.join(', ');
122
+ throw new Error(`Could not extract time points data for the following tags: ${concatTagNames}`);
123
+ }
124
+ const convertedTimePoints = _convertTimePointsUnit(timePoints, timePointsUnit);
125
+ timePoints = convertedTimePoints.timePoints;
126
+ timePointsUnit = convertedTimePoints.timePointsUnit;
127
+ return {
128
+ timePoints,
129
+ timePointsUnit
130
+ };
131
+ }
132
+ function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetService) {
133
+ const displaySets = displaySetService.getActiveDisplaySets();
134
+ const dynamic4DDisplaySet = displaySets.find(displaySet => {
135
+ const anInstance = displaySet.instances?.[0];
136
+ if (anInstance) {
137
+ return anInstance.FrameReferenceTime !== undefined || anInstance.NumberOfTimeSlices !== undefined;
138
+ }
139
+ return false;
140
+ });
141
+
142
+ // const referencedDynamicVolume = cs.cache.getVolume(dynamic4DDisplaySet.displaySetInstanceUID);
143
+ let volumeCacheKey;
144
+ const volumeId = dynamic4DDisplaySet.displaySetInstanceUID;
145
+ for (const [key] of esm.cache._volumeCache) {
146
+ if (key.includes(volumeId)) {
147
+ volumeCacheKey = key;
148
+ break;
149
+ }
150
+ }
151
+ let referencedDynamicVolume;
152
+ if (volumeCacheKey) {
153
+ referencedDynamicVolume = esm.cache.getVolume(volumeCacheKey);
154
+ }
155
+ const {
156
+ StudyInstanceUID,
157
+ StudyDescription
158
+ } = src.DicomMetadataStore.getInstanceByImageId(referencedDynamicVolume.imageIds[0]);
159
+ const [timeData, _] = csToolsUtils.dynamicVolume.getDataInTime(referencedDynamicVolume, {
160
+ maskVolumeId: segmentation.id
161
+ });
162
+ const pixelCount = timeData.length;
163
+ if (pixelCount === 0) {
164
+ return [];
165
+ }
166
+
167
+ // since we only use one segmentation representation per segmentationId
168
+ // it is fine to pick the first one
169
+ const segmentationRepresentations = dist_esm.segmentation.state.getSegmentationIdRepresentations(segmentation.id);
170
+ const segmentationRepresentationUID = segmentationRepresentations[0].segmentationRepresentationUID;
171
+ const toolGroupId = dist_esm.segmentation.state.getToolGroupIdFromSegmentationRepresentationUID(segmentationRepresentationUID);
172
+
173
+ // Todo: this is useless we should be able to grab color with just segRepUID and segmentIndex
174
+ const color = dist_esm.segmentation.config.color.getColorForSegmentIndex(toolGroupId, segmentationRepresentationUID, 1 // segmentIndex
175
+ );
176
+ const hexColor = esm.utilities.color.rgbToHex(...color);
177
+ let timePointsData = volumesTimePointsCache.get(referencedDynamicVolume);
178
+ if (!timePointsData) {
179
+ timePointsData = _getTimePointsData(referencedDynamicVolume);
180
+ volumesTimePointsCache.set(referencedDynamicVolume, timePointsData);
181
+ }
182
+ const {
183
+ timePoints,
184
+ timePointsUnit
185
+ } = timePointsData;
186
+ if (timePoints.length !== timeData[0].length) {
187
+ throw new Error('Invalid number of time points returned');
188
+ }
189
+ const timepointsCount = timePoints.length;
190
+ const chartSeriesData = new Array(timepointsCount);
191
+ for (let i = 0; i < timepointsCount; i++) {
192
+ const average = timeData.reduce((acc, cur) => acc + cur[i] / pixelCount, 0);
193
+ chartSeriesData[i] = [timePoints[i], average];
194
+ }
195
+ return {
196
+ StudyInstanceUID,
197
+ StudyDescription,
198
+ chartData: {
199
+ series: {
200
+ label: segmentation.label,
201
+ points: chartSeriesData,
202
+ color: hexColor
203
+ },
204
+ axis: {
205
+ x: {
206
+ label: `Time (${timePointsUnit})`
207
+ },
208
+ y: {
209
+ label: `Vl (Bq/ml)`
210
+ }
211
+ }
212
+ }
213
+ };
214
+ }
215
+ function _getInstanceFromSegmentations(segmentations, displaySetService) {
216
+ if (!segmentations.length) {
217
+ return;
218
+ }
219
+ const volumesTimePointsCache = new WeakMap();
220
+ const segmentationsData = segmentations.map(segmentation => _getSegmentationData(segmentation, volumesTimePointsCache, displaySetService));
221
+ const {
222
+ date: seriesDate,
223
+ time: seriesTime
224
+ } = _getDateTimeStr();
225
+ const series = segmentationsData.reduce((allSeries, curSegData) => {
226
+ return [...allSeries, curSegData.chartData.series];
227
+ }, []);
228
+ const instance = {
229
+ SOPClassUID: ChartDataSOPClassUid,
230
+ Modality: CHART_MODALITY,
231
+ SOPInstanceUID: src.utils.guid(),
232
+ SeriesDate: seriesDate,
233
+ SeriesTime: seriesTime,
234
+ SeriesInstanceUID: SEG_CHART_INSTANCE_UID,
235
+ StudyInstanceUID: segmentationsData[0].StudyInstanceUID,
236
+ StudyDescription: segmentationsData[0].StudyDescription,
237
+ SeriesNumber: 100,
238
+ SeriesDescription: 'Segmentation chart series data',
239
+ chartData: {
240
+ series,
241
+ axis: {
242
+ ...segmentationsData[0].chartData.axis
243
+ }
244
+ }
245
+ };
246
+ const seriesMetadata = {
247
+ StudyInstanceUID: instance.StudyInstanceUID,
248
+ StudyDescription: instance.StudyDescription,
249
+ SeriesInstanceUID: instance.SeriesInstanceUID,
250
+ SeriesDescription: instance.SeriesDescription,
251
+ SeriesNumber: instance.SeriesNumber,
252
+ SeriesTime: instance.SeriesTime,
253
+ SOPClassUID: instance.SOPClassUID,
254
+ Modality: instance.Modality
255
+ };
256
+ return {
257
+ seriesMetadata,
258
+ instance
259
+ };
260
+ }
261
+ function updateSegmentationsChartDisplaySet({
262
+ servicesManager
263
+ }) {
264
+ const {
265
+ segmentationService,
266
+ displaySetService
267
+ } = servicesManager.services;
268
+ const segmentations = segmentationService.getSegmentations();
269
+ const {
270
+ seriesMetadata,
271
+ instance
272
+ } = _getInstanceFromSegmentations(segmentations, displaySetService) ?? {};
273
+ if (seriesMetadata && instance) {
274
+ // An event is triggered after adding the instance and the displaySet is created
275
+ src.DicomMetadataStore.addSeriesMetadata([seriesMetadata], true);
276
+ src.DicomMetadataStore.addInstances([instance], true);
277
+ }
278
+ }
279
+
280
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/actions/index.ts
281
+
282
+
283
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/commandsModule.ts
284
+
285
+
286
+
287
+ const LABELMAP = dist_esm.Enums.SegmentationRepresentations.Labelmap;
288
+ const commandsModule = ({
289
+ commandsManager,
290
+ servicesManager
291
+ }) => {
292
+ const services = servicesManager.services;
293
+ const {
294
+ displaySetService,
295
+ viewportGridService,
296
+ segmentationService
297
+ } = services;
298
+ const actions = {
299
+ ...actions_namespaceObject,
300
+ getDynamic4DDisplaySet: () => {
301
+ const displaySets = displaySetService.getActiveDisplaySets();
302
+ const dynamic4DDisplaySet = displaySets.find(displaySet => {
303
+ const anInstance = displaySet.instances?.[0];
304
+ if (anInstance) {
305
+ return anInstance.FrameReferenceTime !== undefined || anInstance.NumberOfTimeSlices !== undefined || anInstance.TemporalPositionIdentifier !== undefined;
306
+ }
307
+ return false;
308
+ });
309
+ return dynamic4DDisplaySet;
310
+ },
311
+ getComputedDisplaySets: () => {
312
+ const displaySetCache = displaySetService.getDisplaySetCache();
313
+ const cachedDisplaySets = [...displaySetCache.values()];
314
+ const computedDisplaySets = cachedDisplaySets.filter(displaySet => {
315
+ return displaySet.isDerived;
316
+ });
317
+ return computedDisplaySets;
318
+ },
319
+ exportTimeReportCSV: ({
320
+ segmentations,
321
+ config,
322
+ options,
323
+ summaryStats
324
+ }) => {
325
+ const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet();
326
+ const volumeId = dynamic4DDisplaySet?.displaySetInstanceUID;
327
+
328
+ // cache._volumeCache is a map that has a key that includes the volumeId
329
+ // it is not exactly the volumeId, but it is the key that includes the volumeId
330
+ // so we can't do cache._volumeCache.get(volumeId) we should iterate
331
+ // over the keys and find the one that includes the volumeId
332
+ let volumeCacheKey;
333
+ for (const [key] of esm.cache._volumeCache) {
334
+ if (key.includes(volumeId)) {
335
+ volumeCacheKey = key;
336
+ break;
337
+ }
338
+ }
339
+ let dynamicVolume;
340
+ if (volumeCacheKey) {
341
+ dynamicVolume = esm.cache.getVolume(volumeCacheKey);
342
+ }
343
+ const instance = dynamic4DDisplaySet.instances[0];
344
+ const csv = [];
345
+
346
+ // CSV header information with placeholder empty values for the metadata lines
347
+ csv.push(`Patient ID,${instance.PatientID},`);
348
+ csv.push(`Study Date,${instance.StudyDate},`);
349
+ csv.push(`StudyInstanceUID,${instance.StudyInstanceUID},`);
350
+ csv.push(`StudyDescription,${instance.StudyDescription},`);
351
+ csv.push(`SeriesInstanceUID,${instance.SeriesInstanceUID},`);
352
+
353
+ // empty line
354
+ csv.push('');
355
+ csv.push('');
356
+
357
+ // Helper function to calculate standard deviation
358
+ function calculateStandardDeviation(data) {
359
+ const n = data.length;
360
+ const mean = data.reduce((acc, value) => acc + value, 0) / n;
361
+ const squaredDifferences = data.map(value => (value - mean) ** 2);
362
+ const variance = squaredDifferences.reduce((acc, value) => acc + value, 0) / n;
363
+ const stdDeviation = Math.sqrt(variance);
364
+ return stdDeviation;
365
+ }
366
+
367
+ // Iterate through each segmentation to get the timeData and ijkCoords
368
+ segmentations.forEach((segmentation, segmentationIndex) => {
369
+ const [timeData, ijkCoords] = dist_esm.utilities.dynamicVolume.getDataInTime(dynamicVolume, {
370
+ maskVolumeId: segmentation.id
371
+ });
372
+ if (summaryStats) {
373
+ // Adding column headers for pixel identifier and segmentation label ids
374
+ let headers = 'Operation,Segmentation Label ID';
375
+ const maxLength = dynamicVolume.numTimePoints;
376
+ for (let t = 0; t < maxLength; t++) {
377
+ headers += `,Time Point ${t}`;
378
+ }
379
+ csv.push(headers);
380
+ // // perform summary statistics on the timeData including for each time point, mean, median, min, max, and standard deviation for
381
+ // // all the voxels in the ROI
382
+ const mean = [];
383
+ const min = [];
384
+ const minIJK = [];
385
+ const max = [];
386
+ const maxIJK = [];
387
+ const std = [];
388
+ const numVoxels = timeData.length;
389
+ // Helper function to calculate standard deviation
390
+ for (let timeIndex = 0; timeIndex < maxLength; timeIndex++) {
391
+ // for each voxel in the ROI, get the value at the current time point
392
+ const voxelValues = [];
393
+ for (let voxelIndex = 0; voxelIndex < numVoxels; voxelIndex++) {
394
+ voxelValues.push(timeData[voxelIndex][timeIndex]);
395
+ }
396
+ mean.push(voxelValues.reduce((acc, value) => acc + value, 0) / numVoxels);
397
+ const minimum = Math.min(...voxelValues);
398
+ min.push(minimum);
399
+ minIJK.push(ijkCoords[voxelValues.indexOf(minimum)]);
400
+ const maximum = Math.max(...voxelValues);
401
+ max.push(maximum);
402
+ maxIJK.push(ijkCoords[voxelValues.indexOf(maximum)]);
403
+ std.push(calculateStandardDeviation(voxelValues));
404
+ }
405
+ let row = `Mean,${segmentation.label}`;
406
+ // Generate separate rows for each statistic
407
+ for (let t = 0; t < maxLength; t++) {
408
+ row += `,${mean[t]}`;
409
+ }
410
+ csv.push(row);
411
+ row = `Standard Deviation,${segmentation.label}`;
412
+ for (let t = 0; t < maxLength; t++) {
413
+ row += `,${std[t]}`;
414
+ }
415
+ csv.push(row);
416
+ row = `Min,${segmentation.label}`;
417
+ for (let t = 0; t < maxLength; t++) {
418
+ row += `,${min[t]}`;
419
+ }
420
+ csv.push(row);
421
+ row = `Max,${segmentation.label}`;
422
+ for (let t = 0; t < maxLength; t++) {
423
+ row += `,${max[t]}`;
424
+ }
425
+ csv.push(row);
426
+ } else {
427
+ // Adding column headers for pixel identifier and segmentation label ids
428
+ let headers = 'Pixel Identifier (IJK),Segmentation Label ID';
429
+ const maxLength = dynamicVolume.numTimePoints;
430
+ for (let t = 0; t < maxLength; t++) {
431
+ headers += `,Time Point ${t}`;
432
+ }
433
+ csv.push(headers);
434
+ // Assuming timeData and ijkCoords are of the same length
435
+ for (let i = 0; i < timeData.length; i++) {
436
+ // Generate the pixel identifier
437
+ const pixelIdentifier = `${ijkCoords[i][0]}_${ijkCoords[i][1]}_${ijkCoords[i][2]}`;
438
+
439
+ // Start a new row for the current pixel
440
+ let row = `${pixelIdentifier},${segmentation.label}`;
441
+
442
+ // Add time data points for this pixel
443
+ for (let t = 0; t < timeData[i].length; t++) {
444
+ row += `,${timeData[i][t]}`;
445
+ }
446
+
447
+ // Append the row to the CSV array
448
+ csv.push(row);
449
+ }
450
+ }
451
+ });
452
+
453
+ // Convert to CSV string
454
+ const csvContent = csv.join('\n');
455
+
456
+ // Generate filename and trigger download
457
+ const filename = `${instance.PatientID}.csv`;
458
+ const blob = new Blob([csvContent], {
459
+ type: 'text/csv;charset=utf-8;'
460
+ });
461
+ const link = document.createElement('a');
462
+ const url = URL.createObjectURL(blob);
463
+ link.setAttribute('href', url);
464
+ link.setAttribute('download', filename);
465
+ link.style.visibility = 'hidden';
466
+ document.body.appendChild(link);
467
+ link.click();
468
+ document.body.removeChild(link);
469
+ },
470
+ swapDynamicWithComputedDisplaySet: ({
471
+ displaySet
472
+ }) => {
473
+ const computedDisplaySet = displaySet;
474
+ const displaySetCache = displaySetService.getDisplaySetCache();
475
+ const cachedDisplaySetKeys = [displaySetCache.keys()];
476
+ const {
477
+ displaySetInstanceUID
478
+ } = computedDisplaySet;
479
+ // Check to see if computed display set is already in cache
480
+ if (!cachedDisplaySetKeys.includes(displaySetInstanceUID)) {
481
+ displaySetCache.set(displaySetInstanceUID, computedDisplaySet);
482
+ }
483
+
484
+ // Get all viewports and their corresponding indices
485
+ const {
486
+ viewports
487
+ } = viewportGridService.getState();
488
+
489
+ // get the viewports in the grid
490
+ // iterate over them and find the ones that are showing a dynamic
491
+ // volume (displaySet), and replace that exact displaySet with the
492
+ // computed displaySet
493
+
494
+ const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet();
495
+ const viewportsToUpdate = [];
496
+ for (const [key, value] of viewports) {
497
+ const viewport = value;
498
+ const viewportOptions = viewport.viewportOptions;
499
+ const {
500
+ displaySetInstanceUIDs
501
+ } = viewport;
502
+ const displaySetInstanceUIDIndex = displaySetInstanceUIDs.indexOf(dynamic4DDisplaySet.displaySetInstanceUID);
503
+ if (displaySetInstanceUIDIndex !== -1) {
504
+ const newViewport = {
505
+ viewportId: viewport.viewportId,
506
+ // merge the other displaySetInstanceUIDs with the new one
507
+ displaySetInstanceUIDs: [...displaySetInstanceUIDs.slice(0, displaySetInstanceUIDIndex), displaySetInstanceUID, ...displaySetInstanceUIDs.slice(displaySetInstanceUIDIndex + 1)],
508
+ viewportOptions: {
509
+ initialImageOptions: viewportOptions.initialImageOptions,
510
+ viewportType: 'volume',
511
+ orientation: viewportOptions.orientation,
512
+ background: viewportOptions.background
513
+ }
514
+ };
515
+ viewportsToUpdate.push(newViewport);
516
+ }
517
+ }
518
+ viewportGridService.setDisplaySetsForViewports(viewportsToUpdate);
519
+ },
520
+ swapComputedWithDynamicDisplaySet: () => {
521
+ // Todo: this assumes there is only one dynamic display set in the viewer
522
+ const dynamicDisplaySet = actions.getDynamic4DDisplaySet();
523
+ const displaySetCache = displaySetService.getDisplaySetCache();
524
+ const cachedDisplaySetKeys = [...displaySetCache.keys()]; // Fix: Spread to get the array
525
+ const {
526
+ displaySetInstanceUID
527
+ } = dynamicDisplaySet;
528
+
529
+ // Check to see if dynamic display set is already in cache
530
+ if (!cachedDisplaySetKeys.includes(displaySetInstanceUID)) {
531
+ displaySetCache.set(displaySetInstanceUID, dynamicDisplaySet);
532
+ }
533
+
534
+ // Get all viewports and their corresponding indices
535
+ const {
536
+ viewports
537
+ } = viewportGridService.getState();
538
+
539
+ // Get the computed 4D display set
540
+ const computed4DDisplaySet = actions.getComputedDisplaySets()[0];
541
+ const viewportsToUpdate = [];
542
+ for (const [key, value] of viewports) {
543
+ const viewport = value;
544
+ const viewportOptions = viewport.viewportOptions;
545
+ const {
546
+ displaySetInstanceUIDs
547
+ } = viewport;
548
+ const displaySetInstanceUIDIndex = displaySetInstanceUIDs.indexOf(computed4DDisplaySet.displaySetInstanceUID);
549
+ if (displaySetInstanceUIDIndex !== -1) {
550
+ const newViewport = {
551
+ viewportId: viewport.viewportId,
552
+ // merge the other displaySetInstanceUIDs with the new one
553
+ displaySetInstanceUIDs: [...displaySetInstanceUIDs.slice(0, displaySetInstanceUIDIndex), displaySetInstanceUID, ...displaySetInstanceUIDs.slice(displaySetInstanceUIDIndex + 1)],
554
+ viewportOptions: {
555
+ initialImageOptions: viewportOptions.initialImageOptions,
556
+ viewportType: 'volume',
557
+ orientation: viewportOptions.orientation,
558
+ background: viewportOptions.background
559
+ }
560
+ };
561
+ viewportsToUpdate.push(newViewport);
562
+ }
563
+ }
564
+ viewportGridService.setDisplaySetsForViewports(viewportsToUpdate);
565
+ },
566
+ createNewLabelMapForDynamicVolume: async ({
567
+ label
568
+ }) => {
569
+ const {
570
+ viewports,
571
+ activeViewportId
572
+ } = viewportGridService.getState();
573
+
574
+ // get the dynamic 4D display set
575
+ const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet();
576
+ const dynamic4DDisplaySetInstanceUID = dynamic4DDisplaySet.displaySetInstanceUID;
577
+
578
+ // check if the dynamic 4D display set is in the display, if not we might have
579
+ // the computed volumes and we should choose them for the segmentation
580
+ // creation
581
+
582
+ let referenceDisplaySet;
583
+ const activeViewport = viewports.get(activeViewportId);
584
+ const activeDisplaySetInstanceUIDs = activeViewport.displaySetInstanceUIDs;
585
+ const dynamicIsInActiveViewport = activeDisplaySetInstanceUIDs.includes(dynamic4DDisplaySetInstanceUID);
586
+ if (dynamicIsInActiveViewport) {
587
+ referenceDisplaySet = dynamic4DDisplaySet;
588
+ }
589
+ if (!referenceDisplaySet) {
590
+ // try to see if there is any derived displaySet in the active viewport
591
+ // which is referencing the dynamic 4D display set
592
+
593
+ // Todo: this is wrong but I don't have time to fix it now
594
+ const cachedDisplaySets = displaySetService.getDisplaySetCache();
595
+ for (const [key, displaySet] of cachedDisplaySets) {
596
+ if (displaySet.referenceDisplaySetUID === dynamic4DDisplaySetInstanceUID) {
597
+ referenceDisplaySet = displaySet;
598
+ break;
599
+ }
600
+ }
601
+ }
602
+ if (!referenceDisplaySet) {
603
+ throw new Error('No reference display set found based on the dynamic data');
604
+ }
605
+ const segmentationId = await segmentationService.createSegmentationForDisplaySet(referenceDisplaySet.displaySetInstanceUID, {
606
+ label
607
+ });
608
+
609
+ // Add Segmentation to all toolGroupIds in the viewer
610
+ const toolGroupIds = Array.from(viewports.values(), viewport => viewport.viewportOptions.toolGroupId);
611
+ const representationType = LABELMAP;
612
+ for (const toolGroupId of toolGroupIds) {
613
+ const hydrateSegmentation = true;
614
+ await segmentationService.addSegmentationRepresentationToToolGroup(toolGroupId, segmentationId, hydrateSegmentation, representationType);
615
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
616
+ }
617
+ return segmentationId;
618
+ }
619
+ };
620
+ const definitions = {
621
+ updateSegmentationsChartDisplaySet: {
622
+ commandFn: actions.updateSegmentationsChartDisplaySet,
623
+ storeContexts: [],
624
+ options: {}
625
+ },
626
+ exportTimeReportCSV: {
627
+ commandFn: actions.exportTimeReportCSV,
628
+ storeContexts: [],
629
+ options: {}
630
+ },
631
+ swapDynamicWithComputedDisplaySet: {
632
+ commandFn: actions.swapDynamicWithComputedDisplaySet,
633
+ storeContexts: [],
634
+ options: {}
635
+ },
636
+ createNewLabelMapForDynamicVolume: {
637
+ commandFn: actions.createNewLabelMapForDynamicVolume,
638
+ storeContexts: [],
639
+ options: {}
640
+ },
641
+ swapComputedWithDynamicDisplaySet: {
642
+ commandFn: actions.swapComputedWithDynamicDisplaySet,
643
+ storeContexts: [],
644
+ options: {}
645
+ }
646
+ };
647
+ return {
648
+ actions,
649
+ definitions,
650
+ defaultContext: 'DYNAMIC-VOLUME:CORNERSTONE'
651
+ };
652
+ };
653
+ /* harmony default export */ const src_commandsModule = (commandsModule);
654
+ // EXTERNAL MODULE: ../../../node_modules/react/index.js
655
+ var react = __webpack_require__(41766);
656
+ // EXTERNAL MODULE: ../../../node_modules/prop-types/index.js
657
+ var prop_types = __webpack_require__(11374);
658
+ var prop_types_default = /*#__PURE__*/__webpack_require__.n(prop_types);
659
+ // EXTERNAL MODULE: ../../ui/src/index.js + 785 modules
660
+ var ui_src = __webpack_require__(5085);
661
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/streaming-image-volume-loader/dist/esm/index.js + 13 modules
662
+ var streaming_image_volume_loader_dist_esm = __webpack_require__(23722);
663
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/DynamicVolumeControls.tsx
664
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
665
+
666
+
667
+
668
+ const controlClassNames = {
669
+ sizeClassName: 'w-[58px] h-[28px]',
670
+ arrowsDirection: 'horizontal',
671
+ labelPosition: 'bottom'
672
+ };
673
+ const Header = ({
674
+ title,
675
+ tooltip
676
+ }) => /*#__PURE__*/react.createElement("div", {
677
+ className: "flex items-center space-x-1"
678
+ }, /*#__PURE__*/react.createElement(ui_src/* Tooltip */.m_, {
679
+ content: /*#__PURE__*/react.createElement("div", {
680
+ className: "text-white"
681
+ }, tooltip),
682
+ position: "bottom-left",
683
+ tight: true,
684
+ tooltipBoxClassName: "max-w-xs"
685
+ }, /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
686
+ name: "info-link",
687
+ className: "text-primary-active h-[14px] w-[14px]"
688
+ })), /*#__PURE__*/react.createElement("span", {
689
+ className: "text-aqua-pale text-[11px] uppercase"
690
+ }, title));
691
+ const DynamicVolumeControls = ({
692
+ isPlaying,
693
+ onPlayPauseChange,
694
+ // fps
695
+ fps,
696
+ onFpsChange,
697
+ minFps,
698
+ maxFps,
699
+ // Frames
700
+ currentFrameIndex,
701
+ onFrameChange,
702
+ framesLength,
703
+ onGenerate,
704
+ onDoubleRangeChange,
705
+ onDynamicClick
706
+ }) => {
707
+ const [computedView, setComputedView] = (0,react.useState)(false);
708
+ const [computeViewMode, setComputeViewMode] = (0,react.useState)(esm.Enums.DynamicOperatorType.SUM);
709
+ const [sliderRangeValues, setSliderRangeValues] = (0,react.useState)([framesLength / 4, framesLength / 2]);
710
+ (0,react.useEffect)(() => {
711
+ setSliderRangeValues([framesLength / 4, framesLength / 2]);
712
+ }, [framesLength]);
713
+ const handleSliderChange = newValues => {
714
+ onDoubleRangeChange(newValues);
715
+ if (newValues[0] === sliderRangeValues[0] && newValues[1] === sliderRangeValues[1]) {
716
+ return;
717
+ }
718
+ setSliderRangeValues(newValues);
719
+ };
720
+ return /*#__PURE__*/react.createElement("div", {
721
+ className: "flex select-none flex-col"
722
+ }, /*#__PURE__*/react.createElement(ui_src/* PanelSection */.aU, {
723
+ title: "Controls",
724
+ childrenClassName: "space-y-4 pb-5 px-5"
725
+ }, /*#__PURE__*/react.createElement("div", {
726
+ className: "mt-2"
727
+ }, /*#__PURE__*/react.createElement(Header, {
728
+ title: "View",
729
+ tooltip: 'Select the view mode, 4D to view the dynamic volume or Computed to view the computed volume'
730
+ }), /*#__PURE__*/react.createElement(ui_src/* ButtonGroup */.e2, {
731
+ className: "mt-2 w-full"
732
+ }, /*#__PURE__*/react.createElement("button", {
733
+ className: "w-1/2",
734
+ onClick: () => {
735
+ setComputedView(false);
736
+ onDynamicClick?.();
737
+ }
738
+ }, "4D"), /*#__PURE__*/react.createElement("button", {
739
+ className: "w-1/2",
740
+ onClick: () => {
741
+ setComputedView(true);
742
+ }
743
+ }, "Computed"))), /*#__PURE__*/react.createElement("div", null, /*#__PURE__*/react.createElement(FrameControls, {
744
+ onPlayPauseChange: onPlayPauseChange,
745
+ isPlaying: isPlaying,
746
+ computedView: computedView
747
+ // fps
748
+ ,
749
+ fps: fps,
750
+ onFpsChange: onFpsChange,
751
+ minFps: minFps,
752
+ maxFps: maxFps
753
+ //
754
+ ,
755
+ framesLength: framesLength,
756
+ onFrameChange: onFrameChange,
757
+ currentFrameIndex: currentFrameIndex
758
+ })), /*#__PURE__*/react.createElement("div", {
759
+ className: `mt-6 flex flex-col ${computedView ? '' : 'ohif-disabled'}`
760
+ }, /*#__PURE__*/react.createElement(Header, {
761
+ title: "Computed Operation"
762
+ }), /*#__PURE__*/react.createElement(ui_src/* ButtonGroup */.e2, {
763
+ className: `mt-2 w-full `,
764
+ separated: true
765
+ }, /*#__PURE__*/react.createElement("button", {
766
+ className: "w-1/2",
767
+ onClick: () => setComputeViewMode(esm.Enums.DynamicOperatorType.SUM)
768
+ }, esm.Enums.DynamicOperatorType.SUM.toString().toUpperCase()), /*#__PURE__*/react.createElement("button", {
769
+ className: "w-1/2",
770
+ onClick: () => setComputeViewMode(esm.Enums.DynamicOperatorType.AVERAGE)
771
+ }, esm.Enums.DynamicOperatorType.AVERAGE.toString().toUpperCase()), /*#__PURE__*/react.createElement("button", {
772
+ className: "w-1/2",
773
+ onClick: () => setComputeViewMode(esm.Enums.DynamicOperatorType.SUBTRACT)
774
+ }, esm.Enums.DynamicOperatorType.SUBTRACT.toString().toUpperCase())), /*#__PURE__*/react.createElement("div", {
775
+ className: "w-ful mt-2"
776
+ }, /*#__PURE__*/react.createElement(ui_src/* InputDoubleRange */.Z5, {
777
+ values: sliderRangeValues,
778
+ onChange: handleSliderChange,
779
+ minValue: 0,
780
+ showLabel: true,
781
+ allowNumberEdit: true,
782
+ maxValue: framesLength,
783
+ step: 1
784
+ })), /*#__PURE__*/react.createElement(ui_src/* Button */.$n, {
785
+ className: "mt-2 !h-[26px] !w-[115px] self-start !p-0",
786
+ onClick: () => {
787
+ onGenerate(computeViewMode);
788
+ }
789
+ }, "Generate"))));
790
+ };
791
+ /* harmony default export */ const panels_DynamicVolumeControls = (DynamicVolumeControls);
792
+ function FrameControls({
793
+ isPlaying,
794
+ onPlayPauseChange,
795
+ fps,
796
+ minFps,
797
+ maxFps,
798
+ onFpsChange,
799
+ framesLength,
800
+ onFrameChange,
801
+ currentFrameIndex,
802
+ computedView
803
+ }) {
804
+ const getPlayPauseIconName = () => isPlaying ? 'icon-pause' : 'icon-play';
805
+ return /*#__PURE__*/react.createElement("div", {
806
+ className: computedView && 'ohif-disabled'
807
+ }, /*#__PURE__*/react.createElement(Header, {
808
+ title: "4D Controls"
809
+ }), /*#__PURE__*/react.createElement("div", {
810
+ className: "mt-3 flex justify-between"
811
+ }, /*#__PURE__*/react.createElement(ui_src/* IconButton */.K0, {
812
+ className: "bg-customblue-30 h-[26px] w-[58px] rounded-[4px]",
813
+ onClick: () => onPlayPauseChange(!isPlaying)
814
+ }, /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
815
+ name: getPlayPauseIconName(),
816
+ className: " active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
817
+ })), /*#__PURE__*/react.createElement(ui_src/* InputNumber */.YI, _extends({
818
+ value: currentFrameIndex,
819
+ onChange: onFrameChange,
820
+ minValue: 0,
821
+ maxValue: framesLength,
822
+ label: "Frame"
823
+ }, controlClassNames)), /*#__PURE__*/react.createElement(ui_src/* InputNumber */.YI, _extends({
824
+ value: fps,
825
+ onChange: onFpsChange,
826
+ minValue: minFps,
827
+ maxValue: maxFps
828
+ }, controlClassNames, {
829
+ label: "FPS"
830
+ }))));
831
+ }
832
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/PanelGenerateImage.tsx
833
+
834
+
835
+
836
+
837
+
838
+
839
+
840
+ const SOPClassHandlerId = '@ohif/extension-default.sopClassHandlerModule.stack';
841
+ function PanelGenerateImage({
842
+ servicesManager,
843
+ commandsManager
844
+ }) {
845
+ const {
846
+ cornerstoneViewportService,
847
+ viewportGridService,
848
+ displaySetService
849
+ } = servicesManager.services;
850
+ const [{
851
+ isCineEnabled
852
+ }, cineService] = (0,ui_src/* useCine */.tq)();
853
+ const [{
854
+ activeViewportId
855
+ }] = (0,ui_src/* useViewportGrid */.ih)();
856
+
857
+ //
858
+ const [timePointsRange, setTimePointsRange] = (0,react.useState)([]);
859
+ const [timePointsRangeToUseForGenerate, setTimePointsRangeToUseForGenerate] = (0,react.useState)([]);
860
+ const [computedDisplaySet, setComputedDisplaySet] = (0,react.useState)(null);
861
+ const [dynamicVolume, setDynamicVolume] = (0,react.useState)(null);
862
+ const [frameRate, setFrameRate] = (0,react.useState)(20);
863
+ const [isPlaying, setIsPlaying] = (0,react.useState)(isCineEnabled);
864
+ const [timePointRendered, setTimePointRendered] = (0,react.useState)(null);
865
+ const [displayingComputed, setDisplayingComputed] = (0,react.useState)(false);
866
+
867
+ //
868
+ const uuidComputedVolume = (0,react.useRef)(esm.utilities.uuidv4());
869
+ const uuidDynamicVolume = (0,react.useRef)(null);
870
+ const computedVolumeId = `cornerstoneStreamingImageVolume:${uuidComputedVolume.current}`;
871
+ (0,react.useEffect)(() => {
872
+ const evt = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
873
+ const {
874
+ unsubscribe
875
+ } = cornerstoneViewportService.subscribe(evt, evtDetails => {
876
+ evtDetails.viewportData.data.forEach(volumeData => {
877
+ if (volumeData.volume.isDynamicVolume()) {
878
+ setDynamicVolume(volumeData.volume);
879
+ uuidDynamicVolume.current = volumeData.displaySetInstanceUID;
880
+ setTimePointsRange([1, volumeData.volume.numTimePoints]);
881
+ }
882
+ });
883
+ });
884
+ return () => {
885
+ unsubscribe();
886
+ };
887
+ }, [cornerstoneViewportService]);
888
+ (0,react.useEffect)(() => {
889
+ const {
890
+ unsubscribe
891
+ } = servicesManager.services.cineService.subscribe(servicesManager.services.cineService.EVENTS.CINE_STATE_CHANGED, evt => {
892
+ setIsPlaying(evt.isPlaying);
893
+ });
894
+ return () => {
895
+ unsubscribe();
896
+ };
897
+ }, [cineService]);
898
+ (0,react.useEffect)(() => {
899
+ const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(activeViewportId);
900
+ if (!displaySetUIDs || displaySetUIDs.length === 0) {
901
+ return;
902
+ }
903
+ const displaySets = displaySetUIDs.map(displaySetUID => displaySetService.getDisplaySetByUID(displaySetUID));
904
+ const dynamicVolumeDisplaySet = displaySets.find(displaySet => displaySet.isDynamicVolume);
905
+ if (!dynamicVolumeDisplaySet) {
906
+ return;
907
+ }
908
+ const dynamicVolume = esm.cache.getVolumes().find(volume => volume.volumeId.includes(dynamicVolumeDisplaySet.displaySetInstanceUID));
909
+ if (!dynamicVolume) {
910
+ return;
911
+ }
912
+ setDynamicVolume(dynamicVolume);
913
+ uuidDynamicVolume.current = dynamicVolumeDisplaySet.displaySetInstanceUID;
914
+ setTimePointsRange([1, dynamicVolume.numTimePoints]);
915
+ }, [activeViewportId, cornerstoneViewportService]);
916
+ (0,react.useEffect)(() => {
917
+ // ~~ Subscription
918
+ const evt = streaming_image_volume_loader_dist_esm/* Enums.Events */.fX.s.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;
919
+ const callback = evt => {
920
+ setTimePointRendered(evt.detail.timePointIndex);
921
+ };
922
+ esm.eventTarget.addEventListener(evt, callback);
923
+ return () => {
924
+ esm.eventTarget.removeEventListener(evt, callback);
925
+ };
926
+ }, [cornerstoneViewportService]);
927
+ function renderGeneratedImage(displaySet) {
928
+ commandsManager.runCommand('swapDynamicWithComputedDisplaySet', {
929
+ displaySet
930
+ });
931
+ setDisplayingComputed(true);
932
+ }
933
+ function renderDynamicImage(displaySet) {
934
+ commandsManager.runCommand('swapComputedWithDynamicDisplaySet');
935
+ }
936
+
937
+ // Get computed volume from cache, calculate the data across the time frames,
938
+ // set the scalar data to the computedVolume, and create displaySet
939
+ async function onGenerateImage(operationName) {
940
+ const dynamicVolumeId = dynamicVolume.volumeId;
941
+ if (!dynamicVolumeId) {
942
+ return;
943
+ }
944
+ let computedVolume = esm.cache.getVolume(computedVolumeId);
945
+ if (!computedVolume) {
946
+ await createComputedVolume(dynamicVolumeId, computedVolumeId);
947
+ computedVolume = esm.cache.getVolume(computedVolumeId);
948
+ }
949
+ const vals = timePointsRangeToUseForGenerate;
950
+ const targets = Array.from({
951
+ length: vals[1] - vals[0] + 1
952
+ }, (_, i) => i + vals[0]);
953
+ const dataInTime = dist_esm.utilities.dynamicVolume.generateImageFromTimeData(dynamicVolume, operationName, operationName === 'SUBTRACT' ? vals : targets);
954
+
955
+ // Add loadStatus.loaded to computed volume and set to true
956
+ computedVolume.loadStatus = {};
957
+ computedVolume.loadStatus.loaded = true;
958
+ // Set computed scalar data to volume
959
+ const scalarData = computedVolume.getScalarData();
960
+ scalarData.set(dataInTime);
961
+
962
+ // If computed display set does not exist, create an object to be used as
963
+ // the displaySet. If it does exist, update the image data and vtkTexture
964
+ if (!computedDisplaySet) {
965
+ const displaySet = {
966
+ volumeLoaderSchema: computedVolume.volumeId.split(':')[0],
967
+ displaySetInstanceUID: uuidComputedVolume.current,
968
+ SOPClassHandlerId: SOPClassHandlerId,
969
+ Modality: dynamicVolume.metadata.Modality,
970
+ isMultiFrame: false,
971
+ numImageFrames: 1,
972
+ uid: uuidComputedVolume.current,
973
+ referenceDisplaySetUID: dynamicVolume.volumeId.split(':')[1],
974
+ madeInClient: true,
975
+ FrameOfReferenceUID: dynamicVolume.metadata.FrameOfReferenceUID,
976
+ isDerived: true
977
+ };
978
+ setComputedDisplaySet(displaySet);
979
+ renderGeneratedImage(displaySet);
980
+ } else {
981
+ commandsManager.runCommand('updateVolumeData', {
982
+ volume: computedVolume
983
+ });
984
+ // Check if viewport is currently displaying the computed volume, if so,
985
+ // call render on the viewports to update the image, if not, call
986
+ // renderGeneratedImage
987
+ // if (!cache.getVolume(dynamicVolumeId)) {
988
+ // for (const viewportId of viewports.keys()) {
989
+ // const viewportForRendering =
990
+ // cornerstoneViewportService.getCornerstoneViewport(viewportId);
991
+ // viewportForRendering.render();
992
+ // }
993
+ // } else {
994
+ cornerstoneViewportService.getRenderingEngine().render();
995
+ renderGeneratedImage(computedDisplaySet);
996
+ // }
997
+ }
998
+ }
999
+ const onPlayPauseChange = isPlaying => {
1000
+ isPlaying ? handlePlay() : handleStop();
1001
+ };
1002
+ const handlePlay = () => {
1003
+ setIsPlaying(true);
1004
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(activeViewportId);
1005
+ if (!viewportInfo) {
1006
+ return;
1007
+ }
1008
+ const {
1009
+ element
1010
+ } = viewportInfo;
1011
+ cineService.playClip(element, {
1012
+ framesPerSecond: frameRate,
1013
+ viewportId: activeViewportId
1014
+ });
1015
+ };
1016
+ const handleStop = () => {
1017
+ setIsPlaying(false);
1018
+ const {
1019
+ element
1020
+ } = cornerstoneViewportService.getViewportInfo(activeViewportId);
1021
+ cineService.stopClip(element);
1022
+ };
1023
+ const handleSetFrameRate = newFrameRate => {
1024
+ setFrameRate(newFrameRate);
1025
+ handleStop();
1026
+ handlePlay();
1027
+ };
1028
+ function handleSliderChange(newValues) {
1029
+ if (newValues[0] === timePointsRangeToUseForGenerate[0] && newValues[1] === timePointsRangeToUseForGenerate[1]) {
1030
+ return;
1031
+ }
1032
+ setTimePointsRangeToUseForGenerate(newValues);
1033
+ }
1034
+ if (!dynamicVolume || timePointsRange.length === 0) {
1035
+ return null;
1036
+ }
1037
+ return /*#__PURE__*/react.createElement(panels_DynamicVolumeControls, {
1038
+ fps: frameRate,
1039
+ isPlaying: isPlaying,
1040
+ onPlayPauseChange: onPlayPauseChange,
1041
+ minFps: 1,
1042
+ maxFps: 50,
1043
+ currentFrameIndex: timePointRendered,
1044
+ onFpsChange: handleSetFrameRate,
1045
+ framesLength: timePointsRange[1],
1046
+ onFrameChange: timePointIndex => {
1047
+ dynamicVolume.timePointIndex = timePointIndex;
1048
+ },
1049
+ onGenerate: onGenerateImage,
1050
+ onDynamicClick: displayingComputed ? () => renderDynamicImage(computedDisplaySet) : null,
1051
+ onDoubleRangeChange: handleSliderChange
1052
+ });
1053
+ }
1054
+ async function createComputedVolume(dynamicVolumeId, computedVolumeId) {
1055
+ if (!esm.cache.getVolume(computedVolumeId)) {
1056
+ const computedVolume = await esm.volumeLoader.createAndCacheDerivedVolume(dynamicVolumeId, {
1057
+ volumeId: computedVolumeId
1058
+ });
1059
+ return computedVolume;
1060
+ }
1061
+ }
1062
+ PanelGenerateImage.propTypes = {
1063
+ servicesManager: prop_types_default().shape({
1064
+ services: prop_types_default().shape({
1065
+ measurementService: prop_types_default().shape({
1066
+ getMeasurements: (prop_types_default()).func.isRequired,
1067
+ subscribe: (prop_types_default()).func.isRequired,
1068
+ EVENTS: (prop_types_default()).object.isRequired,
1069
+ VALUE_TYPES: (prop_types_default()).object.isRequired
1070
+ }).isRequired
1071
+ }).isRequired
1072
+ }).isRequired
1073
+ };
1074
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/DynamicDataPanel.tsx
1075
+
1076
+
1077
+ function DynamicDataPanel({
1078
+ servicesManager,
1079
+ commandsManager
1080
+ }) {
1081
+ return /*#__PURE__*/react.createElement("div", {
1082
+ className: "flex flex-auto flex-col text-white",
1083
+ "data-cy": 'dynamic-volume-panel'
1084
+ }, /*#__PURE__*/react.createElement(PanelGenerateImage, {
1085
+ commandsManager: commandsManager,
1086
+ servicesManager: servicesManager
1087
+ }));
1088
+ }
1089
+ /* harmony default export */ const panels_DynamicDataPanel = (DynamicDataPanel);
1090
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/WorkflowPanel.tsx
1091
+
1092
+ function WorkflowPanel({
1093
+ servicesManager,
1094
+ extensionManager
1095
+ }) {
1096
+ const ProgressDropdownWithService = useMemo(() => {
1097
+ const defaultComponents = extensionManager.getModuleEntry('@ohif/extension-default.customizationModule.default').value;
1098
+ return defaultComponents.find(component => component.id === 'progressDropdownWithServiceComponent').component;
1099
+ }, [extensionManager]);
1100
+ return /*#__PURE__*/React.createElement("div", {
1101
+ "data-cy": 'workflow-panel',
1102
+ className: "bg-secondary-dark mb-1 px-3 py-4"
1103
+ }, /*#__PURE__*/React.createElement("div", {
1104
+ className: "mb-1"
1105
+ }, "Workflow"), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(ProgressDropdownWithService, {
1106
+ servicesManager: servicesManager
1107
+ })));
1108
+ }
1109
+ /* harmony default export */ const panels_WorkflowPanel = ((/* unused pure expression or super */ null && (WorkflowPanel)));
1110
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/index.js
1111
+
1112
+
1113
+
1114
+
1115
+ // EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
1116
+ var es = __webpack_require__(80619);
1117
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/panels/DynamicExport.tsx
1118
+
1119
+
1120
+
1121
+ function DynamicExport({
1122
+ commandsManager,
1123
+ servicesManager,
1124
+ extensionManager
1125
+ }) {
1126
+ const {
1127
+ segmentationService
1128
+ } = servicesManager.services;
1129
+ const {
1130
+ t
1131
+ } = (0,es/* useTranslation */.Bd)('dynamicExport');
1132
+ const [segmentations, setSegmentations] = (0,react.useState)(() => segmentationService.getSegmentations());
1133
+ const actions = [{
1134
+ label: 'Export Time Data',
1135
+ onClick: () => {
1136
+ commandsManager.runCommand('exportTimeReportCSV', {
1137
+ segmentations,
1138
+ options: {
1139
+ filename: 'TimeData.csv'
1140
+ }
1141
+ });
1142
+ },
1143
+ disabled: !segmentations?.length
1144
+ }, {
1145
+ label: 'Export ROI Stats',
1146
+ onClick: () => {
1147
+ commandsManager.runCommand('exportTimeReportCSV', {
1148
+ segmentations,
1149
+ summaryStats: true,
1150
+ options: {
1151
+ filename: 'ROIStats.csv'
1152
+ }
1153
+ });
1154
+ },
1155
+ disabled: !segmentations?.length
1156
+ }];
1157
+
1158
+ /**
1159
+ * Update UI based on segmentation changes (added, removed, updated)
1160
+ */
1161
+ (0,react.useEffect)(() => {
1162
+ // ~~ Subscription
1163
+ const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
1164
+ const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
1165
+ const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED;
1166
+ const subscriptions = [];
1167
+ [added, updated, removed].forEach(evt => {
1168
+ const {
1169
+ unsubscribe
1170
+ } = segmentationService.subscribe(evt, () => {
1171
+ const segmentations = segmentationService.getSegmentations();
1172
+ setSegmentations(segmentations);
1173
+ });
1174
+ subscriptions.push(unsubscribe);
1175
+ });
1176
+ return () => {
1177
+ subscriptions.forEach(unsub => {
1178
+ unsub();
1179
+ });
1180
+ };
1181
+ }, []);
1182
+ return /*#__PURE__*/react.createElement("div", null, /*#__PURE__*/react.createElement("div", {
1183
+ className: "mt-3 flex justify-center px-2"
1184
+ }, /*#__PURE__*/react.createElement(ui_src/* ActionButtons */.wr, {
1185
+ actions: actions,
1186
+ t: t
1187
+ })));
1188
+ }
1189
+ /* harmony default export */ const panels_DynamicExport = (DynamicExport);
1190
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx
1191
+
1192
+
1193
+
1194
+
1195
+ function getPanelModule({
1196
+ commandsManager,
1197
+ extensionManager,
1198
+ servicesManager
1199
+ }) {
1200
+ const wrappedDynamicDataPanel = () => {
1201
+ return /*#__PURE__*/react.createElement(panels_DynamicDataPanel, {
1202
+ commandsManager: commandsManager,
1203
+ servicesManager: servicesManager,
1204
+ extensionManager: extensionManager
1205
+ });
1206
+ };
1207
+ const wrappedDynamicToolbox = () => {
1208
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(ui_src/* Toolbox */.OO, {
1209
+ commandsManager: commandsManager,
1210
+ servicesManager: servicesManager,
1211
+ extensionManager: extensionManager,
1212
+ buttonSectionId: "dynamic-toolbox",
1213
+ title: "Threshold Tools"
1214
+ }));
1215
+ };
1216
+ const wrappedDynamicExport = () => {
1217
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(panels_DynamicExport, {
1218
+ commandsManager: commandsManager,
1219
+ servicesManager: servicesManager,
1220
+ extensionManager: extensionManager
1221
+ }));
1222
+ };
1223
+ return [{
1224
+ name: 'dynamic-volume',
1225
+ iconName: 'group-layers',
1226
+ iconLabel: '4D Workflow',
1227
+ label: '4D Workflow',
1228
+ component: wrappedDynamicDataPanel
1229
+ }, {
1230
+ name: 'dynamic-toolbox',
1231
+ iconName: 'group-layers',
1232
+ iconLabel: '4D Workflow',
1233
+ label: 'Dynamic Toolbox',
1234
+ component: wrappedDynamicToolbox
1235
+ }, {
1236
+ name: 'dynamic-export',
1237
+ iconName: 'group-layers',
1238
+ iconLabel: '4D Workflow',
1239
+ label: '4D Workflow',
1240
+ component: wrappedDynamicExport
1241
+ }];
1242
+ }
1243
+ /* harmony default export */ const src_getPanelModule = (getPanelModule);
1244
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/getHangingProtocolModule.ts
1245
+ const DEFAULT_COLORMAP = '2hot';
1246
+ const toolGroupIds = {
1247
+ pt: 'dynamic4D-pt',
1248
+ fusion: 'dynamic4D-fusion',
1249
+ ct: 'dynamic4D-ct'
1250
+ };
1251
+ function getPTOptions({
1252
+ colormap,
1253
+ voiInverted
1254
+ } = {}) {
1255
+ return {
1256
+ blendMode: 'MIP',
1257
+ colormap,
1258
+ voi: {
1259
+ windowWidth: 5,
1260
+ windowCenter: 2.5
1261
+ },
1262
+ voiInverted
1263
+ };
1264
+ }
1265
+ function getPTViewports() {
1266
+ const ptOptionsParams = {
1267
+ colormap: {
1268
+ name: DEFAULT_COLORMAP,
1269
+ opacity: [{
1270
+ value: 0,
1271
+ opacity: 0
1272
+ }, {
1273
+ value: 0.1,
1274
+ opacity: 1
1275
+ }, {
1276
+ value: 1,
1277
+ opacity: 1
1278
+ }]
1279
+ },
1280
+ voiInverted: false
1281
+ };
1282
+ return [{
1283
+ viewportOptions: {
1284
+ viewportId: 'ptAxial',
1285
+ viewportType: 'volume',
1286
+ orientation: 'axial',
1287
+ toolGroupId: toolGroupIds.pt,
1288
+ initialImageOptions: {
1289
+ preset: 'middle' // 'first', 'last', 'middle'
1290
+ },
1291
+ syncGroups: [{
1292
+ type: 'cameraPosition',
1293
+ id: 'axialSync',
1294
+ source: true,
1295
+ target: true
1296
+ }, {
1297
+ type: 'voi',
1298
+ id: 'ptWLSync',
1299
+ source: true,
1300
+ target: true
1301
+ }]
1302
+ },
1303
+ displaySets: [{
1304
+ id: 'ptDisplaySet',
1305
+ options: {
1306
+ ...getPTOptions(ptOptionsParams)
1307
+ }
1308
+ }]
1309
+ }, {
1310
+ viewportOptions: {
1311
+ viewportId: 'ptSagittal',
1312
+ viewportType: 'volume',
1313
+ orientation: 'sagittal',
1314
+ toolGroupId: toolGroupIds.pt,
1315
+ initialImageOptions: {
1316
+ preset: 'middle' // 'first', 'last', 'middle'
1317
+ },
1318
+ syncGroups: [{
1319
+ type: 'cameraPosition',
1320
+ id: 'sagittalSync',
1321
+ source: true,
1322
+ target: true
1323
+ }, {
1324
+ type: 'voi',
1325
+ id: 'ptWLSync',
1326
+ source: true,
1327
+ target: true
1328
+ }]
1329
+ },
1330
+ displaySets: [{
1331
+ id: 'ptDisplaySet',
1332
+ options: {
1333
+ ...getPTOptions(ptOptionsParams)
1334
+ }
1335
+ }]
1336
+ }, {
1337
+ viewportOptions: {
1338
+ viewportId: 'ptCoronal',
1339
+ viewportType: 'volume',
1340
+ orientation: 'coronal',
1341
+ toolGroupId: toolGroupIds.pt,
1342
+ initialImageOptions: {
1343
+ preset: 'middle' // 'first', 'last', 'middle'
1344
+ },
1345
+ syncGroups: [{
1346
+ type: 'cameraPosition',
1347
+ id: 'coronalSync',
1348
+ source: true,
1349
+ target: true
1350
+ }, {
1351
+ type: 'voi',
1352
+ id: 'ptWLSync',
1353
+ source: true,
1354
+ target: true
1355
+ }]
1356
+ },
1357
+ displaySets: [{
1358
+ id: 'ptDisplaySet',
1359
+ options: {
1360
+ ...getPTOptions(ptOptionsParams)
1361
+ }
1362
+ }]
1363
+ }];
1364
+ }
1365
+ function getFusionViewports() {
1366
+ const ptOptionsParams = {
1367
+ colormap: {
1368
+ name: DEFAULT_COLORMAP,
1369
+ opacity: [{
1370
+ value: 0,
1371
+ opacity: 0
1372
+ }, {
1373
+ value: 0.1,
1374
+ opacity: 0.3
1375
+ }, {
1376
+ value: 1,
1377
+ opacity: 0.3
1378
+ }]
1379
+ }
1380
+ };
1381
+ return [{
1382
+ viewportOptions: {
1383
+ viewportId: 'fusionAxial',
1384
+ viewportType: 'volume',
1385
+ orientation: 'axial',
1386
+ toolGroupId: toolGroupIds.fusion,
1387
+ initialImageOptions: {
1388
+ preset: 'middle' // 'first', 'last', 'middle'
1389
+ },
1390
+ syncGroups: [{
1391
+ type: 'cameraPosition',
1392
+ id: 'axialSync',
1393
+ source: true,
1394
+ target: true
1395
+ }, {
1396
+ type: 'voi',
1397
+ id: 'ctWLSync',
1398
+ source: false,
1399
+ target: true
1400
+ }, {
1401
+ type: 'voi',
1402
+ id: 'fusionWLSync',
1403
+ source: true,
1404
+ target: true
1405
+ }, {
1406
+ type: 'voi',
1407
+ id: 'ptFusionWLSync',
1408
+ source: false,
1409
+ target: true,
1410
+ options: {
1411
+ syncInvertState: false
1412
+ }
1413
+ }]
1414
+ },
1415
+ displaySets: [{
1416
+ id: 'ctDisplaySet'
1417
+ }, {
1418
+ options: {
1419
+ ...getPTOptions(ptOptionsParams)
1420
+ },
1421
+ id: 'ptDisplaySet'
1422
+ }]
1423
+ }, {
1424
+ viewportOptions: {
1425
+ viewportId: 'fusionSagittal',
1426
+ viewportType: 'volume',
1427
+ orientation: 'sagittal',
1428
+ toolGroupId: toolGroupIds.fusion,
1429
+ initialImageOptions: {
1430
+ preset: 'middle' // 'first', 'last', 'middle'
1431
+ },
1432
+ syncGroups: [{
1433
+ type: 'cameraPosition',
1434
+ id: 'sagittalSync',
1435
+ source: true,
1436
+ target: true
1437
+ }, {
1438
+ type: 'voi',
1439
+ id: 'ctWLSync',
1440
+ source: false,
1441
+ target: true
1442
+ }, {
1443
+ type: 'voi',
1444
+ id: 'fusionWLSync',
1445
+ source: true,
1446
+ target: true
1447
+ }, {
1448
+ type: 'voi',
1449
+ id: 'ptFusionWLSync',
1450
+ source: false,
1451
+ target: true,
1452
+ options: {
1453
+ syncInvertState: false
1454
+ }
1455
+ }]
1456
+ },
1457
+ displaySets: [{
1458
+ id: 'ctDisplaySet'
1459
+ }, {
1460
+ options: {
1461
+ ...getPTOptions(ptOptionsParams)
1462
+ },
1463
+ id: 'ptDisplaySet'
1464
+ }]
1465
+ }, {
1466
+ viewportOptions: {
1467
+ viewportId: 'fusionCoronal',
1468
+ viewportType: 'volume',
1469
+ orientation: 'coronal',
1470
+ toolGroupId: toolGroupIds.fusion,
1471
+ initialImageOptions: {
1472
+ preset: 'middle' // 'first', 'last', 'middle'
1473
+ },
1474
+ syncGroups: [{
1475
+ type: 'cameraPosition',
1476
+ id: 'coronalSync',
1477
+ source: true,
1478
+ target: true
1479
+ }, {
1480
+ type: 'voi',
1481
+ id: 'ctWLSync',
1482
+ source: false,
1483
+ target: true
1484
+ }, {
1485
+ type: 'voi',
1486
+ id: 'fusionWLSync',
1487
+ source: true,
1488
+ target: true
1489
+ }, {
1490
+ type: 'voi',
1491
+ id: 'ptFusionWLSync',
1492
+ source: false,
1493
+ target: true,
1494
+ options: {
1495
+ syncInvertState: false
1496
+ }
1497
+ }]
1498
+ },
1499
+ displaySets: [{
1500
+ id: 'ctDisplaySet'
1501
+ }, {
1502
+ options: {
1503
+ ...getPTOptions(ptOptionsParams)
1504
+ },
1505
+ id: 'ptDisplaySet'
1506
+ }]
1507
+ }];
1508
+ }
1509
+ function getSeriesChartViewport() {
1510
+ return {
1511
+ viewportOptions: {
1512
+ viewportId: 'seriesChart'
1513
+ },
1514
+ displaySets: [{
1515
+ id: 'chartDisplaySet',
1516
+ options: {
1517
+ // This dataset does not require the download of any instance since it is pre-computed locally,
1518
+ // but interleaveTopToBottom.ts was not loading any series because it consider that all viewports
1519
+ // are a Cornerstone viewport which is not true in this case and it waits for all viewports to
1520
+ // have called interleaveTopToBottom(...).
1521
+ skipLoading: true
1522
+ }
1523
+ }]
1524
+ };
1525
+ }
1526
+ function getCTViewports() {
1527
+ return [{
1528
+ viewportOptions: {
1529
+ viewportId: 'ctAxial',
1530
+ viewportType: 'volume',
1531
+ orientation: 'axial',
1532
+ toolGroupId: toolGroupIds.ct,
1533
+ initialImageOptions: {
1534
+ preset: 'middle' // 'first', 'last', 'middle'
1535
+ },
1536
+ syncGroups: [{
1537
+ type: 'cameraPosition',
1538
+ id: 'axialSync',
1539
+ source: true,
1540
+ target: true
1541
+ }, {
1542
+ type: 'voi',
1543
+ id: 'ctWLSync',
1544
+ source: true,
1545
+ target: true
1546
+ }]
1547
+ },
1548
+ displaySets: [{
1549
+ id: 'ctDisplaySet'
1550
+ }]
1551
+ }, {
1552
+ viewportOptions: {
1553
+ viewportId: 'ctSagittal',
1554
+ viewportType: 'volume',
1555
+ orientation: 'sagittal',
1556
+ toolGroupId: toolGroupIds.ct,
1557
+ initialImageOptions: {
1558
+ preset: 'middle'
1559
+ },
1560
+ syncGroups: [{
1561
+ type: 'cameraPosition',
1562
+ id: 'sagittalSync',
1563
+ source: true,
1564
+ target: true
1565
+ }, {
1566
+ type: 'voi',
1567
+ id: 'ctWLSync',
1568
+ source: true,
1569
+ target: true
1570
+ }]
1571
+ },
1572
+ displaySets: [{
1573
+ id: 'ctDisplaySet'
1574
+ }]
1575
+ }, {
1576
+ viewportOptions: {
1577
+ viewportId: 'ctCoronal',
1578
+ viewportType: 'volume',
1579
+ orientation: 'coronal',
1580
+ toolGroupId: toolGroupIds.ct,
1581
+ initialImageOptions: {
1582
+ preset: 'middle'
1583
+ },
1584
+ syncGroups: [{
1585
+ type: 'cameraPosition',
1586
+ id: 'coronalSync',
1587
+ source: true,
1588
+ target: true
1589
+ }, {
1590
+ type: 'voi',
1591
+ id: 'ctWLSync',
1592
+ source: true,
1593
+ target: true
1594
+ }]
1595
+ },
1596
+ displaySets: [{
1597
+ id: 'ctDisplaySet'
1598
+ }]
1599
+ }];
1600
+ }
1601
+ const defaultProtocol = {
1602
+ id: 'default4D',
1603
+ locked: true,
1604
+ // Don't store this hanging protocol as it applies to the currently active
1605
+ // display set by default
1606
+ // cacheId: null,
1607
+ hasUpdatedPriorsInformation: false,
1608
+ name: 'Default',
1609
+ createdDate: '2023-01-01T00:00:00.000Z',
1610
+ modifiedDate: '2023-01-01T00:00:00.000Z',
1611
+ availableTo: {},
1612
+ editableBy: {},
1613
+ imageLoadStrategy: 'default',
1614
+ // "default" , "interleaveTopToBottom", "interleaveCenter"
1615
+ protocolMatchingRules: [{
1616
+ attribute: 'ModalitiesInStudy',
1617
+ constraint: {
1618
+ contains: ['CT', 'PT']
1619
+ }
1620
+ }],
1621
+ // -1 would be used to indicate active only, whereas other values are
1622
+ // the number of required priors referenced - so 0 means active with
1623
+ // 0 or more priors.
1624
+ numberOfPriorsReferenced: -1,
1625
+ displaySetSelectors: {
1626
+ defaultDisplaySetId: {
1627
+ // Unused currently
1628
+ imageMatchingRules: [],
1629
+ // Matches displaysets, NOT series
1630
+ seriesMatchingRules: [
1631
+ // Try to match series with images by default, to prevent weird display
1632
+ // on SEG/SR containing studies
1633
+ {
1634
+ attribute: 'numImageFrames',
1635
+ constraint: {
1636
+ greaterThan: {
1637
+ value: 0
1638
+ }
1639
+ }
1640
+ }]
1641
+ // Can be used to select matching studies
1642
+ // studyMatchingRules: [],
1643
+ },
1644
+ ctDisplaySet: {
1645
+ // Unused currently
1646
+ imageMatchingRules: [],
1647
+ // Matches displaysets, NOT series
1648
+ seriesMatchingRules: [{
1649
+ attribute: 'Modality',
1650
+ constraint: {
1651
+ equals: {
1652
+ value: 'CT'
1653
+ }
1654
+ },
1655
+ required: true
1656
+ }, {
1657
+ attribute: 'isReconstructable',
1658
+ constraint: {
1659
+ equals: {
1660
+ value: true
1661
+ }
1662
+ },
1663
+ required: true
1664
+ }]
1665
+ // Can be used to select matching studies
1666
+ // studyMatchingRules: [],
1667
+ },
1668
+ ptDisplaySet: {
1669
+ // Unused currently
1670
+ imageMatchingRules: [],
1671
+ // Matches displaysets, NOT series
1672
+ seriesMatchingRules: [{
1673
+ attribute: 'Modality',
1674
+ constraint: {
1675
+ equals: 'PT'
1676
+ },
1677
+ required: true
1678
+ }, {
1679
+ attribute: 'isReconstructable',
1680
+ constraint: {
1681
+ equals: {
1682
+ value: true
1683
+ }
1684
+ },
1685
+ required: true
1686
+ }, {
1687
+ attribute: 'SeriesDescription',
1688
+ constraint: {
1689
+ contains: 'Corrected'
1690
+ }
1691
+ }, {
1692
+ weight: 2,
1693
+ attribute: 'SeriesDescription',
1694
+ constraint: {
1695
+ doesNotContain: {
1696
+ value: 'Uncorrected'
1697
+ }
1698
+ }
1699
+ }
1700
+
1701
+ // Should we check if CorrectedImage contains ATTN?
1702
+ // (0028,0051) (CorrectedImage): NORM\DTIM\ATTN\SCAT\RADL\DECY
1703
+ ]
1704
+ // Can be used to select matching studies
1705
+ // studyMatchingRules: [],
1706
+ },
1707
+ chartDisplaySet: {
1708
+ // Unused currently
1709
+ imageMatchingRules: [],
1710
+ // Matches displaysets, NOT series
1711
+ seriesMatchingRules: [{
1712
+ attribute: 'Modality',
1713
+ constraint: {
1714
+ equals: {
1715
+ value: 'CHT'
1716
+ }
1717
+ },
1718
+ required: true
1719
+ }]
1720
+ }
1721
+ },
1722
+ stages: [{
1723
+ id: 'dataPreparation',
1724
+ name: 'Data Preparation',
1725
+ viewportStructure: {
1726
+ layoutType: 'grid',
1727
+ properties: {
1728
+ rows: 1,
1729
+ columns: 3
1730
+ }
1731
+ },
1732
+ viewports: [...getPTViewports()],
1733
+ createdDate: '2023-01-01T00:00:00.000Z'
1734
+ }, {
1735
+ id: 'registration',
1736
+ name: 'Registration',
1737
+ viewportStructure: {
1738
+ layoutType: 'grid',
1739
+ properties: {
1740
+ rows: 3,
1741
+ columns: 3
1742
+ }
1743
+ },
1744
+ viewports: [...getFusionViewports(), ...getCTViewports(), ...getPTViewports()],
1745
+ createdDate: '2023-01-01T00:00:00.000Z'
1746
+ }, {
1747
+ id: 'roiQuantification',
1748
+ name: 'ROI Quantification',
1749
+ viewportStructure: {
1750
+ layoutType: 'grid',
1751
+ properties: {
1752
+ rows: 1,
1753
+ columns: 3
1754
+ }
1755
+ },
1756
+ viewports: [...getFusionViewports()],
1757
+ createdDate: '2023-01-01T00:00:00.000Z'
1758
+ }, {
1759
+ id: 'kineticAnalysis',
1760
+ name: 'Kinetic Analysis',
1761
+ viewportStructure: {
1762
+ layoutType: 'grid',
1763
+ properties: {
1764
+ rows: 2,
1765
+ columns: 3,
1766
+ layoutOptions: [{
1767
+ x: 0,
1768
+ y: 0,
1769
+ width: 1 / 3,
1770
+ height: 1 / 2
1771
+ }, {
1772
+ x: 1 / 3,
1773
+ y: 0,
1774
+ width: 1 / 3,
1775
+ height: 1 / 2
1776
+ }, {
1777
+ x: 2 / 3,
1778
+ y: 0,
1779
+ width: 1 / 3,
1780
+ height: 1 / 2
1781
+ }, {
1782
+ x: 0,
1783
+ y: 1 / 2,
1784
+ width: 1,
1785
+ height: 1 / 2
1786
+ }]
1787
+ }
1788
+ },
1789
+ viewports: [...getFusionViewports(), getSeriesChartViewport()],
1790
+ createdDate: '2023-01-01T00:00:00.000Z'
1791
+ }]
1792
+ };
1793
+
1794
+ /**
1795
+ * HangingProtocolModule should provide a list of hanging protocols that will be
1796
+ * available in OHIF for Modes to use to decide on the structure of the viewports
1797
+ * and also the series that hung in the viewports. Each hanging protocol is defined by
1798
+ * { name, protocols}. Examples include the default hanging protocol provided by
1799
+ * the default extension that shows 2x2 viewports.
1800
+ */
1801
+
1802
+ function getHangingProtocolModule() {
1803
+ return [{
1804
+ name: defaultProtocol.id,
1805
+ protocol: defaultProtocol
1806
+ }];
1807
+ }
1808
+ /* harmony default export */ const src_getHangingProtocolModule = (getHangingProtocolModule);
1809
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dynamic-volume/src/index.ts
1810
+
1811
+
1812
+
1813
+
1814
+
1815
+
1816
+ /**
1817
+ * You can remove any of the following modules if you don't need them.
1818
+ */
1819
+ const dynamicVolumeExtension = {
1820
+ /**
1821
+ * Only required property. Should be a unique value across all extensions.
1822
+ * You ID can be anything you want, but it should be unique.
1823
+ */
1824
+ id: id,
1825
+ /**
1826
+ * Perform any pre-registration tasks here. This is called before the extension
1827
+ * is registered. Usually we run tasks such as: configuring the libraries
1828
+ * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
1829
+ * this extension is providing.
1830
+ */
1831
+ preRegistration: ({
1832
+ servicesManager,
1833
+ commandsManager,
1834
+ configuration = {}
1835
+ }) => {
1836
+ // TODO: look for the right fix
1837
+ esm.cache.setMaxCacheSize(5 * 1024 * 1024 * 1024);
1838
+ },
1839
+ /**
1840
+ * PanelModule should provide a list of panels that will be available in OHIF
1841
+ * for Modes to consume and render. Each panel is defined by a {name,
1842
+ * iconName, iconLabel, label, component} object. Example of a panel module
1843
+ * is the StudyBrowserPanel that is provided by the default extension in OHIF.
1844
+ */
1845
+ getPanelModule: src_getPanelModule,
1846
+ /**
1847
+ * ViewportModule should provide a list of viewports that will be available in OHIF
1848
+ * for Modes to consume and use in the viewports. Each viewport is defined by
1849
+ * {name, component} object. Example of a viewport module is the CornerstoneViewport
1850
+ * that is provided by the Cornerstone extension in OHIF.
1851
+ */
1852
+ getHangingProtocolModule: src_getHangingProtocolModule,
1853
+ /**
1854
+ * CommandsModule should provide a list of commands that will be available in OHIF
1855
+ * for Modes to consume and use in the viewports. Each command is defined by
1856
+ * an object of { actions, definitions, defaultContext } where actions is an
1857
+ * object of functions, definitions is an object of available commands, their
1858
+ * options, and defaultContext is the default context for the command to run against.
1859
+ */
1860
+ getCommandsModule: ({
1861
+ servicesManager,
1862
+ commandsManager,
1863
+ extensionManager
1864
+ }) => {
1865
+ return src_commandsModule({
1866
+ servicesManager,
1867
+ commandsManager,
1868
+ extensionManager
1869
+ });
1870
+ }
1871
+ };
1872
+
1873
+
1874
+ /***/ })
1875
+
1876
+ }]);