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,665 @@
1
+ <template>
2
+ <div
3
+ class="vtk-container-wrapper"
4
+ tabindex="0"
5
+ @pointerenter="hover = true"
6
+ @pointerleave="hover = false"
7
+ @focusin="hover = true"
8
+ @focusout="hover = false"
9
+ >
10
+ <div class="vtk-gutter">
11
+ <v-tooltip right transition="slide-x-transition">
12
+ <template v-slot:activator="{ on, attrs }">
13
+ <v-btn
14
+ dark
15
+ x-small
16
+ icon
17
+ @click="enableResizeToFit"
18
+ v-bind="attrs"
19
+ v-on="on"
20
+ >
21
+ <v-icon small class="py-1">mdi-camera-flip-outline</v-icon>
22
+ </v-btn>
23
+ </template>
24
+ <span>Reset camera</span>
25
+ </v-tooltip>
26
+ <slice-slider
27
+ class="slice-slider"
28
+ :slice="slice"
29
+ :min="sliceMin"
30
+ :max="sliceMax"
31
+ :step="1"
32
+ :handle-height="20"
33
+ @input="setSlice"
34
+ />
35
+ </div>
36
+ <div class="vtk-container" :class="active ? 'active' : ''">
37
+ <div class="vtk-sub-container">
38
+ <div class="vtk-view" ref="vtkContainerRef" />
39
+ </div>
40
+ <div class="overlay-no-events tool-layer">
41
+ <pan-tool :view-id="viewID" />
42
+ <zoom-tool :view-id="viewID" />
43
+ <slice-scroll-tool :view-id="viewID" />
44
+ <window-level-tool :view-id="viewID" />
45
+ <ruler-tool
46
+ view-type="2D"
47
+ :view-id="viewID"
48
+ :widget-manager="widgetManager"
49
+ :view-direction="viewDirection"
50
+ :slice="slice"
51
+ />
52
+ <paint-tool
53
+ :view-id="viewID"
54
+ :view-direction="viewDirection"
55
+ :widget-manager="widgetManager"
56
+ :slice="slice"
57
+ />
58
+ <crosshairs-tool
59
+ :view-id="viewID"
60
+ :view-direction="viewDirection"
61
+ :widget-manager="widgetManager"
62
+ :slice="slice"
63
+ />
64
+ <crop-tool :view-id="viewID" />
65
+ </div>
66
+ <view-overlay-grid class="overlay-no-events view-annotations">
67
+ <template v-slot:top-middle>
68
+ <div class="annotation-cell">
69
+ <span>{{ topLabel }}</span>
70
+ </div>
71
+ </template>
72
+ <template v-slot:middle-left>
73
+ <div class="annotation-cell">
74
+ <span>{{ leftLabel }}</span>
75
+ </div>
76
+ </template>
77
+ <template v-slot:bottom-left>
78
+ <div class="annotation-cell">
79
+ <div>Slice: {{ slice + 1 }}/{{ sliceMax + 1 }}</div>
80
+ <div
81
+ v-if="
82
+ typeof windowWidth === 'number' &&
83
+ typeof windowLevel === 'number'
84
+ "
85
+ >
86
+ W/L: {{ windowWidth.toFixed(2) }} / {{ windowLevel.toFixed(2) }}
87
+ </div>
88
+ </div>
89
+ </template>
90
+ <template v-slot:top-right>
91
+ <div class="annotation-cell">
92
+ <v-menu
93
+ open-on-hover
94
+ offset-y
95
+ left
96
+ nudge-left="10"
97
+ dark
98
+ v-if="dicomInfo !== null"
99
+ max-width="300px"
100
+ >
101
+ <template v-slot:activator="{ on, attrs }">
102
+ <v-btn
103
+ dark
104
+ small
105
+ icon
106
+ v-bind="attrs"
107
+ v-on="on"
108
+ class="pointer-events-all"
109
+ >
110
+ <v-icon dark> mdi-information </v-icon>
111
+ </v-btn>
112
+ </template>
113
+ <v-list class="grey darken-3">
114
+ <v-list-item>
115
+ <v-list-item-content>
116
+ <v-list-item-title class="font-weight-bold">
117
+ PATIENT / CASE
118
+ </v-list-item-title>
119
+ <v-divider />
120
+ <v-list-item-title>
121
+ ID: {{ dicomInfo.patientID }}
122
+ </v-list-item-title>
123
+ </v-list-item-content>
124
+ </v-list-item>
125
+ <v-list-item>
126
+ <v-list-item-content>
127
+ <v-list-item-title class="font-weight-bold">
128
+ STUDY
129
+ </v-list-item-title>
130
+ <v-divider />
131
+ <v-list-item-title>
132
+ ID: {{ dicomInfo.studyID }}
133
+ </v-list-item-title>
134
+ <v-list-item-title>
135
+ {{ dicomInfo.studyDescription }}
136
+ </v-list-item-title>
137
+ </v-list-item-content>
138
+ </v-list-item>
139
+ <v-list-item>
140
+ <v-list-item-content>
141
+ <v-list-item-title class="font-weight-bold">
142
+ SERIES
143
+ </v-list-item-title>
144
+ <v-divider />
145
+ <v-list-item-title>
146
+ Series #: {{ dicomInfo.seriesNumber }}
147
+ </v-list-item-title>
148
+ <v-list-item-title>
149
+ {{ dicomInfo.seriesDescription }}
150
+ </v-list-item-title>
151
+ </v-list-item-content>
152
+ </v-list-item>
153
+ </v-list>
154
+ </v-menu>
155
+ </div>
156
+ </template>
157
+ </view-overlay-grid>
158
+ <transition name="loading">
159
+ <div v-if="isImageLoading" class="overlay-no-events loading">
160
+ <div>Loading the image</div>
161
+ <div>
162
+ <v-progress-circular indeterminate color="blue" />
163
+ </div>
164
+ </div>
165
+ </transition>
166
+ </div>
167
+ </div>
168
+ </template>
169
+
170
+ <script lang="ts">
171
+ import {
172
+ computed,
173
+ defineComponent,
174
+ onBeforeMount,
175
+ onBeforeUnmount,
176
+ onMounted,
177
+ PropType,
178
+ provide,
179
+ ref,
180
+ toRefs,
181
+ watch,
182
+ watchEffect,
183
+ } from '@vue/composition-api';
184
+ import { vec3 } from 'gl-matrix';
185
+ import { onKeyStroke } from '@vueuse/core';
186
+
187
+ import { useResizeToFit } from '@src/composables/useResizeToFit';
188
+ import vtkLPSView2DProxy from '@src/vtk/LPSView2DProxy';
189
+ import vtkIJKSliceRepresentationProxy from '@src/vtk/IJKSliceRepresentationProxy';
190
+ import { manageVTKSubscription } from '@src/composables/manageVTKSubscription';
191
+ import SliceSlider from '@src/components/SliceSlider.vue';
192
+ import ViewOverlayGrid from '@src/components/ViewOverlayGrid.vue';
193
+ import { useResizeObserver } from '../composables/useResizeObserver';
194
+ import { useOrientationLabels } from '../composables/useOrientationLabels';
195
+ import { getLPSAxisFromDir } from '../utils/lps';
196
+ import { useCurrentImage } from '../composables/useCurrentImage';
197
+ import { useCameraOrientation } from '../composables/useCameraOrientation';
198
+ import WindowLevelTool from './tools/WindowLevelTool.vue';
199
+ import SliceScrollTool from './tools/SliceScrollTool.vue';
200
+ import PanTool from './tools/PanTool.vue';
201
+ import ZoomTool from './tools/ZoomTool.vue';
202
+ import RulerTool from './tools/RulerTool.vue';
203
+ import PaintTool from './tools/PaintTool.vue';
204
+ import { useSceneBuilder } from '../composables/useSceneBuilder';
205
+ import { useDICOMStore } from '../store/datasets-dicom';
206
+ import { useLabelmapStore } from '../store/datasets-labelmaps';
207
+ import vtkLabelMapSliceRepProxy from '../vtk/LabelMapSliceRepProxy';
208
+ import { usePaintToolStore } from '../store/tools/paint';
209
+ import { useViewConfigStore } from '../store/view-configs';
210
+ import { usePersistCameraConfig } from '../composables/usePersistCameraConfig';
211
+ import CrosshairsTool from './tools/CrosshairsTool.vue';
212
+ import { LPSAxisDir } from '../types/lps';
213
+ import { ViewProxyType } from '../core/proxies';
214
+ import { useViewProxy } from '../composables/useViewProxy';
215
+ import { useWidgetManager } from '../composables/useWidgetManager';
216
+ import { CameraConfig } from '../store/view-configs/types';
217
+ import { defaultSliceConfig } from '../store/view-configs/slicing';
218
+ import { useWindowingSync } from '../composables/sync/useWindowingSync';
219
+ import CropTool from './tools/CropTool.vue';
220
+ import { VTKTwoViewWidgetManager } from '../constants';
221
+
222
+ const SLICE_OFFSET_KEYS: Record<string, number> = {
223
+ ArrowLeft: -1,
224
+ ArrowRight: 1,
225
+ ArrowUp: -1,
226
+ ArrowDown: 1,
227
+ } as const;
228
+
229
+ export default defineComponent({
230
+ name: 'VtkTwoView',
231
+ props: {
232
+ id: {
233
+ type: String,
234
+ required: true,
235
+ },
236
+ viewDirection: {
237
+ type: String as PropType<LPSAxisDir>,
238
+ required: true,
239
+ },
240
+ viewUp: {
241
+ type: String as PropType<LPSAxisDir>,
242
+ required: true,
243
+ },
244
+ },
245
+ components: {
246
+ SliceSlider,
247
+ ViewOverlayGrid,
248
+ WindowLevelTool,
249
+ SliceScrollTool,
250
+ PanTool,
251
+ ZoomTool,
252
+ RulerTool,
253
+ PaintTool,
254
+ CrosshairsTool,
255
+ CropTool,
256
+ },
257
+ setup(props) {
258
+ const viewConfigStore = useViewConfigStore();
259
+ const paintStore = usePaintToolStore();
260
+
261
+ const { id: viewID, viewDirection, viewUp } = toRefs(props);
262
+
263
+ const vtkContainerRef = ref<HTMLElement>();
264
+
265
+ const viewAxis = computed(() => getLPSAxisFromDir(viewDirection.value));
266
+
267
+ // --- computed vars --- //
268
+
269
+ const {
270
+ currentImageData: curImageData,
271
+ currentImageID: curImageID,
272
+ currentImageMetadata: curImageMetadata,
273
+ isImageLoading,
274
+ } = useCurrentImage();
275
+
276
+ const dicomStore = useDICOMStore();
277
+
278
+ const sliceConfigDefaults = defaultSliceConfig();
279
+ const sliceConfig = computed(() =>
280
+ curImageID.value !== null
281
+ ? viewConfigStore.getSliceConfig(viewID.value, curImageID.value)!
282
+ : null
283
+ );
284
+ const currentSlice = computed(() =>
285
+ sliceConfig.value !== null
286
+ ? sliceConfig.value.slice
287
+ : sliceConfigDefaults.slice
288
+ );
289
+ const sliceMin = computed(() =>
290
+ sliceConfig.value !== null
291
+ ? sliceConfig.value.min
292
+ : sliceConfigDefaults.min
293
+ );
294
+ const sliceMax = computed(() =>
295
+ sliceConfig.value !== null
296
+ ? sliceConfig.value.max
297
+ : sliceConfigDefaults.max
298
+ );
299
+
300
+ const wlConfig = computed({
301
+ get: () => {
302
+ const _viewID = viewID.value;
303
+ const imageID = curImageID.value;
304
+ if (imageID !== null) {
305
+ return viewConfigStore.getWindowingConfig(_viewID, imageID) ?? null;
306
+ }
307
+ return null;
308
+ },
309
+ set: (newValue) => {
310
+ const imageID = curImageID.value;
311
+ if (imageID !== null && newValue != null) {
312
+ viewConfigStore.updateWindowingConfig(
313
+ viewID.value,
314
+ imageID,
315
+ newValue
316
+ );
317
+ }
318
+ },
319
+ });
320
+
321
+ const windowWidth = computed(() => wlConfig.value?.width);
322
+ const windowLevel = computed(() => wlConfig.value?.level);
323
+ const dicomInfo = computed(() => {
324
+ if (
325
+ curImageID.value !== null &&
326
+ curImageID.value in dicomStore.imageIDToVolumeKey
327
+ ) {
328
+ const volumeKey = dicomStore.imageIDToVolumeKey[curImageID.value];
329
+ const volumeInfo = dicomStore.volumeInfo[volumeKey];
330
+ const studyKey = dicomStore.volumeStudy[volumeKey];
331
+ const studyInfo = dicomStore.studyInfo[studyKey];
332
+ const patientKey = dicomStore.studyPatient[studyKey];
333
+ const patientInfo = dicomStore.patientInfo[patientKey];
334
+
335
+ const patientID = patientInfo.PatientID;
336
+ const studyID = studyInfo.StudyID;
337
+ const studyDescription = studyInfo.StudyDescription;
338
+ const seriesNumber = volumeInfo.SeriesNumber;
339
+ const seriesDescription = volumeInfo.SeriesDescription;
340
+
341
+ return {
342
+ patientID,
343
+ studyID,
344
+ studyDescription,
345
+ seriesNumber,
346
+ seriesDescription,
347
+ };
348
+ }
349
+
350
+ return null;
351
+ });
352
+
353
+ const sliceDomain = computed(() => {
354
+ const { lpsOrientation, dimensions } = curImageMetadata.value;
355
+ const ijkIndex = lpsOrientation[viewAxis.value];
356
+ const dimMax = dimensions[ijkIndex];
357
+
358
+ return {
359
+ min: 0,
360
+ max: dimMax - 1,
361
+ };
362
+ });
363
+
364
+ // --- setters --- //
365
+
366
+ const setSlice = (slice: number) => {
367
+ if (curImageID.value !== null) {
368
+ viewConfigStore.updateSliceConfig(viewID.value, curImageID.value, {
369
+ slice,
370
+ });
371
+ }
372
+ };
373
+
374
+ // --- view proxy setup --- //
375
+
376
+ const { viewProxy, setContainer: setViewProxyContainer } =
377
+ useViewProxy<vtkLPSView2DProxy>(viewID, ViewProxyType.Slice);
378
+
379
+ onBeforeMount(() => {
380
+ // do this before mount, as the ManipulatorTools run onMounted
381
+ // before this component does.
382
+ viewProxy.value.getInteractorStyle2D().removeAllManipulators();
383
+ });
384
+
385
+ onMounted(() => {
386
+ setViewProxyContainer(vtkContainerRef.value);
387
+ viewProxy.value.setOrientationAxesVisibility(false);
388
+ });
389
+
390
+ onBeforeUnmount(() => {
391
+ setViewProxyContainer(null);
392
+ });
393
+
394
+ // --- Slicing setup --- //
395
+
396
+ watchEffect(() => {
397
+ const ijkIndex = curImageMetadata.value.lpsOrientation[viewAxis.value];
398
+ viewProxy.value.setSlicingMode('IJK'[ijkIndex]);
399
+ });
400
+
401
+ watch(
402
+ [viewID, curImageID, viewDirection],
403
+ () => {
404
+ if (
405
+ curImageID.value &&
406
+ !viewConfigStore.getSliceConfig(viewID.value, curImageID.value)
407
+ ) {
408
+ viewConfigStore.updateSliceConfig(viewID.value, curImageID.value, {
409
+ ...sliceDomain.value,
410
+ axisDirection: viewDirection.value,
411
+ });
412
+ viewConfigStore.resetSlice(viewID.value, curImageID.value);
413
+ }
414
+ },
415
+ { immediate: true, deep: true }
416
+ );
417
+
418
+ // --- arrows change slice --- //
419
+
420
+ const { currentImageID } = useCurrentImage();
421
+
422
+ const hover = ref(false);
423
+ const onKeyDown = (event: KeyboardEvent) => {
424
+ if (!currentImageID.value || !hover.value) return;
425
+
426
+ const sliceOffset = SLICE_OFFSET_KEYS[event.key] ?? 0;
427
+ if (sliceOffset) {
428
+ viewConfigStore.updateSliceConfig(viewID.value, currentImageID.value, {
429
+ slice: currentSlice.value + sliceOffset,
430
+ });
431
+
432
+ event.stopPropagation();
433
+ }
434
+ };
435
+ onKeyStroke(Object.keys(SLICE_OFFSET_KEYS), onKeyDown);
436
+
437
+ // --- resizing --- //
438
+
439
+ useResizeObserver(vtkContainerRef, () => viewProxy.value.resize());
440
+
441
+ // --- widget manager --- //
442
+
443
+ const { widgetManager } = useWidgetManager(viewProxy);
444
+ provide(VTKTwoViewWidgetManager, widgetManager);
445
+
446
+ // --- window/level setup --- //
447
+
448
+ watch(
449
+ curImageData,
450
+ (imageData, oldImageData) => {
451
+ if (imageData && imageData !== oldImageData) {
452
+ // TODO listen to changes in point data
453
+ const range = imageData.getPointData().getScalars().getRange();
454
+ if (
455
+ curImageID.value !== null &&
456
+ !viewConfigStore.getWindowingConfig(viewID.value, curImageID.value)
457
+ ) {
458
+ viewConfigStore.updateWindowingConfig(
459
+ viewID.value,
460
+ curImageID.value,
461
+ {
462
+ min: range[0],
463
+ max: range[1],
464
+ }
465
+ );
466
+ viewConfigStore.resetWindowLevel(viewID.value, curImageID.value);
467
+ }
468
+ }
469
+ },
470
+ {
471
+ immediate: true,
472
+ }
473
+ );
474
+
475
+ // --- sync windowing parameters --- //
476
+
477
+ useWindowingSync(curImageID, wlConfig);
478
+
479
+ // --- scene setup --- //
480
+
481
+ const labelmapStore = useLabelmapStore();
482
+
483
+ const labelmapIDs = computed(() => {
484
+ return labelmapStore.idList.filter(
485
+ (id) => labelmapStore.parentImage[id] === curImageID.value
486
+ );
487
+ });
488
+
489
+ const { baseImageRep, labelmapReps } = useSceneBuilder<
490
+ vtkIJKSliceRepresentationProxy,
491
+ vtkLabelMapSliceRepProxy
492
+ >(viewID, {
493
+ baseImage: curImageID,
494
+ labelmaps: labelmapIDs,
495
+ });
496
+
497
+ // --- camera setup --- //
498
+
499
+ const { cameraDirVec, cameraUpVec } = useCameraOrientation(
500
+ viewDirection,
501
+ viewUp,
502
+ curImageMetadata
503
+ );
504
+ const { resizeToFit, ignoreResizeToFitTracking, resetResizeToFitTracking } =
505
+ useResizeToFit(viewProxy.value.getCamera(), false);
506
+
507
+ const resizeToFitScene = () =>
508
+ ignoreResizeToFitTracking(() => {
509
+ // resize to fit
510
+ const lookAxis =
511
+ curImageMetadata.value.lpsOrientation[
512
+ getLPSAxisFromDir(viewDirection.value)
513
+ ];
514
+ const upAxis =
515
+ curImageMetadata.value.lpsOrientation[
516
+ getLPSAxisFromDir(viewUp.value)
517
+ ];
518
+ const dimsWithSpacing = curImageMetadata.value.dimensions.map(
519
+ (d, i) => d * curImageMetadata.value.spacing[i]
520
+ );
521
+ viewProxy.value.resizeToFit(lookAxis, upAxis, dimsWithSpacing);
522
+ resetResizeToFitTracking();
523
+ });
524
+
525
+ const resetCamera = () => {
526
+ const bounds = curImageMetadata.value.worldBounds;
527
+ const center = [
528
+ (bounds[0] + bounds[1]) / 2,
529
+ (bounds[2] + bounds[3]) / 2,
530
+ (bounds[4] + bounds[5]) / 2,
531
+ ] as vec3;
532
+
533
+ // do not track resizeToFit state
534
+ ignoreResizeToFitTracking(() => {
535
+ viewProxy.value.updateCamera(
536
+ cameraDirVec.value,
537
+ cameraUpVec.value,
538
+ center
539
+ );
540
+ viewProxy.value.resetCamera(bounds);
541
+ });
542
+
543
+ resizeToFitScene();
544
+
545
+ viewProxy.value.render();
546
+ };
547
+
548
+ manageVTKSubscription(
549
+ viewProxy.value.onResize(() => {
550
+ if (resizeToFit.value) {
551
+ resizeToFitScene();
552
+ }
553
+ })
554
+ );
555
+
556
+ // if we re-enable resizeToFit, reset the camera
557
+ watch(resizeToFit, () => {
558
+ if (resizeToFit.value) {
559
+ resetCamera();
560
+ }
561
+ });
562
+
563
+ const { restoreCameraConfig } = usePersistCameraConfig(
564
+ viewID,
565
+ curImageID,
566
+ viewProxy,
567
+ 'parallelScale',
568
+ 'position',
569
+ 'focalPoint',
570
+ 'viewUp'
571
+ );
572
+
573
+ watch(
574
+ [curImageID, cameraDirVec, cameraUpVec],
575
+ () => {
576
+ let cameraConfig: CameraConfig | undefined;
577
+ if (curImageID.value !== null) {
578
+ cameraConfig = viewConfigStore.getCameraConfig(
579
+ viewID.value,
580
+ curImageID.value
581
+ );
582
+ }
583
+
584
+ // If we have a save camera configuration restore it
585
+ if (cameraConfig) {
586
+ restoreCameraConfig(cameraConfig);
587
+
588
+ viewProxy.value.getRenderer().resetCameraClippingRange();
589
+ viewProxy.value.render();
590
+
591
+ // Prevent resize
592
+ resizeToFit.value = false;
593
+ } else if (resizeToFit.value) {
594
+ resetCamera();
595
+ } else {
596
+ // this will trigger a resetCamera() call
597
+ resizeToFit.value = true;
598
+ }
599
+ },
600
+ { immediate: true, deep: true }
601
+ );
602
+
603
+ // --- viewport orientation/camera labels --- //
604
+
605
+ const { top: topLabel, left: leftLabel } = useOrientationLabels(viewProxy);
606
+
607
+ // --- apply windowing and slice configs --- //
608
+
609
+ watchEffect(() => {
610
+ if (sliceConfig.value === null || wlConfig.value === null) {
611
+ return;
612
+ }
613
+
614
+ const { slice } = sliceConfig.value;
615
+ const { width, level } = wlConfig.value;
616
+ const rep = baseImageRep.value;
617
+ if (rep) {
618
+ rep.setSlice(slice);
619
+ rep.setWindowWidth(width);
620
+ rep.setWindowLevel(level);
621
+ }
622
+ labelmapReps.value.forEach((lmRep) => {
623
+ lmRep.setSlice(slice);
624
+ });
625
+
626
+ viewProxy.value.render();
627
+ });
628
+
629
+ // --- apply labelmap opacity --- //
630
+
631
+ watchEffect(() => {
632
+ const { labelmapOpacity } = paintStore;
633
+ labelmapReps.value.forEach((lmRep) => {
634
+ lmRep.setOpacity(labelmapOpacity);
635
+ });
636
+ });
637
+
638
+ return {
639
+ vtkContainerRef,
640
+ viewID,
641
+ viewProxy,
642
+ viewAxis,
643
+ active: true,
644
+ slice: currentSlice,
645
+ sliceMin,
646
+ sliceMax,
647
+ windowWidth,
648
+ windowLevel,
649
+ topLabel,
650
+ leftLabel,
651
+ isImageLoading,
652
+ setSlice,
653
+ widgetManager,
654
+ dicomInfo,
655
+ enableResizeToFit() {
656
+ resizeToFit.value = true;
657
+ },
658
+ hover,
659
+ };
660
+ },
661
+ });
662
+ </script>
663
+
664
+ <style scoped src="@/src/components/styles/vtk-view.css"></style>
665
+ <style scoped src="@/src/components/styles/utils.css"></style>
@@ -0,0 +1,32 @@
1
+ import Vue from 'vue';
2
+ import Vuetify from 'vuetify';
3
+ import { expect } from 'chai';
4
+ import { createLocalVue, mount } from '@vue/test-utils';
5
+
6
+ import ResizableNavDrawer, {
7
+ RESIZE_CURSOR,
8
+ } from '@/src/components/ResizableNavDrawer.vue';
9
+
10
+ Vue.use(Vuetify);
11
+
12
+ const localVue = createLocalVue();
13
+
14
+ describe('ResizableNavDrawer.vue', () => {
15
+ let vuetify;
16
+
17
+ beforeEach(() => {
18
+ vuetify = new Vuetify();
19
+ });
20
+
21
+ it('Body styles are applied correctly on resize', () => {
22
+ const wrapper = mount(ResizableNavDrawer, { localVue, vuetify });
23
+
24
+ wrapper.vm.startResize();
25
+ expect(document.body.style.cursor).to.equal(RESIZE_CURSOR);
26
+ expect(document.body.style.userSelect).to.equal('none');
27
+
28
+ wrapper.vm.stopResize();
29
+ expect(document.body.style.cursor).to.be.empty;
30
+ expect(document.body.style.userSelect).to.be.empty;
31
+ });
32
+ });