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,154 @@
1
+ <script lang="ts">
2
+ import { useRulerStore } from '@/src/store/tools/rulers';
3
+ import { vtkRulerViewWidget } from '@/src/vtk/RulerWidget';
4
+ import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
5
+ import {
6
+ computed,
7
+ defineComponent,
8
+ onBeforeUnmount,
9
+ onMounted,
10
+ PropType,
11
+ ref,
12
+ toRefs,
13
+ watch,
14
+ watchEffect,
15
+ } from '@vue/composition-api';
16
+ import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
17
+ import { useCurrentImage } from '@/src/composables/useCurrentImage';
18
+ import { updatePlaneManipulatorFor2DView } from '@/src/utils/manipulators';
19
+ import { vtkSubscription } from '@kitware/vtk.js/interfaces';
20
+ import { getCSSCoordinatesFromEvent } from '@/src/utils/vtk-helpers';
21
+ import { LPSAxisDir } from '@/src/types/lps';
22
+
23
+ export default defineComponent({
24
+ name: 'RulerWidget2D',
25
+ props: {
26
+ rulerId: {
27
+ type: String,
28
+ required: true,
29
+ },
30
+ widgetManager: {
31
+ type: Object as PropType<vtkWidgetManager>,
32
+ required: true,
33
+ },
34
+ viewId: {
35
+ type: String,
36
+ required: true,
37
+ },
38
+ viewDirection: {
39
+ type: String as PropType<LPSAxisDir>,
40
+ required: true,
41
+ },
42
+ slice: {
43
+ type: Number,
44
+ required: true,
45
+ },
46
+ focused: Boolean,
47
+ },
48
+ setup(props, { emit }) {
49
+ const {
50
+ rulerId: rulerIDRef,
51
+ widgetManager: widgetManagerRef,
52
+ viewDirection,
53
+ focused,
54
+ slice,
55
+ } = toRefs(props);
56
+ const rulerStore = useRulerStore();
57
+ const factoryRef = computed(() =>
58
+ rulerStore.getVTKFactory(rulerIDRef.value)
59
+ );
60
+ const widgetRef = ref<vtkRulerViewWidget | null>(null);
61
+ const rulerRef = computed(() => rulerStore.rulers[rulerIDRef.value]);
62
+
63
+ onMounted(() => {
64
+ if (!factoryRef.value) {
65
+ throw new Error(
66
+ `No widget exists for ruler with ID ${rulerIDRef.value}`
67
+ );
68
+ }
69
+ const widgetManager = widgetManagerRef.value;
70
+ const factory = factoryRef.value;
71
+
72
+ widgetRef.value = widgetManager.addWidget(factory) as vtkRulerViewWidget;
73
+ });
74
+
75
+ // --- right click handling --- //
76
+
77
+ let rightClickSub: vtkSubscription | null = null;
78
+
79
+ onMounted(() => {
80
+ rightClickSub = widgetRef.value!.onRightClickEvent((eventData) => {
81
+ const coords = getCSSCoordinatesFromEvent(eventData);
82
+ if (coords) {
83
+ emit('contextmenu', coords);
84
+ }
85
+ });
86
+ });
87
+
88
+ onBeforeUnmount(() => {
89
+ if (rightClickSub) {
90
+ rightClickSub.unsubscribe();
91
+ rightClickSub = null;
92
+ }
93
+ });
94
+
95
+ // --- manipulator --- //
96
+
97
+ const manipulator = vtkPlaneManipulator.newInstance();
98
+
99
+ onMounted(() => {
100
+ widgetRef.value!.setManipulator(manipulator);
101
+ });
102
+
103
+ const { currentImageMetadata } = useCurrentImage();
104
+
105
+ watchEffect(() => {
106
+ updatePlaneManipulatorFor2DView(
107
+ manipulator,
108
+ viewDirection.value,
109
+ rulerRef.value.slice ?? slice.value,
110
+ currentImageMetadata.value
111
+ );
112
+ });
113
+
114
+ // --- focus --- //
115
+
116
+ watchEffect(() => {
117
+ const widgetManager = widgetManagerRef.value;
118
+ const widget = widgetRef.value;
119
+ if (focused.value) {
120
+ widgetManager.grabFocus(widget!);
121
+ } else {
122
+ // TODO unfocus the specific widget; necessary?
123
+ }
124
+ });
125
+
126
+ onBeforeUnmount(() => {
127
+ const widgetManager = widgetManagerRef.value;
128
+ const widget = widgetRef.value;
129
+ widgetManager.removeWidget(widget!);
130
+ });
131
+
132
+ // --- visibility --- //
133
+
134
+ watch(
135
+ () => {
136
+ const rulerSlice = rulerRef.value.slice;
137
+ const curSlice = slice.value;
138
+ return !Number.isInteger(rulerSlice) || rulerSlice === curSlice;
139
+ },
140
+ (visible) => {
141
+ widgetRef.value!.setVisibility(visible);
142
+ }
143
+ );
144
+
145
+ onMounted(() => {
146
+ // hide handle visibility
147
+ widgetRef.value!.setHandleVisibility(false);
148
+ widgetManagerRef.value.renderWidgets();
149
+ });
150
+
151
+ return () => null;
152
+ },
153
+ });
154
+ </script>
@@ -0,0 +1,57 @@
1
+ import { expect } from 'chai';
2
+ import vueCompositionApi, { computed } from '@vue/composition-api';
3
+ import Vue from 'vue';
4
+ import { useDoubleRecord } from '../useDoubleRecord';
5
+
6
+ Vue.use(vueCompositionApi);
7
+
8
+ describe('useDoubleRecord', () => {
9
+ it('should work', () => {
10
+ type Config = {
11
+ propA: number;
12
+ };
13
+
14
+ const dr = useDoubleRecord<Config>();
15
+
16
+ const ab = dr.getComputed('a', 'b');
17
+ const abPropA = computed(() => ab.value?.propA);
18
+ const hasAB = dr.hasComputed('a', 'b');
19
+ expect(ab.value).to.be.undefined;
20
+ expect(abPropA.value).to.be.undefined;
21
+ expect(hasAB.value).to.be.false;
22
+
23
+ dr.set('a', 'b', {
24
+ propA: 43,
25
+ });
26
+
27
+ expect(ab.value).to.not.be.undefined;
28
+ expect(hasAB.value).to.be.true;
29
+ expect(abPropA.value).to.equal(43);
30
+
31
+ dr.set('a', 'b', {
32
+ ...ab.value!,
33
+ propA: 11,
34
+ });
35
+ expect(abPropA.value).to.equal(11);
36
+
37
+ dr.deleteSecondKey('b');
38
+
39
+ expect(ab.value).to.be.undefined;
40
+ expect(abPropA.value).to.be.undefined;
41
+ expect(hasAB.value).to.be.false;
42
+
43
+ dr.set('a', 'b', {
44
+ propA: 9,
45
+ });
46
+
47
+ expect(hasAB.value).to.be.true;
48
+
49
+ dr.deleteFirstKey('a');
50
+
51
+ expect(hasAB.value).to.be.false;
52
+
53
+ expect(() => {
54
+ dr.delete('nonexistent', 'nonexistent');
55
+ }).to.not.throw;
56
+ });
57
+ });
@@ -0,0 +1,29 @@
1
+ import { expect } from 'chai';
2
+
3
+ import { toOrderedLabels } from '@src/composables/useOrientationLabels';
4
+
5
+ const SQRT1_3 = 1 / Math.sqrt(3);
6
+
7
+ describe('toOrderedLabels', () => {
8
+ it("correctly orders a vector's LPS labels", () => {
9
+ type Cases = Array<[[number, number, number], string]>;
10
+ const cases: Cases = [
11
+ [[1, 0, 0], 'L'],
12
+ [[0, 1, 0], 'P'],
13
+ [[0, 0, 1], 'S'],
14
+ [[Math.SQRT1_2, Math.SQRT1_2, 0], 'LP'],
15
+ [[Math.SQRT1_2, -Math.SQRT1_2, 0], 'LA'],
16
+ [[0, Math.SQRT1_2, Math.SQRT1_2], 'PS'],
17
+ [[0, Math.SQRT1_2, -Math.SQRT1_2], 'PI'],
18
+ [[Math.SQRT1_2, 0, Math.SQRT1_2], 'LS'],
19
+ [[-Math.SQRT1_2, 0, Math.SQRT1_2], 'RS'],
20
+ [[SQRT1_3, SQRT1_3, SQRT1_3], 'LPS'],
21
+ [[-SQRT1_3, SQRT1_3, -SQRT1_3], 'RPI'],
22
+ [[0.06, 0.989, 0.128], 'PSL'],
23
+ ];
24
+
25
+ cases.forEach(([vector, expected]) =>
26
+ expect(toOrderedLabels(vector)).to.equal(expected)
27
+ );
28
+ });
29
+ });
@@ -0,0 +1,25 @@
1
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
2
+ import { computed, ref, unref } from '@vue/composition-api';
3
+ import { MaybeRef } from '@vueuse/core';
4
+ import { useVTKCallback } from './useVTKCallback';
5
+
6
+ export function isViewAnimating(viewProxy: MaybeRef<vtkViewProxy>) {
7
+ const isAnimating = ref(false);
8
+
9
+ const onStartAnimation = useVTKCallback(
10
+ computed(() => unref(viewProxy).getInteractor().onStartAnimation)
11
+ );
12
+ const onEndAnimation = useVTKCallback(
13
+ computed(() => unref(viewProxy).getInteractor().onEndAnimation)
14
+ );
15
+
16
+ onStartAnimation(() => {
17
+ isAnimating.value = true;
18
+ });
19
+
20
+ onEndAnimation(() => {
21
+ isAnimating.value = false;
22
+ });
23
+
24
+ return isAnimating;
25
+ }
@@ -0,0 +1,6 @@
1
+ import { onUnmounted } from '@vue/composition-api';
2
+ import { vtkSubscription } from '@kitware/vtk.js/interfaces';
3
+
4
+ export function manageVTKSubscription(subscription: vtkSubscription) {
5
+ onUnmounted(() => subscription.unsubscribe());
6
+ }
@@ -0,0 +1,50 @@
1
+ import { vtkSubscription } from '@kitware/vtk.js/interfaces';
2
+ import { onBeforeUnmount } from '@vue/composition-api';
3
+ import { withProxyManager } from './proxyManager';
4
+ import vtkProxyObject from '../types/vtk-types';
5
+
6
+ export enum ProxyManagerEvent {
7
+ ProxyCreated,
8
+ ProxyModified,
9
+ ProxyDeleted,
10
+ ProxyRegistrationChange,
11
+ }
12
+
13
+ export function onProxyManagerEvent(
14
+ event: ProxyManagerEvent,
15
+ cb: (proxyID: string, obj: vtkProxyObject | null) => void
16
+ ) {
17
+ const subs: vtkSubscription[] = [];
18
+
19
+ withProxyManager((proxyManager) => {
20
+ const proxySubs = Object.create(null);
21
+
22
+ subs.push(
23
+ proxyManager.onProxyRegistrationChange((info) => {
24
+ const { action, proxyId, proxy } = info;
25
+ if (action === 'register') {
26
+ if (event === ProxyManagerEvent.ProxyCreated) {
27
+ cb(proxyId, proxy);
28
+ }
29
+ if (event === ProxyManagerEvent.ProxyModified) {
30
+ proxySubs[proxyId] = proxy.onModified(() => cb(proxyId, proxy));
31
+ }
32
+ } else if (action === 'unregister') {
33
+ if (proxyId in proxySubs) {
34
+ proxySubs[proxyId].unsubscribe();
35
+ delete proxySubs[proxyId];
36
+ }
37
+ if (event === ProxyManagerEvent.ProxyDeleted) {
38
+ cb(proxyId, null);
39
+ }
40
+ }
41
+ })
42
+ );
43
+ });
44
+
45
+ onBeforeUnmount(() => {
46
+ while (subs.length) {
47
+ subs.pop()!.unsubscribe();
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,13 @@
1
+ import { inject } from '@vue/composition-api';
2
+ import vtkProxyManager from '@kitware/vtk.js/Proxy/Core/ProxyManager';
3
+
4
+ const defaultKey = 'ProxyManager';
5
+
6
+ export function withProxyManager(cb: (pxm: vtkProxyManager) => void) {
7
+ const pxm = inject<vtkProxyManager>('ProxyManager');
8
+ return pxm ? cb(pxm) : null;
9
+ }
10
+
11
+ export function useProxyManager(key = defaultKey) {
12
+ return inject<vtkProxyManager>(key ?? defaultKey);
13
+ }
@@ -0,0 +1,41 @@
1
+ import Vue from 'vue';
2
+ import VueCompositionApi, { ref } from '@vue/composition-api';
3
+ import { expect } from 'chai';
4
+ import { createPrimitiveSyncContext } from '../createPrimitiveSyncContext';
5
+
6
+ describe('createPrimitiveSyncContext', () => {
7
+ before(() => {
8
+ Vue.use(VueCompositionApi);
9
+ });
10
+
11
+ it('should work', () => {
12
+ const useSync = createPrimitiveSyncContext(0);
13
+
14
+ const var1 = ref(0);
15
+ const var2 = ref(0);
16
+ const key1 = ref('key1');
17
+ const key2 = ref('key1');
18
+
19
+ useSync(key1, var1);
20
+ useSync(key2, var2);
21
+
22
+ expect(var1.value).to.equal(0);
23
+ expect(var2.value).to.equal(0);
24
+
25
+ var1.value = 2;
26
+
27
+ expect(var1.value).to.equal(2);
28
+ expect(var2.value).to.equal(2);
29
+
30
+ var2.value = 4;
31
+
32
+ expect(var1.value).to.equal(4);
33
+ expect(var2.value).to.equal(4);
34
+
35
+ key2.value = 'key2';
36
+ var2.value = 6;
37
+
38
+ expect(var1.value).to.equal(4);
39
+ expect(var2.value).to.equal(6);
40
+ });
41
+ });
@@ -0,0 +1,51 @@
1
+ import Vue from 'vue';
2
+ import VueCompositionApi, { computed, ref } from '@vue/composition-api';
3
+ import { WindowLevelConfig } from '@/src/store/view-configs/types';
4
+ import Sinon from 'sinon';
5
+ import chai, { expect } from 'chai';
6
+ import SinonChai from 'sinon-chai';
7
+ import { useWindowingSync } from '../useWindowingSync';
8
+
9
+ chai.use(SinonChai);
10
+
11
+ function makeContext(k: string) {
12
+ const setSpy = Sinon.spy();
13
+ const windowing = ref<WindowLevelConfig>({
14
+ width: 1,
15
+ level: 0,
16
+ min: 0,
17
+ max: 10,
18
+ });
19
+ const key = ref(k);
20
+ const source = computed({
21
+ get: () => windowing.value,
22
+ set: (newValue) => {
23
+ setSpy(newValue);
24
+ windowing.value = newValue;
25
+ },
26
+ });
27
+
28
+ return { windowing, key, source, setSpy };
29
+ }
30
+
31
+ describe('useWindowingSync', () => {
32
+ before(() => {
33
+ Vue.use(VueCompositionApi);
34
+ });
35
+ it('should work', () => {
36
+ const ctxt1 = makeContext('abc');
37
+ const ctxt2 = makeContext('abc');
38
+
39
+ useWindowingSync(ctxt1.key, ctxt1.source);
40
+ useWindowingSync(ctxt2.key, ctxt2.source);
41
+
42
+ const newValue = {
43
+ ...ctxt1.windowing.value,
44
+ level: 4,
45
+ };
46
+ ctxt1.windowing.value = newValue;
47
+
48
+ expect(ctxt2.setSpy).to.have.been.calledWith(newValue);
49
+ expect(ctxt2.windowing.value.level).to.equal(newValue.level);
50
+ });
51
+ });
@@ -0,0 +1,83 @@
1
+ import { ref, Ref, UnwrapRef, watch } from '@vue/composition-api';
2
+ import { WatchOptions } from 'vue';
3
+
4
+ /**
5
+ * immediate: sync from sync state to valueRef immediately.
6
+ */
7
+ type SyncOptions = {
8
+ immediate: boolean;
9
+ watchOptions?: WatchOptions;
10
+ };
11
+
12
+ /**
13
+ * Only tested on primitive values.
14
+ */
15
+ export function createPrimitiveSyncContext<T, K>(initialState: T) {
16
+ const states = new Map<K, Ref<UnwrapRef<T>>>();
17
+ const usage = new Map<K, number>();
18
+
19
+ const register = (key: K, valueRef: Ref<T>, options?: SyncOptions) => {
20
+ if (!states.has(key)) {
21
+ states.set(key, ref(initialState));
22
+ }
23
+ const state = states.get(key)!;
24
+ usage.set(key, 1 + (usage.get(key) ?? 0));
25
+
26
+ const stopTo = watch(
27
+ valueRef,
28
+ (newValue) => {
29
+ if (newValue !== state.value) {
30
+ state.value = newValue as UnwrapRef<T>;
31
+ }
32
+ },
33
+ {
34
+ flush: 'sync',
35
+ immediate: true,
36
+ ...options?.watchOptions,
37
+ }
38
+ );
39
+
40
+ const stopFrom = watch(
41
+ state,
42
+ (newState) => {
43
+ if (newState !== valueRef.value) {
44
+ /* eslint-disable-next-line no-param-reassign */
45
+ valueRef.value = newState as T;
46
+ }
47
+ },
48
+ {
49
+ flush: 'sync',
50
+ immediate: true,
51
+ ...options?.watchOptions,
52
+ }
53
+ );
54
+
55
+ return () => {
56
+ stopTo();
57
+ stopFrom();
58
+
59
+ usage.set(key, usage.get(key)! - 1);
60
+ if (usage.get(key) === 0) {
61
+ usage.delete(key);
62
+ states.delete(key);
63
+ }
64
+ };
65
+ };
66
+
67
+ const useSync = (key: Ref<K>, valueRef: Ref<T>, options?: SyncOptions) => {
68
+ let cleanup: (() => void) | null = null;
69
+ watch(
70
+ key,
71
+ (newKey) => {
72
+ if (cleanup) cleanup();
73
+ cleanup = register(newKey, valueRef, options);
74
+ },
75
+ {
76
+ flush: 'sync',
77
+ immediate: true,
78
+ }
79
+ );
80
+ };
81
+
82
+ return useSync;
83
+ }
@@ -0,0 +1,48 @@
1
+ import { WindowLevelConfig } from '@/src/store/view-configs/types';
2
+ import {
3
+ computed,
4
+ Ref,
5
+ triggerRef,
6
+ WritableComputedRef,
7
+ } from '@vue/composition-api';
8
+ import { createPrimitiveSyncContext } from './createPrimitiveSyncContext';
9
+
10
+ function generateComputed(
11
+ source: WritableComputedRef<WindowLevelConfig | null>,
12
+ prop: keyof WindowLevelConfig
13
+ ) {
14
+ return computed({
15
+ get: () => source.value?.[prop] || null,
16
+ set: (newValue) => {
17
+ if (source.value && newValue != null) {
18
+ /* eslint-disable no-param-reassign */
19
+ source.value = {
20
+ ...source.value,
21
+ [prop]: newValue,
22
+ };
23
+ }
24
+ },
25
+ });
26
+ }
27
+
28
+ const useLevelSync = createPrimitiveSyncContext<number | null, string | null>(
29
+ 0
30
+ );
31
+ const useWidthSync = createPrimitiveSyncContext<number | null, string | null>(
32
+ 0
33
+ );
34
+
35
+ export function useWindowingSync(
36
+ dataID: Ref<string | null>,
37
+ source: WritableComputedRef<WindowLevelConfig | null>
38
+ ) {
39
+ const level = generateComputed(source, 'level');
40
+ const width = generateComputed(source, 'width');
41
+
42
+ // not 100% sure why, but this triggers tracking.
43
+ triggerRef(level);
44
+ triggerRef(width);
45
+
46
+ useLevelSync(dataID, level);
47
+ useWidthSync(dataID, width);
48
+ }
@@ -0,0 +1,73 @@
1
+ import { TYPE } from 'vue-toastification';
2
+ import { useToast } from '@/src/composables/useToast';
3
+ import { ToastID } from 'vue-toastification/dist/types/src/types';
4
+ import { useMessageStore } from '../store/messages';
5
+
6
+ export function useAppLoadingNotifications() {
7
+ const toast = useToast();
8
+ const messageStore = useMessageStore();
9
+
10
+ let loadingCount = 0;
11
+ let toastID: ToastID | null = null;
12
+ let error: Error | null = null;
13
+
14
+ const resetState = () => {
15
+ loadingCount = 0;
16
+ toastID = null;
17
+ error = null;
18
+ };
19
+
20
+ const _setError = (err: Error) => {
21
+ error = err;
22
+ };
23
+
24
+ const startLoading = () => {
25
+ loadingCount++;
26
+ if (toastID === null) {
27
+ toastID = toast.info('Loading datasets...', {
28
+ timeout: false,
29
+ closeButton: false,
30
+ closeOnClick: false,
31
+ });
32
+ }
33
+ };
34
+
35
+ const stopLoading = () => {
36
+ loadingCount--;
37
+ if (loadingCount === 0 && toastID !== null) {
38
+ if (error) {
39
+ toast.dismiss(toastID);
40
+ messageStore.addError('Some files failed to load', error);
41
+ } else {
42
+ toast.update(toastID, {
43
+ content: 'Datasets loaded!',
44
+ options: {
45
+ type: TYPE.SUCCESS,
46
+ timeout: 3000,
47
+ closeButton: 'button',
48
+ closeOnClick: true,
49
+ },
50
+ });
51
+ }
52
+ resetState();
53
+ }
54
+ };
55
+
56
+ /**
57
+ * Notifies of loading status until all function calls return.
58
+ * @param fn function to call
59
+ * @returns whatever fn returns.
60
+ */
61
+ const runAsLoading = async (fn: (setError: typeof _setError) => void) => {
62
+ startLoading();
63
+ try {
64
+ return await fn(_setError);
65
+ } finally {
66
+ stopLoading();
67
+ }
68
+ };
69
+
70
+ return {
71
+ runAsLoading,
72
+ };
73
+ }
@@ -0,0 +1,34 @@
1
+ import { computed, Ref, unref } from '@vue/composition-api';
2
+ import { MaybeRef } from '@vueuse/core';
3
+ import { mat3 } from 'gl-matrix';
4
+ import { ImageMetadata } from '../store/datasets-images';
5
+ import { LPSAxisDir } from '../types/lps';
6
+ import { getLPSDirections } from '../utils/lps';
7
+
8
+ /**
9
+ *
10
+ * @param {Ref<LPSAxisDir>} viewDirection an LPS view look-direction
11
+ * @param {Ref<LPSAxisDir>} viewUp an LPS view up-direction
12
+ * @param {Ref<ImageMetadata>} imageMetadataRef image metadata
13
+ */
14
+ export function useCameraOrientation(
15
+ viewDirection: MaybeRef<LPSAxisDir>,
16
+ viewUp: MaybeRef<LPSAxisDir>,
17
+ imageMetadataRef: Ref<ImageMetadata>
18
+ ) {
19
+ const orientationMatrix = computed(
20
+ () => imageMetadataRef.value.orientation as mat3
21
+ );
22
+ const lpsDirections = computed(() =>
23
+ getLPSDirections(orientationMatrix.value)
24
+ );
25
+ const cameraDirVec = computed(
26
+ () => lpsDirections.value[unref(viewDirection)]
27
+ );
28
+ const cameraUpVec = computed(() => lpsDirections.value[unref(viewUp)]);
29
+
30
+ return {
31
+ cameraDirVec,
32
+ cameraUpVec,
33
+ };
34
+ }