diff options
Diffstat (limited to 'silx/gui/plot3d/scene')
-rw-r--r-- | silx/gui/plot3d/scene/cutplane.py | 4 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/function.py | 75 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/primitives.py | 10 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/text.py | 3 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/transform.py | 65 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/utils.py | 4 |
6 files changed, 103 insertions, 58 deletions
diff --git a/silx/gui/plot3d/scene/cutplane.py b/silx/gui/plot3d/scene/cutplane.py index 81c74c7..88147df 100644 --- a/silx/gui/plot3d/scene/cutplane.py +++ b/silx/gui/plot3d/scene/cutplane.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2016-2019 European Synchrotron Radiation Facility +# Copyright (c) 2016-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 @@ -88,7 +88,7 @@ class ColormapMesh3D(Geometry): float value = texture3D(data, vTexCoords).r; vec4 color = $colormapCall(value); - color.a = alpha; + color.a *= alpha; gl_FragColor = $lightingCall(color, vPosition, vNormal); diff --git a/silx/gui/plot3d/scene/function.py b/silx/gui/plot3d/scene/function.py index 69a24dd..2deb785 100644 --- a/silx/gui/plot3d/scene/function.py +++ b/silx/gui/plot3d/scene/function.py @@ -389,10 +389,13 @@ class Colormap(event.Notifier, ProgramFunction): 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 * @@ -421,7 +424,12 @@ class Colormap(event.Notifier, ProgramFunction): $discard - vec4 color = texture2D(cmap_texture, vec2(value, 0.5)); + 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; } """) @@ -458,9 +466,10 @@ class Colormap(event.Notifier, ProgramFunction): 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._update_texture = True + self._textureToDiscard = None if colormap is None: # default colormap @@ -468,7 +477,7 @@ class Colormap(event.Notifier, ProgramFunction): colormap[:] = numpy.arange(256, dtype=numpy.uint8)[:, numpy.newaxis] - # Set to param values through properties to go through asserts + # Set to values through properties to perform asserts and updates self.colormap = colormap self.norm = norm self.gamma = gamma @@ -491,10 +500,41 @@ class Colormap(event.Notifier, ProgramFunction): assert colormap.ndim == 2 assert colormap.shape[1] in (3, 4) self._colormap = colormap - self._update_texture = True + + 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. @@ -576,9 +616,6 @@ class Colormap(event.Notifier, ProgramFunction): """ 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'], @@ -607,23 +644,11 @@ 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.) + gl.glUniform4f(program.uniforms['nancolor'], *self._nancolor) 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 + if self._textureToDiscard is not None: + self._textureToDiscard.discard() + self._textureToDiscard = None + + self._texture.prepare() diff --git a/silx/gui/plot3d/scene/primitives.py b/silx/gui/plot3d/scene/primitives.py index 7db61e8..b4c8e26 100644 --- a/silx/gui/plot3d/scene/primitives.py +++ b/silx/gui/plot3d/scene/primitives.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2019 European Synchrotron Radiation Facility +# 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 @@ -49,7 +49,7 @@ from . import event from . import core from . import transform from . import utils -from .function import Colormap, Fog +from .function import Colormap _logger = logging.getLogger(__name__) @@ -367,7 +367,7 @@ class Geometry(core.Elem): min_ = numpy.nanmin(attribute, axis=0) max_ = numpy.nanmax(attribute, axis=0) else: - min_, max_ = numpy.zeros((2, attribute.shape[1]), dtype=numpy.float32) + min_, max_ = numpy.zeros((2, attribute.shape[1]), dtype=numpy.float32) toCopy = min(len(min_), 3-index) if toCopy != len(min_): @@ -2077,7 +2077,7 @@ class _Image(Geometry): self._update_texture = True # By updating the position rather than always using a unit square # we benefit from Geometry bounds handling - self.setAttribute('position', self._UNIT_SQUARE * self._data.shape[:2]) + self.setAttribute('position', self._UNIT_SQUARE * (self._data.shape[1], self._data.shape[0])) self.notify() def getData(self, copy=True): @@ -2188,7 +2188,7 @@ class _Image(Geometry): gl.glUniform1f(program.uniforms['alpha'], self._alpha) shape = self._data.shape - gl.glUniform2f(program.uniforms['dataScale'], 1./shape[0], 1./shape[1]) + gl.glUniform2f(program.uniforms['dataScale'], 1./shape[1], 1./shape[0]) gl.glUniform1i(program.uniforms['data'], self._texture.texUnit) diff --git a/silx/gui/plot3d/scene/text.py b/silx/gui/plot3d/scene/text.py index c2983d5..bacc2e6 100644 --- a/silx/gui/plot3d/scene/text.py +++ b/silx/gui/plot3d/scene/text.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2016-2017 European Synchrotron Radiation Facility +# Copyright (c) 2016-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 @@ -251,6 +251,7 @@ class Text2D(primitives.Geometry): minFilter=gl.GL_NEAREST, magFilter=gl.GL_NEAREST, wrap=gl.GL_CLAMP_TO_EDGE) + self._texture.prepare() self._dirtyAlign = True # To force update of offset if self._dirtyAlign: diff --git a/silx/gui/plot3d/scene/transform.py b/silx/gui/plot3d/scene/transform.py index 1b82397..43b739b 100644 --- a/silx/gui/plot3d/scene/transform.py +++ b/silx/gui/plot3d/scene/transform.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2018 European Synchrotron Radiation Facility +# 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 @@ -855,13 +855,13 @@ class _Projection(Transform): class Orthographic(_Projection): - """Orthographic (i.e., parallel) projection which keeps aspect ratio. + """Orthographic (i.e., parallel) projection which can keep aspect ratio. Clipping planes are adjusted to match the aspect ratio of - the :attr:`size` attribute. + the :attr:`size` attribute if :attr:`keepaspect` is True. - The left, right, bottom and top parameters defines the area which must - always remain visible. + In this case, the left, right, bottom and top parameters defines the area + which must always remain visible. Effective clipping planes are adjusted to keep the aspect ratio. :param float left: Coord of the left clipping plane. @@ -873,12 +873,15 @@ class Orthographic(_Projection): :param size: Viewport's size used to compute the aspect ratio (width, height). :type size: 2-tuple of float + :param bool keepaspect: + True (default) to keep aspect ratio, False otherwise. """ def __init__(self, left=0., right=1., bottom=1., top=0., near=-1., far=1., - size=(1., 1.)): + size=(1., 1.), keepaspect=True): self._left, self._right = left, right self._bottom, self._top = bottom, top + self._keepaspect = bool(keepaspect) super(Orthographic, self).__init__(near, far, checkDepthExtent=False, size=size) # _update called when setting size @@ -888,22 +891,23 @@ class Orthographic(_Projection): self.left, self.right, self.bottom, self.top, self.near, self.far) def _update(self, left, right, bottom, top): - width, height = self.size - aspect = width / height + if self.keepaspect: + width, height = self.size + aspect = width / height - orthoaspect = abs(left - right) / abs(bottom - top) + orthoaspect = abs(left - right) / abs(bottom - top) - if orthoaspect >= aspect: # Keep width, enlarge height - newheight = \ - numpy.sign(top - bottom) * abs(left - right) / aspect - bottom = 0.5 * (bottom + top) - 0.5 * newheight - top = bottom + newheight + if orthoaspect >= aspect: # Keep width, enlarge height + newheight = \ + numpy.sign(top - bottom) * abs(left - right) / aspect + bottom = 0.5 * (bottom + top) - 0.5 * newheight + top = bottom + newheight - else: # Keep height, enlarge width - newwidth = \ - numpy.sign(right - left) * abs(bottom - top) * aspect - left = 0.5 * (left + right) - 0.5 * newwidth - right = left + newwidth + else: # Keep height, enlarge width + newwidth = \ + numpy.sign(right - left) * abs(bottom - top) * aspect + left = 0.5 * (left + right) - 0.5 * newwidth + right = left + newwidth # Store values self._left, self._right = left, right @@ -942,15 +946,30 @@ class Orthographic(_Projection): @property def size(self): - """Viewport size as a 2-tuple of float (width, height) or None.""" + """Viewport size as a 2-tuple of float (width, height)""" return self._size @size.setter def size(self, size): assert len(size) == 2 - self._size = float(size[0]), float(size[1]) - self._update(self.left, self.right, self.bottom, self.top) - self.notify() + size = float(size[0]), float(size[1]) + if size != self._size: + self._size = size + self._update(self.left, self.right, self.bottom, self.top) + self.notify() + + @property + def keepaspect(self): + """True to keep aspect ratio, False otherwise.""" + return self._keepaspect + + @keepaspect.setter + def keepaspect(self, aspect): + aspect = bool(aspect) + if aspect != self._keepaspect: + self._keepaspect = aspect + self._update(self.left, self.right, self.bottom, self.top) + self.notify() class Ortho2DWidget(_Projection): diff --git a/silx/gui/plot3d/scene/utils.py b/silx/gui/plot3d/scene/utils.py index bddbcac..c6cd129 100644 --- a/silx/gui/plot3d/scene/utils.py +++ b/silx/gui/plot3d/scene/utils.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2019 European Synchrotron Radiation Facility +# 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 @@ -540,7 +540,7 @@ def segmentVolumeIntersect(segment, nbins): # bin edges/line intersection points points = t.reshape(-1, 1) * delta + p0 centers = (points[:-1] + points[1:]) / 2. - bins = numpy.floor(centers).astype(numpy.int) + bins = numpy.floor(centers).astype(numpy.int64) return bins |