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,74 @@
1
+ import { computed } from '@vue/composition-api';
2
+ import { useDatasetStore } from '../store/datasets';
3
+ import { useDICOMStore } from '../store/datasets-dicom';
4
+ import { defaultImageMetadata, useImageStore } from '../store/datasets-images';
5
+ import { createLPSBounds, getAxisBounds } from '../utils/lps';
6
+
7
+ // Returns a spatially inflated image extent
8
+ export function getImageSpatialExtent(imageID: string | null) {
9
+ const imageStore = useImageStore();
10
+
11
+ if (imageID && imageID in imageStore.metadata) {
12
+ const { lpsOrientation } = imageStore.metadata[imageID];
13
+ const image = imageStore.dataIndex[imageID];
14
+ if (image) {
15
+ const extent = image.getSpatialExtent();
16
+ return {
17
+ Sagittal: getAxisBounds(extent, 'Sagittal', lpsOrientation),
18
+ Coronal: getAxisBounds(extent, 'Coronal', lpsOrientation),
19
+ Axial: getAxisBounds(extent, 'Axial', lpsOrientation),
20
+ };
21
+ }
22
+ }
23
+ return createLPSBounds();
24
+ }
25
+
26
+ export function useCurrentImage() {
27
+ const dataStore = useDatasetStore();
28
+ const dicomStore = useDICOMStore();
29
+ const imageStore = useImageStore();
30
+
31
+ const currentImageID = computed(() => {
32
+ const { primarySelection } = dataStore;
33
+ const { volumeToImageID } = dicomStore;
34
+
35
+ if (primarySelection?.type === 'image') {
36
+ return primarySelection.dataID;
37
+ }
38
+ if (primarySelection?.type === 'dicom') {
39
+ return volumeToImageID[primarySelection.volumeKey] || null;
40
+ }
41
+ return null;
42
+ });
43
+
44
+ const currentImageMetadata = computed(() => {
45
+ const { metadata } = imageStore;
46
+ const imageID = currentImageID.value;
47
+
48
+ if (imageID) {
49
+ return metadata[imageID];
50
+ }
51
+ return defaultImageMetadata();
52
+ });
53
+
54
+ const currentImageData = computed(() => {
55
+ // assumed to be only images for now
56
+ return dataStore.primaryDataset;
57
+ });
58
+
59
+ const currentImageExtent = computed(() =>
60
+ getImageSpatialExtent(currentImageID.value)
61
+ );
62
+
63
+ const isImageLoading = computed(() => {
64
+ return !!dataStore.primarySelection && !dataStore.primaryDataset;
65
+ });
66
+
67
+ return {
68
+ currentImageData,
69
+ currentImageID,
70
+ currentImageMetadata,
71
+ currentImageExtent,
72
+ isImageLoading,
73
+ };
74
+ }
@@ -0,0 +1,43 @@
1
+ import { Vector3 } from '@kitware/vtk.js/types';
2
+ import { computed } from '@vue/composition-api';
3
+ import { MaybeRef } from '@vueuse/core';
4
+ import { useViewConfigStore } from '../store/view-configs';
5
+ import { getLPSAxisFromDir } from '../utils/lps';
6
+ import { useCurrentImage } from './useCurrentImage';
7
+
8
+ /**
9
+ * Returns information about the current slice.
10
+ *
11
+ * axisName: the name of the axis
12
+ * axisIndex: corresponding index in an LPS coordinate array
13
+ * number: slice value
14
+ * planeNormal: slice plane normal
15
+ * planeOrigin: slice plane origin
16
+ * @param viewID
17
+ */
18
+ export function useCurrentSlice(viewID: MaybeRef<string | null>) {
19
+ const viewConfigStore = useViewConfigStore();
20
+ const currentImage = useCurrentImage();
21
+ const { currentImageMetadata } = currentImage;
22
+ const sliceConfig = viewConfigStore.getComputedSliceConfig(
23
+ viewID,
24
+ currentImage.currentImageID
25
+ );
26
+ return computed(() => {
27
+ const config = sliceConfig.value;
28
+ const { lpsOrientation } = currentImageMetadata.value;
29
+ if (config) {
30
+ const axis = getLPSAxisFromDir(config.axisDirection);
31
+ const planeOrigin = [0, 0, 0] as Vector3;
32
+ planeOrigin[lpsOrientation[axis]] = config.slice;
33
+ return {
34
+ axisName: axis,
35
+ axisIndex: lpsOrientation[axis],
36
+ number: config.slice,
37
+ planeNormal: lpsOrientation[config.axisDirection] as Vector3,
38
+ planeOrigin,
39
+ };
40
+ }
41
+ return null;
42
+ });
43
+ }
@@ -0,0 +1,77 @@
1
+ import { computed, del, reactive, set, unref } from '@vue/composition-api';
2
+ import { MaybeRef } from '@vueuse/core';
3
+
4
+ export function useDoubleRecord<V>() {
5
+ type Map1 = Record<string, Record<string, V>>;
6
+ type Map2 = Record<string, string>;
7
+ const reactiveRoot = reactive<{ map: Map1 }>({
8
+ map: {},
9
+ });
10
+
11
+ const _set = (k1: string, k2: string, v: V) => {
12
+ if (!(k1 in reactiveRoot.map)) {
13
+ set(reactiveRoot.map, k1, reactive<Map2>({}));
14
+ }
15
+ set(reactiveRoot.map[k1], k2, v);
16
+ };
17
+
18
+ const get = (k1: string, k2: string) => {
19
+ return reactiveRoot.map[k1]?.[k2] ?? undefined;
20
+ };
21
+
22
+ const getComputed = (
23
+ k1: MaybeRef<string | null | undefined>,
24
+ k2: MaybeRef<string | null | undefined>
25
+ ) => {
26
+ return computed(() => {
27
+ const k1v = unref(k1);
28
+ const k2v = unref(k2);
29
+ const { map } = reactiveRoot;
30
+ if (!k1v || !k2v) return undefined;
31
+ return map[k1v]?.[k2v];
32
+ });
33
+ };
34
+
35
+ const has = (k1: string, k2: string) => {
36
+ const { map } = reactiveRoot;
37
+ return k1 in map && k2 in map[k1];
38
+ };
39
+
40
+ const hasComputed = (
41
+ k1: MaybeRef<string | null | undefined>,
42
+ k2: MaybeRef<string | null | undefined>
43
+ ) => {
44
+ return computed(() => {
45
+ const k1v = unref(k1);
46
+ const k2v = unref(k2);
47
+ const { map } = reactiveRoot;
48
+ if (!k1v || !k2v) return undefined;
49
+ return k1v in map && k2v in map[k1v];
50
+ });
51
+ };
52
+
53
+ const _delete = (k1: string, k2: string) => {
54
+ if (k1 in reactiveRoot.map) {
55
+ del(reactiveRoot.map[k1], k2);
56
+ }
57
+ };
58
+
59
+ const deleteFirstKey = (k1: string) => {
60
+ del(reactiveRoot.map, k1);
61
+ };
62
+
63
+ const deleteSecondKey = (k2: string) => {
64
+ Object.keys(reactiveRoot.map).forEach((k1) => _delete(k1, k2));
65
+ };
66
+
67
+ return {
68
+ set: _set,
69
+ get,
70
+ getComputed,
71
+ has,
72
+ hasComputed,
73
+ delete: _delete,
74
+ deleteFirstKey,
75
+ deleteSecondKey,
76
+ };
77
+ }
@@ -0,0 +1,26 @@
1
+ import { onUnmounted, Ref, watch } from '@vue/composition-api';
2
+ import { NOOP } from '../constants';
3
+
4
+ export function useEventListener(
5
+ targetRef: Ref<HTMLElement | null | undefined>,
6
+ eventName: string,
7
+ listener: (evt: Event) => void
8
+ ) {
9
+ let cleanup = NOOP;
10
+
11
+ watch(
12
+ targetRef,
13
+ (target) => {
14
+ cleanup();
15
+ cleanup = NOOP;
16
+
17
+ if (target) {
18
+ target.addEventListener(eventName, listener);
19
+ cleanup = () => target.removeEventListener(eventName, listener);
20
+ }
21
+ },
22
+ { immediate: true, flush: 'post' }
23
+ );
24
+
25
+ onUnmounted(cleanup);
26
+ }
@@ -0,0 +1,18 @@
1
+ import { onBeforeUnmount, onMounted } from '@vue/composition-api';
2
+ import { useMessageStore } from '../store/messages';
3
+
4
+ export function useGlobalErrorHook() {
5
+ const messageStore = useMessageStore();
6
+
7
+ const onError = (event: ErrorEvent) => {
8
+ messageStore.addError('Application error (click for details)', event.error);
9
+ };
10
+
11
+ onMounted(() => {
12
+ window.addEventListener('error', onError);
13
+ });
14
+
15
+ onBeforeUnmount(() => {
16
+ window.removeEventListener('error', onError);
17
+ });
18
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ computed,
3
+ Ref,
4
+ ref,
5
+ UnwrapRefSimple,
6
+ watch,
7
+ } from '@vue/composition-api';
8
+
9
+ export function useMultiSelection<T = string>(allItems: Ref<T[]>) {
10
+ const selected = ref<T[]>([]);
11
+ const selectedAll = ref(false);
12
+ const selectedSome = computed(() => selected.value.length > 0);
13
+
14
+ watch(selected, () => {
15
+ selectedAll.value =
16
+ selected.value.length > 0 &&
17
+ selected.value.length === allItems.value.length;
18
+ });
19
+
20
+ watch(selectedAll, (yn) => {
21
+ if (yn) {
22
+ selected.value = allItems.value as UnwrapRefSimple<T[]>;
23
+ } else {
24
+ selected.value = [];
25
+ }
26
+ });
27
+
28
+ return { selected, selectedAll, selectedSome };
29
+ }
@@ -0,0 +1,62 @@
1
+ import { computed, Ref, ref } from '@vue/composition-api';
2
+ import { vec3 } from 'gl-matrix';
3
+ import { Vector3 } from '@kitware/vtk.js/types';
4
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
5
+ import { EPSILON } from '../constants';
6
+ import { useVTKCallback } from './useVTKCallback';
7
+
8
+ export function toOrderedLabels(vec: Vector3) {
9
+ return (
10
+ vec
11
+ .map((v, i) => ({ value: v, axis: i }))
12
+ // remove components close to zero, since we won't show those labels
13
+ .filter(({ value }) => Math.abs(value) > EPSILON)
14
+ // sort from largest to smallest component
15
+ .sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
16
+ // apply labels
17
+ .map(({ value, axis }) => (Math.sign(value) === 1 ? 'LPS' : 'RAI')[axis])
18
+ .join('')
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Computes orientation labels for a given view.
24
+ *
25
+ * Labels are with respect to the current camera orientation.
26
+ *
27
+ * vtk.js coordinate coordinate is implied to be LPS, so orientation labels
28
+ * are determined solely from the camera' direction and view-up.
29
+ */
30
+ export function useOrientationLabels(view: Ref<vtkViewProxy>) {
31
+ const camera = computed(() => view.value.getCamera());
32
+
33
+ const top = ref('');
34
+ const left = ref('');
35
+ const bottom = ref('');
36
+ const right = ref('');
37
+
38
+ function updateAxes() {
39
+ const vup = camera.value.getViewUp();
40
+ const vdir = camera.value.getDirectionOfProjection();
41
+ const vright = [0, 0, 0] as Vector3;
42
+
43
+ // vup and vdir should not be parallel for cameras
44
+ vec3.cross(vright, vdir, vup);
45
+
46
+ const vbottom = vup.map((c) => c * -1) as Vector3;
47
+ const vleft = vright.map((c) => c * -1) as Vector3;
48
+
49
+ top.value = toOrderedLabels(vup);
50
+ right.value = toOrderedLabels(vright);
51
+ bottom.value = toOrderedLabels(vbottom);
52
+ left.value = toOrderedLabels(vleft);
53
+ }
54
+
55
+ const cameraOnModified = useVTKCallback(
56
+ computed(() => camera.value.onModified)
57
+ );
58
+ cameraOnModified(updateAxes);
59
+ updateAxes();
60
+
61
+ return { top, right, bottom, left };
62
+ }
@@ -0,0 +1,42 @@
1
+ import { computed, ref, Ref, watch } from '@vue/composition-api';
2
+
3
+ export interface Panel {
4
+ key: string;
5
+ isOpen: boolean;
6
+ }
7
+
8
+ export function usePanels(keys: Ref<Set<string>>) {
9
+ const panels = ref<Panel[]>([]);
10
+
11
+ watch(
12
+ keys,
13
+ (newKeys, oldKeys) => {
14
+ // remove deleted
15
+ panels.value = panels.value.filter(({ key }) => newKeys.has(key));
16
+
17
+ const addedKeys = [...newKeys].filter((key) => !oldKeys?.has(key));
18
+ // order in temporal created order, not template order. See https://github.com/vuetifyjs/vuetify/issues/11225
19
+ panels.value = [
20
+ ...panels.value,
21
+ ...addedKeys.map((key) => ({ key, isOpen: true })),
22
+ ];
23
+ },
24
+ // flush: 'post' keeps panels open when deleting patients
25
+ // because panels.value has the same number panels in v-expansion-panels after flush
26
+ { flush: 'post', immediate: true }
27
+ );
28
+
29
+ const handlePanelChange = (changeKey: string) => {
30
+ const panel = panels.value.find(({ key }) => key === changeKey);
31
+ panel!.isOpen = !panel!.isOpen;
32
+ };
33
+
34
+ const openPanels: Ref<number[]> = computed(() => {
35
+ return panels.value
36
+ .map(({ isOpen }, idx) => ({ isOpen, idx }))
37
+ .filter(({ isOpen }) => isOpen)
38
+ .map(({ idx }) => idx);
39
+ });
40
+
41
+ return { handlePanelChange, openPanels };
42
+ }
@@ -0,0 +1,138 @@
1
+ import { manageVTKSubscription } from '@src/composables/manageVTKSubscription';
2
+ import { Ref } from '@vue/composition-api';
3
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
4
+ import { useViewConfigStore } from '../store/view-configs';
5
+ import { CameraConfig } from '../store/view-configs/types';
6
+
7
+ export function usePersistCameraConfig(
8
+ viewID: Ref<string>,
9
+ dataID: Ref<string | null>,
10
+ viewProxy: Ref<vtkViewProxy>,
11
+ ...toPersist: (keyof CameraConfig)[]
12
+ ) {
13
+ const viewConfigStore = useViewConfigStore();
14
+ let persistCameraConfig = true;
15
+
16
+ // We setup this list of functions to avoid if and indexOf in the onModified
17
+ // call.
18
+ const persist: (() => void)[] = [];
19
+
20
+ if (toPersist.indexOf('position') > -1) {
21
+ persist.push(() => {
22
+ if (dataID.value !== null && persistCameraConfig) {
23
+ viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
24
+ position: viewProxy.value.getCamera().getPosition(),
25
+ });
26
+ }
27
+ });
28
+ }
29
+ if (toPersist.indexOf('viewUp') > -1) {
30
+ persist.push(() => {
31
+ if (dataID.value !== null && persistCameraConfig) {
32
+ viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
33
+ viewUp: viewProxy.value.getCamera().getViewUp(),
34
+ });
35
+ }
36
+ });
37
+ }
38
+ if (toPersist.indexOf('focalPoint') > -1) {
39
+ persist.push(() => {
40
+ if (dataID.value !== null && persistCameraConfig) {
41
+ viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
42
+ focalPoint: viewProxy.value.getCamera().getFocalPoint(),
43
+ });
44
+ }
45
+ });
46
+ }
47
+ if (toPersist.indexOf('directionOfProjection') > -1) {
48
+ persist.push(() => {
49
+ if (dataID.value !== null && persistCameraConfig) {
50
+ viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
51
+ directionOfProjection: viewProxy.value
52
+ .getCamera()
53
+ .getDirectionOfProjection(),
54
+ });
55
+ }
56
+ });
57
+ }
58
+ if (toPersist.indexOf('parallelScale') > -1) {
59
+ persist.push(() => {
60
+ if (dataID.value !== null && persistCameraConfig) {
61
+ viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
62
+ parallelScale: viewProxy.value.getCamera().getParallelScale(),
63
+ });
64
+ }
65
+ });
66
+ }
67
+
68
+ manageVTKSubscription(
69
+ viewProxy.value.getCamera().onModified(() => {
70
+ persist.forEach((persistFunc) => persistFunc());
71
+ })
72
+ );
73
+
74
+ function blockPersistingCameraConfig(func: () => void) {
75
+ persistCameraConfig = false;
76
+ try {
77
+ func();
78
+ } finally {
79
+ persistCameraConfig = true;
80
+ }
81
+ }
82
+
83
+ function restoreCameraConfig(cameraConfig: CameraConfig) {
84
+ blockPersistingCameraConfig(() => {
85
+ toPersist.forEach((key: keyof CameraConfig) => {
86
+ // Parallel scale
87
+ if (key === 'parallelScale' && cameraConfig.parallelScale) {
88
+ viewProxy.value
89
+ .getCamera()
90
+ .setParallelScale(cameraConfig.parallelScale);
91
+ }
92
+ // Position
93
+ else if (key === 'position' && cameraConfig.position) {
94
+ const { position } = cameraConfig;
95
+ viewProxy.value
96
+ .getCamera()
97
+ .setPosition(position[0], position[1], position[2]);
98
+ }
99
+ // Focal point
100
+ else if (key === 'focalPoint' && cameraConfig.focalPoint) {
101
+ const { focalPoint } = cameraConfig;
102
+ viewProxy.value
103
+ .getCamera()
104
+ .setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
105
+ viewProxy.value
106
+ .getInteractorStyle2D()
107
+ .setCenterOfRotation([...focalPoint]);
108
+ viewProxy.value
109
+ .getInteractorStyle3D()
110
+ .setCenterOfRotation([...focalPoint]);
111
+ }
112
+ // Direction of projection
113
+ else if (
114
+ key === 'directionOfProjection' &&
115
+ cameraConfig.directionOfProjection
116
+ ) {
117
+ const { directionOfProjection } = cameraConfig;
118
+ viewProxy.value
119
+ .getCamera()
120
+ .setDirectionOfProjection(
121
+ directionOfProjection[0],
122
+ directionOfProjection[1],
123
+ directionOfProjection[2]
124
+ );
125
+ }
126
+ // View up
127
+ else if (key === 'viewUp' && cameraConfig.viewUp) {
128
+ const { viewUp } = cameraConfig;
129
+ viewProxy.value
130
+ .getCamera()
131
+ .setViewUp(viewUp[0], viewUp[1], viewUp[2]);
132
+ }
133
+ });
134
+ });
135
+ }
136
+
137
+ return { restoreCameraConfig };
138
+ }
@@ -0,0 +1,37 @@
1
+ import { onBeforeUnmount, Ref, unref, watch } from '@vue/composition-api';
2
+
3
+ /**
4
+ * Invokes a callback whenever an element is resized.
5
+ */
6
+ export function useResizeObserver(
7
+ targetElRef: Ref<Element | null | undefined>,
8
+ callback: (entry: ResizeObserverEntry) => void
9
+ ) {
10
+ const observer = new ResizeObserver((entries) => {
11
+ if (entries.length === 1) {
12
+ callback(entries[0]);
13
+ }
14
+ });
15
+
16
+ watch(
17
+ targetElRef,
18
+ (targetEl, prevTarget) => {
19
+ if (prevTarget) {
20
+ observer.unobserve(prevTarget);
21
+ }
22
+ if (targetEl) {
23
+ observer.observe(targetEl);
24
+ }
25
+ },
26
+ { immediate: true }
27
+ );
28
+
29
+ onBeforeUnmount(() => {
30
+ const targetEl = unref(targetElRef);
31
+ if (targetEl) {
32
+ observer.unobserve(targetEl);
33
+ }
34
+ });
35
+
36
+ return observer;
37
+ }
@@ -0,0 +1,50 @@
1
+ import { ref } from '@vue/composition-api';
2
+ import vtkCamera from '@kitware/vtk.js/Rendering/Core/Camera';
3
+ import { manageVTKSubscription } from '@src/composables/manageVTKSubscription';
4
+ import { vec3 } from 'gl-matrix';
5
+
6
+ export function useResizeToFit(
7
+ camera: vtkCamera,
8
+ initialValue: boolean = false
9
+ ) {
10
+ const resizeToFit = ref(initialValue);
11
+
12
+ let trackResizeToFit = true;
13
+ const cachedCameraInfo = {
14
+ position: [0, 0, 0] as vec3,
15
+ parallelScale: 0,
16
+ };
17
+
18
+ manageVTKSubscription(
19
+ camera.onModified(() => {
20
+ if (trackResizeToFit && resizeToFit.value) {
21
+ const position = camera.getPosition();
22
+ const parallelScale = camera.getParallelScale();
23
+ if (
24
+ !vec3.equals(position, cachedCameraInfo.position) ||
25
+ parallelScale !== cachedCameraInfo.parallelScale
26
+ ) {
27
+ resizeToFit.value = false;
28
+ }
29
+ }
30
+ })
31
+ );
32
+
33
+ function ignoreResizeToFitTracking(cb: () => void) {
34
+ if (trackResizeToFit) {
35
+ trackResizeToFit = false;
36
+ try {
37
+ cb();
38
+ } finally {
39
+ trackResizeToFit = true;
40
+ }
41
+ }
42
+ }
43
+
44
+ function resetResizeToFitTracking() {
45
+ cachedCameraInfo.position = camera.getPosition();
46
+ cachedCameraInfo.parallelScale = camera.getParallelScale();
47
+ }
48
+
49
+ return { resizeToFit, ignoreResizeToFitTracking, resetResizeToFitTracking };
50
+ }
@@ -0,0 +1,96 @@
1
+ import vtkAbstractRepresentationProxy from '@kitware/vtk.js/Proxy/Core/AbstractRepresentationProxy';
2
+ import { computed, Ref, watch } from '@vue/composition-api';
3
+ import { useViewStore } from '../store/views';
4
+ import { vtkLPSViewProxy } from '../types/vtk-types';
5
+
6
+ interface Scene {
7
+ baseImage?: Ref<string | null>;
8
+ labelmaps?: Ref<string[]>;
9
+ models?: Ref<string[]>;
10
+ }
11
+
12
+ type RepProxy = vtkAbstractRepresentationProxy;
13
+
14
+ export function useSceneBuilder<
15
+ BaseImageType extends RepProxy = RepProxy,
16
+ LabelMapType extends RepProxy = RepProxy,
17
+ ModelType extends RepProxy = RepProxy
18
+ >(viewID: Ref<string>, sceneIDs: Scene) {
19
+ const viewStore = useViewStore();
20
+ const viewProxy = computed(() =>
21
+ viewStore.getViewProxy<vtkLPSViewProxy>(viewID.value)
22
+ );
23
+
24
+ const baseImageRep = computed(() => {
25
+ const _viewID = viewID.value;
26
+ if (sceneIDs.baseImage) {
27
+ const imageID = sceneIDs.baseImage.value;
28
+ if (imageID) {
29
+ return viewStore.getDataRepresentationForView<BaseImageType>(
30
+ imageID,
31
+ _viewID
32
+ );
33
+ }
34
+ }
35
+ return null;
36
+ });
37
+
38
+ const labelmapReps = computed(() => {
39
+ const _viewID = viewID.value;
40
+ if (sceneIDs.labelmaps) {
41
+ const labelmapIDs = sceneIDs.labelmaps.value;
42
+ if (labelmapIDs) {
43
+ return labelmapIDs
44
+ .map((id) =>
45
+ viewStore.getDataRepresentationForView<LabelMapType>(id, _viewID)
46
+ )
47
+ .filter(Boolean) as LabelMapType[];
48
+ }
49
+ }
50
+ return [];
51
+ });
52
+
53
+ const modelReps = computed(() => {
54
+ const _viewID = viewID.value;
55
+ if (sceneIDs.models) {
56
+ const modelIDs = sceneIDs.models.value;
57
+ if (modelIDs) {
58
+ return modelIDs
59
+ .map((id) =>
60
+ viewStore.getDataRepresentationForView<ModelType>(id, _viewID)
61
+ )
62
+ .filter(Boolean) as ModelType[];
63
+ }
64
+ }
65
+ return [];
66
+ });
67
+
68
+ watch(
69
+ [viewProxy, baseImageRep, labelmapReps, modelReps],
70
+ ([view, baseRep, lmReps, mReps]) => {
71
+ if (!view) {
72
+ throw new Error('[useSceneBuilder] No view available');
73
+ }
74
+
75
+ view.removeAllRepresentations();
76
+
77
+ if (baseRep) {
78
+ // we control the color mapping range manually
79
+ baseRep.setRescaleOnColorBy(false);
80
+ view.addRepresentation(baseRep);
81
+ }
82
+ lmReps.forEach((rep) => view.addRepresentation(rep));
83
+ mReps.forEach((rep) => view.addRepresentation(rep));
84
+
85
+ // TODO not sure why I need this, but might as well keep
86
+ // the renderer up to date.
87
+ // For reference, this doesn't get invoked when resetting the
88
+ // camera with a supplied bounds, so we manually invoke it here.
89
+ view.getRenderer().computeVisiblePropBounds();
90
+ view.render();
91
+ },
92
+ { deep: true, immediate: true }
93
+ );
94
+
95
+ return { baseImageRep, labelmapReps, modelReps };
96
+ }