def __init__(self, module_manager): """Standard constructor. All DeVIDE modules have these, we do the required setup actions. """ # we record the setting here, in case the user changes it # during the lifetime of this model, leading to different # states at init and shutdown. self.IMAGE_VIEWER = IMAGE_VIEWER # we need all this for our contours self.mask_data = None self.image_data = None self.lungVolume = None self.contour_severe_actor = vtk.vtkActor() self.contour_moderate_actor = vtk.vtkActor() self.contour_lungedge_actor = vtk.vtkActor() self.severe_mapper = vtk.vtkPolyDataMapper() self.severe_mapper.ScalarVisibilityOff() self.moderate_mapper = vtk.vtkPolyDataMapper() self.moderate_mapper.ScalarVisibilityOff() self.lung_mapper = vtk.vtkPolyDataMapper() self.lung_mapper.ScalarVisibilityOff() self.contour_severe_actor.SetMapper(self.severe_mapper) self.contour_severe_actor.GetProperty().SetColor(1, 0, 0) self.contour_severe_actor.GetProperty().SetOpacity(0.5) self.contour_moderate_actor.SetMapper(self.moderate_mapper) self.contour_moderate_actor.GetProperty().SetColor(0.5, 0, 1) self.contour_moderate_actor.GetProperty().SetOpacity(0.25) self.contour_lungedge_actor.SetMapper(self.lung_mapper) self.contour_lungedge_actor.GetProperty().SetColor(0.9, 0.9, 0.9) self.contour_lungedge_actor.GetProperty().SetOpacity(0.1) ModuleBase.__init__(self, module_manager) # create the view frame self._view_frame = module_utils.instantiate_module_view_frame( self, self._module_manager, EmphysemaViewerFrame.EmphysemaViewerFrame) # change the title to something more spectacular (or at least something non-default) self._view_frame.SetTitle('EmphysemaViewer') # create the necessary VTK objects: we only need a renderer, # the RenderWindowInteractor in the view_frame has the rest. self.ren = vtk.vtkRenderer() self.ren.SetBackground(0.5, 0.5, 0.5) self._view_frame.rwi.GetRenderWindow().AddRenderer(self.ren) self.ren.AddActor(self.contour_severe_actor) self.ren.AddActor(self.contour_moderate_actor) self.ren.AddActor(self.contour_lungedge_actor) self.ren2 = vtk.vtkRenderer() self.ren2.SetBackground(0.5, 0.5, 0.5) self._view_frame.overlay.GetRenderWindow().AddRenderer(self.ren2) self.slice_viewer1 = CMSliceViewer(self._view_frame.overlay, self.ren2) self.ren3 = vtk.vtkRenderer() self.ren3.SetBackground(0.5, 0.5, 0.5) self._view_frame.original.GetRenderWindow().AddRenderer(self.ren3) self.slice_viewer2 = CMSliceViewer(self._view_frame.original, self.ren3) self.slice_viewer3 = CMSliceViewer(self._view_frame.rwi, self.ren) self.sync = SyncSliceViewers() self.sync.add_slice_viewer(self.slice_viewer1) self.sync.add_slice_viewer(self.slice_viewer2) self.sync.add_slice_viewer2(self.slice_viewer3) # hook up all event handlers self._bind_events() # anything you stuff into self._config will be saved self._config.last_used_dir = '' # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their # views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic()
class EmphysemaViewer(IntrospectModuleMixin, ModuleBase): """Module to visualize lungemphysema in a CT scan. A lung mask is also needed. EmphysemaViewer consists of a volume rendering and two linked slice-based views; one with the original data and one with an emphysema overlay. The volume rendering shows 3 contours: the lungedges and 2 different contours of emphysema; a normal one and a severe one. There are two ways of setting the emphysema values. - The first way is choosing the 'default' values, which are literature-based. They are set on -950 HU (emphysema) and -970 HU (severe). - The other way is a computational way: The lowest 11% values, that are present in the data are marked as emphysema, the lowest 8,5% values are marked as severe emphysema. The theory behind this is the hypothesis that the histograms of emphysema patients differ from healthy people in a way that in emphysema patients there are relatively more lower values present. In both ways you can finetune the values, or completely change them (if you want to). After loading your image data and mask data, you can inspect the data and examine the severity of the emphysema of the patient. Controls: LMB: The left mouse button can be used to rotate objects in the 3D scene, or to poll Houndsfield Units in areas of interest (click and hold to see the values)\n RMB: For the slice viewers, you can set the window and level values by clicking and holding the right mouse button in a slice and moving your mouse. You can see the current window and level values in the bottom of the viewer. Outside of the slice, this zooms the camera in and out\n MMB: The middle mouse button enables stepping through the slices if clicked and held in the center of the slice. When clicking on de edges of a slice, this re-orients the entire slice. Outside of the slice, this pans the camera\n Scrollwheel: The scrollwheel can be used for zooming in and out of a scene, but also for sliceviewing if used with the CTRL- or SHIFT-key\n SHIFT: By holding the SHIFT-key, it is possible to use the mouse scrollwheel to scroll through the slices.\n CTRL: Holding the CTRL-key does the same, but enables stepping through the data in steps of 10 slices.\n """ def __init__(self, module_manager): """Standard constructor. All DeVIDE modules have these, we do the required setup actions. """ # we record the setting here, in case the user changes it # during the lifetime of this model, leading to different # states at init and shutdown. self.IMAGE_VIEWER = IMAGE_VIEWER # we need all this for our contours self.mask_data = None self.image_data = None self.lungVolume = None self.contour_severe_actor = vtk.vtkActor() self.contour_moderate_actor = vtk.vtkActor() self.contour_lungedge_actor = vtk.vtkActor() self.severe_mapper = vtk.vtkPolyDataMapper() self.severe_mapper.ScalarVisibilityOff() self.moderate_mapper = vtk.vtkPolyDataMapper() self.moderate_mapper.ScalarVisibilityOff() self.lung_mapper = vtk.vtkPolyDataMapper() self.lung_mapper.ScalarVisibilityOff() self.contour_severe_actor.SetMapper(self.severe_mapper) self.contour_severe_actor.GetProperty().SetColor(1, 0, 0) self.contour_severe_actor.GetProperty().SetOpacity(0.5) self.contour_moderate_actor.SetMapper(self.moderate_mapper) self.contour_moderate_actor.GetProperty().SetColor(0.5, 0, 1) self.contour_moderate_actor.GetProperty().SetOpacity(0.25) self.contour_lungedge_actor.SetMapper(self.lung_mapper) self.contour_lungedge_actor.GetProperty().SetColor(0.9, 0.9, 0.9) self.contour_lungedge_actor.GetProperty().SetOpacity(0.1) ModuleBase.__init__(self, module_manager) # create the view frame self._view_frame = module_utils.instantiate_module_view_frame( self, self._module_manager, EmphysemaViewerFrame.EmphysemaViewerFrame) # change the title to something more spectacular (or at least something non-default) self._view_frame.SetTitle('EmphysemaViewer') # create the necessary VTK objects: we only need a renderer, # the RenderWindowInteractor in the view_frame has the rest. self.ren = vtk.vtkRenderer() self.ren.SetBackground(0.5, 0.5, 0.5) self._view_frame.rwi.GetRenderWindow().AddRenderer(self.ren) self.ren.AddActor(self.contour_severe_actor) self.ren.AddActor(self.contour_moderate_actor) self.ren.AddActor(self.contour_lungedge_actor) self.ren2 = vtk.vtkRenderer() self.ren2.SetBackground(0.5, 0.5, 0.5) self._view_frame.overlay.GetRenderWindow().AddRenderer(self.ren2) self.slice_viewer1 = CMSliceViewer(self._view_frame.overlay, self.ren2) self.ren3 = vtk.vtkRenderer() self.ren3.SetBackground(0.5, 0.5, 0.5) self._view_frame.original.GetRenderWindow().AddRenderer(self.ren3) self.slice_viewer2 = CMSliceViewer(self._view_frame.original, self.ren3) self.slice_viewer3 = CMSliceViewer(self._view_frame.rwi, self.ren) self.sync = SyncSliceViewers() self.sync.add_slice_viewer(self.slice_viewer1) self.sync.add_slice_viewer(self.slice_viewer2) self.sync.add_slice_viewer2(self.slice_viewer3) # hook up all event handlers self._bind_events() # anything you stuff into self._config will be saved self._config.last_used_dir = '' # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their # views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic() def close(self): """Clean-up method called on all DeVIDE modules when they are deleted. FIXME: Still get a nasty X error :( """ # with this complicated de-init, we make sure that VTK is # properly taken care of self.ren.RemoveAllViewProps() self.ren2.RemoveAllViewProps() self.ren3.RemoveAllViewProps() # this finalize makes sure we don't get any strange X # errors when we kill the module. self.slice_viewer1.close() self.slice_viewer2.close() self.slice_viewer3.close() self._view_frame.rwi.GetRenderWindow().Finalize() self._view_frame.rwi.SetRenderWindow(None) self._view_frame.overlay.GetRenderWindow().Finalize() self._view_frame.overlay.SetRenderWindow(None) self._view_frame.original.GetRenderWindow().Finalize() self._view_frame.original.SetRenderWindow(None) del self._view_frame.rwi del self._view_frame.overlay del self._view_frame.original del self.slice_viewer3 del self.slice_viewer2 del self.slice_viewer1 # done with VTK de-init # now take care of the wx window self._view_frame.close() # then shutdown our introspection mixin IntrospectModuleMixin.close(self) def get_input_descriptions(self): # define this as a tuple of input descriptions if you want to # take input data e.g. return ('vtkPolyData', 'my kind of # data') return () def get_output_descriptions(self): # define this as a tuple of output descriptions if you want to # generate output data. return () def set_input(self, idx, input_stream): # this gets called right before you get executed. take the # input_stream and store it so that it's available during # execute_module() pass def get_output(self, idx): # this can get called at any time when a consumer module wants # you output data. pass def execute_module(self): # when it's you turn to execute as part of a network # execution, this gets called. pass def logic_to_config(self): pass def config_to_logic(self): pass def config_to_view(self): pass def view_to_config(self): pass def view(self): self._view_frame.Show() self._view_frame.Raise() # because we have an RWI involved, we have to do this # SafeYield, so that the window does actually appear before we # call the render. If we don't do this, we get an initial # empty renderwindow. wx.SafeYield() self.render() def create_volumerender(self, contourValueModerate, contourValueSevere): """Creates a volumerender of the masked data using iso-contour surfaces created by the Marching Cubes algorithm at the specified contourvalues. """ self._view_frame.SetStatusText("Creating Volumerender...") self.image_data mask = vtk.vtkImageMask() severeFraction = 0.10 moderateFraction = 0.12 # We only want to contour the lungs, so mask it mask.SetMaskInput(self.mask_data) mask.SetInput(self.image_data) mask.Update() self.lungVolume = mask.GetOutput() if contourValueModerate == 0 and contourValueSevere == 0: # This means we get to calculate the percentual values ourselves! scalars = self.lungVolume.GetScalarRange() range = scalars[1] - scalars[0] contourValueSevere = scalars[0] + range * severeFraction contourValueModerate = scalars[0] + range * moderateFraction self._view_frame.upper_slider.SetValue(contourValueModerate) self._view_frame.lower_slider.SetValue(contourValueSevere) self.create_overlay(contourValueModerate, contourValueSevere) # Create the contours self.adjust_contour(self.lungVolume, contourValueSevere, self.severe_mapper) self.adjust_contour(self.lungVolume, contourValueModerate, self.moderate_mapper) #self.adjust_contour(self.mask_data, 0.5, self.lung_mapper) self.create_lungcontour() # Set the camera to a nice view cam = self.ren.GetActiveCamera() cam.SetPosition(0, -100, 0) cam.SetFocalPoint(0, 0, 0) cam.SetViewUp(0, 0, 1) self.ren.ResetCamera() self.render() self._view_frame.SetStatusText("Created Volumerender") def adjust_contour(self, volume, contourValue, mapper): """Adjust or create an isocontour using the Marching Cubes surface at the given value using the given mapper """ self._view_frame.SetStatusText("Calculating new volumerender...") contour = vtk.vtkMarchingCubes() contour.SetValue(0, contourValue) contour.SetInput(volume) mapper.SetInput(contour.GetOutput()) mapper.Update() self.render() self._view_frame.SetStatusText("Calculated new volumerender") def create_lungcontour(self): """Create a lungcontour using the Marching Cubes algorithm and smooth the surface """ self._view_frame.SetStatusText("Calculating lungcontour...") contourLung = vtk.vtkMarchingCubes() contourLung.SetValue(0, 1) contourLung.SetInput(self.mask_data) smoother = vtk.vtkWindowedSincPolyDataFilter() smoother.SetInput(contourLung.GetOutput()) smoother.BoundarySmoothingOn() smoother.SetNumberOfIterations(40) smoother.Update() self.lung_mapper.SetInput(smoother.GetOutput()) self.lung_mapper.Update() self._view_frame.SetStatusText("Calculated lungcontour") def create_overlay(self, emphysemavalue, severeemphysemavalue): """Creates an overlay for the slice-based volume view 0: no emphysema 1: moderate emphysema 2: severe emphysema """ self._view_frame.SetStatusText("Creating Overlay...") mask = vtk.vtkImageMask() mask2 = vtk.vtkImageMask() threshold = vtk.vtkImageThreshold() threshold2 = vtk.vtkImageThreshold() math = vtk.vtkImageMathematics() mask.SetInput(self.image_data) mask.SetMaskInput(self.mask_data) threshold.SetInput(mask.GetOutput()) threshold.ThresholdByLower(emphysemavalue) threshold.SetOutValue(0) threshold.SetInValue(1) threshold2.SetInput(mask.GetOutput()) threshold2.ThresholdByLower(severeemphysemavalue) threshold2.SetOutValue(1) threshold2.SetInValue(2) math.SetOperationToMultiply() math.SetInput1(threshold.GetOutput()) math.SetInput2(threshold2.GetOutput()) math.Update() overlay = math.GetOutput() self.slice_viewer1.set_overlay_input(None) self.slice_viewer1.set_overlay_input(overlay) self.render() self._view_frame.SetStatusText("Created Overlay") def load_data_from_file(self, file_path): """Loads scanvolume data from file. Also sets the volume as input for the sliceviewers """ self._view_frame.SetStatusText("Opening file: %s..." % (file_path)) filename = os.path.split(file_path)[1] fileBaseName = os.path.splitext(filename)[0] reader = vtk.vtkMetaImageReader() reader.SetFileName(file_path) reader.Update() self.image_data = reader.GetOutput() self.slice_viewer1.set_input(self.image_data) self.slice_viewer1.reset_camera() self.slice_viewer1.render() self.slice_viewer2.set_input(self.image_data) self.slice_viewer2.reset_camera() self.slice_viewer2.render() self.slice_viewer3.set_input(self.image_data) self.slice_viewer3.render() self.slice_viewer3.set_opacity(0.1) cam = self.ren.GetActiveCamera() cam.SetPosition(0, -100, 0) cam.SetFocalPoint(0, 0, 0) cam.SetViewUp(0, 0, 1) self.ren.ResetCamera() if (self.mask_data ) is not None: # We can start calculating the volumerender self.create_volumerender(0, 0) else: self._view_frame.SetStatusText("Opened file") def load_mask_from_file(self, file_path): """Loads mask file """ self._view_frame.SetStatusText("Opening mask: %s..." % (file_path)) filename = os.path.split(file_path)[1] fileBaseName = os.path.splitext(filename)[0] reader = vtk.vtkMetaImageReader() reader.SetFileName(file_path) reader.Update() self.mask_data = reader.GetOutput() if (self.image_data) is not None: self.create_volumerender(0, 0) else: self._view_frame.SetStatusText("Opened mask file") def save_to_file(self, file_path): """Save data from main renderwindow (the contour one) to a PNG-file """ w2i = vtk.vtkWindowToImageFilter() w2i.SetInput(self._view_frame.rwi.GetRenderWindow()) w2i.Update() writer = vtk.vtkPNGWriter() writer.SetInput(w2i.GetOutput()) writer.SetFileName(file_path) writer.Update() result = writer.Write() if result == 0: self._view_frame.SetStatusText("Saved file") else: self._view_frame.SetStatusText("Saved file to: %s..." % (file_path)) def _bind_events(self): """Bind wx events to Python callable object event handlers. """ vf = self._view_frame vf.Bind(wx.EVT_MENU, self._handler_file_open, id=vf.id_file_open) vf.Bind(wx.EVT_MENU, self._handler_mask_open, id=vf.id_mask_open) vf.Bind(wx.EVT_MENU, self._handler_file_save, id=vf.id_mask_save) self._view_frame.button1.Bind(wx.EVT_BUTTON, self._handler_button1) self._view_frame.button2.Bind(wx.EVT_BUTTON, self._handler_button2) self._view_frame.button3.Bind(wx.EVT_BUTTON, self._handler_button3) self._view_frame.button4.Bind(wx.EVT_BUTTON, self._handler_button4) self._view_frame.button5.Bind(wx.EVT_BUTTON, self._handler_button5) self._view_frame.button6.Bind(wx.EVT_BUTTON, self._handler_button6) self._view_frame.upper_slider.Bind(wx.EVT_SCROLL_CHANGED, self._handler_slider1) self._view_frame.lower_slider.Bind(wx.EVT_SCROLL_CHANGED, self._handler_slider2) def _handler_button1(self, event): """Reset the camera of the main render window """ self.ren.ResetCamera() self.render() def _handler_button2(self, event): """Reset all for the main render window """ cam = self.ren.GetActiveCamera() cam.SetPosition(0, -100, 0) cam.SetFocalPoint(0, 0, 0) cam.SetViewUp(0, 0, 1) self.ren.ResetCamera() self.render() def _handler_button3(self, event): """Reset the camera for the sliceviewers """ self.slice_viewer1.reset_camera() self.slice_viewer2.reset_camera() self.render() def _handler_button4(self, event): """Reset all for the sliceviewers """ self.slice_viewer1.reset_to_default_view(2) self.slice_viewer2.reset_to_default_view(2) orientations = [2, 0, 1] for i, ipw in enumerate(self.slice_viewer1.ipws): ipw.SetPlaneOrientation(orientations[i]) # axial ipw.SetSliceIndex(0) self.render() for i, ipw in enumerate(self.slice_viewer2.ipws): ipw.SetPlaneOrientation(orientations[i]) # axial ipw.SetSliceIndex(0) self.render() def _handler_button5(self, event): """Adjust the contourvalues to values recommended in literature """ if self.lungVolume == None: return else: self._view_frame.upper_slider.SetValue(-950) self._view_frame.lower_slider.SetValue(-970) self.adjust_contour(self.lungVolume, -950, self.moderate_mapper) self.adjust_contour(self.lungVolume, -970, self.severe_mapper) self.create_overlay(-950, -970) def _handler_button6(self, event): """Adjust the contourvalues to values calculated from data """ if self.lungVolume == None: return else: self.create_volumerender(0, 0) def _handler_file_open(self, event): """Handler for file opening """ filters = 'Volume files (*.mhd)|*.mhd;' dlg = wx.FileDialog(self._view_frame, "Please choose a CT-thorax file", self._config.last_used_dir, "", filters, wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetFilename() self._config.last_used_dir = dlg.GetDirectory() full_file_path = "%s/%s" % (self._config.last_used_dir, filename) self.load_data_from_file(full_file_path) dlg.Destroy() def _handler_mask_open(self, event): """Handler for mask opening """ filters = 'Mask files (*.mhd;#.mha)|*.mhd;*mha;' dlg = wx.FileDialog(self._view_frame, "Please choose a CT-thorax mask file", self._config.last_used_dir, "", filters, wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetFilename() self._config.last_used_dir = dlg.GetDirectory() full_file_path = "%s/%s" % (self._config.last_used_dir, filename) self.load_mask_from_file(full_file_path) dlg.Destroy() def _handler_file_save(self, event): """Handler for filesaving """ self._view_frame.SetStatusText("Saving file...") filters = 'png file (*.png)|*.png' dlg = wx.FileDialog(self._view_frame, "Choose a destination", self._config.last_used_dir, "", filters, wx.SAVE) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetFilename() self._config.last_used_dir = dlg.GetDirectory() file_path = "%s/%s" % (self._config.last_used_dir, filename) self.save_to_file(file_path) dlg.Destroy() self._view_frame.SetStatusText("Saved file") def _handler_slider1(self, event): """Handler for slider adjustment (Severe emphysema) """ if self.lungVolume == None: return else: contourValue = self._view_frame.upper_slider.GetValue() self.adjust_contour(self.lungVolume, contourValue, self.moderate_mapper) self.create_overlay(contourValue, self._view_frame.lower_slider.GetValue()) def _handler_slider2(self, event): """Handler for slider adjustment (Moderate emphysema) """ if self.lungVolume == None: return else: contourValue = self._view_frame.lower_slider.GetValue() self.adjust_contour(self.lungVolume, contourValue, self.severe_mapper) self.create_overlay(self._view_frame.upper_slider.GetValue(), contourValue) def render(self): """Method that calls Render() on the embedded RenderWindow. Use this after having made changes to the scene. """ self._view_frame.render() self.slice_viewer1.render()
def _init_frame(): # call base constructor ModuleBase.__init__(self, module_manager) self._numDataInputs = self.NUM_INPUTS # use list comprehension to create list keeping track of inputs self._inputs = [{'Connected' : None, 'inputData' : None, 'vtkActor' : None, 'ipw' : None} for i in range(self._numDataInputs)] # create the view frame self.frame = module_utils.instantiate_module_view_frame( self, self._module_manager, multiDirectionalSlicedViewSegmentation3dVieWeRFrame.multiDirectionalSlicedViewSegmentation3dVieWeRFrame) #THE FRAME (reference) frame = self.frame # change the title to something more spectacular (or at least something non-default) frame.SetTitle('multiDirectionalSlicedViewSegmentation3dVieWeR') # predefine this self.selectedData = [] self.controlIsCurrentlyDown = 0 # list of objects that want to be contoured by this slice self._contourObjectsDict1 = {} self._contourObjectsDict2 = {} self._contourObjectsDict3 = {} # anything you stuff into self._config will be saved self._config.last_used_dir = '' #color definitions twoD_bg_color = (0.19,0.19,0.19) threeD_bg_color = (0.62,0.62,0.62) contour_color = (0.6,0.6,0.6) # create the necessary VTK objects # setup the Top Renderer (id: 1) self.renderer_top = vtk.vtkRenderer() self.renderer_top.SetBackground(twoD_bg_color) frame.top.GetRenderWindow().AddRenderer(self.renderer_top) self.slice_viewer_top = CMSliceViewer(frame.top, self.renderer_top) self.slice_viewer_top.set_parallel() # setup the Side Renderer (id: 2) self.renderer_side = vtk.vtkRenderer() self.renderer_side.SetBackground(twoD_bg_color) frame.side.GetRenderWindow().AddRenderer(self.renderer_side) self.slice_viewer_side = CMSliceViewer(frame.side, self.renderer_side) self.slice_viewer_side.set_parallel() # setup the Front Renderer (id: 3) self.renderer_front = vtk.vtkRenderer() self.renderer_front.SetBackground(twoD_bg_color) frame.front.GetRenderWindow().AddRenderer(self.renderer_front) self.slice_viewer_front = CMSliceViewer(frame.front, self.renderer_front) self.slice_viewer_front.set_parallel() # setup the 3D Renderer (id: 4) self.contour_actor = vtk.vtkActor() self.contour_mapper = vtk.vtkPolyDataMapper() self.contour_mapper.ScalarVisibilityOff() self.contour_selected_actors = [] self.contour_actor.SetMapper(self.contour_mapper) self.contour_actor.GetProperty().SetColor(contour_color) self._on_slide_transparency() self.renderer_3d = vtk.vtkRenderer() self.renderer_3d.SetBackground(threeD_bg_color) self.renderer_3d.AddActor(self.contour_actor) frame.view3d.GetRenderWindow().AddRenderer(self.renderer_3d) frame.view3d._outline_source = vtk.vtkOutlineSource() om = vtk.vtkPolyDataMapper() om.SetInput(frame.view3d._outline_source.GetOutput()) frame.view3d._outline_actor = vtk.vtkActor() frame.view3d._outline_actor.SetMapper(om) frame.view3d._cInteractorStyle = vtk.vtkInteractorStyleTrackballCamera() frame.view3d.SetInteractorStyle(frame.view3d._cInteractorStyle) frame.view3d._orientation_widget.On() # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic() self.clearSeedPoints()
class multiDirectionalSlicedViewSegmentation3dVieWeR(IntrospectModuleMixin, ModuleBase): """Module to visualize VTI images, easily combining axial, coronal and sagittal data in one screen, and giving the possibility to select seedpoints that will grow into contours on the 3D screen. The multiDirectionalSlicedViewSegmentation3dVieWeR consists of three sliceviewers, and a 3D viewer. There are two ways of setting the input. - The first way is using a vtiRDR, to load the data via the network. - The other way is just using the file browser to collect a VTI file from your file system. After loading your image data , you can inspect the data and examine the patient, by highlighting one or multiple areas by your choice. Controls: LMB: The left mouse button can be used to select a seedpoint from the different 2D views\n CTRL + LMB: Holding the CTRL-key does the same, but adds a seedpoint to your selection, LMB then returns your selection to a single point\n RMB: For the slice viewers, you can set the window and level values by clicking and holding the right mouse button in a slice and moving your mouse. You can see the current window and level values in the bottom of the viewer. Outside of the slice, this zooms the camera in and out\n MMB: The middle mouse button enables stepping through the slices if clicked and held in the center of the slice. When clicking on de edges of a slice, this re-orients the entire slice. Outside of the slice, this pans the camera\n Scrollwheel: The scrollwheel can be used for zooming in and out of a scene\n """ NUM_INPUTS = 1 PARTS_TO_INPUTS = {0 : tuple(range(NUM_INPUTS))} # PARTS_TO_OUTPUTS = {0 : (3,4), 1 : (0,4), 2 : (1,4), 3 : (2,4)} def __init__(self, module_manager): """Standard constructor. All DeVIDE modules have these, we do the required setup actions. """ def _init_frame(): # call base constructor ModuleBase.__init__(self, module_manager) self._numDataInputs = self.NUM_INPUTS # use list comprehension to create list keeping track of inputs self._inputs = [{'Connected' : None, 'inputData' : None, 'vtkActor' : None, 'ipw' : None} for i in range(self._numDataInputs)] # create the view frame self.frame = module_utils.instantiate_module_view_frame( self, self._module_manager, multiDirectionalSlicedViewSegmentation3dVieWeRFrame.multiDirectionalSlicedViewSegmentation3dVieWeRFrame) #THE FRAME (reference) frame = self.frame # change the title to something more spectacular (or at least something non-default) frame.SetTitle('multiDirectionalSlicedViewSegmentation3dVieWeR') # predefine this self.selectedData = [] self.controlIsCurrentlyDown = 0 # list of objects that want to be contoured by this slice self._contourObjectsDict1 = {} self._contourObjectsDict2 = {} self._contourObjectsDict3 = {} # anything you stuff into self._config will be saved self._config.last_used_dir = '' #color definitions twoD_bg_color = (0.19,0.19,0.19) threeD_bg_color = (0.62,0.62,0.62) contour_color = (0.6,0.6,0.6) # create the necessary VTK objects # setup the Top Renderer (id: 1) self.renderer_top = vtk.vtkRenderer() self.renderer_top.SetBackground(twoD_bg_color) frame.top.GetRenderWindow().AddRenderer(self.renderer_top) self.slice_viewer_top = CMSliceViewer(frame.top, self.renderer_top) self.slice_viewer_top.set_parallel() # setup the Side Renderer (id: 2) self.renderer_side = vtk.vtkRenderer() self.renderer_side.SetBackground(twoD_bg_color) frame.side.GetRenderWindow().AddRenderer(self.renderer_side) self.slice_viewer_side = CMSliceViewer(frame.side, self.renderer_side) self.slice_viewer_side.set_parallel() # setup the Front Renderer (id: 3) self.renderer_front = vtk.vtkRenderer() self.renderer_front.SetBackground(twoD_bg_color) frame.front.GetRenderWindow().AddRenderer(self.renderer_front) self.slice_viewer_front = CMSliceViewer(frame.front, self.renderer_front) self.slice_viewer_front.set_parallel() # setup the 3D Renderer (id: 4) self.contour_actor = vtk.vtkActor() self.contour_mapper = vtk.vtkPolyDataMapper() self.contour_mapper.ScalarVisibilityOff() self.contour_selected_actors = [] self.contour_actor.SetMapper(self.contour_mapper) self.contour_actor.GetProperty().SetColor(contour_color) self._on_slide_transparency() self.renderer_3d = vtk.vtkRenderer() self.renderer_3d.SetBackground(threeD_bg_color) self.renderer_3d.AddActor(self.contour_actor) frame.view3d.GetRenderWindow().AddRenderer(self.renderer_3d) frame.view3d._outline_source = vtk.vtkOutlineSource() om = vtk.vtkPolyDataMapper() om.SetInput(frame.view3d._outline_source.GetOutput()) frame.view3d._outline_actor = vtk.vtkActor() frame.view3d._outline_actor.SetMapper(om) frame.view3d._cInteractorStyle = vtk.vtkInteractorStyleTrackballCamera() frame.view3d.SetInteractorStyle(frame.view3d._cInteractorStyle) frame.view3d._orientation_widget.On() # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic() self.clearSeedPoints() # END OF _INIT_FRAME _init_frame() self._reset_frame() self._bind_events() def _bind_events(self): """Bind wx events to Python callable object event handlers. """ frame = self.frame #CONTROL check frame.top.Bind(wx.EVT_LEFT_UP, self.onLeftUp) frame.side.Bind(wx.EVT_LEFT_UP, self.onLeftUp) frame.front.Bind(wx.EVT_LEFT_UP, self.onLeftUp) # bind onClickedAViewer self.slice_viewer_top.ipws[0].AddObserver('StartInteractionEvent', lambda e, o: self._ipwStartInteractionCallback(1)) self.slice_viewer_top.ipws[0].AddObserver('InteractionEvent', lambda e, o: self._ipwInteractionCallback(1)) self.slice_viewer_top.ipws[0].AddObserver('EndInteractionEvent', lambda e, o: self._ipwEndInteractionCallback(1, e)) self.slice_viewer_side.ipws[0].AddObserver('StartInteractionEvent', lambda e, o: self._ipwStartInteractionCallback(2)) self.slice_viewer_side.ipws[0].AddObserver('InteractionEvent', lambda e, o: self._ipwInteractionCallback(2)) self.slice_viewer_side.ipws[0].AddObserver('EndInteractionEvent', lambda e, o: self._ipwEndInteractionCallback(2, e)) self.slice_viewer_front.ipws[0].AddObserver('StartInteractionEvent', lambda e, o: self._ipwStartInteractionCallback(3)) self.slice_viewer_front.ipws[0].AddObserver('InteractionEvent', lambda e, o: self._ipwInteractionCallback(3)) self.slice_viewer_front.ipws[0].AddObserver('EndInteractionEvent', lambda e, o: self._ipwEndInteractionCallback(3, e)) # bind onScrollViewer frame.view3d.Unbind(wx.EVT_MOUSEWHEEL) frame.view3d.Bind(wx.EVT_MOUSEWHEEL, lambda evt: self._on_scroll_viewer(evt, 4)) # bind onChangeSliderSlice frame.top_zoomer.Bind(wx.EVT_SLIDER, lambda evt: self._on_slide_slice(evt, 1)) frame.side_zoomer.Bind(wx.EVT_SLIDER, lambda evt: self._on_slide_slice(evt, 2)) frame.front_zoomer.Bind(wx.EVT_SLIDER, lambda evt: self._on_slide_slice(evt, 3)) frame.top_zoomer.Bind(wx.EVT_SCROLL_CHANGED, lambda evt: self._on_zoomer_released(evt, 1)) frame.side_zoomer.Bind(wx.EVT_SCROLL_CHANGED, lambda evt: self._on_zoomer_released(evt, 2)) frame.front_zoomer.Bind(wx.EVT_SCROLL_CHANGED, lambda evt: self._on_zoomer_released(evt, 3)) # bind onChangeSliderToleranceLow frame.lower_slider.Bind(wx.EVT_SCROLL_CHANGED, self._on_slide_tolerance_low) # bind onChangeSliderToleranceHigh frame.upper_slider.Bind(wx.EVT_SCROLL_CHANGED, self._on_slide_tolerance_high) # bind onChangeSliderTransparency frame.transparency_slider.Bind(wx.EVT_SCROLL_CHANGED, self._on_slide_transparency) # bind onChangeSelectionColor frame.color_picker.Bind(wx.EVT_COLOURPICKER_CHANGED, self._on_select_new_color) # bind onCheckContinuous frame.continuous_check.Bind(wx.EVT_CHECKBOX, self._on_check_continuous) # bind onClickresetViewer frame.reset_top.Bind(wx.EVT_BUTTON, lambda x: self._reset_viewer(1, False)) frame.reset_side.Bind(wx.EVT_BUTTON, lambda x: self._reset_viewer(2, False)) frame.reset_front.Bind(wx.EVT_BUTTON, lambda x: self._reset_viewer(3, False)) frame.reset_view3d.Bind(wx.EVT_BUTTON, lambda x: self._reset_viewer(4, False)) # bind onClickFileButton frame.file_button.Bind(wx.EVT_BUTTON, self._on_clicked_btn_new_file) # bind onClickSaveSnapshotButton frame.save_button.Bind(wx.EVT_BUTTON, self._save_snapshot) # bind onClickRemoveSeedPointsButtom frame.seedpoint_button.Bind(wx.EVT_BUTTON, self.removeSeedPoints) def _ipwStartInteractionCallback(self, viewer_id): """Method for handling seedpoint selection in the ipw """ self.tempCursorData = None self._ipwInteractionCallback(viewer_id) def _ipwInteractionCallback(self, viewer_id): """Method for handling seedpoint selection in the ipw """ cd = 4 * [0.0] if viewer_id == 1 and self.slice_viewer_top.ipws[0].GetCursorData(cd): self.tempCursorData = cd elif viewer_id == 2 and self.slice_viewer_side.ipws[0].GetCursorData(cd): self.tempCursorData = cd elif viewer_id == 3 and self.slice_viewer_front.ipws[0].GetCursorData(cd): self.tempCursorData = cd def onLeftUp(self, evt): """Method for handling seedpoint selection in the ipw """ self.controlIsCurrentlyDown = evt.ControlDown() evt.Skip() def _ipwEndInteractionCallback(self, viewer_id, event): """Method for handling seedpoint selection in the ipw """ if not (self.controlIsCurrentlyDown): self.clearSeedPoints() self.addSeedPoint(self.tempCursorData) self._calculate_selection() def clearSeedPoints(self): """Method for clearing the seedpoint list """ self.seedPoints = [] self.frame.seedpoint_list.DeleteAllItems() def addSeedPoint(self, point): """Method for adding a point to the seedpoint list """ self.seedPoints.append(point) index = len(self.seedPoints) - 1 self.frame.seedpoint_list.InsertStringItem(index, str(point[0]).rstrip('0').rstrip('.')) self.frame.seedpoint_list.SetStringItem(index, 1, str(point[1]).rstrip('0').rstrip('.')) self.frame.seedpoint_list.SetStringItem(index, 2, str(point[2]).rstrip('0').rstrip('.')) self.frame.seedpoint_list.SetStringItem(index, 3, str(point[3]).rstrip('0').rstrip('.')) def removeSeedPoints(self, event): """Event method call to remove selected seedpoints from list """ points = self.frame.seedpoint_list count = points.GetSelectedItemCount() if count <= 0: return while (count > 0) : itemIndex = points.GetFirstSelected() #recreate point to be able to remove from array point = [] for i in range(0,points.GetColumnCount()): point.append(float(points.GetItem(itemIndex, i).GetText())) #delete item self.seedPoints.remove(point) points.DeleteItem(itemIndex) count -= 1 self._reset_viewer(4, False) def _on_scroll_viewer(self, event, viewer_id): if viewer_id == 1: # Top Viewer return elif viewer_id == 2: # Side Viewer return elif viewer_id == 3: # Front Viewer return elif viewer_id == 4: # 3D Viewer # event.GetWheelRotation() is + or - 120 depending on # direction of turning. if event.ControlDown(): delta = 10 elif event.ShiftDown(): delta = 1 else: # if user is NOT doing shift / control, we pass on to the # default handling which will give control to the VTK # mousewheel handlers. self.frame.view3d.OnMouseWheel(event) return selected_sds = self.sliceDirections.getSelectedSliceDirections() if len(selected_sds) == 0: if len(self.sliceDirections._sliceDirectionsDict) == 1: # convenience: nothing selected, but there is only one SD, use that then! sd = self.sliceDirections._sliceDirectionsDict.items()[0][1] else: return else: sd = selected_sds[0] if event.GetWheelRotation() > 0: sd.delta_slice(+delta) else: sd.delta_slice(-delta) self.render() def _on_slide_slice(self, event, viewer_id): """Handler for zoomer interaction (sliders on 2D views) """ if self._inputs[0]['inputData'] == None: return sv = None slicer_max = 0 if viewer_id == 1: # Top Viewer sv = self.slice_viewer_top slicer_max = self.top_zoomer_max elif viewer_id == 2: # Side Viewer sv = self.slice_viewer_side slicer_max = self.side_zoomer_max elif viewer_id == 3: # Front Viewer sv = self.slice_viewer_front slicer_max = self.front_zoomer_max if not(sv == None): value = slicer_max - event.GetEventObject().GetValue() sv.ipws[0].SetSliceIndex(value) self.render() def _on_zoomer_released(self, event, viewerIndex): """Handler for slider adjustment end (Zoomers) """ for data in self.selectedData: self.syncContourToObject(viewerIndex, data) def _on_slide_tolerance_low(self, event): """Handler for slider adjustment (Lower Threshold) """ if len(self.seedPoints) == 0: return else: self._calculate_selection() def _on_slide_tolerance_high(self, event): """Handler for slider adjustment (Upper Threshold) """ if len(self.seedPoints) == 0: return else: self._calculate_selection() def _on_select_new_color(self, event = None): """Handler for color adjustment (Color of selection) """ for actor in self.contour_selected_actors: actor.GetProperty().SetColor(self.frame.color_picker.GetColour().Get()) def _on_slide_transparency(self, event = None): """Handler for slider adjustment (Transparency of unselected Actors) """ self.contour_actor.GetProperty().SetOpacity(float(self.frame.transparency_slider.GetValue()) / 100) def _on_check_continuous(self, event): """Handler for checkbox adjustment (Continous selection) """ if len(self.seedPoints) == 0: return else: self._calculate_selection() def _on_clicked_btn_new_file(self, event): """Handler for file opening """ filters = 'Volume files (*.vti)|*.vti;' dlg = wx.FileDialog(self.frame, "Please choose a VTI file", self._config.last_used_dir, "", filters, wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename=dlg.GetFilename() self._config.last_used_dir=dlg.GetDirectory() full_file_path = "%s/%s" % (self._config.last_used_dir, filename) self._load_data_from_file(full_file_path) dlg.Destroy() def _load_data_from_file(self, file_path): """Loads scanvolume data from file. Also sets the volume as input for the sliceviewers """ #self.frame.SetStatusText("Opening file: %s..." % (file_path)) filename = os.path.split(file_path)[1] fileBaseName = os.path.splitext(filename)[0] self.frame._set_filename(filename) reader = vtk.vtkXMLImageDataReader() reader.SetFileName(file_path) reader.Update() self.set_input(0, reader.GetOutput()) def _save_snapshot(self, event): """Handler for filesaving """ filters = 'png file (*.png)|*.png' dlg = wx.FileDialog(self.frame, "Choose a destination", self._config.last_used_dir, "", filters, wx.SAVE) if dlg.ShowModal() == wx.ID_OK: filename=dlg.GetFilename() self._config.last_used_dir=dlg.GetDirectory() file_path = "%s/%s" % (self._config.last_used_dir, filename) w2i = vtk.vtkWindowToImageFilter() w2i.SetInput(self.frame.view3d.GetRenderWindow()); w2i.Update() writer = vtk.vtkPNGWriter() writer.SetInput(w2i.GetOutput()) writer.SetFileName(file_path) writer.Update() result = writer.Write() dlg.Destroy() def _reset_frame(self, event = None): """Handler for resetting the frame """ self._reset_controls() self._reset_all_viewers() def _reset_controls(self, event = None): """Handler for resetting the controls """ self.frame._reset_controls() self._calculate_selection() def _reset_all_viewers(self, event = None): """Handler for resetting all viewer """ self._reset_viewer(1, True) self._reset_viewer(2, True) self._reset_viewer(3, True) self._reset_viewer(4, True) def _reset_viewer(self, viewer_id, reset_zoom, event = None): """Handler for resetting a specific viewer """ def resetCamera(viewerIndex): sliceViewer = None renderer = None if(viewerIndex == 1): sliceViewer = self.slice_viewer_top renderer = self.renderer_top elif(viewerIndex == 2): sliceViewer = self.slice_viewer_side renderer = self.renderer_side elif(viewerIndex ==3): sliceViewer = self.slice_viewer_front renderer = self.renderer_front elif(viewerIndex ==4): sliceViewer = self.frame.view3d; renderer = self.renderer_3d cam = renderer.GetActiveCamera() # then make sure it's up is the right way if(viewerIndex == 1): cam.SetViewUp(0, 1 ,0) posX = 0 posY = 0 posZ = 1 cam.SetPosition(posX, posY, posZ) cam.SetFocalPoint(0, 0, 0) elif(viewerIndex == 2): cam.SetViewUp(0, 1 ,0) posX = 1 posY = 0 posZ = 0 cam.SetPosition(posX, posY, posZ) cam.SetFocalPoint(0, 0, 0) elif(viewerIndex ==3): cam.SetViewUp(0,1,0) posX = 0 posY = -1 posZ = 0 cam.SetPosition(posX, posY, posZ) cam.SetFocalPoint(0, 0, 0) if(viewerIndex == 4): cam.SetViewUp(0, 1, 0) fp = cam.GetFocalPoint() cp = cam.GetPosition() if cp[2] < fp[2]: z = fp[2] + (fp[2] - cp[2]) else: z = cp[2] posX = fp[0] posY = fp[1] posZ = z cam.SetFocalPoint(0, 99999999, 0) #Look towards infinity cam.SetPosition(posX, posY, posZ) #print("Setting camera " + str(viewerIndex) + " to (" + str(posX) + ", " + str(posY) + ", " + str(posZ) + ")") # first reset the camera renderer.ResetCamera() if(viewerIndex == 4): sliceViewer.Render() else: sliceViewer.render() if self._inputs[0]['inputData'] == None: return else: size = self._inputs[0]['inputData'].GetDimensions() colorRange = self._inputs[0]['inputData'].GetScalarRange() if viewer_id == 1: # Top Viewer if(reset_zoom): self.top_zoomer_max = size[2]-1 self.frame.top_zoomer.SetMax(self.top_zoomer_max) value = self.top_zoomer_max / 2 self.frame.top_zoomer.SetValue(value) self.slice_viewer_top.ipws[0].SetPlaneOrientation(2) for i, ipw in enumerate(self.slice_viewer_top.ipws): ipw.SetSliceIndex(value) ipw.GetColorMap().GetLookupTable().SetRange(colorRange[0], colorRange[1]) resetCamera(1) elif viewer_id == 2: # Side Viewer if(reset_zoom): self.side_zoomer_max = size[0]-1 self.frame.side_zoomer.SetMax(self.side_zoomer_max) value = self.side_zoomer_max / 2 self.frame.side_zoomer.SetValue(value) self.slice_viewer_side.ipws[0].SetPlaneOrientation(0) for i, ipw in enumerate(self.slice_viewer_side.ipws): ipw.SetSliceIndex(value) ipw.GetColorMap().GetLookupTable().SetRange(colorRange[0], colorRange[1]) resetCamera(2) elif viewer_id == 3: # Front Viewer if(reset_zoom): self.front_zoomer_max = size[1]-1 self.frame.front_zoomer.SetMax(self.front_zoomer_max) value = self.front_zoomer_max / 2 self.frame.front_zoomer.SetValue(value) self.slice_viewer_front.ipws[0].SetPlaneOrientation(1) for i, ipw in enumerate(self.slice_viewer_front.ipws): ipw.SetSliceIndex(value) ipw.GetColorMap().GetLookupTable().SetRange(colorRange[0], colorRange[1]) resetCamera(3) elif viewer_id == 4: # 3D Viewer self._update_3d_renderer(self._inputs[0]['inputData']) resetCamera(4) self.render() return def set_input(self, idx, input_stream): # this gets called right before you get executed. take the # input_stream and store it so that it's available during execute_module() def add_primary_init(input_stream): """After a new primary has been added, a number of other actions have to be performed. """ # add outline actor to renderer self.renderer_3d.AddActor(self.frame.view3d._outline_actor) self.frame.view3d._outline_actor.PickableOff() # end of method add_primary_init() def _handleNewImageDataInput(): connecteds = [i['Connected'] for i in self._inputs] # if we already have a primary, make sure the new inputStream # is added at a higher port number than all existing # primaries and overlays if 'vtkImageDataPrimary' in connecteds: highestPortIndex = connecteds.index('vtkImageDataPrimary') for i in range(highestPortIndex, len(connecteds)): if connecteds[i] == 'vtkImageDataOverlay': highestPortIndex = i if idx <= highestPortIndex: raise Exception, \ "Please add overlay data at higher input " \ "port numbers " \ "than existing primary data and overlays." # find out whether this is primary or an overlay, record it if 'vtkImageDataPrimary' in connecteds: # there's no way there can be only overlays in the list, # the check above makes sure of that self._inputs[idx]['Connected'] = 'vtkImageDataOverlay' else: # there are no primaries or overlays, this must be # a primary then self._inputs[idx]['Connected'] = 'vtkImageDataPrimary' # also store binding to the data itself self._inputs[idx]['inputData'] = input_stream if self._inputs[idx]['Connected'] == 'vtkImageDataPrimary': # things to setup when primary data is added add_primary_init(input_stream) if self.frame._get_filename() == None: self.frame._set_filename('FROM NETWORK') self.selectedData = [] self.contour_selected_actors = [] self._contourObjectsDict1 = {} self._contourObjectsDict2 = {} self._contourObjectsDict3 = {} self.clearSeedPoints() #set the input on the 2d slice viewers self._update_2d_renderers(input_stream) #update our 3d renderer self._update_3d_renderer(input_stream) self._reset_all_viewers() # end of function _handleImageDataInput() if not(input_stream == None): if input_stream.IsA('vtkImageData'): if self._inputs[idx]['Connected'] is None: _handleNewImageDataInput() else: # take necessary actions to refresh prevData = self._inputs[idx]['inputData'] #set the input on the 2d slice viewers self._update_2d_renderers(input_stream) #update our 3d renderer self._update_3d_renderer(input_stream) # record it in our main structure self._inputs[idx]['inputData'] = input_stream if self.frame._get_filename() == None: self.frame._set_filename('FROM NETWORK') self._reset_all_viewers() else: print "ERROR: input_stream isn't vtkImageData!" else: self.frame._set_filename() print "No input_stream" # end of set_input def _update_2d_renderers(self, input_stream): """Convenience method to pass the input stream to the 2d slice viewers """ try: self.slice_viewer_top.set_input(input_stream) self.slice_viewer_side.set_input(input_stream) self.slice_viewer_front.set_input(input_stream) except: print "ISSUE: adding input_streams" def _update_3d_renderer(self, input_stream): """Calculate the contour based on the input data """ self._config.iso_value = 128 contourFilter = vtk.vtkContourFilter() contourFilter.SetInput(input_stream) contourFilter.Update() data = contourFilter.GetOutput() self.contour_mapper.SetInput(data) self.contour_mapper.Update() self._calculate_selection() self.renderer_3d.ResetCamera() return data def _calculate_selection(self): """Calculate the selection contour based on the input data and seedpoints """ for actor in self.contour_selected_actors: self.renderer_top.RemoveActor(actor) self.renderer_side.RemoveActor(actor) self.renderer_front.RemoveActor(actor) self.renderer_3d.RemoveActor(actor) # initial cleanup self.selectedData = [] self.contour_selected_actors = [] self._contourObjectsDict1 = {} self._contourObjectsDict2 = {} self._contourObjectsDict3 = {} if self.frame.continuous_check.GetValue(): for seed_point in self.seedPoints: #self._config._thresh_interval = 5 _image_threshold = vtk.vtkImageThreshold() # seedconnect wants unsigned char at input _image_threshold.SetOutputScalarTypeToUnsignedChar() _image_threshold.SetInValue(1) _image_threshold.SetOutValue(0) _image_threshold.SetInput(self._inputs[0]['inputData']) _seed_connect = vtk.vtkImageSeedConnectivity() _seed_connect.SetInputConnectValue(1) _seed_connect.SetOutputConnectedValue(1) _seed_connect.SetOutputUnconnectedValue(0) _seed_connect.SetInput(_image_threshold.GetOutput()) # extract a list from the input points _seed_connect.RemoveAllSeeds() # we need to call Modified() explicitly as RemoveAllSeeds() # doesn't. AddSeed() does, but sometimes the list is empty at # this stage and AddSeed() isn't called. _seed_connect.Modified() for seedPoint in self.seedPoints: if not seedPoint == None: _seed_connect.AddSeed(seedPoint[0], seedPoint[1], seedPoint[2]) #Determine threshold iso_value = seed_point[3] lower_thresh = iso_value + self.frame.lower_slider.GetValue() upper_thresh = iso_value + self.frame.upper_slider.GetValue() _image_threshold.ThresholdBetween(lower_thresh, upper_thresh) #Update all stuff _image_threshold.GetInput().Update() _image_threshold.Update() _seed_connect.Update() #Create the contour contourFilter = vtk.vtkContourFilter() contourFilter.SetInput(_seed_connect.GetOutput()) contourFilter.GenerateValues(contourFilter.GetNumberOfContours(), 1, 1) #because 1 is output in-value contourFilter.Update() # Setup Actor and Mapper actor = vtk.vtkActor() mapper = vtk.vtkPolyDataMapper() mapper.ScalarVisibilityOff() actor.SetMapper(mapper) self.renderer_3d.AddActor(actor) # Set output to mapper data = contourFilter.GetOutput() mapper.SetInput(data) mapper.Update() self.addSelectionTo2DViewers(data, actor) # Save result self.selectedData.append(data) self.contour_selected_actors.append(actor) #End for-loop else: for seedPoint in self.seedPoints: if not seedPoint == None: iso_value = seedPoint[3] #print "working on seedpoint with iso" + str(iso_value) # Setup Actor and Mapper actor = vtk.vtkActor() mapper = vtk.vtkPolyDataMapper() mapper.ScalarVisibilityOff() actor.SetMapper(mapper) self.renderer_3d.AddActor(actor) # Calculate Polydata contourFilter = vtk.vtkContourFilter() contourFilter.SetInput(self._inputs[0]['inputData']) contourFilter.GenerateValues(contourFilter.GetNumberOfContours(), iso_value + self.frame.lower_slider.GetValue(), iso_value + self.frame.upper_slider.GetValue()) contourFilter.Update() # Set output to mapper data = contourFilter.GetOutput() mapper.SetInput(data) mapper.Update() self.addSelectionTo2DViewers(data, actor) # Save result self.selectedData.append(data) self.contour_selected_actors.append(actor) # Set colors self._on_select_new_color() self.render() return self.selectedData def addSelectionTo2DViewers(self, data, actor): self.addContourObject(1, data, actor) self.addContourObject(2, data, actor) self.addContourObject(3, data, actor) def addContourObject(self, viewerIndex, contourObject, prop3D): """Activate contouring for the contourObject. The contourObject is usually a tdObject and specifically a vtkPolyData. We also need the prop3D that represents this polydata in the 3d scene. """ #TODO include this to show selection in 2d renders #if self._contourObjectsDict.has_key(contourObject): # # we already have this, thanks # return #prop3D = vtk.vtkActor() renderer = None if viewerIndex == 1: renderer = self.renderer_top elif viewerIndex == 2: renderer = self.renderer_side elif viewerIndex == 3: renderer = self.renderer_front try: contourable = contourObject.IsA('vtkPolyData') except: contourable = False if contourable: # we need a cutter to calculate the contours and then a stripper # to string them all together cutter = vtk.vtkCutter() plane = vtk.vtkPlane() cutter.SetCutFunction(plane) trfm = vtk.vtkTransform() trfm.SetMatrix(prop3D.GetMatrix()) trfmFilter = vtk.vtkTransformPolyDataFilter() trfmFilter.SetTransform(trfm) trfmFilter.SetInput(contourObject) cutter.SetInput(trfmFilter.GetOutput()) stripper = vtk.vtkStripper() stripper.SetInput(cutter.GetOutput()) cutter.SetValue(0, 1) # #tubef = vtk.vtkTubeFilter() #tubef.SetNumberOfSides(12) #tubef.SetRadius(0.5) #tubef.SetInput(stripper.GetOutput()) # and create the overlay at least for the 3d renderer mapper = vtk.vtkPolyDataMapper() mapper.SetInput(stripper.GetOutput()) mapper.ScalarVisibilityOff() actor = vtk.vtkActor() actor.SetMapper(mapper) #color = self.sliceDirections.slice3dVWR._tdObjects.getObjectColour( # contourObject) actor.GetProperty().SetColor(self.frame.color_picker.GetColour().Get()) actor.GetProperty().SetInterpolationToFlat() # add it to the renderer self.contour_selected_actors.append(actor) renderer.AddActor(actor) # add all necessary metadata to our dict contourDict = {'contourObject' : contourObject, 'contourObjectProp' : prop3D, 'trfmFilter' : trfmFilter, 'cutter' : cutter, 'tdActor' : actor} if viewerIndex == 1: self._contourObjectsDict1[contourObject] = contourDict elif viewerIndex == 2: self._contourObjectsDict2[contourObject] = contourDict elif viewerIndex == 3: self._contourObjectsDict3[contourObject] = contourDict # now sync the bugger self.syncContourToObject(viewerIndex, contourObject) else: print "Error: polyData is not Contourable!!!" def syncContourToObject(self, viewerIndex, contourObject): """Update the contour for the given contourObject. contourObject corresponds to a tdObject in tdObjects.py. """ # yes, in and not in work on dicts, doh #if contourObject not in self._contourObjectsDict: # print "Error!! contourObject not in _contourobjects!" # return #else: # print "syncContourToObject!" slicerPlane = None contourDict = None if viewerIndex == 1: slicerPlane = self.slice_viewer_top.ipws[0] contourDict = self._contourObjectsDict1[contourObject] elif viewerIndex == 2: slicerPlane = self.slice_viewer_side.ipws[0] contourDict = self._contourObjectsDict2[contourObject] elif viewerIndex == 3: slicerPlane = self.slice_viewer_front.ipws[0] contourDict = self._contourObjectsDict3[contourObject] # get the contourObject metadata cutter = contourDict['cutter'] plane = cutter.GetCutFunction() # adjust the implicit plane (if we got this far (i.e. plane.SetNormal(slicerPlane.GetNormal()) plane.SetOrigin(slicerPlane.GetOrigin()) # hack to turn around normal for front-view. if viewerIndex == 3: normal = slicerPlane.GetNormal() plane.SetNormal((-normal[0], -normal[1], -normal[2])) # also make sure the transform knows about the new object position contourDict['trfmFilter'].GetTransform().SetMatrix(contourDict['contourObjectProp'].GetMatrix()) # calculate it cutter.Update() ################################################################################### # _____ _______ ____ _____ _____ ______ _____ _____ _ _ _____ # # / ____|__ __/ __ \| __ \ | __ \| ____| /\ | __ \_ _| \ | |/ ____| # # | (___ | | | | | | |__) | | |__) | |__ / \ | | | || | | \| | | __ # # \___ \ | | | | | | ___/ | _ /| __| / /\ \ | | | || | | . ` | | |_ | # # ____) | | | | |__| | | | | \ \| |____ / ____ \| |__| || |_| |\ | |__| | # # |_____/ |_| \____/|_| |_| \_\______/_/ \_\_____/_____|_| \_|\_____| # # # ################################################################################### def view(self): self.frame.Show() self.frame.Raise() # because we have an RWI involved, we have to do this # SafeYield, so that the window does actually appear before we # call the render. If we don't do this, we get an initial # empty renderwindow. wx.SafeYield() self.render() def render(self): """Method that calls Render() on the embedded RenderWindow. Use this after having made changes to the scene. """ self.frame.render() self.renderer_3d.Render() self.slice_viewer_top.render() self.slice_viewer_side.render() self.slice_viewer_front.render() def get_input_descriptions(self): # define this as a tuple of input descriptions if you want to # take input data e.g. return ('vtkPolyData', 'my kind of # data') # concatenate it num_inputs times (but these are shallow copies!) return self._numDataInputs * ('vtkImageData',) def get_output_descriptions(self): # define this as a tuple of output descriptions if you want to # generate output data. return () def get_output(self, idx): # this can get called at any time when a consumer module wants # you output data. pass def execute_module(self): # when it's you turn to execute as part of a network # execution, this gets called. pass def logic_to_config(self): pass def config_to_logic(self): pass def config_to_view(self): pass def view_to_config(self): pass def close(self): """Clean-up method called on all DeVIDE modules when they are deleted. FIXME: Still get a nasty X error :( """ #THE FRAME (reference) frame = self.frame # with this complicated de-init, we make sure that VTK is # properly taken care of self.renderer_top.RemoveAllViewProps() self.renderer_side.RemoveAllViewProps() self.renderer_front.RemoveAllViewProps() self.renderer_3d.RemoveAllViewProps() # this finalize makes sure we don't get any strange X # errors when we kill the module. self.slice_viewer_top.close() self.slice_viewer_side.close() self.slice_viewer_front.close() frame.top.GetRenderWindow().Finalize() frame.top.SetRenderWindow(None) frame.side.GetRenderWindow().Finalize() frame.side.SetRenderWindow(None) frame.front.GetRenderWindow().Finalize() frame.front.SetRenderWindow(None) frame.view3d.GetRenderWindow().Finalize() frame.view3d.SetRenderWindow(None) del frame.top del frame.side del frame.front del frame.view3d del self.slice_viewer_top del self.slice_viewer_side del self.slice_viewer_front # done with VTK de-init # now take care of the wx window frame.close() # then shutdown our introspection mixin IntrospectModuleMixin.close(self)
def __init__(self, module_manager): """Standard constructor. All DeVIDE modules have these, we do the required setup actions. """ # we record the setting here, in case the user changes it # during the lifetime of this model, leading to different # states at init and shutdown. self.IMAGE_VIEWER = IMAGE_VIEWER # we need all this for our contours self.mask_data = None self.image_data = None self.lungVolume = None self.contour_severe_actor = vtk.vtkActor() self.contour_moderate_actor = vtk.vtkActor() self.contour_lungedge_actor = vtk.vtkActor() self.severe_mapper = vtk.vtkPolyDataMapper() self.severe_mapper.ScalarVisibilityOff() self.moderate_mapper = vtk.vtkPolyDataMapper() self.moderate_mapper.ScalarVisibilityOff() self.lung_mapper = vtk.vtkPolyDataMapper() self.lung_mapper.ScalarVisibilityOff() self.contour_severe_actor.SetMapper(self.severe_mapper) self.contour_severe_actor.GetProperty().SetColor(1,0,0) self.contour_severe_actor.GetProperty().SetOpacity(0.5) self.contour_moderate_actor.SetMapper(self.moderate_mapper) self.contour_moderate_actor.GetProperty().SetColor(0.5,0,1) self.contour_moderate_actor.GetProperty().SetOpacity(0.25) self.contour_lungedge_actor.SetMapper(self.lung_mapper) self.contour_lungedge_actor.GetProperty().SetColor(0.9,0.9,0.9) self.contour_lungedge_actor.GetProperty().SetOpacity(0.1) ModuleBase.__init__(self, module_manager) # create the view frame self._view_frame = module_utils.instantiate_module_view_frame( self, self._module_manager, EmphysemaViewerFrame.EmphysemaViewerFrame) # change the title to something more spectacular (or at least something non-default) self._view_frame.SetTitle('EmphysemaViewer') # create the necessary VTK objects: we only need a renderer, # the RenderWindowInteractor in the view_frame has the rest. self.ren = vtk.vtkRenderer() self.ren.SetBackground(0.5,0.5,0.5) self._view_frame.rwi.GetRenderWindow().AddRenderer(self.ren) self.ren.AddActor(self.contour_severe_actor) self.ren.AddActor(self.contour_moderate_actor) self.ren.AddActor(self.contour_lungedge_actor) self.ren2 = vtk.vtkRenderer() self.ren2.SetBackground(0.5,0.5,0.5) self._view_frame.overlay.GetRenderWindow().AddRenderer(self.ren2) self.slice_viewer1 = CMSliceViewer(self._view_frame.overlay, self.ren2) self.ren3 = vtk.vtkRenderer() self.ren3.SetBackground(0.5,0.5,0.5) self._view_frame.original.GetRenderWindow().AddRenderer(self.ren3) self.slice_viewer2 = CMSliceViewer(self._view_frame.original, self.ren3) self.slice_viewer3 = CMSliceViewer(self._view_frame.rwi, self.ren) self.sync = SyncSliceViewers() self.sync.add_slice_viewer(self.slice_viewer1) self.sync.add_slice_viewer(self.slice_viewer2) self.sync.add_slice_viewer2(self.slice_viewer3) # hook up all event handlers self._bind_events() # anything you stuff into self._config will be saved self._config.last_used_dir = '' # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their # views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic()
class EmphysemaViewer(IntrospectModuleMixin, ModuleBase): """Module to visualize lungemphysema in a CT scan. A lung mask is also needed. EmphysemaViewer consists of a volume rendering and two linked slice-based views; one with the original data and one with an emphysema overlay. The volume rendering shows 3 contours: the lungedges and 2 different contours of emphysema; a normal one and a severe one. There are two ways of setting the emphysema values. - The first way is choosing the 'default' values, which are literature-based. They are set on -950 HU (emphysema) and -970 HU (severe). - The other way is a computational way: The lowest 11% values, that are present in the data are marked as emphysema, the lowest 8,5% values are marked as severe emphysema. The theory behind this is the hypothesis that the histograms of emphysema patients differ from healthy people in a way that in emphysema patients there are relatively more lower values present. In both ways you can finetune the values, or completely change them (if you want to). After loading your image data and mask data, you can inspect the data and examine the severity of the emphysema of the patient. Controls: LMB: The left mouse button can be used to rotate objects in the 3D scene, or to poll Houndsfield Units in areas of interest (click and hold to see the values)\n RMB: For the slice viewers, you can set the window and level values by clicking and holding the right mouse button in a slice and moving your mouse. You can see the current window and level values in the bottom of the viewer. Outside of the slice, this zooms the camera in and out\n MMB: The middle mouse button enables stepping through the slices if clicked and held in the center of the slice. When clicking on de edges of a slice, this re-orients the entire slice. Outside of the slice, this pans the camera\n Scrollwheel: The scrollwheel can be used for zooming in and out of a scene, but also for sliceviewing if used with the CTRL- or SHIFT-key\n SHIFT: By holding the SHIFT-key, it is possible to use the mouse scrollwheel to scroll through the slices.\n CTRL: Holding the CTRL-key does the same, but enables stepping through the data in steps of 10 slices.\n """ def __init__(self, module_manager): """Standard constructor. All DeVIDE modules have these, we do the required setup actions. """ # we record the setting here, in case the user changes it # during the lifetime of this model, leading to different # states at init and shutdown. self.IMAGE_VIEWER = IMAGE_VIEWER # we need all this for our contours self.mask_data = None self.image_data = None self.lungVolume = None self.contour_severe_actor = vtk.vtkActor() self.contour_moderate_actor = vtk.vtkActor() self.contour_lungedge_actor = vtk.vtkActor() self.severe_mapper = vtk.vtkPolyDataMapper() self.severe_mapper.ScalarVisibilityOff() self.moderate_mapper = vtk.vtkPolyDataMapper() self.moderate_mapper.ScalarVisibilityOff() self.lung_mapper = vtk.vtkPolyDataMapper() self.lung_mapper.ScalarVisibilityOff() self.contour_severe_actor.SetMapper(self.severe_mapper) self.contour_severe_actor.GetProperty().SetColor(1,0,0) self.contour_severe_actor.GetProperty().SetOpacity(0.5) self.contour_moderate_actor.SetMapper(self.moderate_mapper) self.contour_moderate_actor.GetProperty().SetColor(0.5,0,1) self.contour_moderate_actor.GetProperty().SetOpacity(0.25) self.contour_lungedge_actor.SetMapper(self.lung_mapper) self.contour_lungedge_actor.GetProperty().SetColor(0.9,0.9,0.9) self.contour_lungedge_actor.GetProperty().SetOpacity(0.1) ModuleBase.__init__(self, module_manager) # create the view frame self._view_frame = module_utils.instantiate_module_view_frame( self, self._module_manager, EmphysemaViewerFrame.EmphysemaViewerFrame) # change the title to something more spectacular (or at least something non-default) self._view_frame.SetTitle('EmphysemaViewer') # create the necessary VTK objects: we only need a renderer, # the RenderWindowInteractor in the view_frame has the rest. self.ren = vtk.vtkRenderer() self.ren.SetBackground(0.5,0.5,0.5) self._view_frame.rwi.GetRenderWindow().AddRenderer(self.ren) self.ren.AddActor(self.contour_severe_actor) self.ren.AddActor(self.contour_moderate_actor) self.ren.AddActor(self.contour_lungedge_actor) self.ren2 = vtk.vtkRenderer() self.ren2.SetBackground(0.5,0.5,0.5) self._view_frame.overlay.GetRenderWindow().AddRenderer(self.ren2) self.slice_viewer1 = CMSliceViewer(self._view_frame.overlay, self.ren2) self.ren3 = vtk.vtkRenderer() self.ren3.SetBackground(0.5,0.5,0.5) self._view_frame.original.GetRenderWindow().AddRenderer(self.ren3) self.slice_viewer2 = CMSliceViewer(self._view_frame.original, self.ren3) self.slice_viewer3 = CMSliceViewer(self._view_frame.rwi, self.ren) self.sync = SyncSliceViewers() self.sync.add_slice_viewer(self.slice_viewer1) self.sync.add_slice_viewer(self.slice_viewer2) self.sync.add_slice_viewer2(self.slice_viewer3) # hook up all event handlers self._bind_events() # anything you stuff into self._config will be saved self._config.last_used_dir = '' # make our window appear (this is a viewer after all) self.view() # all modules should toggle this once they have shown their # views. self.view_initialised = True # apply config information to underlying logic self.sync_module_logic_with_config() # then bring it all the way up again to the view self.sync_module_view_with_logic() def close(self): """Clean-up method called on all DeVIDE modules when they are deleted. FIXME: Still get a nasty X error :( """ # with this complicated de-init, we make sure that VTK is # properly taken care of self.ren.RemoveAllViewProps() self.ren2.RemoveAllViewProps() self.ren3.RemoveAllViewProps() # this finalize makes sure we don't get any strange X # errors when we kill the module. self.slice_viewer1.close() self.slice_viewer2.close() self.slice_viewer3.close() self._view_frame.rwi.GetRenderWindow().Finalize() self._view_frame.rwi.SetRenderWindow(None) self._view_frame.overlay.GetRenderWindow().Finalize() self._view_frame.overlay.SetRenderWindow(None) self._view_frame.original.GetRenderWindow().Finalize() self._view_frame.original.SetRenderWindow(None) del self._view_frame.rwi del self._view_frame.overlay del self._view_frame.original del self.slice_viewer3 del self.slice_viewer2 del self.slice_viewer1 # done with VTK de-init # now take care of the wx window self._view_frame.close() # then shutdown our introspection mixin IntrospectModuleMixin.close(self) def get_input_descriptions(self): # define this as a tuple of input descriptions if you want to # take input data e.g. return ('vtkPolyData', 'my kind of # data') return () def get_output_descriptions(self): # define this as a tuple of output descriptions if you want to # generate output data. return () def set_input(self, idx, input_stream): # this gets called right before you get executed. take the # input_stream and store it so that it's available during # execute_module() pass def get_output(self, idx): # this can get called at any time when a consumer module wants # you output data. pass def execute_module(self): # when it's you turn to execute as part of a network # execution, this gets called. pass def logic_to_config(self): pass def config_to_logic(self): pass def config_to_view(self): pass def view_to_config(self): pass def view(self): self._view_frame.Show() self._view_frame.Raise() # because we have an RWI involved, we have to do this # SafeYield, so that the window does actually appear before we # call the render. If we don't do this, we get an initial # empty renderwindow. wx.SafeYield() self.render() def create_volumerender(self, contourValueModerate, contourValueSevere): """Creates a volumerender of the masked data using iso-contour surfaces created by the Marching Cubes algorithm at the specified contourvalues. """ self._view_frame.SetStatusText("Creating Volumerender...") self.image_data mask = vtk.vtkImageMask() severeFraction = 0.10 moderateFraction = 0.12 # We only want to contour the lungs, so mask it mask.SetMaskInput(self.mask_data) mask.SetInput(self.image_data) mask.Update() self.lungVolume = mask.GetOutput() if contourValueModerate == 0 and contourValueSevere == 0: # This means we get to calculate the percentual values ourselves! scalars = self.lungVolume.GetScalarRange() range = scalars[1]-scalars[0] contourValueSevere = scalars[0]+range*severeFraction contourValueModerate = scalars[0]+range*moderateFraction self._view_frame.upper_slider.SetValue(contourValueModerate) self._view_frame.lower_slider.SetValue(contourValueSevere) self.create_overlay(contourValueModerate,contourValueSevere) # Create the contours self.adjust_contour(self.lungVolume, contourValueSevere, self.severe_mapper) self.adjust_contour(self.lungVolume, contourValueModerate, self.moderate_mapper) #self.adjust_contour(self.mask_data, 0.5, self.lung_mapper) self.create_lungcontour() # Set the camera to a nice view cam = self.ren.GetActiveCamera() cam.SetPosition(0,-100,0) cam.SetFocalPoint(0,0,0) cam.SetViewUp(0,0,1) self.ren.ResetCamera() self.render() self._view_frame.SetStatusText("Created Volumerender") def adjust_contour(self, volume, contourValue, mapper): """Adjust or create an isocontour using the Marching Cubes surface at the given value using the given mapper """ self._view_frame.SetStatusText("Calculating new volumerender...") contour = vtk.vtkMarchingCubes() contour.SetValue(0,contourValue) contour.SetInput(volume) mapper.SetInput(contour.GetOutput()) mapper.Update() self.render() self._view_frame.SetStatusText("Calculated new volumerender") def create_lungcontour(self): """Create a lungcontour using the Marching Cubes algorithm and smooth the surface """ self._view_frame.SetStatusText("Calculating lungcontour...") contourLung = vtk.vtkMarchingCubes() contourLung.SetValue(0,1) contourLung.SetInput(self.mask_data) smoother = vtk.vtkWindowedSincPolyDataFilter() smoother.SetInput(contourLung.GetOutput()) smoother.BoundarySmoothingOn() smoother.SetNumberOfIterations(40) smoother.Update() self.lung_mapper.SetInput(smoother.GetOutput()) self.lung_mapper.Update() self._view_frame.SetStatusText("Calculated lungcontour") def create_overlay(self, emphysemavalue, severeemphysemavalue): """Creates an overlay for the slice-based volume view 0: no emphysema 1: moderate emphysema 2: severe emphysema """ self._view_frame.SetStatusText("Creating Overlay...") mask = vtk.vtkImageMask() mask2 = vtk.vtkImageMask() threshold = vtk.vtkImageThreshold() threshold2 = vtk.vtkImageThreshold() math=vtk.vtkImageMathematics() mask.SetInput(self.image_data) mask.SetMaskInput(self.mask_data) threshold.SetInput(mask.GetOutput()) threshold.ThresholdByLower(emphysemavalue) threshold.SetOutValue(0) threshold.SetInValue(1) threshold2.SetInput(mask.GetOutput()) threshold2.ThresholdByLower(severeemphysemavalue) threshold2.SetOutValue(1) threshold2.SetInValue(2) math.SetOperationToMultiply() math.SetInput1(threshold.GetOutput()) math.SetInput2(threshold2.GetOutput()) math.Update() overlay = math.GetOutput() self.slice_viewer1.set_overlay_input(None) self.slice_viewer1.set_overlay_input(overlay) self.render() self._view_frame.SetStatusText("Created Overlay") def load_data_from_file(self, file_path): """Loads scanvolume data from file. Also sets the volume as input for the sliceviewers """ self._view_frame.SetStatusText("Opening file: %s..." % (file_path)) filename = os.path.split(file_path)[1] fileBaseName =os.path.splitext(filename)[0] reader = vtk.vtkMetaImageReader() reader.SetFileName(file_path) reader.Update() self.image_data = reader.GetOutput() self.slice_viewer1.set_input(self.image_data) self.slice_viewer1.reset_camera() self.slice_viewer1.render() self.slice_viewer2.set_input(self.image_data) self.slice_viewer2.reset_camera() self.slice_viewer2.render() self.slice_viewer3.set_input(self.image_data) self.slice_viewer3.render() self.slice_viewer3.set_opacity(0.1) cam = self.ren.GetActiveCamera() cam.SetPosition(0,-100,0) cam.SetFocalPoint(0,0,0) cam.SetViewUp(0,0,1) self.ren.ResetCamera() if (self.mask_data) is not None: # We can start calculating the volumerender self.create_volumerender(0,0) else: self._view_frame.SetStatusText("Opened file") def load_mask_from_file(self, file_path): """Loads mask file """ self._view_frame.SetStatusText( "Opening mask: %s..." % (file_path)) filename = os.path.split(file_path)[1] fileBaseName =os.path.splitext(filename)[0] reader = vtk.vtkMetaImageReader() reader.SetFileName(file_path) reader.Update() self.mask_data = reader.GetOutput() if (self.image_data) is not None: self.create_volumerender(0,0) else: self._view_frame.SetStatusText("Opened mask file") def save_to_file(self, file_path): """Save data from main renderwindow (the contour one) to a PNG-file """ w2i = vtk.vtkWindowToImageFilter() w2i.SetInput(self._view_frame.rwi.GetRenderWindow()); w2i.Update() writer = vtk.vtkPNGWriter() writer.SetInput(w2i.GetOutput()) writer.SetFileName(file_path) writer.Update() result = writer.Write() if result == 0: self._view_frame.SetStatusText( "Saved file") else: self._view_frame.SetStatusText( "Saved file to: %s..." % (file_path)) def _bind_events(self): """Bind wx events to Python callable object event handlers. """ vf = self._view_frame vf.Bind(wx.EVT_MENU, self._handler_file_open, id = vf.id_file_open) vf.Bind(wx.EVT_MENU, self._handler_mask_open, id = vf.id_mask_open) vf.Bind(wx.EVT_MENU, self._handler_file_save, id = vf.id_mask_save) self._view_frame.button1.Bind(wx.EVT_BUTTON, self._handler_button1) self._view_frame.button2.Bind(wx.EVT_BUTTON, self._handler_button2) self._view_frame.button3.Bind(wx.EVT_BUTTON, self._handler_button3) self._view_frame.button4.Bind(wx.EVT_BUTTON, self._handler_button4) self._view_frame.button5.Bind(wx.EVT_BUTTON, self._handler_button5) self._view_frame.button6.Bind(wx.EVT_BUTTON, self._handler_button6) self._view_frame.upper_slider.Bind(wx.EVT_SCROLL_CHANGED, self._handler_slider1) self._view_frame.lower_slider.Bind(wx.EVT_SCROLL_CHANGED, self._handler_slider2) def _handler_button1(self, event): """Reset the camera of the main render window """ self.ren.ResetCamera() self.render() def _handler_button2(self, event): """Reset all for the main render window """ cam = self.ren.GetActiveCamera() cam.SetPosition(0,-100,0) cam.SetFocalPoint(0,0,0) cam.SetViewUp(0,0,1) self.ren.ResetCamera() self.render() def _handler_button3(self, event): """Reset the camera for the sliceviewers """ self.slice_viewer1.reset_camera() self.slice_viewer2.reset_camera() self.render() def _handler_button4(self, event): """Reset all for the sliceviewers """ self.slice_viewer1.reset_to_default_view(2) self.slice_viewer2.reset_to_default_view(2) orientations = [2, 0, 1] for i, ipw in enumerate(self.slice_viewer1.ipws): ipw.SetPlaneOrientation(orientations[i]) # axial ipw.SetSliceIndex(0) self.render() for i, ipw in enumerate(self.slice_viewer2.ipws): ipw.SetPlaneOrientation(orientations[i]) # axial ipw.SetSliceIndex(0) self.render() def _handler_button5(self, event): """Adjust the contourvalues to values recommended in literature """ if self.lungVolume == None: return else: self._view_frame.upper_slider.SetValue(-950) self._view_frame.lower_slider.SetValue(-970) self.adjust_contour(self.lungVolume, -950, self.moderate_mapper) self.adjust_contour(self.lungVolume, -970, self.severe_mapper) self.create_overlay(-950,-970) def _handler_button6(self, event): """Adjust the contourvalues to values calculated from data """ if self.lungVolume == None: return else: self.create_volumerender(0, 0) def _handler_file_open(self, event): """Handler for file opening """ filters = 'Volume files (*.mhd)|*.mhd;' dlg = wx.FileDialog(self._view_frame, "Please choose a CT-thorax file", self._config.last_used_dir, "", filters, wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename=dlg.GetFilename() self._config.last_used_dir=dlg.GetDirectory() full_file_path = "%s/%s" % (self._config.last_used_dir, filename) self.load_data_from_file(full_file_path) dlg.Destroy() def _handler_mask_open(self, event): """Handler for mask opening """ filters = 'Mask files (*.mhd;#.mha)|*.mhd;*mha;' dlg = wx.FileDialog(self._view_frame, "Please choose a CT-thorax mask file", self._config.last_used_dir, "", filters, wx.OPEN) if dlg.ShowModal() == wx.ID_OK: filename=dlg.GetFilename() self._config.last_used_dir=dlg.GetDirectory() full_file_path = "%s/%s" % (self._config.last_used_dir, filename) self.load_mask_from_file(full_file_path) dlg.Destroy() def _handler_file_save(self, event): """Handler for filesaving """ self._view_frame.SetStatusText( "Saving file...") filters = 'png file (*.png)|*.png' dlg = wx.FileDialog(self._view_frame, "Choose a destination", self._config.last_used_dir, "", filters, wx.SAVE) if dlg.ShowModal() == wx.ID_OK: filename=dlg.GetFilename() self._config.last_used_dir=dlg.GetDirectory() file_path = "%s/%s" % (self._config.last_used_dir, filename) self.save_to_file(file_path) dlg.Destroy() self._view_frame.SetStatusText( "Saved file") def _handler_slider1(self, event): """Handler for slider adjustment (Severe emphysema) """ if self.lungVolume == None: return else: contourValue = self._view_frame.upper_slider.GetValue() self.adjust_contour(self.lungVolume, contourValue, self.moderate_mapper) self.create_overlay(contourValue, self._view_frame.lower_slider.GetValue()) def _handler_slider2(self, event): """Handler for slider adjustment (Moderate emphysema) """ if self.lungVolume == None: return else: contourValue = self._view_frame.lower_slider.GetValue() self.adjust_contour(self.lungVolume, contourValue, self.severe_mapper) self.create_overlay(self._view_frame.upper_slider.GetValue(),contourValue) def render(self): """Method that calls Render() on the embedded RenderWindow. Use this after having made changes to the scene. """ self._view_frame.render() self.slice_viewer1.render()