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,102 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiAlmost from 'chai-almost';
3
+ import { vec3, mat3 } from 'gl-matrix';
4
+
5
+ import { getLPSDirections } from '@src/utils/lps';
6
+
7
+ chai.use(chaiAlmost());
8
+
9
+ describe('getLPSDirections', () => {
10
+ it('correctly assigns LPS directions from an orientation', () => {
11
+ // Only need 3 of the 6 due to negation
12
+ interface Expected {
13
+ Left: vec3;
14
+ Posterior: vec3;
15
+ Superior: vec3;
16
+ Coronal: number;
17
+ Sagittal: number;
18
+ Axial: number;
19
+ }
20
+
21
+ function evaluate(desc: string, mat: mat3, expected: Expected) {
22
+ const actual = getLPSDirections(mat);
23
+
24
+ expect(actual.Coronal, `${desc}: Coronal`).to.equal(expected.Coronal);
25
+ expect(actual.Sagittal, `${desc}: Coronal`).to.equal(expected.Sagittal);
26
+ expect(actual.Axial, `${desc}: Coronal`).to.equal(expected.Axial);
27
+
28
+ expect(actual.Left, `${desc}: Left`).to.deep.almost(expected.Left);
29
+ expect(actual.Right, `${desc}: Right`).to.deep.almost(
30
+ expected.Left.map((c) => c * -1)
31
+ );
32
+ expect(actual.Posterior, `${desc}: Posterior`).to.deep.almost(
33
+ expected.Posterior
34
+ );
35
+ expect(actual.Anterior, `${desc}: Anterior`).to.deep.almost(
36
+ expected.Posterior.map((c) => c * -1)
37
+ );
38
+ expect(actual.Superior, `${desc}: Superior`).to.deep.almost(
39
+ expected.Superior
40
+ );
41
+ expect(actual.Inferior, `${desc}: Inferior`).to.deep.almost(
42
+ expected.Superior.map((c) => c * -1)
43
+ );
44
+ }
45
+
46
+ evaluate(
47
+ 'Identity',
48
+ // prettier-ignore
49
+ [
50
+ 1, 0, 0,
51
+ 0, 1, 0,
52
+ 0, 0, 1,
53
+ ],
54
+ {
55
+ Left: [1, 0, 0],
56
+ Posterior: [0, 1, 0],
57
+ Superior: [0, 0, 1],
58
+ Sagittal: 0,
59
+ Coronal: 1,
60
+ Axial: 2,
61
+ }
62
+ );
63
+ evaluate(
64
+ 'Axis-aligned case #1',
65
+ // prettier-ignore
66
+ [
67
+ 0, 0, -1,
68
+ 1, 0, 0,
69
+ 0, -1, 0,
70
+ ],
71
+ {
72
+ Left: [1, 0, 0],
73
+ Posterior: [0, 1, 0],
74
+ Superior: [0, 0, 1],
75
+ Sagittal: 1,
76
+ Coronal: 2,
77
+ Axial: 0,
78
+ }
79
+ );
80
+ evaluate(
81
+ 'Oblique case #1',
82
+ // prettier-ignore
83
+ [
84
+ 0.5986634492874146, 0.22716301679611206, -0.7681139707565308,
85
+ 0.5627936124801636, 0.5630670785903931 , 0.6051601767539978,
86
+ 0.569969654083252 , -0.7945769429206848, 0.20924176275730133,
87
+ ],
88
+ {
89
+ Left: [0.5627936124801636, 0.5630670785903931, 0.6051601767539978],
90
+ Posterior: [
91
+ -0.569969654083252, 0.7945769429206848, -0.20924176275730133,
92
+ ],
93
+ Superior: [
94
+ -0.5986634492874146, -0.22716301679611206, 0.7681139707565308,
95
+ ],
96
+ Sagittal: 1,
97
+ Coronal: 2,
98
+ Axial: 0,
99
+ }
100
+ );
101
+ });
102
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Converts an RGBA tuple to a hex string with alpha.
3
+ *
4
+ * Adds the prefix '#'.
5
+ *
6
+ * @param rgba a 4-tuple with components ranging from 0-255.
7
+ */
8
+ export function rgbaToHexa(rgba: number[]) {
9
+ const hexa = rgba.map((comp) => `0${comp.toString(16)}`.slice(-2));
10
+ return `#${hexa.join('')}`;
11
+ }
12
+
13
+ /**
14
+ *
15
+ * @param hexa a hexa color in the format "#AABBCCDD"
16
+ */
17
+ export function hexaToRGBA(hexa: string) {
18
+ const rgba = [0, 0, 0, 0];
19
+ for (let i = 0; i < 4; i++) {
20
+ rgba[i] = Number.parseInt(hexa.substring(i + 1, i + 3), 16);
21
+ }
22
+ return rgba;
23
+ }
@@ -0,0 +1,3 @@
1
+ /* eslint-disable max-classes-per-file */
2
+
3
+ export class IndexError extends Error {}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Percent is in [0, 1]. If it's Infinity, then the progress is indeterminate.
3
+ */
4
+ export type ProgressCallback = (percent: number) => void;
5
+
6
+ export interface FetchOptions {
7
+ progress?: ProgressCallback;
8
+ }
9
+
10
+ interface URLHandler {
11
+ testURL(url: string): boolean;
12
+ fetchURL(url: string, options?: FetchOptions): Promise<Blob | null>;
13
+ }
14
+
15
+ /**
16
+ * Handles HTTP(S) connections.
17
+ *
18
+ * Progress is approximate, given that data can be sent over gzipped.
19
+ */
20
+ const HTTPHandler: URLHandler = {
21
+ testURL: (url) => {
22
+ const { protocol } = new URL(url);
23
+ return protocol === 'http:' || protocol === 'https:';
24
+ },
25
+ fetchURL: async (url, options = {}) => {
26
+ const { progress } = options;
27
+
28
+ const response = await fetch(url);
29
+ const contentLength = Number(response.headers.get('content-length')) ?? -1;
30
+
31
+ if (!progress) {
32
+ return response.blob();
33
+ }
34
+
35
+ if (contentLength < 0) {
36
+ progress(Infinity);
37
+ return response.blob();
38
+ }
39
+
40
+ const reader = response.body?.getReader();
41
+ if (!reader) {
42
+ return null;
43
+ }
44
+
45
+ const bytes = new Uint8Array(contentLength);
46
+ let recv = 0;
47
+ let done = false;
48
+ do {
49
+ // eslint-disable-next-line no-await-in-loop
50
+ const readData = await reader.read();
51
+ done = readData.done;
52
+ if (readData.value && !done) {
53
+ bytes.set(readData.value, recv);
54
+ recv += readData.value.length;
55
+ progress(recv / contentLength);
56
+ }
57
+ } while (!done);
58
+
59
+ return new Blob([bytes]);
60
+ },
61
+ };
62
+
63
+ /**
64
+ * Handles Google Bucket URLs
65
+ */
66
+ const GoogleBucketHandler: URLHandler = {
67
+ testURL: (url) => new URL(url).protocol === 'gs:',
68
+ fetchURL: async (url, options = {}) => {
69
+ const urlComponents = new URL(url);
70
+ // gs URIs look like gs://bucket_name/file_name,
71
+ // and pathname is everything after gs:
72
+ // strip off both of the leading slashes.
73
+ const bucketPath = urlComponents.pathname.slice(2);
74
+ const httpURL = `https://storage.googleapis.com/${bucketPath}`;
75
+ return HTTPHandler.fetchURL(httpURL, options);
76
+ },
77
+ };
78
+
79
+ const HANDLERS = [GoogleBucketHandler, HTTPHandler];
80
+
81
+ /**
82
+ * Fetches a file.
83
+ * @returns a File instance.
84
+ */
85
+ export async function fetchFile(
86
+ url: string,
87
+ name: string,
88
+ options?: FetchOptions
89
+ ) {
90
+ const handler = HANDLERS.find((h) => h.testURL(url));
91
+ if (!handler) {
92
+ throw new Error(`Cannot find a handler for URL: ${url}`);
93
+ }
94
+
95
+ const blob = await handler.fetchURL(url, options);
96
+ if (!blob) {
97
+ throw new Error(`Failed to download ${url}`);
98
+ }
99
+
100
+ return new File([blob], name);
101
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * document.exitPointerLock is undefined on iOS.
3
+ * - Tested on iOS Safari 15.6.1.
4
+ */
5
+ export function patchExitPointerLock() {
6
+ const { exitPointerLock } = document;
7
+ document.exitPointerLock = () => {
8
+ try {
9
+ exitPointerLock?.call(document);
10
+ } catch (e) {
11
+ // ignore if undefined
12
+ }
13
+ };
14
+ }
@@ -0,0 +1,83 @@
1
+ type PromiseResolveFunction<T> = (value: T) => void;
2
+ type PromiseRejectFunction = (reason?: Error) => void;
3
+ export interface Deferred<T> {
4
+ promise: Promise<T>;
5
+ resolve: PromiseResolveFunction<T>;
6
+ reject: PromiseRejectFunction;
7
+ }
8
+
9
+ export function defer<T>(): Deferred<T> {
10
+ let innerResolve: PromiseResolveFunction<T> | null = null;
11
+ let innerReject: PromiseRejectFunction | null = null;
12
+
13
+ const resolve = (value: T) => {
14
+ if (innerResolve) innerResolve(value);
15
+ };
16
+ const reject = (reason?: Error) => {
17
+ if (innerReject) innerReject(reason);
18
+ };
19
+
20
+ const promise = new Promise<T>((res, rej) => {
21
+ innerResolve = res;
22
+ innerReject = rej;
23
+ });
24
+
25
+ return { promise, resolve, reject };
26
+ }
27
+
28
+ export function removeFromArray<T>(arr: Array<T>, el: T) {
29
+ const idx = arr.indexOf(el);
30
+ if (idx > -1) {
31
+ arr.splice(idx, 1);
32
+ }
33
+ }
34
+
35
+ export function clampValue(value: number, min: number, max: number) {
36
+ return Math.min(max, Math.max(min, value));
37
+ }
38
+
39
+ export function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
40
+ return keys.reduce((o, k) => ({ ...o, [k]: obj[k] }), {} as Pick<T, K>);
41
+ }
42
+
43
+ export function plural(n: number, word: string, pluralWord?: string) {
44
+ return n > 1 ? pluralWord ?? `${word}s` : word;
45
+ }
46
+
47
+ export const ensureDefault = <T>(
48
+ key: string,
49
+ records: Record<string, T>,
50
+ default_: T
51
+ ) => {
52
+ if (!(key in records)) {
53
+ // eslint-disable-next-line no-param-reassign
54
+ records[key] = default_;
55
+ }
56
+
57
+ return records[key];
58
+ };
59
+
60
+ export function arrayEquals<T>(a: T[], b: T[]) {
61
+ if (a.length !== b.length) return false;
62
+ for (let i = 0; i < a.length; i++) {
63
+ if (a[i] !== b[i]) return false;
64
+ }
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Wraps non-arrays in an array.
70
+ * @param maybeArray
71
+ */
72
+ export function wrapInArray<T>(maybeArray: T | T[]): T[] {
73
+ return Array.isArray(maybeArray) ? maybeArray : [maybeArray];
74
+ }
75
+
76
+ /**
77
+ * Extracts the basename from a URL.
78
+ *
79
+ * Requires that the URL global be present.
80
+ */
81
+ export function getURLBasename(url: string) {
82
+ return new URL(url).pathname.split('/').at(-1) ?? '';
83
+ }
@@ -0,0 +1,129 @@
1
+ import { Bounds } from '@kitware/vtk.js/types';
2
+ import { vec3, mat3 } from 'gl-matrix';
3
+ import {
4
+ LPSAxis,
5
+ LPSAxisDir,
6
+ LPSBounds,
7
+ LPSDirections,
8
+ LPSPoint,
9
+ } from '../types/lps';
10
+
11
+ export const LPSAxes: LPSAxis[] = ['Sagittal', 'Coronal', 'Axial'];
12
+
13
+ export function createLPSPoint(): LPSPoint {
14
+ return {
15
+ Sagittal: 0,
16
+ Coronal: 0,
17
+ Axial: 0,
18
+ };
19
+ }
20
+
21
+ export function createLPSBounds(): LPSBounds {
22
+ return {
23
+ Sagittal: [0, 0],
24
+ Coronal: [0, 0],
25
+ Axial: [0, 0],
26
+ };
27
+ }
28
+
29
+ export function getAxisBounds(
30
+ bounds: Bounds,
31
+ axis: LPSAxis,
32
+ directions: LPSDirections
33
+ ) {
34
+ const index = 2 * directions[axis];
35
+ return bounds.slice(index, index + 2) as [number, number];
36
+ }
37
+
38
+ export function getLPSAxisFromDir(dir: LPSAxisDir): LPSAxis {
39
+ switch (dir) {
40
+ case 'Superior':
41
+ case 'Inferior':
42
+ return 'Axial';
43
+ case 'Left':
44
+ case 'Right':
45
+ return 'Sagittal';
46
+ case 'Posterior':
47
+ case 'Anterior':
48
+ return 'Coronal';
49
+ default:
50
+ throw new Error(`Invalid LPS direction: ${dir}`);
51
+ }
52
+ }
53
+
54
+ export const defaultLPSDirections = () => ({
55
+ Left: vec3.fromValues(1, 0, 0),
56
+ Right: vec3.fromValues(-1, 0, 0),
57
+ Posterior: vec3.fromValues(0, 1, 0),
58
+ Anterior: vec3.fromValues(0, -1, 0),
59
+ Superior: vec3.fromValues(0, 0, 1),
60
+ Inferior: vec3.fromValues(0, 0, -1),
61
+
62
+ Sagittal: 0 as const,
63
+ Coronal: 1 as const,
64
+ Axial: 2 as const,
65
+ });
66
+
67
+ /**
68
+ * Associates the column vectors of a 3x3 matrix with the LPS axes.
69
+ *
70
+ * For each of the LPS axes, this function returns the associated column index (0, 1, 2)
71
+ * in the provided 3x3 column-major matrix.
72
+ *
73
+ * Approach:
74
+ * - find the max of the direction matrix, ignoring columns and rows marked as done
75
+ * - assign the column vector of that max value to the row axis
76
+ * - mark that row and column as done
77
+ * - continue until all rows and columns are done
78
+ */
79
+ export function getLPSDirections(direction: mat3): LPSDirections {
80
+ // Track the rows and columns that have yet to be assigned.
81
+ const availableCols = [0, 1, 2];
82
+ const availableRows = [0, 1, 2];
83
+ const lpsDirs: LPSDirections = defaultLPSDirections();
84
+
85
+ for (let i = 0; i < 3; i++) {
86
+ let bestValue = 0;
87
+ let bestValueLoc = [0, 0]; // col, row
88
+ let removeIndices = [0, 0]; // indices into availableCols/Rows for deletion
89
+
90
+ availableCols.forEach((col, colIdx) => {
91
+ availableRows.forEach((row, rowIdx) => {
92
+ const value = direction[col * 3 + row];
93
+ if (Math.abs(value) > Math.abs(bestValue)) {
94
+ bestValue = value;
95
+ bestValueLoc = [col, row];
96
+ removeIndices = [colIdx, rowIdx];
97
+ }
98
+ });
99
+ });
100
+
101
+ // the row index corresponds to the index of the LPS axis
102
+ const [col, axis] = bestValueLoc;
103
+ const axisVector = direction.slice(col * 3, (col + 1) * 3);
104
+ const vecSign = Math.sign(bestValue);
105
+ const posVector = axisVector.map((c) => c * vecSign) as vec3;
106
+ const negVector = axisVector.map((c) => c * -vecSign) as vec3;
107
+ if (axis === 0) {
108
+ // Sagittal
109
+ lpsDirs.Left = posVector;
110
+ lpsDirs.Right = negVector;
111
+ lpsDirs.Sagittal = col as 0 | 1 | 2;
112
+ } else if (axis === 1) {
113
+ // Coronal
114
+ lpsDirs.Posterior = posVector;
115
+ lpsDirs.Anterior = negVector;
116
+ lpsDirs.Coronal = col as 0 | 1 | 2;
117
+ } else if (axis === 2) {
118
+ // Axial
119
+ lpsDirs.Superior = posVector;
120
+ lpsDirs.Inferior = negVector;
121
+ lpsDirs.Axial = col as 0 | 1 | 2;
122
+ }
123
+
124
+ availableCols.splice(removeIndices[0], 1);
125
+ availableRows.splice(removeIndices[1], 1);
126
+ }
127
+
128
+ return lpsDirs;
129
+ }
@@ -0,0 +1,35 @@
1
+ import { Vector3 } from '@kitware/vtk.js/types';
2
+ import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
3
+ import { vec3 } from 'gl-matrix';
4
+ import { ImageMetadata } from '../store/datasets-images';
5
+ import { LPSAxisDir } from '../types/lps';
6
+ import { getLPSAxisFromDir } from './lps';
7
+
8
+ export function updatePlaneManipulatorFor2DView(
9
+ manipulator: vtkPlaneManipulator,
10
+ viewDir: LPSAxisDir,
11
+ slice: number,
12
+ imageMetadata: ImageMetadata
13
+ ) {
14
+ const { lpsOrientation } = imageMetadata;
15
+ const axis = lpsOrientation[getLPSAxisFromDir(viewDir)];
16
+
17
+ const normal: vec3 = lpsOrientation[viewDir];
18
+ const origin: vec3 = [0, 0, 0];
19
+ origin[axis] = slice;
20
+
21
+ vec3.transformMat4(origin, origin, imageMetadata.indexToWorld);
22
+
23
+ manipulator.setUserNormal(normal as Vector3);
24
+ manipulator.setUserOrigin(origin as Vector3);
25
+ }
26
+
27
+ export function createPlaneManipulatorFor2DView(
28
+ viewDir: LPSAxisDir,
29
+ slice: number,
30
+ imageMetadata: ImageMetadata
31
+ ) {
32
+ const manipulator = vtkPlaneManipulator.newInstance();
33
+ updatePlaneManipulatorFor2DView(manipulator, viewDir, slice, imageMetadata);
34
+ return manipulator;
35
+ }
@@ -0,0 +1,45 @@
1
+ /* eslint-disable */
2
+ import { defer, Deferred } from './index';
3
+
4
+ // to be used to instantiate a worker
5
+ export default class PromiseWorker {
6
+ private msgID: number;
7
+ private worker: Worker;
8
+ private waiting: Record<string, Deferred<any>>;
9
+
10
+ constructor(worker: Worker) {
11
+ this.msgID = 0;
12
+ this.worker = worker;
13
+ this.waiting = {};
14
+
15
+ this.worker.onmessage = this.handleMessage.bind(this);
16
+ }
17
+
18
+ postMessage(message: any, transferables: any[] = []) {
19
+ const wrappedMsg = {
20
+ id: this.msgID,
21
+ message,
22
+ };
23
+
24
+ const deferred = defer();
25
+ this.waiting[wrappedMsg.id] = deferred;
26
+
27
+ this.worker.postMessage(wrappedMsg, transferables);
28
+
29
+ this.msgID += 1;
30
+ return deferred.promise;
31
+ }
32
+
33
+ handleMessage(ev: any) {
34
+ const { id, error, message } = ev.data;
35
+ if (id in this.waiting) {
36
+ const deferred = this.waiting[id];
37
+ delete this.waiting[id];
38
+ if (error) {
39
+ deferred.reject(new Error(error));
40
+ } else {
41
+ deferred.resolve(message);
42
+ }
43
+ }
44
+ }
45
+ }