python - 在 Vispy 中标准化可缩放时间序列图的比例
问题描述
一般来说,我是 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()
- 我究竟做错了什么?我正在“估计”opengl 外部的可见线选择,并将比例校正参数传递给 opengl 管道。然而,我得到明显的视觉故障以及扭曲的线条
- 在 vispy 中有没有更聪明的方法来解决这个问题?也许是解决片段着色器中的标准化或通过相机技巧的一种方法?
解决方案
事实证明,这些故障来自于在 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()
推荐阅读
- r - 使用 ifelse 突变具有多个条件的新列时如何处理或忽略 NA(已解决)
- python - python绘图更改背景颜色和线条颜色
- c# - 计算使用 Linq 进行的尝试次数
- javascript - 如何发送 POST 请求以响应 node.js Express?
- css - 即使 div 改变大小并保持 div 居中,也可以修复 div 的顶部位置
- android - 如何从 Android Q 中的媒体商店获取专辑?
- django - Docker 连接错误“连接被 NewConnectionError 破坏”
- validation - Vee-validate - 仅在调用 $validator.validateAll() 时触发字段级别验证,但在触摸输入字段时不触发
- wordpress - 酒店预订插件 wordpress
- php - 在具有 4 个数据库连接设置的应用程序中仅使用 1 个连接时,数据库连接是否有效?