vispy 0.15.0__cp313-cp313-win_amd64.whl

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.

Potentially problematic release.


This version of vispy might be problematic. Click here for more details.

Files changed (521) hide show
  1. vispy/__init__.py +33 -0
  2. vispy/app/__init__.py +15 -0
  3. vispy/app/_default_app.py +76 -0
  4. vispy/app/_detect_eventloop.py +148 -0
  5. vispy/app/application.py +263 -0
  6. vispy/app/backends/__init__.py +52 -0
  7. vispy/app/backends/_egl.py +264 -0
  8. vispy/app/backends/_glfw.py +513 -0
  9. vispy/app/backends/_jupyter_rfb.py +278 -0
  10. vispy/app/backends/_offscreen_util.py +121 -0
  11. vispy/app/backends/_osmesa.py +235 -0
  12. vispy/app/backends/_pyglet.py +451 -0
  13. vispy/app/backends/_pyqt4.py +36 -0
  14. vispy/app/backends/_pyqt5.py +36 -0
  15. vispy/app/backends/_pyqt6.py +40 -0
  16. vispy/app/backends/_pyside.py +37 -0
  17. vispy/app/backends/_pyside2.py +52 -0
  18. vispy/app/backends/_pyside6.py +53 -0
  19. vispy/app/backends/_qt.py +1003 -0
  20. vispy/app/backends/_sdl2.py +444 -0
  21. vispy/app/backends/_template.py +244 -0
  22. vispy/app/backends/_test.py +8 -0
  23. vispy/app/backends/_tk.py +800 -0
  24. vispy/app/backends/_wx.py +476 -0
  25. vispy/app/backends/tests/__init__.py +0 -0
  26. vispy/app/backends/tests/test_offscreen_util.py +52 -0
  27. vispy/app/backends/tests/test_rfb.py +77 -0
  28. vispy/app/base.py +294 -0
  29. vispy/app/canvas.py +828 -0
  30. vispy/app/qt.py +92 -0
  31. vispy/app/tests/__init__.py +0 -0
  32. vispy/app/tests/qt-designer.ui +58 -0
  33. vispy/app/tests/test_app.py +442 -0
  34. vispy/app/tests/test_backends.py +164 -0
  35. vispy/app/tests/test_canvas.py +122 -0
  36. vispy/app/tests/test_context.py +92 -0
  37. vispy/app/tests/test_qt.py +47 -0
  38. vispy/app/tests/test_simultaneous.py +134 -0
  39. vispy/app/timer.py +174 -0
  40. vispy/color/__init__.py +17 -0
  41. vispy/color/_color_dict.py +193 -0
  42. vispy/color/color_array.py +447 -0
  43. vispy/color/color_space.py +181 -0
  44. vispy/color/colormap.py +1213 -0
  45. vispy/color/tests/__init__.py +0 -0
  46. vispy/color/tests/test_color.py +378 -0
  47. vispy/conftest.py +12 -0
  48. vispy/ext/__init__.py +0 -0
  49. vispy/ext/cocoapy.py +1522 -0
  50. vispy/ext/cubehelix.py +138 -0
  51. vispy/ext/egl.py +375 -0
  52. vispy/ext/fontconfig.py +118 -0
  53. vispy/ext/gdi32plus.py +206 -0
  54. vispy/ext/osmesa.py +105 -0
  55. vispy/geometry/__init__.py +23 -0
  56. vispy/geometry/_triangulation_debugger.py +171 -0
  57. vispy/geometry/calculations.py +162 -0
  58. vispy/geometry/curves.py +399 -0
  59. vispy/geometry/generation.py +643 -0
  60. vispy/geometry/isocurve.py +175 -0
  61. vispy/geometry/isosurface.py +465 -0
  62. vispy/geometry/meshdata.py +700 -0
  63. vispy/geometry/normals.py +78 -0
  64. vispy/geometry/parametric.py +56 -0
  65. vispy/geometry/polygon.py +137 -0
  66. vispy/geometry/rect.py +210 -0
  67. vispy/geometry/tests/__init__.py +0 -0
  68. vispy/geometry/tests/test_calculations.py +23 -0
  69. vispy/geometry/tests/test_generation.py +56 -0
  70. vispy/geometry/tests/test_meshdata.py +106 -0
  71. vispy/geometry/tests/test_triangulation.py +594 -0
  72. vispy/geometry/torusknot.py +142 -0
  73. vispy/geometry/triangulation.py +876 -0
  74. vispy/gloo/__init__.py +56 -0
  75. vispy/gloo/buffer.py +505 -0
  76. vispy/gloo/context.py +272 -0
  77. vispy/gloo/framebuffer.py +257 -0
  78. vispy/gloo/gl/__init__.py +234 -0
  79. vispy/gloo/gl/_constants.py +332 -0
  80. vispy/gloo/gl/_es2.py +986 -0
  81. vispy/gloo/gl/_gl2.py +1365 -0
  82. vispy/gloo/gl/_proxy.py +499 -0
  83. vispy/gloo/gl/_pyopengl2.py +362 -0
  84. vispy/gloo/gl/dummy.py +24 -0
  85. vispy/gloo/gl/es2.py +62 -0
  86. vispy/gloo/gl/gl2.py +98 -0
  87. vispy/gloo/gl/glplus.py +168 -0
  88. vispy/gloo/gl/pyopengl2.py +97 -0
  89. vispy/gloo/gl/tests/__init__.py +0 -0
  90. vispy/gloo/gl/tests/test_basics.py +282 -0
  91. vispy/gloo/gl/tests/test_functionality.py +568 -0
  92. vispy/gloo/gl/tests/test_names.py +246 -0
  93. vispy/gloo/gl/tests/test_use.py +71 -0
  94. vispy/gloo/glir.py +1824 -0
  95. vispy/gloo/globject.py +101 -0
  96. vispy/gloo/preprocessor.py +67 -0
  97. vispy/gloo/program.py +543 -0
  98. vispy/gloo/tests/__init__.py +0 -0
  99. vispy/gloo/tests/test_buffer.py +558 -0
  100. vispy/gloo/tests/test_context.py +119 -0
  101. vispy/gloo/tests/test_framebuffer.py +195 -0
  102. vispy/gloo/tests/test_glir.py +307 -0
  103. vispy/gloo/tests/test_globject.py +35 -0
  104. vispy/gloo/tests/test_program.py +302 -0
  105. vispy/gloo/tests/test_texture.py +732 -0
  106. vispy/gloo/tests/test_use_gloo.py +187 -0
  107. vispy/gloo/tests/test_util.py +60 -0
  108. vispy/gloo/tests/test_wrappers.py +261 -0
  109. vispy/gloo/texture.py +1046 -0
  110. vispy/gloo/util.py +129 -0
  111. vispy/gloo/wrappers.py +762 -0
  112. vispy/glsl/__init__.py +42 -0
  113. vispy/glsl/antialias/antialias.glsl +7 -0
  114. vispy/glsl/antialias/cap-butt.glsl +31 -0
  115. vispy/glsl/antialias/cap-round.glsl +29 -0
  116. vispy/glsl/antialias/cap-square.glsl +30 -0
  117. vispy/glsl/antialias/cap-triangle-in.glsl +30 -0
  118. vispy/glsl/antialias/cap-triangle-out.glsl +30 -0
  119. vispy/glsl/antialias/cap.glsl +67 -0
  120. vispy/glsl/antialias/caps.glsl +67 -0
  121. vispy/glsl/antialias/filled.glsl +50 -0
  122. vispy/glsl/antialias/outline.glsl +40 -0
  123. vispy/glsl/antialias/stroke.glsl +43 -0
  124. vispy/glsl/arrowheads/angle.glsl +99 -0
  125. vispy/glsl/arrowheads/arrowheads.frag +60 -0
  126. vispy/glsl/arrowheads/arrowheads.glsl +12 -0
  127. vispy/glsl/arrowheads/arrowheads.vert +83 -0
  128. vispy/glsl/arrowheads/curved.glsl +48 -0
  129. vispy/glsl/arrowheads/inhibitor.glsl +26 -0
  130. vispy/glsl/arrowheads/stealth.glsl +46 -0
  131. vispy/glsl/arrowheads/triangle.glsl +97 -0
  132. vispy/glsl/arrowheads/util.glsl +13 -0
  133. vispy/glsl/arrows/angle-30.glsl +12 -0
  134. vispy/glsl/arrows/angle-60.glsl +12 -0
  135. vispy/glsl/arrows/angle-90.glsl +12 -0
  136. vispy/glsl/arrows/arrow.frag +39 -0
  137. vispy/glsl/arrows/arrow.vert +49 -0
  138. vispy/glsl/arrows/arrows.glsl +17 -0
  139. vispy/glsl/arrows/common.glsl +187 -0
  140. vispy/glsl/arrows/curved.glsl +63 -0
  141. vispy/glsl/arrows/stealth.glsl +50 -0
  142. vispy/glsl/arrows/triangle-30.glsl +12 -0
  143. vispy/glsl/arrows/triangle-60.glsl +12 -0
  144. vispy/glsl/arrows/triangle-90.glsl +12 -0
  145. vispy/glsl/arrows/util.glsl +98 -0
  146. vispy/glsl/build_spatial_filters.py +660 -0
  147. vispy/glsl/collections/agg-fast-path.frag +20 -0
  148. vispy/glsl/collections/agg-fast-path.vert +78 -0
  149. vispy/glsl/collections/agg-glyph.frag +60 -0
  150. vispy/glsl/collections/agg-glyph.vert +33 -0
  151. vispy/glsl/collections/agg-marker.frag +35 -0
  152. vispy/glsl/collections/agg-marker.vert +48 -0
  153. vispy/glsl/collections/agg-path.frag +55 -0
  154. vispy/glsl/collections/agg-path.vert +166 -0
  155. vispy/glsl/collections/agg-point.frag +21 -0
  156. vispy/glsl/collections/agg-point.vert +35 -0
  157. vispy/glsl/collections/agg-segment.frag +32 -0
  158. vispy/glsl/collections/agg-segment.vert +75 -0
  159. vispy/glsl/collections/marker.frag +38 -0
  160. vispy/glsl/collections/marker.vert +48 -0
  161. vispy/glsl/collections/raw-path.frag +15 -0
  162. vispy/glsl/collections/raw-path.vert +24 -0
  163. vispy/glsl/collections/raw-point.frag +14 -0
  164. vispy/glsl/collections/raw-point.vert +31 -0
  165. vispy/glsl/collections/raw-segment.frag +18 -0
  166. vispy/glsl/collections/raw-segment.vert +26 -0
  167. vispy/glsl/collections/raw-triangle.frag +13 -0
  168. vispy/glsl/collections/raw-triangle.vert +26 -0
  169. vispy/glsl/collections/sdf-glyph-ticks.vert +69 -0
  170. vispy/glsl/collections/sdf-glyph.frag +80 -0
  171. vispy/glsl/collections/sdf-glyph.vert +59 -0
  172. vispy/glsl/collections/tick-labels.vert +71 -0
  173. vispy/glsl/colormaps/autumn.glsl +20 -0
  174. vispy/glsl/colormaps/blues.glsl +20 -0
  175. vispy/glsl/colormaps/color-space.glsl +17 -0
  176. vispy/glsl/colormaps/colormaps.glsl +24 -0
  177. vispy/glsl/colormaps/cool.glsl +20 -0
  178. vispy/glsl/colormaps/fire.glsl +21 -0
  179. vispy/glsl/colormaps/gray.glsl +20 -0
  180. vispy/glsl/colormaps/greens.glsl +20 -0
  181. vispy/glsl/colormaps/hot.glsl +22 -0
  182. vispy/glsl/colormaps/ice.glsl +20 -0
  183. vispy/glsl/colormaps/icefire.glsl +23 -0
  184. vispy/glsl/colormaps/parse.py +40 -0
  185. vispy/glsl/colormaps/reds.glsl +20 -0
  186. vispy/glsl/colormaps/spring.glsl +20 -0
  187. vispy/glsl/colormaps/summer.glsl +20 -0
  188. vispy/glsl/colormaps/user.glsl +22 -0
  189. vispy/glsl/colormaps/util.glsl +41 -0
  190. vispy/glsl/colormaps/wheel.glsl +21 -0
  191. vispy/glsl/colormaps/winter.glsl +20 -0
  192. vispy/glsl/lines/agg.frag +320 -0
  193. vispy/glsl/lines/agg.vert +241 -0
  194. vispy/glsl/markers/arrow.glsl +12 -0
  195. vispy/glsl/markers/asterisk.glsl +16 -0
  196. vispy/glsl/markers/chevron.glsl +14 -0
  197. vispy/glsl/markers/clover.glsl +20 -0
  198. vispy/glsl/markers/club.glsl +31 -0
  199. vispy/glsl/markers/cross.glsl +17 -0
  200. vispy/glsl/markers/diamond.glsl +12 -0
  201. vispy/glsl/markers/disc.glsl +9 -0
  202. vispy/glsl/markers/ellipse.glsl +67 -0
  203. vispy/glsl/markers/hbar.glsl +9 -0
  204. vispy/glsl/markers/heart.glsl +15 -0
  205. vispy/glsl/markers/infinity.glsl +15 -0
  206. vispy/glsl/markers/marker-sdf.frag +74 -0
  207. vispy/glsl/markers/marker-sdf.vert +41 -0
  208. vispy/glsl/markers/marker.frag +36 -0
  209. vispy/glsl/markers/marker.vert +46 -0
  210. vispy/glsl/markers/markers.glsl +24 -0
  211. vispy/glsl/markers/pin.glsl +18 -0
  212. vispy/glsl/markers/ring.glsl +11 -0
  213. vispy/glsl/markers/spade.glsl +28 -0
  214. vispy/glsl/markers/square.glsl +10 -0
  215. vispy/glsl/markers/tag.glsl +11 -0
  216. vispy/glsl/markers/triangle.glsl +14 -0
  217. vispy/glsl/markers/vbar.glsl +9 -0
  218. vispy/glsl/math/circle-through-2-points.glsl +30 -0
  219. vispy/glsl/math/constants.glsl +48 -0
  220. vispy/glsl/math/double.glsl +114 -0
  221. vispy/glsl/math/functions.glsl +20 -0
  222. vispy/glsl/math/point-to-line-distance.glsl +31 -0
  223. vispy/glsl/math/point-to-line-projection.glsl +29 -0
  224. vispy/glsl/math/signed-line-distance.glsl +27 -0
  225. vispy/glsl/math/signed-segment-distance.glsl +30 -0
  226. vispy/glsl/misc/regular-grid.frag +244 -0
  227. vispy/glsl/misc/spatial-filters.frag +1407 -0
  228. vispy/glsl/misc/viewport-NDC.glsl +20 -0
  229. vispy/glsl/transforms/azimuthal-equal-area.glsl +32 -0
  230. vispy/glsl/transforms/azimuthal-equidistant.glsl +38 -0
  231. vispy/glsl/transforms/hammer.glsl +44 -0
  232. vispy/glsl/transforms/identity.glsl +6 -0
  233. vispy/glsl/transforms/identity_forward.glsl +23 -0
  234. vispy/glsl/transforms/identity_inverse.glsl +23 -0
  235. vispy/glsl/transforms/linear-scale.glsl +127 -0
  236. vispy/glsl/transforms/log-scale.glsl +126 -0
  237. vispy/glsl/transforms/mercator-transverse-forward.glsl +40 -0
  238. vispy/glsl/transforms/mercator-transverse-inverse.glsl +40 -0
  239. vispy/glsl/transforms/panzoom.glsl +10 -0
  240. vispy/glsl/transforms/polar.glsl +41 -0
  241. vispy/glsl/transforms/position.glsl +44 -0
  242. vispy/glsl/transforms/power-scale.glsl +139 -0
  243. vispy/glsl/transforms/projection.glsl +7 -0
  244. vispy/glsl/transforms/pvm.glsl +13 -0
  245. vispy/glsl/transforms/rotate.glsl +45 -0
  246. vispy/glsl/transforms/trackball.glsl +15 -0
  247. vispy/glsl/transforms/translate.glsl +35 -0
  248. vispy/glsl/transforms/transverse_mercator.glsl +38 -0
  249. vispy/glsl/transforms/viewport-clipping.glsl +14 -0
  250. vispy/glsl/transforms/viewport-transform.glsl +16 -0
  251. vispy/glsl/transforms/viewport.glsl +50 -0
  252. vispy/glsl/transforms/x.glsl +24 -0
  253. vispy/glsl/transforms/y.glsl +19 -0
  254. vispy/glsl/transforms/z.glsl +14 -0
  255. vispy/io/__init__.py +20 -0
  256. vispy/io/_data/spatial-filters.npy +0 -0
  257. vispy/io/datasets.py +94 -0
  258. vispy/io/image.py +231 -0
  259. vispy/io/mesh.py +122 -0
  260. vispy/io/stl.py +167 -0
  261. vispy/io/tests/__init__.py +0 -0
  262. vispy/io/tests/test_image.py +47 -0
  263. vispy/io/tests/test_io.py +121 -0
  264. vispy/io/wavefront.py +350 -0
  265. vispy/plot/__init__.py +36 -0
  266. vispy/plot/fig.py +58 -0
  267. vispy/plot/plotwidget.py +522 -0
  268. vispy/plot/tests/__init__.py +0 -0
  269. vispy/plot/tests/test_plot.py +46 -0
  270. vispy/scene/__init__.py +43 -0
  271. vispy/scene/cameras/__init__.py +27 -0
  272. vispy/scene/cameras/_base.py +38 -0
  273. vispy/scene/cameras/arcball.py +105 -0
  274. vispy/scene/cameras/base_camera.py +551 -0
  275. vispy/scene/cameras/fly.py +474 -0
  276. vispy/scene/cameras/magnify.py +163 -0
  277. vispy/scene/cameras/panzoom.py +311 -0
  278. vispy/scene/cameras/perspective.py +338 -0
  279. vispy/scene/cameras/tests/__init__.py +0 -0
  280. vispy/scene/cameras/tests/test_cameras.py +27 -0
  281. vispy/scene/cameras/tests/test_link.py +53 -0
  282. vispy/scene/cameras/tests/test_perspective.py +122 -0
  283. vispy/scene/cameras/turntable.py +183 -0
  284. vispy/scene/canvas.py +639 -0
  285. vispy/scene/events.py +85 -0
  286. vispy/scene/node.py +644 -0
  287. vispy/scene/subscene.py +20 -0
  288. vispy/scene/tests/__init__.py +0 -0
  289. vispy/scene/tests/test_canvas.py +119 -0
  290. vispy/scene/tests/test_node.py +142 -0
  291. vispy/scene/tests/test_visuals.py +141 -0
  292. vispy/scene/visuals.py +276 -0
  293. vispy/scene/widgets/__init__.py +18 -0
  294. vispy/scene/widgets/anchor.py +25 -0
  295. vispy/scene/widgets/axis.py +88 -0
  296. vispy/scene/widgets/colorbar.py +176 -0
  297. vispy/scene/widgets/console.py +351 -0
  298. vispy/scene/widgets/grid.py +509 -0
  299. vispy/scene/widgets/label.py +50 -0
  300. vispy/scene/widgets/tests/__init__.py +0 -0
  301. vispy/scene/widgets/tests/test_colorbar.py +47 -0
  302. vispy/scene/widgets/viewbox.py +199 -0
  303. vispy/scene/widgets/widget.py +478 -0
  304. vispy/testing/__init__.py +51 -0
  305. vispy/testing/_runners.py +448 -0
  306. vispy/testing/_testing.py +416 -0
  307. vispy/testing/image_tester.py +494 -0
  308. vispy/testing/rendered_array_tester.py +85 -0
  309. vispy/testing/tests/__init__.py +0 -0
  310. vispy/testing/tests/test_testing.py +20 -0
  311. vispy/util/__init__.py +32 -0
  312. vispy/util/bunch.py +15 -0
  313. vispy/util/check_environment.py +57 -0
  314. vispy/util/config.py +490 -0
  315. vispy/util/dpi/__init__.py +19 -0
  316. vispy/util/dpi/_linux.py +69 -0
  317. vispy/util/dpi/_quartz.py +26 -0
  318. vispy/util/dpi/_win32.py +34 -0
  319. vispy/util/dpi/tests/__init__.py +0 -0
  320. vispy/util/dpi/tests/test_dpi.py +16 -0
  321. vispy/util/eq.py +41 -0
  322. vispy/util/event.py +774 -0
  323. vispy/util/fetching.py +276 -0
  324. vispy/util/filter.py +44 -0
  325. vispy/util/fonts/__init__.py +14 -0
  326. vispy/util/fonts/_freetype.py +73 -0
  327. vispy/util/fonts/_quartz.py +192 -0
  328. vispy/util/fonts/_triage.py +36 -0
  329. vispy/util/fonts/_vispy_fonts.py +20 -0
  330. vispy/util/fonts/_win32.py +105 -0
  331. vispy/util/fonts/data/OpenSans-Bold.ttf +0 -0
  332. vispy/util/fonts/data/OpenSans-BoldItalic.ttf +0 -0
  333. vispy/util/fonts/data/OpenSans-Italic.ttf +0 -0
  334. vispy/util/fonts/data/OpenSans-Regular.ttf +0 -0
  335. vispy/util/fonts/tests/__init__.py +0 -0
  336. vispy/util/fonts/tests/test_font.py +45 -0
  337. vispy/util/fourier.py +69 -0
  338. vispy/util/frozen.py +25 -0
  339. vispy/util/gallery_scraper.py +268 -0
  340. vispy/util/keys.py +91 -0
  341. vispy/util/logs.py +358 -0
  342. vispy/util/osmesa_gl.py +17 -0
  343. vispy/util/profiler.py +135 -0
  344. vispy/util/ptime.py +16 -0
  345. vispy/util/quaternion.py +229 -0
  346. vispy/util/svg/__init__.py +18 -0
  347. vispy/util/svg/base.py +20 -0
  348. vispy/util/svg/color.py +219 -0
  349. vispy/util/svg/element.py +51 -0
  350. vispy/util/svg/geometry.py +478 -0
  351. vispy/util/svg/group.py +66 -0
  352. vispy/util/svg/length.py +81 -0
  353. vispy/util/svg/number.py +25 -0
  354. vispy/util/svg/path.py +332 -0
  355. vispy/util/svg/shapes.py +57 -0
  356. vispy/util/svg/style.py +59 -0
  357. vispy/util/svg/svg.py +40 -0
  358. vispy/util/svg/transform.py +223 -0
  359. vispy/util/svg/transformable.py +28 -0
  360. vispy/util/svg/viewport.py +73 -0
  361. vispy/util/tests/__init__.py +0 -0
  362. vispy/util/tests/test_config.py +58 -0
  363. vispy/util/tests/test_docstring_parameters.py +123 -0
  364. vispy/util/tests/test_emitter_group.py +262 -0
  365. vispy/util/tests/test_event_emitter.py +743 -0
  366. vispy/util/tests/test_fourier.py +35 -0
  367. vispy/util/tests/test_gallery_scraper.py +112 -0
  368. vispy/util/tests/test_import.py +127 -0
  369. vispy/util/tests/test_key.py +22 -0
  370. vispy/util/tests/test_logging.py +45 -0
  371. vispy/util/tests/test_run.py +14 -0
  372. vispy/util/tests/test_transforms.py +42 -0
  373. vispy/util/tests/test_vispy.py +48 -0
  374. vispy/util/transforms.py +201 -0
  375. vispy/util/wrappers.py +155 -0
  376. vispy/version.py +21 -0
  377. vispy/visuals/__init__.py +50 -0
  378. vispy/visuals/_scalable_textures.py +487 -0
  379. vispy/visuals/axis.py +678 -0
  380. vispy/visuals/border.py +208 -0
  381. vispy/visuals/box.py +79 -0
  382. vispy/visuals/collections/__init__.py +30 -0
  383. vispy/visuals/collections/agg_fast_path_collection.py +219 -0
  384. vispy/visuals/collections/agg_path_collection.py +197 -0
  385. vispy/visuals/collections/agg_point_collection.py +52 -0
  386. vispy/visuals/collections/agg_segment_collection.py +142 -0
  387. vispy/visuals/collections/array_list.py +401 -0
  388. vispy/visuals/collections/base_collection.py +482 -0
  389. vispy/visuals/collections/collection.py +253 -0
  390. vispy/visuals/collections/path_collection.py +23 -0
  391. vispy/visuals/collections/point_collection.py +19 -0
  392. vispy/visuals/collections/polygon_collection.py +25 -0
  393. vispy/visuals/collections/raw_path_collection.py +119 -0
  394. vispy/visuals/collections/raw_point_collection.py +113 -0
  395. vispy/visuals/collections/raw_polygon_collection.py +77 -0
  396. vispy/visuals/collections/raw_segment_collection.py +112 -0
  397. vispy/visuals/collections/raw_triangle_collection.py +78 -0
  398. vispy/visuals/collections/segment_collection.py +19 -0
  399. vispy/visuals/collections/triangle_collection.py +16 -0
  400. vispy/visuals/collections/util.py +168 -0
  401. vispy/visuals/colorbar.py +699 -0
  402. vispy/visuals/cube.py +41 -0
  403. vispy/visuals/ellipse.py +162 -0
  404. vispy/visuals/filters/__init__.py +10 -0
  405. vispy/visuals/filters/base_filter.py +242 -0
  406. vispy/visuals/filters/clipper.py +60 -0
  407. vispy/visuals/filters/clipping_planes.py +122 -0
  408. vispy/visuals/filters/color.py +181 -0
  409. vispy/visuals/filters/markers.py +28 -0
  410. vispy/visuals/filters/mesh.py +801 -0
  411. vispy/visuals/filters/picking.py +60 -0
  412. vispy/visuals/filters/tests/__init__.py +3 -0
  413. vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
  414. vispy/visuals/filters/tests/test_wireframe_filter.py +16 -0
  415. vispy/visuals/glsl/__init__.py +1 -0
  416. vispy/visuals/glsl/antialiasing.py +133 -0
  417. vispy/visuals/glsl/color.py +63 -0
  418. vispy/visuals/graphs/__init__.py +1 -0
  419. vispy/visuals/graphs/graph.py +240 -0
  420. vispy/visuals/graphs/layouts/__init__.py +55 -0
  421. vispy/visuals/graphs/layouts/circular.py +49 -0
  422. vispy/visuals/graphs/layouts/force_directed.py +211 -0
  423. vispy/visuals/graphs/layouts/networkx_layout.py +87 -0
  424. vispy/visuals/graphs/layouts/random.py +52 -0
  425. vispy/visuals/graphs/tests/__init__.py +1 -0
  426. vispy/visuals/graphs/tests/test_layouts.py +139 -0
  427. vispy/visuals/graphs/tests/test_networkx_layout.py +47 -0
  428. vispy/visuals/graphs/util.py +120 -0
  429. vispy/visuals/gridlines.py +161 -0
  430. vispy/visuals/gridmesh.py +98 -0
  431. vispy/visuals/histogram.py +58 -0
  432. vispy/visuals/image.py +701 -0
  433. vispy/visuals/image_complex.py +130 -0
  434. vispy/visuals/infinite_line.py +199 -0
  435. vispy/visuals/instanced_mesh.py +152 -0
  436. vispy/visuals/isocurve.py +213 -0
  437. vispy/visuals/isoline.py +241 -0
  438. vispy/visuals/isosurface.py +113 -0
  439. vispy/visuals/line/__init__.py +6 -0
  440. vispy/visuals/line/arrow.py +289 -0
  441. vispy/visuals/line/dash_atlas.py +90 -0
  442. vispy/visuals/line/line.py +545 -0
  443. vispy/visuals/line_plot.py +135 -0
  444. vispy/visuals/linear_region.py +199 -0
  445. vispy/visuals/markers.py +819 -0
  446. vispy/visuals/mesh.py +373 -0
  447. vispy/visuals/mesh_normals.py +159 -0
  448. vispy/visuals/plane.py +54 -0
  449. vispy/visuals/polygon.py +145 -0
  450. vispy/visuals/rectangle.py +196 -0
  451. vispy/visuals/regular_polygon.py +56 -0
  452. vispy/visuals/scrolling_lines.py +197 -0
  453. vispy/visuals/shaders/__init__.py +17 -0
  454. vispy/visuals/shaders/compiler.py +206 -0
  455. vispy/visuals/shaders/expression.py +99 -0
  456. vispy/visuals/shaders/function.py +788 -0
  457. vispy/visuals/shaders/multiprogram.py +145 -0
  458. vispy/visuals/shaders/parsing.py +140 -0
  459. vispy/visuals/shaders/program.py +161 -0
  460. vispy/visuals/shaders/shader_object.py +162 -0
  461. vispy/visuals/shaders/tests/__init__.py +0 -0
  462. vispy/visuals/shaders/tests/test_function.py +486 -0
  463. vispy/visuals/shaders/tests/test_multiprogram.py +78 -0
  464. vispy/visuals/shaders/tests/test_parsing.py +57 -0
  465. vispy/visuals/shaders/variable.py +272 -0
  466. vispy/visuals/spectrogram.py +169 -0
  467. vispy/visuals/sphere.py +80 -0
  468. vispy/visuals/surface_plot.py +192 -0
  469. vispy/visuals/tests/__init__.py +0 -0
  470. vispy/visuals/tests/test_arrows.py +109 -0
  471. vispy/visuals/tests/test_axis.py +120 -0
  472. vispy/visuals/tests/test_collections.py +15 -0
  473. vispy/visuals/tests/test_colorbar.py +179 -0
  474. vispy/visuals/tests/test_colormap.py +97 -0
  475. vispy/visuals/tests/test_ellipse.py +122 -0
  476. vispy/visuals/tests/test_gridlines.py +30 -0
  477. vispy/visuals/tests/test_histogram.py +24 -0
  478. vispy/visuals/tests/test_image.py +392 -0
  479. vispy/visuals/tests/test_image_complex.py +36 -0
  480. vispy/visuals/tests/test_infinite_line.py +53 -0
  481. vispy/visuals/tests/test_instanced_mesh.py +50 -0
  482. vispy/visuals/tests/test_isosurface.py +22 -0
  483. vispy/visuals/tests/test_linear_region.py +152 -0
  484. vispy/visuals/tests/test_markers.py +54 -0
  485. vispy/visuals/tests/test_mesh.py +261 -0
  486. vispy/visuals/tests/test_mesh_normals.py +218 -0
  487. vispy/visuals/tests/test_polygon.py +112 -0
  488. vispy/visuals/tests/test_rectangle.py +163 -0
  489. vispy/visuals/tests/test_regular_polygon.py +111 -0
  490. vispy/visuals/tests/test_scalable_textures.py +196 -0
  491. vispy/visuals/tests/test_sdf.py +73 -0
  492. vispy/visuals/tests/test_spectrogram.py +42 -0
  493. vispy/visuals/tests/test_surface_plot.py +57 -0
  494. vispy/visuals/tests/test_text.py +95 -0
  495. vispy/visuals/tests/test_volume.py +542 -0
  496. vispy/visuals/tests/test_windbarb.py +33 -0
  497. vispy/visuals/text/__init__.py +7 -0
  498. vispy/visuals/text/_sdf_cpu.cp313-win_amd64.pyd +0 -0
  499. vispy/visuals/text/_sdf_cpu.pyx +112 -0
  500. vispy/visuals/text/_sdf_gpu.py +316 -0
  501. vispy/visuals/text/text.py +675 -0
  502. vispy/visuals/transforms/__init__.py +34 -0
  503. vispy/visuals/transforms/_util.py +191 -0
  504. vispy/visuals/transforms/base_transform.py +233 -0
  505. vispy/visuals/transforms/chain.py +300 -0
  506. vispy/visuals/transforms/interactive.py +98 -0
  507. vispy/visuals/transforms/linear.py +564 -0
  508. vispy/visuals/transforms/nonlinear.py +398 -0
  509. vispy/visuals/transforms/tests/__init__.py +0 -0
  510. vispy/visuals/transforms/tests/test_transforms.py +243 -0
  511. vispy/visuals/transforms/transform_system.py +339 -0
  512. vispy/visuals/tube.py +173 -0
  513. vispy/visuals/visual.py +923 -0
  514. vispy/visuals/volume.py +1366 -0
  515. vispy/visuals/windbarb.py +291 -0
  516. vispy/visuals/xyz_axis.py +34 -0
  517. vispy-0.15.0.dist-info/METADATA +243 -0
  518. vispy-0.15.0.dist-info/RECORD +521 -0
  519. vispy-0.15.0.dist-info/WHEEL +5 -0
  520. vispy-0.15.0.dist-info/licenses/LICENSE.txt +36 -0
  521. vispy-0.15.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1366 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Vispy Development Team. All Rights Reserved.
3
+ # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4
+
5
+ """
6
+ About this technique
7
+ --------------------
8
+
9
+ In Python, we define the six faces of a cuboid to draw, as well as
10
+ texture cooridnates corresponding with the vertices of the cuboid.
11
+ The back faces of the cuboid are drawn (and front faces are culled)
12
+ because only the back faces are visible when the camera is inside the
13
+ volume.
14
+
15
+ In the vertex shader, we intersect the view ray with the near and far
16
+ clipping planes. In the fragment shader, we use these two points to
17
+ compute the ray direction and then compute the position of the front
18
+ cuboid surface (or near clipping plane) along the view ray.
19
+
20
+ Next we calculate the number of steps to walk from the front surface
21
+ to the back surface and iterate over these positions in a for-loop.
22
+ At each iteration, the fragment color or other voxel information is
23
+ updated depending on the selected rendering method.
24
+
25
+ It is important for the texture interpolation is 'linear' for most volumes,
26
+ since with 'nearest' the result can look very ugly; however for volumes with
27
+ discrete data 'nearest' is sometimes appropriate. The wrapping should be
28
+ clamp_to_edge to avoid artifacts when the ray takes a small step outside the
29
+ volume.
30
+
31
+ The ray direction is established by mapping the vertex to the document
32
+ coordinate frame, adjusting z to +/-1, and mapping the coordinate back.
33
+ The ray is expressed in coordinates local to the volume (i.e. texture
34
+ coordinates).
35
+
36
+ """
37
+ from __future__ import annotations
38
+
39
+ from typing import Optional
40
+ from functools import lru_cache
41
+ import warnings
42
+
43
+ from ._scalable_textures import CPUScaledTexture3D, GPUScaledTextured3D, Texture2D
44
+ from ..gloo import VertexBuffer, IndexBuffer
45
+ from . import Visual
46
+ from .shaders import Function
47
+ from ..color import get_colormap
48
+ from ..io import load_spatial_filters
49
+
50
+ import numpy as np
51
+
52
+ # todo: implement more render methods (port from visvis)
53
+ # todo: allow anisotropic data
54
+ # todo: what to do about lighting? ambi/diffuse/spec/shinynes on each visual?
55
+
56
+
57
+ _VERTEX_SHADER = """
58
+ attribute vec3 a_position;
59
+ uniform vec3 u_shape;
60
+
61
+ varying vec3 v_position;
62
+ varying vec4 v_nearpos;
63
+ varying vec4 v_farpos;
64
+
65
+ void main() {
66
+ v_position = a_position;
67
+
68
+ // Project local vertex coordinate to camera position. Then do a step
69
+ // backward (in cam coords) and project back. Voila, we get our ray vector.
70
+ vec4 pos_in_cam = $viewtransformf(vec4(v_position, 1));
71
+
72
+ // intersection of ray and near clipping plane (z = -1 in clip coords)
73
+ pos_in_cam.z = -pos_in_cam.w;
74
+ v_nearpos = $viewtransformi(pos_in_cam);
75
+
76
+ // intersection of ray and far clipping plane (z = +1 in clip coords)
77
+ pos_in_cam.z = pos_in_cam.w;
78
+ v_farpos = $viewtransformi(pos_in_cam);
79
+
80
+ gl_Position = $transform(vec4(v_position, 1.0));
81
+ }
82
+ """ # noqa
83
+
84
+ _FRAGMENT_SHADER = """
85
+ // uniforms
86
+ uniform $sampler_type u_volumetex;
87
+ uniform vec3 u_shape;
88
+ uniform vec2 clim;
89
+ uniform float gamma;
90
+ uniform float u_threshold;
91
+ uniform float u_attenuation;
92
+ uniform float u_relative_step_size;
93
+ uniform float u_mip_cutoff;
94
+ uniform float u_minip_cutoff;
95
+
96
+ //varyings
97
+ varying vec3 v_position;
98
+ varying vec4 v_nearpos;
99
+ varying vec4 v_farpos;
100
+
101
+ // uniforms for lighting. Hard coded until we figure out how to do lights
102
+ const vec4 u_ambient = vec4(0.2, 0.2, 0.2, 1.0);
103
+ const vec4 u_diffuse = vec4(0.8, 0.2, 0.2, 1.0);
104
+ const vec4 u_specular = vec4(1.0, 1.0, 1.0, 1.0);
105
+ const float u_shininess = 40.0;
106
+
107
+ // uniforms for plane definition. Defined in data coordinates.
108
+ uniform vec3 u_plane_normal;
109
+ uniform vec3 u_plane_position;
110
+ uniform float u_plane_thickness;
111
+
112
+ //varying vec3 lightDirs[1];
113
+
114
+ // global holding view direction in local coordinates
115
+ vec3 view_ray;
116
+
117
+ float rand(vec2 co)
118
+ {
119
+ // Create a pseudo-random number between 0 and 1.
120
+ // http://stackoverflow.com/questions/4200224
121
+ return fract(sin(dot(co.xy ,vec2(12.9898, 78.233))) * 43758.5453);
122
+ }
123
+
124
+ float colorToVal(vec4 color1)
125
+ {
126
+ return color1.r; // todo: why did I have this abstraction in visvis?
127
+ }
128
+
129
+ vec4 applyColormap(float data) {
130
+ data = clamp(data, min(clim.x, clim.y), max(clim.x, clim.y));
131
+ data = (data - clim.x) / (clim.y - clim.x);
132
+ vec4 color = $cmap(pow(data, gamma));
133
+ return color;
134
+ }
135
+
136
+
137
+ vec4 calculateColor(vec4 betterColor, vec3 loc, vec3 step)
138
+ {
139
+ // Calculate color by incorporating lighting
140
+ vec4 color1;
141
+ vec4 color2;
142
+
143
+ // View direction
144
+ vec3 V = normalize(view_ray);
145
+
146
+ // calculate normal vector from gradient
147
+ vec3 N; // normal
148
+ color1 = $get_data(loc+vec3(-step[0],0.0,0.0) );
149
+ color2 = $get_data(loc+vec3(step[0],0.0,0.0) );
150
+ N[0] = colorToVal(color1) - colorToVal(color2);
151
+ betterColor = max(max(color1, color2),betterColor);
152
+ color1 = $get_data(loc+vec3(0.0,-step[1],0.0) );
153
+ color2 = $get_data(loc+vec3(0.0,step[1],0.0) );
154
+ N[1] = colorToVal(color1) - colorToVal(color2);
155
+ betterColor = max(max(color1, color2),betterColor);
156
+ color1 = $get_data(loc+vec3(0.0,0.0,-step[2]) );
157
+ color2 = $get_data(loc+vec3(0.0,0.0,step[2]) );
158
+ N[2] = colorToVal(color1) - colorToVal(color2);
159
+ betterColor = max(max(color1, color2),betterColor);
160
+ float gm = length(N); // gradient magnitude
161
+ N = normalize(N);
162
+
163
+ // Flip normal so it points towards viewer
164
+ float Nselect = float(dot(N,V) > 0.0);
165
+ N = (2.0*Nselect - 1.0) * N; // == Nselect * N - (1.0-Nselect)*N;
166
+
167
+ // Get color of the texture (albeido)
168
+ color1 = betterColor;
169
+ color2 = color1;
170
+ // todo: parametrise color1_to_color2
171
+
172
+ // Init colors
173
+ vec4 ambient_color = vec4(0.0, 0.0, 0.0, 0.0);
174
+ vec4 diffuse_color = vec4(0.0, 0.0, 0.0, 0.0);
175
+ vec4 specular_color = vec4(0.0, 0.0, 0.0, 0.0);
176
+ vec4 final_color;
177
+
178
+ // todo: allow multiple light, define lights on viewvox or subscene
179
+ int nlights = 1;
180
+ for (int i=0; i<nlights; i++)
181
+ {
182
+ // Get light direction (make sure to prevent zero devision)
183
+ vec3 L = normalize(view_ray); //lightDirs[i];
184
+ float lightEnabled = float( length(L) > 0.0 );
185
+ L = normalize(L+(1.0-lightEnabled));
186
+
187
+ // Calculate lighting properties
188
+ float lambertTerm = clamp( dot(N,L), 0.0, 1.0 );
189
+ vec3 H = normalize(L+V); // Halfway vector
190
+ float specularTerm = pow( max(dot(H,N),0.0), u_shininess);
191
+
192
+ // Calculate mask
193
+ float mask1 = lightEnabled;
194
+
195
+ // Calculate colors
196
+ ambient_color += mask1 * u_ambient; // * gl_LightSource[i].ambient;
197
+ diffuse_color += mask1 * lambertTerm;
198
+ specular_color += mask1 * specularTerm * u_specular;
199
+ }
200
+
201
+ // Calculate final color by componing different components
202
+ final_color = color2 * ( ambient_color + diffuse_color) + specular_color;
203
+ final_color.a = color2.a;
204
+
205
+ // Done
206
+ return final_color;
207
+ }
208
+
209
+
210
+ vec3 intersectLinePlane(vec3 linePosition,
211
+ vec3 lineVector,
212
+ vec3 planePosition,
213
+ vec3 planeNormal) {
214
+ // function to find the intersection between a line and a plane
215
+ // line is defined by position and vector
216
+ // plane is defined by position and normal vector
217
+ // https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection
218
+
219
+ // find scale factor for line vector
220
+ float scaleFactor = dot(planePosition - linePosition, planeNormal) /
221
+ dot(lineVector, planeNormal);
222
+
223
+ // calculate intersection
224
+ return linePosition + ( scaleFactor * lineVector );
225
+ }
226
+
227
+ // for some reason, this has to be the last function in order for the
228
+ // filters to be inserted in the correct place...
229
+
230
+ void main() {
231
+ vec3 farpos = v_farpos.xyz / v_farpos.w;
232
+ vec3 nearpos = v_nearpos.xyz / v_nearpos.w;
233
+
234
+ // Calculate unit vector pointing in the view direction through this
235
+ // fragment.
236
+ view_ray = normalize(farpos.xyz - nearpos.xyz);
237
+
238
+ // Variables to keep track of where to set the frag depth.
239
+ // frag_depth_point is in data coordinates.
240
+ vec3 frag_depth_point;
241
+
242
+ // Set up the ray casting
243
+ // This snippet must define three variables:
244
+ // vec3 start_loc - the starting location of the ray in texture coordinates
245
+ // vec3 step - the step vector in texture coordinates
246
+ // int nsteps - the number of steps to make through the texture
247
+
248
+ $raycasting_setup
249
+
250
+ // For testing: show the number of steps. This helps to establish
251
+ // whether the rays are correctly oriented
252
+ //gl_FragColor = vec4(0.0, f_nsteps / 3.0 / u_shape.x, 1.0, 1.0);
253
+ //return;
254
+
255
+ $before_loop
256
+
257
+ // This outer loop seems necessary on some systems for large
258
+ // datasets. Ugly, but it works ...
259
+ vec3 loc = start_loc;
260
+ int iter = 0;
261
+
262
+ // keep track if the texture is ever sampled; if not, fragment will be discarded
263
+ // this allows us to discard fragments that only traverse clipped parts of the texture
264
+ bool texture_sampled = false;
265
+
266
+ while (iter < nsteps) {
267
+ for (iter=iter; iter<nsteps; iter++)
268
+ {
269
+ // Only sample volume if loc is not clipped by clipping planes
270
+ float distance_from_clip = $clip_with_planes(loc, u_shape);
271
+ if (distance_from_clip >= 0)
272
+ {
273
+ // Get sample color
274
+ vec4 color = $get_data(loc);
275
+ float val = color.r;
276
+ texture_sampled = true;
277
+
278
+ $in_loop
279
+ }
280
+ // Advance location deeper into the volume
281
+ loc += step;
282
+ }
283
+ }
284
+
285
+ if (!texture_sampled)
286
+ discard;
287
+
288
+ $after_loop
289
+
290
+ // set frag depth
291
+ vec4 frag_depth_vector = vec4(frag_depth_point, 1);
292
+ vec4 iproj = $viewtransformf(frag_depth_vector);
293
+ iproj.z /= iproj.w;
294
+ gl_FragDepth = (iproj.z+1.0)/2.0;
295
+ }
296
+ """ # noqa
297
+
298
+ _RAYCASTING_SETUP_VOLUME = """
299
+ // Compute the distance to the front surface or near clipping plane
300
+ float distance = dot(nearpos-v_position, view_ray);
301
+ distance = max(distance, min((-0.5 - v_position.x) / view_ray.x,
302
+ (u_shape.x - 0.5 - v_position.x) / view_ray.x));
303
+ distance = max(distance, min((-0.5 - v_position.y) / view_ray.y,
304
+ (u_shape.y - 0.5 - v_position.y) / view_ray.y));
305
+ distance = max(distance, min((-0.5 - v_position.z) / view_ray.z,
306
+ (u_shape.z - 0.5 - v_position.z) / view_ray.z));
307
+
308
+ // Now we have the starting position on the front surface
309
+ vec3 front = v_position + view_ray * distance;
310
+
311
+ // Decide how many steps to take
312
+ int nsteps = int(-distance / u_relative_step_size + 0.5);
313
+ float f_nsteps = float(nsteps);
314
+ if( nsteps < 1 )
315
+ discard;
316
+
317
+ // Get starting location and step vector in texture coordinates
318
+ vec3 step = ((v_position - front) / u_shape) / f_nsteps;
319
+ // 0.5 offset needed to get back to correct texture coordinates (vispy#2239)
320
+ vec3 start_loc = (front + 0.5) / u_shape;
321
+
322
+ // set frag depth to the cube face; this can be overridden by projection snippets
323
+ frag_depth_point = front;
324
+ """
325
+
326
+ _RAYCASTING_SETUP_PLANE = """
327
+ // find intersection of view ray with plane in data coordinates
328
+ // 0.5 offset needed to get back to correct texture coordinates (vispy#2239)
329
+ vec3 intersection = intersectLinePlane(v_position.xyz, view_ray,
330
+ u_plane_position, u_plane_normal);
331
+ // and texture coordinates
332
+ vec3 intersection_tex = (intersection + 0.5) / u_shape;
333
+
334
+ // discard if intersection not in texture
335
+
336
+ float out_of_bounds = 0;
337
+
338
+ out_of_bounds += float(intersection_tex.x > 1);
339
+ out_of_bounds += float(intersection_tex.x < 0);
340
+ out_of_bounds += float(intersection_tex.y > 1);
341
+ out_of_bounds += float(intersection_tex.y < 0);
342
+ out_of_bounds += float(intersection_tex.z > 1);
343
+ out_of_bounds += float(intersection_tex.z < 0);
344
+
345
+ if (out_of_bounds > 0)
346
+ discard;
347
+
348
+
349
+ // Decide how many steps to take
350
+ int nsteps = int(u_plane_thickness / u_relative_step_size + 0.5);
351
+ float f_nsteps = float(nsteps);
352
+ if( nsteps < 1 )
353
+ discard;
354
+
355
+ // Get step vector and starting location in texture coordinates
356
+ // step vector is along plane normal
357
+ vec3 N = normalize(u_plane_normal);
358
+ vec3 step = N / u_shape;
359
+ vec3 start_loc = intersection_tex - ((step * f_nsteps) / 2);
360
+
361
+ // Ensure that frag depth value will be set to plane intersection
362
+ frag_depth_point = intersection;
363
+ """
364
+
365
+
366
+ _MIP_SNIPPETS = dict(
367
+ before_loop="""
368
+ float maxval = u_mip_cutoff; // The maximum encountered value
369
+ int maxi = -1; // Where the maximum value was encountered
370
+ """,
371
+ in_loop="""
372
+ if ( val > maxval ) {
373
+ maxval = val;
374
+ maxi = iter;
375
+ if ( maxval >= clim.y ) {
376
+ // stop if no chance of finding a higher maxval
377
+ iter = nsteps;
378
+ }
379
+ }
380
+ """,
381
+ after_loop="""
382
+ // Refine search for max value, but only if anything was found
383
+ if ( maxi > -1 ) {
384
+ // Calculate starting location of ray for sampling
385
+ vec3 start_loc_refine = start_loc + step * (float(maxi) - 0.5);
386
+ loc = start_loc_refine;
387
+
388
+ // Variables to keep track of current value and where max was encountered
389
+ vec3 max_loc_tex = start_loc_refine;
390
+
391
+ vec3 small_step = step * 0.1;
392
+ for (int i=0; i<10; i++) {
393
+ float val = $get_data(loc).r;
394
+ if ( val > maxval) {
395
+ maxval = val;
396
+ max_loc_tex = start_loc_refine + (small_step * i);
397
+ }
398
+ loc += small_step;
399
+ }
400
+ frag_depth_point = max_loc_tex * u_shape;
401
+ gl_FragColor = applyColormap(maxval);
402
+ } else {
403
+ discard;
404
+ }
405
+ """,
406
+ )
407
+
408
+ _ATTENUATED_MIP_SNIPPETS = dict(
409
+ before_loop="""
410
+ float maxval = u_mip_cutoff; // The maximum encountered value
411
+ float sumval = 0.0; // The sum of the encountered values
412
+ float scale = 0.0; // The cumulative attenuation
413
+ int maxi = -1; // Where the maximum value was encountered
414
+ vec3 max_loc_tex = vec3(0.0); // Location where the maximum value was encountered
415
+ """,
416
+ in_loop="""
417
+ // Scale and clamp accumulation in `sumval` by contrast limits so that:
418
+ // * attenuation value does not depend on data values
419
+ // * negative values do not amplify instead of attenuate
420
+ sumval = sumval + u_relative_step_size * clamp((val - clim.x) / (clim.y - clim.x), 0.0, 1.0);
421
+ scale = exp(-u_attenuation * (sumval - 1));
422
+ if( maxval > scale * clim.y ) {
423
+ // stop if no chance of finding a higher maxval
424
+ iter = nsteps;
425
+ } else if( val * scale > maxval ) {
426
+ maxval = val * scale;
427
+ maxi = iter;
428
+ max_loc_tex = loc;
429
+ }
430
+ """,
431
+ after_loop="""
432
+ if ( maxi > -1 ) {
433
+ frag_depth_point = max_loc_tex * u_shape;
434
+ gl_FragColor = applyColormap(maxval);
435
+ }
436
+ else {
437
+ discard;
438
+ }
439
+ """,
440
+ )
441
+
442
+ _MINIP_SNIPPETS = dict(
443
+ before_loop="""
444
+ float minval = u_minip_cutoff; // The minimum encountered value
445
+ int mini = -1; // Where the minimum value was encountered
446
+ """,
447
+ in_loop="""
448
+ if ( val < minval ) {
449
+ minval = val;
450
+ mini = iter;
451
+ if ( minval <= clim.x ) {
452
+ // stop if no chance of finding a lower minval
453
+ iter = nsteps;
454
+ }
455
+ }
456
+ """,
457
+ after_loop="""
458
+ // Refine search for min value, but only if anything was found
459
+ if ( mini > -1 ) {
460
+ // Calculate starting location of ray for sampling
461
+ vec3 start_loc_refine = start_loc + step * (float(mini) - 0.5);
462
+ loc = start_loc_refine;
463
+
464
+ // Variables to keep track of current value and where max was encountered
465
+ vec3 min_loc_tex = start_loc_refine;
466
+
467
+ vec3 small_step = step * 0.1;
468
+ for (int i=0; i<10; i++) {
469
+ float val = $get_data(loc).r;
470
+ if ( val < minval) {
471
+ minval = val;
472
+ min_loc_tex = start_loc_refine + (small_step * i);
473
+ }
474
+ loc += small_step;
475
+ }
476
+ frag_depth_point = min_loc_tex * u_shape;
477
+ gl_FragColor = applyColormap(minval);
478
+ } else {
479
+ discard;
480
+ }
481
+ """,
482
+ )
483
+
484
+ _TRANSLUCENT_SNIPPETS = dict(
485
+ before_loop="""
486
+ vec4 integrated_color = vec4(0., 0., 0., 0.);
487
+ """,
488
+ in_loop="""
489
+ color = applyColormap(val);
490
+ float a1 = integrated_color.a;
491
+ float a2 = color.a * (1 - a1);
492
+ float alpha = max(a1 + a2, 0.001);
493
+
494
+ // Doesn't work.. GLSL optimizer bug?
495
+ //integrated_color = (integrated_color * a1 / alpha) +
496
+ // (color * a2 / alpha);
497
+ // This should be identical but does work correctly:
498
+ integrated_color *= a1 / alpha;
499
+ integrated_color += color * a2 / alpha;
500
+
501
+ integrated_color.a = alpha;
502
+
503
+ if( alpha > 0.99 ){
504
+ // stop integrating if the fragment becomes opaque
505
+ iter = nsteps;
506
+ }
507
+ """,
508
+ after_loop="""
509
+ gl_FragColor = integrated_color;
510
+ """,
511
+ )
512
+
513
+ _ADDITIVE_SNIPPETS = dict(
514
+ before_loop="""
515
+ vec4 integrated_color = vec4(0., 0., 0., 0.);
516
+ """,
517
+ in_loop="""
518
+ color = applyColormap(val);
519
+
520
+ integrated_color = 1.0 - (1.0 - integrated_color) * (1.0 - color);
521
+ """,
522
+ after_loop="""
523
+ gl_FragColor = integrated_color;
524
+ """,
525
+ )
526
+
527
+ _ISO_SNIPPETS = dict(
528
+ before_loop="""
529
+ vec4 color3 = vec4(0.0); // final color
530
+ vec3 dstep = 1.5 / u_shape; // step to sample derivative
531
+ gl_FragColor = vec4(0.0);
532
+ bool discard_fragment = true;
533
+ """,
534
+ in_loop="""
535
+ if (val > u_threshold-0.2) {
536
+ // Take the last interval in smaller steps
537
+ vec3 iloc = loc - step;
538
+ for (int i=0; i<10; i++) {
539
+ color = $get_data(iloc);
540
+ if (color.r > u_threshold) {
541
+ color = calculateColor(color, iloc, dstep);
542
+ gl_FragColor = applyColormap(color.r);
543
+
544
+ // set the variables for the depth buffer
545
+ frag_depth_point = iloc * u_shape;
546
+ discard_fragment = false;
547
+
548
+ iter = nsteps;
549
+ break;
550
+ }
551
+ iloc += step * 0.1;
552
+ }
553
+ }
554
+ """,
555
+ after_loop="""
556
+ if (discard_fragment)
557
+ discard;
558
+ """,
559
+ )
560
+
561
+
562
+ _AVG_SNIPPETS = dict(
563
+ before_loop="""
564
+ float n = 0; // Counter for encountered values
565
+ float meanval = 0.0; // The mean of encountered values
566
+ float prev_mean = 0.0; // Variable to store the previous incremental mean
567
+ """,
568
+ in_loop="""
569
+ // Incremental mean value used for numerical stability
570
+ n += 1; // Increment the counter
571
+ prev_mean = meanval; // Update the mean for previous iteration
572
+ meanval = prev_mean + (val - prev_mean) / n; // Calculate the mean
573
+ """,
574
+ after_loop="""
575
+ // Apply colormap on mean value
576
+ gl_FragColor = applyColormap(meanval);
577
+ """,
578
+ )
579
+
580
+ _INTERPOLATION_TEMPLATE = """
581
+ #include "misc/spatial-filters.frag"
582
+ vec4 texture_lookup_filtered(vec3 texcoord) {
583
+ // no need to discard out of bounds, already checked during raycasting
584
+ return %s($texture, $shape, texcoord);
585
+ }"""
586
+
587
+ _TEXTURE_LOOKUP = """
588
+ vec4 texture_lookup(vec3 texcoord) {
589
+ // no need to discard out of bounds, already checked during raycasting
590
+ return texture3D($texture, texcoord);
591
+ }"""
592
+
593
+
594
+ class VolumeVisual(Visual):
595
+ """Displays a 3D Volume
596
+
597
+ Parameters
598
+ ----------
599
+ vol : ndarray
600
+ The volume to display. Must be ndim==3. Array is assumed to be stored
601
+ as ``(z, y, x)``.
602
+ clim : str | tuple
603
+ Limits to use for the colormap. I.e. the values that map to black and white
604
+ in a gray colormap. Can be 'auto' to auto-set bounds to
605
+ the min and max of the data. If not given or None, 'auto' is used.
606
+ method : {'mip', 'attenuated_mip', 'minip', 'translucent', 'additive',
607
+ 'iso', 'average'}
608
+ The render method to use. See corresponding docs for details.
609
+ Default 'mip'.
610
+ threshold : float
611
+ The threshold to use for the isosurface render method. By default
612
+ the mean of the given volume is used.
613
+ attenuation: float
614
+ The attenuation rate to apply for the attenuated mip render method.
615
+ Default: 1.0.
616
+ relative_step_size : float
617
+ The relative step size to step through the volume. Default 0.8.
618
+ Increase to e.g. 1.5 to increase performance, at the cost of
619
+ quality.
620
+ cmap : str
621
+ Colormap to use.
622
+ gamma : float
623
+ Gamma to use during colormap lookup. Final color will be cmap(val**gamma).
624
+ by default: 1.
625
+ interpolation : str
626
+ Selects method of texture interpolation. Makes use of the two hardware
627
+ interpolation methods and the available interpolation methods defined
628
+ in vispy/gloo/glsl/misc/spatial_filters.frag
629
+
630
+ * 'nearest': Default, uses 'nearest' with Texture interpolation.
631
+ * 'linear': uses 'linear' with Texture interpolation.
632
+ * 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'cubic',
633
+ 'catrom', 'mitchell', 'spline16', 'spline36', 'gaussian',
634
+ 'bessel', 'sinc', 'lanczos', 'blackman'
635
+ texture_format : numpy.dtype | str | None
636
+ How to store data on the GPU. OpenGL allows for many different storage
637
+ formats and schemes for the low-level texture data stored in the GPU.
638
+ Most common is unsigned integers or floating point numbers.
639
+ Unsigned integers are the most widely supported while other formats
640
+ may not be supported on older versions of OpenGL or with older GPUs.
641
+ Default value is ``None`` which means data will be scaled on the
642
+ CPU and the result stored in the GPU as an unsigned integer. If a
643
+ numpy dtype object, an internal texture format will be chosen to
644
+ support that dtype and data will *not* be scaled on the CPU. Not all
645
+ dtypes are supported. If a string, then
646
+ it must be one of the OpenGL internalformat strings described in the
647
+ table on this page: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml
648
+ The name should have `GL_` removed and be lowercase (ex.
649
+ `GL_R32F` becomes ``'r32f'``). Lastly, this can also be the string
650
+ ``'auto'`` which will use the data type of the provided volume data
651
+ to determine the internalformat of the texture.
652
+ When this is specified (not ``None``) data is scaled on the
653
+ GPU which allows for faster color limit changes. Additionally, when
654
+ 32-bit float data is provided it won't be copied before being
655
+ transferred to the GPU. Note this visual is limited to "luminance"
656
+ formatted data (single band). This is equivalent to `GL_RED` format
657
+ in OpenGL 4.0.
658
+ raycasting_mode : {'volume', 'plane'}
659
+ Whether to cast a ray through the whole volume or perpendicular to a
660
+ plane through the volume defined.
661
+ plane_position : ArrayLike
662
+ A (3,) array containing a position on a plane of interest in the volume.
663
+ The position is defined in data coordinates. Only relevant in
664
+ raycasting_mode = 'plane'.
665
+ plane_normal : ArrayLike
666
+ A (3,) array containing a vector normal to the plane of interest in the
667
+ volume. The normal vector is defined in data coordinates. Only relevant
668
+ in raycasting_mode = 'plane'.
669
+ plane_thickness : float
670
+ A value defining the total length of the ray perpendicular to the
671
+ plane interrogated during rendering. Defined in data coordinates.
672
+ Only relevant in raycasting_mode = 'plane'.
673
+
674
+
675
+ .. versionchanged: 0.7
676
+
677
+ Deprecate 'emulate_texture' keyword argument.
678
+
679
+ """
680
+
681
+ _rendering_methods = {
682
+ 'mip': _MIP_SNIPPETS,
683
+ 'minip': _MINIP_SNIPPETS,
684
+ 'attenuated_mip': _ATTENUATED_MIP_SNIPPETS,
685
+ 'iso': _ISO_SNIPPETS,
686
+ 'translucent': _TRANSLUCENT_SNIPPETS,
687
+ 'additive': _ADDITIVE_SNIPPETS,
688
+ 'average': _AVG_SNIPPETS
689
+ }
690
+
691
+ _raycasting_modes = {
692
+ 'volume': _RAYCASTING_SETUP_VOLUME,
693
+ 'plane': _RAYCASTING_SETUP_PLANE
694
+ }
695
+
696
+ _shaders = {
697
+ 'vertex': _VERTEX_SHADER,
698
+ 'fragment': _FRAGMENT_SHADER,
699
+ }
700
+
701
+ _func_templates = {
702
+ 'texture_lookup_interpolated': _INTERPOLATION_TEMPLATE,
703
+ 'texture_lookup': _TEXTURE_LOOKUP,
704
+ }
705
+
706
+ def __init__(self, vol, clim="auto", method='mip', threshold=None,
707
+ attenuation=1.0, relative_step_size=0.8, cmap='grays',
708
+ gamma=1.0, interpolation='linear', texture_format=None,
709
+ raycasting_mode='volume', plane_position=None,
710
+ plane_normal=None, plane_thickness=1.0, clipping_planes=None,
711
+ clipping_planes_coord_system='scene', mip_cutoff=None,
712
+ minip_cutoff=None):
713
+
714
+ tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']
715
+ if clipping_planes_coord_system not in tr:
716
+ raise ValueError(f'Invalid coordinate system {clipping_planes_coord_system}. Must be one of {tr}.')
717
+ self._clipping_planes_coord_system = clipping_planes_coord_system
718
+ self._clip_transform = None
719
+ # Storage of information of volume
720
+ self._vol_shape = ()
721
+ self._gamma = gamma
722
+ self._raycasting_mode = raycasting_mode
723
+ self._need_vertex_update = True
724
+ # Set the colormap
725
+ self._cmap = get_colormap(cmap)
726
+ self._is_zyx = True
727
+
728
+ # Create gloo objects
729
+ self._vertices = VertexBuffer()
730
+
731
+ kernel, interpolation_methods = load_spatial_filters()
732
+ self._kerneltex = Texture2D(kernel, interpolation='nearest')
733
+ interpolation_methods, interpolation_fun = self._init_interpolation(
734
+ interpolation_methods)
735
+ self._interpolation_methods = interpolation_methods
736
+ self._interpolation_fun = interpolation_fun
737
+ self._interpolation = interpolation
738
+ if self._interpolation not in self._interpolation_methods:
739
+ raise ValueError("interpolation must be one of %s" %
740
+ ', '.join(self._interpolation_methods))
741
+ self._data_lookup_fn = None
742
+ self._need_interpolation_update = True
743
+
744
+ self._texture = self._create_texture(texture_format, vol)
745
+ # used to store current data for later CPU-side scaling if
746
+ # texture_format is None
747
+ self._last_data = None
748
+
749
+ # Create program
750
+ Visual.__init__(self, vcode=self._shaders['vertex'], fcode=self._shaders['fragment'])
751
+ self.shared_program['u_volumetex'] = self._texture
752
+ self.shared_program['a_position'] = self._vertices
753
+ self.shared_program['gamma'] = self._gamma
754
+ self._draw_mode = 'triangle_strip'
755
+ self._index_buffer = IndexBuffer()
756
+
757
+ # Only show back faces of cuboid. This is required because if we are
758
+ # inside the volume, then the front faces are outside of the clipping
759
+ # box and will not be drawn.
760
+ self.set_gl_state('translucent', cull_face=False)
761
+
762
+ # Apply clim and set data at the same time
763
+ self.set_data(vol, clim or "auto")
764
+
765
+ # Set params
766
+ self.raycasting_mode = raycasting_mode
767
+ self.mip_cutoff = mip_cutoff
768
+ self.minip_cutoff = minip_cutoff
769
+ self.method = method
770
+ self.relative_step_size = relative_step_size
771
+ self.threshold = threshold if threshold is not None else vol.mean()
772
+ self.attenuation = attenuation
773
+
774
+ # Set plane params
775
+ if plane_position is None:
776
+ self.plane_position = [x / 2 for x in vol.shape]
777
+ else:
778
+ self.plane_position = plane_position
779
+ if plane_normal is None:
780
+ self.plane_normal = [1, 0, 0]
781
+ else:
782
+ self.plane_normal = plane_normal
783
+ self.plane_thickness = plane_thickness
784
+
785
+ self.clipping_planes = clipping_planes
786
+
787
+ self.freeze()
788
+
789
+ def _init_interpolation(self, interpolation_methods):
790
+ # create interpolation shader functions for available
791
+ # interpolations
792
+ fun = [Function(self._func_templates['texture_lookup_interpolated'] % (n + '3D'))
793
+ for n in interpolation_methods]
794
+ interpolation_methods = [n.lower() for n in interpolation_methods]
795
+
796
+ interpolation_fun = dict(zip(interpolation_methods, fun))
797
+ interpolation_methods = tuple(sorted(interpolation_methods))
798
+
799
+ # overwrite "nearest" and "linear" spatial-filters
800
+ # with "hardware" interpolation _data_lookup_fn
801
+ hardware_lookup = Function(self._func_templates['texture_lookup'])
802
+ interpolation_fun['nearest'] = hardware_lookup
803
+ interpolation_fun['linear'] = hardware_lookup
804
+ # alias bicubic to cubic (but deprecate)
805
+ interpolation_methods = interpolation_methods + ('bicubic',)
806
+ return interpolation_methods, interpolation_fun
807
+
808
+ def _create_texture(self, texture_format, data):
809
+ if texture_format is not None:
810
+ tex_cls = GPUScaledTextured3D
811
+ else:
812
+ tex_cls = CPUScaledTexture3D
813
+
814
+ if self._interpolation == 'linear':
815
+ texture_interpolation = 'linear'
816
+ else:
817
+ texture_interpolation = 'nearest'
818
+
819
+ # clamp_to_edge means any texture coordinates outside of 0-1 should be
820
+ # clamped to 0 and 1.
821
+ # NOTE: This doesn't actually set the data in the texture. Only
822
+ # creates a placeholder texture that will be resized later on.
823
+ return tex_cls(data, interpolation=texture_interpolation,
824
+ internalformat=texture_format,
825
+ format='luminance',
826
+ wrapping='clamp_to_edge')
827
+
828
+ def set_data(self, vol, clim=None, copy=True):
829
+ """Set the volume data.
830
+
831
+ Parameters
832
+ ----------
833
+ vol : ndarray
834
+ The 3D volume.
835
+ clim : tuple
836
+ Colormap limits to use (min, max). None will use the min and max
837
+ values. Defaults to ``None``.
838
+ copy : bool
839
+ Whether to copy the input volume prior to applying clim
840
+ normalization on the CPU. Has no effect if visual was created
841
+ with 'texture_format' not equal to None as data is not modified
842
+ on the CPU and data must already be copied to the GPU.
843
+ Data must be 32-bit floating point data to completely avoid any
844
+ data copying when scaling on the CPU. Defaults to ``True`` for
845
+ CPU scaled data. It is forced to ``False`` for GPU scaled data.
846
+
847
+ """
848
+ # Check volume
849
+ if not isinstance(vol, np.ndarray):
850
+ raise ValueError('Volume visual needs a numpy array.')
851
+ if not ((vol.ndim == 3) or (vol.ndim == 4 and vol.shape[-1] > 1)):
852
+ raise ValueError('Volume visual needs a 3D array.')
853
+ if isinstance(self._texture, GPUScaledTextured3D):
854
+ copy = False
855
+
856
+ if clim is not None and clim != self._texture.clim:
857
+ self._texture.set_clim(clim)
858
+
859
+ # Apply to texture
860
+ self._texture.check_data_format(vol)
861
+ self._last_data = vol
862
+ self._texture.scale_and_set_data(vol, copy=copy)
863
+ self.shared_program['clim'] = self._texture.clim_normalized
864
+ self.shared_program['u_shape'] = (vol.shape[2], vol.shape[1],
865
+ vol.shape[0])
866
+
867
+ shape = vol.shape[:3]
868
+ if self._vol_shape != shape:
869
+ self._vol_shape = shape
870
+ self._need_vertex_update = True
871
+ self._vol_shape = shape
872
+
873
+ @property
874
+ def rendering_methods(self):
875
+ return list(self._rendering_methods)
876
+
877
+ @property
878
+ def raycasting_modes(self):
879
+ return list(self._raycasting_modes)
880
+
881
+ @property
882
+ def clim(self):
883
+ """The contrast limits that were applied to the volume data.
884
+
885
+ Volume display is mapped from black to white with these values.
886
+ Settable via set_data() as well as @clim.setter.
887
+ """
888
+ return self._texture.clim
889
+
890
+ @clim.setter
891
+ def clim(self, value):
892
+ """Set contrast limits used when rendering the image.
893
+
894
+ ``value`` should be a 2-tuple of floats (min_clim, max_clim), where each value is
895
+ within the range set by self.clim. If the new value is outside of the (min, max)
896
+ range of the clims previously used to normalize the texture data, then data will
897
+ be renormalized using set_data.
898
+ """
899
+ if self._texture.set_clim(value):
900
+ self.set_data(self._last_data, clim=value)
901
+ self.shared_program['clim'] = self._texture.clim_normalized
902
+ self.update()
903
+
904
+ @property
905
+ def gamma(self):
906
+ """The gamma used when rendering the image."""
907
+ return self._gamma
908
+
909
+ @gamma.setter
910
+ def gamma(self, value):
911
+ """Set gamma used when rendering the image."""
912
+ if value <= 0:
913
+ raise ValueError("gamma must be > 0")
914
+ self._gamma = float(value)
915
+ self.shared_program['gamma'] = self._gamma
916
+ self.update()
917
+
918
+ @property
919
+ def cmap(self):
920
+ return self._cmap
921
+
922
+ @cmap.setter
923
+ def cmap(self, cmap):
924
+ self._cmap = get_colormap(cmap)
925
+ self.shared_program.frag['cmap'] = Function(self._cmap.glsl_map)
926
+ self.shared_program['texture2D_LUT'] = self.cmap.texture_lut()
927
+ self.update()
928
+
929
+ @property
930
+ def interpolation_methods(self):
931
+ return self._interpolation_methods
932
+
933
+ @property
934
+ def interpolation(self):
935
+ """Get interpolation algorithm name."""
936
+ return self._interpolation
937
+
938
+ @interpolation.setter
939
+ def interpolation(self, i):
940
+ if i not in self._interpolation_methods:
941
+ raise ValueError("interpolation must be one of %s" %
942
+ ', '.join(self._interpolation_methods))
943
+ if self._interpolation != i:
944
+ self._interpolation = i
945
+ self._need_interpolation_update = True
946
+ self.update()
947
+
948
+ # The interpolation code could be transferred to a dedicated filter
949
+ # function in visuals/filters as discussed in #1051
950
+ def _build_interpolation(self):
951
+ """Rebuild the _data_lookup_fn for different interpolations."""
952
+ interpolation = self._interpolation
953
+ # alias bicubic to cubic
954
+ if interpolation == 'bicubic':
955
+ warnings.warn(
956
+ "'bicubic' interpolation is Deprecated. Use 'cubic' instead.",
957
+ DeprecationWarning,
958
+ stacklevel=2,
959
+ )
960
+ interpolation = 'cubic'
961
+ self._data_lookup_fn = self._interpolation_fun[interpolation]
962
+ try:
963
+ self.shared_program.frag['get_data'] = self._data_lookup_fn
964
+ except Exception as e:
965
+ print(e)
966
+
967
+ # only 'linear' uses 'linear' texture interpolation
968
+ if interpolation == 'linear':
969
+ texture_interpolation = 'linear'
970
+ else:
971
+ # 'nearest' (and also 'linear') doesn't use spatial_filters.frag
972
+ # so u_kernel and shape setting is skipped
973
+ texture_interpolation = 'nearest'
974
+ if interpolation != 'nearest':
975
+ self.shared_program['u_kernel'] = self._kerneltex
976
+ self._data_lookup_fn['shape'] = self._last_data.shape[:3][::-1]
977
+
978
+ if self._texture.interpolation != texture_interpolation:
979
+ self._texture.interpolation = texture_interpolation
980
+
981
+ self._data_lookup_fn['texture'] = self._texture
982
+
983
+ self._need_interpolation_update = False
984
+
985
+ @staticmethod
986
+ @lru_cache(maxsize=10)
987
+ def _build_clipping_planes_glsl(n_planes: int) -> str:
988
+ """Build the code snippet used to clip the volume based on self.clipping_planes."""
989
+ func_template = '''
990
+ float clip_planes(vec3 loc, vec3 vol_shape) {{
991
+ vec3 loc_transf = $clip_transform(vec4(loc * vol_shape, 1)).xyz;
992
+ float distance_from_clip = 3.4e38; // max float
993
+ {clips};
994
+ return distance_from_clip;
995
+ }}
996
+ '''
997
+ # the vertex is considered clipped if on the "negative" side of the plane
998
+ clip_template = '''
999
+ vec3 relative_vec{idx} = loc_transf - $clipping_plane_pos{idx};
1000
+ float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});
1001
+ distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);
1002
+ '''
1003
+ all_clips = []
1004
+ for idx in range(n_planes):
1005
+ all_clips.append(clip_template.format(idx=idx))
1006
+ formatted_code = func_template.format(clips=''.join(all_clips))
1007
+ return formatted_code
1008
+
1009
+ @property
1010
+ def clipping_planes(self) -> np.ndarray:
1011
+ """The set of planes used to clip the volume. Values on the negative side of the normal are discarded.
1012
+
1013
+ Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3).
1014
+ The order is xyz, as opposed to data's zyx (for consistency with the rest of vispy)
1015
+
1016
+ Example: one plane in position (0, 0, 0) and with normal (0, 0, 1),
1017
+ and a plane in position (1, 1, 1) with normal (0, 1, 0):
1018
+
1019
+ >>> volume.clipping_planes = np.array([
1020
+ >>> [[0, 0, 0], [0, 0, 1]],
1021
+ >>> [[1, 1, 1], [0, 1, 0]],
1022
+ >>> ])
1023
+
1024
+ """
1025
+ return self._clipping_planes
1026
+
1027
+ @clipping_planes.setter
1028
+ def clipping_planes(self, value: Optional[np.ndarray]):
1029
+ if value is None:
1030
+ value = np.empty([0, 2, 3])
1031
+ self._clipping_planes = value
1032
+
1033
+ self._clip_func = Function(self._build_clipping_planes_glsl(len(value)))
1034
+ self.shared_program.frag['clip_with_planes'] = self._clip_func
1035
+
1036
+ self._clip_func['clip_transform'] = self._clip_transform
1037
+ for idx, plane in enumerate(value):
1038
+ self._clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])
1039
+ self._clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])
1040
+ self.update()
1041
+
1042
+ @property
1043
+ def clipping_planes_coord_system(self) -> str:
1044
+ """
1045
+ Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
1046
+ """
1047
+ return self._clipping_planes_coord_system
1048
+
1049
+ @property
1050
+ def _before_loop_snippet(self):
1051
+ return self._rendering_methods[self.method]['before_loop']
1052
+
1053
+ @property
1054
+ def _in_loop_snippet(self):
1055
+ return self._rendering_methods[self.method]['in_loop']
1056
+
1057
+ @property
1058
+ def _after_loop_snippet(self):
1059
+ return self._rendering_methods[self.method]['after_loop']
1060
+
1061
+ @property
1062
+ def method(self):
1063
+ """The render method to use
1064
+
1065
+ Current options are:
1066
+
1067
+ * translucent: voxel colors are blended along the view ray until
1068
+ the result is opaque.
1069
+ * mip: maxiumum intensity projection. Cast a ray and display the
1070
+ maximum value that was encountered.
1071
+ * minip: minimum intensity projection. Cast a ray and display the
1072
+ minimum value that was encountered.
1073
+ * attenuated_mip: attenuated maximum intensity projection. Cast a
1074
+ ray and display the maximum value encountered. Values are
1075
+ attenuated as the ray moves deeper into the volume.
1076
+ * additive: voxel colors are added along the view ray until
1077
+ the result is saturated.
1078
+ * iso: isosurface. Cast a ray until a certain threshold is
1079
+ encountered. At that location, lighning calculations are
1080
+ performed to give the visual appearance of a surface.
1081
+ * average: average intensity projection. Cast a ray and display the
1082
+ average of values that were encountered.
1083
+ """
1084
+ return self._method
1085
+
1086
+ @method.setter
1087
+ def method(self, method):
1088
+ # Check and save
1089
+ known_methods = list(self._rendering_methods.keys())
1090
+ if method not in known_methods:
1091
+ raise ValueError('Volume render method should be in %r, not %r' %
1092
+ (known_methods, method))
1093
+ self._method = method
1094
+
1095
+ # $get_data needs to be unset and re-set, since it's present inside the snippets.
1096
+ # Program should probably be able to do this automatically
1097
+ self.shared_program.frag['get_data'] = None
1098
+ self.shared_program.frag['raycasting_setup'] = self._raycasting_setup_snippet
1099
+ self.shared_program.frag['before_loop'] = self._before_loop_snippet
1100
+ self.shared_program.frag['in_loop'] = self._in_loop_snippet
1101
+ self.shared_program.frag['after_loop'] = self._after_loop_snippet
1102
+ self.shared_program.frag['sampler_type'] = self._texture.glsl_sampler_type
1103
+ self.shared_program.frag['cmap'] = Function(self._cmap.glsl_map)
1104
+ self.shared_program['texture2D_LUT'] = self.cmap.texture_lut()
1105
+ self.shared_program['u_mip_cutoff'] = self._mip_cutoff
1106
+ self.shared_program['u_minip_cutoff'] = self._minip_cutoff
1107
+ self._need_interpolation_update = True
1108
+ self.update()
1109
+
1110
+ @property
1111
+ def _raycasting_setup_snippet(self):
1112
+ return self._raycasting_modes[self.raycasting_mode]
1113
+
1114
+ @property
1115
+ def raycasting_mode(self):
1116
+ """The raycasting mode to use.
1117
+
1118
+ This defines whether to cast a ray through the whole volume or
1119
+ perpendicular to a plane through the volume.
1120
+ must be in {'volume', 'plane'}
1121
+ """
1122
+ return self._raycasting_mode
1123
+
1124
+ @raycasting_mode.setter
1125
+ def raycasting_mode(self, value: str):
1126
+ valid_raycasting_modes = self._raycasting_modes.keys()
1127
+ if value not in valid_raycasting_modes:
1128
+ raise ValueError(f"Raycasting mode should be in {valid_raycasting_modes}, not {value}")
1129
+ self._raycasting_mode = value
1130
+ self.shared_program.frag['raycasting_setup'] = self._raycasting_setup_snippet
1131
+ self.update()
1132
+
1133
+ @property
1134
+ def threshold(self):
1135
+ """The threshold value to apply for the isosurface render method."""
1136
+ return self._threshold
1137
+
1138
+ @threshold.setter
1139
+ def threshold(self, value):
1140
+ self._threshold = float(value)
1141
+ self.shared_program['u_threshold'] = self._threshold
1142
+ self.update()
1143
+
1144
+ @property
1145
+ def attenuation(self):
1146
+ """The attenuation rate to apply for the attenuated mip render method."""
1147
+ return self._attenuation
1148
+
1149
+ @attenuation.setter
1150
+ def attenuation(self, value):
1151
+ self._attenuation = float(value)
1152
+ self.shared_program['u_attenuation'] = self._attenuation
1153
+ self.update()
1154
+
1155
+ @property
1156
+ def relative_step_size(self):
1157
+ """The relative step size used during raycasting.
1158
+
1159
+ Larger values yield higher performance at reduced quality. If
1160
+ set > 2.0 the ray skips entire voxels. Recommended values are
1161
+ between 0.5 and 1.5. The amount of quality degredation depends
1162
+ on the render method.
1163
+ """
1164
+ return self._relative_step_size
1165
+
1166
+ @relative_step_size.setter
1167
+ def relative_step_size(self, value):
1168
+ """Set the relative step size used during raycasting.
1169
+
1170
+ Very small values give increased detail when rendering volumes with
1171
+ few voxels, but values that are too small give worse performance
1172
+ (framerate), in extreme cases causing a GPU hang and for the process
1173
+ to be killed by the OS. See discussion at:
1174
+
1175
+ https://github.com/vispy/vispy/pull/2587
1176
+
1177
+ For this reason, this setter issues a warning when the value is
1178
+ smaller than ``side_len / (2 * MAX_CANVAS_SIZE)``, where ``side_len``
1179
+ is the smallest side of the volume and ``MAX_CANVAS_SIZE`` is what
1180
+ we consider to be the largest likely monitor resolution along its
1181
+ longest side: 7680 pixels, equivalent to an 8K monitor.
1182
+
1183
+ This setter also raises a ValueError when the value is 0 or negative.
1184
+ """
1185
+ value = float(value)
1186
+ side_len = np.min(self._vol_shape)
1187
+ MAX_CANVAS_SIZE = 7680
1188
+ minimum_val = side_len / (2 * MAX_CANVAS_SIZE)
1189
+ if value <= 0:
1190
+ raise ValueError('relative_step_size cannot be 0 or negative.')
1191
+ elif value < minimum_val:
1192
+ warnings.warn(
1193
+ f'To display a volume of shape {self._vol_shape} without '
1194
+ f'artifacts, you need a step size no smaller than {side_len} /'
1195
+ f'(2 * {MAX_CANVAS_SIZE}) = {minimum_val:,.3g}. To prevent '
1196
+ 'extreme degradation in rendering performance, the provided '
1197
+ f'value of {value} is being clipped to {minimum_val:,.3g}. If '
1198
+ 'you believe you need a smaller step size, please raise an '
1199
+ 'issue at https://github.com/vispy/vispy/issues.'
1200
+ )
1201
+ value = minimum_val
1202
+ self._relative_step_size = value
1203
+ self.shared_program['u_relative_step_size'] = value
1204
+
1205
+ @property
1206
+ def plane_position(self):
1207
+ """Position on a plane through the volume.
1208
+
1209
+ A (3,) array containing a position on a plane of interest in the volume.
1210
+ The position is defined in data coordinates. Only relevant in
1211
+ raycasting_mode = 'plane'.
1212
+ """
1213
+ return self._plane_position
1214
+
1215
+ @plane_position.setter
1216
+ def plane_position(self, value):
1217
+ value = np.array(value, dtype=np.float32).ravel()
1218
+ if value.shape != (3, ):
1219
+ raise ValueError('plane_position must be a 3 element array-like object')
1220
+ self._plane_position = value
1221
+ self.shared_program['u_plane_position'] = value[::-1]
1222
+ self.update()
1223
+
1224
+ @property
1225
+ def plane_normal(self):
1226
+ """Direction normal to a plane through the volume.
1227
+
1228
+ A (3,) array containing a vector normal to the plane of interest in the
1229
+ volume. The normal vector is defined in data coordinates. Only relevant
1230
+ in raycasting_mode = 'plane'.
1231
+ """
1232
+ return self._plane_normal
1233
+
1234
+ @plane_normal.setter
1235
+ def plane_normal(self, value):
1236
+ value = np.array(value, dtype=np.float32).ravel()
1237
+ if value.shape != (3, ):
1238
+ raise ValueError('plane_normal must be a 3 element array-like object')
1239
+ self._plane_normal = value
1240
+ self.shared_program['u_plane_normal'] = value[::-1]
1241
+ self.update()
1242
+
1243
+ @property
1244
+ def plane_thickness(self):
1245
+ """Thickness of a plane through the volume.
1246
+
1247
+ A value defining the total length of the ray perpendicular to the
1248
+ plane interrogated during rendering. Defined in data coordinates.
1249
+ Only relevant in raycasting_mode = 'plane'.
1250
+ """
1251
+ return self._plane_thickness
1252
+
1253
+ @plane_thickness.setter
1254
+ def plane_thickness(self, value: float):
1255
+ value = float(value)
1256
+ if value < 1:
1257
+ raise ValueError('plane_thickness should be at least 1.0')
1258
+ self._plane_thickness = value
1259
+ self.shared_program['u_plane_thickness'] = value
1260
+ self.update()
1261
+
1262
+ @property
1263
+ def mip_cutoff(self):
1264
+ """The lower cutoff value for `mip` and `attenuated_mip`.
1265
+
1266
+ When using the `mip` or `attenuated_mip` rendering methods, fragments
1267
+ with values below the cutoff will be discarded.
1268
+ """
1269
+ return self._mip_cutoff
1270
+
1271
+ @mip_cutoff.setter
1272
+ def mip_cutoff(self, value):
1273
+ if value is None:
1274
+ value = np.finfo('float32').min
1275
+ self._mip_cutoff = float(value)
1276
+ self.shared_program['u_mip_cutoff'] = self._mip_cutoff
1277
+ self.update()
1278
+
1279
+ @property
1280
+ def minip_cutoff(self):
1281
+ """The upper cutoff value for `minip`.
1282
+
1283
+ When using the `minip` rendering method, fragments
1284
+ with values above the cutoff will be discarded.
1285
+ """
1286
+ return self._minip_cutoff
1287
+
1288
+ @minip_cutoff.setter
1289
+ def minip_cutoff(self, value):
1290
+ if value is None:
1291
+ value = np.finfo('float32').max
1292
+ self._minip_cutoff = float(value)
1293
+ self.shared_program['u_minip_cutoff'] = self._minip_cutoff
1294
+ self.update()
1295
+
1296
+ def _create_vertex_data(self):
1297
+ """Create and set positions and texture coords from the given shape
1298
+
1299
+ We have six faces with 1 quad (2 triangles) each, resulting in
1300
+ 6*2*3 = 36 vertices in total.
1301
+ """
1302
+ shape = self._vol_shape
1303
+
1304
+ # Get corner coordinates. The -0.5 offset is to center
1305
+ # pixels/voxels. This works correctly for anisotropic data.
1306
+ x0, x1 = -0.5, shape[2] - 0.5
1307
+ y0, y1 = -0.5, shape[1] - 0.5
1308
+ z0, z1 = -0.5, shape[0] - 0.5
1309
+
1310
+ pos = np.array([
1311
+ [x0, y0, z0],
1312
+ [x1, y0, z0],
1313
+ [x0, y1, z0],
1314
+ [x1, y1, z0],
1315
+ [x0, y0, z1],
1316
+ [x1, y0, z1],
1317
+ [x0, y1, z1],
1318
+ [x1, y1, z1],
1319
+ ], dtype=np.float32)
1320
+
1321
+ """
1322
+ 6-------7
1323
+ /| /|
1324
+ 4-------5 |
1325
+ | | | |
1326
+ | 2-----|-3
1327
+ |/ |/
1328
+ 0-------1
1329
+ """
1330
+
1331
+ # Order is chosen such that normals face outward; front faces will be
1332
+ # culled.
1333
+ indices = np.array([2, 6, 0, 4, 5, 6, 7, 2, 3, 0, 1, 5, 3, 7],
1334
+ dtype=np.uint32)
1335
+
1336
+ # Apply
1337
+ self._vertices.set_data(pos)
1338
+ self._index_buffer.set_data(indices)
1339
+
1340
+ def _compute_bounds(self, axis, view):
1341
+ if self._is_zyx:
1342
+ # axis=(x, y, z) -> shape(..., z, y, x)
1343
+ ndim = len(self._vol_shape)
1344
+ return 0, self._vol_shape[ndim - 1 - axis]
1345
+ else:
1346
+ # axis=(x, y, z) -> shape(x, y, z)
1347
+ return 0, self._vol_shape[axis]
1348
+
1349
+ def _prepare_transforms(self, view):
1350
+ trs = view.transforms
1351
+ view.view_program.vert['transform'] = trs.get_transform()
1352
+
1353
+ view_tr_f = trs.get_transform('visual', 'document')
1354
+ view_tr_i = view_tr_f.inverse
1355
+ view.view_program.vert['viewtransformf'] = view_tr_f
1356
+ view.view_program.vert['viewtransformi'] = view_tr_i
1357
+ view.view_program.frag['viewtransformf'] = view_tr_f
1358
+
1359
+ self._clip_transform = trs.get_transform('visual', self._clipping_planes_coord_system)
1360
+
1361
+ def _prepare_draw(self, view):
1362
+ if self._need_vertex_update:
1363
+ self._create_vertex_data()
1364
+
1365
+ if self._need_interpolation_update:
1366
+ self._build_interpolation()