volview 4.0.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.
Files changed (315) hide show
  1. package/.browserslistrc +4 -0
  2. package/.editorconfig +6 -0
  3. package/.env +6 -0
  4. package/.eslintrc.js +109 -0
  5. package/.gitattributes +1 -0
  6. package/.github/workflows/checks.yml +16 -0
  7. package/.github/workflows/publish.yml +52 -0
  8. package/.prettierignore +5 -0
  9. package/CONTRIBUTING.md +29 -0
  10. package/LICENSE +202 -0
  11. package/README.md +44 -0
  12. package/babel.config.js +3 -0
  13. package/documentation/config.js +15 -0
  14. package/documentation/content/KWVolViewLogo.png +0 -0
  15. package/documentation/content/KWVolViewLogo.svg +35 -0
  16. package/documentation/content/VolViewLogo.png +0 -0
  17. package/documentation/content/VolViewLogo.svg +22 -0
  18. package/documentation/content/doc/index.md +64 -0
  19. package/documentation/content/doc/loading_data.md +30 -0
  20. package/documentation/content/doc/mouse_controls.md +34 -0
  21. package/documentation/content/doc/quick_start_guide.md +75 -0
  22. package/documentation/content/doc/rendering.md +27 -0
  23. package/documentation/content/doc/state_files.md +18 -0
  24. package/documentation/content/doc/toolbar.md +20 -0
  25. package/documentation/content/doc/welcome_screen.md +23 -0
  26. package/documentation/content/gallery/01-volview-welcome-notes.jpg +0 -0
  27. package/documentation/content/gallery/01-volview-welcome.jpg +0 -0
  28. package/documentation/content/gallery/02-volview-about.jpg +0 -0
  29. package/documentation/content/gallery/02-volview-notifications.jpg +0 -0
  30. package/documentation/content/gallery/02-volview-settings.jpg +0 -0
  31. package/documentation/content/gallery/03-volview-sample-datasets.jpg +0 -0
  32. package/documentation/content/gallery/04-volview-active-data.jpg +0 -0
  33. package/documentation/content/gallery/05-volview-select-data-to-delete.jpg +0 -0
  34. package/documentation/content/gallery/06-volview-corner-annotation.jpg +0 -0
  35. package/documentation/content/gallery/07-volview-layout-notes.jpg +0 -0
  36. package/documentation/content/gallery/07-volview-layout.jpg +0 -0
  37. package/documentation/content/gallery/09-volview-layout-3DPrimary.jpg +0 -0
  38. package/documentation/content/gallery/10-volview-wl-pan-zoom-notes.jpg +0 -0
  39. package/documentation/content/gallery/10-volview-wl-pan-zoom.jpg +0 -0
  40. package/documentation/content/gallery/11-volview-paint-notes.jpg +0 -0
  41. package/documentation/content/gallery/11-volview-paint.jpg +0 -0
  42. package/documentation/content/gallery/12-volview-paint-options.jpg +0 -0
  43. package/documentation/content/gallery/13-volview-crop-notes.jpg +0 -0
  44. package/documentation/content/gallery/13-volview-crop.jpg +0 -0
  45. package/documentation/content/gallery/14-volview-ruler.jpg +0 -0
  46. package/documentation/content/gallery/15-volview-crosshairs.jpg +0 -0
  47. package/documentation/content/gallery/16-volview-rendering.jpg +0 -0
  48. package/documentation/content/gallery/17-volview-colormap-notes.jpg +0 -0
  49. package/documentation/content/gallery/17-volview-colormap.jpg +0 -0
  50. package/documentation/content/gallery/17-volview-opacity-notes.jpg +0 -0
  51. package/documentation/content/gallery/17-volview-opacity.jpg +0 -0
  52. package/documentation/content/gallery/17-volview-opacity2.jpg +0 -0
  53. package/documentation/content/gallery/18-volview-presets.jpg +0 -0
  54. package/documentation/content/gallery/19-volview-color.jpg +0 -0
  55. package/documentation/content/gallery/19-volview-color2.jpg +0 -0
  56. package/documentation/content/gallery/20-volview-lightfollowcamera0.jpg +0 -0
  57. package/documentation/content/gallery/20-volview-lightfollowcamera1.jpg +0 -0
  58. package/documentation/content/gallery/20-volview-lightfollowcamera2.jpg +0 -0
  59. package/documentation/content/gallery/20-volview-lightfollowcamera3.jpg +0 -0
  60. package/documentation/content/gallery/21-volview-hybrid-final.jpg +0 -0
  61. package/documentation/content/gallery/21-volview-hybrid0.0.jpg +0 -0
  62. package/documentation/content/gallery/21-volview-hybrid0.5.jpg +0 -0
  63. package/documentation/content/gallery/21-volview-hybrid1.0.jpg +0 -0
  64. package/documentation/content/gallery/LocalAmbientOcclusion.jpg +0 -0
  65. package/documentation/content/gallery/Notes.pptx +0 -0
  66. package/documentation/content/gallery/VolView-Overview.jpg +0 -0
  67. package/documentation/content/gallery/index.md +53 -0
  68. package/documentation/content/icon/favicon-16x16.png +0 -0
  69. package/documentation/content/icon/favicon-196x196.png +0 -0
  70. package/documentation/content/icon/favicon-32x32.png +0 -0
  71. package/documentation/content/icon/favicon-96x96.png +0 -0
  72. package/documentation/content/index.pug +43 -0
  73. package/documentation/content/logo.png +0 -0
  74. package/documentation/content/logo.svg +20 -0
  75. package/documentation/data/menu.yml +3 -0
  76. package/documentation/themes/navy/layout/partial/footer.swig +22 -0
  77. package/documentation/tpl/__en__ +31 -0
  78. package/documentation/tpl/__sidebar__ +11 -0
  79. package/package.json +96 -0
  80. package/patches/jsdom-global+3.0.2.patch +12 -0
  81. package/patches/vue-toastification+1.7.14.patch +41 -0
  82. package/patches/vuetify+2.6.10.patch +13 -0
  83. package/prettier.config.js +9 -0
  84. package/public/favicon.ico +0 -0
  85. package/public/global.css +20 -0
  86. package/public/index.html +28 -0
  87. package/public/pvm-sample.png +0 -0
  88. package/server/helper.py +147 -0
  89. package/server/host.js +134 -0
  90. package/server/serialize.js +111 -0
  91. package/server/serialize.py +203 -0
  92. package/server/server.py +84 -0
  93. package/server/test.py +17 -0
  94. package/src/assets/KitwareHeadAndNeck.jpg +0 -0
  95. package/src/assets/logo.png +0 -0
  96. package/src/assets/logo.svg +20 -0
  97. package/src/assets/samples/3DUS-Fetus.jpg +0 -0
  98. package/src/assets/samples/CTA-Head_and_Neck.jpg +0 -0
  99. package/src/assets/samples/MRA-Head_and_Neck.jpg +0 -0
  100. package/src/assets/samples/MRI-Cardiac.jpg +0 -0
  101. package/src/assets/samples/MRI-PROSTATEx.jpg +0 -0
  102. package/src/components/AboutBox.vue +157 -0
  103. package/src/components/AiModule.vue +60 -0
  104. package/src/components/AnnotationsModule.vue +37 -0
  105. package/src/components/App.vue +539 -0
  106. package/src/components/ColorFunctionSlider.vue +181 -0
  107. package/src/components/DataBrowser.vue +173 -0
  108. package/src/components/DragAndDrop.vue +107 -0
  109. package/src/components/GroupableItem.vue +30 -0
  110. package/src/components/ImageDataBrowser.vue +186 -0
  111. package/src/components/ImageListCard.vue +114 -0
  112. package/src/components/ItemGroup.vue +74 -0
  113. package/src/components/LabelmapList.vue +36 -0
  114. package/src/components/LayoutGrid.vue +96 -0
  115. package/src/components/MeasurementsList.vue +134 -0
  116. package/src/components/MessageCenter.vue +189 -0
  117. package/src/components/MessageItem.vue +83 -0
  118. package/src/components/MessageNotificationContent.vue +20 -0
  119. package/src/components/MessageNotifications.vue +84 -0
  120. package/src/components/ModelBrowser.vue +83 -0
  121. package/src/components/ModulePanel.vue +137 -0
  122. package/src/components/PaintControls.vue +174 -0
  123. package/src/components/PatientBrowser.vue +280 -0
  124. package/src/components/PatientStudyVolumeBrowser.vue +325 -0
  125. package/src/components/RenderingModule.vue +49 -0
  126. package/src/components/ResizableNavDrawer.vue +158 -0
  127. package/src/components/SampleDataBrowser.vue +198 -0
  128. package/src/components/SaveSession.vue +57 -0
  129. package/src/components/Settings.vue +35 -0
  130. package/src/components/SliceSlider.vue +182 -0
  131. package/src/components/SpleenModule.vue +101 -0
  132. package/src/components/ToolButton.vue +58 -0
  133. package/src/components/ToolStrip.vue +186 -0
  134. package/src/components/ViewOverlayGrid.vue +84 -0
  135. package/src/components/VolumePresets.vue +165 -0
  136. package/src/components/VolumeProperties.vue +171 -0
  137. package/src/components/VolumeRendering.vue +365 -0
  138. package/src/components/VtkThreeView.vue +512 -0
  139. package/src/components/VtkTwoView.vue +665 -0
  140. package/src/components/__tests__/ResizableNavDrawer.spec.js +32 -0
  141. package/src/components/__tests__/ToolButton.spec.js +46 -0
  142. package/src/components/icons/KitwareLogoIcon.vue +54 -0
  143. package/src/components/icons/VolViewFullLogo.vue +139 -0
  144. package/src/components/icons/VolViewLogo.vue +78 -0
  145. package/src/components/styles/utils.css +16 -0
  146. package/src/components/styles/vtk-view.css +116 -0
  147. package/src/components/tools/CropTool.vue +61 -0
  148. package/src/components/tools/CrosshairsTool.vue +93 -0
  149. package/src/components/tools/MouseManipulatorTool.vue +108 -0
  150. package/src/components/tools/PaintTool.vue +54 -0
  151. package/src/components/tools/PanTool.vue +35 -0
  152. package/src/components/tools/RulerTool.vue +202 -0
  153. package/src/components/tools/SliceScrollTool.vue +123 -0
  154. package/src/components/tools/WindowLevelTool.vue +191 -0
  155. package/src/components/tools/ZoomTool.vue +35 -0
  156. package/src/components/tools/crop/Crop2D.vue +367 -0
  157. package/src/components/tools/crop/Crop2DLineHandle.vue +103 -0
  158. package/src/components/tools/crop/Crop3D.vue +192 -0
  159. package/src/components/tools/crop/CropControls.vue +38 -0
  160. package/src/components/tools/crop/types.ts +12 -0
  161. package/src/components/tools/crosshairs/CrosshairSVG2D.vue +125 -0
  162. package/src/components/tools/crosshairs/CrosshairsWidget2D.vue +100 -0
  163. package/src/components/tools/paint/PaintWidget2D.vue +192 -0
  164. package/src/components/tools/ruler/RulerSVG2D.vue +181 -0
  165. package/src/components/tools/ruler/RulerWidget2D.vue +154 -0
  166. package/src/composables/__tests__/useDoubleRecord.spec.ts +57 -0
  167. package/src/composables/__tests__/useOrientationLabels.spec.ts +29 -0
  168. package/src/composables/isViewAnimating.ts +25 -0
  169. package/src/composables/manageVTKSubscription.ts +6 -0
  170. package/src/composables/onProxyManagerEvent.ts +50 -0
  171. package/src/composables/proxyManager.ts +13 -0
  172. package/src/composables/sync/__tests__/createPrimitiveSyncContext.spec.ts +41 -0
  173. package/src/composables/sync/__tests__/useWindowingSync.spec.ts +51 -0
  174. package/src/composables/sync/createPrimitiveSyncContext.ts +83 -0
  175. package/src/composables/sync/useWindowingSync.ts +48 -0
  176. package/src/composables/useAppLoadingNotifications.ts +73 -0
  177. package/src/composables/useCameraOrientation.ts +34 -0
  178. package/src/composables/useCurrentImage.ts +74 -0
  179. package/src/composables/useCurrentSlice.ts +43 -0
  180. package/src/composables/useDoubleRecord.ts +77 -0
  181. package/src/composables/useEventListener.ts +26 -0
  182. package/src/composables/useGlobalErrorHook.ts +18 -0
  183. package/src/composables/useMultiSelection.ts +29 -0
  184. package/src/composables/useOrientationLabels.ts +62 -0
  185. package/src/composables/usePanels.ts +42 -0
  186. package/src/composables/usePersistCameraConfig.ts +138 -0
  187. package/src/composables/useResizeObserver.ts +37 -0
  188. package/src/composables/useResizeToFit.ts +50 -0
  189. package/src/composables/useSceneBuilder.ts +96 -0
  190. package/src/composables/useToast.js +11 -0
  191. package/src/composables/useVTKCallback.ts +33 -0
  192. package/src/composables/useVTKWorldToDisplay.ts +141 -0
  193. package/src/composables/useViewProxy.ts +87 -0
  194. package/src/composables/useVolumeThumbnailing.ts +173 -0
  195. package/src/composables/useWebGLWatchdog.ts +32 -0
  196. package/src/composables/useWidgetManager.ts +41 -0
  197. package/src/config.ts +182 -0
  198. package/src/constants.ts +38 -0
  199. package/src/core/id.ts +18 -0
  200. package/src/core/provider.ts +31 -0
  201. package/src/core/proxies.ts +90 -0
  202. package/src/core/thumbnailers/__tests__/vtk-image.spec.ts +58 -0
  203. package/src/core/thumbnailers/index.ts +5 -0
  204. package/src/core/thumbnailers/volume-thumbnailer.ts +130 -0
  205. package/src/core/thumbnailers/vtk-image.ts +142 -0
  206. package/src/core/tools/__tests__/paint.spec.ts +101 -0
  207. package/src/core/tools/__tests__/ruler.spec.ts +73 -0
  208. package/src/core/tools/paint/brush.ts +20 -0
  209. package/src/core/tools/paint/ellipse-brush.ts +96 -0
  210. package/src/core/tools/paint/index.ts +141 -0
  211. package/src/core/tools/ruler.ts +149 -0
  212. package/src/global.d.ts +24 -0
  213. package/src/io/__tests__/io.spec.ts +26 -0
  214. package/src/io/dicom.ts +187 -0
  215. package/src/io/index.ts +11 -0
  216. package/src/io/io.ts +157 -0
  217. package/src/io/itk/itkConfig.js +8 -0
  218. package/src/io/itk-dicom/dicom.cpp +327 -0
  219. package/src/io/itk-dicom/web-build/dicom.js +16 -0
  220. package/src/io/itk-dicom/web-build/dicom.umd.js +21 -0
  221. package/src/io/itk-dicom/web-build/dicom.umd.wasm +0 -0
  222. package/src/io/itk-dicom/web-build/dicom.wasm +0 -0
  223. package/src/io/manifest.ts +30 -0
  224. package/src/io/readers.ts +36 -0
  225. package/src/io/state-file/index.ts +170 -0
  226. package/src/io/state-file/schema.ts +277 -0
  227. package/src/io/state-file/utils.ts +33 -0
  228. package/src/io/types.ts +4 -0
  229. package/src/io/vtk/async.reader.worker.ts +50 -0
  230. package/src/io/vtk/async.ts +58 -0
  231. package/src/io/vtk/async.writer.worker.ts +46 -0
  232. package/src/io/vtk/common.ts +30 -0
  233. package/src/io/zip.ts +31 -0
  234. package/src/main.js +66 -0
  235. package/src/main.ts +1 -0
  236. package/src/plugins/vuetify.js +25 -0
  237. package/src/shims-itk.d.ts +67 -0
  238. package/src/shims-misc.d.ts +4 -0
  239. package/src/shims-tsx.d.ts +13 -0
  240. package/src/shims-vtk.d.ts +1042 -0
  241. package/src/shims-vue.d.ts +5 -0
  242. package/src/store/__tests__/datasets.spec.ts +75 -0
  243. package/src/store/__tests__/messages.spec.ts +74 -0
  244. package/src/store/__tests__/rulers.spec.ts +77 -0
  245. package/src/store/__tests__/view-configs.spec.ts +37 -0
  246. package/src/store/datasets-dicom.ts +398 -0
  247. package/src/store/datasets-files.ts +35 -0
  248. package/src/store/datasets-images.ts +118 -0
  249. package/src/store/datasets-labelmaps.ts +136 -0
  250. package/src/store/datasets-models.ts +25 -0
  251. package/src/store/datasets.ts +341 -0
  252. package/src/store/messages.ts +144 -0
  253. package/src/store/tools/crop.ts +136 -0
  254. package/src/store/tools/crosshairs.ts +131 -0
  255. package/src/store/tools/index.ts +95 -0
  256. package/src/store/tools/paint.ts +191 -0
  257. package/src/store/tools/rulers.ts +305 -0
  258. package/src/store/tools/types.ts +54 -0
  259. package/src/store/view-configs/camera.ts +51 -0
  260. package/src/store/view-configs/common.ts +61 -0
  261. package/src/store/view-configs/slicing.ts +86 -0
  262. package/src/store/view-configs/types.ts +39 -0
  263. package/src/store/view-configs/volume-coloring.ts +193 -0
  264. package/src/store/view-configs/windowing.ts +69 -0
  265. package/src/store/view-configs.ts +78 -0
  266. package/src/store/views.ts +130 -0
  267. package/src/types/index.ts +16 -0
  268. package/src/types/layout.ts +10 -0
  269. package/src/types/lps.ts +40 -0
  270. package/src/types/views.ts +65 -0
  271. package/src/types/vtk-types.ts +29 -0
  272. package/src/utils/__tests__/lps.spec.ts +102 -0
  273. package/src/utils/color.ts +23 -0
  274. package/src/utils/errors.js +3 -0
  275. package/src/utils/fetch.ts +101 -0
  276. package/src/utils/hacks.ts +14 -0
  277. package/src/utils/index.ts +83 -0
  278. package/src/utils/lps.ts +129 -0
  279. package/src/utils/manipulators.ts +35 -0
  280. package/src/utils/promise-worker.ts +45 -0
  281. package/src/utils/vtk-helpers.ts +215 -0
  282. package/src/utils/workerHandler.js +42 -0
  283. package/src/vtk/ColorMaps.ts +72 -0
  284. package/src/vtk/CrosshairsWidget/behavior.ts +112 -0
  285. package/src/vtk/CrosshairsWidget/index.d.ts +22 -0
  286. package/src/vtk/CrosshairsWidget/index.js +73 -0
  287. package/src/vtk/CrosshairsWidget/state.ts +51 -0
  288. package/src/vtk/IJKSliceRepresentationProxy/index.d.ts +9 -0
  289. package/src/vtk/IJKSliceRepresentationProxy/index.js +59 -0
  290. package/src/vtk/LPSView2DProxy/index.d.ts +18 -0
  291. package/src/vtk/LPSView2DProxy/index.js +89 -0
  292. package/src/vtk/LPSView3DProxy/index.d.ts +16 -0
  293. package/src/vtk/LPSView3DProxy/index.js +78 -0
  294. package/src/vtk/LabelMap/index.d.ts +16 -0
  295. package/src/vtk/LabelMap/index.js +49 -0
  296. package/src/vtk/LabelMapSliceRepProxy/index.d.ts +6 -0
  297. package/src/vtk/LabelMapSliceRepProxy/index.js +104 -0
  298. package/src/vtk/MedicalColorPresets.json +1448 -0
  299. package/src/vtk/PaintBrushContextRepresentation/index.js +191 -0
  300. package/src/vtk/PaintWidget/behavior.ts +101 -0
  301. package/src/vtk/PaintWidget/index.d.ts +28 -0
  302. package/src/vtk/PaintWidget/index.js +56 -0
  303. package/src/vtk/PaintWidget/state.ts +41 -0
  304. package/src/vtk/PiecewiseWidget/index.js +203 -0
  305. package/src/vtk/RulerWidget/behavior.ts +185 -0
  306. package/src/vtk/RulerWidget/index.d.ts +25 -0
  307. package/src/vtk/RulerWidget/index.js +79 -0
  308. package/src/vtk/RulerWidget/state.ts +63 -0
  309. package/src/vtk/TreJsonConverter/index.js +150 -0
  310. package/src/vtk/proxy.js +66 -0
  311. package/src/vtk/proxyUtils.js +16 -0
  312. package/src/vtk/webvr-empty.js +1 -0
  313. package/tests/testUtils.js +12 -0
  314. package/tsconfig.json +46 -0
  315. package/vue.config.js +86 -0
package/src/io/io.ts ADDED
@@ -0,0 +1,157 @@
1
+ import { extensionToImageIO } from 'itk-wasm';
2
+ import { extractFilesFromZip } from './zip';
3
+ import { FileEntry } from './types';
4
+
5
+ export const ARCHIVE_FILE_TYPES = new Set(['zip', 'application/zip']);
6
+
7
+ export const ITK_IMAGE_EXTENSIONS = Array.from(
8
+ new Set(Array.from(extensionToImageIO.keys()).map((ext) => ext.toLowerCase()))
9
+ );
10
+
11
+ // remove duplicates
12
+ export const FILE_TYPES = Array.from(
13
+ new Set(['vti', 'vtp', 'stl', 'dcm', 'zip', 'json', ...ITK_IMAGE_EXTENSIONS])
14
+ );
15
+
16
+ /**
17
+ * file magic database
18
+ * Used to handle certain cases where files have no extension
19
+ */
20
+ const FILE_MAGIC_DB = [
21
+ {
22
+ type: 'dcm',
23
+ skip: 128,
24
+ header: Array.from('DICM').map((c) => c.charCodeAt(0)),
25
+ },
26
+ ];
27
+
28
+ // How much data to read when extracting file magic.
29
+ // This should be generous enough for most files.
30
+ const HEAD_CHUNK = 512;
31
+
32
+ function prefixEquals(target: Uint8Array, prefix: number[] | Uint8Array) {
33
+ if (prefix.length > target.length) {
34
+ return false;
35
+ }
36
+ for (let i = 0; i < prefix.length; i += 1) {
37
+ if (prefix[i] !== target[i]) {
38
+ return false;
39
+ }
40
+ }
41
+ return true;
42
+ }
43
+
44
+ async function getFileTypeFromMagic(file: File): Promise<string | null> {
45
+ return new Promise((resolve) => {
46
+ const head = file.slice(0, HEAD_CHUNK);
47
+ const reader = new FileReader();
48
+ reader.onload = () => {
49
+ const chunk = new Uint8Array(reader.result! as ArrayBuffer);
50
+ for (let i = 0; i < FILE_MAGIC_DB.length; i += 1) {
51
+ const { type, header, skip = 0 } = FILE_MAGIC_DB[i];
52
+ if (prefixEquals(chunk.slice(skip), header)) {
53
+ resolve(type);
54
+ return;
55
+ }
56
+ }
57
+ resolve(null);
58
+ };
59
+ reader.readAsArrayBuffer(head);
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Retypes a given File.
65
+ *
66
+ * Handy for when a file object has no type given.
67
+ * Type is inferred from extension or magic.
68
+ *
69
+ * If type is not supported, file.type will be "".
70
+ * @param file
71
+ */
72
+ export async function retypeFile(file: File): Promise<File> {
73
+ let type: string | null;
74
+
75
+ type =
76
+ FILE_TYPES.find((ext) => file.name.toLowerCase().endsWith(`.${ext}`)) ??
77
+ null;
78
+
79
+ if (!type) {
80
+ type = await getFileTypeFromMagic(file);
81
+ }
82
+
83
+ if (!type) {
84
+ type = '';
85
+ }
86
+
87
+ const retypedFile = new File([file], file.name, { type: type.toLowerCase() });
88
+ return retypedFile;
89
+ }
90
+
91
+ export async function extractArchivesRecursively(
92
+ files: File[]
93
+ ): Promise<FileEntry[]> {
94
+ const results = await Promise.all(
95
+ files.map(async (file) => {
96
+ if (ARCHIVE_FILE_TYPES.has(file.type)) {
97
+ return extractFilesFromZip(file);
98
+ }
99
+
100
+ return [{ file, path: '' }];
101
+ })
102
+ );
103
+
104
+ const archivedEntries = await Promise.all(
105
+ results.flat().map(async (entry) => {
106
+ return { file: await retypeFile(entry.file), path: entry.path };
107
+ })
108
+ );
109
+
110
+ const archives = archivedEntries
111
+ .filter(({ file }) => ARCHIVE_FILE_TYPES.has(file.type))
112
+ .map(({ file }) => file);
113
+
114
+ if (archives.length > 0) {
115
+ const moreEntries = await extractArchivesRecursively(archives);
116
+ return [...archivedEntries, ...moreEntries];
117
+ }
118
+
119
+ return archivedEntries;
120
+ }
121
+
122
+ export type ReadAsType = 'ArrayBuffer' | 'Text';
123
+
124
+ async function readFileAs<T extends ArrayBuffer | string>(
125
+ file: File,
126
+ type: ReadAsType
127
+ ): Promise<T> {
128
+ return new Promise((resolve) => {
129
+ const fio = new globalThis.FileReader();
130
+ fio.onload = () => resolve(fio.result as T);
131
+ if (type === 'ArrayBuffer') {
132
+ fio.readAsArrayBuffer(file);
133
+ } else if (type === 'Text') {
134
+ fio.readAsText(file);
135
+ } else {
136
+ throw new TypeError(`readAs${type} is not a function`);
137
+ }
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Reads a file and returns an ArrayBuffer
143
+ * @async
144
+ * @param {File} file
145
+ */
146
+ export async function readFileAsArrayBuffer(file: File) {
147
+ return readFileAs<ArrayBuffer>(file, 'ArrayBuffer');
148
+ }
149
+
150
+ /**
151
+ * Reads a file and returns UTF-8 text
152
+ * @async
153
+ * @param {File} file
154
+ */
155
+ export async function readFileAsUTF8Text(file: File) {
156
+ return readFileAs<string>(file, 'Text');
157
+ }
@@ -0,0 +1,8 @@
1
+ const itkConfig = {
2
+ pipelineWorkerUrl: '/itk/web-workers/min-bundles/pipeline.worker.js',
3
+ imageIOUrl: '/itk/image-io',
4
+ meshIOUrl: '/itk/mesh-io',
5
+ pipelinesUrl: '/itk/pipelines',
6
+ };
7
+
8
+ export default itkConfig;
@@ -0,0 +1,327 @@
1
+ #include <cerrno>
2
+ #include <cstdio>
3
+ #include <dirent.h>
4
+ #include <fstream>
5
+ #include <iostream>
6
+ #include <stdexcept>
7
+ #include <string>
8
+ #include <sys/stat.h>
9
+ #include <sys/types.h>
10
+ #include <unordered_map>
11
+ #include <utility>
12
+ #include <vector>
13
+
14
+ #ifdef WEB_BUILD
15
+ // Building with the itk.js docker container has a more recent gcc version
16
+ #include <filesystem>
17
+ namespace fs = std::filesystem;
18
+ #else
19
+ // Building locally with gcc 7.5.0 means I need -lstdc++fs and
20
+ // experimental/filesystem
21
+ #include <experimental/filesystem>
22
+ namespace fs = std::experimental::filesystem;
23
+ #endif
24
+
25
+ #ifdef WEB_BUILD
26
+ #include <emscripten.h>
27
+ #endif
28
+
29
+ #include <fstream>
30
+ #include <iostream>
31
+ #include <nlohmann/json.hpp>
32
+
33
+ #include "itkCastImageFilter.h"
34
+ #include "itkGDCMImageIO.h"
35
+ #include "itkGDCMSeriesFileNames.h"
36
+ #include "itkImage.h"
37
+ #include "itkImageFileReader.h"
38
+ #include "itkImageIOBase.h"
39
+ #include "itkImageSeriesReader.h"
40
+ #include "itkRescaleIntensityImageFilter.h"
41
+ #include "itkVectorImage.h"
42
+
43
+ #include "itkOutputImage.h"
44
+ #include "itkOutputTextStream.h"
45
+ #include "itkPipeline.h"
46
+
47
+ #include "gdcmImageHelper.h"
48
+ #include "gdcmReader.h"
49
+
50
+ using json = nlohmann::json;
51
+ using ImageType = itk::Image<float, 3>;
52
+ using ReaderType = itk::ImageFileReader<ImageType>;
53
+ using SeriesReaderType = itk::ImageSeriesReader<ImageType>;
54
+ using FileNamesContainer = std::vector<std::string>;
55
+ using DicomIO = itk::GDCMImageIO;
56
+ // volumeID -> filenames[]
57
+ using VolumeMapType = std::unordered_map<std::string, std::vector<std::string>>;
58
+ // VolumeID[]
59
+ using VolumeIDList = std::vector<std::string>;
60
+
61
+ static const double EPSILON = 10e-5;
62
+
63
+ #ifdef WEB_BUILD
64
+ extern "C" const char *EMSCRIPTEN_KEEPALIVE unpack_error_what(intptr_t ptr) {
65
+ auto error = reinterpret_cast<std::runtime_error *>(ptr);
66
+ return error->what();
67
+ }
68
+ #endif
69
+
70
+ void replaceChars(std::string &str, char search, char replaceChar) {
71
+ int pos;
72
+ std::string replace(1, replaceChar);
73
+ while ((pos = str.find(search)) != std::string::npos) {
74
+ str.replace(pos, 1, replace);
75
+ }
76
+ }
77
+
78
+ // doesn't actually do any length checks, or overflow checks, or anything
79
+ // really.
80
+ template <int N>
81
+ double dotProduct(const std::vector<double> &vec1,
82
+ const std::vector<double> &vec2) {
83
+ double result = 0;
84
+ for (int i = 0; i < N; i++) {
85
+ result += vec1.at(i) * vec2.at(i);
86
+ }
87
+ return result;
88
+ }
89
+
90
+ std::vector<double> ReadImageOrientationValue(const std::string &filename) {
91
+ gdcm::Reader reader;
92
+ reader.SetFileName(filename.c_str());
93
+ if (!reader.Read()) {
94
+ throw std::runtime_error("gdcm: failed to read file");
95
+ }
96
+ const gdcm::File &file = reader.GetFile();
97
+ // This helper method asserts that the vector has length 6.
98
+ return gdcm::ImageHelper::GetDirectionCosinesValue(file);
99
+ }
100
+
101
+ bool areCosinesAlmostEqual(std::vector<double> cosines1,
102
+ std::vector<double> cosines2,
103
+ double epsilon = EPSILON) {
104
+ for (int i = 0; i <= 1; i++) {
105
+ std::vector<double> vec1{cosines1.at(i), cosines1.at(i + 1),
106
+ cosines1.at(i + 2)};
107
+ std::vector<double> vec2{cosines2.at(i), cosines2.at(i + 1),
108
+ cosines2.at(i + 2)};
109
+ double dot = dotProduct<3>(vec1, vec2);
110
+ if (dot < (1 - EPSILON)) {
111
+ return false;
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+
117
+ VolumeMapType SeparateOnImageOrientation(const VolumeMapType &volumeMap) {
118
+ VolumeMapType newVolumeMap;
119
+ // Vector< Pair< cosines, volumeID >>
120
+ std::vector<std::pair<std::vector<double>, std::string>> cosinesToID;
121
+
122
+ // append unique ID part to the volume ID, based on cosines
123
+ // The format replaces non-alphanumeric chars to be semi-consistent with DICOM
124
+ // UID spec,
125
+ // and to make debugging easier when looking at the full volume IDs.
126
+ // Format: COSINE || "S" || COSINE || "S" || ...
127
+ // COSINE: A decimal number -DD.DDDD gets reformatted into NDDSDDDD
128
+ auto encodeCosinesAsIDPart = [](const std::vector<double> &cosines) {
129
+ std::string concatenated;
130
+ for (auto it = cosines.begin(); it != cosines.end(); ++it) {
131
+ concatenated += std::to_string(*it);
132
+ if (it != cosines.end() - 1) {
133
+ concatenated += 'S';
134
+ }
135
+ }
136
+
137
+ replaceChars(concatenated, '-', 'N');
138
+ replaceChars(concatenated, '.', 'D');
139
+
140
+ return concatenated;
141
+ };
142
+
143
+ for (const auto &[volumeID, names] : volumeMap) {
144
+ for (const auto &filename : names) {
145
+ std::vector<double> curCosines = ReadImageOrientationValue(filename);
146
+
147
+ bool inserted = false;
148
+ for (const auto &entry : cosinesToID) {
149
+ if (areCosinesAlmostEqual(curCosines, entry.first)) {
150
+ newVolumeMap[entry.second].push_back(filename);
151
+ inserted = true;
152
+ break;
153
+ }
154
+ }
155
+
156
+ if (!inserted) {
157
+ const auto encodedIDPart = encodeCosinesAsIDPart(curCosines);
158
+ auto newID = volumeID + '.' + encodedIDPart;
159
+ newVolumeMap[newID].push_back(filename);
160
+ cosinesToID.push_back(std::make_pair(curCosines, newID));
161
+ }
162
+ }
163
+ }
164
+
165
+ return newVolumeMap;
166
+ }
167
+
168
+ /**
169
+ * categorizeFiles extracts out the volumes contained within a set of DICOM
170
+ * files.
171
+ *
172
+ * Return a mapping of volume ID to the files containing the volume.
173
+ */
174
+ int categorizeFiles(itk::wasm::Pipeline &pipeline) {
175
+
176
+ // inputs
177
+ FileNamesContainer files;
178
+ pipeline.add_option("-f,--files", files, "File names to categorize")
179
+ ->required()
180
+ ->check(CLI::ExistingFile)
181
+ ->expected(1, -1);
182
+
183
+ // outputs
184
+ itk::wasm::OutputTextStream volumeMapJSONStream;
185
+ pipeline
186
+ .add_option("volumeMap", volumeMapJSONStream,
187
+ "JSON object encoding volumeID => filenames.")
188
+ ->required();
189
+
190
+ ITK_WASM_PARSE(pipeline);
191
+
192
+ std::string path = "./";
193
+
194
+ // parse out series
195
+ typedef itk::GDCMSeriesFileNames SeriesFileNames;
196
+ SeriesFileNames::Pointer seriesFileNames = SeriesFileNames::New();
197
+ // files are all default dumped to cwd
198
+ seriesFileNames->SetDirectory(path);
199
+ seriesFileNames->SetUseSeriesDetails(true);
200
+ seriesFileNames->SetGlobalWarningDisplay(false);
201
+ seriesFileNames->AddSeriesRestriction("0008|0021");
202
+ seriesFileNames->SetRecursive(false);
203
+ // Does this affect series organization?
204
+ seriesFileNames->SetLoadPrivateTags(false);
205
+
206
+ // Obtain the initial separation of imported files into distinct volumes.
207
+ auto &gdcmSeriesUIDs = seriesFileNames->GetSeriesUIDs();
208
+
209
+ // The initial series UIDs are used as the basis for our volume IDs.
210
+ VolumeMapType volumeMap;
211
+ for (auto seriesUID : gdcmSeriesUIDs) {
212
+ volumeMap[seriesUID] = seriesFileNames->GetFileNames(seriesUID.c_str());
213
+ }
214
+
215
+ // further restrict on orientation
216
+ volumeMap = SeparateOnImageOrientation(volumeMap);
217
+
218
+ // strip off tmp prefix
219
+ for (auto &entry : volumeMap) {
220
+ auto &fileNames = entry.second;
221
+ for (auto &f : fileNames) {
222
+ f = f.substr(path.size());
223
+ }
224
+ }
225
+
226
+ // Generate the JSON and add to output stream
227
+ auto volumeMapJSON = json(volumeMap);
228
+ volumeMapJSONStream.Get() << volumeMapJSON;
229
+
230
+ // Clean up files
231
+ for (auto &file : files) {
232
+ remove(file.c_str());
233
+ }
234
+
235
+ return EXIT_SUCCESS;
236
+ }
237
+
238
+ /**
239
+ * Reads an image slice and returns the optionally thumbnailed image.
240
+ */
241
+ int getSliceImage(itk::wasm::Pipeline &pipeline) {
242
+
243
+ // inputs
244
+ std::string fileName;
245
+ pipeline.add_option("-f,--file", fileName, "File name generate image for")
246
+ ->required()
247
+ ->check(CLI::ExistingFile)
248
+ ->expected(1);
249
+
250
+ bool asThumbnail = false;
251
+ pipeline.add_option("-t,--thumbnail", asThumbnail,
252
+ "Generate thumbnail image");
253
+
254
+ ITK_WASM_PRE_PARSE(pipeline);
255
+
256
+ // Setup reader
257
+ typename DicomIO::Pointer dicomIO = DicomIO::New();
258
+ dicomIO->LoadPrivateTagsOff();
259
+ typename ReaderType::Pointer reader = ReaderType::New();
260
+ reader->SetFileName(fileName);
261
+
262
+ if (asThumbnail) {
263
+ using InputImageType = ImageType;
264
+ using OutputPixelType = uint8_t;
265
+ using OutputImageType = itk::Image<OutputPixelType, 3>;
266
+ using RescaleFilter =
267
+ itk::RescaleIntensityImageFilter<InputImageType, InputImageType>;
268
+ using CastImageFilter =
269
+ itk::CastImageFilter<InputImageType, OutputImageType>;
270
+
271
+ // outputs
272
+ using WasmOutputImageType = itk::wasm::OutputImage<OutputImageType>;
273
+ WasmOutputImageType outputImage;
274
+ pipeline.add_option("OutputImage", outputImage, "The slice")->required();
275
+
276
+ ITK_WASM_PARSE(pipeline);
277
+
278
+ auto rescaleFilter = RescaleFilter::New();
279
+ rescaleFilter->SetInput(reader->GetOutput());
280
+ rescaleFilter->SetOutputMinimum(0);
281
+ rescaleFilter->SetOutputMaximum(itk::NumericTraits<OutputPixelType>::max());
282
+
283
+ auto castFilter = CastImageFilter::New();
284
+ castFilter->SetInput(rescaleFilter->GetOutput());
285
+ castFilter->Update();
286
+
287
+ // Set the output image
288
+ outputImage.Set(castFilter->GetOutput());
289
+ } else {
290
+ // outputs
291
+ using WasmOutputImageType = itk::wasm::OutputImage<ImageType>;
292
+ WasmOutputImageType outputImage;
293
+ pipeline.add_option("OutputImage", outputImage, "The slice")->required();
294
+
295
+ ITK_WASM_PARSE(pipeline);
296
+
297
+ reader->Update();
298
+ outputImage.Set(reader->GetOutput());
299
+ }
300
+
301
+ // Clean up the file
302
+ remove(fileName.c_str());
303
+
304
+ return EXIT_SUCCESS;
305
+ }
306
+
307
+ int main(int argc, char *argv[]) {
308
+ std::string action;
309
+ itk::wasm::Pipeline pipeline("VolView pipeline to access DICOM data", argc,
310
+ argv);
311
+ pipeline.add_option("-a,--action", action, "The action to run")
312
+ ->check(CLI::IsMember({"categorize", "getSliceImage"}));
313
+
314
+ // Pre parse so we can get the action
315
+ ITK_WASM_PRE_PARSE(pipeline)
316
+
317
+ if (action == "categorize") {
318
+
319
+ ITK_WASM_CATCH_EXCEPTION(pipeline, categorizeFiles(pipeline));
320
+
321
+ } else if (action == "getSliceImage") {
322
+
323
+ ITK_WASM_CATCH_EXCEPTION(pipeline, getSliceImage(pipeline));
324
+ }
325
+
326
+ return EXIT_SUCCESS;
327
+ }