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,11 @@
1
+ import { createToastInterface } from 'vue-toastification';
2
+
3
+ const toast = createToastInterface({
4
+ position: 'bottom-left',
5
+ timeout: 3000,
6
+ hideProgressBar: true,
7
+ maxToasts: 3,
8
+ transition: 'Vue-Toastification__fade',
9
+ });
10
+
11
+ export const useToast = () => toast;
@@ -0,0 +1,33 @@
1
+ import { vtkSubscription } from '@kitware/vtk.js/interfaces';
2
+ import { isRef, onUnmounted, watchEffect } from '@vue/composition-api';
3
+ import { MaybeRef } from '@vueuse/core';
4
+
5
+ type Listener = (obj: any) => void;
6
+ type VTKCallback = (listener: Listener) => vtkSubscription;
7
+
8
+ export function useVTKCallback(
9
+ callback: MaybeRef<VTKCallback | null | undefined>
10
+ ) {
11
+ const handleListener = (listener: Listener) => {
12
+ let subscription: vtkSubscription | null = null;
13
+
14
+ const cleanup = () => {
15
+ subscription?.unsubscribe();
16
+ subscription = null;
17
+ };
18
+ onUnmounted(cleanup);
19
+
20
+ if (isRef(callback)) {
21
+ watchEffect(() => {
22
+ cleanup();
23
+ if (callback.value) {
24
+ subscription = callback.value(listener);
25
+ }
26
+ });
27
+ } else if (callback) {
28
+ subscription = callback(listener);
29
+ }
30
+ };
31
+
32
+ return handleListener;
33
+ }
@@ -0,0 +1,141 @@
1
+ import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow';
2
+ import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
3
+ import { Vector2, Vector3 } from '@kitware/vtk.js/types';
4
+ import { computed, ref, unref, watchEffect } from '@vue/composition-api';
5
+ import { MaybeRef, useResizeObserver } from '@vueuse/core';
6
+ import { vec3 } from 'gl-matrix';
7
+ import { useVTKCallback } from './useVTKCallback';
8
+ import { computeWorldToDisplay } from '../utils/vtk-helpers';
9
+
10
+ type MaybeCoords = Vector3 | vec3 | null | undefined;
11
+ type MaybeMultiCoords = Vector3[] | vec3[] | null | undefined;
12
+ type MaybeRenderer = vtkRenderer | null | undefined;
13
+
14
+ /**
15
+ * Returns a reactive vtkOpenGLRenderWindow container ref.
16
+ *
17
+ * @param view
18
+ * @returns
19
+ */
20
+ export function useVTKViewContainer(
21
+ view: MaybeRef<vtkOpenGLRenderWindow | null | undefined>
22
+ ) {
23
+ const container = ref<HTMLElement | null>(null);
24
+
25
+ const update = () => {
26
+ container.value = unref(view)?.getContainer() ?? null;
27
+ };
28
+
29
+ const onModified = useVTKCallback(computed(() => unref(view)?.onModified));
30
+ onModified(update);
31
+ update();
32
+
33
+ return container;
34
+ }
35
+
36
+ /**
37
+ * Transforms a list of world coordinates into display coordinates.
38
+ *
39
+ * @param worldCoords
40
+ * @param renderer
41
+ * @returns
42
+ */
43
+ export function useVTKMultiWorldToDisplay(
44
+ worldCoords: MaybeRef<MaybeMultiCoords>,
45
+ renderer: MaybeRef<MaybeRenderer>
46
+ ) {
47
+ const displayCoords = ref<Vector2[]>([]);
48
+ const view = computed(
49
+ () =>
50
+ unref(renderer)
51
+ ?.getRenderWindow()
52
+ ?.getViews()?.[0] as vtkOpenGLRenderWindow
53
+ );
54
+ const container = useVTKViewContainer(view);
55
+
56
+ const update = () => {
57
+ const coords = unref(worldCoords);
58
+ const ren = unref(renderer);
59
+
60
+ if (coords == null || !ren) {
61
+ return;
62
+ }
63
+
64
+ displayCoords.value = coords.map((xyz) => {
65
+ const disp = computeWorldToDisplay(xyz as Vector3, ren)!;
66
+ // convert from canvas space to display space
67
+ return [
68
+ disp[0] / devicePixelRatio,
69
+ disp[1] / devicePixelRatio,
70
+ ] as Vector2;
71
+ }) as Vector2[];
72
+ };
73
+
74
+ const cameraOnModified = useVTKCallback(
75
+ computed(() => unref(renderer)?.getActiveCamera().onModified)
76
+ );
77
+
78
+ useResizeObserver(container, update);
79
+ watchEffect(update);
80
+ cameraOnModified(update);
81
+ update();
82
+
83
+ return displayCoords;
84
+ }
85
+
86
+ /**
87
+ * Transforms a single world coordinate into a display coordinate.
88
+ *
89
+ * @param worldCoords
90
+ * @param renderer
91
+ * @returns
92
+ */
93
+ export function useVTKWorldToDisplay(
94
+ worldCoords: MaybeRef<MaybeCoords>,
95
+ renderer: MaybeRef<MaybeRenderer>
96
+ ) {
97
+ const manyCoords = computed(() => {
98
+ const c = unref(worldCoords);
99
+ if (c != null) {
100
+ return [c];
101
+ }
102
+ return null;
103
+ });
104
+
105
+ const displayCoords = useVTKMultiWorldToDisplay(manyCoords, renderer);
106
+ return computed(() => {
107
+ if (displayCoords.value.length === 1) {
108
+ return displayCoords.value[0];
109
+ }
110
+ return null;
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Transforms a list of world coordinates into SVG coordinates.
116
+ *
117
+ * @param worldCoords
118
+ * @param renderer
119
+ * @returns
120
+ */
121
+ export function useVTKMultiWorldToSVG(
122
+ worldCoords: MaybeRef<MaybeMultiCoords>,
123
+ renderer: MaybeRef<MaybeRenderer>
124
+ ) {
125
+ const displayCoords = useVTKMultiWorldToDisplay(worldCoords, renderer);
126
+ const svgCoords = computed(() => {
127
+ const disp = displayCoords.value;
128
+ const ren = unref(renderer);
129
+ const view = ren?.getRenderWindow()?.getViews()?.[0];
130
+ if (ren && view) {
131
+ const [, height] = view.getViewportSize(ren);
132
+ return disp.map((xy) => {
133
+ // flip Y axis
134
+ return [xy[0], height / devicePixelRatio - xy[1]];
135
+ });
136
+ }
137
+ return null;
138
+ });
139
+
140
+ return svgCoords;
141
+ }
@@ -0,0 +1,87 @@
1
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
2
+ import { computed, ref, unref, watch } from '@vue/composition-api';
3
+ import { MaybeRef } from '@vueuse/core';
4
+ import { ViewProxyType } from '../core/proxies';
5
+ import { useViewStore } from '../store/views';
6
+ import { useVTKCallback } from './useVTKCallback';
7
+
8
+ export function useViewProxy<T extends vtkViewProxy = vtkViewProxy>(
9
+ id: MaybeRef<string>,
10
+ type: MaybeRef<ViewProxyType>
11
+ ) {
12
+ const viewStore = useViewStore();
13
+
14
+ const container = ref<HTMLElement | null>(null);
15
+
16
+ const setContainer = (el: HTMLElement | null | undefined) => {
17
+ container.value = el ?? null;
18
+ };
19
+
20
+ const viewProxy = computed<T>(() =>
21
+ viewStore.createOrGetViewProxy(unref(id), unref(type))
22
+ );
23
+
24
+ watch(viewProxy, (curViewProxy, oldViewProxy) => {
25
+ oldViewProxy.setContainer(null);
26
+ curViewProxy.setContainer(container.value);
27
+ });
28
+
29
+ watch(container, (curContainer) => {
30
+ viewProxy.value.setContainer(curContainer);
31
+ // setContainer doesn't call modified
32
+ viewProxy.value.modified();
33
+ });
34
+
35
+ return {
36
+ viewProxy,
37
+ setContainer,
38
+ };
39
+ }
40
+
41
+ function onViewProxyModified<T extends vtkViewProxy = vtkViewProxy>(
42
+ viewProxy: MaybeRef<T>,
43
+ callback: () => void
44
+ ) {
45
+ const onModified = useVTKCallback(
46
+ computed(() => unref(viewProxy).onModified)
47
+ );
48
+ onModified(callback);
49
+ }
50
+
51
+ export function useViewProxyMounted<T extends vtkViewProxy = vtkViewProxy>(
52
+ viewProxy: MaybeRef<T>,
53
+ callback: () => void
54
+ ) {
55
+ const mounted = ref(false);
56
+
57
+ onViewProxyModified(viewProxy, () => {
58
+ mounted.value = !!unref(viewProxy).getContainer();
59
+ });
60
+
61
+ watch(
62
+ mounted,
63
+ (m) => {
64
+ if (m) callback();
65
+ },
66
+ { immediate: true }
67
+ );
68
+ }
69
+
70
+ export function useViewProxyUnmounted<T extends vtkViewProxy = vtkViewProxy>(
71
+ viewProxy: MaybeRef<T>,
72
+ callback: () => void
73
+ ) {
74
+ const mounted = ref(false);
75
+
76
+ onViewProxyModified(viewProxy, () => {
77
+ mounted.value = !!unref(viewProxy).getContainer();
78
+ });
79
+
80
+ watch(
81
+ mounted,
82
+ (m, prev) => {
83
+ if (prev && !m) callback();
84
+ },
85
+ { immediate: true }
86
+ );
87
+ }
@@ -0,0 +1,173 @@
1
+ import {
2
+ del,
3
+ onBeforeUnmount,
4
+ reactive,
5
+ ref,
6
+ set,
7
+ watch,
8
+ } from '@vue/composition-api';
9
+ import vtkLookupTableProxy from '@kitware/vtk.js/Proxy/Core/LookupTableProxy';
10
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
11
+ import vtkPiecewiseFunctionProxy from '@kitware/vtk.js/Proxy/Core/PiecewiseFunctionProxy';
12
+ import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps';
13
+ import { Vector3 } from '@kitware/vtk.js/types';
14
+ import { createVolumeThumbnailer } from '../core/thumbnailers/volume-thumbnailer';
15
+ import { useCameraOrientation } from './useCameraOrientation';
16
+ import { useImageStore } from '../store/datasets-images';
17
+ import { InitViewIDs, InitViewSpecs } from '../config';
18
+ import { useCurrentImage } from './useCurrentImage';
19
+ import {
20
+ getColorFunctionRangeFromPreset,
21
+ getOpacityRangeFromPreset,
22
+ } from '../utils/vtk-helpers';
23
+ import { PresetNameList } from '../vtk/ColorMaps';
24
+
25
+ function resetOpacityFunction(
26
+ pwfProxy: vtkPiecewiseFunctionProxy,
27
+ dataRange: [number, number],
28
+ presetName: string
29
+ ) {
30
+ // reset pwf proxy range
31
+ pwfProxy.setDataRange(...dataRange);
32
+
33
+ const preset = vtkColorMaps.getPresetByName(presetName);
34
+ if (preset.OpacityPoints) {
35
+ const OpacityPoints = preset.OpacityPoints as number[];
36
+ const points = [];
37
+ for (let i = 0; i < OpacityPoints.length; i += 2) {
38
+ points.push([OpacityPoints[i], OpacityPoints[i + 1]]);
39
+ }
40
+
41
+ const [xmin, xmax] = dataRange;
42
+ const width = xmax - xmin;
43
+ const pointsNormalized = points.map(([x, y]) => [(x - xmin) / width, y]);
44
+
45
+ pwfProxy.setMode(vtkPiecewiseFunctionProxy.Mode.Points);
46
+ pwfProxy.setPoints(pointsNormalized);
47
+ } else {
48
+ pwfProxy.setMode(vtkPiecewiseFunctionProxy.Mode.Gaussians);
49
+ pwfProxy.setGaussians(vtkPiecewiseFunctionProxy.Defaults.Gaussians);
50
+ }
51
+ }
52
+
53
+ export function useVolumeThumbnailing(thumbnailSize: number) {
54
+ const thumbnails = reactive<Record<string, Record<string, string>>>({});
55
+ const thumbnailer = createVolumeThumbnailer(thumbnailSize);
56
+ const currentThumbnails = ref<Record<string, string>>({});
57
+
58
+ const { currentImageMetadata, currentImageID, currentImageData } =
59
+ useCurrentImage();
60
+
61
+ // same as 3D view
62
+ const { cameraDirVec, cameraUpVec } = useCameraOrientation(
63
+ InitViewSpecs[InitViewIDs.Three].props.viewDirection,
64
+ InitViewSpecs[InitViewIDs.Three].props.viewUp,
65
+ currentImageMetadata
66
+ );
67
+
68
+ // used to interrupt a thumbnailing cycle if
69
+ // doThumbnailing is called again
70
+ let interruptSentinel = Symbol('interrupt');
71
+
72
+ async function doThumbnailing(imageID: string, image: vtkImageData) {
73
+ const localSentinel = Symbol('interrupt');
74
+ interruptSentinel = localSentinel;
75
+
76
+ thumbnailer.setInputImage(image);
77
+ const imageDataRange = image.getPointData().getScalars().getRange();
78
+
79
+ async function helper(presetName: string) {
80
+ // bail if a new thumbnail process has started
81
+ if (interruptSentinel !== localSentinel) {
82
+ return;
83
+ }
84
+
85
+ // sanity check; did the current image change
86
+ if (imageID !== currentImageID.value) {
87
+ return;
88
+ }
89
+
90
+ if (!(imageID in thumbnails)) {
91
+ set(thumbnails, imageID, {});
92
+ }
93
+
94
+ if (presetName in thumbnails[imageID]) {
95
+ return;
96
+ }
97
+
98
+ const opRange = getOpacityRangeFromPreset(presetName);
99
+ resetOpacityFunction(
100
+ thumbnailer.opacityFuncProxy,
101
+ opRange || imageDataRange,
102
+ presetName
103
+ );
104
+
105
+ thumbnailer.colorTransferFuncProxy.setMode(
106
+ vtkLookupTableProxy.Mode.Preset
107
+ );
108
+ thumbnailer.colorTransferFuncProxy.setPresetName(presetName);
109
+ const ctRange = getColorFunctionRangeFromPreset(presetName);
110
+ thumbnailer.colorTransferFuncProxy.setDataRange(
111
+ ...(ctRange || imageDataRange)
112
+ );
113
+
114
+ thumbnailer.resetCameraWithOrientation(
115
+ cameraDirVec.value as Vector3,
116
+ cameraUpVec.value as Vector3
117
+ );
118
+
119
+ const renWin = thumbnailer.scene.getRenderWindow();
120
+ renWin.render();
121
+ const imageURL = await renWin.captureImages()[0];
122
+ if (imageURL) {
123
+ set(thumbnails[imageID], presetName, imageURL);
124
+ }
125
+ }
126
+
127
+ PresetNameList.reduce(
128
+ (promise, presetName) => promise.then(() => helper(presetName)),
129
+ Promise.resolve()
130
+ );
131
+ }
132
+
133
+ // workaround for computed not properly working on deeply reactive objects
134
+ // in vue 2.
135
+ watch(
136
+ thumbnails,
137
+ () => {
138
+ if (currentImageID.value) {
139
+ currentThumbnails.value = thumbnails[currentImageID.value];
140
+ }
141
+ },
142
+ { deep: true }
143
+ );
144
+
145
+ // force thumbnailing to stop
146
+ onBeforeUnmount(() => {
147
+ interruptSentinel = Symbol('unmount');
148
+ });
149
+
150
+ // trigger thumbnailing
151
+ watch(
152
+ currentImageID,
153
+ (imageID) => {
154
+ if (imageID) {
155
+ doThumbnailing(imageID, currentImageData.value!);
156
+ }
157
+ },
158
+ { immediate: true }
159
+ );
160
+
161
+ // delete thumbnails if an image is deleted
162
+ const imageStore = useImageStore();
163
+ imageStore.$onAction(({ name, args, after }) => {
164
+ if (name === 'deleteData') {
165
+ const [id] = args as [string];
166
+ if (id in thumbnails) {
167
+ after(() => del(thumbnails, id));
168
+ }
169
+ }
170
+ });
171
+
172
+ return { currentThumbnails };
173
+ }
@@ -0,0 +1,32 @@
1
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
2
+ import { useEventListener, useThrottleFn } from '@vueuse/core';
3
+ import { Messages } from '../constants';
4
+ import { useMessageStore } from '../store/messages';
5
+ import { onProxyManagerEvent, ProxyManagerEvent } from './onProxyManagerEvent';
6
+
7
+ export function useWebGLWatchdog() {
8
+ const watchdogs = new Map<string, () => void>();
9
+
10
+ const reportError = useThrottleFn(() => {
11
+ const messageStore = useMessageStore();
12
+ messageStore.addError(Messages.WebGLLost.title, Messages.WebGLLost.details);
13
+ }, 100);
14
+
15
+ onProxyManagerEvent(ProxyManagerEvent.ProxyCreated, (id, obj) => {
16
+ if (!obj || !obj.isA('vtkViewProxy')) return;
17
+ const view = obj as vtkViewProxy;
18
+ // TODO getCanvas() typing
19
+ const rw = view.getOpenglRenderWindow() as any;
20
+ const canvas = rw.getCanvas() as HTMLCanvasElement;
21
+
22
+ const cleanup = useEventListener(canvas, 'webglcontextlost', reportError);
23
+ watchdogs.set(id, cleanup);
24
+ });
25
+
26
+ onProxyManagerEvent(ProxyManagerEvent.ProxyDeleted, (id) => {
27
+ if (watchdogs.has(id)) {
28
+ watchdogs.get(id)!();
29
+ watchdogs.delete(id);
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,41 @@
1
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
2
+ import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
3
+ import { CaptureOn } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants';
4
+ import { computed, onUnmounted, Ref, watch } from '@vue/composition-api';
5
+ import { useViewProxyMounted, useViewProxyUnmounted } from './useViewProxy';
6
+
7
+ export function useWidgetManager(viewProxy: Ref<vtkViewProxy>) {
8
+ const widgetManager = computed(() => {
9
+ const wm = vtkWidgetManager.newInstance({
10
+ pickingEnabled: false,
11
+ useSvgLayer: false,
12
+ captureOn: CaptureOn.MOUSE_MOVE,
13
+ });
14
+ return wm;
15
+ });
16
+
17
+ useViewProxyMounted(viewProxy, () => {
18
+ widgetManager.value.setRenderer(viewProxy.value.getRenderer());
19
+ widgetManager.value.enablePicking();
20
+ });
21
+
22
+ useViewProxyUnmounted(viewProxy, () => {
23
+ widgetManager.value.disablePicking();
24
+ });
25
+
26
+ watch(widgetManager, (curWM, oldWM) => {
27
+ if (curWM) {
28
+ curWM.setRenderer(viewProxy.value.getRenderer());
29
+ curWM.enablePicking();
30
+ }
31
+ if (oldWM) {
32
+ oldWM.delete();
33
+ }
34
+ });
35
+
36
+ onUnmounted(() => {
37
+ widgetManager.value.delete();
38
+ });
39
+
40
+ return { widgetManager };
41
+ }
package/src/config.ts ADDED
@@ -0,0 +1,182 @@
1
+ import MRICardiacThumbnail from '@/src/assets/samples/MRI-Cardiac.jpg';
2
+ import MRIPROSTATExThumbnail from '@/src/assets/samples/MRI-PROSTATEx.jpg';
3
+ import MRAHeadThumbnail from '@/src/assets/samples/MRA-Head_and_Neck.jpg';
4
+ import CTAHeadThumbnail from '@/src/assets/samples/CTA-Head_and_Neck.jpg';
5
+ import USFetusThumbnail from '@/src/assets/samples/3DUS-Fetus.jpg';
6
+ import { Layout, LayoutDirection } from './types/layout';
7
+ import { ViewSpec } from './types/views';
8
+ import { SampleDataset } from './types';
9
+
10
+ /**
11
+ * These are the initial view IDs.
12
+ */
13
+ export const InitViewIDs: Record<string, string> = {
14
+ Coronal: 'Coronal',
15
+ Sagittal: 'Sagittal',
16
+ Axial: 'Axial',
17
+ Three: '3D',
18
+ };
19
+
20
+ /**
21
+ * View spec for the initial view IDs.
22
+ */
23
+ export const InitViewSpecs: Record<string, ViewSpec> = {
24
+ [InitViewIDs.Coronal]: {
25
+ viewType: '2D',
26
+ props: {
27
+ viewDirection: 'Right',
28
+ viewUp: 'Superior',
29
+ },
30
+ },
31
+ [InitViewIDs.Sagittal]: {
32
+ viewType: '2D',
33
+ props: {
34
+ viewDirection: 'Posterior',
35
+ viewUp: 'Superior',
36
+ },
37
+ },
38
+ [InitViewIDs.Axial]: {
39
+ viewType: '2D',
40
+ props: {
41
+ viewDirection: 'Superior',
42
+ viewUp: 'Anterior',
43
+ },
44
+ },
45
+ [InitViewIDs.Three]: {
46
+ viewType: '3D',
47
+ props: {
48
+ viewDirection: 'Posterior',
49
+ viewUp: 'Superior',
50
+ },
51
+ },
52
+ };
53
+
54
+ /**
55
+ * The default view spec.
56
+ */
57
+ export const DefaultViewSpec = InitViewSpecs[InitViewIDs.Axial];
58
+
59
+ /**
60
+ * Defines the default layouts.
61
+ */
62
+ export const Layouts: Record<string, Layout> = [
63
+ {
64
+ name: 'Axial Primary',
65
+ direction: LayoutDirection.V,
66
+ items: [
67
+ InitViewIDs.Axial,
68
+ {
69
+ direction: LayoutDirection.H,
70
+ items: [InitViewIDs.Three, InitViewIDs.Coronal, InitViewIDs.Sagittal],
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ name: '3D Primary',
76
+ direction: LayoutDirection.V,
77
+ items: [
78
+ InitViewIDs.Three,
79
+ {
80
+ direction: LayoutDirection.H,
81
+ items: [InitViewIDs.Coronal, InitViewIDs.Sagittal, InitViewIDs.Axial],
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ name: 'Quad View',
87
+ direction: LayoutDirection.H,
88
+ items: [
89
+ {
90
+ direction: LayoutDirection.V,
91
+ items: [InitViewIDs.Coronal, InitViewIDs.Three],
92
+ },
93
+ {
94
+ direction: LayoutDirection.V,
95
+ items: [InitViewIDs.Sagittal, InitViewIDs.Axial],
96
+ },
97
+ ],
98
+ },
99
+ {
100
+ name: '3D Only',
101
+ direction: LayoutDirection.H,
102
+ items: [InitViewIDs.Three],
103
+ },
104
+ ].reduce((layouts, layout) => {
105
+ return { ...layouts, [layout.name]: layout };
106
+ }, {});
107
+
108
+ export const LABELMAP_PALETTE = {
109
+ 0: [0, 0, 0, 0], // eraser
110
+ 1: [153, 153, 0, 255],
111
+ 2: [76, 76, 0, 255],
112
+ 3: [255, 255, 0, 255],
113
+ 4: [0, 76, 0, 255],
114
+ 5: [0, 153, 0, 255],
115
+ 6: [0, 255, 0, 255],
116
+ 7: [76, 0, 0, 255],
117
+ 8: [153, 0, 0, 255],
118
+ 9: [255, 0, 0, 255],
119
+ 10: [0, 76, 76, 255],
120
+ 11: [0, 153, 153, 255],
121
+ 12: [0, 255, 255, 255],
122
+ 13: [0, 0, 76, 255],
123
+ 14: [0, 0, 153, 255],
124
+ } as Record<number, number[]>;
125
+
126
+ export const SAMPLE_DATA: SampleDataset[] = [
127
+ {
128
+ name: 'CTA Head and Neck',
129
+ filename: 'CTA-Head_and_Neck.zip',
130
+ description:
131
+ 'CTA head and neck scan of elderly patient with tumor. (80 MB)',
132
+ url: 'https://data.kitware.com/api/v1/item/6347159711dab81428208e24/download',
133
+ image: CTAHeadThumbnail,
134
+ },
135
+ {
136
+ name: 'MRA Head and Neck',
137
+ filename: 'MRA-Head_and_Neck.zip',
138
+ description:
139
+ 'MRA from Patient Contributed Image Repository. Click application help icon "(?)" for more info. (15 MB)',
140
+ url: 'https://data.kitware.com/api/v1/item/6352a2b311dab8142820a33b/download',
141
+ image: MRAHeadThumbnail,
142
+ },
143
+ {
144
+ name: 'MRI Cardiac 3D and Cine',
145
+ filename: 'MRI-Cardiac-3D_and_Cine.zip',
146
+ description:
147
+ 'MRI scan with two series: 3D axial non-gated and 2 chamber cine. (4 MB)',
148
+ url: 'https://data.kitware.com/api/v1/item/6350b28f11dab8142820949d/download',
149
+ image: MRICardiacThumbnail,
150
+ },
151
+ {
152
+ name: 'MRI PROSTATEx',
153
+ filename: 'MRI-PROSTATEx-0004.zip',
154
+ description:
155
+ 'MRI from the SPIE-AAPM-NCI PROSTATEx challenge. Click application help "(?)" icon for more info. (3 MB)',
156
+ url: 'https://data.kitware.com/api/v1/item/63527c7311dab8142820a338/download',
157
+ image: MRIPROSTATExThumbnail,
158
+ },
159
+ {
160
+ name: '3D US Fetus',
161
+ filename: '3DUS-Fetus.mha',
162
+ description:
163
+ '3D ultrasound of a baby. Downloaded from tomovision.com.(8 MB)',
164
+ url: 'https://data.kitware.com/api/v1/item/635679c311dab8142820a4f4/download',
165
+ image: USFetusThumbnail,
166
+ },
167
+ ];
168
+
169
+ export const TOOL_COLORS = [
170
+ '#8de4d3',
171
+ '#f0a4b1',
172
+ '#58f24c',
173
+ '#a3c9fe',
174
+ '#c8f251',
175
+ '#fea53b',
176
+ ];
177
+
178
+ export const DEFAULT_PRESET_BY_MODALITY: Record<string, string> = {
179
+ CT: 'CT-AAA',
180
+ MR: 'CT-Coronary-Arteries-2',
181
+ US: 'US-Fetal',
182
+ };