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
@@ -0,0 +1,118 @@
1
+ import { del, set } from '@vue/composition-api';
2
+ import { defineStore } from 'pinia';
3
+ import { vec3, mat3, mat4 } from 'gl-matrix';
4
+ import { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
5
+ import { Bounds } from '@kitware/vtk.js/types';
6
+
7
+ import { defaultLPSDirections, getLPSDirections } from '../utils/lps';
8
+ import { removeFromArray } from '../utils';
9
+ import { StateFile, DataSetType, DataSet } from '../io/state-file/schema';
10
+ import { serializeData } from '../io/state-file/utils';
11
+ import { FILE_READERS } from '../io';
12
+ import { useFileStore } from './datasets-files';
13
+ import { LPSDirections } from '../types/lps';
14
+
15
+ export interface ImageMetadata {
16
+ name: string;
17
+ orientation: mat3;
18
+ lpsOrientation: LPSDirections;
19
+ spacing: vec3;
20
+ origin: vec3;
21
+ dimensions: vec3;
22
+ worldBounds: Bounds;
23
+ worldToIndex: mat4;
24
+ indexToWorld: mat4;
25
+ }
26
+
27
+ export const defaultImageMetadata = () => ({
28
+ name: '(none)',
29
+ orientation: mat3.create(),
30
+ lpsOrientation: defaultLPSDirections(),
31
+ spacing: vec3.fromValues(1, 1, 1),
32
+ origin: vec3.create(),
33
+ dimensions: vec3.fromValues(1, 1, 1),
34
+ worldBounds: [0, 1, 0, 1, 0, 1] as Bounds,
35
+ worldToIndex: mat4.create(),
36
+ indexToWorld: mat4.create(),
37
+ });
38
+
39
+ interface State {
40
+ idList: string[]; // list of IDs
41
+ dataIndex: Record<string, vtkImageData>; // ID -> VTK object
42
+ metadata: Record<string, ImageMetadata>; // ID -> metadata
43
+ }
44
+
45
+ export const useImageStore = defineStore('images', {
46
+ state: (): State => ({
47
+ idList: [],
48
+ dataIndex: Object.create(null),
49
+ metadata: Object.create(null),
50
+ }),
51
+ actions: {
52
+ addVTKImageData(name: string, imageData: vtkImageData) {
53
+ const id = this.$id.nextID();
54
+
55
+ this.idList.push(id);
56
+ set(this.dataIndex, id, imageData);
57
+
58
+ this.$proxies.addData(id, imageData);
59
+
60
+ set(this.metadata, id, { ...defaultImageMetadata(), name });
61
+ this.updateData(id, imageData);
62
+ return id;
63
+ },
64
+
65
+ updateData(id: string, imageData: vtkImageData) {
66
+ if (id in this.metadata) {
67
+ const metadata: ImageMetadata = {
68
+ name: this.metadata[id].name,
69
+ dimensions: imageData.getDimensions() as vec3,
70
+ spacing: imageData.getSpacing() as vec3,
71
+ origin: imageData.getOrigin() as vec3,
72
+ orientation: imageData.getDirection(),
73
+ lpsOrientation: getLPSDirections(imageData.getDirection()),
74
+ worldBounds: imageData.getBounds(),
75
+ worldToIndex: imageData.getWorldToIndex(),
76
+ indexToWorld: imageData.getIndexToWorld(),
77
+ };
78
+
79
+ set(this.metadata, id, metadata);
80
+ }
81
+ },
82
+
83
+ deleteData(id: string) {
84
+ if (id in this.dataIndex) {
85
+ del(this.dataIndex, id);
86
+ del(this.metadata, id);
87
+ removeFromArray(this.idList, id);
88
+ }
89
+ },
90
+
91
+ async serialize(stateFile: StateFile) {
92
+ const fileStore = useFileStore();
93
+ // We want to filter out volume data (which is generated so don't have
94
+ // associated files).
95
+ const dataIDs = this.idList.filter((id) => {
96
+ return id in fileStore.byDataID;
97
+ });
98
+
99
+ await serializeData(stateFile, dataIDs, DataSetType.IMAGE);
100
+ },
101
+
102
+ async deserialize(dataSet: DataSet, file: File) {
103
+ const reader = FILE_READERS.get(file.type);
104
+ if (reader) {
105
+ const dataObj = await reader(
106
+ new File([file], file.name, { type: file.type })
107
+ );
108
+ if (dataObj.isA('vtkImageData')) {
109
+ const id = this.addVTKImageData(file.name, dataObj as vtkImageData);
110
+
111
+ return id;
112
+ }
113
+ }
114
+
115
+ throw new Error(`No reader for ${file.name}`);
116
+ },
117
+ },
118
+ });
@@ -0,0 +1,136 @@
1
+ import { set } from '@vue/composition-api';
2
+ import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
3
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
4
+ import { defineStore } from 'pinia';
5
+ import { useImageStore } from '@/src/store/datasets-images';
6
+ import vtkLabelMap from '../vtk/LabelMap';
7
+ import { LABELMAP_PALETTE } from '../config';
8
+ import { StateFile, Manifest } from '../io/state-file/schema';
9
+ import { vtiReader, vtiWriter } from '../io/vtk/async';
10
+ import { FileEntry } from '../io/types';
11
+
12
+ const LabelmapArrayType = Uint8Array;
13
+ export type LabelmapArrayType = Uint8Array;
14
+
15
+ interface State {
16
+ idList: string[];
17
+ labelmaps: Record<string, vtkLabelMap>;
18
+ parentImage: Record<string, string>;
19
+ }
20
+
21
+ function createLabelmapFromImage(imageData: vtkImageData) {
22
+ const points = new LabelmapArrayType(imageData.getNumberOfPoints());
23
+ const labelmap = vtkLabelMap.newInstance(
24
+ imageData.get('spacing', 'origin', 'direction')
25
+ );
26
+ labelmap.getPointData().setScalars(
27
+ vtkDataArray.newInstance({
28
+ numberOfComponents: 1,
29
+ values: points,
30
+ })
31
+ );
32
+ labelmap.setDimensions(imageData.getDimensions());
33
+ labelmap.computeTransforms();
34
+ labelmap.setColorMap(LABELMAP_PALETTE);
35
+
36
+ return labelmap;
37
+ }
38
+
39
+ function toLabelMap(imageData: vtkImageData) {
40
+ const labelmap = vtkLabelMap.newInstance(
41
+ imageData.get(
42
+ 'spacing',
43
+ 'origin',
44
+ 'direction',
45
+ 'indexToWorld',
46
+ 'worldToIndex',
47
+ 'dataDescription',
48
+ 'pointData'
49
+ )
50
+ );
51
+ labelmap.setDimensions(imageData.getDimensions());
52
+ labelmap.computeTransforms();
53
+ labelmap.setColorMap(LABELMAP_PALETTE);
54
+
55
+ return labelmap;
56
+ }
57
+
58
+ export const useLabelmapStore = defineStore('labelmap', {
59
+ state: (): State => ({
60
+ idList: [],
61
+ labelmaps: Object.create(null),
62
+ parentImage: Object.create(null),
63
+ }),
64
+ actions: {
65
+ newLabelmapFromImage(imageID: string) {
66
+ const imageStore = useImageStore();
67
+ const imageData = imageStore.dataIndex[imageID];
68
+ if (!imageData) {
69
+ return null;
70
+ }
71
+
72
+ const id = this.$id.nextID();
73
+ const labelmap = createLabelmapFromImage(imageData);
74
+
75
+ this.idList.push(id);
76
+ set(this.parentImage, id, imageID);
77
+ set(this.labelmaps, id, labelmap);
78
+
79
+ this.$proxies.addData(id, labelmap);
80
+
81
+ return id;
82
+ },
83
+ async serialize(state: StateFile) {
84
+ const { labelMaps } = state.manifest;
85
+ const { zip } = state;
86
+
87
+ await Promise.all(
88
+ Object.entries(this.labelmaps).map(async ([id, labelMap]) => {
89
+ const labelPath = `labels/${id}.vti`;
90
+ const parent = this.parentImage[id];
91
+ labelMaps.push({
92
+ id,
93
+ parent,
94
+ path: labelPath,
95
+ });
96
+
97
+ const serializedData = await vtiWriter(labelMap);
98
+ zip.file(labelPath, serializedData);
99
+ })
100
+ );
101
+ },
102
+ async deserialize(
103
+ manifest: Manifest,
104
+ stateFiles: FileEntry[],
105
+ dataIDMap: Record<string, string>
106
+ ) {
107
+ const { labelMaps } = manifest;
108
+
109
+ const labelmapIDMap: Record<string, string> = {};
110
+
111
+ labelMaps.forEach(async (labelMap) => {
112
+ const [file] = stateFiles
113
+ .filter(
114
+ (entry) => `${entry.path}${entry.file.name}` === labelMap.path
115
+ )
116
+ .map((entry) => entry.file);
117
+
118
+ // map parent id to new id
119
+ labelMap.parent = dataIDMap[labelMap.parent];
120
+
121
+ const { parent } = labelMap;
122
+ const id = this.$id.nextID();
123
+ labelmapIDMap[labelMap.id] = id;
124
+
125
+ const imageData = await vtiReader(file);
126
+ const labelMapObj = toLabelMap(imageData as vtkImageData);
127
+ this.idList.push(id);
128
+ set(this.parentImage, id, parent);
129
+ set(this.labelmaps, id, labelMapObj);
130
+ this.$proxies.addData(id, labelMapObj);
131
+ });
132
+
133
+ return labelmapIDMap;
134
+ },
135
+ },
136
+ });
@@ -0,0 +1,25 @@
1
+ import { defineStore } from 'pinia';
2
+ import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
3
+ import { set } from '@vue/composition-api';
4
+
5
+ interface State {
6
+ idList: string[]; // list of IDs
7
+ dataIndex: Record<string, vtkPolyData>; // ID -> VTK object
8
+ metadata: Record<string, {}>; // ID -> metadata
9
+ }
10
+ export const useModelStore = defineStore('models', {
11
+ state: (): State => ({
12
+ idList: [],
13
+ dataIndex: {},
14
+ metadata: {},
15
+ }),
16
+ actions: {
17
+ addVTKPolyData(name: string, polyData: vtkPolyData) {
18
+ const id = this.$id.nextID();
19
+ this.idList.push(id);
20
+ set(this.dataIndex, id, polyData);
21
+ this.$proxies.addData(id, polyData);
22
+ return id;
23
+ },
24
+ },
25
+ });
@@ -0,0 +1,341 @@
1
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
2
+ import { vtkObject } from '@kitware/vtk.js/interfaces';
3
+ import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
4
+ import { defineStore } from 'pinia';
5
+ import { computed, ref } from '@vue/composition-api';
6
+ import { useDICOMStore } from './datasets-dicom';
7
+ import { useImageStore } from './datasets-images';
8
+ import { useModelStore } from './datasets-models';
9
+ import { extractArchivesRecursively, retypeFile, FILE_READERS } from '../io';
10
+ import { useFileStore } from './datasets-files';
11
+ import { DataSet, StateFile, DataSetType } from '../io/state-file/schema';
12
+ import { useMessageStore } from './messages';
13
+ import { readRemoteManifestFile } from '../io/manifest';
14
+
15
+ export const DataType = {
16
+ Image: 'Image',
17
+ Model: 'Model',
18
+ };
19
+
20
+ const makeFileSuccessStatus = (
21
+ file: File,
22
+ type: 'model' | 'image',
23
+ dataID: string
24
+ ) =>
25
+ ({
26
+ type: 'file',
27
+ loaded: true,
28
+ filename: file.name,
29
+ dataID,
30
+ dataType: type,
31
+ } as const);
32
+
33
+ export type FileLoadSuccess = ReturnType<typeof makeFileSuccessStatus>;
34
+
35
+ export const makeFileFailureStatus = (file: File | string, reason: string) =>
36
+ ({
37
+ type: 'file',
38
+ loaded: false,
39
+ filename: typeof file === 'string' ? file : file.name,
40
+ error: new Error(reason),
41
+ } as const);
42
+
43
+ export type FileLoadFailure = ReturnType<typeof makeFileFailureStatus>;
44
+
45
+ const makeDICOMSuccessStatus = (volumeKey: string) =>
46
+ ({
47
+ type: 'dicom',
48
+ loaded: true,
49
+ dataID: volumeKey,
50
+ dataType: 'dicom',
51
+ } as const);
52
+
53
+ export type DICOMLoadSuccess = ReturnType<typeof makeDICOMSuccessStatus>;
54
+
55
+ const makeDICOMFailureStatus = (error: Error) =>
56
+ ({
57
+ type: 'dicom',
58
+ loaded: false,
59
+ error,
60
+ } as const);
61
+
62
+ export type DICOMLoadFailure = ReturnType<typeof makeDICOMFailureStatus>;
63
+
64
+ export type FileLoadResult = FileLoadSuccess | FileLoadFailure;
65
+ export type DICOMLoadResult = DICOMLoadSuccess | DICOMLoadFailure;
66
+ export type LoadResult = FileLoadResult | DICOMLoadResult;
67
+
68
+ export const makeDICOMSelection = (volumeKey: string) =>
69
+ ({
70
+ type: 'dicom',
71
+ volumeKey,
72
+ } as const);
73
+
74
+ export type DICOMSelection = ReturnType<typeof makeDICOMSelection>;
75
+
76
+ export const makeImageSelection = (imageID: string) =>
77
+ ({
78
+ type: 'image',
79
+ dataID: imageID,
80
+ } as const);
81
+
82
+ export type ImageSelection = ReturnType<typeof makeImageSelection>;
83
+
84
+ export type DataSelection = DICOMSelection | ImageSelection;
85
+
86
+ export function selectionEquals(s1: DataSelection, s2: DataSelection) {
87
+ if (s1.type === 'dicom' && s2.type === 'dicom') {
88
+ return s1.volumeKey === s2.volumeKey;
89
+ }
90
+ if (s1.type === 'image' && s2.type === 'image') {
91
+ return s1.dataID === s2.dataID;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ export function convertSuccessResultToDataSelection(
97
+ result: FileLoadSuccess | DICOMLoadSuccess
98
+ ) {
99
+ if (result.type === 'dicom') {
100
+ return makeDICOMSelection(result.dataID);
101
+ }
102
+ if (result.type === 'file') {
103
+ if (result.dataType === 'image') {
104
+ return makeImageSelection(result.dataID);
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+
110
+ export const useDatasetStore = defineStore('dataset', () => {
111
+ type _This = ReturnType<typeof useDatasetStore>;
112
+
113
+ const imageStore = useImageStore();
114
+ const modelStore = useModelStore();
115
+ const dicomStore = useDICOMStore();
116
+ const fileStore = useFileStore();
117
+
118
+ // --- state --- //
119
+
120
+ const primarySelection = ref<DataSelection | null>(null);
121
+
122
+ // --- getters --- //
123
+
124
+ const primaryDataset = computed<vtkImageData | null>(() => {
125
+ const sel = primarySelection.value;
126
+ const { dataIndex } = imageStore;
127
+ const { volumeToImageID } = dicomStore;
128
+
129
+ if (sel?.type === 'dicom') {
130
+ const id = volumeToImageID[sel.volumeKey];
131
+ return dataIndex[id] || null;
132
+ }
133
+ if (sel?.type === 'image') {
134
+ return dataIndex[sel.dataID] || null;
135
+ }
136
+ return null;
137
+ });
138
+
139
+ const allDataIDs = computed(() => {
140
+ return [...imageStore.idList, ...modelStore.idList];
141
+ });
142
+
143
+ function getDataProxyByID<T extends vtkObject>(this: _This, id: string) {
144
+ return this.$proxies.getData<T>(id);
145
+ }
146
+
147
+ // --- actions --- //
148
+
149
+ async function setPrimarySelection(sel: DataSelection | null) {
150
+ primarySelection.value = sel;
151
+
152
+ if (sel === null) {
153
+ return;
154
+ }
155
+
156
+ // if selection is dicom, call buildVolume
157
+ if (sel.type === 'dicom') {
158
+ try {
159
+ // trigger dicom dataset building
160
+ await dicomStore.buildVolume(sel.volumeKey);
161
+ } catch (err) {
162
+ if (err instanceof Error) {
163
+ const messageStore = useMessageStore();
164
+ messageStore.addError('Failed to build volume(s)', {
165
+ details: `${err}. More details can be found in the developer's console.`,
166
+ });
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ async function loadFiles(files: File[]): Promise<LoadResult[]> {
173
+ let allFiles = await Promise.all(files.map((f) => retypeFile(f)));
174
+
175
+ // process any json manifests
176
+ const manifestFiles = allFiles.filter((file) => file.type === 'json');
177
+ allFiles = allFiles.filter((file) => file.type !== 'json');
178
+
179
+ const manifestStatuses: FileLoadResult[] = [];
180
+ if (manifestFiles.length) {
181
+ await Promise.all(
182
+ manifestFiles.map(async (file) => {
183
+ try {
184
+ allFiles.push(...(await readRemoteManifestFile(file)));
185
+ } catch (err) {
186
+ manifestStatuses.push(
187
+ makeFileFailureStatus(
188
+ file,
189
+ 'Failed to parse or download manifest'
190
+ )
191
+ );
192
+ }
193
+ })
194
+ );
195
+ }
196
+
197
+ // process archives
198
+ const fileEntries = await extractArchivesRecursively(allFiles);
199
+ allFiles = fileEntries.map((entry) => entry.file);
200
+
201
+ const dicoms = allFiles.filter(({ type }) => type === 'dcm');
202
+ const otherFiles = allFiles.filter(({ type }) => type !== 'dcm');
203
+
204
+ const dicomStatus = dicomStore
205
+ .importFiles(dicoms)
206
+ .then((volumeKeys) =>
207
+ volumeKeys.map((volKey) => makeDICOMSuccessStatus(volKey.volumeKey))
208
+ )
209
+ .catch((err) => [makeDICOMFailureStatus(err)]);
210
+
211
+ const otherStatuses = Promise.all([
212
+ ...otherFiles.map(async (file) => {
213
+ const reader = FILE_READERS.get(file.type);
214
+ if (reader) {
215
+ try {
216
+ const dataObj = await reader(file);
217
+ if (dataObj.isA('vtkImageData')) {
218
+ const id = imageStore.addVTKImageData(
219
+ file.name,
220
+ dataObj as vtkImageData
221
+ );
222
+ fileStore.add(id, [file]);
223
+
224
+ return makeFileSuccessStatus(file, 'image', id);
225
+ }
226
+ if (dataObj.isA('vtkPolyData')) {
227
+ const id = modelStore.addVTKPolyData(
228
+ file.name,
229
+ dataObj as vtkPolyData
230
+ );
231
+ fileStore.add(id, [file]);
232
+
233
+ return makeFileSuccessStatus(file, 'model', id);
234
+ }
235
+ return makeFileFailureStatus(
236
+ file,
237
+ `${file.name} did not result in a valid dataset`
238
+ );
239
+ } catch (e) {
240
+ return makeFileFailureStatus(
241
+ file,
242
+ `Reading ${file.name} gave an error: ${e}`
243
+ );
244
+ }
245
+ }
246
+ // indicate an error has occurred
247
+ return makeFileFailureStatus(file, `No reader for ${file.name}`);
248
+ }),
249
+ ]);
250
+
251
+ const statuses = [...(await dicomStatus), ...(await otherStatuses)];
252
+ return statuses;
253
+ }
254
+
255
+ async function serialize(stateFile: StateFile) {
256
+ await dicomStore.serialize(stateFile);
257
+ await imageStore.serialize(stateFile);
258
+
259
+ if (primarySelection.value !== null) {
260
+ let id = null;
261
+ if (primarySelection.value.type === 'dicom') {
262
+ id = primarySelection.value.volumeKey;
263
+ } else {
264
+ id = primarySelection.value.dataID;
265
+ }
266
+
267
+ const { manifest } = stateFile;
268
+ manifest.primarySelection = id;
269
+ }
270
+ }
271
+
272
+ async function deserialize(dataSet: DataSet, files: File[]) {
273
+ // dicom
274
+ if (dataSet.type === DataSetType.DICOM) {
275
+ return dicomStore
276
+ .deserialize(dataSet, files)
277
+ .then((volumeKey) => makeDICOMSuccessStatus(volumeKey))
278
+ .catch((err) => makeDICOMFailureStatus(err));
279
+ }
280
+
281
+ // image
282
+ if (files.length !== 1) {
283
+ throw new Error('Invalid state file.');
284
+ }
285
+ const file = files[0];
286
+
287
+ return imageStore
288
+ .deserialize(dataSet, file)
289
+ .then((dataID) => makeFileSuccessStatus(file, 'image', dataID))
290
+ .catch((err) =>
291
+ makeFileFailureStatus(
292
+ file.name,
293
+ `Reading ${file.name} gave an error: ${err}`
294
+ )
295
+ );
296
+ }
297
+
298
+ // --- watch for deletion --- //
299
+
300
+ imageStore.$onAction(({ name, args, after }) => {
301
+ if (name !== 'deleteData') {
302
+ return;
303
+ }
304
+ after(() => {
305
+ const [id] = args;
306
+ const sel = primarySelection.value;
307
+ if (sel?.type === 'image' && sel.dataID === id) {
308
+ primarySelection.value = null;
309
+ }
310
+ // remove file store entry
311
+ fileStore.remove(id);
312
+ });
313
+ });
314
+
315
+ dicomStore.$onAction(({ name, args, after }) => {
316
+ if (name !== 'deleteVolume') {
317
+ return;
318
+ }
319
+ after(() => {
320
+ const [volumeKey] = args;
321
+ const sel = primarySelection.value;
322
+ if (sel?.type === 'dicom' && volumeKey === sel.volumeKey) {
323
+ primarySelection.value = null;
324
+ }
325
+
326
+ // remove file store entry
327
+ fileStore.remove(volumeKey);
328
+ });
329
+ });
330
+
331
+ return {
332
+ primarySelection,
333
+ primaryDataset,
334
+ allDataIDs,
335
+ getDataProxyByID,
336
+ setPrimarySelection,
337
+ loadFiles,
338
+ serialize,
339
+ deserialize,
340
+ };
341
+ });