Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

PyQtGraph:3Dプロット (GLSurfacePlotItem, GLMeshItem) の高さ(z軸)で色を変える方法

PyQtGraph の3Dサーフェス/メッシュプロットでは,高さに応じて色を変えることができます

具体的には,GLSurfacePlotItem もしくは GLMeshItem のコンストラクタのキーワード引数 としてshader='heightColor'を指定します.

n_x, n_y = 128, 128
xy_init  = 1.0, 4.0
x_range  = -10, 10
y_range  = -10, 10
x        = np.linspace(x_range[0], x_range[1], n_x)
y        = np.linspace(y_range[0], y_range[1], n_y)

rad = 0.5
x_grid, y_grid     = np.meshgrid(y, x)
z = 3 * np.exp(-((x_grid-xy_init[0])**2.0)*rad**2) \
      * np.exp(-((y_grid-xy_init[1])**2.0)*rad**2)

p = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='heightColor')

f:id:Kurene:20210615000622p:plain:w400

ここで注意点として,GLSurfacePlotItem, GLMeshItem にはcolor, colors といったキーワード引数がありますが,shader='heightColor'を指定した場合,それらの引数による色の指定は無効になります。

代替方法として,p.shader()['colorMap']に1次元9要素の配列をセットすることで,3Dデータの高さ(z軸の値)に応じて色を制御できます

p.shader()['colorMap'] = np.array([
                    0.0, 0.0, 1.0,  # red
                    0.4, 1.5, 1.0,  # green
                    1.5, 1.5, 1.0])   # blue

この指定方法について調査したところ,pyqtgraph.opengl.shadersに以下のように書かれていました.

pyqtgraph.opengl.shaders.py

        ## colors fragments by z-value.
        ## This is useful for coloring surface plots by height.
        ## This shader uses a uniform called "colorMap" to determine how to map the colors:
        ##    red   = pow(z * colorMap[0] + colorMap[1], colorMap[2])
        ##    green = pow(z * colorMap[3] + colorMap[4], colorMap[5])
        ##    blue  = pow(z * colorMap[6] + colorMap[7], colorMap[8])
        ## (set the values like this: shader['uniformMap'] = array([...])
        ShaderProgram('heightColor', [
            VertexShader("""
                varying vec4 pos;
                void main() {
                    gl_FrontColor = gl_Color;
                    gl_BackColor = gl_Color;
                    pos = gl_Vertex;
                    gl_Position = ftransform();
                }
            """),
            FragmentShader("""
                uniform float colorMap[9];
                varying vec4 pos;
                //out vec4 gl_FragColor;   // only needed for later glsl versions
                //in vec4 gl_Color;
                void main() {
                    vec4 color = gl_Color;
                    color.x = colorMap[0] * (pos.z + colorMap[1]);
                    if (colorMap[2] != 1.0)
                        color.x = pow(color.x, colorMap[2]);
                    color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x);
                    
                    color.y = colorMap[3] * (pos.z + colorMap[4]);
                    if (colorMap[5] != 1.0)
                        color.y = pow(color.y, colorMap[5]);
                    color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y);
                    
                    color.z = colorMap[6] * (pos.z + colorMap[7]);
                    if (colorMap[8] != 1.0)
                        color.z = pow(color.z, colorMap[8]);
                    color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z);
                    
                    color.w = 1.0;
                    gl_FragColor = color;
                }
            """),
        ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}),

すなわち、与えた z,つまり高さの値に対して,

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

となります。ただし,それぞれ最終的に 0.0未満だと0.0に、1.0以上だと1.0に設定されます。

従って、最終的にred, green, blueの値は0.0~1.0の範囲の値となります。

つまり,プロットするzの値の範囲に合わせて colorMapを設定すればOKです

例えば,緑色単色で,zの値の範囲にGreenの0~1の範囲を線形に割り当てるとすると,以下のようになります.

p.shader()['colorMap'] = np.array([
                    0.0, 0.0, 1.0,  # red
                    1.0/(np.max(z)-np.min(z)), -np.min(z), 1.0, # green
                    0.0, 0.0, 1.0]) # blue

f:id:Kurene:20210615003247p:plain

ただ、今回のデータzは小さい値が多いため暗く見えます。そう言う場合は,例えば他の色を土台として使うと見やすくなるかもしれません。

p.shader()['colorMap'] = np.array([
                    0.0, 0.0, 1.0,  # red
                    1.0/(np.max(z)-np.min(z)), -np.min(z), 0.5, # green
                    1.0, np.max(z), 1.0]) # blue

f:id:Kurene:20210615005357p:plain


応用例として,リアルタイムプロットやアニメーションで色を変更することもできます。

f:id:Kurene:20210613222103g:plain

www.wizard-notes.com