@@ -393,6 +393,155 @@ def _handle_upload(self, change=None, a=None, b=None):
393393 with open (self .path , 'wb' ) as f : f .write (content )
394394 print ('Uploaded {0} ({1} bytes)' .format (self .filename , metadata ['size' ]))
395395
396+ class InteractiveView (object ):
397+
398+ def __init__ (self , renderView ):
399+ from ipyevents import Event
400+ from ipycanvas import Canvas
401+
402+ self .renderView = renderView
403+
404+ # Quality vs performance
405+ self .minTimBetweenRendersSec = 0.2
406+ self .compressionQuality = 50
407+ self .trackMouseMove = False # refresh if mouse is just moving (not dragging)
408+
409+ # If not receiving new rendering request for 10ms then a render is requested
410+ self .fullRenderRequestTimer = qt .QTimer ()
411+ self .fullRenderRequestTimer .setSingleShot (True )
412+ self .fullRenderRequestTimer .setInterval (1000 )
413+ self .fullRenderRequestTimer .connect ('timeout()' , self .fullRender )
414+
415+ # If not receiving new rendering request for 10ms then a render is requested
416+ self .quickRenderRequestTimer = qt .QTimer ()
417+ self .quickRenderRequestTimer .setSingleShot (True )
418+ self .quickRenderRequestTimer .setInterval (200 )
419+ self .quickRenderRequestTimer .connect ('timeout()' , self .quickRender )
420+
421+ # Get image size
422+ image = self .getImage ()
423+ self .canvas = Canvas (width = image .width , height = image .height )
424+ self .canvasHeight = int (image .height )
425+ self .canvas .draw_image (image )
426+
427+ self .interactor = self .renderView .interactorStyle ().GetInteractor ()
428+
429+ self .dragging = False
430+
431+ self .interactionEvents = Event ()
432+ self .interactionEvents .source = self .canvas
433+ self .interactionEvents .watched_events = [
434+ 'dragstart' , 'mouseenter' , 'mouseleave' ,
435+ 'mousedown' , 'mouseup' , 'mousemove' ,
436+ #'wheel', # commented out so that user can scroll through the notebook using mousewheel
437+ 'keyup' , 'keydown'
438+ ]
439+ #self.interactionEvents.msg_throttle = 1 # does not seem to have effect
440+ self .interactionEvents .prevent_default_action = True
441+ self .interactionEvents .on_dom_event (self .handleInteractionEvent )
442+
443+ self .keyToSym = {
444+ 'ArrowLeft' : 'Left' ,
445+ 'ArrowRight' : 'Right' ,
446+ 'ArrowUp' : 'Up' ,
447+ 'ArrowDown' : 'Down' ,
448+ # TODO: more key codes could be added
449+ }
450+
451+ # Errors are not displayed when a widget is displayed,
452+ # this variable can be used to retrieve error messages
453+ self .error = None
454+
455+ # Enable logging of UI events
456+ self .logEvents = False
457+ self .loggedEvents = []
458+
459+ def getImage (self , compress = True , forceRender = True ):
460+ from ipywidgets import Image
461+ slicer .app .processEvents ()
462+ if forceRender :
463+ self .renderView .forceRender ()
464+ screenshot = self .renderView .grab ()
465+ bArray = qt .QByteArray ()
466+ buffer = qt .QBuffer (bArray )
467+ buffer .open (qt .QIODevice .WriteOnly )
468+ if compress :
469+ screenshot .save (buffer , "JPG" , self .compressionQuality )
470+ else :
471+ screenshot .save (buffer , "PNG" )
472+ return Image (value = bArray .data (), width = screenshot .width (), height = screenshot .height ())
473+
474+ def fullRender (self ):
475+ self .fullRenderRequestTimer .stop ()
476+ self .quickRenderRequestTimer .stop ()
477+ self .canvas .draw_image (self .getImage (compress = False , forceRender = True ))
478+
479+ def quickRender (self ):
480+ self .fullRenderRequestTimer .stop ()
481+ self .quickRenderRequestTimer .stop ()
482+ self .canvas .draw_image (self .getImage (compress = True , forceRender = False ))
483+ self .fullRenderRequestTimer .start ()
484+
485+ def updateInteractorEventData (self , event ):
486+ if event ['event' ]== 'keydown' or event ['event' ]== 'keyup' :
487+ key = event ['key' ]
488+ sym = self .keyToSym [key ] if key in self .keyToSym .keys () else key
489+ self .interactor .SetKeySym (sym )
490+ if len (key ) == 1 :
491+ self .interactor .SetKeyCode (key )
492+ self .interactor .SetRepeatCount (1 )
493+ else :
494+ self .interactor .SetEventPosition (event ['offsetX' ], self .canvasHeight - event ['offsetY' ])
495+ self .interactor .SetShiftKey (event ['shiftKey' ])
496+ self .interactor .SetControlKey (event ['ctrlKey' ])
497+ self .interactor .SetAltKey (event ['altKey' ])
498+
499+ def handleInteractionEvent (self , event ):
500+ try :
501+ if self .logEvents :
502+ self .loggedEvents .append (event )
503+ renderNow = True
504+ if event ['event' ]== 'mousemove' :
505+ if not self .dragging and not self .trackMouseMove :
506+ return
507+ renderNow = False
508+ self .updateInteractorEventData (event )
509+ self .interactor .MouseMoveEvent ()
510+ self .canvas .draw_image (self .getImage (compress = True , forceRender = False ))
511+ self .quickRenderRequestTimer .start ()
512+ elif event ['event' ]== 'mousedown' :
513+ self .dragging = True
514+ self .updateInteractorEventData (event )
515+ if event ['button' ] == 0 :
516+ self .interactor .LeftButtonPressEvent ()
517+ elif event ['button' ] == 2 :
518+ self .interactor .RightButtonPressEvent ()
519+ elif event ['button' ] == 1 :
520+ self .interactor .MiddleButtonPressEvent ()
521+ self .fullRender ()
522+ elif event ['event' ]== 'mouseup' :
523+ self .updateInteractorEventData (event )
524+ if event ['button' ] == 0 :
525+ self .interactor .LeftButtonReleaseEvent ()
526+ elif event ['button' ] == 2 :
527+ self .interactor .RightButtonReleaseEvent ()
528+ elif event ['button' ] == 1 :
529+ self .interactor .MiddleButtonReleaseEvent ()
530+ self .dragging = False
531+ self .fullRender ()
532+ elif event ['event' ]== 'keydown' :
533+ self .updateInteractorEventData (event )
534+ self .interactor .KeyPressEvent ()
535+ self .interactor .CharEvent ()
536+ self .fullRender ()
537+ elif event ['event' ]== 'keyup' :
538+ self .updateInteractorEventData (event )
539+ self .interactor .KeyReleaseEvent ()
540+ self .fullRender ()
541+ except Exception as e :
542+ self .error = str (e )
543+
544+
396545def cliRunSync (module , node = None , parameters = None , delete_temporary_files = True , update_display = True ):
397546
398547 node = slicer .cli .run (module , node = node , parameters = parameters , wait_for_completion = False ,
@@ -417,6 +566,7 @@ def cliRunSync(module, node=None, parameters=None, delete_temporary_files=True,
417566slicer .nb .displayViews = displayViews
418567slicer .nb .displaySliceView = displaySliceView
419568slicer .nb .display3DView = display3DView
569+ slicer .nb .InteractiveView = InteractiveView
420570
421571slicer .nb .displayMarkups = displayMarkups
422572slicer .nb .displayModel = displayModel
0 commit comments