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,38 @@
1
+ import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
2
+ import { ComputedRef, InjectionKey } from '@vue/composition-api';
3
+
4
+ export const NO_PROXY = -1;
5
+ export const NO_SELECTION = -1;
6
+ export const NO_WIDGET = -1;
7
+
8
+ export const EPSILON = 10e-6;
9
+ export const NOOP = () => {};
10
+
11
+ /**
12
+ * Retrieves the parent VtkTwoView's widget manager.
13
+ */
14
+ export const VTKTwoViewWidgetManager: InjectionKey<
15
+ ComputedRef<vtkWidgetManager>
16
+ > = Symbol('VTKTwoViewWidgetManager');
17
+
18
+ /**
19
+ * Retrieves the parent VtkThreeView's widget manager.
20
+ */
21
+ export const VTKThreeViewWidgetManager: InjectionKey<
22
+ ComputedRef<vtkWidgetManager>
23
+ > = Symbol('VTKThreeViewWidgetManager');
24
+
25
+ export const DataTypes = {
26
+ Image: 'Image',
27
+ Labelmap: 'Labelmap',
28
+ Dicom: 'DICOM',
29
+ Model: 'Model',
30
+ };
31
+
32
+ export const Messages = {
33
+ WebGLLost: {
34
+ title: 'Viewer Error',
35
+ details:
36
+ 'Lost the WebGL context! Please reload the webpage. If the problem persists, you may need to restart your web browser.',
37
+ },
38
+ } as const;
package/src/core/id.ts ADDED
@@ -0,0 +1,18 @@
1
+ const START_ID = 0;
2
+
3
+ export default class IDGenerator {
4
+ private id: number;
5
+
6
+ constructor() {
7
+ this.id = START_ID;
8
+ }
9
+
10
+ nextID() {
11
+ this.id += 1;
12
+ return String(this.id);
13
+ }
14
+
15
+ reset() {
16
+ this.id = START_ID;
17
+ }
18
+ }
@@ -0,0 +1,31 @@
1
+ import { DICOMIO } from '../io/dicom';
2
+ import IDGenerator from './id';
3
+ import ProxyWrapper from './proxies';
4
+ import PaintTool from './tools/paint';
5
+ import RulerTool from './tools/ruler';
6
+
7
+ /**
8
+ * Pinia plugin for injecting tool services.
9
+ */
10
+ export function CorePiniaProviderPlugin({
11
+ rulers,
12
+ paint,
13
+ proxies,
14
+ id,
15
+ dicomIO,
16
+ }: {
17
+ rulers?: RulerTool;
18
+ paint?: PaintTool;
19
+ proxies?: ProxyWrapper;
20
+ id?: IDGenerator;
21
+ dicomIO?: DICOMIO;
22
+ } = {}) {
23
+ const dependencies = {
24
+ $rulers: rulers ?? new RulerTool(),
25
+ $paint: paint ?? new PaintTool(),
26
+ $proxies: proxies,
27
+ $id: id ?? new IDGenerator(),
28
+ $dicomIO: dicomIO ?? new DICOMIO(),
29
+ };
30
+ return () => dependencies;
31
+ }
@@ -0,0 +1,90 @@
1
+ import { vtkObject } from '@kitware/vtk.js/interfaces';
2
+ import vtkAbstractRepresentationProxy from '@kitware/vtk.js/Proxy/Core/AbstractRepresentationProxy';
3
+ import vtkProxyManager from '@kitware/vtk.js/Proxy/Core/ProxyManager';
4
+ import vtkSourceProxy from '@kitware/vtk.js/Proxy/Core/SourceProxy';
5
+ import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
6
+ import vtkProxyObject from '../types/vtk-types';
7
+
8
+ // mapped in proxy.js
9
+ export enum ViewProxyType {
10
+ Volume = 'View3D',
11
+ Slice = 'View2D',
12
+ }
13
+
14
+ /**
15
+ * Wrapper around the vtkProxyManager, since we don't need some of
16
+ * its complexities.
17
+ */
18
+ export default class ProxyWrapper {
19
+ private viewProxies: Map<string, vtkViewProxy>;
20
+ private dataProxies: Map<string, vtkSourceProxy<vtkObject>>;
21
+ private proxyManager: vtkProxyManager;
22
+
23
+ constructor(proxyManager: vtkProxyManager) {
24
+ this.viewProxies = new Map();
25
+ this.dataProxies = new Map();
26
+ this.proxyManager = proxyManager;
27
+ }
28
+
29
+ delete() {
30
+ const deleteProxy = (proxy: vtkProxyObject) =>
31
+ this.proxyManager.deleteProxy(proxy);
32
+
33
+ this.viewProxies.forEach(deleteProxy);
34
+ this.dataProxies.forEach(deleteProxy);
35
+ }
36
+
37
+ createView(id: string, type: ViewProxyType) {
38
+ if (this.viewProxies.has(id)) {
39
+ throw new Error('Cannot create a view with the same ID');
40
+ }
41
+
42
+ const proxy = this.proxyManager.createProxy<vtkViewProxy>('Views', type, {
43
+ name: type,
44
+ });
45
+
46
+ this.viewProxies.set(id, proxy);
47
+ return proxy;
48
+ }
49
+
50
+ getView<T extends vtkViewProxy>(id: string) {
51
+ return <T | null>this.viewProxies.get(id);
52
+ }
53
+
54
+ removeView(id: string) {
55
+ const proxy = this.viewProxies.get(id);
56
+ if (proxy) {
57
+ this.proxyManager.deleteProxy(proxy);
58
+ this.viewProxies.delete(id);
59
+ }
60
+ }
61
+
62
+ addData<T extends vtkObject>(id: string, data: T) {
63
+ if (this.dataProxies.has(id)) {
64
+ return;
65
+ }
66
+ const proxy = this.proxyManager.createProxy<vtkSourceProxy<T>>(
67
+ 'Sources',
68
+ 'TrivialProducer'
69
+ );
70
+ proxy.setInputData(data);
71
+ this.dataProxies.set(id, proxy);
72
+ }
73
+
74
+ getData<T extends vtkObject>(id: string) {
75
+ return <vtkSourceProxy<T> | null>(this.dataProxies.get(id) ?? null);
76
+ }
77
+
78
+ getDataRepresentationForView<T extends vtkAbstractRepresentationProxy>(
79
+ dataID: string,
80
+ viewID: string
81
+ ): T | null {
82
+ const dataProxy = this.dataProxies.get(dataID);
83
+ const viewProxy = this.viewProxies.get(viewID);
84
+ if (!dataProxy || !viewProxy) {
85
+ return null;
86
+ }
87
+
88
+ return this.proxyManager.getRepresentation(dataProxy, viewProxy);
89
+ }
90
+ }
@@ -0,0 +1,58 @@
1
+ import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
2
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
3
+ import { expect } from 'chai';
4
+ import { ThumbnailSlice } from '..';
5
+ import { createVTKImageThumbnailer } from '../vtk-image';
6
+
7
+ // workaround to get ImageData in the global scope
8
+ global.ImageData = document
9
+ .createElement('canvas')
10
+ .getContext('2d')
11
+ ?.createImageData(1, 1).constructor as any;
12
+
13
+ // creates a 3x3x3 image
14
+ function createImageData() {
15
+ const id = vtkImageData.newInstance();
16
+ id.setDimensions([3, 3, 3]);
17
+ const values = new Uint8Array(3 * 3 * 3);
18
+ const da = vtkDataArray.newInstance({
19
+ numberOfComponents: 1,
20
+ values,
21
+ });
22
+ id.getPointData().setScalars(da);
23
+ return { imageData: id, scalars: values };
24
+ }
25
+
26
+ // index to offset for a 3x3
27
+ function i2o(x: number, y: number, z: number) {
28
+ return z * 9 + y * 3 + x;
29
+ }
30
+
31
+ describe('VTK Image Thumbnailer', () => {
32
+ const SLICE_POSITION_WORDS = ['first', 'middle', 'last'];
33
+ const SLICE_POSITIONS = [
34
+ ThumbnailSlice.First,
35
+ ThumbnailSlice.Middle,
36
+ ThumbnailSlice.Last,
37
+ ];
38
+
39
+ for (let axis: 0 | 1 | 2 = 0; axis < 3; axis++) {
40
+ for (let slice = 0; slice < 3; slice++) {
41
+ const sliceWord = SLICE_POSITION_WORDS[slice];
42
+ const thumbSlice = SLICE_POSITIONS[slice];
43
+
44
+ it(`should thumbnail the ${sliceWord} slice on axis ${axis}`, () => {
45
+ const { imageData, scalars } = createImageData();
46
+ const thumbnailer = createVTKImageThumbnailer();
47
+
48
+ const point: [number, number, number] = [1, 1, 1];
49
+ point[axis] = slice;
50
+ scalars[i2o(...point)] = 255;
51
+
52
+ const im = thumbnailer.generate(imageData, axis, thumbSlice);
53
+ // each pixel is rgba
54
+ expect(im.data[4 * 4]).to.equal(255);
55
+ });
56
+ }
57
+ }
58
+ });
@@ -0,0 +1,5 @@
1
+ export enum ThumbnailSlice {
2
+ First,
3
+ Middle,
4
+ Last,
5
+ }
@@ -0,0 +1,130 @@
1
+ import vtkGenericRenderWindow from '@kitware/vtk.js/Rendering/Misc/GenericRenderWindow';
2
+ import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
3
+ import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
4
+ import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
5
+ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
6
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
7
+ import vtkPiecewiseFunctionProxy from '@kitware/vtk.js/Proxy/Core/PiecewiseFunctionProxy';
8
+ import vtkLookupTableProxy from '@kitware/vtk.js/Proxy/Core/LookupTableProxy';
9
+ import { vec3 } from 'gl-matrix';
10
+ import { Vector3 } from '@kitware/vtk.js/types';
11
+ import vtkVolumeProperty from '@kitware/vtk.js/Rendering/Core/VolumeProperty';
12
+ import { getDiagonalLength } from '@kitware/vtk.js/Common/DataModel/BoundingBox';
13
+
14
+ export function createRenderingPipeline() {
15
+ const actor = vtkVolume.newInstance();
16
+ const mapper = vtkVolumeMapper.newInstance();
17
+ const property = actor.getProperty();
18
+ const cfun = vtkColorTransferFunction.newInstance();
19
+ const ofun = vtkPiecewiseFunction.newInstance();
20
+ property.setRGBTransferFunction(0, cfun);
21
+ property.setScalarOpacity(0, ofun);
22
+ actor.setMapper(mapper);
23
+ return {
24
+ actor,
25
+ mapper,
26
+ property,
27
+ cfun,
28
+ ofun,
29
+ };
30
+ }
31
+
32
+ function updateRenderingProperty(
33
+ prop: vtkVolumeProperty,
34
+ mapper: vtkVolumeMapper,
35
+ image: vtkImageData
36
+ ) {
37
+ const scalars = image.getPointData().getScalars();
38
+ const dataRange = scalars.getRange();
39
+
40
+ const sampleDistance =
41
+ 0.7 *
42
+ Math.sqrt(
43
+ image
44
+ .getSpacing()
45
+ .map((v) => v * v)
46
+ .reduce((a, b) => a + b, 0)
47
+ );
48
+ mapper.setSampleDistance(sampleDistance * 2 ** (0.4 * 3.0 - 1.5));
49
+
50
+ prop.setScalarOpacityUnitDistance(
51
+ 0,
52
+ getDiagonalLength(image.getBounds()) / Math.max(...image.getDimensions())
53
+ );
54
+ prop.setGradientOpacityMinimumValue(0, 0);
55
+ prop.setGradientOpacityMaximumValue(0, (dataRange[1] - dataRange[0]) * 0.05);
56
+ // - Use shading based on gradient
57
+ prop.setShade(true);
58
+ prop.setUseGradientOpacity(0, true);
59
+ // - generic good default
60
+ prop.setGradientOpacityMinimumOpacity(0, 0.0);
61
+ prop.setGradientOpacityMaximumOpacity(0, 1.0);
62
+ prop.setAmbient(0.2);
63
+ prop.setDiffuse(0.7);
64
+ prop.setSpecular(0.3);
65
+ prop.setSpecularPower(8.0);
66
+ }
67
+
68
+ export function createVolumeThumbnailer(size: number) {
69
+ const container = document.createElement('div');
70
+ container.style.width = `${size}px`;
71
+ container.style.height = `${size}px`;
72
+
73
+ const scene = vtkGenericRenderWindow.newInstance({
74
+ listenWindowResize: false,
75
+ background: [0.2, 0.3, 0.4],
76
+ });
77
+ scene.setContainer(container);
78
+
79
+ const pipeline = createRenderingPipeline();
80
+ const { actor, mapper } = pipeline;
81
+ const renderer = scene.getRenderer();
82
+
83
+ // wrap with proxies for easier usage
84
+ const opacityFuncProxy = vtkPiecewiseFunctionProxy.newInstance({
85
+ piecewiseFunction: pipeline.ofun,
86
+ });
87
+ const colorTransferFuncProxy = vtkLookupTableProxy.newInstance({
88
+ lookupTable: pipeline.cfun,
89
+ });
90
+
91
+ return {
92
+ scene,
93
+ pipeline,
94
+ opacityFuncProxy,
95
+ colorTransferFuncProxy,
96
+ setInputImage(image: vtkImageData | null) {
97
+ if (image) {
98
+ mapper.setInputData(image);
99
+ updateRenderingProperty(actor.getProperty(), mapper, image);
100
+ if (!renderer.hasViewProp(actor)) {
101
+ renderer.addVolume(actor);
102
+ }
103
+ } else {
104
+ renderer.removeVolume(actor);
105
+ }
106
+ },
107
+ resetCameraWithOrientation(direction: Vector3, up: Vector3) {
108
+ const image = mapper.getInputData() as vtkImageData | null;
109
+ if (image) {
110
+ const camera = renderer.getActiveCamera();
111
+ const bounds = image.getBounds();
112
+ const center = [
113
+ (bounds[0] + bounds[1]) / 2,
114
+ (bounds[2] + bounds[3]) / 2,
115
+ (bounds[4] + bounds[5]) / 2,
116
+ ] as Vector3;
117
+
118
+ const position = vec3.clone(center) as Vector3;
119
+ vec3.sub(position, position, direction);
120
+ camera.setFocalPoint(...center);
121
+ camera.setPosition(...position);
122
+ camera.setDirectionOfProjection(...direction);
123
+ camera.setViewUp(...up);
124
+ renderer.resetCamera();
125
+ // ensure correct lighting post camera manip
126
+ renderer.updateLightsGeometryToFollowCamera();
127
+ }
128
+ },
129
+ };
130
+ }
@@ -0,0 +1,142 @@
1
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
2
+ import { TypedArray } from '@kitware/vtk.js/types';
3
+ import { ThumbnailSlice } from '.';
4
+
5
+ function scalarImageToImageData(
6
+ values: TypedArray,
7
+ width: number,
8
+ height: number,
9
+ scaleMin: number,
10
+ scaleMax: number
11
+ ) {
12
+ const im = new ImageData(width, height);
13
+ const arr32 = new Uint32Array(im.data.buffer);
14
+ // scale to 1 unsigned byte
15
+ const factor = 255 / (scaleMax - scaleMin);
16
+ for (let i = 0; i < values.length; i += 1) {
17
+ const byte = Math.floor((values[i] - scaleMin) * factor);
18
+ // ABGR order
19
+ // eslint-disable-next-line no-bitwise
20
+ arr32[i] = (255 << 24) | (byte << 16) | (byte << 8) | byte;
21
+ }
22
+
23
+ return im;
24
+ }
25
+
26
+ /**
27
+ * Generates a thumbnail given an image data.
28
+ *
29
+ * Assumption: image is comprised of single-component scalars
30
+ */
31
+ function generateThumbnail(
32
+ imageData: vtkImageData,
33
+ axis: 0 | 1 | 2 = 2,
34
+ whichSlice = ThumbnailSlice.Middle
35
+ ) {
36
+ const scalars = imageData.getPointData().getScalars();
37
+ const data = scalars.getData() as TypedArray;
38
+ const dataRange = scalars.getRange();
39
+ const dims = imageData.getDimensions();
40
+
41
+ // ThumbnailSlice.First
42
+ let slice = 0;
43
+ if (whichSlice === ThumbnailSlice.Middle) {
44
+ slice = Math.floor(dims[axis] / 2);
45
+ } else if (whichSlice === ThumbnailSlice.Last) {
46
+ slice = dims[axis] - 1;
47
+ }
48
+
49
+ let sliceData: TypedArray;
50
+ let width: number;
51
+ let height: number;
52
+
53
+ if (axis === 0) {
54
+ // work-around for typing data.constructor.
55
+ // data is not necessarily of type Uint8Array.
56
+ sliceData = new (<Uint8ArrayConstructor>data.constructor)(
57
+ dims[1] * dims[2]
58
+ );
59
+ [, width, height] = dims;
60
+ for (let k = 0; k < dims[2]; k++) {
61
+ for (let j = 0; j < dims[1]; j++) {
62
+ const index = slice + j * dims[0] + k * dims[0] * dims[1];
63
+ const offset = k * dims[1] + j;
64
+ sliceData[offset] = data[index];
65
+ }
66
+ }
67
+ } else if (axis === 1) {
68
+ sliceData = new (<Uint8ArrayConstructor>data.constructor)(
69
+ dims[0] * dims[2]
70
+ );
71
+ [width, , height] = dims;
72
+ for (let k = 0; k < dims[2]; k++) {
73
+ for (let i = 0; i < dims[0]; i++) {
74
+ const index = i + slice * dims[0] + k * dims[0] * dims[1];
75
+ const offset = k * dims[0] + i;
76
+ sliceData[offset] = data[index];
77
+ }
78
+ }
79
+ } else if (axis === 2) {
80
+ [width, height] = dims;
81
+ const skip = dims[0] * dims[1];
82
+ const sliceOffset = slice * skip;
83
+ sliceData = Array.isArray(data)
84
+ ? data.slice(sliceOffset, sliceOffset + skip)
85
+ : data.subarray(sliceOffset, sliceOffset + skip);
86
+ }
87
+
88
+ return scalarImageToImageData(
89
+ sliceData!,
90
+ width!,
91
+ height!,
92
+ dataRange[0],
93
+ dataRange[1]
94
+ );
95
+ }
96
+
97
+ export function createVTKImageThumbnailer() {
98
+ const canvas = document.createElement('canvas');
99
+ const ctx = canvas.getContext('2d');
100
+
101
+ const resizeCanvas = document.createElement('canvas');
102
+ const resizeContext = resizeCanvas.getContext('2d');
103
+
104
+ if (!ctx || !resizeContext) {
105
+ throw new Error('[thumbnailer] Failed to create a 2D context');
106
+ }
107
+ return {
108
+ generate(
109
+ imageData: vtkImageData,
110
+ axis: 0 | 1 | 2 = 2,
111
+ whichSlice = ThumbnailSlice.Middle
112
+ ) {
113
+ return generateThumbnail(imageData, axis, whichSlice);
114
+ },
115
+ imageDataToDataURI(
116
+ im: ImageData,
117
+ resizeWidth: number,
118
+ resizeHeight: number
119
+ ) {
120
+ canvas.width = im.width;
121
+ canvas.height = im.height;
122
+ ctx.putImageData(im, 0, 0);
123
+
124
+ let { width, height } = im;
125
+ if (im.width > im.height) {
126
+ width = resizeWidth;
127
+ height *= resizeWidth / im.width;
128
+ } else {
129
+ height = resizeHeight;
130
+ width *= resizeHeight / im.height;
131
+ }
132
+
133
+ resizeCanvas.width = width;
134
+ resizeCanvas.height = height;
135
+ resizeContext.clearRect(0, 0, width, height);
136
+ resizeContext.scale(width / im.width, height / im.height);
137
+ resizeContext.drawImage(canvas, 0, 0);
138
+ // jpegs are smaller than pngs
139
+ return resizeCanvas.toDataURL('image/jpeg');
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,101 @@
1
+ import vtkLabelMap from '@/src/vtk/LabelMap';
2
+ import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
3
+ import { expect } from 'chai';
4
+ import PaintTool from '../paint';
5
+ import EllipsePaintBrush from '../paint/ellipse-brush';
6
+
7
+ describe('Paint Tool', () => {
8
+ describe('EllipsePaintBrush', () => {
9
+ it('should rasterize a ellipse', () => {
10
+ const brush = new EllipsePaintBrush();
11
+
12
+ brush.setSize(1);
13
+ brush.setScale([1, 1]);
14
+ expect(brush.getStencil()).to.deep.equal({
15
+ pixels: new Uint8Array([1]),
16
+ size: [1, 1],
17
+ });
18
+
19
+ brush.setSize(1);
20
+ brush.setScale([3, 5]);
21
+ expect(brush.getStencil()).to.deep.equal({
22
+ // prettier-ignore
23
+ pixels: new Uint8Array([
24
+ 0, 1, 0,
25
+ 1, 1, 1,
26
+ 1, 1, 1,
27
+ 1, 1, 1,
28
+ 0, 1, 0,
29
+ ]),
30
+ size: [3, 5],
31
+ });
32
+
33
+ brush.setSize(4);
34
+ brush.setScale([1.2, 0.8]);
35
+ expect(brush.getStencil()).to.deep.equal({
36
+ // prettier-ignore
37
+ pixels: new Uint8Array([
38
+ 0, 1, 1, 1, 0,
39
+ 1, 1, 1, 1, 1,
40
+ 1, 1, 1, 1, 1,
41
+ 0, 1, 1, 1, 0,
42
+ ]),
43
+ size: [5, 4],
44
+ });
45
+ });
46
+ });
47
+
48
+ describe('PaintTool', () => {
49
+ describe('setBrushSize', () => {
50
+ it('should update the widget stencil', () => {
51
+ const tool = new PaintTool();
52
+ tool.setBrushSize(5);
53
+ tool.setBrushScale([1, 1]);
54
+ const state = tool.factory.getWidgetState();
55
+ expect(state.getStencil().size).to.deep.equal([5, 5]);
56
+ });
57
+ });
58
+ describe('paintLabelmap', () => {
59
+ it('should add paint of a given value to a labelmap', () => {
60
+ const labelmap = vtkLabelMap.newInstance();
61
+ const points = new Uint8Array(4 * 4 * 4);
62
+ labelmap.setDimensions([4, 4, 4]);
63
+ labelmap.getPointData().setScalars(
64
+ vtkDataArray.newInstance({
65
+ numberOfComponents: 1,
66
+ values: points,
67
+ })
68
+ );
69
+ const brushValue = 8;
70
+ const tool = new PaintTool();
71
+ tool.setBrushValue(brushValue);
72
+ tool.setBrushSize(4);
73
+ tool.paintLabelmap(labelmap, 0, [5, 5, 5]);
74
+ expect(points.every((value) => !value)).to.be.true;
75
+ tool.paintLabelmap(labelmap, 0, [1, 1, 1]);
76
+ // only checks some of the brush, not the entire brush.
77
+ expect(points[1 + 4 * 1 + 16 * 1]).to.equal(brushValue);
78
+ });
79
+ it('should linearly interpolate points', () => {
80
+ const labelmap = vtkLabelMap.newInstance();
81
+ const points = new Uint8Array(4 * 4 * 4);
82
+ labelmap.setDimensions([4, 4, 4]);
83
+ labelmap.getPointData().setScalars(
84
+ vtkDataArray.newInstance({
85
+ numberOfComponents: 1,
86
+ values: points,
87
+ })
88
+ );
89
+ const brushValue = 8;
90
+ const tool = new PaintTool();
91
+ tool.setBrushValue(brushValue);
92
+ tool.setBrushSize(1);
93
+ tool.paintLabelmap(labelmap, 2, [0, 0, 0], [3, 3, 0]);
94
+ for (let i = 0; i <= 3; i++) {
95
+ const offset = i + 4 * i;
96
+ expect(points[offset]).to.equal(brushValue);
97
+ }
98
+ });
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,73 @@
1
+ import { InteractionState } from '@/src/vtk/RulerWidget/state';
2
+ import { Vector3 } from '@kitware/vtk.js/types';
3
+ import { expect } from 'chai';
4
+ import RulerTool, { RulerStateUpdate } from '../ruler';
5
+
6
+ type EventData = {
7
+ id: string;
8
+ update: RulerStateUpdate;
9
+ };
10
+
11
+ describe('Ruler Tool Manager', () => {
12
+ describe('createRuler', () => {
13
+ it('should create a ruler', () => {
14
+ const manager = new RulerTool();
15
+ const factory = manager.createRuler('1');
16
+ expect(factory!.getClassName()).to.equal('vtkRulerWidget');
17
+
18
+ manager.teardown();
19
+ });
20
+ it('should register an event emitter for when state is updated', async () => {
21
+ const manager = new RulerTool();
22
+ const factory = manager.createRuler('1');
23
+ const state = factory.getWidgetState();
24
+
25
+ const promise = new Promise<EventData>((resolve) => {
26
+ manager.events.on('widgetUpdate', (eventData) => {
27
+ resolve(eventData);
28
+ });
29
+ });
30
+
31
+ state.getFirstPoint().setOrigin([10, 20, 10]);
32
+ const eventData = await promise;
33
+
34
+ expect(eventData.id).to.equal('1');
35
+ expect(eventData.update.firstPoint).to.deep.equal([10, 20, 10]);
36
+
37
+ manager.teardown();
38
+ });
39
+ });
40
+
41
+ describe('updateRuler', () => {
42
+ it('should update the internal widget state', () => {
43
+ const manager = new RulerTool();
44
+ const factory = manager.createRuler('1');
45
+ const state = factory.getWidgetState();
46
+
47
+ const firstPoint = [12, 13, 14] as Vector3;
48
+ const secondPoint = [15, 16, 17] as Vector3;
49
+
50
+ manager.updateRuler('1', {
51
+ firstPoint,
52
+ secondPoint,
53
+ interactionState: InteractionState.Settled,
54
+ });
55
+
56
+ expect(state.getFirstPoint().getOrigin()).to.deep.equal(firstPoint);
57
+ expect(state.getFirstPoint().getVisible()).to.be.true;
58
+ expect(state.getSecondPoint().getOrigin()).to.deep.equal(secondPoint);
59
+ expect(state.getSecondPoint().getVisible()).to.be.true;
60
+ expect(state.getInteractionState()).to.equal(InteractionState.Settled);
61
+ });
62
+ });
63
+
64
+ describe('removeRuler', () => {
65
+ it('should delete a ruler', () => {
66
+ const manager = new RulerTool();
67
+ manager.createRuler('1');
68
+ manager.removeRuler('1');
69
+
70
+ expect(manager.getFactory('1')).to.be.null;
71
+ });
72
+ });
73
+ });