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,144 @@
1
+ import { defineStore } from 'pinia';
2
+ import Vue from 'vue';
3
+
4
+ export enum MessageType {
5
+ Error,
6
+ Warning,
7
+ Info,
8
+ Success,
9
+ }
10
+
11
+ export type MessageOptions = {
12
+ details?: string;
13
+ persist?: boolean;
14
+ };
15
+
16
+ export interface Message {
17
+ id: string;
18
+ type: MessageType;
19
+ title: string;
20
+ options: MessageOptions;
21
+ }
22
+
23
+ export type UpdateProgressFunction = (progress: number) => void;
24
+ export type TaskFunction = (updateProgress?: UpdateProgressFunction) => any;
25
+ interface State {
26
+ _nextID: number;
27
+ byID: Record<string, Message>;
28
+ msgList: string[];
29
+ }
30
+
31
+ export const useMessageStore = defineStore('message', {
32
+ state: (): State => ({
33
+ _nextID: 1,
34
+ byID: {},
35
+ msgList: [],
36
+ }),
37
+ getters: {
38
+ messages(): Array<Message> {
39
+ return this.msgList.map((id: string) => this.byID[id]);
40
+ },
41
+ // only info, error, warn
42
+ importantMessages(): Array<Message> {
43
+ return this.messages.filter((msg) => msg.type !== MessageType.Success);
44
+ },
45
+ },
46
+ actions: {
47
+ /**
48
+ * Adds an error message.
49
+ * @param title message title
50
+ * @param opts an Error, a string containing details, or a MessageOptions
51
+ */
52
+ addError(title: string, opts?: Error | string | MessageOptions) {
53
+ if (opts instanceof Error) {
54
+ return this._addMessage(
55
+ {
56
+ type: MessageType.Error,
57
+ title,
58
+ },
59
+ {
60
+ details: String(opts),
61
+ persist: false,
62
+ }
63
+ );
64
+ }
65
+ return this._addMessage(
66
+ {
67
+ type: MessageType.Error,
68
+ title,
69
+ },
70
+ opts
71
+ );
72
+ },
73
+ /**
74
+ * Adds a warning message.
75
+ * @param title message title
76
+ * @param opts a string containing details or a MessageOptions
77
+ */
78
+ addWarning(title: string, details?: string | MessageOptions) {
79
+ return this._addMessage(
80
+ {
81
+ type: MessageType.Warning,
82
+ title,
83
+ },
84
+ details
85
+ );
86
+ },
87
+ /**
88
+ * Adds a success message.
89
+ * @param title message title
90
+ * @param opts a string containing details or a MessageOptions
91
+ */
92
+ addInfo(title: string, details?: string | MessageOptions) {
93
+ return this._addMessage(
94
+ {
95
+ type: MessageType.Info,
96
+ title,
97
+ },
98
+ details
99
+ );
100
+ },
101
+ /**
102
+ * Adds a success message.
103
+ * @param title message title
104
+ * @param opts a string containing details or a MessageOptions
105
+ */
106
+ addSuccess(title: string, details?: string | MessageOptions) {
107
+ return this._addMessage(
108
+ {
109
+ type: MessageType.Success,
110
+ title,
111
+ },
112
+ details
113
+ );
114
+ },
115
+ clearOne(id: string) {
116
+ if (id in this.byID) {
117
+ Vue.delete(this.byID, id);
118
+ const idx = this.msgList.indexOf(id);
119
+ this.msgList.splice(idx, 1);
120
+ }
121
+ },
122
+ clearAll() {
123
+ this.byID = {};
124
+ this.msgList = [];
125
+ },
126
+ _addMessage(
127
+ msg: Omit<Message, 'id' | 'options'>,
128
+ details?: string | MessageOptions
129
+ ) {
130
+ const id = String(this._nextID++);
131
+ const options: MessageOptions = {
132
+ persist: false,
133
+ ...(typeof details === 'string' ? { details } : details),
134
+ };
135
+ Vue.set(this.byID, id, {
136
+ ...msg,
137
+ options,
138
+ id,
139
+ });
140
+ this.msgList.push(id);
141
+ return id;
142
+ },
143
+ },
144
+ });
@@ -0,0 +1,136 @@
1
+ import { getImageSpatialExtent } from '@/src/composables/useCurrentImage';
2
+ import { LPSAxis } from '@/src/types/lps';
3
+ import { getAxisBounds } from '@/src/utils/lps';
4
+ import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
5
+ import { Vector2, Vector3 } from '@kitware/vtk.js/types';
6
+ import { computed, reactive, readonly, set, unref } from '@vue/composition-api';
7
+ import { MaybeRef } from '@vueuse/core';
8
+ import { vec3 } from 'gl-matrix';
9
+ import { defineStore } from 'pinia';
10
+ import { ImageMetadata, useImageStore } from '../datasets-images';
11
+ import { LPSCroppingPlanes } from './types';
12
+ import { StateFile, Manifest } from '../../io/state-file/schema';
13
+
14
+ function clampRangeToBounds(range: Vector2, bounds: Vector2) {
15
+ return [
16
+ Math.max(bounds[0], Math.min(bounds[1], range[0])),
17
+ Math.max(bounds[0], Math.min(bounds[1], range[1])),
18
+ ] as Vector2;
19
+ }
20
+
21
+ function convertCropBoundsToVTKPlane(
22
+ cropBounds: LPSCroppingPlanes,
23
+ metadata: ImageMetadata,
24
+ axis: LPSAxis,
25
+ lowerUpper: 0 | 1
26
+ ) {
27
+ const { indexToWorld, orientation, lpsOrientation: lpsDirs } = metadata;
28
+
29
+ const origin = [0, 0, 0] as Vector3;
30
+ const axisIndex = lpsDirs[axis];
31
+ origin[axisIndex] = cropBounds[axis][lowerUpper];
32
+ vec3.transformMat4(origin, origin, indexToWorld);
33
+
34
+ // The lower bound normal is the associated column in the
35
+ // image orientation matrix. The upper bound normal is the
36
+ // lower bound normal, but negated.
37
+ const neg = -(lowerUpper * 2 - 1);
38
+ const normal = [
39
+ ...orientation.slice(axisIndex * 3, axisIndex * 3 + 3).map((c) => c * neg),
40
+ ] as Vector3;
41
+
42
+ return vtkPlane.newInstance({ origin, normal });
43
+ }
44
+
45
+ export const useCropStore = defineStore('crop', () => {
46
+ const imageStore = useImageStore();
47
+
48
+ const state = reactive({
49
+ croppingByImageID: {} as Record<string, LPSCroppingPlanes>,
50
+ });
51
+
52
+ const getComputedVTKPlanes = (imageID: MaybeRef<string | null>) =>
53
+ computed(() => {
54
+ const id = unref(imageID);
55
+ if (id && id in state.croppingByImageID && id in imageStore.metadata) {
56
+ const cropBounds = state.croppingByImageID[id];
57
+ const metadata = imageStore.metadata[id];
58
+ return [
59
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Sagittal', 0),
60
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Sagittal', 1),
61
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Coronal', 0),
62
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Coronal', 1),
63
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Axial', 0),
64
+ convertCropBoundsToVTKPlane(cropBounds, metadata, 'Axial', 1),
65
+ ];
66
+ }
67
+ return null;
68
+ });
69
+
70
+ const clampCroppingPlanes = (
71
+ imageID: string,
72
+ planes: LPSCroppingPlanes
73
+ ): LPSCroppingPlanes => {
74
+ const lpsBounds = getImageSpatialExtent(imageID);
75
+ // if perf becomes an issue, change this to modify the planes arg
76
+ return {
77
+ Sagittal: clampRangeToBounds(planes.Sagittal, lpsBounds.Sagittal),
78
+ Coronal: clampRangeToBounds(planes.Coronal, lpsBounds.Coronal),
79
+ Axial: clampRangeToBounds(planes.Axial, lpsBounds.Axial),
80
+ };
81
+ };
82
+
83
+ const setCropping = (imageID: string, planes: LPSCroppingPlanes) => {
84
+ set(state.croppingByImageID, imageID, clampCroppingPlanes(imageID, planes));
85
+ };
86
+
87
+ const setCroppingForAxis = (
88
+ imageID: string,
89
+ axis: LPSAxis,
90
+ planes: Vector2
91
+ ) => {
92
+ if (imageID in state.croppingByImageID) {
93
+ setCropping(imageID, {
94
+ ...state.croppingByImageID[imageID],
95
+ [axis]: planes,
96
+ });
97
+ }
98
+ };
99
+
100
+ const resetCropping = (imageID: string) => {
101
+ const image = imageStore.dataIndex[imageID];
102
+ if (!image) return;
103
+
104
+ const { lpsOrientation } = imageStore.metadata[imageID];
105
+ const extent = image.getSpatialExtent();
106
+ setCropping(imageID, {
107
+ Sagittal: getAxisBounds(extent, 'Sagittal', lpsOrientation),
108
+ Coronal: getAxisBounds(extent, 'Coronal', lpsOrientation),
109
+ Axial: getAxisBounds(extent, 'Axial', lpsOrientation),
110
+ });
111
+ };
112
+
113
+ function serialize(stateFile: StateFile) {
114
+ const { tools } = stateFile.manifest;
115
+ tools.crop = state.croppingByImageID;
116
+ }
117
+
118
+ function deserialize(manifest: Manifest, dataIDMap: Record<string, string>) {
119
+ const cropping = manifest.tools.crop;
120
+
121
+ Object.entries(cropping).forEach(([imageID, planes]) => {
122
+ const newImageID = dataIDMap[imageID];
123
+ setCropping(newImageID, planes);
124
+ });
125
+ }
126
+
127
+ return {
128
+ croppingByImageID: readonly(state.croppingByImageID),
129
+ getComputedVTKPlanes,
130
+ setCropping,
131
+ setCroppingForAxis,
132
+ resetCropping,
133
+ serialize,
134
+ deserialize,
135
+ };
136
+ });
@@ -0,0 +1,131 @@
1
+ import { useCurrentImage } from '@/src/composables/useCurrentImage';
2
+ import vtkCrosshairsWidget from '@/src/vtk/CrosshairsWidget';
3
+ import { Bounds, Vector3 } from '@kitware/vtk.js/types';
4
+ import { inflate } from '@kitware/vtk.js/Common/DataModel/BoundingBox';
5
+ import { computed, ref, unref, watch } from '@vue/composition-api';
6
+ import { vec3 } from 'gl-matrix';
7
+ import { defineStore } from 'pinia';
8
+ import { getLPSAxisFromDir } from '@/src/utils/lps';
9
+ import { Manifest, StateFile } from '@/src/io/state-file/schema';
10
+ import { useViewConfigStore } from '../view-configs';
11
+ import { useViewStore } from '../views';
12
+
13
+ export const useCrosshairsToolStore = defineStore('crosshairs', () => {
14
+ type _This = ReturnType<typeof useCrosshairsToolStore>;
15
+
16
+ const factory = vtkCrosshairsWidget.newInstance();
17
+ const widgetState = factory.getWidgetState();
18
+ const handle = widgetState.getHandle();
19
+
20
+ const active = ref(false);
21
+ const { currentImageID, currentImageMetadata } = useCurrentImage();
22
+
23
+ // world-space
24
+ const position = ref<Vector3>([0, 0, 0]);
25
+ // image space
26
+ const imagePosition = computed(() => {
27
+ const out = vec3.create();
28
+ vec3.transformMat4(
29
+ out,
30
+ position.value,
31
+ currentImageMetadata.value.worldToIndex
32
+ );
33
+ return out as Vector3;
34
+ });
35
+
36
+ const viewConfigStore = useViewConfigStore();
37
+ const viewStore = useViewStore();
38
+
39
+ // only gets views that have a slicing config
40
+ const currentViewIDs = computed(() => {
41
+ const imageID = unref(currentImageID);
42
+ if (imageID) {
43
+ return viewStore.viewIDs.filter(
44
+ (viewID) => !!viewConfigStore.getSliceConfig(viewID, imageID)
45
+ );
46
+ }
47
+ return [];
48
+ });
49
+
50
+ function getWidgetFactory(this: _This) {
51
+ return factory;
52
+ }
53
+
54
+ function setPosition(pos: Vector3) {
55
+ position.value = pos;
56
+ }
57
+
58
+ // update the slicing
59
+ watch(imagePosition, (indexPos) => {
60
+ if (active.value) {
61
+ const imageID = unref(currentImageID);
62
+ const { lpsOrientation } = unref(currentImageMetadata);
63
+
64
+ if (!imageID) {
65
+ return;
66
+ }
67
+
68
+ currentViewIDs.value.forEach((viewID) => {
69
+ const sliceConfig = viewConfigStore.getSliceConfig(viewID, imageID);
70
+ const axis = getLPSAxisFromDir(sliceConfig!.axisDirection);
71
+ const index = lpsOrientation[axis];
72
+ const slice = Math.round(indexPos[index]);
73
+ viewConfigStore.updateSliceConfig(viewID, imageID, { slice });
74
+ });
75
+ }
76
+ });
77
+
78
+ // update widget state based on current image
79
+ watch(
80
+ currentImageMetadata,
81
+ (metadata) => {
82
+ widgetState.setIndexToWorld(metadata.indexToWorld);
83
+ widgetState.setWorldToIndex(metadata.worldToIndex);
84
+ const [xDim, yDim, zDim] = metadata.dimensions;
85
+ const imageBounds: Bounds = [0, xDim - 1, 0, yDim - 1, 0, zDim - 1];
86
+ // inflate by 0.5, since the image slice rendering is inflated
87
+ // by 0.5.
88
+ handle.setBounds(inflate(imageBounds, 0.5));
89
+ },
90
+ { immediate: true }
91
+ );
92
+
93
+ // update the position
94
+ handle.onModified(() => {
95
+ const origin = handle.getOrigin();
96
+ if (origin) {
97
+ position.value = origin;
98
+ }
99
+ });
100
+
101
+ function activateTool() {
102
+ widgetState.setPlaced(false);
103
+ active.value = true;
104
+ return true;
105
+ }
106
+
107
+ function deactivateTool() {
108
+ active.value = false;
109
+ }
110
+
111
+ function serialize(state: StateFile) {
112
+ const { crosshairs } = state.manifest.tools;
113
+ crosshairs.position = position.value;
114
+ }
115
+
116
+ function deserialize(manifest: Manifest) {
117
+ const { crosshairs } = manifest.tools;
118
+ position.value = crosshairs.position;
119
+ }
120
+
121
+ return {
122
+ getWidgetFactory,
123
+ setPosition,
124
+ position,
125
+ imagePosition,
126
+ activateTool,
127
+ deactivateTool,
128
+ serialize,
129
+ deserialize,
130
+ };
131
+ });
@@ -0,0 +1,95 @@
1
+ import { Manifest, StateFile } from '@/src/io/state-file/schema';
2
+ import { defineStore } from 'pinia';
3
+ import { useCropStore } from './crop';
4
+ import { useCrosshairsToolStore } from './crosshairs';
5
+ import { usePaintToolStore } from './paint';
6
+ import { useRulerStore } from './rulers';
7
+ import { Tools } from './types';
8
+
9
+ interface State {
10
+ currentTool: Tools;
11
+ }
12
+
13
+ export interface IToolStore {
14
+ activateTool: () => boolean;
15
+ deactivateTool: () => void;
16
+ }
17
+
18
+ function getStore(tool: Tools): IToolStore | null {
19
+ if (tool === Tools.Ruler) {
20
+ return useRulerStore();
21
+ }
22
+ if (tool === Tools.Paint) {
23
+ return usePaintToolStore();
24
+ }
25
+ if (tool === Tools.Crosshairs) {
26
+ return useCrosshairsToolStore();
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Returns true if the tool is ready to be
33
+ * activated. By default, a tool without a
34
+ * store setup() will be activated.
35
+ */
36
+ function setupTool(tool: Tools) {
37
+ const store = getStore(tool);
38
+ if (store) {
39
+ return store.activateTool();
40
+ }
41
+ return true;
42
+ }
43
+
44
+ function teardownTool(tool: Tools) {
45
+ const store = getStore(tool);
46
+ if (store) {
47
+ store.deactivateTool();
48
+ }
49
+ }
50
+
51
+ export const useToolStore = defineStore('tool', {
52
+ state: (): State => ({
53
+ currentTool: Tools.WindowLevel,
54
+ }),
55
+ actions: {
56
+ setCurrentTool(tool: Tools) {
57
+ if (!setupTool(tool)) {
58
+ return;
59
+ }
60
+ teardownTool(this.currentTool);
61
+ this.currentTool = tool;
62
+ },
63
+ serialize(state: StateFile) {
64
+ const { tools } = state.manifest;
65
+ const rulerStore = useRulerStore();
66
+ const crosshairsStore = useCrosshairsToolStore();
67
+ const paintStore = usePaintToolStore();
68
+ const cropStore = useCropStore();
69
+
70
+ rulerStore.serialize(state);
71
+ crosshairsStore.serialize(state);
72
+ paintStore.serialize(state);
73
+ cropStore.serialize(state);
74
+
75
+ tools.current = this.currentTool;
76
+ },
77
+ deserialize(
78
+ manifest: Manifest,
79
+ labelmapIDMap: Record<string, string>,
80
+ dataIDMap: Record<string, string>
81
+ ) {
82
+ const { tools } = manifest;
83
+ const rulerStore = useRulerStore();
84
+ const crosshairsStore = useCrosshairsToolStore();
85
+ const paintStore = usePaintToolStore();
86
+ const cropStore = useCropStore();
87
+
88
+ rulerStore.deserialize(manifest);
89
+ crosshairsStore.deserialize(manifest);
90
+ paintStore.deserialize(manifest, labelmapIDMap);
91
+ cropStore.deserialize(manifest, dataIDMap);
92
+ this.currentTool = tools.current;
93
+ },
94
+ },
95
+ });
@@ -0,0 +1,191 @@
1
+ import { Vector2 } from '@kitware/vtk.js/types';
2
+ import { useCurrentImage } from '@/src/composables/useCurrentImage';
3
+ import { Manifest, StateFile } from '@/src/io/state-file/schema';
4
+ import { computed, ref, watch } from '@vue/composition-api';
5
+ import { vec3 } from 'gl-matrix';
6
+ import { defineStore } from 'pinia';
7
+ import { Tools } from './types';
8
+ import { useLabelmapStore } from '../datasets-labelmaps';
9
+
10
+ const DEFAULT_BRUSH_SIZE = 4;
11
+ const DEFAULT_BRUSH_VALUE = 1;
12
+
13
+ export const usePaintToolStore = defineStore('paint', () => {
14
+ type _This = ReturnType<typeof usePaintToolStore>;
15
+
16
+ const activeLabelmapID = ref<string | null>(null);
17
+ const brushSize = ref(DEFAULT_BRUSH_SIZE);
18
+ const brushValue = ref(DEFAULT_BRUSH_VALUE);
19
+ const strokePoints = ref<vec3[]>([]);
20
+ const labelmapOpacity = ref(1);
21
+ const isActive = ref(false);
22
+
23
+ const { currentImageID } = useCurrentImage();
24
+
25
+ function getWidgetFactory(this: _This) {
26
+ return this.$paint.factory;
27
+ }
28
+
29
+ const activeLabelmap = computed(() => {
30
+ if (!activeLabelmapID.value) return null;
31
+ const labelmapStore = useLabelmapStore();
32
+ return labelmapStore.labelmaps[activeLabelmapID.value] ?? null;
33
+ });
34
+
35
+ // --- actions --- //
36
+
37
+ function selectOrCreateLabelmap(imageID: string | null) {
38
+ if (!imageID) {
39
+ activeLabelmapID.value = null;
40
+ return;
41
+ }
42
+
43
+ const labelmapStore = useLabelmapStore();
44
+ const found = Object.entries(labelmapStore.parentImage).find(
45
+ ([, parentID]) => imageID === parentID
46
+ );
47
+ if (found) {
48
+ [activeLabelmapID.value] = found;
49
+ } else {
50
+ activeLabelmapID.value = labelmapStore.newLabelmapFromImage(imageID);
51
+ }
52
+ }
53
+
54
+ function setBrushSize(this: _This, size: number) {
55
+ brushSize.value = Math.round(size);
56
+ this.$paint.setBrushSize(size);
57
+ }
58
+
59
+ function setBrushValue(this: _This, value: number) {
60
+ brushValue.value = value;
61
+ this.$paint.setBrushValue(value);
62
+ }
63
+
64
+ function setLabelmapOpacity(opacity: number) {
65
+ labelmapOpacity.value = Math.min(1, Math.max(0, opacity));
66
+ }
67
+
68
+ function doPaintStroke(this: _This, axisIndex: 0 | 1 | 2) {
69
+ if (!activeLabelmap.value) {
70
+ return;
71
+ }
72
+
73
+ const lastIndex = strokePoints.value.length - 1;
74
+ if (lastIndex >= 0) {
75
+ const lastPoint = strokePoints.value[lastIndex];
76
+ const prevPoint =
77
+ lastIndex >= 1 ? strokePoints.value[lastIndex - 1] : undefined;
78
+ this.$paint.paintLabelmap(
79
+ activeLabelmap.value,
80
+ axisIndex,
81
+ lastPoint,
82
+ prevPoint
83
+ );
84
+ }
85
+ }
86
+
87
+ function setSliceAxis(this: _This, axisIndex: 0 | 1 | 2) {
88
+ if (!activeLabelmap.value) return;
89
+ const spacing = activeLabelmap.value.getSpacing();
90
+ spacing.splice(axisIndex, 1);
91
+ const scale: Vector2 = [1 / spacing[0], 1 / spacing[1]];
92
+ this.$paint.setBrushScale(scale);
93
+ }
94
+
95
+ function startStroke(this: _This, indexPoint: vec3, axisIndex: 0 | 1 | 2) {
96
+ strokePoints.value = [vec3.clone(indexPoint)];
97
+ doPaintStroke.call(this, axisIndex);
98
+ }
99
+
100
+ function placeStrokePoint(
101
+ this: _This,
102
+ indexPoint: vec3,
103
+ axisIndex: 0 | 1 | 2
104
+ ) {
105
+ strokePoints.value.push(indexPoint);
106
+ doPaintStroke.call(this, axisIndex);
107
+ }
108
+
109
+ function endStroke(this: _This, indexPoint: vec3, axisIndex: 0 | 1 | 2) {
110
+ strokePoints.value.push(indexPoint);
111
+ doPaintStroke.call(this, axisIndex);
112
+ }
113
+
114
+ // --- setup and teardown --- //
115
+
116
+ function activateTool(this: _This) {
117
+ const imageID = currentImageID.value;
118
+ if (!imageID) {
119
+ return false;
120
+ }
121
+ selectOrCreateLabelmap(imageID);
122
+ this.$paint.setBrushSize(this.brushSize);
123
+
124
+ isActive.value = true;
125
+ return true;
126
+ }
127
+
128
+ function deactivateTool() {
129
+ activeLabelmapID.value = null;
130
+ isActive.value = false;
131
+ }
132
+
133
+ function serialize(state: StateFile) {
134
+ const { paint } = state.manifest.tools;
135
+
136
+ paint.activeLabelmapID = activeLabelmapID.value;
137
+ paint.brushSize = brushSize.value;
138
+ paint.brushValue = brushValue.value;
139
+ paint.labelmapOpacity = labelmapOpacity.value;
140
+ }
141
+
142
+ function deserialize(
143
+ this: _This,
144
+ manifest: Manifest,
145
+ labelmapIDMap: Record<string, string>
146
+ ) {
147
+ const { paint } = manifest.tools;
148
+ this.setBrushSize(paint.brushSize);
149
+ this.setBrushValue(paint.brushValue);
150
+ this.setLabelmapOpacity(paint.labelmapOpacity);
151
+ isActive.value = manifest.tools.current === Tools.Paint;
152
+
153
+ if (paint.activeLabelmapID !== null) {
154
+ activeLabelmapID.value = labelmapIDMap[paint.activeLabelmapID];
155
+ }
156
+ }
157
+
158
+ // --- change labelmap if paint is active --- //
159
+
160
+ watch(currentImageID, (imageID) => {
161
+ if (isActive.value) {
162
+ selectOrCreateLabelmap(imageID);
163
+ }
164
+ });
165
+
166
+ return {
167
+ // state
168
+ activeLabelmapID,
169
+ brushSize,
170
+ brushValue,
171
+ strokePoints,
172
+ labelmapOpacity,
173
+ isActive,
174
+
175
+ getWidgetFactory,
176
+
177
+ activateTool,
178
+ deactivateTool,
179
+
180
+ selectOrCreateLabelmap,
181
+ setBrushSize,
182
+ setBrushValue,
183
+ setLabelmapOpacity,
184
+ setSliceAxis,
185
+ startStroke,
186
+ placeStrokePoint,
187
+ endStroke,
188
+ serialize,
189
+ deserialize,
190
+ };
191
+ });