Simple paint

98 views
Skip to first unread message

Marco

unread,
Apr 15, 2018, 1:43:02 PM4/15/18
to vispy
Hi,
here the code for a simple painter in Vispy.

I see that cameras are used for handle mouse event, then I placed there the code. Do you think that it is a good idea?
Will be simple pass from PanZoomCamera to a "PaintCamera" like in this script.

You can run the code. I tried with some different systems.
The refresh is very slow, why?

import sys
import numpy as np
from vispy import app, scene
from vispy.visuals.transforms import STTransform
from vispy.scene.visuals import Ellipse, Line


class PaintCamera(scene.PanZoomCamera):
   
def __init__(self, rect=(0, 0, 1, 1), aspect=None, **kwargs):
       
super(PaintCamera, self).__init__(**kwargs)
       
self.transform = STTransform()
       
self.aspect = aspect
       
self._rect = None
       
self.rect = rect

   
def viewbox_mouse_event(self, event):
       
global old_mouse_coord

       
# Standard mouse wheel handling
       
if event.type == 'mouse_wheel':
            center
= self._scene_transform.imap(event.pos)
           
self.zoom((1 + self.zoom_factor) ** (-event.delta[1] * 30), center)
           
event.handled = True

       
elif event.type == 'mouse_press':
            ttf
= self.scene_node.node_transform(self)
            mouse_coord
= list(map(int, ttf.map(event.pos)))
           
# Draw an ellipse
            ellipse
= Ellipse(center=(0, 0), radius=(10, 10),
                              color
="red", border_width=2, border_color="white",
                              num_segments
=10, parent=view.scene)
           
# Move the ellipse at mouse coordinates
            ellipse
.transform = STTransform(translate=[mouse_coord[0], mouse_coord[1]])

           
# I can't understand why if I write:
           
# ellipse = Ellipse(center=(mouse_coord[0], mouse_coord[1])...
           
# the position of ellipse is different

           
self.update()
           
# Store the coordinates for successive use
            old_mouse_coord
= mouse_coord
           
event.handled = True

       
elif event.type == 'mouse_move':
            ttf
= self.scene_node.node_transform(self)
            mouse_coord
= list(map(int, ttf.map(event.pos)))

           
# Draw another ellipse and move
            ellipse
= Ellipse(center=(0, 0), radius=(10, 10),
                              color
="green", border_width=2, border_color="white",
                              num_segments
=10, parent=view.scene)
            ellipse
.transform = STTransform(translate=[mouse_coord[0], mouse_coord[1]])

           
# Draw a line between old_mouse_coord and mouse_coord
            coord_linea
= np.array([[old_mouse_coord[0], old_mouse_coord[1]], [mouse_coord[0], mouse_coord[1]]])
           
Line(coord_linea, width=20, color="white", parent=view.scene)
           
# You can use method ="agg" and width will be respected but it will be very slow
           
# Line(coord_linea, width=20, color="white", parent=view.scene, method ="agg")

           
# Store the coordinates for successive use
            old_mouse_coord
= mouse_coord
           
self.update()
           
event.handled = True
       
else:
           
event.handled = False


# Create the SceneCanvas and Viewbox
canvas
= scene.SceneCanvas(keys='interactive', size=(800, 600))
canvas
.size = 800, 600
canvas
.show()
view
= canvas.central_widget.add_view()

# Set the camera
view
.camera = PaintCamera(aspect=1)
view
.camera.flip = (0, 1, 0)
view
.camera.set_range((0, 1000, 0, 1000))

if __name__ == '__main__' and sys.flags.interactive == 0:
    app
.run()




Marco

unread,
Apr 15, 2018, 2:08:48 PM4/15/18
to vispy
This version is quite faster but:
  1. again after the mouse_press the refresh is slow
  2. how to set the camera?

Try: simply click and drag.


import sys
import numpy as np

from vispy import app,
scene

canvas
= scene.SceneCanvas(keys='interactive', show=True)
vista
= canvas.central_widget.add_view()
# vista.camera=scene.PanZoomCamera(aspect=1)

pointer
= scene.visuals.Markers(parent=vista.scene)

canvas
.update()


@canvas.connect
def on_mouse_press(event):
   
global new_coord, line, ellipse, pointer
    pp
= event.pos
    new_coord
= np.array([pp, pp + 0.001])
    line
= scene.visuals.Line(pos=new_coord, color="red", parent=vista.scene, width=20, method="agg")
   
# line = scene.visuals.Line(pos=new_coord, color="red", parent=vista.scene, width=10, method="gl")

    pointer
.set_data(pos=np.array([pp]))
    canvas
.update()


@canvas.connect
def on_mouse_move(event):
   
global new_coord, pointer
    pp
= event.pos
    pointer
.set_data(pos=np.array([pp]))
   
if event.button == 1:
        new_coord
= np.append(new_coord, [pp], axis=0)
        line
.set_data(pos=new_coord)
        canvas
.update()


if __name__ == '__main__':
    canvas
.show()
   
if sys.flags.interactive == 0:
        app
.run()


David Hoese

unread,
Apr 16, 2018, 9:12:31 AM4/16/18
to vispy
Marco,

The main issue with the way you are doing things is that you are creating new visuals inside your event handler. You should try to construct all of your visuals when the Canvas is first initialized if possible. Normally someone would create some visuals initially then update the *data* for that visual and limit the number of new visuals you create, especially with mouse events.

As for setting the camera, your original example sets the camera, I'm not sure what you mean.

Dave

Marco

unread,
Apr 22, 2018, 2:27:19 PM4/22/18
to vispy
Thanks David,
it works. I publish the code for help others:

import sys
import numpy as np
from vispy import app, scene

canvas
= scene.SceneCanvas(keys='interactive', show=True)
vista
= canvas.central_widget.add_view()


pointer
= scene.visuals.Ellipse(center=(0., 0.), radius=(10., 10,), color=None, border_width=1, border_color="white",
                                num_segments
=10, parent=vista.scene)

canvas
.update()

lines_list
= []
for i in range(1, 1000):
    lines_list
.append(
        scene
.visuals.Line(pos=np.array([[0, 0], [0, 0]]), color="red", parent=vista.scene, width=20, method="agg"))
line_index
= -1


@canvas.connect
def on_mouse_press(event):
   
global coords, pointer, line_index
    line_index
= line_index + 1
    pp
= event.pos
    coords
= np.array([pp, pp])
    lines_list
[line_index].set_data(pos=coords, color="red", width=20)
    canvas
.update()


@canvas.connect
def on_mouse_move(event):
   
global coords, pointer
    pp
= event.pos
    pointer
.center = pp
   
if event.button == 1:
        coords
= np.append(coords, [pp], axis=0)
        lines_list
[line_index].set_data(pos=coords)

David Hoese

unread,
Apr 22, 2018, 7:52:54 PM4/22/18
to vispy
Marco,

Awesome! I'm glad you got it to work. If this project is part of a larger piece of code and you need better performing draws, I would suggest combining all of your line visuals in to a single line visual. There are many different ways to define lines in a line visual, see the LineVisual documentation for more info. Once you have them all in one line visual you can edit the overall numpy array for a specific line and then set the data for the entire line visual. This will send more data per update, but will allow draws on the GPU to be much faster (running one GL program versus 999).

Thanks for responding with your updated code to help others.

Dave

Marco

unread,
Apr 24, 2018, 3:48:11 PM4/24/18
to vispy
Il giorno lunedì 23 aprile 2018 01:52:54 UTC+2, David Hoese ha scritto:
If this project is part of a larger piece of code and you need better performing draws, I would suggest combining all of your line visuals in to a single line visual.

Yes, can be used in a bigger project. I will post other snippets if it is useful.

I think that you talk about this:

connect : str or array

Determines which vertices are connected by lines.

  • “strip” causes the line to be drawn with each vertex connected to the next.
  • “segments” causes each pair of vertices to draw an independent line segment
  • numpy arrays specify the exact set of segment pairs to connect.
Great!

A related question: is there a similar way for plot many shapes (paths or polygons) in a single object?
In examples directory we see the "collections" but it seems not possible to use with "parent=vista.scene"

Ciao,
Marco

David Hoese

unread,
Apr 24, 2018, 3:57:44 PM4/24/18
to vispy
Marco,

Yes "connect" is what I was referring to. Collections are meant to solve this issue of drawing a lot of shapes in one object but as of yet are not wrapped in a Visual (that I know of). If this is something you would like to use in a Scene I would suggest making an issue on github (or searching for one that might already exist) so we can organize the steps needed to make this possible. Thanks.

Dave
Reply all
Reply to author
Forward
0 new messages