1

一般来说,我是 Vispy 和 opengl 的新手。我已将 realtime_signals 演示改编为我的数据集。我使用的数据是非平稳的,通常呈现出某种趋势。因此,放大数据通常效果不佳,因为漂移值会超出窗口。

我正在尝试将沿 y 轴的 min-max 归一化添加到示例代码中。这样,无论我的缩放级别是什么,数据都应该保持在窗口的中心。但是,我的解决方案产生了我无法解释的故障。

from vispy import gloo
from vispy import app
import numpy as np
import math
import numpy.ma as ma



#Data
num_samples = 1000
num_features = 3
df_raw = np.reshape(1+(np.random.normal(size=num_samples*num_features, loc=[0], scale=[0.01])), [num_samples, num_features]).cumprod(0).astype('float32')
df_raw_std = 1+((df_raw-np.min(df_raw,0))*2)/(np.min(df_raw,0)-np.max(df_raw,0))

# Generate the signals as a (num_features, num_samples) array. 320x1000
y = df_raw_std.transpose()

# Signal 2D index of each vertex (row and col) and x-index (sample index
# within each signal).
index_col = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(np.arange(num_features), num_samples)].astype(np.float32)
y_flat = np.reshape(y,y.shape[0]*y.shape[1])
index_y_scaled_orig = np.c_[-1 + 2*(index_col[:,0] / num_samples),y_flat].astype(np.float32)
index_y_scaled = index_y_scaled_orig.copy()

index_min = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(1, num_samples*num_features)].astype(np.float32)
index_max = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(1, num_samples*num_features)].astype(np.float32)


#This is called once for each vertex
VERT_SHADER = """
#version 120
//scaling. Running minimum and maximum of visible time series
attribute float index_min;
attribute float index_max;

// y coordinate of the position.
attribute float y;

// row, and time index.
attribute vec2 index_col;

// 2D scaling factor (zooming).
uniform vec2 scale;
uniform vec2 num_features;

// Number of samples per signal.
uniform float num_samples;

// for fragment shader
varying vec2 v_index;

// Varying variables used for clipping in the fragment shader.
varying vec2 v_position;
varying vec4 v_ab;

void main() {
    float nrows = num_features.x;

    // Compute the x coordinate from the time index
    float x = -1 + 2 * (index_col.x / (num_samples-1));

    //0 is zoom from center. 1 is zoom on the right. -1 is zoom on the left. WE should map mouse x pos to this.
    float zoom_x_pos = 0.0;

    // RELATIVE LINE POSITION
    // =============================
    // Manipulate x/y position here?
    // =============================
    // vec2 position = vec2(x - (1 - 1 / scale.x)*zoom_x_pos,y); // DEACTIVATED SCALING, NICE PLOTS EMERGE
    vec2 position = vec2(x - (1 - 1 / scale.x)*zoom_x_pos,(y-index_min)/(index_max-index_min)); //SCALING, GLITCHY

    // SPREAD
    //does not scale the x pos, just the y pos by an equal amount per row
    float spread = 1;
    vec2 a = vec2(spread, spread/nrows);

    // LOCATION
    vec2 b = vec2(0, -1 + 2*(index_col.y+.5) / nrows);

    // COMBINE RELATIVE LINE POSITION + SPREAD + LOCATION
    gl_Position = vec4(a*scale*position+b, 0.0, 1.0);

    // WRAP UP
    v_index = index_col;
    // For clipping test in the fragment shader.
    v_position = gl_Position.xy;
    v_ab = vec4(a, b);
}
"""

FRAG_SHADER = """
#version 120
varying vec2 v_index;
varying vec2 v_position;
varying vec4 v_ab;
void main() {
    gl_FragColor = vec4(1., 1., 1., 1.);
    // Discard the fragments between the signals (emulate glMultiDrawArrays).

    if (fract(v_index.y) > 0.)
        discard;

    // Clipping test.
    vec2 test = abs((v_position.xy-v_ab.zw)/v_ab.xy);
    if ((test.x > 1) || (test.y > 1))
        discard;
}
"""


class Canvas(app.Canvas):
    def __init__(self):
        app.Canvas.__init__(self, title='Use your wheel to zoom!',
                            keys='interactive')
        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
        self.program['y'] = y.reshape(-1, 1)
        self.program['index_col'] = index_col
        self.program['scale'] = (1., 1.)
        self.program['num_features'] = (num_features, 1)
        self.program['num_samples'] = num_samples
        self.program['index_min'] = index_min[:,0].reshape(-1, 1)
        self.program['index_max'] = index_max[:,0].reshape(-1, 1)
        gloo.set_viewport(0, 0, *self.physical_size)

        self._timer = app.Timer('auto', connect=self.on_timer, start=True)

        gloo.set_state(clear_color='black', blend=True,
                       blend_func=('src_alpha', 'one_minus_src_alpha'))

        self.show()

    def on_resize(self, event):
        gloo.set_viewport(0, 0, *event.physical_size)

    def on_mouse_wheel(self, event):
        dx = np.sign(event.delta[1]) * .05
        scale_x, scale_y = self.program['scale']

        index_y_scaled[:,0] = index_y_scaled_orig[:,0] * scale_x
        index_y_scaled[:, 1] = index_y_scaled_orig[:, 1] * scale_x
        valid = ((index_y_scaled[:,0]>-1)*(index_y_scaled[:,0]<1))

        index_y_scaled_reshaped = (np.reshape(index_y_scaled[:, 1],[num_features,num_samples]))
        shown = ma.masked_array(index_y_scaled_reshaped, mask=np.logical_not(valid))
        runmin = np.array(np.min(shown, 1))
        runmax = np.array(np.max(shown, 1))
        index_min[:, 1] = np.repeat(runmin, num_samples)
        index_max[:, 1] = np.repeat(runmax, num_samples)

        print(scale_x)
        print(runmin)
        print(runmax)

        self.program['index_min'] = index_min[:,1].reshape(-1, 1)
        self.program['index_max'] = index_max[:,1].reshape(-1, 1)
        #print(self.program['print_position'])

        scale_x_new, scale_y_new = (scale_x * math.exp(1.0*dx),
                                    scale_y * math.exp(1.0*dx))
        #print(scale_x_new)
        self.program['scale'] = (max(1, scale_x_new), max(1, scale_y_new))
        self.update()

    def on_timer(self, event):
        """Add some data at the end of each signal (real-time signals)."""
        self.program['y'].set_data(y.ravel().astype(np.float32)) #(10920,)
        self.update()

    def on_draw(self, event):
        gloo.clear()
        self.program.draw('line_strip')

if __name__ == '__main__':
    c = Canvas()
    app.run()
  1. 我究竟做错了什么?我正在“估计”opengl 外部的可见线选择,并将比例校正参数传递给 opengl 管道。然而,我得到明显的视觉故障以及扭曲的线条
  2. 在 vispy 中有没有更聪明的方法来解决这个问题?也许是解决片段着色器中的标准化或通过相机技巧的一种方法?
4

1 回答 1

1

事实证明,这些故障来自于在 opengl 之外计算归一化参数的错误。我在下面发布了正确的代码 - 请注意,图表正在标准化,适应缩放值,

不过,我对我目前的解决方案并不满意。它需要仍然在 opengl 之外计算归一化参数,一旦我转向更高的数据量和许多线图进行可视化,可能会大大减慢执行速度。我知道在顶点着色器中不可能做到这一点,因为它只对单个顶点进行操作——归一化需要了解给定线图中所有其他顶点的相对位置。

然而,我想知道是否有可能实现归一化——这只是线对象的线性仿射比例和变换——例如在几何着色器中。我还是 opengl 的新手,但是如果我正确理解了管道,它将需要在其自己的顶点缓冲区原语中定义每个线图(在我的示例代码中为 3)。然后我可以在几何着色器中使用这些图元,使用 gl_in 上的循环遍历给定线图的顶点,计算整体最小值和最大值,然后平移和缩放 gl_in 中每个顶点的位置。

vispy可以做到这一点吗?我知道我可以定义一个几何着色器,但实际上我很难为每个线图创建单独的图元。在我的示例代码中,我认为我将所有 3 条线图视为一个原语。

from vispy import gloo
from vispy import app
import numpy as np
import math
import numpy.ma as ma


import matplotlib.pyplot as plt
import pandas as pd


#Data
num_samples = 10000
num_features = 3
df_raw = np.reshape(1+(np.random.normal(size=num_samples*num_features, loc=[0], scale=[0.01])), [num_samples, num_features]).cumprod(0).astype('float32')
df_raw_std = 1+((df_raw-np.min(df_raw,0))*2)/(np.min(df_raw,0)-np.max(df_raw,0))

# Generate the signals as a (num_features, num_samples) array. 320x1000
y = df_raw_std.transpose()

# Signal 2D index of each vertex (row and col) and x-index (sample index
# within each signal).
index_col = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(np.arange(num_features), num_samples)].astype(np.float32)
y_flat = y.flatten()

index_y_scaled_orig = np.c_[-1 + 2*(index_col[:,0] / num_samples),y_flat].astype(np.float32)
index_y_scaled = index_y_scaled_orig.copy()

index_min = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(1, num_samples*num_features)].astype(np.float32)
index_max = np.c_[np.tile(np.arange(num_samples), num_features),np.repeat(1, num_samples*num_features)].astype(np.float32)


#This is called once for each vertex
VERT_SHADER = """
#version 120
//scaling. Running minimum and maximum of visible time series
attribute float index_min;
attribute float index_max;

// y coordinate of the position.
attribute float y;

// row, and time index.
attribute vec2 index_col;

// 2D scaling factor (zooming).
uniform vec2 scale;
uniform vec2 num_features;

// Number of samples per signal.
uniform float num_samples;

// for fragment shader
varying vec2 v_index;

// Varying variables used for clipping in the fragment shader.
varying vec2 v_position;
varying vec4 v_ab;

void main() {
    float nrows = num_features.x;

    // Compute the x coordinate from the time index
    float x = -1 + 2 * (index_col.x / (num_samples-1));

    //0 is zoom from center. 1 is zoom on the right. -1 is zoom on the left. WE should map mouse x pos to this.
    float zoom_x_pos = 0.0;

    // RELATIVE LINE POSITION
    // =============================
    // Manipulate x/y position here?
    // =============================
    // vec2 position = vec2(x - (1 - 1 / scale.x)*zoom_x_pos,y); // DEACTIVATED SCALING, NICE PLOTS EMERGE
    vec2 position = vec2(x - (1 - 1 / scale.x)*zoom_x_pos,y); //SCALING, GLITCHY

    vec2 yscale_a = vec2(0., index_min);
    vec2 yscale_b = vec2(1., 2/(index_max-index_min));
    vec2 yscale_c = vec2(0., -1/nrows);


    // SPREAD
    //does not scale the x pos, just the y pos by an equal amount per row
    float spread = 1;
    vec2 a = vec2(spread, spread/nrows);

    // LOCATION
    vec2 b = vec2(0, -1 + 2*(index_col.y+.5) / nrows);

    // COMBINE RELATIVE LINE POSITION + SPREAD + LOCATION
    // gl_Position = vec4(a*(scale*position-yscale_a)*yscale_b+b, 0.0, 1.0);
    gl_Position = vec4(a*(scale*position-yscale_a)*yscale_b+b+yscale_c, 0.0, 1.0);



    // WRAP UP
    v_index = index_col;
    // For clipping test in the fragment shader.
    v_position = gl_Position.xy;
    v_ab = vec4(a, b);
}
"""

FRAG_SHADER = """
#version 120
varying vec2 v_index;
varying vec2 v_position;
varying vec4 v_ab;
void main() {
    gl_FragColor = vec4(1., 1., 1., 1.);
    // Discard the fragments between the signals (emulate glMultiDrawArrays).

    if (fract(v_index.y) > 0.)
        discard;

    // Clipping test.
    vec2 test = abs((v_position.xy-v_ab.zw)/v_ab.xy);
    if ((test.x > 1) || (test.y > 1))
        discard;
}
"""


class Canvas(app.Canvas):
    def __init__(self):
        app.Canvas.__init__(self, title='Use your wheel to zoom!',
                            keys='interactive')
        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
        self.program['y'] = y_flat
        self.program['index_col'] = index_col
        self.program['scale'] = (1., 1.)
        self.program['num_features'] = (num_features, 1)
        self.program['num_samples'] = num_samples
        self.program['index_min'] = index_min[:,0].flatten()
        self.program['index_max'] = index_max[:,0].flatten()
        gloo.set_viewport(0, 0, *self.physical_size)

        self._timer = app.Timer('auto', connect=self.on_timer, start=True)

        gloo.set_state(clear_color='black', blend=True,
                       blend_func=('src_alpha', 'one_minus_src_alpha'))

        self.show()

    def on_resize(self, event):
        gloo.set_viewport(0, 0, *event.physical_size)

    def on_mouse_wheel(self, event):
        dx = np.sign(event.delta[1]) * .05
        scale_x, scale_y = self.program['scale']

        index_y_scaled[:,0] = index_y_scaled_orig[:,0] * scale_x
        valid = ((index_y_scaled[:,0]>-1)*(index_y_scaled[:,0]<1))
        y_flat_scaled = y_flat * scale_x
        shown = ma.masked_array(y_flat_scaled, mask=np.logical_not(valid))
        shown_reshaped = (shown.reshape(num_features,num_samples))

        runmin = np.array(np.min(shown_reshaped, 1))
        runmax = np.array(np.max(shown_reshaped, 1))
        index_min[:, 1] = np.repeat(runmin, num_samples)
        index_max[:, 1] = np.repeat(runmax, num_samples)

        # scale_x = 10
        # scale_y = 10
        # print(scale_x)
        # print(scale_y)
        # print(runmin)
        # print(runmax)
        # forplot=(y_flat_scaled*valid).reshape(num_features,num_samples).transpose()
        # pd.DataFrame(forplot).plot(subplots=True)
        # forplot2 = (((y_flat_scaled * valid - index_min[:, 1])).flatten()).reshape([num_features, num_samples]).transpose()
        # pd.DataFrame(forplot2).plot(subplots=True)
        # forplot3 = (((y_flat_scaled * valid - index_min[:, 1]) / (index_max[:, 1] - index_min[:, 1])).flatten()).reshape([num_features, num_samples]).transpose()
        # pd.DataFrame(forplot3).plot(subplots=True)

        self.program['index_min'] = index_min[:,1].flatten()
        self.program['index_max'] = index_max[:,1].flatten()
        #print(self.program['print_position'])

        scale_x_new, scale_y_new = (scale_x * math.exp(1.0*dx),
                                    scale_y * math.exp(1.0*dx))
        #print(scale_x_new)
        self.program['scale'] = (max(1, scale_x_new), max(1, scale_y_new))
        self.update()

    def on_timer(self, event):
        """Add some data at the end of each signal (real-time signals)."""
        # y[:, :-1] = y[:, 1:]
        # y[:, -1:] = np.random.normal(size=3, loc=[0], scale=[0.01]).reshape(3, 1)

        self.program['y'].set_data(y.flatten().astype(np.float32)) #(10920,)
        self.update()

    def on_draw(self, event):
        gloo.clear()
        self.program.draw('line_strip')

if __name__ == '__main__':
    c = Canvas()
    app.run()
于 2020-02-13T14:02:54.750 回答