summaryrefslogtreecommitdiff
path: root/silx/gui/plot/backends/BackendOpenGL.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/backends/BackendOpenGL.py')
-rw-r--r--silx/gui/plot/backends/BackendOpenGL.py254
1 files changed, 157 insertions, 97 deletions
diff --git a/silx/gui/plot/backends/BackendOpenGL.py b/silx/gui/plot/backends/BackendOpenGL.py
index 3c18f4f..0001bb9 100644
--- a/silx/gui/plot/backends/BackendOpenGL.py
+++ b/silx/gui/plot/backends/BackendOpenGL.py
@@ -28,7 +28,7 @@ from __future__ import division
__authors__ = ["T. Vincent"]
__license__ = "MIT"
-__date__ = "16/08/2017"
+__date__ = "24/04/2018"
from collections import OrderedDict, namedtuple
from ctypes import c_void_p
@@ -38,8 +38,7 @@ import numpy
from .._utils import FLOAT32_MINPOS
from . import BackendBase
-from .. import Colors
-from ..Colormap import Colormap
+from ... import colors
from ... import qt
from ..._glutils import gl
@@ -355,7 +354,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
self._markers = OrderedDict()
self._items = OrderedDict()
self._plotContent = PlotDataContent() # For images and curves
- self._selectionAreas = OrderedDict()
self._glGarbageCollector = []
self._plotFrame = GLPlotFrame2D(
@@ -399,7 +397,7 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
previousMousePosInPixels = self._mousePosInPixels
self._mousePosInPixels = (xPixel, yPixel) if isCursorInPlot else None
if (self._crosshairCursor is not None and
- previousMousePosInPixels != self._crosshairCursor):
+ previousMousePosInPixels != self._mousePosInPixels):
# Avoid replot when cursor remains outside plot area
self._plot._setDirtyPlot(overlayOnly=True)
@@ -431,14 +429,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# OpenGLWidget API
- @staticmethod
- def _setBlendFuncGL():
- # gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
- gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA,
- gl.GL_ONE_MINUS_SRC_ALPHA,
- gl.GL_ONE,
- gl.GL_ONE)
-
def initializeGL(self):
gl.testGL()
@@ -446,7 +436,11 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
gl.glClearStencil(0)
gl.glEnable(gl.GL_BLEND)
- self._setBlendFuncGL()
+ # gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+ gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA,
+ gl.GL_ONE_MINUS_SRC_ALPHA,
+ gl.GL_ONE,
+ gl.GL_ONE)
# For lines
gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST)
@@ -500,7 +494,7 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
gl.glUniform1i(self._progTex.uniforms['tex'], texUnit)
gl.glUniformMatrix4fv(self._progTex.uniforms['matrix'], 1, gl.GL_TRUE,
- mat4Identity())
+ mat4Identity().astype(numpy.float32))
stride = self._plotVertices.shape[-1] * self._plotVertices.itemsize
gl.glEnableVertexAttribArray(self._progTex.attributes['position'])
@@ -649,24 +643,20 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
- isXLog = self._plotFrame.xAxis.isLog
- isYLog = self._plotFrame.yAxis.isLog
-
# Render in plot area
gl.glScissor(self._plotFrame.margins.left,
self._plotFrame.margins.bottom,
plotWidth, plotHeight)
gl.glEnable(gl.GL_SCISSOR_TEST)
- gl.glViewport(self._plotFrame.margins.left,
- self._plotFrame.margins.bottom,
- plotWidth, plotHeight)
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
# Prepare vertical and horizontal markers rendering
self._progBase.use()
- gl.glUniformMatrix4fv(self._progBase.uniforms['matrix'], 1, gl.GL_TRUE,
- self._plotFrame.transformedDataProjMat)
- gl.glUniform2i(self._progBase.uniforms['isLog'], isXLog, isYLog)
+ gl.glUniformMatrix4fv(
+ self._progBase.uniforms['matrix'], 1, gl.GL_TRUE,
+ self.matScreenProj.astype(numpy.float32))
+ gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
gl.glUniform1i(self._progBase.uniforms['hatchStep'], 0)
gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
posAttrib = self._progBase.attributes['position']
@@ -677,10 +667,12 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
for marker in self._markers.values():
xCoord, yCoord = marker['x'], marker['y']
- if ((isXLog and xCoord is not None and
- xCoord < FLOAT32_MINPOS) or
- (isYLog and yCoord is not None and
- yCoord < FLOAT32_MINPOS)):
+ if ((self._plotFrame.xAxis.isLog and
+ xCoord is not None and
+ xCoord <= 0) or
+ (self._plotFrame.yAxis.isLog and
+ yCoord is not None and
+ yCoord <= 0)):
# Do not render markers with negative coords on log axis
continue
@@ -706,9 +698,9 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
align=RIGHT, valign=BOTTOM)
labels.append(label)
- xMin, xMax = self._plotFrame.dataRanges.x
- vertices = numpy.array(((xMin, yCoord),
- (xMax, yCoord)),
+ width = self._plotFrame.size[0]
+ vertices = numpy.array(((0, pixelPos[1]),
+ (width, pixelPos[1])),
dtype=numpy.float32)
else: # yCoord is None: vertical line in data space
@@ -721,13 +713,12 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
align=LEFT, valign=TOP)
labels.append(label)
- yMin, yMax = self._plotFrame.dataRanges.y
- vertices = numpy.array(((xCoord, yMin),
- (xCoord, yMax)),
+ height = self._plotFrame.size[1]
+ vertices = numpy.array(((pixelPos[0], 0),
+ (pixelPos[0], height)),
dtype=numpy.float32)
self._progBase.use()
-
gl.glUniform4f(self._progBase.uniforms['color'],
*marker['color'])
@@ -759,13 +750,12 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# For now simple implementation: using a curve for each marker
# Should pack all markers to a single set of points
markerCurve = GLPlotCurve2D(
- numpy.array((xCoord,), dtype=numpy.float32),
- numpy.array((yCoord,), dtype=numpy.float32),
+ numpy.array((pixelPos[0],), dtype=numpy.float64),
+ numpy.array((pixelPos[1],), dtype=numpy.float64),
marker=marker['symbol'],
markerColor=marker['color'],
markerSize=11)
- markerCurve.render(self._plotFrame.transformedDataProjMat,
- isXLog, isYLog)
+ markerCurve.render(self.matScreenProj, False, False)
markerCurve.discard()
gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
@@ -777,8 +767,8 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
gl.glDisable(gl.GL_SCISSOR_TEST)
def _renderOverlayGL(self):
- # Render selection area and crosshair cursor
- if self._selectionAreas or self._crosshairCursor is not None:
+ # Render crosshair cursor
+ if self._crosshairCursor is not None:
plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
# Scissor to plot area
@@ -788,41 +778,21 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
gl.glEnable(gl.GL_SCISSOR_TEST)
self._progBase.use()
- gl.glUniform2i(self._progBase.uniforms['isLog'],
- self._plotFrame.xAxis.isLog,
- self._plotFrame.yAxis.isLog)
+ gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
posAttrib = self._progBase.attributes['position']
matrixUnif = self._progBase.uniforms['matrix']
colorUnif = self._progBase.uniforms['color']
hatchStepUnif = self._progBase.uniforms['hatchStep']
- # Render selection area in plot area
- if self._selectionAreas:
- gl.glViewport(self._plotFrame.margins.left,
- self._plotFrame.margins.bottom,
- plotWidth, plotHeight)
-
- gl.glUniformMatrix4fv(matrixUnif, 1, gl.GL_TRUE,
- self._plotFrame.transformedDataProjMat)
-
- for shape in self._selectionAreas.values():
- if shape.isVideoInverted:
- gl.glBlendFunc(gl.GL_ONE_MINUS_DST_COLOR, gl.GL_ZERO)
-
- shape.render(posAttrib, colorUnif, hatchStepUnif)
-
- if shape.isVideoInverted:
- self._setBlendFuncGL()
-
- # Render crosshair cursor is screen frame but with scissor
+ # Render crosshair cursor in screen frame but with scissor
if (self._crosshairCursor is not None and
self._mousePosInPixels is not None):
gl.glViewport(
0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
gl.glUniformMatrix4fv(matrixUnif, 1, gl.GL_TRUE,
- self.matScreenProj)
+ self.matScreenProj.astype(numpy.float32))
color, lineWidth = self._crosshairCursor
gl.glUniform4f(colorUnif, *color)
@@ -881,31 +851,30 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
isXLog, isYLog)
# Render Items
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+
self._progBase.use()
gl.glUniformMatrix4fv(self._progBase.uniforms['matrix'], 1, gl.GL_TRUE,
- self._plotFrame.transformedDataProjMat)
- gl.glUniform2i(self._progBase.uniforms['isLog'],
- self._plotFrame.xAxis.isLog,
- self._plotFrame.yAxis.isLog)
+ self.matScreenProj.astype(numpy.float32))
+ gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
for item in self._items.values():
- shape2D = item.get('_shape2D')
- if shape2D is None:
- closed = item['shape'] != 'polylines'
- shape2D = Shape2D(tuple(zip(item['x'], item['y'])),
- fill=item['fill'],
- fillColor=item['color'],
- stroke=True,
- strokeColor=item['color'],
- strokeClosed=closed)
- item['_shape2D'] = shape2D
-
- if ((isXLog and shape2D.xMin < FLOAT32_MINPOS) or
- (isYLog and shape2D.yMin < FLOAT32_MINPOS)):
+ if ((isXLog and numpy.min(item['x']) < FLOAT32_MINPOS) or
+ (isYLog and numpy.min(item['y']) < FLOAT32_MINPOS)):
# Ignore items <= 0. on log axes
continue
+ closed = item['shape'] != 'polylines'
+ points = [self.dataToPixel(x, y, axis='left', check=False)
+ for (x, y) in zip(item['x'], item['y'])]
+ shape2D = Shape2D(points,
+ fill=item['fill'],
+ fillColor=item['color'],
+ stroke=True,
+ strokeColor=item['color'],
+ strokeClosed=closed)
+
posAttrib = self._progBase.attributes['position']
colorUnif = self._progBase.uniforms['color']
hatchStepUnif = self._progBase.uniforms['hatchStep']
@@ -944,6 +913,21 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# Add methods
+ @staticmethod
+ def _castArrayTo(v):
+ """Returns best floating type to cast the array to.
+
+ :param numpy.ndarray v: Array to cast
+ :rtype: numpy.dtype
+ :raise ValueError: If dtype is not supported
+ """
+ if numpy.issubdtype(v.dtype, numpy.floating):
+ return numpy.float32 if v.itemsize <= 4 else numpy.float64
+ elif numpy.issubdtype(v.dtype, numpy.integer):
+ return numpy.float32 if v.itemsize <= 2 else numpy.float64
+ else:
+ raise ValueError('Unsupported data type')
+
def addCurve(self, x, y, legend,
color, symbol, linewidth, linestyle,
yaxis,
@@ -954,8 +938,21 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
assert parameter is not None
assert yaxis in ('left', 'right')
- x = numpy.array(x, dtype=numpy.float32, copy=False, order='C')
- y = numpy.array(y, dtype=numpy.float32, copy=False, order='C')
+ # Convert input data
+ x = numpy.array(x, copy=False)
+ y = numpy.array(y, copy=False)
+
+ # Check if float32 is enough
+ if (self._castArrayTo(x) is numpy.float32 and
+ self._castArrayTo(y) is numpy.float32):
+ dtype = numpy.float32
+ else:
+ dtype = numpy.float64
+
+ x = numpy.array(x, dtype=dtype, copy=False, order='C')
+ y = numpy.array(y, dtype=dtype, copy=False, order='C')
+
+ # Convert errors to float32
if xerror is not None:
xerror = numpy.array(
xerror, dtype=numpy.float32, copy=False, order='C')
@@ -963,6 +960,47 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
yerror = numpy.array(
yerror, dtype=numpy.float32, copy=False, order='C')
+ # Handle axes log scale: convert data
+
+ if self._plotFrame.xAxis.isLog:
+ logX = numpy.log10(x)
+
+ if xerror is not None:
+ # Transform xerror so that
+ # log10(x) +/- xerror' = log10(x +/- xerror)
+ if hasattr(xerror, 'shape') and len(xerror.shape) == 2:
+ xErrorMinus, xErrorPlus = xerror[0], xerror[1]
+ else:
+ xErrorMinus, xErrorPlus = xerror, xerror
+ xErrorMinus = logX - numpy.log10(x - xErrorMinus)
+ xErrorPlus = numpy.log10(x + xErrorPlus) - logX
+ xerror = numpy.array((xErrorMinus, xErrorPlus),
+ dtype=numpy.float32)
+
+ x = logX
+
+ isYLog = (yaxis == 'left' and self._plotFrame.yAxis.isLog) or (
+ yaxis == 'right' and self._plotFrame.y2Axis.isLog)
+
+ if isYLog:
+ logY = numpy.log10(y)
+
+ if yerror is not None:
+ # Transform yerror so that
+ # log10(y) +/- yerror' = log10(y +/- yerror)
+ if hasattr(yerror, 'shape') and len(yerror.shape) == 2:
+ yErrorMinus, yErrorPlus = yerror[0], yerror[1]
+ else:
+ yErrorMinus, yErrorPlus = yerror, yerror
+ yErrorMinus = logY - numpy.log10(y - yErrorMinus)
+ yErrorPlus = numpy.log10(y + yErrorPlus) - logY
+ yerror = numpy.array((yErrorMinus, yErrorPlus),
+ dtype=numpy.float32)
+
+ y = logY
+
+ # TODO check if need more filtering of error (e.g., clip to positive)
+
# TODO check and improve this
if (len(color) == 4 and
type(color[3]) in [type(1), numpy.uint8, numpy.int8]):
@@ -973,7 +1011,7 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
color = None
else:
colorArray = None
- color = Colors.rgba(color)
+ color = colors.rgba(color)
if alpha < 1.: # Apply image transparency
if colorArray is not None and colorArray.shape[1] == 4:
@@ -995,7 +1033,8 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
marker=symbol,
markerColor=color,
markerSize=symbolsize,
- fillColor=color if fill else None)
+ fillColor=color if fill else None,
+ isYLog=isYLog)
curve.info = {
'legend': legend,
'zOrder': z,
@@ -1054,7 +1093,13 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
elif len(data.shape) == 3:
# For RGB, RGBA data
assert data.shape[2] in (3, 4)
- assert data.dtype in (numpy.float32, numpy.uint8)
+
+ if numpy.issubdtype(data.dtype, numpy.floating):
+ data = numpy.array(data, dtype=numpy.float32, copy=False)
+ elif numpy.issubdtype(data.dtype, numpy.integer):
+ data = numpy.array(data, dtype=numpy.uint8, copy=False)
+ else:
+ raise ValueError('Unsupported data type')
image = GLPlotRGBAImage(data, origin, scale, alpha)
@@ -1106,7 +1151,7 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
self._items[legend] = {
'shape': shape,
- 'color': Colors.rgba(color),
+ 'color': colors.rgba(color),
'fill': 'hatch' if fill else None,
'x': x,
'y': y
@@ -1133,19 +1178,12 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
if isConstraint:
x, y = constraint(x, y)
- if x is not None and self._plotFrame.xAxis.isLog and x <= 0.:
- raise RuntimeError(
- 'Cannot add marker with X <= 0 with X axis log scale')
- if y is not None and self._plotFrame.yAxis.isLog and y <= 0.:
- raise RuntimeError(
- 'Cannot add marker with Y <= 0 with Y axis log scale')
-
self._markers[legend] = {
'x': x,
'y': y,
'legend': legend,
'text': text,
- 'color': Colors.rgba(color),
+ 'color': colors.rgba(color),
'behaviors': behaviors,
'constraint': constraint if isConstraint else None,
'symbol': symbol,
@@ -1204,7 +1242,7 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
"BackendOpenGL.setGraphCursor linestyle parameter ignored")
if flag:
- color = Colors.rgba(color)
+ color = colors.rgba(color)
crosshairCursor = color, linewidth
else:
crosshairCursor = None
@@ -1304,6 +1342,16 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
else:
yPickMin, yPickMax = yPick1, yPick0
+ # Apply log scale if axis is log
+ if self._plotFrame.xAxis.isLog:
+ xPickMin = numpy.log10(xPickMin)
+ xPickMax = numpy.log10(xPickMax)
+
+ if (yAxis == 'left' and self._plotFrame.yAxis.isLog) or (
+ yAxis == 'right' and self._plotFrame.y2Axis.isLog):
+ yPickMin = numpy.log10(yPickMin)
+ yPickMax = numpy.log10(yPickMax)
+
pickedIndices = item.pick(xPickMin, yPickMin,
xPickMax, yPickMax)
if pickedIndices:
@@ -1548,6 +1596,18 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# Graph axes
+ def getXAxisTimeZone(self):
+ return self._plotFrame.xAxis.timeZone
+
+ def setXAxisTimeZone(self, tz):
+ self._plotFrame.xAxis.timeZone = tz
+
+ def isXAxisTimeSeries(self):
+ return self._plotFrame.xAxis.isTimeSeries
+
+ def setXAxisTimeSeries(self, isTimeSeries):
+ self._plotFrame.xAxis.isTimeSeries = isTimeSeries
+
def setXAxisLogarithmic(self, flag):
if flag != self._plotFrame.xAxis.isLog:
if flag and self._keepDataAspectRatio:
@@ -1657,4 +1717,4 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
def setAxesDisplayed(self, displayed):
BackendBase.BackendBase.setAxesDisplayed(self, displayed)
- self._plotFrame.displayed = displayed \ No newline at end of file
+ self._plotFrame.displayed = displayed