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,20 @@
1
+ import { Vector2 } from '@kitware/vtk.js/types';
2
+
3
+ export interface IBrushStencil {
4
+ pixels: Uint8Array;
5
+ size: [number, number];
6
+ }
7
+ export interface IPaintBrush {
8
+ /**
9
+ * Sets the size of the stencil, pre-scaling.
10
+ */
11
+ setSize(size: number): void;
12
+ /**
13
+ * Sets the scale of the stencil.
14
+ */
15
+ setScale(scale: Vector2): void;
16
+ /**
17
+ * Returns the stencil with the applied scaling.
18
+ */
19
+ getStencil(): IBrushStencil;
20
+ }
@@ -0,0 +1,96 @@
1
+ import { Vector2 } from '@kitware/vtk.js/types';
2
+ import { IBrushStencil, IPaintBrush } from './brush';
3
+
4
+ // Adapted from http://members.chello.at/easyfilter/bresenham.html
5
+ function rasterizeEllipse(xdiam: number, ydiam: number) {
6
+ const buffer = new Uint8Array(xdiam * ydiam);
7
+
8
+ const putPixel = (x: number, y: number) => {
9
+ buffer[x + y * xdiam] = 1;
10
+ };
11
+
12
+ const blitLine = (x0: number, x1: number, y: number) => {
13
+ const start = y * xdiam + x0;
14
+ const end = start + (x1 - x0 + 1);
15
+ buffer.fill(1, start, end);
16
+ };
17
+
18
+ let x0 = 0;
19
+ let y0 = 0;
20
+ let x1 = xdiam - 1;
21
+ let y1 = ydiam - 1;
22
+
23
+ let a = x1;
24
+ let b = y1;
25
+ // eslint-disable-next-line no-bitwise
26
+ const b1 = b & 1;
27
+ let dx = 4 * (1 - a) * b * b;
28
+ let dy = 4 * (b1 + 1) * a * a;
29
+ let err = dx + dy + b1 * a * a;
30
+ let e2 = 0;
31
+
32
+ y0 += Math.floor((b + 1) / 2);
33
+ y1 = y0 - b1;
34
+ a *= 8 * a;
35
+ b *= 8 * b;
36
+ do {
37
+ blitLine(x0, x1, y0);
38
+ blitLine(x0, x1, y1);
39
+ e2 = 2 * err;
40
+ if (e2 <= dy) {
41
+ y0++;
42
+ y1--;
43
+ dy += a;
44
+ err += dy;
45
+ }
46
+ if (e2 >= dx || 2 * err > dy) {
47
+ x0++;
48
+ x1--;
49
+ dx += b;
50
+ err += dx;
51
+ }
52
+ } while (x0 <= x1);
53
+ while (y0 - y1 < ydiam) {
54
+ putPixel(x0 - 1, y0);
55
+ putPixel(x1 + 1, y0++);
56
+ putPixel(x0 - 1, y1);
57
+ putPixel(x1 + 1, y1--);
58
+ }
59
+
60
+ return buffer;
61
+ }
62
+
63
+ export default class EllipsePaintBrush implements IPaintBrush {
64
+ private size: number = 1;
65
+ private scale: Vector2 = [1, 1];
66
+ private cachedStencil!: IBrushStencil;
67
+
68
+ constructor() {
69
+ this.recomputeStencil();
70
+ }
71
+
72
+ setSize(size: number) {
73
+ this.size = Math.max(0, size);
74
+ this.recomputeStencil();
75
+ }
76
+
77
+ setScale(scale: Vector2) {
78
+ this.scale = scale;
79
+ this.recomputeStencil();
80
+ }
81
+
82
+ private recomputeStencil() {
83
+ const scaledSize = [
84
+ Math.ceil(this.size * this.scale[0]),
85
+ Math.ceil(this.size * this.scale[1]),
86
+ ] as Vector2;
87
+ this.cachedStencil = {
88
+ size: scaledSize,
89
+ pixels: rasterizeEllipse(scaledSize[0], scaledSize[1]),
90
+ };
91
+ }
92
+
93
+ getStencil() {
94
+ return this.cachedStencil;
95
+ }
96
+ }
@@ -0,0 +1,141 @@
1
+ import vtkLabelMap from '@/src/vtk/LabelMap';
2
+ import vtkPaintWidget from '@/src/vtk/PaintWidget';
3
+ import { Vector2 } from '@kitware/vtk.js/types';
4
+ import { vec3 } from 'gl-matrix';
5
+ import { IPaintBrush } from './brush';
6
+ import EllipsePaintBrush from './ellipse-brush';
7
+
8
+ export default class PaintTool {
9
+ readonly factory: vtkPaintWidget;
10
+ private brush: IPaintBrush;
11
+ private brushValue: number;
12
+
13
+ constructor() {
14
+ this.factory = vtkPaintWidget.newInstance();
15
+ this.brush = new EllipsePaintBrush();
16
+ this.brushValue = 1;
17
+ }
18
+
19
+ private updateWidgetStencil() {
20
+ // use unscaled stencil for paint outline
21
+ const stencil = this.brush.getStencil();
22
+ const widgetState = this.factory.getWidgetState();
23
+ widgetState.setStencil(stencil);
24
+ }
25
+
26
+ setBrushSize(size: number) {
27
+ this.brush.setSize(size);
28
+ this.updateWidgetStencil();
29
+ }
30
+
31
+ setBrushScale(scale: Vector2) {
32
+ this.brush.setScale(scale);
33
+ this.updateWidgetStencil();
34
+ }
35
+
36
+ setBrushValue(value: number) {
37
+ this.brushValue = value;
38
+ }
39
+
40
+ /**
41
+ * Adds paint to a labelmap.
42
+ *
43
+ * If endPoint is specified, then linearly interpolates the brush
44
+ * from the start to the end.
45
+ *
46
+ * Assumption: startPoint and endPoint are on the same slice axis.
47
+ *
48
+ * @param labelmap paint in this labelmap
49
+ * @param sliceAxis Which index-space axis to paint on (0, 1, or 2).
50
+ * @param startPoint start point
51
+ * @param endPoint ending point (optional)
52
+ */
53
+ paintLabelmap(
54
+ labelmap: vtkLabelMap,
55
+ sliceAxis: 0 | 1 | 2,
56
+ startPoint: vec3,
57
+ endPoint?: vec3
58
+ ) {
59
+ const stencil = this.brush.getStencil();
60
+
61
+ const start = [
62
+ // transforms + floating point errors can make zero values occasionally
63
+ // turn into really tiny negative values
64
+ ...startPoint.map((val) => Math.round(val)),
65
+ ];
66
+ // Assumption: startPoint and endPoint are on the same slice axis.
67
+ const ijkSlice = start[sliceAxis];
68
+ start.splice(sliceAxis, 1);
69
+
70
+ let end = [...start];
71
+ if (endPoint) {
72
+ end = [...endPoint.map((val) => Math.round(val))];
73
+ end.splice(sliceAxis, 1);
74
+ }
75
+
76
+ const labelmapPixels = labelmap.getPointData().getScalars().getData();
77
+ const labelmapDims = labelmap.getDimensions();
78
+ const jStride = labelmapDims[0];
79
+ const kStride = labelmapDims[0] * labelmapDims[1];
80
+
81
+ const isInBounds = (point: number[]) =>
82
+ point[0] >= 0 &&
83
+ point[1] >= 0 &&
84
+ point[2] >= 0 &&
85
+ point[0] < labelmapDims[0] &&
86
+ point[1] < labelmapDims[1] &&
87
+ point[2] < labelmapDims[2];
88
+
89
+ const { pixels, size } = stencil;
90
+ const centerX = Math.floor((size[0] - 1) / 2);
91
+ const centerY = Math.floor((size[1] - 1) / 2);
92
+
93
+ const point1 = [...start];
94
+ const point2 = [...end];
95
+ const rounded = [0, 0, 0];
96
+ const curPoint: number[] = [0, 0];
97
+ for (let y = 0; y < size[1]; y++) {
98
+ const ydelta = y - centerY;
99
+ const yoffset = y * size[0];
100
+ for (let x = 0; x < size[0]; x++) {
101
+ const xdelta = x - centerX;
102
+ const pixelOffset = yoffset + x;
103
+ if (pixels[pixelOffset]) {
104
+ point1[0] = start[0] + xdelta;
105
+ point1[1] = start[1] + ydelta;
106
+ point2[0] = end[0] + xdelta;
107
+ point2[1] = end[1] + ydelta;
108
+
109
+ // line between the two points
110
+ const dx = point2[0] - point1[0];
111
+ const dy = point2[1] - point1[1];
112
+ let steps = Math.abs(Math.abs(dx) > Math.abs(dy) ? dx : dy);
113
+ const incX = dx / steps;
114
+ const incY = dy / steps;
115
+ [curPoint[0], curPoint[1]] = point1;
116
+ while (steps-- >= 0) {
117
+ // add slice axis to make a proper 3D index
118
+ curPoint.splice(sliceAxis, 0, ijkSlice);
119
+ rounded[0] = Math.round(curPoint[0]);
120
+ rounded[1] = Math.round(curPoint[1]);
121
+ rounded[2] = Math.round(curPoint[2]);
122
+
123
+ if (isInBounds(rounded)) {
124
+ const offset =
125
+ rounded[0] + rounded[1] * jStride + rounded[2] * kStride;
126
+ labelmapPixels[offset] = this.brushValue;
127
+ }
128
+
129
+ // undo adding the slice axis value
130
+ curPoint.splice(sliceAxis, 1);
131
+
132
+ curPoint[0] += incX;
133
+ curPoint[1] += incY;
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ labelmap.modified();
140
+ }
141
+ }
@@ -0,0 +1,149 @@
1
+ import { Vector3 } from '@kitware/vtk.js/types';
2
+ import { ImageMetadata } from '@/src/store/datasets-images';
3
+ import {
4
+ InteractionState,
5
+ RulerWidgetState,
6
+ } from '@/src/vtk/RulerWidget/state';
7
+ import vtkRulerWidget from '@/src/vtk/RulerWidget';
8
+ import mitt, { Emitter } from 'mitt';
9
+ import { LPSAxis } from '@/src/types/lps';
10
+
11
+ export interface SerializedRulerV1 {
12
+ version: '1.0';
13
+ /**
14
+ * Index space
15
+ */
16
+ firstPoint: Vector3;
17
+ /**
18
+ * Index space
19
+ */
20
+ secondPoint: Vector3;
21
+ viewAxis: LPSAxis;
22
+ /**
23
+ * Index space slice
24
+ */
25
+ slice: number;
26
+ imageID: string;
27
+ /**
28
+ * imageMetadata is saved as context for where
29
+ * the measurement is located in the world.
30
+ */
31
+ imageMetadata: Omit<ImageMetadata, 'name'>;
32
+ /**
33
+ * Ruler metadata
34
+ */
35
+ color: string;
36
+ }
37
+
38
+ export interface RulerStateUpdate {
39
+ firstPoint?: Vector3 | null;
40
+ secondPoint?: Vector3 | null;
41
+ interactionState?: InteractionState;
42
+ }
43
+
44
+ type RulerToolManagerEvents = {
45
+ widgetUpdate: {
46
+ id: string;
47
+ update: RulerStateUpdate;
48
+ };
49
+ };
50
+
51
+ export default class RulerTool {
52
+ private listeners: Map<string, Function>;
53
+
54
+ private factories: Map<string, vtkRulerWidget>;
55
+
56
+ private recurseGuard: boolean = false;
57
+
58
+ public events: Emitter<RulerToolManagerEvents>;
59
+
60
+ constructor() {
61
+ this.listeners = new Map();
62
+ this.factories = new Map();
63
+ this.events = mitt();
64
+ }
65
+
66
+ teardown() {
67
+ this.events.all.clear();
68
+ this.listeners.forEach((stop) => stop());
69
+ this.factories.forEach((factory) => factory.delete());
70
+ this.listeners.clear();
71
+ this.factories.clear();
72
+ }
73
+
74
+ getFactory(id: string) {
75
+ return this.factories.get(id) ?? null;
76
+ }
77
+
78
+ createRuler(id: string): vtkRulerWidget {
79
+ if (this.factories.has(id)) {
80
+ return this.factories.get(id)!;
81
+ }
82
+ const factory = vtkRulerWidget.newInstance();
83
+ this.listeners.set(
84
+ id,
85
+ this.createStoreUpdater(id, factory.getWidgetState())
86
+ );
87
+
88
+ this.factories.set(id, factory);
89
+ return factory;
90
+ }
91
+
92
+ removeRuler(id: string) {
93
+ const factory = this.factories.get(id);
94
+ if (factory) {
95
+ factory.delete();
96
+ this.factories.delete(id);
97
+ }
98
+ const cleanup = this.listeners.get(id);
99
+ if (cleanup) {
100
+ cleanup();
101
+ this.listeners.delete(id);
102
+ }
103
+ }
104
+
105
+ private createStoreUpdater(id: string, state: RulerWidgetState) {
106
+ const sub = state.onModified(() => {
107
+ if (this.recurseGuard) {
108
+ return;
109
+ }
110
+
111
+ const update: RulerStateUpdate = {
112
+ firstPoint: state.getFirstPoint().getOrigin(),
113
+ secondPoint: state.getSecondPoint().getOrigin(),
114
+ interactionState: state.getInteractionState(),
115
+ };
116
+
117
+ this.events.emit('widgetUpdate', { id, update });
118
+ });
119
+
120
+ return () => {
121
+ sub.unsubscribe();
122
+ };
123
+ }
124
+
125
+ updateRuler(id: string, update: RulerStateUpdate) {
126
+ const factory = this.factories.get(id);
127
+ if (factory) {
128
+ this.recurseGuard = true;
129
+
130
+ const state = factory.getWidgetState();
131
+ const first = state.getFirstPoint();
132
+ const second = state.getSecondPoint();
133
+
134
+ if (update.firstPoint) {
135
+ first.setOrigin(update.firstPoint);
136
+ first.setVisible(true);
137
+ }
138
+ if (update.secondPoint) {
139
+ second.setOrigin(update.secondPoint);
140
+ second.setVisible(true);
141
+ }
142
+ if (Number.isInteger(update.interactionState)) {
143
+ state.setInteractionState(update.interactionState!);
144
+ }
145
+
146
+ this.recurseGuard = false;
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,24 @@
1
+ import 'pinia';
2
+ import type { Framework } from 'vuetify/types';
3
+ import IDGenerator from './core/id';
4
+ import ProxyWrapper from './core/proxies';
5
+ import PaintTool from './core/tools/paint';
6
+ import RulerTool from './core/tools/ruler';
7
+ import { DICOMIO } from './io/dicom';
8
+
9
+ declare module 'pinia' {
10
+ export interface PiniaCustomProperties {
11
+ // from CorePiniaProviderPlugin
12
+ $rulers: RulerTool;
13
+ $paint: PaintTool;
14
+ $proxies: ProxyWrapper;
15
+ $id: IDGenerator;
16
+ $dicomIO: DICOMIO;
17
+ }
18
+ }
19
+
20
+ declare module 'vue/types/vue' {
21
+ interface Vue {
22
+ $vuetify: Framework;
23
+ }
24
+ }
@@ -0,0 +1,26 @@
1
+ import { expect } from 'chai';
2
+ import { retypeFile } from '@src/io';
3
+
4
+ function makeEmptyFile(name: string) {
5
+ return new File([], name);
6
+ }
7
+
8
+ function makeDicomFile(name: string) {
9
+ const buffer = new Uint8Array(132);
10
+ buffer[128] = 'D'.charCodeAt(0);
11
+ buffer[129] = 'I'.charCodeAt(0);
12
+ buffer[130] = 'C'.charCodeAt(0);
13
+ buffer[131] = 'M'.charCodeAt(0);
14
+ return new File([buffer.buffer], name);
15
+ }
16
+
17
+ describe('I/O', () => {
18
+ it('should detect dicom files', async () => {
19
+ expect((await retypeFile(makeDicomFile('file.DCM'))).type).to.equal('dcm');
20
+ expect((await retypeFile(makeDicomFile('somedicom'))).type).to.equal('dcm');
21
+ });
22
+
23
+ it('should retype files based on extension', async () => {
24
+ expect((await retypeFile(makeEmptyFile('test.VTI'))).type).to.equal('vti');
25
+ });
26
+ });
@@ -0,0 +1,187 @@
1
+ import {
2
+ runPipeline,
3
+ TextStream,
4
+ InterfaceTypes,
5
+ readDICOMTags,
6
+ readImageDICOMFileSeries,
7
+ } from 'itk-wasm';
8
+
9
+ export interface TagSpec {
10
+ name: string;
11
+ tag: string;
12
+ }
13
+
14
+ // volume ID => file names
15
+ export type VolumesToFilesMap = Record<string, string[]>;
16
+
17
+ export class DICOMIO {
18
+ private webWorker: any;
19
+ private initializeCheck: Promise<void> | null;
20
+
21
+ constructor() {
22
+ this.webWorker = null;
23
+ this.initializeCheck = null;
24
+ }
25
+
26
+ private async runTask(
27
+ module: string,
28
+ args: any[],
29
+ inputs: any[],
30
+ outputs: any[]
31
+ ) {
32
+ return runPipeline(this.webWorker, module, args, outputs, inputs);
33
+ }
34
+
35
+ /**
36
+ * Helper that initializes the webworker.
37
+ *
38
+ * @async
39
+ * @throws Error initialization failed
40
+ */
41
+ private async initialize() {
42
+ if (!this.initializeCheck) {
43
+ this.initializeCheck = new Promise<void>((resolve, reject) => {
44
+ this.runTask('dicom', [], [], [])
45
+ .then((result) => {
46
+ if (result.webWorker) {
47
+ this.webWorker = result.webWorker;
48
+ resolve();
49
+ } else {
50
+ reject(new Error('Could not initialize webworker'));
51
+ }
52
+ })
53
+ .catch(reject);
54
+ });
55
+ }
56
+
57
+ return this.initializeCheck;
58
+ }
59
+
60
+ /**
61
+ * Categorize files
62
+ * @async
63
+ * @param {File[]} files
64
+ * @returns volumeID => file names mapping
65
+ */
66
+ async categorizeFiles(files: File[]): Promise<VolumesToFilesMap> {
67
+ await this.initialize();
68
+
69
+ const inputs = await Promise.all(
70
+ files.map(async (file) => {
71
+ const buffer = await file.arrayBuffer();
72
+ return {
73
+ type: InterfaceTypes.BinaryFile,
74
+ data: {
75
+ path: file.name,
76
+ data: new Uint8Array(buffer),
77
+ },
78
+ };
79
+ })
80
+ );
81
+
82
+ const args = [
83
+ '--action',
84
+ 'categorize',
85
+ '--memory-io',
86
+ '0',
87
+ '--files',
88
+ ...inputs.map((fd) => fd.data.path),
89
+ ];
90
+
91
+ const outputs = [{ type: InterfaceTypes.TextStream }];
92
+
93
+ const result = await this.runTask(
94
+ // module
95
+ 'dicom',
96
+ args,
97
+ inputs,
98
+ outputs
99
+ );
100
+
101
+ return JSON.parse((result.outputs[0].data as TextStream).data);
102
+ }
103
+
104
+ /**
105
+ * Reads a list of tags out from a given file.
106
+ *
107
+ * @param {File} file
108
+ * @param {[]Tag} tags
109
+ */
110
+ async readTags<T extends TagSpec[]>(
111
+ file: File,
112
+ tags: T
113
+ ): Promise<Record<T[number]['name'], string>> {
114
+ const tagsArgs = tags.map((t) => t.tag);
115
+
116
+ const result = await readDICOMTags(this.webWorker, file, tagsArgs);
117
+ const tagValues = result.tags;
118
+
119
+ return tags.reduce((info, t) => {
120
+ const { tag, name } = t;
121
+ if (tagValues.has(tag)) {
122
+ return { ...info, [name]: tagValues.get(tag) };
123
+ }
124
+ return info;
125
+ }, {} as Record<T[number]['name'], string>);
126
+ }
127
+
128
+ /**
129
+ * Retrieves a slice of a volume.
130
+ * @async
131
+ * @param {File} file containing the slice
132
+ * @param {Boolean} asThumbnail cast image to unsigned char. Defaults to false.
133
+ * @returns ItkImage
134
+ */
135
+ async getVolumeSlice(file: File, asThumbnail: boolean = false) {
136
+ await this.initialize();
137
+
138
+ const buffer = await file.arrayBuffer();
139
+
140
+ const inputs = [
141
+ {
142
+ type: InterfaceTypes.BinaryFile,
143
+ data: {
144
+ path: file.name,
145
+ data: new Uint8Array(buffer),
146
+ },
147
+ },
148
+ ];
149
+
150
+ const args = [
151
+ '--action',
152
+ 'getSliceImage',
153
+ '--thumbnail',
154
+ asThumbnail.toString(),
155
+ '--file',
156
+ file.name,
157
+ '--memory-io',
158
+ '0',
159
+ ];
160
+
161
+ const outputs = [{ type: InterfaceTypes.Image }];
162
+
163
+ const result = await this.runTask(
164
+ // module
165
+ 'dicom',
166
+ args,
167
+ inputs,
168
+ outputs
169
+ );
170
+
171
+ return result.outputs[0].data;
172
+ }
173
+
174
+ /**
175
+ * Builds a volume for a set of files.
176
+ * @async
177
+ * @param {File[]} files the set of files to build volume from
178
+ * @returns ItkImage
179
+ */
180
+ async buildVolume(files: File[]) {
181
+ await this.initialize();
182
+
183
+ const result = await readImageDICOMFileSeries(files);
184
+
185
+ return result.image;
186
+ }
187
+ }
@@ -0,0 +1,11 @@
1
+ import { vtkObject } from '@kitware/vtk.js/interfaces';
2
+
3
+ export * from './io';
4
+
5
+ export type ReaderType = (file: File) => vtkObject | Promise<vtkObject>;
6
+ export type FileReaderMap = Map<string, ReaderType>;
7
+
8
+ /**
9
+ * A map of the currently registered file readers.
10
+ */
11
+ export const FILE_READERS: FileReaderMap = new Map();