@ohif/app 3.0.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/151.bundle.07bac9172580a60fae7a.js +2579 -0
- package/dist/192.bundle.62be5f0ef9705a485071.js +894 -0
- package/dist/199.bundle.2286f24cf0a068e7f50c.js +480 -0
- package/dist/205.bundle.39e6c847d618ad2b1b7a.js +62 -0
- package/dist/208.bundle.23748a85dfdc79c05d3a.js +864 -0
- package/dist/270.bundle.abbdb5348274bae3e8bc.js +23906 -0
- package/dist/283.bundle.33f99a75a5e2d9333da2.js +2939 -0
- package/dist/295.bundle.5105ce962be15c92484d.js +48 -0
- package/dist/331.bundle.7ac7b142d249d14fd99e.js +73034 -0
- package/dist/351.bundle.c5d7279ef42e30f61e08.js +1471 -0
- package/dist/351.css +3 -0
- package/dist/36785fbd89b0e17f6099.wasm +0 -0
- package/dist/381.bundle.0905e683605fcbc0895f.js +1009 -0
- package/dist/404.bundle.0f7a500421f246153d89.js +706 -0
- package/dist/50.bundle.4cb103cd20f5ffccf927.js +324 -0
- package/dist/5004fdc02f329ce53b69.wasm +0 -0
- package/dist/531.bundle.1bc152c87c7e2e987d2b.js +5935 -0
- package/dist/55.bundle.a5a215e13a8511f7aee7.js +685 -0
- package/dist/55.css +3 -0
- package/dist/569.bundle.d147c0aa0604f8ea2094.js +514 -0
- package/dist/581.bundle.646c89c5c3e3ee096363.js +508 -0
- package/dist/606.bundle.5d876f5f3dd8287f0a28.js +4939 -0
- package/dist/610.min.worker.js +2 -0
- package/dist/610.min.worker.js.map +1 -0
- package/dist/616.bundle.bec4736d8c9513e62856.js +686 -0
- package/dist/62ab5d58a2bea7b5a1dc.wasm +0 -0
- package/dist/642.bundle.030d908e22c8ff5611f3.js +169 -0
- package/dist/65916ef3def695744bda.wasm +0 -0
- package/dist/664.bundle.4792c88ae0d6d4b5ed13.js +901 -0
- package/dist/707.bundle.0a74aa3e61ed002eb3c6.js +9049 -0
- package/dist/707.css +1 -0
- package/dist/728.bundle.d13856835357400fef82.js +26221 -0
- package/dist/744.bundle.53b07e48e07a11e920ac.js +2355 -0
- package/dist/75788f12450d4c5ed494.wasm +0 -0
- package/dist/75a0c2dfe07b824c7d21.wasm +0 -0
- package/dist/780.bundle.f60ac1906e0ae080dee8.js +4769 -0
- package/dist/790.bundle.b4df2c5d78a2a565b150.js +454 -0
- package/dist/799.bundle.3fff638815e355b0bdfd.js +271 -0
- package/dist/806.css +1 -0
- package/dist/82.bundle.a24015533196e05d190e.js +6104 -0
- package/dist/917.bundle.a094ae9e9de6df4119ae.js +196 -0
- package/dist/926.bundle.dbc9d0e591cb9217fda2.js +72552 -0
- package/dist/935.bundle.deeffff0e4f7b528e3c3.js +1849 -0
- package/dist/945.min.worker.js +2 -0
- package/dist/945.min.worker.js.map +1 -0
- package/dist/953.bundle.c14d9eb6400f697019ee.js +449 -0
- package/dist/973.bundle.4100cf103686b64938d1.js +261 -0
- package/dist/976.bundle.2720eb892514e1818018.js +2725 -0
- package/dist/984.bundle.157fc66ea5040e1364af.js +1842 -0
- package/dist/_headers +6 -0
- package/dist/_redirects +6 -0
- package/dist/app-config.js +215 -0
- package/dist/app.bundle.253eeb2a7ee986e89c50.js +154621 -0
- package/dist/app.bundle.css +21 -0
- package/dist/assets/android-chrome-144x144.png +0 -0
- package/dist/assets/android-chrome-192x192.png +0 -0
- package/dist/assets/android-chrome-256x256.png +0 -0
- package/dist/assets/android-chrome-36x36.png +0 -0
- package/dist/assets/android-chrome-384x384.png +0 -0
- package/dist/assets/android-chrome-48x48.png +0 -0
- package/dist/assets/android-chrome-512x512.png +0 -0
- package/dist/assets/android-chrome-72x72.png +0 -0
- package/dist/assets/android-chrome-96x96.png +0 -0
- package/dist/assets/apple-touch-icon-1024x1024.png +0 -0
- package/dist/assets/apple-touch-icon-114x114.png +0 -0
- package/dist/assets/apple-touch-icon-120x120.png +0 -0
- package/dist/assets/apple-touch-icon-144x144.png +0 -0
- package/dist/assets/apple-touch-icon-152x152.png +0 -0
- package/dist/assets/apple-touch-icon-167x167.png +0 -0
- package/dist/assets/apple-touch-icon-180x180.png +0 -0
- package/dist/assets/apple-touch-icon-57x57.png +0 -0
- package/dist/assets/apple-touch-icon-60x60.png +0 -0
- package/dist/assets/apple-touch-icon-72x72.png +0 -0
- package/dist/assets/apple-touch-icon-76x76.png +0 -0
- package/dist/assets/apple-touch-icon-precomposed.png +0 -0
- package/dist/assets/apple-touch-icon.png +0 -0
- package/dist/assets/apple-touch-startup-image-1182x2208.png +0 -0
- package/dist/assets/apple-touch-startup-image-1242x2148.png +0 -0
- package/dist/assets/apple-touch-startup-image-1496x2048.png +0 -0
- package/dist/assets/apple-touch-startup-image-1536x2008.png +0 -0
- package/dist/assets/apple-touch-startup-image-320x460.png +0 -0
- package/dist/assets/apple-touch-startup-image-640x1096.png +0 -0
- package/dist/assets/apple-touch-startup-image-640x920.png +0 -0
- package/dist/assets/apple-touch-startup-image-748x1024.png +0 -0
- package/dist/assets/apple-touch-startup-image-750x1294.png +0 -0
- package/dist/assets/apple-touch-startup-image-768x1004.png +0 -0
- package/dist/assets/browserconfig.xml +12 -0
- package/dist/assets/coast-228x228.png +0 -0
- package/dist/assets/favicon-16x16.png +0 -0
- package/dist/assets/favicon-32x32.png +0 -0
- package/dist/assets/favicon.ico +0 -0
- package/dist/assets/firefox_app_128x128.png +0 -0
- package/dist/assets/firefox_app_512x512.png +0 -0
- package/dist/assets/firefox_app_60x60.png +0 -0
- package/dist/assets/manifest.webapp +14 -0
- package/dist/assets/mstile-144x144.png +0 -0
- package/dist/assets/mstile-150x150.png +0 -0
- package/dist/assets/mstile-310x150.png +0 -0
- package/dist/assets/mstile-310x310.png +0 -0
- package/dist/assets/mstile-70x70.png +0 -0
- package/dist/assets/yandex-browser-50x50.png +0 -0
- package/dist/assets/yandex-browser-manifest.json +9 -0
- package/dist/b6b803111e2d06a825bd.wasm +0 -0
- package/dist/c22b37c3488e1d6c3aa4.wasm +0 -0
- package/dist/cornerstoneDICOMImageLoader.min.js +2 -0
- package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -0
- package/dist/dicom-microscopy-viewer.bundle.aa60bdf008c32c39cfd7.js +12 -0
- package/dist/dicomMicroscopyViewer.min.js +3 -0
- package/dist/dicomMicroscopyViewer.min.js.LICENSE.txt +29 -0
- package/dist/es6-shim.min.js +12 -0
- package/dist/google.js +75 -0
- package/dist/index.html +1 -0
- package/dist/index.worker.ea71efba2ce63c499055.worker.js +2 -0
- package/dist/index.worker.ea71efba2ce63c499055.worker.js.map +1 -0
- package/dist/index.worker.min.worker.js +2 -0
- package/dist/index.worker.min.worker.js.map +1 -0
- package/dist/init-service-worker.js +59 -0
- package/dist/manifest.json +59 -0
- package/dist/ohif-logo-light.svg +15 -0
- package/dist/ohif-logo.svg +15 -0
- package/dist/oidc-client.min.js +46 -0
- package/dist/polyfill.min.js +1 -0
- package/dist/silent-refresh.html +16 -0
- package/dist/sw.js +56 -0
- package/package.json +24 -23
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[616],{
|
|
2
|
+
|
|
3
|
+
/***/ 72776:
|
|
4
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
// ESM COMPAT FLAG
|
|
8
|
+
__webpack_require__.r(__webpack_exports__);
|
|
9
|
+
|
|
10
|
+
// EXPORTS
|
|
11
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
12
|
+
"default": () => (/* binding */ cornerstone_dicom_seg_src)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/package.json
|
|
16
|
+
const package_namespaceObject = JSON.parse('{"u2":"@ohif/extension-cornerstone-dicom-seg"}');
|
|
17
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/id.js
|
|
18
|
+
|
|
19
|
+
const id = package_namespaceObject.u2;
|
|
20
|
+
const SOPClassHandlerName = 'dicom-seg';
|
|
21
|
+
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
|
22
|
+
|
|
23
|
+
// EXTERNAL MODULE: ../../../node_modules/react/index.js
|
|
24
|
+
var react = __webpack_require__(32735);
|
|
25
|
+
// EXTERNAL MODULE: ../../../node_modules/@kitware/vtk.js/Common/Core/Math.js
|
|
26
|
+
var Core_Math = __webpack_require__(92780);
|
|
27
|
+
// EXTERNAL MODULE: ../../core/src/index.ts + 101 modules
|
|
28
|
+
var src = __webpack_require__(48501);
|
|
29
|
+
// EXTERNAL MODULE: ../../../node_modules/dcmjs/build/dcmjs.es.js
|
|
30
|
+
var dcmjs_es = __webpack_require__(22737);
|
|
31
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
DicomMessage,
|
|
38
|
+
DicomMetaDictionary
|
|
39
|
+
} = dcmjs_es["default"].data;
|
|
40
|
+
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4'];
|
|
41
|
+
let loadPromises = {};
|
|
42
|
+
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
|
43
|
+
const instance = instances[0];
|
|
44
|
+
const {
|
|
45
|
+
StudyInstanceUID,
|
|
46
|
+
SeriesInstanceUID,
|
|
47
|
+
SOPInstanceUID,
|
|
48
|
+
SeriesDescription,
|
|
49
|
+
SeriesNumber,
|
|
50
|
+
SeriesDate,
|
|
51
|
+
SOPClassUID,
|
|
52
|
+
wadoRoot,
|
|
53
|
+
wadoUri,
|
|
54
|
+
wadoUriRoot
|
|
55
|
+
} = instance;
|
|
56
|
+
const displaySet = {
|
|
57
|
+
Modality: 'SEG',
|
|
58
|
+
loading: false,
|
|
59
|
+
isReconstructable: true,
|
|
60
|
+
// by default for now since it is a volumetric SEG currently
|
|
61
|
+
displaySetInstanceUID: src.utils.guid(),
|
|
62
|
+
SeriesDescription,
|
|
63
|
+
SeriesNumber,
|
|
64
|
+
SeriesDate,
|
|
65
|
+
SOPInstanceUID,
|
|
66
|
+
SeriesInstanceUID,
|
|
67
|
+
StudyInstanceUID,
|
|
68
|
+
SOPClassHandlerId: SOPClassHandlerId,
|
|
69
|
+
SOPClassUID,
|
|
70
|
+
referencedImages: null,
|
|
71
|
+
referencedSeriesInstanceUID: null,
|
|
72
|
+
referencedDisplaySetInstanceUID: null,
|
|
73
|
+
isDerivedDisplaySet: true,
|
|
74
|
+
isLoaded: false,
|
|
75
|
+
isHydrated: false,
|
|
76
|
+
segments: {},
|
|
77
|
+
sopClassUids,
|
|
78
|
+
instance,
|
|
79
|
+
instances: [instance],
|
|
80
|
+
wadoRoot,
|
|
81
|
+
wadoUriRoot,
|
|
82
|
+
wadoUri,
|
|
83
|
+
isOverlayDisplaySet: true
|
|
84
|
+
};
|
|
85
|
+
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
|
86
|
+
if (!referencedSeriesSequence) {
|
|
87
|
+
throw new Error('ReferencedSeriesSequence is missing for the SEG');
|
|
88
|
+
}
|
|
89
|
+
const referencedSeries = referencedSeriesSequence[0];
|
|
90
|
+
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
|
91
|
+
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
|
92
|
+
displaySet.getReferenceDisplaySet = () => {
|
|
93
|
+
const {
|
|
94
|
+
displaySetService
|
|
95
|
+
} = servicesManager.services;
|
|
96
|
+
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(displaySet.referencedSeriesInstanceUID);
|
|
97
|
+
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
|
98
|
+
throw new Error('Referenced DisplaySet is missing for the SEG');
|
|
99
|
+
}
|
|
100
|
+
const referencedDisplaySet = referencedDisplaySets[0];
|
|
101
|
+
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
|
102
|
+
|
|
103
|
+
// Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc.
|
|
104
|
+
displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
|
105
|
+
const referencedVolumeId = `cornerstoneStreamingImageVolume:${displaySet.referencedVolumeURI}`;
|
|
106
|
+
displaySet.referencedVolumeId = referencedVolumeId;
|
|
107
|
+
return referencedDisplaySet;
|
|
108
|
+
};
|
|
109
|
+
displaySet.load = async _ref => {
|
|
110
|
+
let {
|
|
111
|
+
headers
|
|
112
|
+
} = _ref;
|
|
113
|
+
return await _load(displaySet, servicesManager, extensionManager, headers);
|
|
114
|
+
};
|
|
115
|
+
return [displaySet];
|
|
116
|
+
}
|
|
117
|
+
function _load(segDisplaySet, servicesManager, extensionManager, headers) {
|
|
118
|
+
const {
|
|
119
|
+
SOPInstanceUID
|
|
120
|
+
} = segDisplaySet;
|
|
121
|
+
const {
|
|
122
|
+
segmentationService
|
|
123
|
+
} = servicesManager.services;
|
|
124
|
+
if ((segDisplaySet.loading || segDisplaySet.isLoaded) && loadPromises[SOPInstanceUID] && _segmentationExists(segDisplaySet, segmentationService)) {
|
|
125
|
+
return loadPromises[SOPInstanceUID];
|
|
126
|
+
}
|
|
127
|
+
segDisplaySet.loading = true;
|
|
128
|
+
|
|
129
|
+
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
|
130
|
+
// and also return the same promise to any other callers.
|
|
131
|
+
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
|
132
|
+
if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) {
|
|
133
|
+
const segments = await _loadSegments(extensionManager, segDisplaySet, headers);
|
|
134
|
+
segDisplaySet.segments = segments;
|
|
135
|
+
}
|
|
136
|
+
const suppressEvents = true;
|
|
137
|
+
segmentationService.createSegmentationForSEGDisplaySet(segDisplaySet, null, suppressEvents).then(() => {
|
|
138
|
+
segDisplaySet.loading = false;
|
|
139
|
+
resolve();
|
|
140
|
+
}).catch(error => {
|
|
141
|
+
segDisplaySet.loading = false;
|
|
142
|
+
reject(error);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
return loadPromises[SOPInstanceUID];
|
|
146
|
+
}
|
|
147
|
+
async function _loadSegments(extensionManager, segDisplaySet, headers) {
|
|
148
|
+
const utilityModule = extensionManager.getModuleEntry('@ohif/extension-cornerstone.utilityModule.common');
|
|
149
|
+
const {
|
|
150
|
+
dicomLoaderService
|
|
151
|
+
} = utilityModule.exports;
|
|
152
|
+
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);
|
|
153
|
+
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
|
154
|
+
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
|
155
|
+
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
|
156
|
+
if (!Array.isArray(dataset.SegmentSequence)) {
|
|
157
|
+
dataset.SegmentSequence = [dataset.SegmentSequence];
|
|
158
|
+
}
|
|
159
|
+
const segments = _getSegments(dataset);
|
|
160
|
+
return segments;
|
|
161
|
+
}
|
|
162
|
+
function _segmentationExists(segDisplaySet, segmentationService) {
|
|
163
|
+
// This should be abstracted with the CornerstoneCacheService
|
|
164
|
+
return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
|
165
|
+
}
|
|
166
|
+
function _getPixelData(dataset, segments) {
|
|
167
|
+
let frameSize = Math.ceil(dataset.Rows * dataset.Columns / 8);
|
|
168
|
+
let nextOffset = 0;
|
|
169
|
+
Object.keys(segments).forEach(segmentKey => {
|
|
170
|
+
const segment = segments[segmentKey];
|
|
171
|
+
segment.numberOfFrames = segment.functionalGroups.length;
|
|
172
|
+
segment.size = segment.numberOfFrames * frameSize;
|
|
173
|
+
segment.offset = nextOffset;
|
|
174
|
+
nextOffset = segment.offset + segment.size;
|
|
175
|
+
const packedSegment = dataset.PixelData[0].slice(segment.offset, nextOffset);
|
|
176
|
+
segment.pixelData = dcmjs_es["default"].data.BitArray.unpack(packedSegment);
|
|
177
|
+
segment.geometry = geometryFromFunctionalGroups(dataset, segment.functionalGroups);
|
|
178
|
+
});
|
|
179
|
+
return segments;
|
|
180
|
+
}
|
|
181
|
+
function geometryFromFunctionalGroups(dataset, perFrame) {
|
|
182
|
+
let pixelMeasures = dataset.SharedFunctionalGroupsSequence.PixelMeasuresSequence;
|
|
183
|
+
let planeOrientation = dataset.SharedFunctionalGroupsSequence.PlaneOrientationSequence;
|
|
184
|
+
let planePosition = perFrame[0].PlanePositionSequence; // TODO: assume sorted frames!
|
|
185
|
+
|
|
186
|
+
const geometry = {};
|
|
187
|
+
|
|
188
|
+
// NB: DICOM PixelSpacing is defined as Row then Column,
|
|
189
|
+
// unlike ImageOrientationPatient
|
|
190
|
+
let spacingBetweenSlices = pixelMeasures.SpacingBetweenSlices;
|
|
191
|
+
if (!spacingBetweenSlices) {
|
|
192
|
+
if (pixelMeasures.SliceThickness) {
|
|
193
|
+
console.log('Using SliceThickness as SpacingBetweenSlices');
|
|
194
|
+
spacingBetweenSlices = pixelMeasures.SliceThickness;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
geometry.spacing = [pixelMeasures.PixelSpacing[1], pixelMeasures.PixelSpacing[0], spacingBetweenSlices].map(Number);
|
|
198
|
+
geometry.dimensions = [dataset.Columns, dataset.Rows, perFrame.length].map(Number);
|
|
199
|
+
let orientation = planeOrientation.ImageOrientationPatient.map(Number);
|
|
200
|
+
const columnStepToPatient = orientation.slice(0, 3);
|
|
201
|
+
const rowStepToPatient = orientation.slice(3, 6);
|
|
202
|
+
geometry.planeNormal = [];
|
|
203
|
+
Core_Math/* default.cross */.ZP.cross(columnStepToPatient, rowStepToPatient, geometry.planeNormal);
|
|
204
|
+
let firstPosition = perFrame[0].PlanePositionSequence.ImagePositionPatient.map(Number);
|
|
205
|
+
let lastPosition = perFrame[perFrame.length - 1].PlanePositionSequence.ImagePositionPatient.map(Number);
|
|
206
|
+
geometry.sliceStep = [];
|
|
207
|
+
Core_Math/* default.subtract */.ZP.subtract(lastPosition, firstPosition, geometry.sliceStep);
|
|
208
|
+
Core_Math/* default.normalize */.ZP.normalize(geometry.sliceStep);
|
|
209
|
+
geometry.direction = columnStepToPatient.concat(rowStepToPatient).concat(geometry.sliceStep);
|
|
210
|
+
geometry.origin = planePosition.ImagePositionPatient.map(Number);
|
|
211
|
+
return geometry;
|
|
212
|
+
}
|
|
213
|
+
function _getSegments(dataset) {
|
|
214
|
+
const segments = {};
|
|
215
|
+
dataset.SegmentSequence.forEach(segment => {
|
|
216
|
+
const cielab = segment.RecommendedDisplayCIELabValue;
|
|
217
|
+
const rgba = dcmjs_es["default"].data.Colors.dicomlab2RGB(cielab).map(x => Math.round(x * 255));
|
|
218
|
+
rgba.push(255);
|
|
219
|
+
const segmentNumber = segment.SegmentNumber;
|
|
220
|
+
segments[segmentNumber] = {
|
|
221
|
+
color: rgba,
|
|
222
|
+
functionalGroups: [],
|
|
223
|
+
offset: null,
|
|
224
|
+
size: null,
|
|
225
|
+
pixelData: null,
|
|
226
|
+
label: segment.SegmentLabel
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// make a list of functional groups per segment
|
|
231
|
+
dataset.PerFrameFunctionalGroupsSequence.forEach(functionalGroup => {
|
|
232
|
+
const segmentNumber = functionalGroup.SegmentIdentificationSequence.ReferencedSegmentNumber;
|
|
233
|
+
segments[segmentNumber].functionalGroups.push(functionalGroup);
|
|
234
|
+
});
|
|
235
|
+
return _getPixelData(dataset, segments);
|
|
236
|
+
}
|
|
237
|
+
function getSopClassHandlerModule(_ref2) {
|
|
238
|
+
let {
|
|
239
|
+
servicesManager,
|
|
240
|
+
extensionManager
|
|
241
|
+
} = _ref2;
|
|
242
|
+
const getDisplaySetsFromSeries = instances => {
|
|
243
|
+
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
244
|
+
};
|
|
245
|
+
return [{
|
|
246
|
+
name: 'dicom-seg',
|
|
247
|
+
sopClassUids,
|
|
248
|
+
getDisplaySetsFromSeries
|
|
249
|
+
}];
|
|
250
|
+
}
|
|
251
|
+
/* harmony default export */ const src_getSopClassHandlerModule = (getSopClassHandlerModule);
|
|
252
|
+
// EXTERNAL MODULE: ../../../node_modules/prop-types/index.js
|
|
253
|
+
var prop_types = __webpack_require__(60216);
|
|
254
|
+
var prop_types_default = /*#__PURE__*/__webpack_require__.n(prop_types);
|
|
255
|
+
// EXTERNAL MODULE: ../../ui/src/index.js + 449 modules
|
|
256
|
+
var ui_src = __webpack_require__(43803);
|
|
257
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
function callInputDialog(uiDialogService, label, callback) {
|
|
261
|
+
const dialogId = 'enter-segment-label';
|
|
262
|
+
const onSubmitHandler = _ref => {
|
|
263
|
+
let {
|
|
264
|
+
action,
|
|
265
|
+
value
|
|
266
|
+
} = _ref;
|
|
267
|
+
switch (action.id) {
|
|
268
|
+
case 'save':
|
|
269
|
+
callback(value.label, action.id);
|
|
270
|
+
break;
|
|
271
|
+
case 'cancel':
|
|
272
|
+
callback('', action.id);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
uiDialogService.dismiss({
|
|
276
|
+
id: dialogId
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
if (uiDialogService) {
|
|
280
|
+
uiDialogService.create({
|
|
281
|
+
id: dialogId,
|
|
282
|
+
centralize: true,
|
|
283
|
+
isDraggable: false,
|
|
284
|
+
showOverlay: true,
|
|
285
|
+
content: ui_src/* Dialog */.Vq,
|
|
286
|
+
contentProps: {
|
|
287
|
+
title: 'Enter Segment Label',
|
|
288
|
+
value: {
|
|
289
|
+
label
|
|
290
|
+
},
|
|
291
|
+
noCloseButton: true,
|
|
292
|
+
onClose: () => uiDialogService.dismiss({
|
|
293
|
+
id: dialogId
|
|
294
|
+
}),
|
|
295
|
+
actions: [{
|
|
296
|
+
id: 'cancel',
|
|
297
|
+
text: 'Cancel',
|
|
298
|
+
type: 'primary'
|
|
299
|
+
}, {
|
|
300
|
+
id: 'save',
|
|
301
|
+
text: 'Confirm',
|
|
302
|
+
type: 'secondary'
|
|
303
|
+
}],
|
|
304
|
+
onSubmit: onSubmitHandler,
|
|
305
|
+
body: _ref2 => {
|
|
306
|
+
let {
|
|
307
|
+
value,
|
|
308
|
+
setValue
|
|
309
|
+
} = _ref2;
|
|
310
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
311
|
+
className: "p-4 bg-primary-dark"
|
|
312
|
+
}, /*#__PURE__*/react.createElement(ui_src/* Input */.II, {
|
|
313
|
+
autoFocus: true,
|
|
314
|
+
className: "mt-2 bg-black border-primary-main",
|
|
315
|
+
type: "text",
|
|
316
|
+
containerClassName: "mr-2",
|
|
317
|
+
value: value.label,
|
|
318
|
+
onChange: event => {
|
|
319
|
+
event.persist();
|
|
320
|
+
setValue(value => ({
|
|
321
|
+
...value,
|
|
322
|
+
label: event.target.value
|
|
323
|
+
}));
|
|
324
|
+
},
|
|
325
|
+
onKeyPress: event => {
|
|
326
|
+
if (event.key === 'Enter') {
|
|
327
|
+
onSubmitHandler({
|
|
328
|
+
value,
|
|
329
|
+
action: {
|
|
330
|
+
id: 'save'
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/* harmony default export */ const panels_callInputDialog = (callInputDialog);
|
|
342
|
+
// EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
|
|
343
|
+
var es = __webpack_require__(21572);
|
|
344
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
function PanelSegmentation(_ref) {
|
|
351
|
+
let {
|
|
352
|
+
servicesManager,
|
|
353
|
+
commandsManager
|
|
354
|
+
} = _ref;
|
|
355
|
+
const {
|
|
356
|
+
segmentationService,
|
|
357
|
+
uiDialogService
|
|
358
|
+
} = servicesManager.services;
|
|
359
|
+
const {
|
|
360
|
+
t
|
|
361
|
+
} = (0,es/* useTranslation */.$G)('PanelSegmentation');
|
|
362
|
+
const [selectedSegmentationId, setSelectedSegmentationId] = (0,react.useState)(null);
|
|
363
|
+
const [segmentationConfiguration, setSegmentationConfiguration] = (0,react.useState)(segmentationService.getConfiguration());
|
|
364
|
+
const [segmentations, setSegmentations] = (0,react.useState)(() => segmentationService.getSegmentations());
|
|
365
|
+
const [isMinimized, setIsMinimized] = (0,react.useState)({});
|
|
366
|
+
const onToggleMinimizeSegmentation = (0,react.useCallback)(id => {
|
|
367
|
+
setIsMinimized(prevState => ({
|
|
368
|
+
...prevState,
|
|
369
|
+
[id]: !prevState[id]
|
|
370
|
+
}));
|
|
371
|
+
}, [setIsMinimized]);
|
|
372
|
+
|
|
373
|
+
// Only expand the last segmentation added to the list and collapse the rest
|
|
374
|
+
(0,react.useEffect)(() => {
|
|
375
|
+
const lastSegmentationId = segmentations[segmentations.length - 1]?.id;
|
|
376
|
+
if (lastSegmentationId) {
|
|
377
|
+
setIsMinimized(prevState => ({
|
|
378
|
+
...prevState,
|
|
379
|
+
[lastSegmentationId]: false
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
}, [segmentations, setIsMinimized]);
|
|
383
|
+
(0,react.useEffect)(() => {
|
|
384
|
+
// ~~ Subscription
|
|
385
|
+
const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
|
|
386
|
+
const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
|
|
387
|
+
const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED;
|
|
388
|
+
const subscriptions = [];
|
|
389
|
+
[added, updated, removed].forEach(evt => {
|
|
390
|
+
const {
|
|
391
|
+
unsubscribe
|
|
392
|
+
} = segmentationService.subscribe(evt, () => {
|
|
393
|
+
const segmentations = segmentationService.getSegmentations();
|
|
394
|
+
setSegmentations(segmentations);
|
|
395
|
+
setSegmentationConfiguration(segmentationService.getConfiguration());
|
|
396
|
+
});
|
|
397
|
+
subscriptions.push(unsubscribe);
|
|
398
|
+
});
|
|
399
|
+
return () => {
|
|
400
|
+
subscriptions.forEach(unsub => {
|
|
401
|
+
unsub();
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
}, []);
|
|
405
|
+
const onSegmentationClick = segmentationId => {
|
|
406
|
+
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
|
|
407
|
+
};
|
|
408
|
+
const onSegmentationDelete = segmentationId => {
|
|
409
|
+
segmentationService.remove(segmentationId);
|
|
410
|
+
};
|
|
411
|
+
const getToolGroupIds = segmentationId => {
|
|
412
|
+
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
|
|
413
|
+
return toolGroupIds;
|
|
414
|
+
};
|
|
415
|
+
const onSegmentClick = (segmentationId, segmentIndex) => {
|
|
416
|
+
segmentationService.setActiveSegmentForSegmentation(segmentationId, segmentIndex);
|
|
417
|
+
const toolGroupIds = getToolGroupIds(segmentationId);
|
|
418
|
+
toolGroupIds.forEach(toolGroupId => {
|
|
419
|
+
// const toolGroupId =
|
|
420
|
+
segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
|
|
421
|
+
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId);
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
const onSegmentEdit = (segmentationId, segmentIndex) => {
|
|
425
|
+
const segmentation = segmentationService.getSegmentation(segmentationId);
|
|
426
|
+
const segment = segmentation.segments[segmentIndex];
|
|
427
|
+
const {
|
|
428
|
+
label
|
|
429
|
+
} = segment;
|
|
430
|
+
panels_callInputDialog(uiDialogService, label, (label, actionId) => {
|
|
431
|
+
if (label === '') {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
segmentationService.setSegmentLabelForSegmentation(segmentationId, segmentIndex, label);
|
|
435
|
+
});
|
|
436
|
+
};
|
|
437
|
+
const onSegmentationEdit = segmentationId => {
|
|
438
|
+
const segmentation = segmentationService.getSegmentation(segmentationId);
|
|
439
|
+
const {
|
|
440
|
+
label
|
|
441
|
+
} = segmentation;
|
|
442
|
+
panels_callInputDialog(uiDialogService, label, (label, actionId) => {
|
|
443
|
+
if (label === '') {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
segmentationService.addOrUpdateSegmentation({
|
|
447
|
+
id: segmentationId,
|
|
448
|
+
label
|
|
449
|
+
}, false,
|
|
450
|
+
// suppress event
|
|
451
|
+
true // notYetUpdatedAtSource
|
|
452
|
+
);
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const onSegmentColorClick = (segmentationId, segmentIndex) => {
|
|
457
|
+
// Todo: Implement color picker later
|
|
458
|
+
return;
|
|
459
|
+
};
|
|
460
|
+
const onSegmentDelete = (segmentationId, segmentIndex) => {
|
|
461
|
+
// segmentationService.removeSegmentFromSegmentation(
|
|
462
|
+
// segmentationId,
|
|
463
|
+
// segmentIndex
|
|
464
|
+
// );
|
|
465
|
+
console.warn('not implemented yet');
|
|
466
|
+
};
|
|
467
|
+
const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
|
|
468
|
+
const segmentation = segmentationService.getSegmentation(segmentationId);
|
|
469
|
+
const segmentInfo = segmentation.segments[segmentIndex];
|
|
470
|
+
const isVisible = !segmentInfo.isVisible;
|
|
471
|
+
const toolGroupIds = getToolGroupIds(segmentationId);
|
|
472
|
+
|
|
473
|
+
// Todo: right now we apply the visibility to all tool groups
|
|
474
|
+
toolGroupIds.forEach(toolGroupId => {
|
|
475
|
+
segmentationService.setSegmentVisibility(segmentationId, segmentIndex, isVisible, toolGroupId);
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
const onToggleSegmentationVisibility = segmentationId => {
|
|
479
|
+
segmentationService.toggleSegmentationVisibility(segmentationId);
|
|
480
|
+
};
|
|
481
|
+
const _setSegmentationConfiguration = (0,react.useCallback)((segmentationId, key, value) => {
|
|
482
|
+
segmentationService.setConfiguration({
|
|
483
|
+
segmentationId,
|
|
484
|
+
[key]: value
|
|
485
|
+
});
|
|
486
|
+
}, [segmentationService]);
|
|
487
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
488
|
+
className: "flex flex-col flex-auto min-h-0 justify-between mt-1"
|
|
489
|
+
}, segmentations?.length ? /*#__PURE__*/react.createElement(ui_src/* SegmentationGroupTable */.cX, {
|
|
490
|
+
title: t('Segmentations'),
|
|
491
|
+
showAddSegmentation: false,
|
|
492
|
+
segmentations: segmentations,
|
|
493
|
+
isMinimized: isMinimized,
|
|
494
|
+
activeSegmentationId: selectedSegmentationId || '',
|
|
495
|
+
onSegmentationClick: onSegmentationClick,
|
|
496
|
+
onSegmentationDelete: onSegmentationDelete,
|
|
497
|
+
onSegmentationEdit: onSegmentationEdit,
|
|
498
|
+
onSegmentClick: onSegmentClick,
|
|
499
|
+
onSegmentEdit: onSegmentEdit,
|
|
500
|
+
onSegmentColorClick: onSegmentColorClick,
|
|
501
|
+
onSegmentDelete: onSegmentDelete,
|
|
502
|
+
onToggleSegmentVisibility: onToggleSegmentVisibility,
|
|
503
|
+
onToggleSegmentationVisibility: onToggleSegmentationVisibility,
|
|
504
|
+
onToggleMinimizeSegmentation: onToggleMinimizeSegmentation,
|
|
505
|
+
segmentationConfig: {
|
|
506
|
+
initialConfig: segmentationConfiguration
|
|
507
|
+
},
|
|
508
|
+
setRenderOutline: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value),
|
|
509
|
+
setOutlineOpacityActive: value => _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value),
|
|
510
|
+
setRenderFill: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value),
|
|
511
|
+
setRenderInactiveSegmentations: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderInactiveSegmentations', value),
|
|
512
|
+
setOutlineWidthActive: value => _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value),
|
|
513
|
+
setFillAlpha: value => _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value),
|
|
514
|
+
setFillAlphaInactive: value => _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
|
|
515
|
+
}) : null);
|
|
516
|
+
}
|
|
517
|
+
PanelSegmentation.propTypes = {
|
|
518
|
+
commandsManager: prop_types_default().shape({
|
|
519
|
+
runCommand: (prop_types_default()).func.isRequired
|
|
520
|
+
}),
|
|
521
|
+
servicesManager: prop_types_default().shape({
|
|
522
|
+
services: prop_types_default().shape({
|
|
523
|
+
segmentationService: prop_types_default().shape({
|
|
524
|
+
getSegmentation: (prop_types_default()).func.isRequired,
|
|
525
|
+
getSegmentations: (prop_types_default()).func.isRequired,
|
|
526
|
+
toggleSegmentationVisibility: (prop_types_default()).func.isRequired,
|
|
527
|
+
subscribe: (prop_types_default()).func.isRequired,
|
|
528
|
+
EVENTS: (prop_types_default()).object.isRequired
|
|
529
|
+
}).isRequired
|
|
530
|
+
}).isRequired
|
|
531
|
+
}).isRequired
|
|
532
|
+
};
|
|
533
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts
|
|
534
|
+
const segProtocol = {
|
|
535
|
+
id: '@ohif/seg',
|
|
536
|
+
// Don't store this hanging protocol as it applies to the currently active
|
|
537
|
+
// display set by default
|
|
538
|
+
// cacheId: null,
|
|
539
|
+
hasUpdatedPriorsInformation: false,
|
|
540
|
+
name: 'Segmentations',
|
|
541
|
+
// Just apply this one when specifically listed
|
|
542
|
+
protocolMatchingRules: [],
|
|
543
|
+
toolGroupIds: ['default'],
|
|
544
|
+
// -1 would be used to indicate active only, whereas other values are
|
|
545
|
+
// the number of required priors referenced - so 0 means active with
|
|
546
|
+
// 0 or more priors.
|
|
547
|
+
numberOfPriorsReferenced: 0,
|
|
548
|
+
// Default viewport is used to define the viewport when
|
|
549
|
+
// additional viewports are added using the layout tool
|
|
550
|
+
defaultViewport: {
|
|
551
|
+
viewportOptions: {
|
|
552
|
+
viewportType: 'stack',
|
|
553
|
+
toolGroupId: 'default',
|
|
554
|
+
allowUnmatchedView: true
|
|
555
|
+
},
|
|
556
|
+
displaySets: [{
|
|
557
|
+
id: 'segDisplaySetId',
|
|
558
|
+
matchedDisplaySetsIndex: -1
|
|
559
|
+
}]
|
|
560
|
+
},
|
|
561
|
+
displaySetSelectors: {
|
|
562
|
+
segDisplaySetId: {
|
|
563
|
+
seriesMatchingRules: [{
|
|
564
|
+
attribute: 'Modality',
|
|
565
|
+
constraint: {
|
|
566
|
+
equals: 'SEG'
|
|
567
|
+
}
|
|
568
|
+
}]
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
stages: [{
|
|
572
|
+
name: 'Segmentations',
|
|
573
|
+
viewportStructure: {
|
|
574
|
+
layoutType: 'grid',
|
|
575
|
+
properties: {
|
|
576
|
+
rows: 1,
|
|
577
|
+
columns: 1
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
viewports: [{
|
|
581
|
+
viewportOptions: {
|
|
582
|
+
allowUnmatchedView: true
|
|
583
|
+
},
|
|
584
|
+
displaySets: [{
|
|
585
|
+
id: 'segDisplaySetId'
|
|
586
|
+
}]
|
|
587
|
+
}]
|
|
588
|
+
}]
|
|
589
|
+
};
|
|
590
|
+
function getHangingProtocolModule() {
|
|
591
|
+
return [{
|
|
592
|
+
name: segProtocol.id,
|
|
593
|
+
protocol: segProtocol
|
|
594
|
+
}];
|
|
595
|
+
}
|
|
596
|
+
/* harmony default export */ const src_getHangingProtocolModule = (getHangingProtocolModule);
|
|
597
|
+
|
|
598
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-seg/src/index.tsx
|
|
599
|
+
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); }
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
const Component = /*#__PURE__*/react.lazy(() => {
|
|
606
|
+
return __webpack_require__.e(/* import() */ 569).then(__webpack_require__.bind(__webpack_require__, 33569));
|
|
607
|
+
});
|
|
608
|
+
const OHIFCornerstoneSEGViewport = props => {
|
|
609
|
+
return /*#__PURE__*/react.createElement(react.Suspense, {
|
|
610
|
+
fallback: /*#__PURE__*/react.createElement("div", null, "Loading...")
|
|
611
|
+
}, /*#__PURE__*/react.createElement(Component, props));
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* You can remove any of the following modules if you don't need them.
|
|
616
|
+
*/
|
|
617
|
+
const extension = {
|
|
618
|
+
/**
|
|
619
|
+
* Only required property. Should be a unique value across all extensions.
|
|
620
|
+
* You ID can be anything you want, but it should be unique.
|
|
621
|
+
*/
|
|
622
|
+
id: id,
|
|
623
|
+
/**
|
|
624
|
+
* PanelModule should provide a list of panels that will be available in OHIF
|
|
625
|
+
* for Modes to consume and render. Each panel is defined by a {name,
|
|
626
|
+
* iconName, iconLabel, label, component} object. Example of a panel module
|
|
627
|
+
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
|
628
|
+
*/
|
|
629
|
+
getPanelModule: _ref => {
|
|
630
|
+
let {
|
|
631
|
+
servicesManager,
|
|
632
|
+
commandsManager,
|
|
633
|
+
extensionManager
|
|
634
|
+
} = _ref;
|
|
635
|
+
const wrappedPanelSegmentation = () => {
|
|
636
|
+
return /*#__PURE__*/react.createElement(PanelSegmentation, {
|
|
637
|
+
commandsManager: commandsManager,
|
|
638
|
+
servicesManager: servicesManager,
|
|
639
|
+
extensionManager: extensionManager
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
return [{
|
|
643
|
+
name: 'panelSegmentation',
|
|
644
|
+
iconName: 'tab-segmentation',
|
|
645
|
+
iconLabel: 'Segmentation',
|
|
646
|
+
label: 'Segmentation',
|
|
647
|
+
component: wrappedPanelSegmentation
|
|
648
|
+
}];
|
|
649
|
+
},
|
|
650
|
+
getViewportModule(_ref2) {
|
|
651
|
+
let {
|
|
652
|
+
servicesManager,
|
|
653
|
+
extensionManager
|
|
654
|
+
} = _ref2;
|
|
655
|
+
const ExtendedOHIFCornerstoneSEGViewport = props => {
|
|
656
|
+
return /*#__PURE__*/react.createElement(OHIFCornerstoneSEGViewport, _extends({
|
|
657
|
+
servicesManager: servicesManager,
|
|
658
|
+
extensionManager: extensionManager
|
|
659
|
+
}, props));
|
|
660
|
+
};
|
|
661
|
+
return [{
|
|
662
|
+
name: 'dicom-seg',
|
|
663
|
+
component: ExtendedOHIFCornerstoneSEGViewport
|
|
664
|
+
}];
|
|
665
|
+
},
|
|
666
|
+
/**
|
|
667
|
+
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
|
668
|
+
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
|
669
|
+
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
|
670
|
+
* Examples include the default sop class handler provided by the default extension
|
|
671
|
+
*/
|
|
672
|
+
getSopClassHandlerModule: src_getSopClassHandlerModule,
|
|
673
|
+
getHangingProtocolModule: src_getHangingProtocolModule
|
|
674
|
+
};
|
|
675
|
+
/* harmony default export */ const cornerstone_dicom_seg_src = (extension);
|
|
676
|
+
|
|
677
|
+
/***/ }),
|
|
678
|
+
|
|
679
|
+
/***/ 78753:
|
|
680
|
+
/***/ (() => {
|
|
681
|
+
|
|
682
|
+
/* (ignored) */
|
|
683
|
+
|
|
684
|
+
/***/ })
|
|
685
|
+
|
|
686
|
+
}]);
|
|
Binary file
|