summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d')
-rw-r--r--silx/gui/plot3d/ScalarFieldView.py6
-rw-r--r--silx/gui/plot3d/items/_pick.py4
-rw-r--r--silx/gui/plot3d/items/core.py54
-rw-r--r--silx/gui/plot3d/items/mixins.py1
-rw-r--r--silx/gui/plot3d/items/volume.py2
-rw-r--r--silx/gui/plot3d/scene/cutplane.py4
-rw-r--r--silx/gui/plot3d/scene/function.py75
-rw-r--r--silx/gui/plot3d/scene/primitives.py10
-rw-r--r--silx/gui/plot3d/scene/text.py3
-rw-r--r--silx/gui/plot3d/scene/transform.py65
-rw-r--r--silx/gui/plot3d/scene/utils.py4
-rw-r--r--silx/gui/plot3d/test/testStatsWidget.py3
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()