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,198 @@
1
+ <script lang="ts">
2
+ import {
3
+ set,
4
+ del,
5
+ defineComponent,
6
+ reactive,
7
+ computed,
8
+ } from '@vue/composition-api';
9
+ import ImageListCard from '@/src/components/ImageListCard.vue';
10
+ import { SAMPLE_DATA } from '../config';
11
+ import {
12
+ convertSuccessResultToDataSelection,
13
+ useDatasetStore,
14
+ } from '../store/datasets';
15
+ import { useMessageStore } from '../store/messages';
16
+ import { SampleDataset } from '../types';
17
+ import { useImageStore } from '../store/datasets-images';
18
+ import { useDICOMStore } from '../store/datasets-dicom';
19
+ import { fetchFile } from '../utils/fetch';
20
+
21
+ enum ProgressState {
22
+ Pending,
23
+ Error,
24
+ Done,
25
+ }
26
+
27
+ interface Progress {
28
+ [name: string]: {
29
+ state: ProgressState;
30
+ progress: number;
31
+ };
32
+ }
33
+
34
+ export default defineComponent({
35
+ components: {
36
+ ImageListCard,
37
+ },
38
+ setup() {
39
+ const datasetStore = useDatasetStore();
40
+ const status = reactive<{ progress: Progress }>({
41
+ progress: {},
42
+ });
43
+ // URL -> data ID
44
+ const loaded = reactive<{
45
+ urlToID: Record<string, string>;
46
+ idToURL: Record<string, string>;
47
+ }>({
48
+ urlToID: {},
49
+ idToURL: {},
50
+ });
51
+
52
+ const clearLoadedStatus = (id: string) => {
53
+ const url = loaded.idToURL[id];
54
+ if (url) {
55
+ del(loaded.idToURL, id);
56
+ del(loaded.urlToID, url);
57
+ }
58
+ };
59
+
60
+ const imageStore = useImageStore();
61
+ imageStore.$onAction(({ name, args, after }) => {
62
+ if (name === 'deleteData') {
63
+ after(() => {
64
+ const [id] = args;
65
+ clearLoadedStatus(id as string);
66
+ });
67
+ }
68
+ });
69
+
70
+ const dicomStore = useDICOMStore();
71
+ dicomStore.$onAction(({ name, args, after }) => {
72
+ if (name === 'deleteVolume') {
73
+ after(() => {
74
+ const [key] = args;
75
+ clearLoadedStatus(key as string);
76
+ });
77
+ }
78
+ });
79
+
80
+ async function downloadSample(sample: SampleDataset) {
81
+ const progress = (percent: number) => {
82
+ // TODO set/del doesn't exist in vue 3
83
+ set(status.progress, sample.name, {
84
+ state: ProgressState.Pending,
85
+ progress: percent * 100,
86
+ });
87
+ };
88
+ try {
89
+ progress(Infinity);
90
+
91
+ const sampleFile = await fetchFile(sample.url, sample.filename, {
92
+ progress,
93
+ });
94
+ status.progress[sample.name].state = ProgressState.Done;
95
+
96
+ if (sampleFile) {
97
+ const [loadResult] = await datasetStore.loadFiles([sampleFile]);
98
+ if (loadResult?.loaded) {
99
+ const selection = convertSuccessResultToDataSelection(loadResult);
100
+ if (selection) {
101
+ const id =
102
+ selection.type === 'image'
103
+ ? selection.dataID
104
+ : selection.volumeKey;
105
+ set(loaded.idToURL, id, sample.url);
106
+ set(loaded.urlToID, sample.url, id);
107
+ }
108
+ datasetStore.setPrimarySelection(selection);
109
+ }
110
+ }
111
+ } catch (error) {
112
+ status.progress[sample.name].state = ProgressState.Error;
113
+ const messageStore = useMessageStore();
114
+ messageStore.addError('Failed to load sample data', error as Error);
115
+ } finally {
116
+ del(status.progress, sample.name);
117
+ }
118
+ }
119
+
120
+ const samples = computed(() =>
121
+ SAMPLE_DATA.map((sample) => {
122
+ const isDone =
123
+ status.progress[sample.name]?.state === ProgressState.Done;
124
+ const isError =
125
+ status.progress[sample.name]?.state === ProgressState.Error;
126
+ const isLoaded = !!loaded.urlToID[sample.url];
127
+ const progress =
128
+ isDone || isLoaded
129
+ ? 100
130
+ : Math.floor(status.progress[sample.name]?.progress);
131
+ return {
132
+ ...sample,
133
+ isDownloading: sample.name in status.progress,
134
+ isDone,
135
+ isError,
136
+ isLoaded,
137
+ indeterminate: progress === Infinity,
138
+ progress,
139
+ };
140
+ })
141
+ );
142
+
143
+ return {
144
+ samples,
145
+ downloadSample,
146
+ };
147
+ },
148
+ });
149
+ </script>
150
+
151
+ <template>
152
+ <div>
153
+ <image-list-card
154
+ v-for="sample in samples"
155
+ :disabled="sample.isDownloading || sample.isLoaded"
156
+ :key="sample.name"
157
+ :title="sample.name"
158
+ :image-url="sample.image"
159
+ :image-size="100"
160
+ @click="downloadSample(sample)"
161
+ >
162
+ <div class="body-2 font-weight-bold text-no-wrap text-truncate">
163
+ {{ sample.name }}
164
+ </div>
165
+ <div class="body-2 mt-2">{{ sample.description }}</div>
166
+ <template
167
+ v-slot:image-overlay
168
+ v-if="sample.isDownloading || sample.isLoaded"
169
+ >
170
+ <v-row class="fill-height ma-0" align="center" justify="center">
171
+ <v-progress-circular
172
+ color="white"
173
+ :indeterminate="sample.indeterminate && !sample.isDone"
174
+ :value="sample.progress"
175
+ >
176
+ <v-icon v-if="sample.isDone || sample.isLoaded" small color="white"
177
+ >mdi-check</v-icon
178
+ >
179
+ <v-icon v-else-if="sample.isError" small color="white">
180
+ mdi-alert-circle
181
+ </v-icon>
182
+ <span v-else-if="!sample.indeterminate" class="caption text--white">
183
+ {{ sample.progress }}
184
+ </span>
185
+ </v-progress-circular>
186
+ </v-row>
187
+ </template>
188
+ </image-list-card>
189
+ </div>
190
+ </template>
191
+
192
+ <style>
193
+ /* The transition animation causes the progress bar to load slowly
194
+ when the value is updated rapidly. */
195
+ .v-progress-circular__overlay {
196
+ transition: unset;
197
+ }
198
+ </style>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <v-card>
3
+ <v-card-title>
4
+ Saving Session State
5
+ <v-spacer />
6
+ <v-btn icon @click="$emit('close')"><v-icon>mdi-close</v-icon></v-btn>
7
+ </v-card-title>
8
+ <v-card-text>
9
+ <v-form v-model="valid">
10
+ <v-text-field
11
+ v-on:keyup.enter="saveSession"
12
+ v-model="fileName"
13
+ hint="The filename to use for the session state file."
14
+ label="Session State Filename"
15
+ :rules="[validFileName]"
16
+ />
17
+ </v-form>
18
+ </v-card-text>
19
+ <v-card-actions>
20
+ <v-spacer />
21
+ <v-btn color="secondary" @click="saveSession" :disabled="!valid">
22
+ <v-icon class="mr-2">mdi-content-save-all</v-icon>
23
+ <span>Save</span>
24
+ </v-btn>
25
+ </v-card-actions>
26
+ </v-card>
27
+ </template>
28
+
29
+ <script lang="ts">
30
+ import { defineComponent, ref } from '@vue/composition-api';
31
+ import { save } from '../io/state-file';
32
+
33
+ const DEFAULT_FILENAME = 'session.volview.zip';
34
+
35
+ export default defineComponent({
36
+ setup(props, { emit }) {
37
+ const fileName = ref(DEFAULT_FILENAME);
38
+ const valid = ref(true);
39
+
40
+ async function saveSession() {
41
+ await save(fileName.value);
42
+ emit('close');
43
+ }
44
+
45
+ function validFileName(name: string) {
46
+ return name.trim().length > 0 || 'Required';
47
+ }
48
+
49
+ return {
50
+ saveSession,
51
+ fileName,
52
+ validFileName,
53
+ valid,
54
+ };
55
+ },
56
+ });
57
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-card>
3
+ <v-card-title>
4
+ Settings
5
+ <v-spacer />
6
+ <v-btn icon @click="$emit('close')"><v-icon>mdi-close</v-icon></v-btn>
7
+ </v-card-title>
8
+ <v-card-text>
9
+ <v-switch label="Enable Dark Theme" v-model="dark"></v-switch>
10
+ </v-card-text>
11
+ </v-card>
12
+ </template>
13
+
14
+ <script lang="ts">
15
+ import { defineComponent, watchEffect } from '@vue/composition-api';
16
+ import { useLocalStorage } from '@vueuse/core';
17
+ import vuetify from '../plugins/vuetify';
18
+
19
+ export default defineComponent({
20
+ setup() {
21
+ const store = useLocalStorage<boolean>(
22
+ 'dark',
23
+ vuetify.framework.theme.dark ?? true
24
+ );
25
+
26
+ watchEffect(() => {
27
+ vuetify.framework.theme.dark = store.value;
28
+ });
29
+
30
+ return {
31
+ dark: store,
32
+ };
33
+ },
34
+ });
35
+ </script>
@@ -0,0 +1,182 @@
1
+ <template>
2
+ <div
3
+ class="slice-slider"
4
+ ref="handleContainer"
5
+ @pointerdown="onDragStart"
6
+ @pointermove="onDragMove"
7
+ @pointerup="onDragEnd"
8
+ @pointercancel="onDragEnd"
9
+ @contextmenu="$event.preventDefault()"
10
+ >
11
+ <div class="slice-slider-track" />
12
+ <div
13
+ class="slice-slider-handle"
14
+ ref="handle"
15
+ :style="{
16
+ height: `${handleHeight}px`,
17
+ transform: `translate3d(0, ${handlePosition}px, 0)`,
18
+ }"
19
+ />
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ function getYOffsetFromTransform(matStr) {
25
+ if (!matStr || matStr === 'none') {
26
+ return 0;
27
+ }
28
+ const values = matStr
29
+ .match(/matrix(?:3d)?\((.+)\)/)[1]
30
+ .split(',')
31
+ .map((v) => Number(v));
32
+ const is3D = matStr.includes('3d');
33
+ return is3D ? values[13] : values[5];
34
+ }
35
+
36
+ export default {
37
+ props: {
38
+ slice: {
39
+ type: Number,
40
+ default: 0,
41
+ },
42
+ min: {
43
+ type: Number,
44
+ required: true,
45
+ },
46
+ max: {
47
+ type: Number,
48
+ required: true,
49
+ },
50
+ step: {
51
+ type: Number,
52
+ required: true,
53
+ },
54
+ handleHeight: {
55
+ type: Number,
56
+ default: 20,
57
+ },
58
+ },
59
+
60
+ data() {
61
+ return {
62
+ maxHandlePos: 0,
63
+ dragging: false,
64
+ initialHandlePos: 0,
65
+ initialMousePosY: 0,
66
+ yOffset: 0,
67
+ };
68
+ },
69
+
70
+ computed: {
71
+ handlePosition() {
72
+ const range = this.max - this.min <= 0 ? 1 : this.max - this.min;
73
+ const pos = this.maxHandlePos * ((this.slice - this.min) / range);
74
+ return this.dragging ? this.draggingHandlePos : pos;
75
+ },
76
+ draggingHandlePos() {
77
+ return Math.min(
78
+ Math.max(0, this.initialHandlePos + this.yOffset),
79
+ this.maxHandlePos
80
+ );
81
+ },
82
+ },
83
+
84
+ mounted() {
85
+ this.updateMaxHandlePos();
86
+ this.resizeObserver = new ResizeObserver((entries) => {
87
+ if (entries.length === 1) {
88
+ this.updateMaxHandlePos();
89
+ }
90
+ });
91
+ this.resizeObserver.observe(this.$refs.handleContainer);
92
+ },
93
+
94
+ beforeDestroy() {
95
+ this.resizeObserver.disconnect();
96
+ },
97
+
98
+ methods: {
99
+ updateMaxHandlePos() {
100
+ this.maxHandlePos =
101
+ this.$refs.handleContainer.clientHeight - this.handleHeight;
102
+ },
103
+
104
+ onDragStart(ev) {
105
+ ev.preventDefault();
106
+
107
+ this.dragging = true;
108
+ this.initialMousePosY = ev.pageY;
109
+
110
+ if (ev.target === this.$refs.handle) {
111
+ const handleStyles = window.getComputedStyle(this.$refs.handle);
112
+ this.initialHandlePos = getYOffsetFromTransform(handleStyles.transform);
113
+ } else {
114
+ // move handle to mouse pos
115
+ const { y } = this.$refs.handleContainer.getBoundingClientRect();
116
+ this.initialHandlePos = Math.max(
117
+ 0,
118
+ Math.min(this.maxHandlePos, ev.pageY - y - this.handleHeight / 2)
119
+ );
120
+ const newSlice = this.getNearestSlice(this.initialHandlePos);
121
+ this.$emit('input', newSlice);
122
+ }
123
+
124
+ this.yOffset = 0;
125
+
126
+ this.$refs.handleContainer.setPointerCapture(ev.pointerId);
127
+ },
128
+
129
+ onDragMove(ev) {
130
+ if (!this.$refs.handleContainer.hasPointerCapture(ev.pointerId)) return;
131
+ ev.preventDefault();
132
+
133
+ this.yOffset = ev.pageY - this.initialMousePosY;
134
+ const slice = this.getNearestSlice(this.handlePosition);
135
+ this.$emit('input', slice);
136
+ },
137
+
138
+ onDragEnd(ev) {
139
+ if (!this.$refs.handleContainer.hasPointerCapture(ev.pointerId)) return;
140
+ ev.preventDefault();
141
+ this.$refs.handleContainer.releasePointerCapture(ev.pointerId);
142
+
143
+ this.dragging = false;
144
+ const slice = this.getNearestSlice(this.handlePosition);
145
+ this.$emit('input', slice);
146
+ },
147
+
148
+ getNearestSlice(pos) {
149
+ const sliceEstimate = pos / this.maxHandlePos;
150
+ const frac = sliceEstimate * (this.max - this.min) + this.min;
151
+ return Math.round(frac / this.step) * this.step;
152
+ },
153
+ },
154
+ };
155
+ </script>
156
+
157
+ <style scoped>
158
+ .slice-slider {
159
+ touch-action: none;
160
+ }
161
+
162
+ .slice-slider-handle {
163
+ position: relative;
164
+ width: 100%;
165
+ background: #ccc;
166
+ box-sizing: border-box;
167
+ border-radius: 3px;
168
+ border: 1px solid #999;
169
+ }
170
+
171
+ .slice-slider-track {
172
+ position: absolute;
173
+ margin: 0 auto;
174
+ top: 8px;
175
+ left: 0;
176
+ right: 0;
177
+ bottom: 8px;
178
+ width: 1px;
179
+ border: 1px solid #888;
180
+ border-radius: 2px;
181
+ }
182
+ </style>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div id="spleen-module" class="mx-2 height-100">
3
+ <v-btn
4
+ :disabled="!canSegment || loading"
5
+ :loading="loading"
6
+ @click="segmentCurrentImage"
7
+ >
8
+ Segment
9
+ </v-btn>
10
+ </div>
11
+ </template>
12
+
13
+ <script>
14
+ import { computed, defineComponent, ref, unref } from '@vue/composition-api';
15
+
16
+ import { useStore, useComputedState } from '@/src/composables/store';
17
+ import { useWidgetProvider } from '@/src/composables/widgetProvider';
18
+ import HostConnection from '@/server/host';
19
+ import vtkLabelMap from '@/src/vtk/LabelMap';
20
+ import { DEFAULT_LABELMAP_COLORS } from '@/src/constants';
21
+
22
+ function copyImageToLabelMap(imageData) {
23
+ const labelmap = vtkLabelMap.newInstance(
24
+ imageData.get('spacing', 'origin', 'direction')
25
+ );
26
+ labelmap.setDimensions(imageData.getDimensions());
27
+ labelmap.getPointData().setScalars(imageData.getPointData().getScalars());
28
+ labelmap.computeTransforms();
29
+
30
+ labelmap.setColorMap(DEFAULT_LABELMAP_COLORS);
31
+ return labelmap;
32
+ }
33
+
34
+ export default defineComponent({
35
+ name: 'SpleenModule',
36
+ setup() {
37
+ const loading = ref(false);
38
+
39
+ const store = useStore();
40
+ const widgetProvider = useWidgetProvider();
41
+
42
+ const { currentImageId, currentImage } = useComputedState({
43
+ currentImageId(state) {
44
+ return state.selectedBaseImage;
45
+ },
46
+ currentImage(state) {
47
+ return state.data.vtkCache[state.selectedBaseImage] ?? null;
48
+ },
49
+ });
50
+
51
+ const canSegment = computed(() => Boolean(currentImage.value));
52
+
53
+ const connection = new HostConnection('ws://localhost:8888/ws');
54
+ connection.connect();
55
+
56
+ async function segmentCurrentImage() {
57
+ const curImage = unref(currentImage);
58
+ const curImageId = unref(currentImageId);
59
+ if (curImage) {
60
+ loading.value = true;
61
+
62
+ const result = await connection.call('run', currentImage.value, 44);
63
+ console.log('got result:', result);
64
+ const { segmentation, measurements } = result;
65
+
66
+ const segMap = copyImageToLabelMap(segmentation);
67
+ await store.dispatch('importLabelMap', {
68
+ name: 'whodis',
69
+ labelMap: segMap,
70
+ parent: curImageId,
71
+ });
72
+
73
+ measurements.measurements.map((mm) => {
74
+ const rulerState = {
75
+ version: '1.0',
76
+ type: 'ruler',
77
+ name: mm.name,
78
+ axis: 'IJK'[mm.axis.findIndex((v) => v === 1)],
79
+ slice: mm.slice,
80
+ system: mm.coordinate_system,
81
+ point1: mm.point1,
82
+ point2: mm.point2,
83
+ };
84
+
85
+ return widgetProvider.createWidget('Ruler', {
86
+ initialState: rulerState,
87
+ });
88
+ });
89
+
90
+ loading.value = false;
91
+ }
92
+ }
93
+
94
+ return {
95
+ canSegment,
96
+ segmentCurrentImage,
97
+ loading,
98
+ };
99
+ },
100
+ });
101
+ </script>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <v-tooltip right transition="slide-x-transition">
3
+ <template v-slot:activator="{ on }">
4
+ <v-btn
5
+ text
6
+ tile
7
+ dark
8
+ :height="sizeV"
9
+ :width="sizeV"
10
+ :min-width="sizeV"
11
+ :max-width="sizeV"
12
+ :class="classV"
13
+ v-bind="$attrs"
14
+ v-on="{ ...on, ...$listeners }"
15
+ >
16
+ <v-icon :size="iconSize">{{ icon }}</v-icon>
17
+ <slot />
18
+ </v-btn>
19
+ </template>
20
+ <span>{{ name }}</span>
21
+ </v-tooltip>
22
+ </template>
23
+
24
+ <script>
25
+ export default {
26
+ name: 'ToolButton',
27
+ props: {
28
+ icon: { type: String, required: true },
29
+ name: { type: String, required: true },
30
+ size: { type: [Number, String], default: 40 },
31
+ buttonClass: [String, Array, Object],
32
+ },
33
+
34
+ computed: {
35
+ sizeV() {
36
+ return Number(this.size);
37
+ },
38
+ iconSize() {
39
+ return Math.floor(0.6 * this.sizeV);
40
+ },
41
+ classV() {
42
+ const classSpec = this.buttonClass;
43
+ if (typeof classSpec === 'string') {
44
+ return classSpec;
45
+ }
46
+ if (Array.isArray(classSpec)) {
47
+ return classSpec.join(' ');
48
+ }
49
+ if (classSpec && Object.keys(classSpec).length) {
50
+ return Object.keys(this.buttonClass)
51
+ .filter((key) => this.buttonClass[key])
52
+ .join(' ');
53
+ }
54
+ return '';
55
+ },
56
+ },
57
+ };
58
+ </script>