diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2021-01-06 14:10:12 +0100 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2021-01-06 14:10:12 +0100 |
commit | b3bea947efa55d2c0f198b6c6795b3177be27f45 (patch) | |
tree | 4116758aafe4483bf472c1d54b519e685737fd77 /silx/gui/plot3d | |
parent | 5ad425ff4e62f5e003178813ebd073577679a00e (diff) |
New upstream version 0.14.0+dfsg
Diffstat (limited to 'silx/gui/plot3d')
-rw-r--r-- | silx/gui/plot3d/ScalarFieldView.py | 6 | ||||
-rw-r--r-- | silx/gui/plot3d/items/_pick.py | 4 | ||||
-rw-r--r-- | silx/gui/plot3d/items/core.py | 54 | ||||
-rw-r--r-- | silx/gui/plot3d/items/mixins.py | 1 | ||||
-rw-r--r-- | silx/gui/plot3d/items/volume.py | 2 | ||||
-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 | ||||
-rw-r--r-- | silx/gui/plot3d/test/testStatsWidget.py | 3 |
12 files changed, 140 insertions, 91 deletions
diff --git a/silx/gui/plot3d/ScalarFieldView.py b/silx/gui/plot3d/ScalarFieldView.py index 50cba05..b2bb254 100644 --- a/silx/gui/plot3d/ScalarFieldView.py +++ b/silx/gui/plot3d/ScalarFieldView.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 @@ -239,7 +239,7 @@ class SelectedRegion(object): def __init__(self, arrayRange, dataBBox, translation=(0., 0., 0.), scale=(1., 1., 1.)): - self._arrayRange = numpy.array(arrayRange, copy=True, dtype=numpy.int) + self._arrayRange = numpy.array(arrayRange, copy=True, dtype=numpy.int64) assert self._arrayRange.shape == (3, 2) assert numpy.all(self._arrayRange[:, 1] >= self._arrayRange[:, 0]) @@ -1449,7 +1449,7 @@ class ScalarFieldView(Plot3DWindow): min(self._data.shape[1], max(*yrange))), (max(0, min(*xrange_)), min(self._data.shape[2], max(*xrange_))), - ), dtype=numpy.int) + ), dtype=numpy.int64) # numpy.equal supports None if not numpy.all(numpy.equal(selectedRange, self._selectedRange)): diff --git a/silx/gui/plot3d/items/_pick.py b/silx/gui/plot3d/items/_pick.py index 8494723..0d6a495 100644 --- a/silx/gui/plot3d/items/_pick.py +++ b/silx/gui/plot3d/items/_pick.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2018-2019 European Synchrotron Radiation Facility +# Copyright (c) 2018-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 @@ -197,7 +197,7 @@ class PickingResult(_PickingResult): super(PickingResult, self).__init__(item, indices) self._objectPositions = numpy.array( - positions, copy=False, dtype=numpy.float) + positions, copy=False, dtype=numpy.float64) # Store matrices to generate positions on demand primitive = item._getScenePrimitive() diff --git a/silx/gui/plot3d/items/core.py b/silx/gui/plot3d/items/core.py index 1745b2b..ab2ceb6 100644 --- a/silx/gui/plot3d/items/core.py +++ b/silx/gui/plot3d/items/core.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-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 @@ -400,32 +400,32 @@ class DataItem3D(Item3D): self._updated(Item3DChangedType.TRANSFORM) def setRotationCenter(self, x=0., y=0., z=0.): - """Set the center of rotation of the item. - - Position of the rotation center is either a float - for an absolute position or one of the following - string to define a position relative to the item's bounding box: - 'lower', 'center', 'upper' - - :param x: rotation center position on the X axis - :rtype: float or str - :param y: rotation center position on the Y axis - :rtype: float or str - :param z: rotation center position on the Z axis - :rtype: float or str - """ - center = [] - for position in (x, y, z): - if isinstance(position, six.string_types): - assert position in self._ROTATION_CENTER_TAGS - else: - position = float(position) - center.append(position) - center = tuple(center) - - if center != self._rotationCenter: - self._rotationCenter = center - self._updateRotationCenter() + """Set the center of rotation of the item. + + Position of the rotation center is either a float + for an absolute position or one of the following + string to define a position relative to the item's bounding box: + 'lower', 'center', 'upper' + + :param x: rotation center position on the X axis + :rtype: float or str + :param y: rotation center position on the Y axis + :rtype: float or str + :param z: rotation center position on the Z axis + :rtype: float or str + """ + center = [] + for position in (x, y, z): + if isinstance(position, six.string_types): + assert position in self._ROTATION_CENTER_TAGS + else: + position = float(position) + center.append(position) + center = tuple(center) + + if center != self._rotationCenter: + self._rotationCenter = center + self._updateRotationCenter() def getRotationCenter(self): """Returns the rotation center set by :meth:`setRotationCenter`. diff --git a/silx/gui/plot3d/items/mixins.py b/silx/gui/plot3d/items/mixins.py index 14cafc8..f512365 100644 --- a/silx/gui/plot3d/items/mixins.py +++ b/silx/gui/plot3d/items/mixins.py @@ -141,6 +141,7 @@ class ColormapMixIn(_ColormapMixIn): self.__sceneColormap.norm = colormap.getNormalization() self.__sceneColormap.gamma = colormap.getGammaNormalizationParameter() self.__sceneColormap.range_ = colormap.getColormapRange(self) + self.__sceneColormap.nancolor = rgba(colormap.getNaNColor()) class ComplexMixIn(_ComplexMixIn): diff --git a/silx/gui/plot3d/items/volume.py b/silx/gui/plot3d/items/volume.py index 6c6562f..f80fea2 100644 --- a/silx/gui/plot3d/items/volume.py +++ b/silx/gui/plot3d/items/volume.py @@ -444,7 +444,7 @@ class Isosurface(Item3D): return None # No intersected triangles intersections = numpy.array(intersections)[numpy.argsort(depths)] - indices = numpy.transpose(numpy.round(intersections).astype(numpy.int)) + indices = numpy.transpose(numpy.round(intersections).astype(numpy.int64)) return PickingResult(self, positions=intersections, indices=indices) 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 diff --git a/silx/gui/plot3d/test/testStatsWidget.py b/silx/gui/plot3d/test/testStatsWidget.py index 1157aec..bcab1a4 100644 --- a/silx/gui/plot3d/test/testStatsWidget.py +++ b/silx/gui/plot3d/test/testStatsWidget.py @@ -34,6 +34,7 @@ import numpy from silx.utils.testutils import ParametricTestCase from silx.gui.utils.testutils import TestCaseQt +from silx.gui.plot.stats.stats import Stats from silx.gui import qt from silx.gui.plot.StatsWidget import BasicStatsWidget @@ -55,6 +56,7 @@ class TestSceneWidget(TestCaseQt, ParametricTestCase): # self.qWaitForWindowExposed(self.sceneWidget) def tearDown(self): + Stats._getContext.cache_clear() self.qapp.processEvents() self.sceneWidget.setAttribute(qt.Qt.WA_DeleteOnClose) self.sceneWidget.close() @@ -147,6 +149,7 @@ class TestScalarFieldView(TestCaseQt): # self.qWaitForWindowExposed(self.sceneWidget) def tearDown(self): + Stats._getContext.cache_clear() self.qapp.processEvents() self.scalarFieldView.setAttribute(qt.Qt.WA_DeleteOnClose) self.scalarFieldView.close() |