Pass values throught InvokeEvent callback

Hi, I’m exploring how the vtk server “event system” works.
I would like to ask a question about the “InvokeEvent” operation (server side).
Into the “protocols.py” there is the “mouseInteraction” method which has the following line:

self.getApplication().InvokeEvent('UpdateEvent')

Into the class “vtk_override_protocols.py” the event is intercepted by the method “addRenderObserver” which prepare the “callback” in the following way (click here to see the code on github):

observerCallback = lambda *args, **kwargs: self.pushRender(realViewId)
tag = self.getApplication().AddObserver('UpdateEvent', observerCallback)

So in that way each time the “UpdateEvent” is fired, the “pushRender” is invoked. That’s great!

My question is:
How can I send a value throught the “InvokeEvent” call?
Basically I need to pass a timestamp, so for example:

timeStamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
self.getApplication().InvokeEvent('UpdateEvent', timeStamp)

And I would like to read the value in the “pushRender” method.
Is that possible? Here I found some hints, but this is for the c++ staff, not for the python ones.

Thanks and regards
Ennio

You can pass a few simple data types and objects that are derived from vtkObject class by using @vtk.calldata_type decorator. See detailed information here: https://www.slicer.org/wiki/Documentation/Nightly/Developers/FAQ#How_can_I_access_callData_argument_in_a_VTK_object_observer_callback_function

@dgobbi is there a user documentation of VTK’s Python wrapper? I couldn’t find a relevant section in in VTK API documentation and full-text search is not enabled, so I couldn’t even see if “vtk.calldata_type” is mentioned.

Thank you Andras,
at the end I figurated it out.
Thank you anyway for sharing me a couple of useful links and documentations.
Still have a couple of issues when passing the complex objects, but I will (hopefully) fix them in the next days.

bye,
Ennio

Hi Andras,
just a quick question: I would like to pass a string or an vtk_object to the callback.
At the moment only a simple integer has been sent as value.
I tried to use VTK_STRING but the program unexpectedly exited (so I think an error has accured).

I really can’t figure out why is not working.
I just substituted the @vtk.calldata_type(vtk.VTK_LONG) with @vtk.calldata_type(vtk.VTK_STRING).
Is there something wrong here?

Thanks,
Ennio

@dgobbi is there a way to pass a string from Python as event data?

@Ennio_Bolondi until you figure out how to send a single string, you can use vtkStringArray object.

Sure, it’s the example in the doc string for calldata_type:

Help on function calldata_type in vtk:

vtk.calldata_type = calldata_type(type)
    set_call_data_type(type) -- convenience decorator to easily set the CallDataType attribute
    for python function used as observer callback.
    For example:

    import vtkmodules.util.calldata_type
    import vtkmodules.util.vtkConstants
    import vtkmodules.vtkCommonCore import vtkCommand, vtkLookupTable

    @calldata_type(vtkConstants.VTK_STRING)
    def onError(caller, event, calldata):
        print("caller: %s - event: %s - msg: %s" % (caller.GetClassName(), event, calldata))

    lt = vtkLookupTable()
    lt.AddObserver(vtkCommand.ErrorEvent, onError)
    lt.SetTableRange(2,1)

Or without bothering to import, you can use a string name for the type:

@vtk.calldata_type('string0')
def vtkErrorEventHandler(caller, event, calldata):```

Thank you Eric and Andreas, ok I’ll give a second chance.
I wrote the same code you have in the example above, but maybe I misspelled it.
Thanks and regards
Ennio

Oops, I didn’t add anything to what you already knew. Do you know the C++ type of the object being passed, or is all your code pure Python? I’m guessing that mapping the object to VTK_LONG is simply casting and sending the address of whatever object is coming out of the C++ call, which is why it works. What’s the value of that parameter on the receiving end when you use VTK_LONG?

More importantly, does InvokeEvent have a second parameter? I can’t find its definition to verify, but all the examples I’ve scanned simply allow a single, event name parameter.

I’m getting it to work in a little example. Maybe start here and work up?

import vtk

@vtk.calldata_type('string0')
def vtkErrorEventHandler(object, event, callData):
    print(object, event, callData)

o = vtk.vtkObject()
o.AddObserver(vtk.vtkCommand.AnyEvent, vtkErrorEventHandler)

print(o.InvokeEvent('Blah', 'Hello, world!'))

When run:

vtkObject (000002332BBF5EB0)
  Debug: Off
  Modified Time: 62
  Reference Count: 1
  Registered Events:
    Registered Observers:
      vtkObserver (000002332BD4F4E0)
        Event: 1
        EventName: AnyEvent
        Command: 000002332BBF5230
        Priority: 0
        Tag: 1

 NoEvent Hello, world!
0
1 Like

Ok finally it worked.
I figured out what was the problem: I was passing a python object which was “”“containing”“” a string.
In other words: I didn’t “typitize” it as a string.

So in my final solution I used:

self.getApplication().InvokeEvent('UpdateEvent', str(stringArgument)) # WORKING SOLUTION

instead of:

self.getApplication().InvokeEvent('UpdateEvent', stringArgument)

So finally it’s working with @vtk.calldata_type(vtk.VTK_STRING)

Thank you so much!
Ennio

PS:

What’s the value of that parameter on the receiving end when you use VTK_LONG ?

I printed it out and it is: ‘8’
“VTK_STRING” is: ‘13’

And I’m happy to say that it’s working with the following as well:

@vtk.calldata_type('string0') # THIS ONE WORKS AS WELL :-)

Thanks!!

PPS:
last question: I was wondering if there is a way to pass an Object (maybe it could be usefull in the future). In the meanwhile I can use a string array as “Andreas” suggested.

Hi Andras, I’ve been on vacation and haven’t been able to read through this thread yet, @jcfr implemented parts of the python event code and he might be able to answer.

Thank you, I think @Ennio_Bolondi figured it out.

You can pass any objects derived from vtkObject.

If you want to pass custom Python objects, then you can serialize it to string: create a json representation manually; or use pickle to get a byte array and convert to string using base-64 encoding.

1 Like

Thank you to everyone.
I think I’m definitely ready to master the callbacks arguments :slight_smile:
Really appreciate the time and patience you put in this thread
Ennio

Ennio, my understanding of calldata_type came from looking at its implementation in util/misc.py. Here’s the part I find the most interesting:

    supported_call_data_types = ['string0', vtkCommonCore.VTK_STRING,
            vtkCommonCore.VTK_OBJECT, vtkCommonCore.VTK_INT,
            vtkCommonCore.VTK_LONG, vtkCommonCore.VTK_DOUBLE, vtkCommonCore.VTK_FLOAT]
1 Like

Ennio, in the VTK C++ source, the second parameter (callData) is a void*. From the VTK Python Wrappers docs:

a void* pointer can accept a pointer in two different ways: either from an Python object that supports the Python buffer protocol (this includes all numpy arrays along with the Python bytes and bytearray types), or from a string that contains a mangled pointer for the form '_hhhhhhhhhhhh_p_void', where 'hhhhhhhhhhhh' is the hexadecimal address. Return-valid void* will always be a string containing the mangled pointer.

This is why wrapping it in a str() worked: you called the object’s __str__() that returned its string representation.

Note that

  • Python 3 bytes = Python 2.7 str
  • Python 3 str = Python 2.7 unicode

However, str works for me as well.