vispy 0.15.0__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- vispy/__init__.py +33 -0
- vispy/app/__init__.py +15 -0
- vispy/app/_default_app.py +76 -0
- vispy/app/_detect_eventloop.py +148 -0
- vispy/app/application.py +263 -0
- vispy/app/backends/__init__.py +52 -0
- vispy/app/backends/_egl.py +264 -0
- vispy/app/backends/_glfw.py +513 -0
- vispy/app/backends/_jupyter_rfb.py +278 -0
- vispy/app/backends/_offscreen_util.py +121 -0
- vispy/app/backends/_osmesa.py +235 -0
- vispy/app/backends/_pyglet.py +451 -0
- vispy/app/backends/_pyqt4.py +36 -0
- vispy/app/backends/_pyqt5.py +36 -0
- vispy/app/backends/_pyqt6.py +40 -0
- vispy/app/backends/_pyside.py +37 -0
- vispy/app/backends/_pyside2.py +52 -0
- vispy/app/backends/_pyside6.py +53 -0
- vispy/app/backends/_qt.py +1003 -0
- vispy/app/backends/_sdl2.py +444 -0
- vispy/app/backends/_template.py +244 -0
- vispy/app/backends/_test.py +8 -0
- vispy/app/backends/_tk.py +800 -0
- vispy/app/backends/_wx.py +476 -0
- vispy/app/backends/tests/__init__.py +0 -0
- vispy/app/backends/tests/test_offscreen_util.py +52 -0
- vispy/app/backends/tests/test_rfb.py +77 -0
- vispy/app/base.py +294 -0
- vispy/app/canvas.py +828 -0
- vispy/app/qt.py +92 -0
- vispy/app/tests/__init__.py +0 -0
- vispy/app/tests/qt-designer.ui +58 -0
- vispy/app/tests/test_app.py +442 -0
- vispy/app/tests/test_backends.py +164 -0
- vispy/app/tests/test_canvas.py +122 -0
- vispy/app/tests/test_context.py +92 -0
- vispy/app/tests/test_qt.py +47 -0
- vispy/app/tests/test_simultaneous.py +134 -0
- vispy/app/timer.py +174 -0
- vispy/color/__init__.py +17 -0
- vispy/color/_color_dict.py +193 -0
- vispy/color/color_array.py +447 -0
- vispy/color/color_space.py +181 -0
- vispy/color/colormap.py +1213 -0
- vispy/color/tests/__init__.py +0 -0
- vispy/color/tests/test_color.py +378 -0
- vispy/conftest.py +12 -0
- vispy/ext/__init__.py +0 -0
- vispy/ext/cocoapy.py +1522 -0
- vispy/ext/cubehelix.py +138 -0
- vispy/ext/egl.py +375 -0
- vispy/ext/fontconfig.py +118 -0
- vispy/ext/gdi32plus.py +206 -0
- vispy/ext/osmesa.py +105 -0
- vispy/geometry/__init__.py +23 -0
- vispy/geometry/_triangulation_debugger.py +171 -0
- vispy/geometry/calculations.py +162 -0
- vispy/geometry/curves.py +399 -0
- vispy/geometry/generation.py +643 -0
- vispy/geometry/isocurve.py +175 -0
- vispy/geometry/isosurface.py +465 -0
- vispy/geometry/meshdata.py +700 -0
- vispy/geometry/normals.py +78 -0
- vispy/geometry/parametric.py +56 -0
- vispy/geometry/polygon.py +137 -0
- vispy/geometry/rect.py +210 -0
- vispy/geometry/tests/__init__.py +0 -0
- vispy/geometry/tests/test_calculations.py +23 -0
- vispy/geometry/tests/test_generation.py +56 -0
- vispy/geometry/tests/test_meshdata.py +106 -0
- vispy/geometry/tests/test_triangulation.py +594 -0
- vispy/geometry/torusknot.py +142 -0
- vispy/geometry/triangulation.py +876 -0
- vispy/gloo/__init__.py +56 -0
- vispy/gloo/buffer.py +505 -0
- vispy/gloo/context.py +272 -0
- vispy/gloo/framebuffer.py +257 -0
- vispy/gloo/gl/__init__.py +234 -0
- vispy/gloo/gl/_constants.py +332 -0
- vispy/gloo/gl/_es2.py +986 -0
- vispy/gloo/gl/_gl2.py +1365 -0
- vispy/gloo/gl/_proxy.py +499 -0
- vispy/gloo/gl/_pyopengl2.py +362 -0
- vispy/gloo/gl/dummy.py +24 -0
- vispy/gloo/gl/es2.py +62 -0
- vispy/gloo/gl/gl2.py +98 -0
- vispy/gloo/gl/glplus.py +168 -0
- vispy/gloo/gl/pyopengl2.py +97 -0
- vispy/gloo/gl/tests/__init__.py +0 -0
- vispy/gloo/gl/tests/test_basics.py +282 -0
- vispy/gloo/gl/tests/test_functionality.py +568 -0
- vispy/gloo/gl/tests/test_names.py +246 -0
- vispy/gloo/gl/tests/test_use.py +71 -0
- vispy/gloo/glir.py +1824 -0
- vispy/gloo/globject.py +101 -0
- vispy/gloo/preprocessor.py +67 -0
- vispy/gloo/program.py +543 -0
- vispy/gloo/tests/__init__.py +0 -0
- vispy/gloo/tests/test_buffer.py +558 -0
- vispy/gloo/tests/test_context.py +119 -0
- vispy/gloo/tests/test_framebuffer.py +195 -0
- vispy/gloo/tests/test_glir.py +307 -0
- vispy/gloo/tests/test_globject.py +35 -0
- vispy/gloo/tests/test_program.py +302 -0
- vispy/gloo/tests/test_texture.py +732 -0
- vispy/gloo/tests/test_use_gloo.py +187 -0
- vispy/gloo/tests/test_util.py +60 -0
- vispy/gloo/tests/test_wrappers.py +261 -0
- vispy/gloo/texture.py +1046 -0
- vispy/gloo/util.py +129 -0
- vispy/gloo/wrappers.py +762 -0
- vispy/glsl/__init__.py +42 -0
- vispy/glsl/antialias/antialias.glsl +7 -0
- vispy/glsl/antialias/cap-butt.glsl +31 -0
- vispy/glsl/antialias/cap-round.glsl +29 -0
- vispy/glsl/antialias/cap-square.glsl +30 -0
- vispy/glsl/antialias/cap-triangle-in.glsl +30 -0
- vispy/glsl/antialias/cap-triangle-out.glsl +30 -0
- vispy/glsl/antialias/cap.glsl +67 -0
- vispy/glsl/antialias/caps.glsl +67 -0
- vispy/glsl/antialias/filled.glsl +50 -0
- vispy/glsl/antialias/outline.glsl +40 -0
- vispy/glsl/antialias/stroke.glsl +43 -0
- vispy/glsl/arrowheads/angle.glsl +99 -0
- vispy/glsl/arrowheads/arrowheads.frag +60 -0
- vispy/glsl/arrowheads/arrowheads.glsl +12 -0
- vispy/glsl/arrowheads/arrowheads.vert +83 -0
- vispy/glsl/arrowheads/curved.glsl +48 -0
- vispy/glsl/arrowheads/inhibitor.glsl +26 -0
- vispy/glsl/arrowheads/stealth.glsl +46 -0
- vispy/glsl/arrowheads/triangle.glsl +97 -0
- vispy/glsl/arrowheads/util.glsl +13 -0
- vispy/glsl/arrows/angle-30.glsl +12 -0
- vispy/glsl/arrows/angle-60.glsl +12 -0
- vispy/glsl/arrows/angle-90.glsl +12 -0
- vispy/glsl/arrows/arrow.frag +39 -0
- vispy/glsl/arrows/arrow.vert +49 -0
- vispy/glsl/arrows/arrows.glsl +17 -0
- vispy/glsl/arrows/common.glsl +187 -0
- vispy/glsl/arrows/curved.glsl +63 -0
- vispy/glsl/arrows/stealth.glsl +50 -0
- vispy/glsl/arrows/triangle-30.glsl +12 -0
- vispy/glsl/arrows/triangle-60.glsl +12 -0
- vispy/glsl/arrows/triangle-90.glsl +12 -0
- vispy/glsl/arrows/util.glsl +98 -0
- vispy/glsl/build_spatial_filters.py +660 -0
- vispy/glsl/collections/agg-fast-path.frag +20 -0
- vispy/glsl/collections/agg-fast-path.vert +78 -0
- vispy/glsl/collections/agg-glyph.frag +60 -0
- vispy/glsl/collections/agg-glyph.vert +33 -0
- vispy/glsl/collections/agg-marker.frag +35 -0
- vispy/glsl/collections/agg-marker.vert +48 -0
- vispy/glsl/collections/agg-path.frag +55 -0
- vispy/glsl/collections/agg-path.vert +166 -0
- vispy/glsl/collections/agg-point.frag +21 -0
- vispy/glsl/collections/agg-point.vert +35 -0
- vispy/glsl/collections/agg-segment.frag +32 -0
- vispy/glsl/collections/agg-segment.vert +75 -0
- vispy/glsl/collections/marker.frag +38 -0
- vispy/glsl/collections/marker.vert +48 -0
- vispy/glsl/collections/raw-path.frag +15 -0
- vispy/glsl/collections/raw-path.vert +24 -0
- vispy/glsl/collections/raw-point.frag +14 -0
- vispy/glsl/collections/raw-point.vert +31 -0
- vispy/glsl/collections/raw-segment.frag +18 -0
- vispy/glsl/collections/raw-segment.vert +26 -0
- vispy/glsl/collections/raw-triangle.frag +13 -0
- vispy/glsl/collections/raw-triangle.vert +26 -0
- vispy/glsl/collections/sdf-glyph-ticks.vert +69 -0
- vispy/glsl/collections/sdf-glyph.frag +80 -0
- vispy/glsl/collections/sdf-glyph.vert +59 -0
- vispy/glsl/collections/tick-labels.vert +71 -0
- vispy/glsl/colormaps/autumn.glsl +20 -0
- vispy/glsl/colormaps/blues.glsl +20 -0
- vispy/glsl/colormaps/color-space.glsl +17 -0
- vispy/glsl/colormaps/colormaps.glsl +24 -0
- vispy/glsl/colormaps/cool.glsl +20 -0
- vispy/glsl/colormaps/fire.glsl +21 -0
- vispy/glsl/colormaps/gray.glsl +20 -0
- vispy/glsl/colormaps/greens.glsl +20 -0
- vispy/glsl/colormaps/hot.glsl +22 -0
- vispy/glsl/colormaps/ice.glsl +20 -0
- vispy/glsl/colormaps/icefire.glsl +23 -0
- vispy/glsl/colormaps/parse.py +40 -0
- vispy/glsl/colormaps/reds.glsl +20 -0
- vispy/glsl/colormaps/spring.glsl +20 -0
- vispy/glsl/colormaps/summer.glsl +20 -0
- vispy/glsl/colormaps/user.glsl +22 -0
- vispy/glsl/colormaps/util.glsl +41 -0
- vispy/glsl/colormaps/wheel.glsl +21 -0
- vispy/glsl/colormaps/winter.glsl +20 -0
- vispy/glsl/lines/agg.frag +320 -0
- vispy/glsl/lines/agg.vert +241 -0
- vispy/glsl/markers/arrow.glsl +12 -0
- vispy/glsl/markers/asterisk.glsl +16 -0
- vispy/glsl/markers/chevron.glsl +14 -0
- vispy/glsl/markers/clover.glsl +20 -0
- vispy/glsl/markers/club.glsl +31 -0
- vispy/glsl/markers/cross.glsl +17 -0
- vispy/glsl/markers/diamond.glsl +12 -0
- vispy/glsl/markers/disc.glsl +9 -0
- vispy/glsl/markers/ellipse.glsl +67 -0
- vispy/glsl/markers/hbar.glsl +9 -0
- vispy/glsl/markers/heart.glsl +15 -0
- vispy/glsl/markers/infinity.glsl +15 -0
- vispy/glsl/markers/marker-sdf.frag +74 -0
- vispy/glsl/markers/marker-sdf.vert +41 -0
- vispy/glsl/markers/marker.frag +36 -0
- vispy/glsl/markers/marker.vert +46 -0
- vispy/glsl/markers/markers.glsl +24 -0
- vispy/glsl/markers/pin.glsl +18 -0
- vispy/glsl/markers/ring.glsl +11 -0
- vispy/glsl/markers/spade.glsl +28 -0
- vispy/glsl/markers/square.glsl +10 -0
- vispy/glsl/markers/tag.glsl +11 -0
- vispy/glsl/markers/triangle.glsl +14 -0
- vispy/glsl/markers/vbar.glsl +9 -0
- vispy/glsl/math/circle-through-2-points.glsl +30 -0
- vispy/glsl/math/constants.glsl +48 -0
- vispy/glsl/math/double.glsl +114 -0
- vispy/glsl/math/functions.glsl +20 -0
- vispy/glsl/math/point-to-line-distance.glsl +31 -0
- vispy/glsl/math/point-to-line-projection.glsl +29 -0
- vispy/glsl/math/signed-line-distance.glsl +27 -0
- vispy/glsl/math/signed-segment-distance.glsl +30 -0
- vispy/glsl/misc/regular-grid.frag +244 -0
- vispy/glsl/misc/spatial-filters.frag +1407 -0
- vispy/glsl/misc/viewport-NDC.glsl +20 -0
- vispy/glsl/transforms/azimuthal-equal-area.glsl +32 -0
- vispy/glsl/transforms/azimuthal-equidistant.glsl +38 -0
- vispy/glsl/transforms/hammer.glsl +44 -0
- vispy/glsl/transforms/identity.glsl +6 -0
- vispy/glsl/transforms/identity_forward.glsl +23 -0
- vispy/glsl/transforms/identity_inverse.glsl +23 -0
- vispy/glsl/transforms/linear-scale.glsl +127 -0
- vispy/glsl/transforms/log-scale.glsl +126 -0
- vispy/glsl/transforms/mercator-transverse-forward.glsl +40 -0
- vispy/glsl/transforms/mercator-transverse-inverse.glsl +40 -0
- vispy/glsl/transforms/panzoom.glsl +10 -0
- vispy/glsl/transforms/polar.glsl +41 -0
- vispy/glsl/transforms/position.glsl +44 -0
- vispy/glsl/transforms/power-scale.glsl +139 -0
- vispy/glsl/transforms/projection.glsl +7 -0
- vispy/glsl/transforms/pvm.glsl +13 -0
- vispy/glsl/transforms/rotate.glsl +45 -0
- vispy/glsl/transforms/trackball.glsl +15 -0
- vispy/glsl/transforms/translate.glsl +35 -0
- vispy/glsl/transforms/transverse_mercator.glsl +38 -0
- vispy/glsl/transforms/viewport-clipping.glsl +14 -0
- vispy/glsl/transforms/viewport-transform.glsl +16 -0
- vispy/glsl/transforms/viewport.glsl +50 -0
- vispy/glsl/transforms/x.glsl +24 -0
- vispy/glsl/transforms/y.glsl +19 -0
- vispy/glsl/transforms/z.glsl +14 -0
- vispy/io/__init__.py +20 -0
- vispy/io/_data/spatial-filters.npy +0 -0
- vispy/io/datasets.py +94 -0
- vispy/io/image.py +231 -0
- vispy/io/mesh.py +122 -0
- vispy/io/stl.py +167 -0
- vispy/io/tests/__init__.py +0 -0
- vispy/io/tests/test_image.py +47 -0
- vispy/io/tests/test_io.py +121 -0
- vispy/io/wavefront.py +350 -0
- vispy/plot/__init__.py +36 -0
- vispy/plot/fig.py +58 -0
- vispy/plot/plotwidget.py +522 -0
- vispy/plot/tests/__init__.py +0 -0
- vispy/plot/tests/test_plot.py +46 -0
- vispy/scene/__init__.py +43 -0
- vispy/scene/cameras/__init__.py +27 -0
- vispy/scene/cameras/_base.py +38 -0
- vispy/scene/cameras/arcball.py +105 -0
- vispy/scene/cameras/base_camera.py +551 -0
- vispy/scene/cameras/fly.py +474 -0
- vispy/scene/cameras/magnify.py +163 -0
- vispy/scene/cameras/panzoom.py +311 -0
- vispy/scene/cameras/perspective.py +338 -0
- vispy/scene/cameras/tests/__init__.py +0 -0
- vispy/scene/cameras/tests/test_cameras.py +27 -0
- vispy/scene/cameras/tests/test_link.py +53 -0
- vispy/scene/cameras/tests/test_perspective.py +122 -0
- vispy/scene/cameras/turntable.py +183 -0
- vispy/scene/canvas.py +639 -0
- vispy/scene/events.py +85 -0
- vispy/scene/node.py +644 -0
- vispy/scene/subscene.py +20 -0
- vispy/scene/tests/__init__.py +0 -0
- vispy/scene/tests/test_canvas.py +119 -0
- vispy/scene/tests/test_node.py +142 -0
- vispy/scene/tests/test_visuals.py +141 -0
- vispy/scene/visuals.py +276 -0
- vispy/scene/widgets/__init__.py +18 -0
- vispy/scene/widgets/anchor.py +25 -0
- vispy/scene/widgets/axis.py +88 -0
- vispy/scene/widgets/colorbar.py +176 -0
- vispy/scene/widgets/console.py +351 -0
- vispy/scene/widgets/grid.py +509 -0
- vispy/scene/widgets/label.py +50 -0
- vispy/scene/widgets/tests/__init__.py +0 -0
- vispy/scene/widgets/tests/test_colorbar.py +47 -0
- vispy/scene/widgets/viewbox.py +199 -0
- vispy/scene/widgets/widget.py +478 -0
- vispy/testing/__init__.py +51 -0
- vispy/testing/_runners.py +448 -0
- vispy/testing/_testing.py +416 -0
- vispy/testing/image_tester.py +494 -0
- vispy/testing/rendered_array_tester.py +85 -0
- vispy/testing/tests/__init__.py +0 -0
- vispy/testing/tests/test_testing.py +20 -0
- vispy/util/__init__.py +32 -0
- vispy/util/bunch.py +15 -0
- vispy/util/check_environment.py +57 -0
- vispy/util/config.py +490 -0
- vispy/util/dpi/__init__.py +19 -0
- vispy/util/dpi/_linux.py +69 -0
- vispy/util/dpi/_quartz.py +26 -0
- vispy/util/dpi/_win32.py +34 -0
- vispy/util/dpi/tests/__init__.py +0 -0
- vispy/util/dpi/tests/test_dpi.py +16 -0
- vispy/util/eq.py +41 -0
- vispy/util/event.py +774 -0
- vispy/util/fetching.py +276 -0
- vispy/util/filter.py +44 -0
- vispy/util/fonts/__init__.py +14 -0
- vispy/util/fonts/_freetype.py +73 -0
- vispy/util/fonts/_quartz.py +192 -0
- vispy/util/fonts/_triage.py +36 -0
- vispy/util/fonts/_vispy_fonts.py +20 -0
- vispy/util/fonts/_win32.py +105 -0
- vispy/util/fonts/data/OpenSans-Bold.ttf +0 -0
- vispy/util/fonts/data/OpenSans-BoldItalic.ttf +0 -0
- vispy/util/fonts/data/OpenSans-Italic.ttf +0 -0
- vispy/util/fonts/data/OpenSans-Regular.ttf +0 -0
- vispy/util/fonts/tests/__init__.py +0 -0
- vispy/util/fonts/tests/test_font.py +45 -0
- vispy/util/fourier.py +69 -0
- vispy/util/frozen.py +25 -0
- vispy/util/gallery_scraper.py +268 -0
- vispy/util/keys.py +91 -0
- vispy/util/logs.py +358 -0
- vispy/util/osmesa_gl.py +17 -0
- vispy/util/profiler.py +135 -0
- vispy/util/ptime.py +16 -0
- vispy/util/quaternion.py +229 -0
- vispy/util/svg/__init__.py +18 -0
- vispy/util/svg/base.py +20 -0
- vispy/util/svg/color.py +219 -0
- vispy/util/svg/element.py +51 -0
- vispy/util/svg/geometry.py +478 -0
- vispy/util/svg/group.py +66 -0
- vispy/util/svg/length.py +81 -0
- vispy/util/svg/number.py +25 -0
- vispy/util/svg/path.py +332 -0
- vispy/util/svg/shapes.py +57 -0
- vispy/util/svg/style.py +59 -0
- vispy/util/svg/svg.py +40 -0
- vispy/util/svg/transform.py +223 -0
- vispy/util/svg/transformable.py +28 -0
- vispy/util/svg/viewport.py +73 -0
- vispy/util/tests/__init__.py +0 -0
- vispy/util/tests/test_config.py +58 -0
- vispy/util/tests/test_docstring_parameters.py +123 -0
- vispy/util/tests/test_emitter_group.py +262 -0
- vispy/util/tests/test_event_emitter.py +743 -0
- vispy/util/tests/test_fourier.py +35 -0
- vispy/util/tests/test_gallery_scraper.py +112 -0
- vispy/util/tests/test_import.py +127 -0
- vispy/util/tests/test_key.py +22 -0
- vispy/util/tests/test_logging.py +45 -0
- vispy/util/tests/test_run.py +14 -0
- vispy/util/tests/test_transforms.py +42 -0
- vispy/util/tests/test_vispy.py +48 -0
- vispy/util/transforms.py +201 -0
- vispy/util/wrappers.py +155 -0
- vispy/version.py +21 -0
- vispy/visuals/__init__.py +50 -0
- vispy/visuals/_scalable_textures.py +487 -0
- vispy/visuals/axis.py +678 -0
- vispy/visuals/border.py +208 -0
- vispy/visuals/box.py +79 -0
- vispy/visuals/collections/__init__.py +30 -0
- vispy/visuals/collections/agg_fast_path_collection.py +219 -0
- vispy/visuals/collections/agg_path_collection.py +197 -0
- vispy/visuals/collections/agg_point_collection.py +52 -0
- vispy/visuals/collections/agg_segment_collection.py +142 -0
- vispy/visuals/collections/array_list.py +401 -0
- vispy/visuals/collections/base_collection.py +482 -0
- vispy/visuals/collections/collection.py +253 -0
- vispy/visuals/collections/path_collection.py +23 -0
- vispy/visuals/collections/point_collection.py +19 -0
- vispy/visuals/collections/polygon_collection.py +25 -0
- vispy/visuals/collections/raw_path_collection.py +119 -0
- vispy/visuals/collections/raw_point_collection.py +113 -0
- vispy/visuals/collections/raw_polygon_collection.py +77 -0
- vispy/visuals/collections/raw_segment_collection.py +112 -0
- vispy/visuals/collections/raw_triangle_collection.py +78 -0
- vispy/visuals/collections/segment_collection.py +19 -0
- vispy/visuals/collections/triangle_collection.py +16 -0
- vispy/visuals/collections/util.py +168 -0
- vispy/visuals/colorbar.py +699 -0
- vispy/visuals/cube.py +41 -0
- vispy/visuals/ellipse.py +162 -0
- vispy/visuals/filters/__init__.py +10 -0
- vispy/visuals/filters/base_filter.py +242 -0
- vispy/visuals/filters/clipper.py +60 -0
- vispy/visuals/filters/clipping_planes.py +122 -0
- vispy/visuals/filters/color.py +181 -0
- vispy/visuals/filters/markers.py +28 -0
- vispy/visuals/filters/mesh.py +801 -0
- vispy/visuals/filters/picking.py +60 -0
- vispy/visuals/filters/tests/__init__.py +3 -0
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/filters/tests/test_wireframe_filter.py +16 -0
- vispy/visuals/glsl/__init__.py +1 -0
- vispy/visuals/glsl/antialiasing.py +133 -0
- vispy/visuals/glsl/color.py +63 -0
- vispy/visuals/graphs/__init__.py +1 -0
- vispy/visuals/graphs/graph.py +240 -0
- vispy/visuals/graphs/layouts/__init__.py +55 -0
- vispy/visuals/graphs/layouts/circular.py +49 -0
- vispy/visuals/graphs/layouts/force_directed.py +211 -0
- vispy/visuals/graphs/layouts/networkx_layout.py +87 -0
- vispy/visuals/graphs/layouts/random.py +52 -0
- vispy/visuals/graphs/tests/__init__.py +1 -0
- vispy/visuals/graphs/tests/test_layouts.py +139 -0
- vispy/visuals/graphs/tests/test_networkx_layout.py +47 -0
- vispy/visuals/graphs/util.py +120 -0
- vispy/visuals/gridlines.py +161 -0
- vispy/visuals/gridmesh.py +98 -0
- vispy/visuals/histogram.py +58 -0
- vispy/visuals/image.py +701 -0
- vispy/visuals/image_complex.py +130 -0
- vispy/visuals/infinite_line.py +199 -0
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/isocurve.py +213 -0
- vispy/visuals/isoline.py +241 -0
- vispy/visuals/isosurface.py +113 -0
- vispy/visuals/line/__init__.py +6 -0
- vispy/visuals/line/arrow.py +289 -0
- vispy/visuals/line/dash_atlas.py +90 -0
- vispy/visuals/line/line.py +545 -0
- vispy/visuals/line_plot.py +135 -0
- vispy/visuals/linear_region.py +199 -0
- vispy/visuals/markers.py +819 -0
- vispy/visuals/mesh.py +373 -0
- vispy/visuals/mesh_normals.py +159 -0
- vispy/visuals/plane.py +54 -0
- vispy/visuals/polygon.py +145 -0
- vispy/visuals/rectangle.py +196 -0
- vispy/visuals/regular_polygon.py +56 -0
- vispy/visuals/scrolling_lines.py +197 -0
- vispy/visuals/shaders/__init__.py +17 -0
- vispy/visuals/shaders/compiler.py +206 -0
- vispy/visuals/shaders/expression.py +99 -0
- vispy/visuals/shaders/function.py +788 -0
- vispy/visuals/shaders/multiprogram.py +145 -0
- vispy/visuals/shaders/parsing.py +140 -0
- vispy/visuals/shaders/program.py +161 -0
- vispy/visuals/shaders/shader_object.py +162 -0
- vispy/visuals/shaders/tests/__init__.py +0 -0
- vispy/visuals/shaders/tests/test_function.py +486 -0
- vispy/visuals/shaders/tests/test_multiprogram.py +78 -0
- vispy/visuals/shaders/tests/test_parsing.py +57 -0
- vispy/visuals/shaders/variable.py +272 -0
- vispy/visuals/spectrogram.py +169 -0
- vispy/visuals/sphere.py +80 -0
- vispy/visuals/surface_plot.py +192 -0
- vispy/visuals/tests/__init__.py +0 -0
- vispy/visuals/tests/test_arrows.py +109 -0
- vispy/visuals/tests/test_axis.py +120 -0
- vispy/visuals/tests/test_collections.py +15 -0
- vispy/visuals/tests/test_colorbar.py +179 -0
- vispy/visuals/tests/test_colormap.py +97 -0
- vispy/visuals/tests/test_ellipse.py +122 -0
- vispy/visuals/tests/test_gridlines.py +30 -0
- vispy/visuals/tests/test_histogram.py +24 -0
- vispy/visuals/tests/test_image.py +392 -0
- vispy/visuals/tests/test_image_complex.py +36 -0
- vispy/visuals/tests/test_infinite_line.py +53 -0
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/tests/test_isosurface.py +22 -0
- vispy/visuals/tests/test_linear_region.py +152 -0
- vispy/visuals/tests/test_markers.py +54 -0
- vispy/visuals/tests/test_mesh.py +261 -0
- vispy/visuals/tests/test_mesh_normals.py +218 -0
- vispy/visuals/tests/test_polygon.py +112 -0
- vispy/visuals/tests/test_rectangle.py +163 -0
- vispy/visuals/tests/test_regular_polygon.py +111 -0
- vispy/visuals/tests/test_scalable_textures.py +196 -0
- vispy/visuals/tests/test_sdf.py +73 -0
- vispy/visuals/tests/test_spectrogram.py +42 -0
- vispy/visuals/tests/test_surface_plot.py +57 -0
- vispy/visuals/tests/test_text.py +95 -0
- vispy/visuals/tests/test_volume.py +542 -0
- vispy/visuals/tests/test_windbarb.py +33 -0
- vispy/visuals/text/__init__.py +7 -0
- vispy/visuals/text/_sdf_cpu.cpython-313-x86_64-linux-gnu.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +112 -0
- vispy/visuals/text/_sdf_gpu.py +316 -0
- vispy/visuals/text/text.py +675 -0
- vispy/visuals/transforms/__init__.py +34 -0
- vispy/visuals/transforms/_util.py +191 -0
- vispy/visuals/transforms/base_transform.py +233 -0
- vispy/visuals/transforms/chain.py +300 -0
- vispy/visuals/transforms/interactive.py +98 -0
- vispy/visuals/transforms/linear.py +564 -0
- vispy/visuals/transforms/nonlinear.py +398 -0
- vispy/visuals/transforms/tests/__init__.py +0 -0
- vispy/visuals/transforms/tests/test_transforms.py +243 -0
- vispy/visuals/transforms/transform_system.py +339 -0
- vispy/visuals/tube.py +173 -0
- vispy/visuals/visual.py +923 -0
- vispy/visuals/volume.py +1366 -0
- vispy/visuals/windbarb.py +291 -0
- vispy/visuals/xyz_axis.py +34 -0
- vispy-0.15.0.dist-info/METADATA +243 -0
- vispy-0.15.0.dist-info/RECORD +521 -0
- vispy-0.15.0.dist-info/WHEEL +6 -0
- vispy-0.15.0.dist-info/licenses/LICENSE.txt +36 -0
- vispy-0.15.0.dist-info/top_level.txt +1 -0
vispy/gloo/texture.py
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# -----------------------------------------------------------------------------
|
|
3
|
+
# Copyright (c) Vispy Development Team. All Rights Reserved.
|
|
4
|
+
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
5
|
+
# -----------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from .globject import GLObject
|
|
13
|
+
from .util import check_enum
|
|
14
|
+
from ..util import np_copy_if_needed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_dtype_limits(dtype):
|
|
18
|
+
if np.issubdtype(dtype, np.floating):
|
|
19
|
+
info = np.finfo(dtype)
|
|
20
|
+
else:
|
|
21
|
+
info = np.iinfo(dtype)
|
|
22
|
+
return info.min, info.max
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def convert_dtype_and_clip(data, dtype, copy=False):
|
|
26
|
+
"""
|
|
27
|
+
cast dtype to a new one, but first clip data to the new dtype's limits if needed
|
|
28
|
+
"""
|
|
29
|
+
old_min, old_max = get_dtype_limits(data.dtype)
|
|
30
|
+
new_min, new_max = get_dtype_limits(dtype)
|
|
31
|
+
if new_max >= old_max and new_min <= old_min:
|
|
32
|
+
# no need to clip
|
|
33
|
+
return np.array(data, dtype=dtype, copy=copy or np_copy_if_needed)
|
|
34
|
+
else:
|
|
35
|
+
# to reduce copying, we clip into a pre-generated array of the right dtype
|
|
36
|
+
new_data = np.empty_like(data, dtype=dtype)
|
|
37
|
+
# allow "unsafe" casting here as we're explicitly clipping to the
|
|
38
|
+
# range of the new dtype - this was a default before numpy 1.25
|
|
39
|
+
np.clip(data, new_min, new_max, out=new_data, casting="unsafe")
|
|
40
|
+
return new_data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def downcast_to_32bit_if_needed(data, copy=False, dtype=None):
|
|
44
|
+
"""Downcast to 32bit dtype if necessary."""
|
|
45
|
+
if dtype is None:
|
|
46
|
+
dtype = data.dtype
|
|
47
|
+
dtype = np.dtype(dtype)
|
|
48
|
+
if dtype.itemsize > 4:
|
|
49
|
+
warnings.warn(
|
|
50
|
+
f"GPUs can't support dtypes bigger than 32-bit, but got '{dtype}'. "
|
|
51
|
+
"Precision will be lost due to downcasting to 32-bit.",
|
|
52
|
+
stacklevel=2,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
size = min(dtype.itemsize, 4)
|
|
56
|
+
kind = dtype.kind
|
|
57
|
+
|
|
58
|
+
new_dtype = np.dtype(f'{kind}{size}')
|
|
59
|
+
return convert_dtype_and_clip(data, new_dtype, copy=copy)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BaseTexture(GLObject):
|
|
63
|
+
"""
|
|
64
|
+
A Texture is used to represent a topological set of scalar values.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
data : ndarray | tuple | None
|
|
69
|
+
Texture data in the form of a numpy array (or something that
|
|
70
|
+
can be turned into one). A tuple with the shape of the texture
|
|
71
|
+
can also be given.
|
|
72
|
+
format : str | enum | None
|
|
73
|
+
The format of the texture: 'luminance', 'alpha',
|
|
74
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
75
|
+
is chosen automatically based on the number of channels.
|
|
76
|
+
When the data has one channel, 'luminance' is assumed.
|
|
77
|
+
resizable : bool
|
|
78
|
+
Indicates whether texture can be resized. Default True.
|
|
79
|
+
interpolation : str | None
|
|
80
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
81
|
+
Default 'nearest'.
|
|
82
|
+
wrapping : str | None
|
|
83
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
84
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
85
|
+
shape : tuple | None
|
|
86
|
+
Optional. A tuple with the shape of the texture. If ``data``
|
|
87
|
+
is also a tuple, it will override the value of ``shape``.
|
|
88
|
+
internalformat : str | None
|
|
89
|
+
Internal format to use.
|
|
90
|
+
resizeable : None
|
|
91
|
+
Deprecated version of `resizable`.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
_ndim = 2
|
|
95
|
+
|
|
96
|
+
_formats = {
|
|
97
|
+
1: 'luminance', # or alpha, or red
|
|
98
|
+
2: 'luminance_alpha', # or rg
|
|
99
|
+
3: 'rgb',
|
|
100
|
+
4: 'rgba'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_inv_formats = {
|
|
104
|
+
'luminance': 1,
|
|
105
|
+
'alpha': 1,
|
|
106
|
+
'red': 1,
|
|
107
|
+
'luminance_alpha': 2,
|
|
108
|
+
'rg': 2,
|
|
109
|
+
'rgb': 3,
|
|
110
|
+
'rgba': 4,
|
|
111
|
+
'depth_component': 1,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# NOTE: non-normalized formats ending with 'i' and 'ui' are currently
|
|
115
|
+
# disabled as they don't work with the current VisPy implementation.
|
|
116
|
+
# Attempting to use them along with the additional enums defined in
|
|
117
|
+
# vispy/gloo/glir.py produces an invalid operation from OpenGL.
|
|
118
|
+
_inv_internalformats = dict([
|
|
119
|
+
(base + suffix, channels)
|
|
120
|
+
for base, channels in [('r', 1), ('rg', 2), ('rgb', 3), ('rgba', 4)]
|
|
121
|
+
for suffix in ['8', '16', '16f', '32f'] # , '8i', '8ui', '32i', '32ui']
|
|
122
|
+
] + [
|
|
123
|
+
('luminance', 1),
|
|
124
|
+
('alpha', 1),
|
|
125
|
+
('red', 1),
|
|
126
|
+
('luminance_alpha', 2),
|
|
127
|
+
('rg', 2),
|
|
128
|
+
('rgb', 3),
|
|
129
|
+
('rgba', 4),
|
|
130
|
+
('depth_component', 1),
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
134
|
+
interpolation=None, wrapping=None, shape=None,
|
|
135
|
+
internalformat=None, resizeable=None):
|
|
136
|
+
GLObject.__init__(self)
|
|
137
|
+
if resizeable is not None:
|
|
138
|
+
resizable = resizeable
|
|
139
|
+
warnings.warn(
|
|
140
|
+
"resizeable has been deprecated in favor of "
|
|
141
|
+
"resizable and will be removed next release",
|
|
142
|
+
DeprecationWarning,
|
|
143
|
+
stacklevel=2,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Init shape and format
|
|
147
|
+
self._resizable = True # at least while we're in init
|
|
148
|
+
self._shape = tuple([0 for i in range(self._ndim+1)])
|
|
149
|
+
self._format = format
|
|
150
|
+
self._internalformat = internalformat
|
|
151
|
+
|
|
152
|
+
# Set texture parameters (before setting data)
|
|
153
|
+
self.interpolation = interpolation or 'nearest'
|
|
154
|
+
self.wrapping = wrapping or 'clamp_to_edge'
|
|
155
|
+
|
|
156
|
+
# Set data or shape (shape arg is for backward compat)
|
|
157
|
+
if isinstance(data, tuple):
|
|
158
|
+
shape, data = data, None
|
|
159
|
+
if data is not None:
|
|
160
|
+
if shape is not None:
|
|
161
|
+
raise ValueError('Texture needs data or shape, not both.')
|
|
162
|
+
data = np.array(data)
|
|
163
|
+
# So we can test the combination
|
|
164
|
+
self._resize(data.shape, format, internalformat)
|
|
165
|
+
self._set_data(data)
|
|
166
|
+
elif shape is not None:
|
|
167
|
+
self._resize(shape, format, internalformat)
|
|
168
|
+
else:
|
|
169
|
+
raise ValueError("Either data or shape must be given")
|
|
170
|
+
|
|
171
|
+
# Set resizable (at end of init)
|
|
172
|
+
self._resizable = bool(resizable)
|
|
173
|
+
|
|
174
|
+
def _normalize_shape(self, data_or_shape):
|
|
175
|
+
# Get data and shape from input
|
|
176
|
+
if isinstance(data_or_shape, np.ndarray):
|
|
177
|
+
data = data_or_shape
|
|
178
|
+
shape = data.shape
|
|
179
|
+
else:
|
|
180
|
+
assert isinstance(data_or_shape, tuple)
|
|
181
|
+
data = None
|
|
182
|
+
shape = data_or_shape
|
|
183
|
+
# Check and correct
|
|
184
|
+
if shape:
|
|
185
|
+
if len(shape) < self._ndim:
|
|
186
|
+
raise ValueError("Too few dimensions for texture")
|
|
187
|
+
elif len(shape) > self._ndim + 1:
|
|
188
|
+
raise ValueError("Too many dimensions for texture")
|
|
189
|
+
elif len(shape) == self._ndim:
|
|
190
|
+
shape = shape + (1,)
|
|
191
|
+
else: # if len(shape) == self._ndim + 1:
|
|
192
|
+
if shape[-1] > 4:
|
|
193
|
+
raise ValueError("Too many channels for texture")
|
|
194
|
+
# Return
|
|
195
|
+
return data.reshape(shape) if data is not None else shape
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def shape(self):
|
|
199
|
+
"""Data shape (last dimension indicates number of color channels)"""
|
|
200
|
+
return self._shape
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def format(self):
|
|
204
|
+
"""The texture format (color channels)."""
|
|
205
|
+
return self._format
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def internalformat(self):
|
|
209
|
+
"""The texture internalformat."""
|
|
210
|
+
return self._internalformat
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def wrapping(self):
|
|
214
|
+
"""Texture wrapping mode"""
|
|
215
|
+
value = self._wrapping
|
|
216
|
+
return value[0] if all([v == value[0] for v in value]) else value
|
|
217
|
+
|
|
218
|
+
@wrapping.setter
|
|
219
|
+
def wrapping(self, value):
|
|
220
|
+
# Convert
|
|
221
|
+
if isinstance(value, int) or isinstance(value, str):
|
|
222
|
+
value = (value,) * self._ndim
|
|
223
|
+
elif isinstance(value, (tuple, list)):
|
|
224
|
+
if len(value) != self._ndim:
|
|
225
|
+
raise ValueError('Texture wrapping needs 1 or %i values' %
|
|
226
|
+
self._ndim)
|
|
227
|
+
else:
|
|
228
|
+
raise ValueError('Invalid value for wrapping: %r' % value)
|
|
229
|
+
# Check and set
|
|
230
|
+
valid = 'repeat', 'clamp_to_edge', 'mirrored_repeat'
|
|
231
|
+
value = tuple([check_enum(value[i], 'tex wrapping', valid)
|
|
232
|
+
for i in range(self._ndim)])
|
|
233
|
+
self._wrapping = value
|
|
234
|
+
self._glir.command('WRAPPING', self._id, value)
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def interpolation(self):
|
|
238
|
+
"""Texture interpolation for minification and magnification."""
|
|
239
|
+
value = self._interpolation
|
|
240
|
+
return value[0] if value[0] == value[1] else value
|
|
241
|
+
|
|
242
|
+
@interpolation.setter
|
|
243
|
+
def interpolation(self, value):
|
|
244
|
+
# Convert
|
|
245
|
+
if isinstance(value, int) or isinstance(value, str):
|
|
246
|
+
value = (value,) * 2
|
|
247
|
+
elif isinstance(value, (tuple, list)):
|
|
248
|
+
if len(value) != 2:
|
|
249
|
+
raise ValueError('Texture interpolation needs 1 or 2 values')
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError('Invalid value for interpolation: %r' % value)
|
|
252
|
+
# Check and set
|
|
253
|
+
valid = 'nearest', 'linear'
|
|
254
|
+
value = (check_enum(value[0], 'tex interpolation', valid),
|
|
255
|
+
check_enum(value[1], 'tex interpolation', valid))
|
|
256
|
+
self._interpolation = value
|
|
257
|
+
self._glir.command('INTERPOLATION', self._id, *value)
|
|
258
|
+
|
|
259
|
+
def resize(self, shape, format=None, internalformat=None):
|
|
260
|
+
"""Set the texture size and format
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
shape : tuple of integers
|
|
265
|
+
New texture shape in zyx order. Optionally, an extra dimention
|
|
266
|
+
may be specified to indicate the number of color channels.
|
|
267
|
+
format : str | enum | None
|
|
268
|
+
The format of the texture: 'luminance', 'alpha',
|
|
269
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
270
|
+
is chosen automatically based on the number of channels.
|
|
271
|
+
When the data has one channel, 'luminance' is assumed.
|
|
272
|
+
internalformat : str | enum | None
|
|
273
|
+
The internal (storage) format of the texture: 'luminance',
|
|
274
|
+
'alpha', 'r8', 'r16', 'r16f', 'r32f'; 'luminance_alpha',
|
|
275
|
+
'rg8', 'rg16', 'rg16f', 'rg32f'; 'rgb', 'rgb8', 'rgb16',
|
|
276
|
+
'rgb16f', 'rgb32f'; 'rgba', 'rgba8', 'rgba16', 'rgba16f',
|
|
277
|
+
'rgba32f'. If None, the internalformat is chosen
|
|
278
|
+
automatically based on the number of channels. This is a
|
|
279
|
+
hint which may be ignored by the OpenGL implementation.
|
|
280
|
+
"""
|
|
281
|
+
return self._resize(shape, format, internalformat)
|
|
282
|
+
|
|
283
|
+
def _check_format_change(self, format, num_channels):
|
|
284
|
+
# Determine format
|
|
285
|
+
if format is None:
|
|
286
|
+
format = self._formats[num_channels]
|
|
287
|
+
# Keep current format if channels match
|
|
288
|
+
if self._format and \
|
|
289
|
+
self._inv_formats[self._format] == self._inv_formats[format]:
|
|
290
|
+
format = self._format
|
|
291
|
+
else:
|
|
292
|
+
format = check_enum(format)
|
|
293
|
+
|
|
294
|
+
if format not in self._inv_formats:
|
|
295
|
+
raise ValueError('Invalid texture format: %r.' % format)
|
|
296
|
+
elif num_channels != self._inv_formats[format]:
|
|
297
|
+
raise ValueError('Format does not match with given shape. '
|
|
298
|
+
'(format expects %d elements, data has %d)' %
|
|
299
|
+
(self._inv_formats[format], num_channels))
|
|
300
|
+
return format
|
|
301
|
+
|
|
302
|
+
def _check_internalformat_change(self, internalformat, num_channels):
|
|
303
|
+
if internalformat is None:
|
|
304
|
+
# Keep current internalformat if channels match
|
|
305
|
+
if self._internalformat and \
|
|
306
|
+
self._inv_internalformats[self._internalformat] == num_channels:
|
|
307
|
+
internalformat = self._internalformat
|
|
308
|
+
else:
|
|
309
|
+
internalformat = check_enum(internalformat)
|
|
310
|
+
|
|
311
|
+
if internalformat is None:
|
|
312
|
+
pass
|
|
313
|
+
elif internalformat not in self._inv_internalformats:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
'Invalid texture internalformat: %r. Allowed formats: %r'
|
|
316
|
+
% (internalformat, self._inv_internalformats)
|
|
317
|
+
)
|
|
318
|
+
elif num_channels != self._inv_internalformats[internalformat]:
|
|
319
|
+
raise ValueError('Internalformat does not match with given shape.')
|
|
320
|
+
return internalformat
|
|
321
|
+
|
|
322
|
+
def _resize(self, shape, format=None, internalformat=None):
|
|
323
|
+
"""Internal method for resize."""
|
|
324
|
+
shape = self._normalize_shape(shape)
|
|
325
|
+
|
|
326
|
+
# Check
|
|
327
|
+
if not self._resizable:
|
|
328
|
+
raise RuntimeError("Texture is not resizable")
|
|
329
|
+
|
|
330
|
+
format = self._check_format_change(format, shape[-1])
|
|
331
|
+
internalformat = self._check_internalformat_change(internalformat, shape[-1])
|
|
332
|
+
|
|
333
|
+
# Store and send GLIR command
|
|
334
|
+
self._shape = shape
|
|
335
|
+
self._format = format
|
|
336
|
+
self._internalformat = internalformat
|
|
337
|
+
self._glir.command('SIZE', self._id, self._shape, self._format,
|
|
338
|
+
self._internalformat)
|
|
339
|
+
|
|
340
|
+
def set_data(self, data, offset=None, copy=False):
|
|
341
|
+
"""Set texture data
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
data : ndarray
|
|
346
|
+
Data to be uploaded
|
|
347
|
+
offset: int | tuple of ints
|
|
348
|
+
Offset in texture where to start copying data
|
|
349
|
+
copy: bool
|
|
350
|
+
Since the operation is deferred, data may change before
|
|
351
|
+
data is actually uploaded to GPU memory. Asking explicitly
|
|
352
|
+
for a copy will prevent this behavior.
|
|
353
|
+
|
|
354
|
+
Notes
|
|
355
|
+
-----
|
|
356
|
+
This operation implicitly resizes the texture to the shape of
|
|
357
|
+
the data if given offset is None.
|
|
358
|
+
"""
|
|
359
|
+
return self._set_data(data, offset, copy)
|
|
360
|
+
|
|
361
|
+
def _set_data(self, data, offset=None, copy=False):
|
|
362
|
+
"""Internal method for set_data."""
|
|
363
|
+
# Copy if needed, check/normalize shape
|
|
364
|
+
data = downcast_to_32bit_if_needed(data, copy=copy)
|
|
365
|
+
data = self._normalize_shape(data)
|
|
366
|
+
|
|
367
|
+
# Maybe resize to purge DATA commands?
|
|
368
|
+
if offset is None:
|
|
369
|
+
self._resize(data.shape)
|
|
370
|
+
elif all([i == 0 for i in offset]) and data.shape == self._shape:
|
|
371
|
+
self._resize(data.shape)
|
|
372
|
+
|
|
373
|
+
# Convert offset to something usable
|
|
374
|
+
offset = offset or tuple([0 for i in range(self._ndim)])
|
|
375
|
+
assert len(offset) == self._ndim
|
|
376
|
+
|
|
377
|
+
# Check if data fits
|
|
378
|
+
for i in range(len(data.shape)-1):
|
|
379
|
+
if offset[i] + data.shape[i] > self._shape[i]:
|
|
380
|
+
raise ValueError("Data is too large")
|
|
381
|
+
|
|
382
|
+
# Send GLIR command
|
|
383
|
+
self._glir.command('DATA', self._id, offset, data)
|
|
384
|
+
|
|
385
|
+
def __setitem__(self, key, data):
|
|
386
|
+
"""x.__getitem__(y) <==> x[y]"""
|
|
387
|
+
# Make sure key is a tuple
|
|
388
|
+
if isinstance(key, (int, slice)) or key == Ellipsis:
|
|
389
|
+
key = (key,)
|
|
390
|
+
|
|
391
|
+
# Default is to access the whole texture
|
|
392
|
+
shape = self._shape
|
|
393
|
+
slices = [slice(0, shape[i]) for i in range(len(shape))]
|
|
394
|
+
|
|
395
|
+
# Check last key/Ellipsis to decide on the order
|
|
396
|
+
keys = key[::+1]
|
|
397
|
+
dims = range(0, len(key))
|
|
398
|
+
if key[0] == Ellipsis:
|
|
399
|
+
keys = key[::-1]
|
|
400
|
+
dims = range(len(self._shape) - 1,
|
|
401
|
+
len(self._shape) - 1 - len(keys), -1)
|
|
402
|
+
|
|
403
|
+
# Find exact range for each key
|
|
404
|
+
for k, dim in zip(keys, dims):
|
|
405
|
+
size = self._shape[dim]
|
|
406
|
+
if isinstance(k, int):
|
|
407
|
+
if k < 0:
|
|
408
|
+
k += size
|
|
409
|
+
if k < 0 or k > size:
|
|
410
|
+
raise IndexError("Texture assignment index out of range")
|
|
411
|
+
start, stop = k, k + 1
|
|
412
|
+
slices[dim] = slice(start, stop, 1)
|
|
413
|
+
elif isinstance(k, slice):
|
|
414
|
+
start, stop, step = k.indices(size)
|
|
415
|
+
if step != 1:
|
|
416
|
+
raise IndexError("Cannot access non-contiguous data")
|
|
417
|
+
if stop < start:
|
|
418
|
+
start, stop = stop, start
|
|
419
|
+
slices[dim] = slice(start, stop, step)
|
|
420
|
+
elif k == Ellipsis:
|
|
421
|
+
pass
|
|
422
|
+
else:
|
|
423
|
+
raise TypeError("Texture indices must be integers")
|
|
424
|
+
|
|
425
|
+
offset = tuple([s.start for s in slices])[:self._ndim]
|
|
426
|
+
shape = tuple([s.stop - s.start for s in slices])
|
|
427
|
+
size = np.prod(shape) if len(shape) > 0 else 1
|
|
428
|
+
|
|
429
|
+
# Make sure data is an array
|
|
430
|
+
if not isinstance(data, np.ndarray):
|
|
431
|
+
data = np.array(data)
|
|
432
|
+
# Make sure data is big enough
|
|
433
|
+
if data.shape != shape:
|
|
434
|
+
data = np.resize(data, shape)
|
|
435
|
+
|
|
436
|
+
# Set data (deferred)
|
|
437
|
+
self._set_data(data=data, offset=offset, copy=False)
|
|
438
|
+
|
|
439
|
+
def __repr__(self):
|
|
440
|
+
return "<%s shape=%r format=%r at 0x%x>" % (
|
|
441
|
+
self.__class__.__name__, self._shape, self._format, id(self))
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# --------------------------------------------------------- Texture1D class ---
|
|
445
|
+
class Texture1D(BaseTexture):
|
|
446
|
+
"""One dimensional texture
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
data : ndarray | tuple | None
|
|
451
|
+
Texture data in the form of a numpy array (or something that
|
|
452
|
+
can be turned into one). A tuple with the shape of the texture
|
|
453
|
+
can also be given.
|
|
454
|
+
format : str | enum | None
|
|
455
|
+
The format of the texture: 'luminance', 'alpha',
|
|
456
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
457
|
+
is chosen automatically based on the number of channels.
|
|
458
|
+
When the data has one channel, 'luminance' is assumed.
|
|
459
|
+
resizable : bool
|
|
460
|
+
Indicates whether texture can be resized. Default True.
|
|
461
|
+
interpolation : str | None
|
|
462
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
463
|
+
Default 'nearest'.
|
|
464
|
+
wrapping : str | None
|
|
465
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
466
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
467
|
+
shape : tuple | None
|
|
468
|
+
Optional. A tuple with the shape of the texture. If ``data``
|
|
469
|
+
is also a tuple, it will override the value of ``shape``.
|
|
470
|
+
internalformat : str | None
|
|
471
|
+
Internal format to use.
|
|
472
|
+
resizeable : None
|
|
473
|
+
Deprecated version of `resizable`.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
_ndim = 1
|
|
477
|
+
_GLIR_TYPE = 'Texture1D'
|
|
478
|
+
|
|
479
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
480
|
+
interpolation=None, wrapping=None, shape=None,
|
|
481
|
+
internalformat=None, resizeable=None):
|
|
482
|
+
BaseTexture.__init__(self, data, format, resizable, interpolation,
|
|
483
|
+
wrapping, shape, internalformat, resizeable)
|
|
484
|
+
|
|
485
|
+
@property
|
|
486
|
+
def width(self):
|
|
487
|
+
"""Texture width"""
|
|
488
|
+
return self._shape[0]
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def glsl_type(self):
|
|
492
|
+
"""GLSL declaration strings required for a variable to hold this data."""
|
|
493
|
+
return 'uniform', 'sampler1D'
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def glsl_sampler_type(self):
|
|
497
|
+
"""GLSL type of the sampler."""
|
|
498
|
+
return 'sampler1D'
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def glsl_sample(self):
|
|
502
|
+
"""GLSL function that samples the texture."""
|
|
503
|
+
return 'texture1D'
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# --------------------------------------------------------- Texture2D class ---
|
|
507
|
+
class Texture2D(BaseTexture):
|
|
508
|
+
"""Two dimensional texture
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
data : ndarray
|
|
513
|
+
Texture data shaped as W, or a tuple with the shape for
|
|
514
|
+
the texture (W).
|
|
515
|
+
format : str | enum | None
|
|
516
|
+
The format of the texture: 'luminance', 'alpha',
|
|
517
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
518
|
+
is chosen automatically based on the number of channels.
|
|
519
|
+
When the data has one channel, 'luminance' is assumed.
|
|
520
|
+
resizable : bool
|
|
521
|
+
Indicates whether texture can be resized. Default True.
|
|
522
|
+
interpolation : str
|
|
523
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
524
|
+
Default 'nearest'.
|
|
525
|
+
wrapping : str
|
|
526
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
527
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
528
|
+
shape : tuple
|
|
529
|
+
Optional. A tuple with the shape HxW. If ``data``
|
|
530
|
+
is also a tuple, it will override the value of ``shape``.
|
|
531
|
+
internalformat : str | None
|
|
532
|
+
Internal format to use.
|
|
533
|
+
resizeable : None
|
|
534
|
+
Deprecated version of `resizable`.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
_ndim = 2
|
|
538
|
+
_GLIR_TYPE = 'Texture2D'
|
|
539
|
+
|
|
540
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
541
|
+
interpolation=None, wrapping=None, shape=None,
|
|
542
|
+
internalformat=None, resizeable=None):
|
|
543
|
+
BaseTexture.__init__(self, data, format, resizable, interpolation,
|
|
544
|
+
wrapping, shape, internalformat, resizeable)
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def height(self):
|
|
548
|
+
"""Texture height"""
|
|
549
|
+
return self._shape[0]
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def width(self):
|
|
553
|
+
"""Texture width"""
|
|
554
|
+
return self._shape[1]
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def glsl_type(self):
|
|
558
|
+
"""GLSL declaration strings required for a variable to hold this data."""
|
|
559
|
+
return 'uniform', 'sampler2D'
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def glsl_sampler_type(self):
|
|
563
|
+
"""GLSL type of the sampler."""
|
|
564
|
+
return 'sampler2D'
|
|
565
|
+
|
|
566
|
+
@property
|
|
567
|
+
def glsl_sample(self):
|
|
568
|
+
"""GLSL function that samples the texture."""
|
|
569
|
+
return 'texture2D'
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# --------------------------------------------------------- Texture3D class ---
|
|
573
|
+
class Texture3D(BaseTexture):
|
|
574
|
+
"""Three dimensional texture
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
data : ndarray | tuple | None
|
|
579
|
+
Texture data in the form of a numpy array (or something that
|
|
580
|
+
can be turned into one). A tuple with the shape of the texture
|
|
581
|
+
can also be given.
|
|
582
|
+
format : str | enum | None
|
|
583
|
+
The format of the texture: 'luminance', 'alpha',
|
|
584
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
585
|
+
is chosen automatically based on the number of channels.
|
|
586
|
+
When the data has one channel, 'luminance' is assumed.
|
|
587
|
+
resizable : bool
|
|
588
|
+
Indicates whether texture can be resized. Default True.
|
|
589
|
+
interpolation : str | None
|
|
590
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
591
|
+
Default 'nearest'.
|
|
592
|
+
wrapping : str | None
|
|
593
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
594
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
595
|
+
shape : tuple | None
|
|
596
|
+
Optional. A tuple with the shape of the texture. If ``data``
|
|
597
|
+
is also a tuple, it will override the value of ``shape``.
|
|
598
|
+
internalformat : str | None
|
|
599
|
+
Internal format to use.
|
|
600
|
+
resizeable : None
|
|
601
|
+
Deprecated version of `resizable`.
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
_ndim = 3
|
|
605
|
+
_GLIR_TYPE = 'Texture3D'
|
|
606
|
+
|
|
607
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
608
|
+
interpolation=None, wrapping=None, shape=None,
|
|
609
|
+
internalformat=None, resizeable=None):
|
|
610
|
+
BaseTexture.__init__(self, data, format, resizable, interpolation,
|
|
611
|
+
wrapping, shape, internalformat, resizeable)
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def width(self):
|
|
615
|
+
"""Texture width"""
|
|
616
|
+
return self._shape[2]
|
|
617
|
+
|
|
618
|
+
@property
|
|
619
|
+
def height(self):
|
|
620
|
+
"""Texture height"""
|
|
621
|
+
return self._shape[1]
|
|
622
|
+
|
|
623
|
+
@property
|
|
624
|
+
def depth(self):
|
|
625
|
+
"""Texture depth"""
|
|
626
|
+
return self._shape[0]
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def glsl_type(self):
|
|
630
|
+
"""GLSL declaration strings required for a variable to hold this data."""
|
|
631
|
+
return 'uniform', 'sampler3D'
|
|
632
|
+
|
|
633
|
+
@property
|
|
634
|
+
def glsl_sampler_type(self):
|
|
635
|
+
"""GLSL type of the sampler."""
|
|
636
|
+
return 'sampler3D'
|
|
637
|
+
|
|
638
|
+
@property
|
|
639
|
+
def glsl_sample(self):
|
|
640
|
+
"""GLSL function that samples the texture."""
|
|
641
|
+
return 'texture3D'
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
# --------------------------------------------------------- TextureCube class ---
|
|
645
|
+
class TextureCube(BaseTexture):
|
|
646
|
+
"""Texture Cube
|
|
647
|
+
|
|
648
|
+
Parameters
|
|
649
|
+
----------
|
|
650
|
+
data : ndarray | tuple | None
|
|
651
|
+
Texture data in the form of a numpy array (or something that
|
|
652
|
+
can be turned into one). A tuple with the shape of the texture
|
|
653
|
+
can also be given.
|
|
654
|
+
format : str | enum | None
|
|
655
|
+
The format of the texture: 'luminance', 'alpha',
|
|
656
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
657
|
+
is chosen automatically based on the number of channels.
|
|
658
|
+
When the data has one channel, 'luminance' is assumed.
|
|
659
|
+
resizable : bool
|
|
660
|
+
Indicates whether texture can be resized. Default True.
|
|
661
|
+
interpolation : str | None
|
|
662
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
663
|
+
Default 'nearest'.
|
|
664
|
+
wrapping : str | None
|
|
665
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
666
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
667
|
+
shape : tuple | None
|
|
668
|
+
Optional. A tuple with the shape of the texture. If ``data``
|
|
669
|
+
is also a tuple, it will override the value of ``shape``.
|
|
670
|
+
internalformat : str | None
|
|
671
|
+
Internal format to use.
|
|
672
|
+
resizeable : None
|
|
673
|
+
Deprecated version of `resizable`.
|
|
674
|
+
"""
|
|
675
|
+
|
|
676
|
+
_ndim = 3
|
|
677
|
+
_GLIR_TYPE = 'TextureCube'
|
|
678
|
+
|
|
679
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
680
|
+
interpolation=None, wrapping=None, shape=None,
|
|
681
|
+
internalformat=None, resizeable=None):
|
|
682
|
+
BaseTexture.__init__(self, data, format, resizable, interpolation,
|
|
683
|
+
wrapping, shape, internalformat, resizeable)
|
|
684
|
+
if self._shape[0] != 6:
|
|
685
|
+
raise ValueError("Texture cube require arrays first dimension to be 6 :"
|
|
686
|
+
" {} was given.".format(self._shape[0]))
|
|
687
|
+
|
|
688
|
+
@property
|
|
689
|
+
def height(self):
|
|
690
|
+
"""Texture height"""
|
|
691
|
+
return self._shape[1]
|
|
692
|
+
|
|
693
|
+
@property
|
|
694
|
+
def width(self):
|
|
695
|
+
"""Texture width"""
|
|
696
|
+
return self._shape[2]
|
|
697
|
+
|
|
698
|
+
@property
|
|
699
|
+
def depth(self):
|
|
700
|
+
"""Texture depth"""
|
|
701
|
+
return self._shape[0]
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def glsl_type(self):
|
|
705
|
+
"""GLSL declaration strings required for a variable to hold this data."""
|
|
706
|
+
return 'uniform', 'samplerCube'
|
|
707
|
+
|
|
708
|
+
@property
|
|
709
|
+
def glsl_sampler_type(self):
|
|
710
|
+
"""GLSL type of the sampler."""
|
|
711
|
+
return 'samplerCube'
|
|
712
|
+
|
|
713
|
+
@property
|
|
714
|
+
def glsl_sample(self):
|
|
715
|
+
"""GLSL function that samples the texture."""
|
|
716
|
+
return 'textureCube'
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
# ------------------------------------------------- TextureEmulated3D class ---
|
|
720
|
+
class TextureEmulated3D(Texture2D):
|
|
721
|
+
"""Two dimensional texture that is emulating a three dimensional texture
|
|
722
|
+
|
|
723
|
+
Parameters
|
|
724
|
+
----------
|
|
725
|
+
data : ndarray | tuple | None
|
|
726
|
+
Texture data in the form of a numpy array (or something that
|
|
727
|
+
can be turned into one). A tuple with the shape of the texture
|
|
728
|
+
can also be given.
|
|
729
|
+
format : str | enum | None
|
|
730
|
+
The format of the texture: 'luminance', 'alpha',
|
|
731
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
732
|
+
is chosen automatically based on the number of channels.
|
|
733
|
+
When the data has one channel, 'luminance' is assumed.
|
|
734
|
+
resizable : bool
|
|
735
|
+
Indicates whether texture can be resized. Default True.
|
|
736
|
+
interpolation : str | None
|
|
737
|
+
Interpolation mode, must be one of: 'nearest', 'linear'.
|
|
738
|
+
Default 'nearest'.
|
|
739
|
+
wrapping : str | None
|
|
740
|
+
Wrapping mode, must be one of: 'repeat', 'clamp_to_edge',
|
|
741
|
+
'mirrored_repeat'. Default 'clamp_to_edge'.
|
|
742
|
+
shape : tuple | None
|
|
743
|
+
Optional. A tuple with the shape of the texture. If ``data``
|
|
744
|
+
is also a tuple, it will override the value of ``shape``.
|
|
745
|
+
internalformat : str | None
|
|
746
|
+
Internal format to use.
|
|
747
|
+
resizeable : None
|
|
748
|
+
Deprecated version of `resizable`.
|
|
749
|
+
"""
|
|
750
|
+
|
|
751
|
+
# TODO: does GL's nearest use floor or round?
|
|
752
|
+
_glsl_sample_nearest = """
|
|
753
|
+
vec4 sample(sampler2D tex, vec3 texcoord) {
|
|
754
|
+
// Don't let adjacent frames be interpolated into this one
|
|
755
|
+
texcoord.x = min(texcoord.x * $shape.x, $shape.x - 0.5);
|
|
756
|
+
texcoord.x = max(0.5, texcoord.x) / $shape.x;
|
|
757
|
+
texcoord.y = min(texcoord.y * $shape.y, $shape.y - 0.5);
|
|
758
|
+
texcoord.y = max(0.5, texcoord.y) / $shape.y;
|
|
759
|
+
|
|
760
|
+
float index = floor(texcoord.z * $shape.z);
|
|
761
|
+
|
|
762
|
+
// Do a lookup in the 2D texture
|
|
763
|
+
float u = (mod(index, $r) + texcoord.x) / $r;
|
|
764
|
+
float v = (floor(index / $r) + texcoord.y) / $c;
|
|
765
|
+
|
|
766
|
+
return texture2D(tex, vec2(u,v));
|
|
767
|
+
}
|
|
768
|
+
"""
|
|
769
|
+
|
|
770
|
+
_glsl_sample_linear = """
|
|
771
|
+
vec4 sample(sampler2D tex, vec3 texcoord) {
|
|
772
|
+
// Don't let adjacent frames be interpolated into this one
|
|
773
|
+
texcoord.x = min(texcoord.x * $shape.x, $shape.x - 0.5);
|
|
774
|
+
texcoord.x = max(0.5, texcoord.x) / $shape.x;
|
|
775
|
+
texcoord.y = min(texcoord.y * $shape.y, $shape.y - 0.5);
|
|
776
|
+
texcoord.y = max(0.5, texcoord.y) / $shape.y;
|
|
777
|
+
|
|
778
|
+
float z = texcoord.z * $shape.z;
|
|
779
|
+
float zindex1 = floor(z);
|
|
780
|
+
float u1 = (mod(zindex1, $r) + texcoord.x) / $r;
|
|
781
|
+
float v1 = (floor(zindex1 / $r) + texcoord.y) / $c;
|
|
782
|
+
|
|
783
|
+
float zindex2 = zindex1 + 1.0;
|
|
784
|
+
float u2 = (mod(zindex2, $r) + texcoord.x) / $r;
|
|
785
|
+
float v2 = (floor(zindex2 / $r) + texcoord.y) / $c;
|
|
786
|
+
|
|
787
|
+
vec4 s1 = texture2D(tex, vec2(u1, v1));
|
|
788
|
+
vec4 s2 = texture2D(tex, vec2(u2, v2));
|
|
789
|
+
|
|
790
|
+
return s1 * (zindex2 - z) + s2 * (z - zindex1);
|
|
791
|
+
}
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
_gl_max_texture_size = 1024 # For now, we just set this manually
|
|
795
|
+
|
|
796
|
+
def __init__(self, data=None, format=None, resizable=True,
|
|
797
|
+
interpolation=None, wrapping=None, shape=None,
|
|
798
|
+
internalformat=None, resizeable=None):
|
|
799
|
+
from ..visuals.shaders import Function
|
|
800
|
+
|
|
801
|
+
self._set_emulated_shape(data)
|
|
802
|
+
Texture2D.__init__(self, self._normalize_emulated_shape(data),
|
|
803
|
+
format, resizable, interpolation, wrapping,
|
|
804
|
+
shape, internalformat, resizeable)
|
|
805
|
+
if self.interpolation == 'nearest':
|
|
806
|
+
self._glsl_sample = Function(self.__class__._glsl_sample_nearest)
|
|
807
|
+
else:
|
|
808
|
+
self._glsl_sample = Function(self.__class__._glsl_sample_linear)
|
|
809
|
+
self._update_variables()
|
|
810
|
+
|
|
811
|
+
def _set_emulated_shape(self, data_or_shape):
|
|
812
|
+
if isinstance(data_or_shape, np.ndarray):
|
|
813
|
+
self._emulated_shape = data_or_shape.shape
|
|
814
|
+
else:
|
|
815
|
+
assert isinstance(data_or_shape, tuple)
|
|
816
|
+
self._emulated_shape = tuple(data_or_shape)
|
|
817
|
+
|
|
818
|
+
depth, width = self._emulated_shape[0], self._emulated_shape[1]
|
|
819
|
+
self._r = TextureEmulated3D._gl_max_texture_size // width
|
|
820
|
+
self._c = depth // self._r
|
|
821
|
+
if math.fmod(depth, self._r):
|
|
822
|
+
self._c += 1
|
|
823
|
+
|
|
824
|
+
def _normalize_emulated_shape(self, data_or_shape):
|
|
825
|
+
if isinstance(data_or_shape, np.ndarray):
|
|
826
|
+
new_shape = self._normalize_emulated_shape(data_or_shape.shape)
|
|
827
|
+
new_data = np.empty(new_shape, dtype=data_or_shape.dtype)
|
|
828
|
+
for j in range(self._c):
|
|
829
|
+
for i in range(self._r):
|
|
830
|
+
i0, i1 = i * self.width, (i+1) * self.width
|
|
831
|
+
j0, j1 = j * self.height, (j+1) * self.height
|
|
832
|
+
k = j * self._r + i
|
|
833
|
+
if k >= self.depth:
|
|
834
|
+
break
|
|
835
|
+
new_data[j0:j1, i0:i1] = data_or_shape[k]
|
|
836
|
+
|
|
837
|
+
return new_data
|
|
838
|
+
|
|
839
|
+
assert isinstance(data_or_shape, tuple)
|
|
840
|
+
return (self._c * self.height, self._r * self.width) + \
|
|
841
|
+
data_or_shape[3:]
|
|
842
|
+
|
|
843
|
+
def _update_variables(self):
|
|
844
|
+
self._glsl_sample['shape'] = self.shape[:3][::-1]
|
|
845
|
+
# On Windows with Python 2.7, self._c can end up being a long
|
|
846
|
+
# integer because Numpy array shapes return long integers. This
|
|
847
|
+
# causes issues when setting the gloo variables since these are
|
|
848
|
+
# expected to be native ints, so we cast the integers to ints
|
|
849
|
+
# to avoid this.
|
|
850
|
+
# Newer GLSL compilers do not implicitly cast types so these integers
|
|
851
|
+
# must be converted to floats lastly
|
|
852
|
+
self._glsl_sample['c'] = float(int(self._c))
|
|
853
|
+
self._glsl_sample['r'] = float(int(self._r))
|
|
854
|
+
|
|
855
|
+
def set_data(self, data, offset=None, copy=False):
|
|
856
|
+
"""Set texture data
|
|
857
|
+
|
|
858
|
+
Parameters
|
|
859
|
+
----------
|
|
860
|
+
data : ndarray
|
|
861
|
+
Data to be uploaded
|
|
862
|
+
offset: int | tuple of ints
|
|
863
|
+
Offset in texture where to start copying data
|
|
864
|
+
copy: bool
|
|
865
|
+
Since the operation is deferred, data may change before
|
|
866
|
+
data is actually uploaded to GPU memory. Asking explicitly
|
|
867
|
+
for a copy will prevent this behavior.
|
|
868
|
+
|
|
869
|
+
Notes
|
|
870
|
+
-----
|
|
871
|
+
This operation implicitely resizes the texture to the shape of
|
|
872
|
+
the data if given offset is None.
|
|
873
|
+
"""
|
|
874
|
+
self._set_emulated_shape(data)
|
|
875
|
+
Texture2D.set_data(self, self._normalize_emulated_shape(data),
|
|
876
|
+
offset, copy)
|
|
877
|
+
self._update_variables()
|
|
878
|
+
|
|
879
|
+
def resize(self, shape, format=None, internalformat=None):
|
|
880
|
+
"""Set the texture size and format
|
|
881
|
+
|
|
882
|
+
Parameters
|
|
883
|
+
----------
|
|
884
|
+
shape : tuple of integers
|
|
885
|
+
New texture shape in zyx order. Optionally, an extra dimention
|
|
886
|
+
may be specified to indicate the number of color channels.
|
|
887
|
+
format : str | enum | None
|
|
888
|
+
The format of the texture: 'luminance', 'alpha',
|
|
889
|
+
'luminance_alpha', 'rgb', or 'rgba'. If not given the format
|
|
890
|
+
is chosen automatically based on the number of channels.
|
|
891
|
+
When the data has one channel, 'luminance' is assumed.
|
|
892
|
+
internalformat : str | enum | None
|
|
893
|
+
The internal (storage) format of the texture: 'luminance',
|
|
894
|
+
'alpha', 'r8', 'r16', 'r16f', 'r32f'; 'luminance_alpha',
|
|
895
|
+
'rg8', 'rg16', 'rg16f', 'rg32f'; 'rgb', 'rgb8', 'rgb16',
|
|
896
|
+
'rgb16f', 'rgb32f'; 'rgba', 'rgba8', 'rgba16', 'rgba16f',
|
|
897
|
+
'rgba32f'. If None, the internalformat is chosen
|
|
898
|
+
automatically based on the number of channels. This is a
|
|
899
|
+
hint which may be ignored by the OpenGL implementation.
|
|
900
|
+
"""
|
|
901
|
+
self._set_emulated_shape(shape)
|
|
902
|
+
Texture2D.resize(self, self._normalize_emulated_shape(shape),
|
|
903
|
+
format, internalformat)
|
|
904
|
+
self._update_variables()
|
|
905
|
+
|
|
906
|
+
@property
|
|
907
|
+
def shape(self):
|
|
908
|
+
"""Data shape (last dimension indicates number of color channels)"""
|
|
909
|
+
return self._emulated_shape
|
|
910
|
+
|
|
911
|
+
@property
|
|
912
|
+
def width(self):
|
|
913
|
+
"""Texture width"""
|
|
914
|
+
return self._emulated_shape[2]
|
|
915
|
+
|
|
916
|
+
@property
|
|
917
|
+
def height(self):
|
|
918
|
+
"""Texture height"""
|
|
919
|
+
return self._emulated_shape[1]
|
|
920
|
+
|
|
921
|
+
@property
|
|
922
|
+
def depth(self):
|
|
923
|
+
"""Texture depth"""
|
|
924
|
+
return self._emulated_shape[0]
|
|
925
|
+
|
|
926
|
+
@property
|
|
927
|
+
def glsl_sample(self):
|
|
928
|
+
"""GLSL function that samples the texture."""
|
|
929
|
+
return self._glsl_sample
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
# ------------------------------------------------------ TextureAtlas class ---
|
|
933
|
+
class TextureAtlas(Texture2D):
|
|
934
|
+
"""Group multiple small data regions into a larger texture.
|
|
935
|
+
|
|
936
|
+
The algorithm is based on the article by Jukka Jylänki : "A Thousand Ways
|
|
937
|
+
to Pack the Bin - A Practical Approach to Two-Dimensional Rectangle Bin
|
|
938
|
+
Packing", February 27, 2010. More precisely, this is an implementation of
|
|
939
|
+
the Skyline Bottom-Left algorithm based on C++ sources provided by Jukka
|
|
940
|
+
Jylänki at: http://clb.demon.fi/files/RectangleBinPack/.
|
|
941
|
+
|
|
942
|
+
Parameters
|
|
943
|
+
----------
|
|
944
|
+
shape : tuple of int
|
|
945
|
+
Texture shape (optional).
|
|
946
|
+
dtype : numpy.dtype object
|
|
947
|
+
Texture starting data type (default: float32)
|
|
948
|
+
|
|
949
|
+
Notes
|
|
950
|
+
-----
|
|
951
|
+
This creates a 2D texture that holds 1D float32 data.
|
|
952
|
+
An example of simple access:
|
|
953
|
+
|
|
954
|
+
>>> atlas = TextureAtlas()
|
|
955
|
+
>>> bounds = atlas.get_free_region(20, 30)
|
|
956
|
+
>>> atlas.set_region(bounds, np.random.rand(20, 30).T)
|
|
957
|
+
"""
|
|
958
|
+
|
|
959
|
+
def __init__(self, shape=(1024, 1024), dtype=np.float32):
|
|
960
|
+
shape = np.array(shape, int)
|
|
961
|
+
assert shape.ndim == 1 and shape.size == 2
|
|
962
|
+
shape = tuple(2 ** (np.log2(shape) + 0.5).astype(int)) + (3,)
|
|
963
|
+
self._atlas_nodes = [(0, 0, shape[1])]
|
|
964
|
+
data = np.zeros(shape, dtype)
|
|
965
|
+
super(TextureAtlas, self).__init__(data, interpolation='linear',
|
|
966
|
+
wrapping='clamp_to_edge')
|
|
967
|
+
|
|
968
|
+
def get_free_region(self, width, height):
|
|
969
|
+
"""Get a free region of given size and allocate it
|
|
970
|
+
|
|
971
|
+
Parameters
|
|
972
|
+
----------
|
|
973
|
+
width : int
|
|
974
|
+
Width of region to allocate
|
|
975
|
+
height : int
|
|
976
|
+
Height of region to allocate
|
|
977
|
+
|
|
978
|
+
Returns
|
|
979
|
+
-------
|
|
980
|
+
bounds : tuple | None
|
|
981
|
+
A newly allocated region as (x, y, w, h) or None
|
|
982
|
+
(if failed).
|
|
983
|
+
"""
|
|
984
|
+
best_height = best_width = np.inf
|
|
985
|
+
best_index = -1
|
|
986
|
+
for i in range(len(self._atlas_nodes)):
|
|
987
|
+
y = self._fit(i, width, height)
|
|
988
|
+
if y >= 0:
|
|
989
|
+
node = self._atlas_nodes[i]
|
|
990
|
+
if (y+height < best_height or
|
|
991
|
+
(y+height == best_height and node[2] < best_width)):
|
|
992
|
+
best_height = y+height
|
|
993
|
+
best_index = i
|
|
994
|
+
best_width = node[2]
|
|
995
|
+
region = node[0], y, width, height
|
|
996
|
+
if best_index == -1:
|
|
997
|
+
return None
|
|
998
|
+
|
|
999
|
+
node = region[0], region[1] + height, width
|
|
1000
|
+
self._atlas_nodes.insert(best_index, node)
|
|
1001
|
+
i = best_index+1
|
|
1002
|
+
while i < len(self._atlas_nodes):
|
|
1003
|
+
node = self._atlas_nodes[i]
|
|
1004
|
+
prev_node = self._atlas_nodes[i-1]
|
|
1005
|
+
if node[0] < prev_node[0]+prev_node[2]:
|
|
1006
|
+
shrink = prev_node[0]+prev_node[2] - node[0]
|
|
1007
|
+
x, y, w = self._atlas_nodes[i]
|
|
1008
|
+
self._atlas_nodes[i] = x+shrink, y, w-shrink
|
|
1009
|
+
if self._atlas_nodes[i][2] <= 0:
|
|
1010
|
+
del self._atlas_nodes[i]
|
|
1011
|
+
i -= 1
|
|
1012
|
+
else:
|
|
1013
|
+
break
|
|
1014
|
+
else:
|
|
1015
|
+
break
|
|
1016
|
+
i += 1
|
|
1017
|
+
|
|
1018
|
+
# Merge nodes
|
|
1019
|
+
i = 0
|
|
1020
|
+
while i < len(self._atlas_nodes)-1:
|
|
1021
|
+
node = self._atlas_nodes[i]
|
|
1022
|
+
next_node = self._atlas_nodes[i+1]
|
|
1023
|
+
if node[1] == next_node[1]:
|
|
1024
|
+
self._atlas_nodes[i] = node[0], node[1], node[2]+next_node[2]
|
|
1025
|
+
del self._atlas_nodes[i+1]
|
|
1026
|
+
else:
|
|
1027
|
+
i += 1
|
|
1028
|
+
|
|
1029
|
+
return region
|
|
1030
|
+
|
|
1031
|
+
def _fit(self, index, width, height):
|
|
1032
|
+
"""Test if region (width, height) fit into self._atlas_nodes[index]"""
|
|
1033
|
+
node = self._atlas_nodes[index]
|
|
1034
|
+
x, y = node[0], node[1]
|
|
1035
|
+
width_left = width
|
|
1036
|
+
if x+width > self._shape[1]:
|
|
1037
|
+
return -1
|
|
1038
|
+
i = index
|
|
1039
|
+
while width_left > 0:
|
|
1040
|
+
node = self._atlas_nodes[i]
|
|
1041
|
+
y = max(y, node[1])
|
|
1042
|
+
if y+height > self._shape[0]:
|
|
1043
|
+
return -1
|
|
1044
|
+
width_left -= node[2]
|
|
1045
|
+
i += 1
|
|
1046
|
+
return y
|