diff options
Diffstat (limited to 'silx/gui/plot3d/scene/function.py')
-rw-r--r-- | silx/gui/plot3d/scene/function.py | 654 |
1 files changed, 0 insertions, 654 deletions
diff --git a/silx/gui/plot3d/scene/function.py b/silx/gui/plot3d/scene/function.py deleted file mode 100644 index 2deb785..0000000 --- a/silx/gui/plot3d/scene/function.py +++ /dev/null @@ -1,654 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-2020 European Synchrotron Radiation Facility -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# ###########################################################################*/ -"""This module provides functions to add to shaders.""" - -from __future__ import absolute_import, division, unicode_literals - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "17/07/2018" - - -import contextlib -import logging -import string -import numpy - -from ... import _glutils -from ..._glutils import gl - -from . import event -from . import utils - - -_logger = logging.getLogger(__name__) - - -class ProgramFunction(object): - """Class providing a function to add to a GLProgram shaders. - """ - - def setupProgram(self, context, program): - """Sets-up uniforms of a program using this shader function. - - :param RenderContext context: The current rendering context - :param GLProgram program: The program to set-up. - It MUST be in use and using this function. - """ - pass - - -class Fog(event.Notifier, ProgramFunction): - """Linear fog over the whole scene content. - - The background of the viewport is used as fog color, - otherwise it defaults to white. - """ - # TODO: add more controls (set fog range), add more fog modes - - _fragDecl = """ - /* (1/(far - near) or 0, near) z in [0 (camera), -inf[ */ - uniform vec2 fogExtentInfo; - - /* Color to use as fog color */ - uniform vec3 fogColor; - - vec4 fog(vec4 color, vec4 cameraPosition) { - /* d = (pos - near) / (far - near) */ - float distance = fogExtentInfo.x * (cameraPosition.z/cameraPosition.w - fogExtentInfo.y); - float fogFactor = clamp(distance, 0.0, 1.0); - vec3 rgb = mix(color.rgb, fogColor, fogFactor); - return vec4(rgb.r, rgb.g, rgb.b, color.a); - } - """ - - _fragDeclNoop = """ - vec4 fog(vec4 color, vec4 cameraPosition) { - return color; - } - """ - - def __init__(self): - super(Fog, self).__init__() - self._isOn = True - - @property - def isOn(self): - """True to enable fog, False to disable (bool)""" - return self._isOn - - @isOn.setter - def isOn(self, isOn): - isOn = bool(isOn) - if self._isOn != isOn: - self._isOn = bool(isOn) - self.notify() - - @property - def fragDecl(self): - return self._fragDecl if self.isOn else self._fragDeclNoop - - @property - def fragCall(self): - return "fog" - - @staticmethod - def _zExtentCamera(viewport): - """Return (far, near) planes Z in camera coordinates. - - :param Viewport viewport: - :return: (far, near) position in camera coords (from 0 to -inf) - """ - # Provide scene z extent in camera coords - bounds = viewport.camera.extrinsic.transformBounds( - viewport.scene.bounds(transformed=True, dataBounds=True)) - return bounds[:, 2] - - def setupProgram(self, context, program): - if not self.isOn: - return - - far, near = context.cache(key='zExtentCamera', - factory=self._zExtentCamera, - viewport=context.viewport) - extent = far - near - gl.glUniform2f(program.uniforms['fogExtentInfo'], - 0.9/extent if extent != 0. else 0., - near) - - # Use background color as fog color - bgColor = context.viewport.background - if bgColor is None: - bgColor = 1., 1., 1. - gl.glUniform3f(program.uniforms['fogColor'], *bgColor[:3]) - - -class ClippingPlane(ProgramFunction): - """Description of a clipping plane and rendering. - - Convention: Clipping is performed in camera/eye space. - - :param point: Local coordinates of a point on the plane. - :type point: numpy.ndarray-like of 3 float32 - :param normal: Local coordinates of the plane normal. - :type normal: numpy.ndarray-like of 3 float32 - """ - - _fragDecl = """ - /* Clipping plane */ - /* as rx + gy + bz + a > 0, clipping all positive */ - uniform vec4 planeEq; - - /* Position is in camera/eye coordinates */ - - bool isClipped(vec4 position) { - vec4 tmp = planeEq * position; - float value = tmp.x + tmp.y + tmp.z + planeEq.a; - return (value < 0.0001); - } - - void clipping(vec4 position) { - if (isClipped(position)) { - discard; - } - } - /* End of clipping */ - """ - - _fragDeclNoop = """ - bool isClipped(vec4 position) - { - return false; - } - - void clipping(vec4 position) {} - """ - - def __init__(self, point=(0., 0., 0.), normal=(0., 0., 0.)): - self._plane = utils.Plane(point, normal) - - @property - def plane(self): - """Plane parameters in camera space.""" - return self._plane - - # GL2 - - @property - def fragDecl(self): - return self._fragDecl if self.plane.isPlane else self._fragDeclNoop - - @property - def fragCall(self): - return "clipping" - - def setupProgram(self, context, program): - """Sets-up uniforms of a program using this shader function. - - :param RenderContext context: The current rendering context - :param GLProgram program: The program to set-up. - It MUST be in use and using this function. - """ - if self.plane.isPlane: - gl.glUniform4f(program.uniforms['planeEq'], *self.plane.parameters) - - -class DirectionalLight(event.Notifier, ProgramFunction): - """Description of a directional Phong light. - - :param direction: The direction of the light or None to disable light - :type direction: ndarray of 3 floats or None - :param ambient: RGB ambient light - :type ambient: ndarray of 3 floats in [0, 1], default: (1., 1., 1.) - :param diffuse: RGB diffuse light parameter - :type diffuse: ndarray of 3 floats in [0, 1], default: (0., 0., 0.) - :param specular: RGB specular light parameter - :type specular: ndarray of 3 floats in [0, 1], default: (1., 1., 1.) - :param int shininess: The shininess of the material for specular term, - default: 0 which disables specular component. - """ - - fragmentShaderFunction = """ - /* Lighting */ - struct DLight { - vec3 lightDir; // Direction of light in object space - vec3 ambient; - vec3 diffuse; - vec3 specular; - float shininess; - vec3 viewPos; // Camera position in object space - }; - - uniform DLight dLight; - - vec4 lighting(vec4 color, vec3 position, vec3 normal) - { - normal = normalize(normal); - // 1-sided - float nDotL = max(0.0, dot(normal, - dLight.lightDir)); - - // 2-sided - //float nDotL = dot(normal, - dLight.lightDir); - //if (nDotL < 0.) { - // nDotL = - nDotL; - // normal = - normal; - //} - - float specFactor = 0.; - if (dLight.shininess > 0. && nDotL > 0.) { - vec3 reflection = reflect(dLight.lightDir, normal); - vec3 viewDir = normalize(dLight.viewPos - position); - specFactor = max(0.0, dot(reflection, viewDir)); - if (specFactor > 0.) { - specFactor = pow(specFactor, dLight.shininess); - } - } - - vec3 enlightedColor = color.rgb * (dLight.ambient + - dLight.diffuse * nDotL) + - dLight.specular * specFactor; - - return vec4(enlightedColor.rgb, color.a); - } - /* End of Lighting */ - """ - - fragmentShaderFunctionNoop = """ - vec4 lighting(vec4 color, vec3 position, vec3 normal) - { - return color; - } - """ - - def __init__(self, direction=None, - ambient=(1., 1., 1.), diffuse=(0., 0., 0.), - specular=(1., 1., 1.), shininess=0): - super(DirectionalLight, self).__init__() - self._direction = None - self.direction = direction # Set _direction - self._isOn = True - self._ambient = ambient - self._diffuse = diffuse - self._specular = specular - self._shininess = shininess - - ambient = event.notifyProperty('_ambient') - diffuse = event.notifyProperty('_diffuse') - specular = event.notifyProperty('_specular') - shininess = event.notifyProperty('_shininess') - - @property - def isOn(self): - """True if light is on, False otherwise.""" - return self._isOn and self._direction is not None - - @isOn.setter - def isOn(self, isOn): - self._isOn = bool(isOn) - - @contextlib.contextmanager - def turnOff(self): - """Context manager to temporary turn off lighting during rendering. - - >>> with light.turnOff(): - ... # Do some rendering without lighting - """ - wason = self._isOn - self._isOn = False - yield - self._isOn = wason - - @property - def direction(self): - """The direction of the light, or None if light is not on.""" - return self._direction - - @direction.setter - def direction(self, direction): - if direction is None: - self._direction = None - else: - assert len(direction) == 3 - direction = numpy.array(direction, dtype=numpy.float32, copy=True) - norm = numpy.linalg.norm(direction) - assert norm != 0 - self._direction = direction / norm - self.notify() - - # GL2 - - @property - def fragmentDef(self): - """Definition to add to fragment shader""" - if self.isOn: - return self.fragmentShaderFunction - else: - return self.fragmentShaderFunctionNoop - - @property - def fragmentCall(self): - """Function name to call in fragment shader""" - return "lighting" - - def setupProgram(self, context, program): - """Sets-up uniforms of a program using this shader function. - - :param RenderContext context: The current rendering context - :param GLProgram program: The program to set-up. - It MUST be in use and using this function. - """ - if self.isOn and self._direction is not None: - # Transform light direction from camera space to object coords - lightdir = context.objectToCamera.transformDir( - self._direction, direct=False) - lightdir /= numpy.linalg.norm(lightdir) - - gl.glUniform3f(program.uniforms['dLight.lightDir'], *lightdir) - - # Convert view position to object coords - viewpos = context.objectToCamera.transformPoint( - numpy.array((0., 0., 0., 1.), dtype=numpy.float32), - direct=False, - perspectiveDivide=True)[:3] - gl.glUniform3f(program.uniforms['dLight.viewPos'], *viewpos) - - gl.glUniform3f(program.uniforms['dLight.ambient'], *self.ambient) - gl.glUniform3f(program.uniforms['dLight.diffuse'], *self.diffuse) - gl.glUniform3f(program.uniforms['dLight.specular'], *self.specular) - gl.glUniform1f(program.uniforms['dLight.shininess'], - self.shininess) - - -class Colormap(event.Notifier, ProgramFunction): - - _declTemplate = string.Template(""" - uniform sampler2D cmap_texture; - uniform int cmap_normalization; - uniform float cmap_parameter; - uniform float cmap_min; - uniform float cmap_oneOverRange; - uniform vec4 nancolor; - - const float oneOverLog10 = 0.43429448190325176; - - vec4 colormap(float value) { - float data = value; /* Keep original input value for isnan test */ - - if (cmap_normalization == 1) { /* Log10 mapping */ - if (value > 0.0) { - value = clamp(cmap_oneOverRange * - (oneOverLog10 * log(value) - cmap_min), - 0.0, 1.0); - } else { - value = 0.0; - } - } else if (cmap_normalization == 2) { /* Sqrt mapping */ - if (value > 0.0) { - value = clamp(cmap_oneOverRange * (sqrt(value) - cmap_min), - 0.0, 1.0); - } else { - value = 0.0; - } - } else if (cmap_normalization == 3) { /*Gamma correction mapping*/ - value = pow( - clamp(cmap_oneOverRange * (value - cmap_min), 0.0, 1.0), - cmap_parameter); - } else if (cmap_normalization == 4) { /* arcsinh mapping */ - /* asinh = log(x + sqrt(x*x + 1) for compatibility with GLSL 1.20 */ - value = clamp(cmap_oneOverRange * (log(value + sqrt(value*value + 1.0)) - cmap_min), 0.0, 1.0); - } else { /* Linear mapping */ - value = clamp(cmap_oneOverRange * (value - cmap_min), 0.0, 1.0); - } - - $discard - - vec4 color; - if (data != data) { /* isnan alternative for compatibility with GLSL 1.20 */ - color = nancolor; - } else { - color = texture2D(cmap_texture, vec2(value, 0.5)); - } - return color; - } - """) - - _discardCode = """ - if (value == 0.) { - discard; - } - """ - - call = "colormap" - - NORMS = 'linear', 'log', 'sqrt', 'gamma', 'arcsinh' - """Tuple of supported normalizations.""" - - _COLORMAP_TEXTURE_UNIT = 1 - """Texture unit to use for storing the colormap""" - - def __init__(self, colormap=None, norm='linear', gamma=0., range_=(1., 10.)): - """Shader function to apply a colormap to a value. - - :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: see :attr:`NORMS`. - :param float gamma: Gamma normalization parameter - :param range_: Range of value to map to the colormap. - :type range_: 2-tuple of float (begin, end). - """ - super(Colormap, self).__init__() - - # Init privates to default - self._colormap = None - self._norm = 'linear' - self._gamma = -1. - self._range = 1., 10. - self._displayValuesBelowMin = True - self._nancolor = numpy.array((1., 1., 1., 0.), dtype=numpy.float32) - - self._texture = None - self._textureToDiscard = None - - 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 values through properties to perform asserts and updates - self.colormap = colormap - self.norm = norm - self.gamma = gamma - self.range_ = range_ - - @property - def decl(self): - """Source code of the function declaration""" - return self._declTemplate.substitute( - discard="" if self.displayValuesBelowMin else self._discardCode) - - @property - 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 - - if self._texture is not None and self._texture.name is not None: - self._textureToDiscard = self._texture - - data = numpy.empty( - (16, self._colormap.shape[0], self._colormap.shape[1]), - dtype=self._colormap.dtype) - data[:] = self._colormap - - format_ = gl.GL_RGBA if data.shape[-1] == 4 else gl.GL_RGB - - self._texture = _glutils.Texture( - format_, data, format_, - texUnit=self._COLORMAP_TEXTURE_UNIT, - minFilter=gl.GL_NEAREST, - magFilter=gl.GL_NEAREST, - wrap=gl.GL_CLAMP_TO_EDGE) - - self.notify() - - @property - def nancolor(self): - """RGBA color to use for Not-A-Number values as 4 float in [0., 1.]""" - return self._nancolor - - @nancolor.setter - def nancolor(self, color): - color = numpy.clip(numpy.array(color, dtype=numpy.float32), 0., 1.) - assert color.ndim == 1 - assert len(color) == 4 - if not numpy.array_equal(self._nancolor, color): - self._nancolor = color - self.notify() - - @property - def norm(self): - """Normalization to use for colormap mapping. - - One of 'linear' (the default), 'log' for log10 mapping or 'sqrt'. - Invalid values (e.g., negative values with 'log' or 'sqrt') are mapped to 0. - """ - return self._norm - - @norm.setter - def norm(self, norm): - if norm != self._norm: - assert norm in self.NORMS - self._norm = norm - if norm in ('log', 'sqrt'): - self.range_ = self.range_ # To test for positive range_ - self.notify() - - @property - def gamma(self): - """Gamma correction normalization parameter (float >= 0.)""" - return self._gamma - - @gamma.setter - def gamma(self, gamma): - if gamma != self._gamma: - assert gamma >= 0. - self._gamma = gamma - self.notify() - - @property - def range_(self): - """Range of values to map to the colormap. - - 2-tuple of floats: (begin, end). - The begin value is mapped to the origin of the colormap and the - end value is mapped to the other end of the colormap. - The colormap is reversed if begin > end. - """ - return self._range - - @range_.setter - def range_(self, range_): - assert len(range_) == 2 - range_ = float(range_[0]), float(range_[1]) - - if self.norm == 'log' and (range_[0] <= 0. or range_[1] <= 0.): - _logger.warning( - "Log normalization and negative range: updating range.") - minPos = numpy.finfo(numpy.float32).tiny - range_ = max(range_[0], minPos), max(range_[1], minPos) - elif self.norm == 'sqrt' and (range_[0] < 0. or range_[1] < 0.): - _logger.warning( - "Sqrt normalization and negative range: updating range.") - range_ = max(range_[0], 0.), max(range_[1], 0.) - - if range_ != self._range: - self._range = range_ - self.notify() - - @property - def displayValuesBelowMin(self): - """True to display values below colormap min, False to discard them. - """ - return self._displayValuesBelowMin - - @displayValuesBelowMin.setter - def displayValuesBelowMin(self, displayValuesBelowMin): - displayValuesBelowMin = bool(displayValuesBelowMin) - if self._displayValuesBelowMin != displayValuesBelowMin: - self._displayValuesBelowMin = displayValuesBelowMin - self.notify() - - def setupProgram(self, context, program): - """Sets-up uniforms of a program using this shader function. - - :param RenderContext context: The current rendering context - :param GLProgram program: The program to set-up. - It MUST be in use and using this function. - """ - self.prepareGL2(context) # TODO see how to handle - - self._texture.bind() - - gl.glUniform1i(program.uniforms['cmap_texture'], - self._texture.texUnit) - - min_, max_ = self.range_ - param = 0. - if self._norm == 'log': - min_, max_ = numpy.log10(min_), numpy.log10(max_) - normID = 1 - elif self._norm == 'sqrt': - min_, max_ = numpy.sqrt(min_), numpy.sqrt(max_) - normID = 2 - elif self._norm == 'gamma': - # Keep min_, max_ as is - param = self._gamma - normID = 3 - elif self._norm == 'arcsinh': - min_, max_ = numpy.arcsinh(min_), numpy.arcsinh(max_) - normID = 4 - else: # Linear - normID = 0 - - gl.glUniform1i(program.uniforms['cmap_normalization'], normID) - gl.glUniform1f(program.uniforms['cmap_parameter'], param) - gl.glUniform1f(program.uniforms['cmap_min'], min_) - gl.glUniform1f(program.uniforms['cmap_oneOverRange'], - (1. / (max_ - min_)) if max_ != min_ else 0.) - gl.glUniform4f(program.uniforms['nancolor'], *self._nancolor) - - def prepareGL2(self, context): - if self._textureToDiscard is not None: - self._textureToDiscard.discard() - self._textureToDiscard = None - - self._texture.prepare() |