#-------------------------------------------------------------------------- # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas # Copyright: (C) 2001 Centro de Pesquisas Renato Archer # Homepage: http://www.softwarepublico.gov.br # Contact: invesalius@cti.gov.br # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) #-------------------------------------------------------------------------- # Este programa e software livre; voce pode redistribui-lo e/ou # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme # publicada pela Free Software Foundation; de acordo com a versao 2 # da Licenca. # # Este programa eh distribuido na expectativa de ser util, mas SEM # QUALQUER GARANTIA; sem mesmo a garantia implicita de # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais # detalhes. #-------------------------------------------------------------------------- import plistlib import os import weakref import numpy import vtk import wx from wx.lib.pubsub import pub as Publisher import constants as const import project as prj import slice_ import converters from data import vtk_utils from vtk.util import numpy_support import session as ses Kernels = { "Basic Smooth 5x5" : [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, 4.0, 4.0, 1.0, 1.0, 4.0, 12.0, 4.0, 1.0, 1.0, 4.0, 4.0, 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] } SHADING = { "Default": { "ambient" :0.15, "diffuse" :0.9, "specular" :0.3, "specularPower" :15, }, "Glossy Vascular":{ "ambient" :0.15, "diffuse" :0.28, "specular" :1.42, "specularPower" :50, }, "Glossy Bone": { "ambient" :0.15, "diffuse" :0.24, "specular" :1.17, "specularPower" :6.98, }, "Endoscopy": { "ambient" :0.12, "diffuse" :0.64, "specular" :0.73, "specularPower" :50, } } class Volume(): def __init__(self): self.config = None self.exist = None self.color_transfer = None self.opacity_transfer_func = None self.ww = None self.wl = None self.curve = 0 self.plane = None self.plane_on = False self.volume = None self.image = None self.loaded_image = 0 self.to_reload = False self.__bind_events() def __bind_events(self): Publisher.subscribe(self.OnHideVolume, 'Hide raycasting volume') Publisher.subscribe(self.OnUpdatePreset, 'Update raycasting preset') Publisher.subscribe(self.OnSetCurve, 'Set raycasting curve') Publisher.subscribe(self.OnSetWindowLevel, 'Set raycasting wwwl') Publisher.subscribe(self.Refresh, 'Set raycasting refresh') Publisher.subscribe(self.OnSetRelativeWindowLevel, 'Set raycasting relative window and level') Publisher.subscribe(self.OnEnableTool, 'Enable raycasting tool') Publisher.subscribe(self.OnCloseProject, 'Close project data') Publisher.subscribe(self.ChangeBackgroundColour, 'Change volume viewer background colour') Publisher.subscribe(self.ResetRayCasting, 'Reset Reaycasting') Publisher.subscribe(self.OnFlipVolume, 'Flip volume') def ResetRayCasting(self, pub_evt): if self.exist: self.exist = None self.LoadVolume() def OnCloseProject(self, pubsub_evt): self.CloseProject() def CloseProject(self): #if self.plane: # self.plane = None # Publisher.sendMessage('Remove surface actor from viewer', self.plane_actor) if self.plane: self.plane.DestroyObjs() del self.plane self.plane = 0 if self.exist: self.exist = None Publisher.sendMessage('Remove surface actor from viewer', self.volume) Publisher.sendMessage('Disable volume cut menu') def OnLoadVolume(self, pubsub_evt): label = pubsub_evt.data #self.LoadConfig(label) self.LoadVolume() def OnHideVolume(self, pubsub_evt): self.volume.SetVisibility(0) if (self.plane and self.plane_on): self.plane.Disable() Publisher.sendMessage('Render volume viewer') def OnShowVolume(self, pubsub_evt = None): if self.exist: self.volume.SetVisibility(1) if (self.plane and self.plane_on): self.plane.Enable() Publisher.sendMessage('Render volume viewer') else: Publisher.sendMessage('Load raycasting preset', const.RAYCASTING_LABEL) self.LoadConfig() self.LoadVolume() self.exist = 1 def OnUpdatePreset(self, pubsub_evt): self.__load_preset_config() if self.config: if self.to_reload: self.exist = False Publisher.sendMessage('Unload volume', self.volume) if self.exist: self.__load_preset() self.volume.SetVisibility(1) #Publisher.sendMessage('Render volume viewer') else: self.LoadVolume() self.CalculateHistogram() self.exist = 1 colour = self.GetBackgroundColour() Publisher.sendMessage('Change volume viewer background colour', colour) Publisher.sendMessage('Change volume viewer gui colour', colour) else: Publisher.sendMessage('Unload volume', self.volume) del self.image del self.imagedata del self.final_imagedata del self.volume del self.color_transfer del self.opacity_transfer_func del self.volume_properties del self.volume_mapper self.volume = None self.exist = False self.loaded_image = False self.image = None self.final_imagedata = None self.opacity_transfer_func = None self.color_transfer = None Publisher.sendMessage('Render volume viewer') def OnFlipVolume(self, pubsub_evt): print "Flipping Volume" self.loaded_image = False del self.image self.image = None self.to_reload = True def __load_preset_config(self): self.config = prj.Project().raycasting_preset def __update_colour_table(self): if self.config['advancedCLUT']: self.Create16bColorTable(self.scale) self.CreateOpacityTable(self.scale) else: self.Create8bColorTable(self.scale) self.Create8bOpacityTable(self.scale) def __load_preset(self): # Update colour table self.__update_colour_table() # Update convolution filter original_imagedata = self.imagedata.GetOutput() imagedata = self.ApplyConvolution(original_imagedata) self.volume_mapper.SetInput(imagedata) # Update other information self.SetShading() self.SetTypeRaycasting() def OnSetCurve(self, pubsub_evt): self.curve = pubsub_evt.data self.CalculateWWWL() ww = self.ww wl = self.wl Publisher.sendMessage('Set volume window and level text', (ww, wl)) def OnSetRelativeWindowLevel(self, pubsub_evt): diff_wl, diff_ww = pubsub_evt.data ww = self.ww + diff_ww wl = self.wl + diff_wl Publisher.sendMessage('Set volume window and level text', (ww, wl)) self.SetWWWL(ww, wl) self.ww = ww self.wl = wl def OnSetWindowLevel(self, pubsub_evt): ww, wl, n = pubsub_evt.data self.curve = n self.SetWWWL(ww,wl) def SetWWWL(self, ww, wl): if self.config['advancedCLUT']: try: curve = self.config['16bitClutCurves'][self.curve] except IndexError: self.curve = 0 curve = self.config['16bitClutCurves'][self.curve] p1 = curve[0] p2 = curve[-1] half = (p2['x'] - p1['x']) / 2.0 middle = p1['x'] + half shiftWL = wl - middle shiftWW = p1['x'] + shiftWL - (wl - 0.5 * ww) factor = 1.0 for n,i in enumerate(curve): factor = abs(i['x'] - middle) / half if factor < 0: factor = 0 i['x'] += shiftWL if n < len(curve)/2.0: i['x'] -= shiftWW * factor else: i['x'] += shiftWW * factor else: self.config['wl'] = wl self.config['ww'] = ww self.__update_colour_table() def CalculateWWWL(self): """ Get the window width & level from the selected curve """ try: curve = self.config['16bitClutCurves'][self.curve] except IndexError: self.curve -= 1 curve = self.config['16bitClutCurves'][self.curve] first_point = curve[0]['x'] last_point = curve[-1]['x'] self.ww = last_point - first_point self.wl = first_point + self.ww / 2.0 def Refresh(self, pubsub_evt): self.__update_colour_table() def Create16bColorTable(self, scale): if self.color_transfer: color_transfer = self.color_transfer else: color_transfer = vtk.vtkColorTransferFunction() color_transfer.RemoveAllPoints() curve_table = self.config['16bitClutCurves'] color_table = self.config['16bitClutColors'] colors = [] for i, l in enumerate(curve_table): for j, lopacity in enumerate(l): gray_level = lopacity['x'] r = color_table[i][j]['red'] g = color_table[i][j]['green'] b = color_table[i][j]['blue'] colors.append((gray_level, r, g, b)) color_transfer.AddRGBPoint( self.TranslateScale(scale, gray_level), r, g, b) self.color_transfer = color_transfer def Create8bColorTable(self, scale): if self.color_transfer: color_transfer = self.color_transfer else: color_transfer = vtk.vtkColorTransferFunction() color_transfer.RemoveAllPoints() color_preset = self.config['CLUT'] if color_preset != "No CLUT": p = plistlib.readPlist( os.path.join(const.RAYCASTING_PRESETS_DIRECTORY, 'color_list', color_preset + '.plist')) r = p['Red'] g = p['Green'] b = p['Blue'] colors = zip(r,g,b) ww = self.config['ww'] wl = self.TranslateScale(scale, self.config['wl']) init = wl - ww/2.0 inc = ww / (len(colors) - 1.0) for n,rgb in enumerate(colors): color_transfer.AddRGBPoint(init + n * inc, *[i/255.0 for i in rgb]) self.color_transfer = color_transfer def CreateOpacityTable(self, scale): if self.opacity_transfer_func: opacity_transfer_func = self.opacity_transfer_func else: opacity_transfer_func = vtk.vtkPiecewiseFunction() opacity_transfer_func.RemoveAllPoints() curve_table = self.config['16bitClutCurves'] opacities = [] ww = self.config['ww'] wl = self.config['wl'] self.ww = ww self.wl = wl l1 = wl - ww/2.0 l2 = wl + ww/2.0 k1 = 0.0 k2 = 1.0 opacity_transfer_func.AddSegment(0, 0, 2**16-1, 0) for i, l in enumerate(curve_table): for j, lopacity in enumerate(l): gray_level = lopacity['x'] #if gray_level <= l1: # opacity = k1 #elif gray_level > l2: # opacity = k2 #else: opacity = lopacity['y'] opacities.append((gray_level, opacity)) opacity_transfer_func.AddPoint( self.TranslateScale(scale, gray_level), opacity) self.opacity_transfer_func = opacity_transfer_func def Create8bOpacityTable(self, scale): if self.opacity_transfer_func: opacity_transfer_func = self.opacity_transfer_func else: opacity_transfer_func = vtk.vtkPiecewiseFunction() opacity_transfer_func.RemoveAllPoints() opacities = [] ww = self.config['ww'] wl = self.TranslateScale(scale, self.config['wl']) l1 = wl - ww/2.0 l2 = wl + ww/2.0 self.ww = ww self.wl = self.config['wl'] opacity_transfer_func.RemoveAllPoints() opacity_transfer_func.AddSegment(0, 0, 2**16-1, 0) k1 = 0.0 k2 = 1.0 opacity_transfer_func.AddPoint(l1, 0) opacity_transfer_func.AddPoint(l2, 1) self.opacity_transfer_func = opacity_transfer_func return opacity_transfer_func def GetBackgroundColour(self): colour = (self.config['backgroundColorRedComponent'], self.config['backgroundColorGreenComponent'], self.config['backgroundColorBlueComponent']) return colour def ChangeBackgroundColour(self, pubsub_evt): if (self.config): self.config['backgroundColorRedComponent'] = pubsub_evt.data[0] * 255 self.config['backgroundColorGreenComponent'] = pubsub_evt.data[1] * 255 self.config['backgroundColorBlueComponent'] = pubsub_evt.data[2] * 255 def BuildTable(): curve_table = p['16bitClutCurves'] color_background = (p['backgroundColorRedComponent'], p['backgroundColorGreenComponent'], p['backgroundColorBlueComponent']) color_background = [i for i in color_background] opacities = [] colors = [] for i, l in enumerate(curve_table): for j, lopacity in enumerate(l): gray_level = lopacity['x'] opacity = lopacity['y'] opacities.append((gray_level, opacity)) r = color_table[i][j]['red'] g = color_table[i][j]['green'] b = color_table[i][j]['blue'] colors.append((gray_level, r, g, b)) return colors, opacities, color_background, p['useShading'] def SetShading(self): if self.config['useShading']: self.volume_properties.ShadeOn() else: self.volume_properties.ShadeOff() shading = SHADING[self.config['shading']] self.volume_properties.SetAmbient(shading['ambient']) self.volume_properties.SetDiffuse(shading['diffuse']) self.volume_properties.SetSpecular(shading['specular']) self.volume_properties.SetSpecularPower(shading['specularPower']) def SetTypeRaycasting(self): if self.volume_mapper.IsA("vtkFixedPointVolumeRayCastMapper") or self.volume_mapper.IsA("vtkGPUVolumeRayCastMapper"): if self.config.get('MIP', False): self.volume_mapper.SetBlendModeToMaximumIntensity() else: self.volume_mapper.SetBlendModeToComposite() else: if self.config.get('MIP', False): raycasting_function = vtk.vtkVolumeRayCastMIPFunction() else: raycasting_function = vtk.vtkVolumeRayCastCompositeFunction() raycasting_function.SetCompositeMethodToInterpolateFirst() if ses.Session().rendering == '0': self.volume_mapper.SetVolumeRayCastFunction(raycasting_function) def ApplyConvolution(self, imagedata, update_progress = None): number_filters = len(self.config['convolutionFilters']) if number_filters: if not(update_progress): update_progress = vtk_utils.ShowProgress(number_filters) for filter in self.config['convolutionFilters']: convolve = vtk.vtkImageConvolve() convolve.SetInput(imagedata) convolve.SetKernel5x5([i/60.0 for i in Kernels[filter]]) convolve.ReleaseDataFlagOn() convolve_ref = weakref.ref(convolve) convolve_ref().AddObserver("ProgressEvent", lambda obj,evt: update_progress(convolve_ref(), "Rendering...")) convolve.Update() del imagedata imagedata = convolve.GetOutput() del convolve #convolve.GetOutput().ReleaseDataFlagOn() return imagedata def LoadImage(self): slice_data = slice_.Slice() n_array = slice_data.matrix spacing = slice_data.spacing slice_number = 0 orientation = 'AXIAL' image = converters.to_vtk(n_array, spacing, slice_number, orientation) self.image = image def LoadVolume(self): proj = prj.Project() #image = imagedata_utils.to_vtk(n_array, spacing, slice_number, orientation) if not self.loaded_image: self.LoadImage() self.loaded_image = 1 image = self.image number_filters = len(self.config['convolutionFilters']) if (prj.Project().original_orientation == const.AXIAL): flip_image = True else: flip_image = False #if (flip_image): update_progress= vtk_utils.ShowProgress(2 + number_filters) # Flip original vtkImageData flip = vtk.vtkImageFlip() flip.SetInput(image) flip.SetFilteredAxis(1) flip.FlipAboutOriginOn() flip.ReleaseDataFlagOn() flip_ref = weakref.ref(flip) flip_ref().AddObserver("ProgressEvent", lambda obj,evt: update_progress(flip_ref(), "Rendering...")) flip.Update() image = flip.GetOutput() scale = image.GetScalarRange() self.scale = scale cast = vtk.vtkImageShiftScale() cast.SetInput(image) cast.SetShift(abs(scale[0])) cast.SetOutputScalarTypeToUnsignedShort() cast.ReleaseDataFlagOn() cast_ref = weakref.ref(cast) cast_ref().AddObserver("ProgressEvent", lambda obj,evt: update_progress(cast_ref(), "Rendering...")) cast.Update() image2 = cast self.imagedata = image2 if self.config['advancedCLUT']: self.Create16bColorTable(scale) self.CreateOpacityTable(scale) else: self.Create8bColorTable(scale) self.Create8bOpacityTable(scale) image2 = self.ApplyConvolution(image2.GetOutput(), update_progress) self.final_imagedata = image2 # Changed the vtkVolumeRayCast to vtkFixedPointVolumeRayCastMapper # because it's faster and the image is better # TODO: To test if it's true. if const.TYPE_RAYCASTING_MAPPER: volume_mapper = vtk.vtkVolumeRayCastMapper() #volume_mapper.AutoAdjustSampleDistancesOff() #volume_mapper.SetInput(image2) #volume_mapper.SetVolumeRayCastFunction(composite_function) #volume_mapper.SetGradientEstimator(gradientEstimator) volume_mapper.IntermixIntersectingGeometryOn() self.volume_mapper = volume_mapper else: if int(ses.Session().rendering) == 0: volume_mapper = vtk.vtkFixedPointVolumeRayCastMapper() #volume_mapper.AutoAdjustSampleDistancesOff() self.volume_mapper = volume_mapper volume_mapper.IntermixIntersectingGeometryOn() else: volume_mapper = vtk.vtkGPUVolumeRayCastMapper() self.volume_mapper = volume_mapper self.SetTypeRaycasting() volume_mapper.SetInput(image2) # TODO: Look to this #volume_mapper_hw = vtk.vtkVolumeTextureMapper3D() #volume_mapper_hw.SetInput(image2) #Cut Plane #CutPlane(image2, volume_mapper) #self.color_transfer = color_transfer volume_properties = vtk.vtkVolumeProperty() #volume_properties.IndependentComponentsOn() volume_properties.SetInterpolationTypeToLinear() volume_properties.SetColor(self.color_transfer) try: volume_properties.SetScalarOpacity(self.opacity_transfer_func) except NameError: pass # Using these lines to improve the raycasting quality. These values # seems related to the distance from ray from raycasting. # TODO: Need to see values that improve the quality and don't decrease # the performance. 2.0 seems to be a good value to pix_diag pix_diag = 2.0 volume_mapper.SetImageSampleDistance(0.25) volume_mapper.SetSampleDistance(pix_diag / 5.0) volume_properties.SetScalarOpacityUnitDistance(pix_diag) self.volume_properties = volume_properties self.SetShading() volume = vtk.vtkVolume() volume.SetMapper(volume_mapper) volume.SetProperty(volume_properties) self.volume = volume colour = self.GetBackgroundColour() self.exist = 1 Publisher.sendMessage('Load volume into viewer', (volume, colour, (self.ww, self.wl))) del flip del cast def OnEnableTool(self, pubsub_evt): tool_name, enable = pubsub_evt.data if tool_name == _("Cut plane"): if self.plane: if enable: self.plane_on = True self.plane.Enable() else: self.plane_on = False self.plane.Disable() else: self.final_imagedata.Update() self.plane_on = True self.plane = CutPlane(self.final_imagedata, self.volume_mapper) def CalculateHistogram(self): image = self.image r = int(image.GetScalarRange()[1] - image.GetScalarRange()[0]) accumulate = vtk.vtkImageAccumulate() accumulate.SetInput(image) accumulate.SetComponentExtent(0, r -1, 0, 0, 0, 0) accumulate.SetComponentOrigin(image.GetScalarRange()[0], 0, 0) accumulate.ReleaseDataFlagOn() accumulate.Update() n_image = numpy_support.vtk_to_numpy(accumulate.GetOutput().GetPointData().GetScalars()) del accumulate Publisher.sendMessage('Load histogram', (n_image, image.GetScalarRange())) def TranslateScale(self, scale, value): #if value < 0: # valor = 2**16 - abs(value) #else: # valor = value return value - scale[0] class CutPlane: def __init__(self, img, volume_mapper): self.img = img self.volume_mapper = volume_mapper self.Create() self.__bind_events() def __bind_events(self): Publisher.subscribe(self.Reset, 'Reset Cut Plane') Publisher.subscribe(self.Enable, 'Enable Cut Plane') Publisher.subscribe(self.Disable, 'Disable Cut Plane') def Create(self): self.plane_widget = plane_widget = vtk.vtkImagePlaneWidget() plane_widget.SetInput(self.img) plane_widget.SetPlaneOrientationToXAxes() #plane_widget.SetResliceInterpolateToLinear() plane_widget.TextureVisibilityOff() #Set left mouse button to move and rotate plane plane_widget.SetLeftButtonAction(1) #SetColor margin to green margin_property = plane_widget.GetMarginProperty() margin_property.SetColor(0,0.8,0) #Disable cross cursor_property = plane_widget.GetCursorProperty() cursor_property.SetOpacity(0) self.plane_source = plane_source = vtk.vtkPlaneSource() plane_source.SetOrigin(plane_widget.GetOrigin()) plane_source.SetPoint1(plane_widget.GetPoint1()) plane_source.SetPoint2(plane_widget.GetPoint2()) plane_source.SetNormal(plane_widget.GetNormal()) plane_mapper = self.plane_mapper = vtk.vtkPolyDataMapper() plane_mapper.SetInput(plane_source.GetOutput()) self.plane_actor = plane_actor = vtk.vtkActor() plane_actor.SetMapper(plane_mapper) plane_actor.GetProperty().BackfaceCullingOn() plane_actor.GetProperty().SetOpacity(0) plane_widget.AddObserver("InteractionEvent", self.Update) Publisher.sendMessage('AppendActor', self.plane_actor) Publisher.sendMessage('Set Widget Interactor', self.plane_widget) plane_actor.SetVisibility(1) plane_widget.On() self.plane = plane = vtk.vtkPlane() plane.SetNormal(self.plane_source.GetNormal()) plane.SetOrigin(self.plane_source.GetOrigin()) self.volume_mapper.AddClippingPlane(plane) #Storage First Position self.origin = plane_widget.GetOrigin() self.p1 = plane_widget.GetPoint1() self.p2 = plane_widget.GetPoint2() self.normal = plane_widget.GetNormal() def Update(self, a, b): plane_source = self.plane_source plane_widget = self.plane_widget plane_source.SetOrigin(plane_widget.GetOrigin()) plane_source.SetPoint1(plane_widget.GetPoint1()) plane_source.SetPoint2(plane_widget.GetPoint2()) plane_source.SetNormal(plane_widget.GetNormal()) self.plane_actor.VisibilityOn() self.plane.SetNormal(plane_source.GetNormal()) self.plane.SetOrigin(plane_source.GetOrigin()) Publisher.sendMessage('Render volume viewer', None) def Enable(self, evt_pubsub=None): self.plane_widget.On() self.plane_actor.VisibilityOn() self.volume_mapper.AddClippingPlane(self.plane) Publisher.sendMessage('Render volume viewer', None) def Disable(self,evt_pubsub=None): self.plane_widget.Off() self.plane_actor.VisibilityOff() self.volume_mapper.RemoveClippingPlane(self.plane) Publisher.sendMessage('Render volume viewer', None) def Reset(self, evt_pubsub=None): plane_source = self.plane_source plane_widget = self.plane_widget plane_source.SetOrigin(self.origin) plane_source.SetPoint1(self.p1) plane_source.SetPoint2(self.p2) plane_source.SetNormal(self.normal) self.plane_actor.VisibilityOn() self.plane.SetNormal(self.normal) self.plane.SetOrigin(self.origin) Publisher.sendMessage('Render volume viewer', None) def DestroyObjs(self): Publisher.sendMessage('Remove surface actor from viewer', self.plane_actor) self.Disable() del self.plane_widget del self.plane_source del self.plane_actor del self.normal del self.plane