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,191 @@
1
+ import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
2
+ import vtkActor2D from '@kitware/vtk.js/Rendering/Core/Actor2D';
3
+ import vtkContextRepresentation from '@kitware/vtk.js/Widgets/Representations/ContextRepresentation';
4
+ import vtkWidgetRepresentation from '@kitware/vtk.js/Widgets/Representations/WidgetRepresentation';
5
+ import macro from '@kitware/vtk.js/macros';
6
+ import vtkMapper2D from '@kitware/vtk.js/Rendering/Core/Mapper2D';
7
+ import vtkCoordinate from '@kitware/vtk.js/Rendering/Core/Coordinate';
8
+ import { Coordinate } from '@kitware/vtk.js/Rendering/Core/Coordinate/Constants';
9
+ import { Representation } from '@kitware/vtk.js/Rendering/Core/Property/Constants';
10
+ import { DisplayLocation } from '@kitware/vtk.js/Rendering/Core/Property2D/Constants';
11
+ import { vec3 } from 'gl-matrix';
12
+
13
+ function generateContour({ stencil, location, slicingIndex, indexToWorld }) {
14
+ const [xdim, ydim] = stencil.size;
15
+ const xoffset = Math.floor((xdim - 1) / 2);
16
+ const yoffset = Math.floor((ydim - 1) / 2);
17
+
18
+ // grid of corner points (top-left to bottom-right)
19
+ const gridxdim = xdim + 1;
20
+ const gridydim = ydim + 1;
21
+
22
+ const vertices = []; // arr of 3-tuples
23
+ const lines = []; // arr of 2-tuples of vertex indices
24
+
25
+ // maps grid x/y to vertex index
26
+ const vertexMap = {};
27
+ const getOrCreateVertexIndex = (x, y) => {
28
+ const key = `${x},${y}`;
29
+ if (key in vertexMap) {
30
+ return vertexMap[key];
31
+ }
32
+
33
+ const indexCoords = [...location].map((val) => Math.round(val));
34
+ if (slicingIndex === 0) {
35
+ indexCoords[1] += x - xoffset - 0.5;
36
+ indexCoords[2] += y - yoffset - 0.5;
37
+ } else if (slicingIndex === 1) {
38
+ indexCoords[0] += x - xoffset - 0.5;
39
+ indexCoords[2] += y - yoffset - 0.5;
40
+ } else if (slicingIndex === 2) {
41
+ indexCoords[0] += x - xoffset - 0.5;
42
+ indexCoords[1] += y - yoffset - 0.5;
43
+ }
44
+
45
+ // get the grid point x/y's actual vertex in world space
46
+ // since grid x/y represents the top-left of the actual pixel
47
+ // at x/y, the true continuous index value of the vertex
48
+ // is x-0.5/y-0.5.
49
+
50
+ const worldCoords = [];
51
+ vec3.transformMat4(worldCoords, indexCoords, indexToWorld);
52
+ vertices.push(worldCoords);
53
+
54
+ const index = vertices.length - 1;
55
+ vertexMap[key] = index;
56
+ return index;
57
+ };
58
+
59
+ const getStampPixelAt = (x, y) => {
60
+ if (x < 0 || x >= xdim) return 0;
61
+ if (y < 0 || y >= ydim) return 0;
62
+ return stencil.pixels[y * xdim + x] || 0;
63
+ };
64
+
65
+ for (let gy = 0; gy < gridydim; gy++) {
66
+ for (let gx = 0; gx < gridxdim; gx++) {
67
+ // evaluate point to see if it needs inclusion
68
+ // by looking at all 4 pixels that share this point
69
+ // as a corner.
70
+ // if all empty or all filled, the current point is
71
+ // not used in the contour.
72
+ const count =
73
+ getStampPixelAt(gx, gy) +
74
+ getStampPixelAt(gx - 1, gy) +
75
+ getStampPixelAt(gx, gy - 1) +
76
+ getStampPixelAt(gx - 1, gy - 1);
77
+
78
+ if (count > 0 && count < 4) {
79
+ const v0 = getOrCreateVertexIndex(gx, gy);
80
+
81
+ // now do line check. We look at the east and south lines.
82
+ // east first
83
+ if (getStampPixelAt(gx, gy) !== getStampPixelAt(gx, gy - 1)) {
84
+ const v1 = getOrCreateVertexIndex(gx + 1, gy);
85
+ lines.push([v0, v1]);
86
+ }
87
+ // south next
88
+ if (getStampPixelAt(gx, gy) !== getStampPixelAt(gx - 1, gy)) {
89
+ const v1 = getOrCreateVertexIndex(gx, gy + 1);
90
+ lines.push([v0, v1]);
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ return {
97
+ points: new Float32Array(vertices.flat()),
98
+ lines: new Uint16Array(lines.map(([v0, v1]) => [2, v0, v1]).flat()),
99
+ };
100
+ }
101
+
102
+ function vtkPaintBrushContextRepresentation(publicAPI, model) {
103
+ model.classHierarchy.push('vtkPaintBrushContextRepresentation');
104
+
105
+ model.internalPolyData = vtkPolyData.newInstance({ mtime: 0 });
106
+ model.internalArrays = {
107
+ points: model.internalPolyData.getPoints(),
108
+ lines: model.internalPolyData.getLines(),
109
+ };
110
+
111
+ model.pipelines = {
112
+ brush: {
113
+ source: publicAPI,
114
+ mapper: vtkMapper2D.newInstance({
115
+ transformCoordinate: vtkCoordinate.newInstance({
116
+ coordinateSystem: Coordinate.WORLD,
117
+ }),
118
+ }),
119
+ actor: vtkActor2D.newInstance({ pickable: false, parentProp: publicAPI }),
120
+ },
121
+ };
122
+
123
+ const actorProperty = model.pipelines.brush.actor.getProperty();
124
+ actorProperty.setLineWidth(2);
125
+ actorProperty.setColor([1, 0, 0]);
126
+ actorProperty.setDisplayLocation(DisplayLocation.FOREGROUND);
127
+ actorProperty.setRepresentation(Representation.SURFACE);
128
+
129
+ vtkWidgetRepresentation.connectPipeline(model.pipelines.brush);
130
+
131
+ publicAPI.addActor(model.pipelines.brush.actor);
132
+
133
+ publicAPI.requestData = (inData, outData) => {
134
+ const widgetState = inData[0];
135
+
136
+ const stencil = widgetState.getStencil();
137
+ const brush = widgetState.getBrush();
138
+ const { indexToWorld, worldToIndex } = model;
139
+
140
+ if (stencil && brush.getOrigin()) {
141
+ const location = [];
142
+ vec3.transformMat4(location, brush.getOrigin(), worldToIndex);
143
+
144
+ const contour = generateContour({
145
+ stencil,
146
+ location,
147
+ slicingIndex: model.slicingIndex,
148
+ indexToWorld,
149
+ });
150
+
151
+ const { points, lines } = model.internalArrays;
152
+ points.setData(contour.points);
153
+ lines.setData(contour.lines);
154
+
155
+ model.internalPolyData.modified();
156
+ }
157
+ outData[0] = model.internalPolyData;
158
+ };
159
+ }
160
+
161
+ const DEFAULT_VALUES = {
162
+ defaultScale: 1,
163
+ drawBorder: false,
164
+ drawFace: true,
165
+ worldToIndex: null,
166
+ indexToWorld: null,
167
+ slicingIndex: 0,
168
+ };
169
+
170
+ // ----------------------------------------------------------------------------
171
+
172
+ export function extend(publicAPI, model, initialValues = {}) {
173
+ Object.assign(model, DEFAULT_VALUES, initialValues);
174
+ vtkContextRepresentation.extend(publicAPI, model, initialValues);
175
+ macro.setGet(publicAPI, model, [
176
+ 'slicingIndex',
177
+ 'indexToWorld',
178
+ 'worldToIndex',
179
+ ]);
180
+ macro.get(publicAPI, model, ['mapper', 'actor']);
181
+ vtkPaintBrushContextRepresentation(publicAPI, model);
182
+ }
183
+
184
+ // ----------------------------------------------------------------------------
185
+
186
+ export const newInstance = macro.newInstance(
187
+ extend,
188
+ 'vtkPaintBrushContextRepresentation'
189
+ );
190
+
191
+ export default { newInstance, extend };
@@ -0,0 +1,101 @@
1
+ import macro from '@kitware/vtk.js/macro';
2
+
3
+ export function shouldIgnoreEvent(e: any) {
4
+ return e.altKey || e.controlKey || e.shiftKey;
5
+ }
6
+
7
+ export default function widgetBehavior(publicAPI: any, model: any) {
8
+ model.classHierarchy.push('vtkPaintWidgetProp');
9
+
10
+ // support setting per-view widget manipulators
11
+ macro.setGet(publicAPI, model, ['manipulator']);
12
+
13
+ let isPainting = false;
14
+
15
+ /**
16
+ * Starts painting
17
+ */
18
+ publicAPI.handleLeftButtonPress = (eventData: any) => {
19
+ if (!model.manipulator || shouldIgnoreEvent(eventData)) {
20
+ return macro.VOID;
21
+ }
22
+
23
+ const worldCoords = model.manipulator.handleEvent(
24
+ eventData,
25
+ model._apiSpecificRenderWindow
26
+ );
27
+
28
+ if (!worldCoords) {
29
+ return macro.VOID;
30
+ }
31
+
32
+ const brush = model.widgetState.getBrush();
33
+ brush.setOrigin(...worldCoords);
34
+
35
+ isPainting = true;
36
+ publicAPI.invokeStartInteractionEvent();
37
+ return macro.EVENT_ABORT;
38
+ };
39
+
40
+ /**
41
+ * Paints
42
+ */
43
+ publicAPI.handleMouseMove = (eventData: any) => {
44
+ if (shouldIgnoreEvent(eventData)) {
45
+ return macro.VOID;
46
+ }
47
+
48
+ const worldCoords = model.manipulator.handleEvent(
49
+ eventData,
50
+ model._apiSpecificRenderWindow
51
+ );
52
+
53
+ if (!worldCoords) {
54
+ return macro.VOID;
55
+ }
56
+
57
+ const brush = model.widgetState.getBrush();
58
+ brush.setOrigin(...worldCoords);
59
+
60
+ if (isPainting) {
61
+ publicAPI.invokeInteractionEvent();
62
+ }
63
+ return macro.EVENT_ABORT;
64
+ };
65
+
66
+ /**
67
+ * Finishes paint
68
+ */
69
+ publicAPI.handleLeftButtonRelease = (eventData: any) => {
70
+ if (!isPainting || shouldIgnoreEvent(eventData)) {
71
+ return macro.VOID;
72
+ }
73
+
74
+ isPainting = false;
75
+ publicAPI.invokeEndInteractionEvent();
76
+ return macro.EVENT_ABORT;
77
+ };
78
+
79
+ publicAPI.handleMouseWheel = () => {
80
+ if (isPainting) {
81
+ return macro.EVENT_ABORT;
82
+ }
83
+ return macro.VOID;
84
+ };
85
+
86
+ publicAPI.grabFocus = () => {
87
+ if (!model.hasFocus) {
88
+ model.hasFocus = true;
89
+ model._interactor.requestAnimation(publicAPI);
90
+ }
91
+ };
92
+
93
+ publicAPI.loseFocus = () => {
94
+ if (model.hasFocus) {
95
+ model._interactor.cancelAnimation(publicAPI);
96
+ }
97
+ model.hasFocus = false;
98
+ // model._widgetManager.enablePicking();
99
+ // model._interactor.render();
100
+ };
101
+ }
@@ -0,0 +1,28 @@
1
+ import vtkAbstractWidget from '@kitware/vtk.js/Widgets/Core/AbstractWidget';
2
+ import vtkAbstractWidgetFactory from '@kitware/vtk.js/Widgets/Core/AbstractWidgetFactory';
3
+ import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
4
+ import { mat4, vec3 } from 'gl-matrix';
5
+ import { PaintWidgetState } from './state';
6
+
7
+ export interface vtkPaintViewWidget extends vtkAbstractWidget {
8
+ setManipulator(manipulator: vtkPlaneManipulator): boolean;
9
+ getManipulator(): vtkPlaneManipulator;
10
+ setSlicingIndex(index: number): boolean;
11
+ setIndexToWorld(transform: mat4): boolean;
12
+ getIndexToWorld(): mat4;
13
+ setWorldToIndex(transform: mat4): boolean;
14
+ getWorldToIndex(): mat4;
15
+ }
16
+
17
+ export interface vtkPaintWidget extends vtkAbstractWidgetFactory {
18
+ getWidgetState(): PaintWidgetState;
19
+ }
20
+
21
+ export function newInstance(): vtkPaintWidget;
22
+
23
+ export function shouldIgnoreEvent(ev: any): boolean;
24
+
25
+ export declare const vtkPaintWidget: {
26
+ newInstance: typeof newInstance;
27
+ };
28
+ export default vtkPaintWidget;
@@ -0,0 +1,56 @@
1
+ import macro from '@kitware/vtk.js/macro';
2
+ import vtkAbstractWidgetFactory from '@kitware/vtk.js/Widgets/Core/AbstractWidgetFactory';
3
+ import vtkPlanePointManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
4
+ import vtkPaintBrushContextRepresentation from '@/src/vtk/PaintBrushContextRepresentation';
5
+
6
+ import widgetBehavior from './behavior';
7
+ import stateGenerator from './state';
8
+
9
+ export { shouldIgnoreEvent } from './behavior';
10
+
11
+ // ----------------------------------------------------------------------------
12
+ // Factory
13
+ // ----------------------------------------------------------------------------
14
+
15
+ function vtkPaintWidget(publicAPI, model) {
16
+ model.classHierarchy.push('vtkPaintWidget');
17
+
18
+ model.methodsToLink = ['slicingIndex', 'indexToWorld', 'worldToIndex'];
19
+
20
+ publicAPI.getRepresentationsForViewType = () => [
21
+ {
22
+ builder: vtkPaintBrushContextRepresentation,
23
+ labels: ['brush'],
24
+ },
25
+ ];
26
+
27
+ // Default manipulator
28
+ model.manipulator = vtkPlanePointManipulator.newInstance();
29
+ }
30
+
31
+ // ----------------------------------------------------------------------------
32
+
33
+ const DEFAULT_VALUES = {};
34
+
35
+ // ----------------------------------------------------------------------------
36
+
37
+ export function extend(publicAPI, model, initialValues = {}) {
38
+ Object.assign(model, DEFAULT_VALUES, initialValues);
39
+
40
+ vtkAbstractWidgetFactory.extend(publicAPI, model, {
41
+ ...initialValues,
42
+ behavior: widgetBehavior,
43
+ widgetState: stateGenerator(),
44
+ });
45
+ macro.get(publicAPI, model, ['manipulator']);
46
+
47
+ vtkPaintWidget(publicAPI, model);
48
+ }
49
+
50
+ // ----------------------------------------------------------------------------
51
+
52
+ export const newInstance = macro.newInstance(extend, 'vtkPaintWidget');
53
+
54
+ // ----------------------------------------------------------------------------
55
+
56
+ export default { newInstance, extend };
@@ -0,0 +1,41 @@
1
+ import { IBrushStencil } from '@/src/core/tools/paint/brush';
2
+ import { Vector3 } from '@kitware/vtk.js/types';
3
+ import vtkStateBuilder from '@kitware/vtk.js/Widgets/Core/StateBuilder';
4
+ import vtkWidgetState from '@kitware/vtk.js/Widgets/Core/WidgetState';
5
+
6
+ export interface PaintPointWidgetState extends vtkWidgetState {
7
+ setOrigin(origin: Vector3 | null): boolean;
8
+ getOrigin(): Vector3 | null;
9
+ setScale1(scale: number): boolean;
10
+ getScale1(): number;
11
+ setVisible(visible: boolean): boolean;
12
+ getVisible(): boolean;
13
+ setColor(color: number): boolean;
14
+ getColor(): number;
15
+ }
16
+
17
+ export interface PaintWidgetState extends vtkWidgetState {
18
+ getBrush(): PaintPointWidgetState;
19
+ getStencil(): IBrushStencil;
20
+ setStencil(stencil: IBrushStencil): boolean;
21
+ }
22
+
23
+ export default function generateState() {
24
+ return vtkStateBuilder
25
+ .createBuilder()
26
+ .addStateFromMixin({
27
+ labels: ['brush'],
28
+ name: 'brush',
29
+ mixins: ['origin', 'scale1', 'visible', 'color'],
30
+ initialValues: {
31
+ scale1: 1,
32
+ origin: null,
33
+ visible: true,
34
+ },
35
+ })
36
+ .addField({
37
+ name: 'stencil',
38
+ initialValue: null,
39
+ })
40
+ .build() as PaintWidgetState;
41
+ }
@@ -0,0 +1,203 @@
1
+ import macro from '@kitware/vtk.js/macro';
2
+ import vtkPiecewiseGaussianWidget from '@kitware/vtk.js/Interaction/Widgets/PiecewiseGaussianWidget';
3
+
4
+ export const Mode = {
5
+ Gaussians: 1,
6
+ Points: 2,
7
+ };
8
+
9
+ export const ADJUST_POSITION_CURSOR = '-webkit-grab';
10
+
11
+ /**
12
+ * Samples a piecewise linear function at regular intervals in [0,1].
13
+ * The first and last values are set to zero, since the output
14
+ * is used for vtkPiecewiseGaussianWidget.drawChart().
15
+ *
16
+ * Assumes normalized x/y coordinate points.
17
+ */
18
+
19
+ export function samplePiecewiseLinear(points, shift = 0, samples = 256) {
20
+ const filledPoints = [...points];
21
+ // set endpoints to drop to 0
22
+ filledPoints.push([points[0][0], 0]);
23
+ filledPoints.push([points[points.length - 1][0], 0]);
24
+
25
+ const sampledPoints = [];
26
+ let pi = 0;
27
+ let p1 = filledPoints[0]; // left
28
+ let p2 = filledPoints[1]; // right
29
+ let slope = (p2[1] - p1[1]) / (p2[0] - p1[0]);
30
+ for (let i = 0; i < samples; i += 1) {
31
+ const sx = i / (samples - 1) - shift; // sample x
32
+ if (i < samples - 1) {
33
+ while (sx > p2[0] && pi < filledPoints.length - 2) {
34
+ pi += 1;
35
+ // extraneous work if this loop goes over more than once,
36
+ // which only happens if sx > 0 on first iteration (aka shift < 0)
37
+ p1 = filledPoints[pi];
38
+ p2 = filledPoints[pi + 1];
39
+ if (Math.abs(p2[0] - p1[0]) < 1e-8) {
40
+ slope = Infinity * Math.sign(p2[1] - p1[1]);
41
+ } else {
42
+ slope = (p2[1] - p1[1]) / (p2[0] - p1[0]);
43
+ }
44
+ }
45
+ if (slope === Infinity) {
46
+ sampledPoints.push(1);
47
+ } else if (slope === -Infinity) {
48
+ sampledPoints.push(0);
49
+ } else {
50
+ sampledPoints.push(slope * (sx - p1[0]) + p1[1]);
51
+ }
52
+ }
53
+ }
54
+ return sampledPoints;
55
+ }
56
+
57
+ // ----------------------------------------------------------------------------
58
+ // vtkPiecewiseWidget methods
59
+ // ----------------------------------------------------------------------------
60
+
61
+ function vtkPiecewiseWidget(publicAPI, model) {
62
+ model.classHierarchy.push('vtkPiecewiseWidget');
63
+
64
+ const superClass = { ...publicAPI };
65
+
66
+ model.pwMode = Mode.Gaussians;
67
+ model.opacityPoints = [];
68
+ model.opacityPointShift = 0;
69
+
70
+ publicAPI.setGaussiansMode = () => {
71
+ model.pwMode = Mode.Gaussians;
72
+ };
73
+
74
+ publicAPI.setPointsMode = () => {
75
+ // clear gaussians so we don't keep this ref around
76
+ model.gaussians = [];
77
+ model.pwMode = Mode.Points;
78
+ };
79
+
80
+ publicAPI.isModeGaussians = () => model.pwMode === Mode.Gaussians;
81
+ publicAPI.isModePoints = () => model.pwMode === Mode.Points;
82
+ publicAPI.getMode = () => model.pwMode;
83
+
84
+ publicAPI.shiftPosition = (coords, meta) => {
85
+ model.opacityPointShift =
86
+ meta.originalOpacityPointShift + coords[0] - meta.originalXY[0];
87
+ return true;
88
+ };
89
+
90
+ publicAPI.onHover = (x, y) => {
91
+ if (publicAPI.isModeGaussians()) {
92
+ return superClass.onHover(x, y);
93
+ }
94
+ model.canvas.style.cursor = ADJUST_POSITION_CURSOR;
95
+ return true;
96
+ };
97
+
98
+ publicAPI.onClick = (x, y) => {
99
+ if (publicAPI.isModeGaussians()) {
100
+ return superClass.onClick(x, y);
101
+ }
102
+ return undefined;
103
+ };
104
+
105
+ publicAPI.onDown = (x, y) => {
106
+ if (publicAPI.isModeGaussians()) {
107
+ return superClass.onDown(x, y);
108
+ }
109
+
110
+ if (!model.mouseIsDown) {
111
+ publicAPI.invokeAnimation(true);
112
+ }
113
+
114
+ const mouseCoords = vtkPiecewiseGaussianWidget.normalizeCoordinates(
115
+ x,
116
+ y,
117
+ model.graphArea
118
+ );
119
+
120
+ model.mouseIsDown = true;
121
+ model.activeGaussian = -1;
122
+ model.dragAction = {
123
+ originalXY: mouseCoords,
124
+ originalOpacityPointShift: model.opacityPointShift,
125
+ };
126
+
127
+ return true;
128
+ };
129
+
130
+ publicAPI.onDrag = (x, y) => {
131
+ if (publicAPI.isModeGaussians()) {
132
+ return superClass.onDrag(x, y);
133
+ }
134
+
135
+ const normCoords = vtkPiecewiseGaussianWidget.normalizeCoordinates(
136
+ x,
137
+ y,
138
+ model.graphArea
139
+ );
140
+ if (publicAPI.shiftPosition(normCoords, model.dragAction)) {
141
+ model.opacities = samplePiecewiseLinear(
142
+ model.opacityPoints,
143
+ model.opacityPointShift
144
+ );
145
+ publicAPI.invokeOpacityChange(publicAPI, true);
146
+ }
147
+
148
+ publicAPI.modified();
149
+ return true;
150
+ };
151
+
152
+ publicAPI.setOpacityPoints = (points, shift = 0) => {
153
+ if (publicAPI.isModePoints()) {
154
+ // deep copy
155
+ model.opacityPoints = points.map((p) => [p[0], p[1]]);
156
+ model.opacityPointShift = shift;
157
+
158
+ model.opacities = samplePiecewiseLinear(
159
+ model.opacityPoints,
160
+ model.opacityPointShift
161
+ );
162
+ publicAPI.modified();
163
+ }
164
+ };
165
+
166
+ publicAPI.getEffectiveOpacityPoints = () =>
167
+ model.opacityPoints.map((p) => [p[0] + model.opacityPointShift, p[1]]);
168
+
169
+ publicAPI.render = () => {
170
+ if (publicAPI.isModePoints()) {
171
+ model.activeGaussian = -1;
172
+ model.selectedGaussian = -1;
173
+ }
174
+ superClass.render();
175
+ };
176
+ }
177
+
178
+ // ----------------------------------------------------------------------------
179
+ // Object factory
180
+ // ----------------------------------------------------------------------------
181
+
182
+ const DEFAULT_VALUES = {};
183
+
184
+ // ----------------------------------------------------------------------------
185
+
186
+ export function extend(publicAPI, model, initialValues = {}) {
187
+ Object.assign(model, DEFAULT_VALUES, initialValues);
188
+
189
+ vtkPiecewiseGaussianWidget.extend(publicAPI, model, initialValues);
190
+
191
+ macro.setGet(publicAPI, model, ['opacityPoints', 'opacityPointShift']);
192
+
193
+ // Object specific methods
194
+ vtkPiecewiseWidget(publicAPI, model);
195
+ }
196
+
197
+ // ----------------------------------------------------------------------------
198
+
199
+ export const newInstance = macro.newInstance(extend, 'vtkPiecewiseWidget');
200
+
201
+ // ----------------------------------------------------------------------------
202
+
203
+ export default { newInstance, extend };