diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
commit | bfa4dba15485b4192f8bbe13345e9658c97ecf76 (patch) | |
tree | fb9c6e5860881fbde902f7cbdbd41dc4a3a9fb5d /silx/gui/plot3d/scene | |
parent | f7bdc2acff3c13a6d632c28c4569690ab106eed7 (diff) |
New upstream version 0.6.0+dfsg
Diffstat (limited to 'silx/gui/plot3d/scene')
-rw-r--r-- | silx/gui/plot3d/scene/axes.py | 19 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/function.py | 128 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/interaction.py | 29 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/primitives.py | 7 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/viewport.py | 15 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/window.py | 11 |
6 files changed, 134 insertions, 75 deletions
diff --git a/silx/gui/plot3d/scene/axes.py b/silx/gui/plot3d/scene/axes.py index 528e4f7..520ef3e 100644 --- a/silx/gui/plot3d/scene/axes.py +++ b/silx/gui/plot3d/scene/axes.py @@ -52,6 +52,8 @@ class LabelledAxes(primitives.GroupBBox): self._font = text.Font() + self._boxVisibility = True + # TODO offset labels from anchor in pixels self._xlabel = text.Text2D(font=self._font) @@ -145,6 +147,21 @@ class LabelledAxes(primitives.GroupBBox): def zlabel(self, text): self._zlabel.text = text + @property + def boxVisible(self): + """Returns bounding box, axes labels and grid visibility.""" + return self._boxVisibility + + @boxVisible.setter + def boxVisible(self, visible): + self._boxVisibility = bool(visible) + for child in self._children: + if child == self._tickLines: + if self._ticksForBounds is not None: + child.visible = self._boxVisibility + elif child != self._group: + child.visible = self._boxVisibility + def _updateTicks(self): """Check if ticks need update and update them if needed.""" bounds = self._group.bounds(transformed=False, dataBounds=True) @@ -187,7 +204,7 @@ class LabelledAxes(primitives.GroupBBox): zcoords[:, 3, 1] += ticklength[1] # Z ticks on YZ plane self._tickLines.setPositions(coords.reshape(-1, 3)) - self._tickLines.visible = True + self._tickLines.visible = self._boxVisibility # Update labels color = self.tickColor diff --git a/silx/gui/plot3d/scene/function.py b/silx/gui/plot3d/scene/function.py index 80ac820..73cdb72 100644 --- a/silx/gui/plot3d/scene/function.py +++ b/silx/gui/plot3d/scene/function.py @@ -35,6 +35,7 @@ import contextlib import logging import numpy +from ... import _glutils from ..._glutils import gl from . import event @@ -296,18 +297,10 @@ class DirectionalLight(event.Notifier, ProgramFunction): class Colormap(event.Notifier, ProgramFunction): # TODO use colors for out-of-bound values, for <=0 with log, for nan - # TODO texture-based colormap decl = """ - #define CMAP_GRAY 0 - #define CMAP_R_GRAY 1 - #define CMAP_RED 2 - #define CMAP_GREEN 3 - #define CMAP_BLUE 4 - #define CMAP_TEMP 5 - uniform struct { - int id; + sampler2D texture; bool isLog; float min; float oneOverRange; @@ -328,60 +321,24 @@ class Colormap(event.Notifier, ProgramFunction): value = clamp(cmap.oneOverRange * (value - cmap.min), 0.0, 1.0); } - if (cmap.id == CMAP_GRAY) { - return vec4(value, value, value, 1.0); - } - else if (cmap.id == CMAP_R_GRAY) { - float invValue = 1.0 - value; - return vec4(invValue, invValue, invValue, 1.0); - } - else if (cmap.id == CMAP_RED) { - return vec4(value, 0.0, 0.0, 1.0); - } - else if (cmap.id == CMAP_GREEN) { - return vec4(0.0, value, 0.0, 1.0); - } - else if (cmap.id == CMAP_BLUE) { - return vec4(0.0, 0.0, value, 1.0); - } - else if (cmap.id == CMAP_TEMP) { - //red: 0.5->0.75: 0->1 - //green: 0.->0.25: 0->1; 0.75->1.: 1->0 - //blue: 0.25->0.5: 1->0 - return vec4( - clamp(4.0 * value - 2.0, 0.0, 1.0), - 1.0 - clamp(4.0 * abs(value - 0.5) - 1.0, 0.0, 1.0), - 1.0 - clamp(4.0 * value - 1.0, 0.0, 1.0), - 1.0); - } - else { - /* Unknown colormap */ - return vec4(0.0, 0.0, 0.0, 1.0); - } + vec4 color = texture2D(cmap.texture, vec2(value, 0.5)); + return color; } """ call = "colormap" - _COLORMAPS = { - 'gray': 0, - 'reversed gray': 1, - 'red': 2, - 'green': 3, - 'blue': 4, - 'temperature': 5 - } - - COLORMAPS = tuple(_COLORMAPS.keys()) - """Tuple of supported colormap names.""" - NORMS = 'linear', 'log' """Tuple of supported normalizations.""" - def __init__(self, name='gray', norm='linear', range_=(1., 10.)): + _COLORMAP_TEXTURE_UNIT = 1 + """Texture unit to use for storing the colormap""" + + def __init__(self, colormap=None, norm='linear', range_=(1., 10.)): """Shader function to apply a colormap to a value. - :param str name: Name of the colormap. + :param colormap: RGB(A) color look-up table (default: gray) + :param colormap: numpy.ndarray of numpy.uint8 of dimension Nx3 or Nx4 :param str norm: Normalization to apply: 'linear' (default) or 'log'. :param range_: Range of value to map to the colormap. :type range_: 2-tuple of float (begin, end). @@ -389,24 +346,35 @@ class Colormap(event.Notifier, ProgramFunction): super(Colormap, self).__init__() # Init privates to default - self._name, self._norm, self._range = 'gray', 'linear', (1., 10.) + self._colormap, self._norm, self._range = None, 'linear', (1., 10.) + + self._texture = None + self._update_texture = True + + if colormap is None: + # default colormap + colormap = numpy.empty((256, 3), dtype=numpy.uint8) + colormap[:] = numpy.arange(256, + dtype=numpy.uint8)[:, numpy.newaxis] # Set to param values through properties to go through asserts - self.name = name + self.colormap = colormap self.norm = norm self.range_ = range_ @property - def name(self): - """Name of the colormap in use.""" - return self._name - - @name.setter - def name(self, name): - if name != self._name: - assert name in self.COLORMAPS - self._name = name - self.notify() + def colormap(self): + """Color look-up table to use.""" + return numpy.array(self._colormap, copy=True) + + @colormap.setter + def colormap(self, colormap): + colormap = numpy.array(colormap, copy=True) + assert colormap.ndim == 2 + assert colormap.shape[1] in (3, 4) + self._colormap = colormap + self._update_texture = True + self.notify() @property def norm(self): @@ -459,7 +427,15 @@ class Colormap(event.Notifier, ProgramFunction): :param GLProgram program: The program to set-up. It MUST be in use and using this function. """ - gl.glUniform1i(program.uniforms['cmap.id'], self._COLORMAPS[self.name]) + self.prepareGL2(context) # TODO see how to handle + + if self._texture is None: # No colormap + return + + self._texture.bind() + + gl.glUniform1i(program.uniforms['cmap.texture'], + self._texture.texUnit) gl.glUniform1i(program.uniforms['cmap.isLog'], self._norm == 'log') min_, max_ = self.range_ @@ -469,3 +445,23 @@ class Colormap(event.Notifier, ProgramFunction): gl.glUniform1f(program.uniforms['cmap.min'], min_) gl.glUniform1f(program.uniforms['cmap.oneOverRange'], (1. / (max_ - min_)) if max_ != min_ else 0.) + + def prepareGL2(self, context): + if self._texture is None or self._update_texture: + if self._texture is not None: + self._texture.discard() + + colormap = numpy.empty( + (16, self._colormap.shape[0], self._colormap.shape[1]), + dtype=self._colormap.dtype) + colormap[:] = self._colormap + + format_ = gl.GL_RGBA if colormap.shape[-1] == 4 else gl.GL_RGB + + self._texture = _glutils.Texture( + format_, colormap, format_, + texUnit=self._COLORMAP_TEXTURE_UNIT, + minFilter=gl.GL_NEAREST, + magFilter=gl.GL_NEAREST, + wrap=gl.GL_CLAMP_TO_EDGE) + self._update_texture = False diff --git a/silx/gui/plot3d/scene/interaction.py b/silx/gui/plot3d/scene/interaction.py index 68bfc13..2911b2c 100644 --- a/silx/gui/plot3d/scene/interaction.py +++ b/silx/gui/plot3d/scene/interaction.py @@ -440,6 +440,26 @@ class FocusManager(StateMachine): # CameraControl ############################################################### +class RotateCameraControl(FocusManager): + """Combine wheel and rotate state machine.""" + def __init__(self, viewport, + orbitAroundCenter=False, + mode='center', scaleTransform=None): + handlers = (CameraWheel(viewport, mode, scaleTransform), + CameraRotate(viewport, orbitAroundCenter, LEFT_BTN)) + super(RotateCameraControl, self).__init__(handlers) + + +class PanCameraControl(FocusManager): + """Combine wheel, selectPan and rotate state machine.""" + def __init__(self, viewport, + mode='center', scaleTransform=None, + selectCB=None): + handlers = (CameraWheel(viewport, mode, scaleTransform), + CameraSelectPan(viewport, LEFT_BTN, selectCB)) + super(PanCameraControl, self).__init__(handlers) + + class CameraControl(FocusManager): """Combine wheel, selectPan and rotate state machine.""" def __init__(self, viewport, @@ -650,3 +670,12 @@ class PanPlaneRotateCameraControl(FocusManager): orbitAroundCenter=False, button=RIGHT_BTN)) super(PanPlaneRotateCameraControl, self).__init__(handlers) + + +class PanPlaneZoomOnWheelControl(FocusManager): + """Combine zoom on wheel and pan plane state machines.""" + def __init__(self, viewport, plane, + mode='center', scaleTransform=None): + handlers = (CameraWheel(viewport, mode, scaleTransform), + PlanePan(viewport, plane, LEFT_BTN)) + super(PanPlaneZoomOnWheelControl, self).__init__(handlers) diff --git a/silx/gui/plot3d/scene/primitives.py b/silx/gui/plot3d/scene/primitives.py index ca2616a..fc38e09 100644 --- a/silx/gui/plot3d/scene/primitives.py +++ b/silx/gui/plot3d/scene/primitives.py @@ -292,8 +292,11 @@ class Geometry(core.Elem): self.__bounds = numpy.zeros((2, 3), dtype=numpy.float32) # Support vertex with to 2 to 4 coordinates positions = self._attributes['position'] - self.__bounds[0, :positions.shape[1]] = positions.min(axis=0)[:3] - self.__bounds[1, :positions.shape[1]] = positions.max(axis=0)[:3] + self.__bounds[0, :positions.shape[1]] = \ + numpy.nanmin(positions, axis=0)[:3] + self.__bounds[1, :positions.shape[1]] = \ + numpy.nanmax(positions, axis=0)[:3] + self.__bounds[numpy.isnan(self.__bounds)] = 0. # Avoid NaNs return self.__bounds.copy() def prepareGL2(self, ctx): diff --git a/silx/gui/plot3d/scene/viewport.py b/silx/gui/plot3d/scene/viewport.py index 83cda43..72e1ea3 100644 --- a/silx/gui/plot3d/scene/viewport.py +++ b/silx/gui/plot3d/scene/viewport.py @@ -314,6 +314,9 @@ class Viewport(event.Notifier): (e.g., if spanning behind the viewpoint with perspective projection). """ bounds = self.scene.bounds(transformed=True) + if bounds is None: + bounds = numpy.array(((0., 0., 0.), (1., 1., 1.)), + dtype=numpy.float32) bounds = self.camera.extrinsic.transformBounds(bounds) if isinstance(self.camera.intrinsic, transform.Perspective): @@ -337,7 +340,11 @@ class Viewport(event.Notifier): It updates the camera position and depth extent. Camera sight direction and up are not affected. """ - self.camera.resetCamera(self.scene.bounds(transformed=True)) + bounds = self.scene.bounds(transformed=True) + if bounds is None: + bounds = numpy.array(((0., 0., 0.), (1., 1., 1.)), + dtype=numpy.float32) + self.camera.resetCamera(bounds) def orbitCamera(self, direction, angle=1.): """Rotate the camera around center of the scene. @@ -347,6 +354,9 @@ class Viewport(event.Notifier): :param float angle: he angle in degrees of the rotation. """ bounds = self.scene.bounds(transformed=True) + if bounds is None: + bounds = numpy.array(((0., 0., 0.), (1., 1., 1.)), + dtype=numpy.float32) center = 0.5 * (bounds[0] + bounds[1]) self.camera.orbit(direction, center, angle) @@ -359,6 +369,9 @@ class Viewport(event.Notifier): :param float step: The ratio of data to step for each pan. """ bounds = self.scene.bounds(transformed=True) + if bounds is None: + bounds = numpy.array(((0., 0., 0.), (1., 1., 1.)), + dtype=numpy.float32) bounds = self.camera.extrinsic.transformBounds(bounds) center = 0.5 * (bounds[0] + bounds[1]) ndcCenter = self.camera.intrinsic.transformPoint( diff --git a/silx/gui/plot3d/scene/window.py b/silx/gui/plot3d/scene/window.py index ad7e6e5..3c63c7a 100644 --- a/silx/gui/plot3d/scene/window.py +++ b/silx/gui/plot3d/scene/window.py @@ -244,6 +244,7 @@ class Window(event.Notifier): void main(void) { gl_FragColor = texture2D(texture, textureCoord); + gl_FragColor.a = 1.0; } """) @@ -304,12 +305,11 @@ class Window(event.Notifier): self._viewports.removeListener(self._updated) self._viewports = event.NotifierList(iterable) self._viewports.addListener(self._updated) - self._dirty = True + self._updated(self) def _updated(self, source, *args, **kwargs): - if source is not self: - self._dirty = True - self.notify(*args, **kwargs) + self._dirty = True + self.notify(*args, **kwargs) framebufferid = property(lambda self: self._framebufferid, doc="Framebuffer ID used to perform rendering") @@ -323,11 +323,12 @@ class Window(event.Notifier): height, width = self.shape image = numpy.empty((height, width, 3), dtype=numpy.uint8) + previousFramebuffer = gl.glGetInteger(gl.GL_FRAMEBUFFER_BINDING) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.framebufferid) gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) gl.glReadPixels( 0, 0, width, height, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, image) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, previousFramebuffer) # glReadPixels gives bottom to top, # while images are stored as top to bottom |