使用VisPy绘制1D直方图异常排查及GPU内存数据直方图绘制、多直方图同图实现技术问询
一、异常窗口的原因及代码修复
你的代码里有几个关键问题导致窗口异常,我逐一拆解并给出修复方案:
无限重绘死循环
在on_draw方法中调用self.update()会触发新的绘制事件,让程序陷入无限重绘的循环,直接导致窗口卡顿、无响应甚至崩溃。解决方法很简单:删掉这行代码,on_draw只负责绘制内容,只有当数据或视图确实变化时才需要调用self.update()。不合理的直方图参数
你设置了bins=1,这意味着所有数据被塞进一个区间里,直方图只会显示一个巨大的柱子,不仅视觉上异常,还可能触发渲染逻辑的问题。建议根据数据范围设置合理的bin数,比如bins=10或者用bins='auto'让VisPy自动计算最优区间。Transform配置缺失
水平方向的直方图(orientation='h')需要适配画布的变换设置,否则会出现位置偏移或内容被裁剪。你需要给直方图添加缩放和平移变换,确保它能在画布内正确显示。画布清空步骤缺失
每次绘制前没有清空画布,会导致上一帧的内容残留,视觉上出现混乱。需要在on_draw开头调用self.context.clear('white')来清空画布。
修复后的snrHistogram类示例:
class snrHistogram(app.Canvas): def __init__(self, *args, **kwargs): app.Canvas.__init__(self, title='histogram fixed', keys="interactive", size=(300, 300)) # 调整bins为合理值 self.snr_hist = visuals.HistogramVisual( np.repeat([0, 1, 1, 20, 20, 40, 40, 80, 80, 90], 400), bins=10, color='r', orientation='h') self.label = visuals.TextVisual("hi", color='blue', pos=[50, 50, 0]) # 添加变换适配画布:平移+缩放,确保直方图在画布内显示 self.snr_hist.transform = visuals.transforms.STTransform(translate=(20, 20), scale=(260/90, 260/4000)) self.configure_transforms() def configure_transforms(self): vp = (0, 0, self.physical_size[0], self.physical_size[1]) self.context.set_viewport(*vp) self.label.transforms.configure(canvas=self, viewport=vp) self.snr_hist.transforms.configure(canvas=self, viewport=vp) def on_resize(self, event): self.configure_transforms() def on_mouse_wheel(self, event): # 可选:添加滚轮缩放功能 scale_factor = 1.1 if event.delta[1] > 0 else 0.9 self.snr_hist.transform.scale *= scale_factor self.update() def on_draw(self, event): self.context.clear('white') # 先清空画布 self.snr_hist.draw() self.label.draw() def on_close(self, event): self.close()
二、VisPy读取GPU内存数据的机制
VisPy底层基于OpenGL,完全支持直接使用GPU内存中的数据,不需要先下载到CPU再重新上传,这也是它高性能的核心原因之一。具体工作逻辑如下:
- 默认数据流:当你传入普通numpy数组给VisPy的Visual时,它会自动将数据上传到GPU的Buffer对象中,后续渲染直接使用GPU中的数据,不需要每次都拷贝。
- 直接复用GPU数据:如果你的数据已经存储在GPU内存中(比如是
vispy.gpuarray.GPUArray实例,或者是其他VisPy Visual创建的Buffer对象),可以直接将其传给HistogramVisual的data参数,VisPy会直接复用这个GPU Buffer,跳过CPU-GPU的数据拷贝步骤,大幅提升效率。 - 注意事项:确保GPU数据的格式与VisPy要求一致(比如常用的float32类型),且数据所在的OpenGL上下文与当前Canvas的上下文一致,避免跨上下文访问的错误。
示例:直接使用GPUArray传入数据
from vispy.gpuarray import GPUArray # 假设gpu_data是已经在GPU中的1D数据(比如从其他Visual获取的Buffer) gpu_data = GPUArray(np.repeat([0,1,20,40,80,90], 400).astype(np.float32)) hist = visuals.HistogramVisual(gpu_data, bins=10, color='g')
三、单个画布绘制两个直方图的实现方法
在VisPy中实现这个需求很简单,只需要创建两个HistogramVisual实例,分别设置不同的数据、颜色和位置,然后在on_draw中依次绘制即可。这里提供两种常用方式:
方式1:手动设置变换布局
通过STTransform给两个直方图设置不同的平移和缩放参数,让它们在画布内分开显示:
class DualHistogram(app.Canvas): def __init__(self): super().__init__(title='Two Histograms', size=(600, 300), keys='interactive') # 第一个直方图:左侧,红色 data1 = np.random.normal(50, 10, 10000) self.hist1 = visuals.HistogramVisual(data1, bins=20, color='r', orientation='v') self.hist1.transform = visuals.transforms.STTransform(translate=(20, 20), scale=(260/80, 260/2000)) # 第二个直方图:右侧,蓝色 data2 = np.random.uniform(0, 100, 10000) self.hist2 = visuals.HistogramVisual(data2, bins=20, color='b', orientation='v') self.hist2.transform = visuals.transforms.STTransform(translate=(320, 20), scale=(260/100, 260/2000)) self.configure_transforms() def configure_transforms(self): vp = (0, 0, self.physical_size[0], self.physical_size[1]) self.context.set_viewport(*vp) self.hist1.transforms.configure(canvas=self, viewport=vp) self.hist2.transforms.configure(canvas=self, viewport=vp) def on_draw(self, event): self.context.clear('white') self.hist1.draw() self.hist2.draw() def on_resize(self, event): self.configure_transforms()
方式2:使用ViewBox实现交互式布局
如果需要支持独立缩放、平移子视图,可以使用VisPy的ViewBox组件,将两个直方图分别放在不同的ViewBox中:
from vispy.scene import SceneCanvas, ViewBox class DualHistogramScene(SceneCanvas): def __init__(self): super().__init__(title='Two Histograms with ViewBox', size=(600, 300), keys='interactive') # 创建左右两个子视图 self.view_left = ViewBox(parent=self.scene) self.view_right = ViewBox(parent=self.scene) # 设置布局位置和大小 self.view_left.pos = (0, 0) self.view_left.size = (300, 300) self.view_right.pos = (300, 0) self.view_right.size = (300, 300) # 添加第一个直方图 data1 = np.random.normal(50, 10, 10000) hist1 = visuals.HistogramVisual(data1, bins=20, color='r') self.view_left.add(hist1) # 添加第二个直方图 data2 = np.random.uniform(0, 100, 10000) hist2 = visuals.HistogramVisual(data2, bins=20, color='b') self.view_right.add(hist2) # 自动适配视图范围 self.view_left.camera.set_range() self.view_right.camera.set_range()
这种方式的好处是每个子视图都有独立的相机控制,用户可以分别缩放、平移两个直方图,适合交互式需求。
内容的提问来源于stack exchange,提问作者iamniki

