Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can i draw real time data with optional Markers? #2514

Open
lfreites opened this issue Aug 9, 2023 · 11 comments
Open

How can i draw real time data with optional Markers? #2514

lfreites opened this issue Aug 9, 2023 · 11 comments

Comments

@lfreites
Copy link

lfreites commented Aug 9, 2023

Hi, i recetly started using vispy library and i am struggling to update real time data with optional Markers in the same view. I am trying to use Markersvisual for the markers but for some reason is not working. I ve also tried generating the lines with LinePlotVisual that have markers included but couldnt achieve it. Which module d be the best one for this case? Thanks in advance!!

import os
import logging

import vispy.app
from PyQt5 import QtWidgets, uic
from mc_log_view.graphic_view import plot_data_model
from mc_log_view.graphic_view import data_source
from mc_log_view.agi4_view import agi4_source
from mc_log_view import settings
from PyQt5 import QtCore
import numpy as np

from vispy.scene import SceneCanvas, Label
from vispy.app import use_app
from vispy import scene, color, visuals

import pyqtgraph

LOG = logging.getLogger(__name__)

#class VispyGraphicView(QtWidgets.QWidget):


class VispyGraphicView(pyqtgraph.PlotWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        main_layout = QtWidgets.QHBoxLayout()
        self._canvas_wrapper = CanvasWrapper()
        main_layout.addWidget(self._canvas_wrapper.canvas.native)
        self.setLayout(main_layout)

    def update_vispy_graphs(self, model_index, data, color, visibility, selected):
        """update graphs
            model_index - index od data, int
            data - [[time,..], [value,..]]
            color - color of graph, QtGui.QColor
            visibility - visibility of graph, bool true/false or 1/0
            selected - indicates that the graph is selected, bool true/false or 1/0
        """
        self._canvas_wrapper.update(
            model_index, data, color, visibility, selected)

    def clear_vispy_graphs(self):
        """TODO FIx clear graphs, locks panzoom and generates incorrect white line"""
        #self._canvas_wrapper.clear_graphs()
        LOG.debug("Clearing vispy graphs!!!!!")

    def update_selected_region(self):
        print("Updating selected region in vispy")

class CanvasWrapper:

    def __init__(self):
        self.canvas = SceneCanvas()

        self.plot_data_model = plot_data_model.PlotDataModel()
        self.reference_data_source_res = {}
        self.data_source = data_source.DataSource(settings.GDS_INDEX)
        self.agi4_source = agi4_source.Agi4Source()

        self.grid = self.canvas.central_widget.add_grid()
        self.grid.spacing = 0
        title = Label("Vispy Plot", color='white')
        title.height_max = 40
        self.grid.add_widget(title, row=0, col=0, col_span=2)

        xaxis1 = scene.AxisWidget(orientation='bottom',
                        axis_label='X Axis',
                        axis_font_size=11,
                        axis_label_margin=50,
                        tick_label_margin=15)
        xaxis1.height_max = 30
        self.grid.add_widget(xaxis1, row=2, col=1)
        yaxis1 = scene.AxisWidget(orientation='left',
                         axis_label='Y Axis',
                         axis_font_size=11,
                         axis_label_margin=50,
                         tick_label_margin=5)
        yaxis1.width_max = 30
        self.grid.add_widget(yaxis1, row=1, col=0)

        self.view = self.grid.add_view(row=1, col=1, border_color='white')
        self.view.camera = "panzoom"
        xaxis1.link_view(self.view)
        yaxis1.link_view(self.view)
        #scene.visuals.GridLines(parent=self.view.scene)
        grid = scene.visuals.GridLines(parent=self.view.scene)
        grid.set_gl_state('translucent')
        self.graphs = {}
        self.max_time = 0.0
        self.min_time = 0.0
        self.max_value = 0.0
        self.min_value = 0.0



    def clear_graphs(self):
        self.view = self.grid.remove_widget(self.view)
        self.graphs = {}
        self.view = self.grid.add_view(row=1, col=1, border_color='white')
        self.view.camera = "panzoom"

    def update(self, model_index, data, color, visibility, selected):
        """update graphs
            model_index - index od data, int
            data - [[time,..], [value,..]]
            color - color of graph, QtGui.QColor
            visibility - visibility of graph, bool true/false or 1/0
            selected - indicates that the graph is selected, bool true/false or 1/0
        """
        vispy_color = (color.redF(), color.greenF(), color.blueF())
        graph = self.graphs.get(model_index)
        data_matrix = np.array([data[0], data[1]]).T

        if graph is None:
            #De la siguiente linea mirar cual de las propiedades afecta la escala al renderizar
            line = scene.visuals.Line(data_matrix, color=vispy_color, width=2.0)
            line.set_gl_state(depth_test=False)
            self.view.add(line)

            #scatter = visuals.markers.MarkersVisual(data_matrix, face_color=vispy_color, size=4.0)
            scatter = scene.visuals.create_visual_node(visuals.MarkersVisual)
            scatter.set_gl_state("translucent", blend=True, depth_test=True)
            self.view.add(scatter)


            #scatter = scene.visuals.Markers(data_matrix, color=vispy_color, width=3.0)
            #scatter.set_gl_state(depth_test=False)
            #self.view.add(scatter)

            graph = {
                'color': color,
                'time': data[0].copy(),
                'value': data[1].copy(),
                'line': line,
                'scatter': scatter
                }
            self.graphs[model_index] = graph
        else:
            graph['time'].extend(data[0])
            graph['value'].extend(data[1])
            graph['line'].set_data(np.array([graph['time'], graph['value']]).T)
            graph['scatter'].set_data(np.array([graph['time'], graph['value']]).T)

        if visibility and selected and graph['line'] is not None:

            if min(data[1]) < self.min_value:
                self.min_value = min(data[1])
            if max(data[1]) >= self.max_value:
                self.max_value = max(data[1])

            self.view.camera.set_range(x=(min(graph['time'][:]), max(graph['time'][:])),
                                       y=(self.min_value, self.max_value))

            #self.canvas.update()
            self.view.camera = "panzoom"

def main():
    import sys
    appi = use_app("pyqt5")
    appi.create()
    win = VispyGraphicView()
    win.show()
    appi.run()

if __name__ == '__main__':
    main()
@djhoese
Copy link
Member

djhoese commented Aug 9, 2023

  1. What do you mean by "optional" Markers?
  2. Is there a reason you are mixing pyqtgraph and vispy? Are you sure this works?
  3. You say it isn't working, but how? What are you expecting to happen and what isn't happening?

If you can make your example code any smaller that might help identify the issue faster. If this seems like a bug then I'll definitely need a minimal example that doesn't have any extra dependencies or external data files so I can run it myself.

@lfreites
Copy link
Author

lfreites commented Aug 9, 2023

What i mean by optional Markers is to be able to have not only lines but also marker points in the graph. I am using pyqtgraph as there is a big menu functionality i need to replicate that i think it d take too much work time in vispy. The provided code is working as the lines are displayed. But i cant get any scatter points (markers) when i try to display them. I ll try to make an example without any dependencies, but i based this development in the example of real time data:

https://vispy.org/gallery/scene/realtime_data/ex02_control_vispy_from_qt.html#sphx-glr-gallery-scene-realtime-data-ex02-control-vispy-from-qt-py

In this case how could i add , appart from the lines, some scatter points in the same view?

Thanks in advance and sorry for the lack of clear information!

@djhoese
Copy link
Member

djhoese commented Aug 9, 2023

You have this code:

            #scatter = visuals.markers.MarkersVisual(data_matrix, face_color=vispy_color, size=4.0)
            scatter = scene.visuals.create_visual_node(visuals.MarkersVisual)
            scatter.set_gl_state("translucent", blend=True, depth_test=True)
            self.view.add(scatter)


            #scatter = scene.visuals.Markers(data_matrix, color=vispy_color, width=3.0)
            #scatter.set_gl_state(depth_test=False)
            #self.view.add(scatter)

The last section of commented out code is what you want. Unless you are creating a custom Visual class you should never need to use create_visual_node. Since you are using a SceneCanvas you'll want the visuals from scene.visuals and not vispy.visuals. Is there a reason you need all these set_gl_state calls? I'd comment them out unless you know you need them (for the lines and the markers).

@lfreites
Copy link
Author

lfreites commented Aug 9, 2023

I have tried that before, but appending the same data matrix that i append to Line module (data_matrix) i get this error:

error:unhashable type: 'numpy.ndarray'

apparently they dont have the same interface, i guess it expects the position to be passed in a different way. I am still trying to figure out why it is correctly received in Line module but not in Markers one.

@djhoese
Copy link
Member

djhoese commented Aug 9, 2023

Does the original visualization work before the append? Could you please provide the full traceback for that unhashable type error? It is hard to tell what is going wrong without the full context.

@lfreites
Copy link
Author

lfreites commented Aug 9, 2023

sure:

import numpy as np
from PyQt5 import QtWidgets

from vispy.scene import SceneCanvas, visuals
from vispy.app import use_app

IMAGE_SHAPE = (600, 800)  # (height, width)
CANVAS_SIZE = (800, 600)  # (width, height)
NUM_LINE_POINTS = 200

COLORMAP_CHOICES = ["viridis", "reds", "blues"]
LINE_COLOR_CHOICES = ["black", "red", "blue"]

class MyMainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        central_widget = QtWidgets.QWidget()
        main_layout = QtWidgets.QHBoxLayout()
        self._controls = Controls()
        main_layout.addWidget(self._controls)
        self._canvas_wrapper = CanvasWrapper()
        main_layout.addWidget(self._canvas_wrapper.canvas.native)
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)
        self._connect_controls()

    def _connect_controls(self):
        self._controls.colormap_chooser.currentTextChanged.connect(self._canvas_wrapper.set_image_colormap)
        self._controls.line_color_chooser.currentTextChanged.connect(self._canvas_wrapper.set_line_color)

class Controls(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QtWidgets.QVBoxLayout()
        self.colormap_label = QtWidgets.QLabel("Image Colormap:")
        layout.addWidget(self.colormap_label)
        self.colormap_chooser = QtWidgets.QComboBox()
        self.colormap_chooser.addItems(COLORMAP_CHOICES)
        layout.addWidget(self.colormap_chooser)

        self.line_color_label = QtWidgets.QLabel("Line color:")
        layout.addWidget(self.line_color_label)
        self.line_color_chooser = QtWidgets.QComboBox()
        self.line_color_chooser.addItems(LINE_COLOR_CHOICES)
        layout.addWidget(self.line_color_chooser)

        layout.addStretch(1)
        self.setLayout(layout)

class CanvasWrapper:
    def __init__(self):
        self.canvas = SceneCanvas(size=CANVAS_SIZE)
        self.grid = self.canvas.central_widget.add_grid()

        self.view_top = self.grid.add_view(0, 0, bgcolor='cyan')
        image_data = _generate_random_image_data(IMAGE_SHAPE)
        self.image = visuals.Image(
            image_data,
            texture_format="auto",
            cmap=COLORMAP_CHOICES[0],
            parent=self.view_top.scene,
        )
        self.view_top.camera = "panzoom"
        self.view_top.camera.set_range(x=(0, IMAGE_SHAPE[1]), y=(0, IMAGE_SHAPE[0]), margin=0)

        self.view_bot = self.grid.add_view(1, 0, bgcolor='#c0c0c0')
        line_data = _generate_random_line_positions(NUM_LINE_POINTS)
        self.line = visuals.Line(line_data, parent=self.view_bot.scene, color=LINE_COLOR_CHOICES[0])
        
        #Scatter Points
        self.scatter = visuals.Markers(line_data, face_color=LINE_COLOR_CHOICES[0], parent=self.view_bot.scene, size=5.0)

        self.view_bot.camera = "panzoom"
        self.view_bot.camera.set_range(x=(0, NUM_LINE_POINTS), y=(0, 1))
        

    def set_image_colormap(self, cmap_name: str):
        print(f"Changing image colormap to {cmap_name}")
        self.image.cmap = cmap_name

    def set_line_color(self, color):
        print(f"Changing line color to {color}")
        self.line.set_data(color=color)

def _generate_random_image_data(shape, dtype=np.float32):
    rng = np.random.default_rng()
    data = rng.random(shape, dtype=dtype)
    return data


def _generate_random_line_positions(num_points, dtype=np.float32):
    rng = np.random.default_rng()
    pos = np.empty((num_points, 2), dtype=np.float32)
    pos[:, 0] = np.arange(num_points)
    pos[:, 1] = rng.random((num_points,), dtype=dtype)
    return pos


if __name__ == "__main__":
    app = use_app("pyqt5")
    app.create()
    win = MyMainWindow()
    win.show()
    app.run()

@lfreites
Copy link
Author

lfreites commented Aug 9, 2023

The problem was that i cant directly set the position as it was with Line module, i need to explicitly call pos=position.
Anyways i am still having trouble when i want to update the properties of the Marker module, in the next example every time i change the color, only the lines are being changed, not the markers face_color.

import numpy as np
from PyQt5 import QtWidgets

from vispy.scene import SceneCanvas, visuals
from vispy.app import use_app

IMAGE_SHAPE = (600, 800)  # (height, width)
CANVAS_SIZE = (800, 600)  # (width, height)
NUM_LINE_POINTS = 200

COLORMAP_CHOICES = ["viridis", "reds", "blues"]
LINE_COLOR_CHOICES = ["black", "red", "blue"]


class MyMainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        central_widget = QtWidgets.QWidget()
        main_layout = QtWidgets.QHBoxLayout()

        self._controls = Controls()
        main_layout.addWidget(self._controls)
        self._canvas_wrapper = CanvasWrapper()
        main_layout.addWidget(self._canvas_wrapper.canvas.native)

        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

        self._connect_controls()

    def _connect_controls(self):
        self._controls.colormap_chooser.currentTextChanged.connect(self._canvas_wrapper.set_image_colormap)
        self._controls.line_color_chooser.currentTextChanged.connect(self._canvas_wrapper.set_line_color)
        self._controls.line_color_chooser.currentTextChanged.connect(self._canvas_wrapper.set_scatter_color)


class Controls(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QtWidgets.QVBoxLayout()
        self.colormap_label = QtWidgets.QLabel("Image Colormap:")
        layout.addWidget(self.colormap_label)
        self.colormap_chooser = QtWidgets.QComboBox()
        self.colormap_chooser.addItems(COLORMAP_CHOICES)
        layout.addWidget(self.colormap_chooser)

        self.line_color_label = QtWidgets.QLabel("Line color:")
        layout.addWidget(self.line_color_label)
        self.line_color_chooser = QtWidgets.QComboBox()
        self.line_color_chooser.addItems(LINE_COLOR_CHOICES)
        layout.addWidget(self.line_color_chooser)

        layout.addStretch(1)
        self.setLayout(layout)


class CanvasWrapper:
    def __init__(self):
        self.canvas = SceneCanvas(size=CANVAS_SIZE)
        self.grid = self.canvas.central_widget.add_grid()

        self.view_top = self.grid.add_view(0, 0, bgcolor='cyan')
        image_data = _generate_random_image_data(IMAGE_SHAPE)
        self.image = visuals.Image(
            image_data,
            texture_format="auto",
            cmap=COLORMAP_CHOICES[0],
            parent=self.view_top.scene,
        )
        self.view_top.camera = "panzoom"
        self.view_top.camera.set_range(x=(0, IMAGE_SHAPE[1]), y=(0, IMAGE_SHAPE[0]), margin=0)

        self.view_bot = self.grid.add_view(1, 0, bgcolor='#c0c0c0')
        line_data = _generate_random_line_positions(NUM_LINE_POINTS)
        self.line = visuals.Line(line_data, parent=self.view_bot.scene, color=LINE_COLOR_CHOICES[0])
        
        #Scatter Marker Points
        self.scatter = visuals.Markers(pos=line_data, face_color=LINE_COLOR_CHOICES[1], parent=self.view_bot.scene, 
                                       size=10.0, edge_color=LINE_COLOR_CHOICES[1], edge_width=1.0)

        self.view_bot.camera = "panzoom"
        self.view_bot.camera.set_range(x=(0, NUM_LINE_POINTS), y=(0, 1))
        

    def set_image_colormap(self, cmap_name: str):
        print(f"Changing image colormap to {cmap_name}")
        self.image.cmap = cmap_name

    def set_line_color(self, color):
        print(f"Changing line color to {color}")
        self.line.set_data(color=color)

    def set_scatter_color(self, color):
        print(f"Changing scatter color to {color}")
        self.scatter.set_data(face_color=color, edge_color=color)


def _generate_random_image_data(shape, dtype=np.float32):
    rng = np.random.default_rng()
    data = rng.random(shape, dtype=dtype)
    return data


def _generate_random_line_positions(num_points, dtype=np.float32):
    rng = np.random.default_rng()
    pos = np.empty((num_points, 2), dtype=np.float32)
    pos[:, 0] = np.arange(num_points)
    pos[:, 1] = rng.random((num_points,), dtype=dtype)
    return pos


if __name__ == "__main__":
    app = use_app("pyqt5")
    app.create()
    win = MyMainWindow()
    win.show()
    app.run()

@djhoese
Copy link
Member

djhoese commented Aug 10, 2023

Sorry, there was a misunderstanding. I want the full traceback of the error. That should tell me what series of calls are causing the error you were getting.

@lfreites
Copy link
Author

Now there is no error showing in the terminal, its just that whenever i change color (set_scatter_color) the lines change but markers stay the same as they were initialized.

@djhoese
Copy link
Member

djhoese commented Aug 10, 2023

The MarkersVisual has the unfortunate design that the other properties (ex. face colors) only get updated in .set_data if pos (the position of the markers) is also passed. You'll need to pass the vertex/marker positions every time you update the color. At least for now.

@lfreites
Copy link
Author

thanks, i ll try that approach now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants