[docs]defcallback_stream_client(stream_client):"""This callback is used to update the image inside of the ImageManager instance Parameters ---------- stream_client : StreamClient """ifstream_client.in_request:returnstream_client.in_request=Truestream_client.window2image_filter.Update()stream_client.window2image_filter.Modified()vtk_image=stream_client.window2image_filter.GetOutput()vtk_array=vtk_image.GetPointData().GetScalars()w,h,_=vtk_image.GetDimensions()np_arr=np.frombuffer(vtk_array,dtype="uint8")ifnp_arrisNone:stream_client.in_request=Falsereturnstream_client.img_manager.write_into(w,h,np_arr)stream_client.in_request=False
[docs]classFuryStreamClient:"""This obj is responsible to create a StreamClient."""@warn_on_args_to_kwargs()def__init__(self,showm,*,max_window_size=None,use_raw_array=True,whithout_iren_start=False,num_buffers=2,):"""A StreamClient extracts a framebuffer from the OpenGL context and writes into a shared memory resource. Parameters ---------- showm : ShowManager max_window_size : tuple of ints, optional This allows resize events inside of the FURY window instance. Should be greater than the window size. use_raw_array : bool, optional If False then FuryStreamClient will use SharedMemory instead of RawArrays. Notice that Python >=3.8 it's a requirement to use SharedMemory) whithout_iren_start : bool, optional Sometimes you can't initiate the vtkInteractor instance. num_buffers : int, optional Number of buffers to be used in the n-buffering technique. """self._whithout_iren_start=whithout_iren_startself.showm=showmself.window2image_filter=vtk.vtkWindowToImageFilter()self.window2image_filter.SetInput(self.showm.window)self.image_buffers=[]self.image_buffer_names=[]self.info_buffer_name=Noneself.image_reprs=[]self.num_buffers=num_buffersifmax_window_sizeisNone:max_window_size=(int(self.showm.size[0]*(1+0.1)),int(self.showm.size[1]*(1+0.1)),)self.max_size=max_window_size[0]*max_window_size[1]self.max_window_size=max_window_sizeifself.max_size<self.showm.size[0]*self.showm.size[1]:raiseValueError("max_window_size must be greater than window_size")ifnotPY_VERSION_8andnotuse_raw_array:raiseValueError(""" SharedMemory works only in python 3.8 or higher""")ifuse_raw_array:self.img_manager=RawArrayImageBufferManager(max_window_size=max_window_size,num_buffers=num_buffers)else:self.img_manager=SharedMemImageBufferManager(max_window_size=max_window_size,num_buffers=num_buffers)self._id_timer=Noneself._id_observer=Noneself._interval_timer=Noneself.in_request=Falseself.update=Trueself.use_raw_array=use_raw_arrayself._started=False
[docs]@warn_on_args_to_kwargs()defstart(self,*,ms=0,use_asyncio=False):"""Start the stream client. Parameters ---------- ms : float, optional positive number. This update the image buffer using a interval of ms milliseconds. If ms is 0 then the stream client will update the buffer after every Render event. use_asyncio : bool, optional If False then the stream client will update the image using a threading technique. """@warn_on_args_to_kwargs()defcallback_for_vtk(caller,event,*args,**kwargs):callback_stream_client(**{"stream_client":kwargs["stream_client"]})use_asyncio=platform.system()=="Windows"oruse_asyncioifself._started:self.stop()ifms>0:ifself._whithout_iren_start:Interval=IntervalTimerifuse_asyncioelseIntervalTimerThreadingself._interval_timer=Interval(ms/1000,callback_stream_client,**{"stream_client":self})else:self._id_observer=self.showm.iren.AddObserver("TimerEvent",partial(callback_for_vtk,**{"stream_client":self}))self._id_timer=self.showm.iren.CreateRepeatingTimer(ms)else:self._id_observer=self.showm.iren.AddObserver("RenderEvent",partial(callback_for_vtk,**{"stream_client":self}))self.showm.window.Render()self.showm.iren.Render()self._started=Truecallback_stream_client(**{"stream_client":self})
[docs]defstop(self):"""Stop the stream client."""ifnotself._started:returnFalseifself._interval_timerisnotNone:self._interval_timer.stop()ifself._id_timerisnotNone:# self.showm.destroy_timer(self._id_timer)self.showm.iren.DestroyTimer(self._id_timer)self._id_timer=Noneifself._id_observerisnotNone:self.showm.iren.RemoveObserver(self._id_observer)self._id_observer=Noneself._started=False
[docs]defcleanup(self):"""Release the shared memory resources if necessary."""ifself.use_raw_array:returnself.img_manager.info_buffer.close()# this it's due the python core issues# https://bugs.python.org/issue38119# https://bugs.python.org/issue39959# https://github.com/luizalabs/shared-memory-dict/issues/13try:self.img_manager.info_buffer.unlink()exceptFileNotFoundError:print(f"Shared Memory {self.img_manager.info_buffer_name}\ (info_buffer) File not found")forbuffer,nameinzip(self.img_manager.image_buffers,self.img_manager.image_buffer_names,):buffer.close()try:buffer.unlink()exceptFileNotFoundError:print(f"Shared Memory {name}(buffer image) File not found")
[docs]definteraction_callback(circular_queue,showm,iren,render_after):"""This callback is used to invoke vtk interaction events reading those events from the provided circular_queue instance Parameters ---------- circular_queue : CircularQueue showm : ShowmManager iren : vtkInteractor render_after : bool, optional If the render method should be called after an dequeue """ts=time.time()*1000data=circular_queue.dequeue()ifdataisNone:returnuser_event_id=data[0]user_timestamp=data[_CQUEUE.index_info.user_timestamp]ts=time.time()*1000newX=int(showm.size[0]*data[_CQUEUE.index_info.x])newY=int(showm.size[1]*data[_CQUEUE.index_info.y])ctrl_key=int(data[_CQUEUE.index_info.ctrl])shift_key=int(data[_CQUEUE.index_info.shift])newY=showm.size[1]-newYevent_ids=_CQUEUE.event_idsifuser_event_id==event_ids.mouse_weel:zoomFactor=1.0-data[_CQUEUE.index_info.weel]/1000.0camera=showm.scene.GetActiveCamera()fp=camera.GetFocalPoint()pos=camera.GetPosition()delta=[fp[i]-pos[i]foriinrange(3)]pos2=camera.GetPosition()camera.SetFocalPoint([pos2[i]+delta[i]foriinrange(3)])camera.Zoom(zoomFactor)elifuser_event_id==event_ids.mouse_move:iren.SetEventInformation(newX,newY,ctrl_key,shift_key,chr(0),0,None)iren.MouseMoveEvent()elifevent_ids.mouse_ids:iren.SetEventInformation(newX,newY,ctrl_key,shift_key,chr(0),0,None)mouse_actions={event_ids.left_btn_press:iren.LeftButtonPressEvent,event_ids.left_btn_release:iren.LeftButtonReleaseEvent,event_ids.middle_btn_press:iren.MiddleButtonPressEvent,event_ids.middle_btn_release:iren.MiddleButtonReleaseEvent,event_ids.right_btn_press:iren.RightButtonPressEvent,event_ids.right_btn_release:iren.RightButtonReleaseEvent,}mouse_actions[user_event_id]()logging.info("Interaction: time to perform event "+f"{ts-user_timestamp:.2f} ms")ifrender_after:showm.window.Render()showm.iren.Render()
[docs]classFuryStreamInteraction:"""This obj. is responsible to manage the user interaction"""@warn_on_args_to_kwargs()def__init__(self,showm,*,max_queue_size=50,use_raw_array=True,whithout_iren_start=False):"""Initialize the StreamInteraction obj. Parameters ---------- showm : ShowmManager max_queue_size : int, optional maximum number of events to be stored. use_raw_array : bool, optional If False then a CircularQueue will be created using SharedMemory instead of RawArrays. Notice that Python >=3.8 it's requirement to use SharedMemory. whithout_iren_start : bool, optional Set that to True if you can't initiate the vtkInteractor instance. """self.showm=showmself.iren=self.showm.irenifuse_raw_array:self.circular_queue=ArrayCircularQueue(max_size=max_queue_size,dimension=_CQUEUE.dimension)else:self.circular_queue=SharedMemCircularQueue(max_size=max_queue_size,dimension=_CQUEUE.dimension)self._id_timer=Noneself._id_observer=Noneself._interval_timer=Noneself._whithout_iren_start=whithout_iren_startself._started=False
[docs]@warn_on_args_to_kwargs()defstart(self,*,ms=3,use_asyncio=False):"""Start the stream interaction client. Parameters ---------- ms : float, optional positive number greater than zero. use_asyncio : bool, optional If False then the interaction will be performed in a separate thread. """use_asyncio=platform.system()=="Windows"oruse_asyncioifms<=0:raiseValueError("ms must be greater than zero")ifself._started:self.stop()ifself._whithout_iren_start:Interval=IntervalTimerifuse_asyncioelseIntervalTimerThreadingself._interval_timer=Interval(ms/1000,interaction_callback,*[self.circular_queue,self.showm,self.iren,True],)else:defcallback(caller,event,*args,**kwargs):interaction_callback(self.circular_queue,self.showm,self.iren,True)self._id_observer=self.showm.iren.AddObserver("TimerEvent",callback)self._id_timer=self.showm.iren.CreateRepeatingTimer(ms)self._started=True
[docs]defstop(self):"""Stop the stream interaction client."""ifnotself._started:returnFalseifself._id_timerisnotNone:# self.showm.destroy_timer(self._id_timer)self.showm.iren.DestroyTimer(self._id_timer)self._id_timer=Noneifself._id_observerisnotNone:self.showm.iren.RemoveObserver(self._id_observer)self._id_observer=Noneifself._interval_timerisnotNone:self._interval_timer.stop()self._interval_timer=Noneself._started=False
[docs]defcleanup(self):"""Release the shared memory resources if necessary."""self.circular_queue.cleanup()