7

I am looking for a way to make my surface plot item to change color base on height. Below is my current method:

def __init__(self, s):

    self.traces = dict()
    self.app = QtGui.QApplication(sys.argv)
    self.w = gl.GLViewWidget()
    self.w.opts['distance'] = 2000
    self.w.setWindowTitle('pyqtgraph example: GLLinePlotItem')
    self.w.setGeometry(0, 0, 600, 600)
    self.w.show()
    self.socket = s

    self.timer = QtCore.QTimer()
    self.timer.setInterval(1) # in milliseconds
    self.timer.start()
    self.timer.timeout.connect(self.onNewData)

    # create the background grids
    #gx is the y grid
    #gz is the x gid
    gx = gl.GLGridItem()
    gx.rotate(90, 0, 1, 0)
    gx.translate(0, 0, 0)
    self.w.addItem(gx)
    gz = gl.GLGridItem()
    gz.translate(200, 0, -500)
    self.w.addItem(gz)
    gx.scale(100, 10, 100)
    gz.scale(20, 10, 100)


    self.y = np.linspace(0, 100, 10)
    self.x = np.linspace(60,400, 708)
    temp_z = np.zeros((10,708))
    self.surf = gl.GLSurfacePlotItem(x=self.y, y=self.x, z=temp_z, shader='heightColor',
                                     computeNormals=False, smooth=False)
    self.surf.scale(3,1,1)
    self.surf.shader()['colorMap'] = np.array([0.7, 2, 0.5, 0.2, 0.7, 0.7, 0.2, 0, 2])
    self.w.addItem(self.surf)

But the method is not working out quiet well. As Z values get very high, the surface become completely white. Btw, I have no idea of what I am doing with colormap, i just took it off the example.

Tim Wang
  • 71
  • 1
  • 2

2 Answers2

5

I suggest you to use the colors option of GLSurfacePlotItem. The idea is to compute colors that are associated with the z values of the surface (the heigths) an make them normalize (between 0 and 1). With this, you can compute a color for each point of the surface with cmap of matlotlib for instance.

# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import matplotlib.pyplot as plt
import numpy as np
import os
from PyQt4.QtGui import QFileDialog
import sys

if not( 'app' in locals()):
    app = QtGui.QApplication([])

traces = dict()
# app = QtGui.QApplication(sys.argv)
w = gl.GLViewWidget()
w.opts['distance'] = 2000
w.setWindowTitle('pyqtgraph example: GLLinePlotItem')
w.setGeometry(0, 0, 600, 600)
w.show()
# socket = s

# timer = QtCore.QTimer()
# timer.setInterval(1) # in milliseconds
# timer.start()
# timer.timeout.connect(onNewData)

# create the background grids
#gx is the y grid
#gz is the x gid
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)
gx.translate(0, 0, 0)
w.addItem(gx)
gz = gl.GLGridItem()
gz.translate(200, 0, -500)
w.addItem(gz)
gx.scale(100, 10, 100)
gz.scale(20, 10, 100)


y = np.linspace(0, 100, 10)
print(y)
x = np.linspace(0,100, 10)
print(x)
temp_z = np.random.rand(len(x),len(y))*100.

cmap = plt.get_cmap('jet')

minZ=np.min(temp_z)
maxZ=np.max(temp_z)
rgba_img = cmap((temp_z-minZ)/(maxZ -minZ))


surf = gl.GLSurfacePlotItem(x=y, y=x, z=temp_z, colors = rgba_img )

surf.scale(3,1,1)
# surf.shader()['colorMap'] = np.array(list(np.linspace(-100, 100, 1000)))
w.addItem(surf)

if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

which give :

enter image description here

ymmx
  • 4,769
  • 5
  • 32
  • 64
1

The correct way to do this is indeed using the 'heightMap' shader.

When using the color option of the GLSurfacePlotItem, the colors are smoothly interpolated between vertices (gridpoints). This is OK, as long as the vertices are not too vertically distant from one another, as the interpolated color will likely coincide with the colormap's actual color. This can be seen in the image below, where the left side is plotted using a colormap, and the right side is plotted using a heightMap shader:

smooth surface

However, as soon as there are large jumps in the data you will start to see interpolation artefacts, as in the image below (most visible as the greenish hue which shows up in some places). This is not an issue when using the shader, and the heighmap will be correctly colored.

random surface

Unfortunately, achieving this with the shader is not trivial. The heightmap shader has a strange way of specifying its colors. Accoring to a comment in the code :

red = pow(colorMap[0]*(z + colorMap[1]), colorMap[2])
green = pow(colorMap[3]*(z + colorMap[4]), colorMap[5])
blue = pow(colorMap[6]*(z + colorMap[7]), colorMap[8])

Getting this to match any existing colormaps is a bit of a nightmare. Here are a few approximations I have managed to figure out :

def default_scaled(z):
    '''
    scales the default shader to z: 
    - lowest will be black
    - mid will be orange-brown
    - highest will be white
    '''
    z_min, z_range = z.min(), z.ptp()
    return [
        2/z_range, -z_min - .00*z_range, 1,  # blue channel
        2/z_range, -z_min - .25*z_range, 1,  # green channel
        2/z_range, -z_min - .50*z_range, 1,  # red channel
    ]


def grayscale(z):
    '''lowest values are black, highest values are white'''
    z_min, z_range = z.min(), z.ptp()
    return 3*[1/z_range, -z_min, 1]  # all channels are the same


def plasma(z):
    '''poor approximation of the `plasma` colormap'''
    z_min, z_range = z.min(), z.ptp()
    return [
        2/z_range, -z_min, 1,  # red channel
        2/z_range, -(z_min+0.5*z_range), 1,  # green channel
        -2/z_range, -(z_min+z_range), 1,  # blue channel
    ]


def cet_l4(z):
    '''poor approximation of the `CET-L4` colormap'''
    z_min, z_range = z.min(), z.ptp()
    return [
        1.5/z_range, -z_min, 0.7,  # red channel
        1.7/z_range, -(z_min+0.4*z_range), 1,  # green channel
        0, 0, 1,  # blue channel is empty
    ]

These can then be applied as follows :

import numpy as np

import pyqtgraph as pg
import pyqtgraph.opengl as gl

# prepare data
x = np.linspace(-10, 10, 21)
y = np.linspace(0, 10, 11)
z = np.random.rand(len(x), len(y))*10-5

# make app & viewer
app = pg.mkQApp()
w = gl.GLViewWidget()

# create grid
gx = gl.GLGridItem()
gx.rotate(90, 1, 0, 0)
w.addItem(gx)

# create the shaded surface
surf1 = gl.GLSurfacePlotItem(x, y, z)
surf1.setShader('heightColor')
surf1.shader()['colorMap'] = cet_l4(z)
w.addItem(surf1)

# create the colored surface
cmap = pg.colormap.get('CET-L4')
c = cmap.map((z-z.min())/z.ptp(), cmap.FLOAT)
surf2 = gl.GLSurfacePlotItem(x, y, z, c)
surf2.translate(0, -10, 0)
w.addItem(surf2)

w.show()
pg.exec()
Hoodlum
  • 950
  • 2
  • 13