diff --git a/invesalius/constants.py b/invesalius/constants.py index b18c370..26b3791 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -78,3 +78,10 @@ WINDOW_LEVEL = ({"Abdomen":(350,50), "Vasculature - Hard":(240,80), "Vasculature - Soft":(650,160) }) + +ORIENTATION_COLOUR = {'AXIAL': (1,0,0), # Red + 'CORONAL': (0,1,0), # Green + 'SAGITAL': (0,0,1)} # Blue +CAM_POSITION = {"AXIAL":(0, 0, 1), "CORONAL":(0, -1, 0), "SAGITAL":(1, 0, 0)} +CAM_VIEW_UP = {"AXIAL":(0, 1, 0), "CORONAL":(0, 0, 1), "SAGITAL":(0, 0, 1)} + diff --git a/invesalius/data/slice_.py b/invesalius/data/slice_.py index c1c8d8b..6e1a9a5 100644 --- a/invesalius/data/slice_.py +++ b/invesalius/data/slice_.py @@ -28,6 +28,8 @@ class Slice(object): ps.Publisher().subscribe(self.OnChangeCurrentMask, 'Change mask selected') ps.Publisher().subscribe(self.CreateSurfaceFromIndex, 'Create surface from index') + ps.Publisher().subscribe(self.UpdateCursorPosition, + 'Update cursor position in slice') def CreateSurfaceFromIndex(self, pubsub_evt): mask_index = pubsub_evt.data @@ -116,7 +118,46 @@ class Slice(object): blend_imagedata.SetInput(1, imagedata_mask) blend_imagedata.SetBlendModeToNormal() blend_imagedata.GetOutput().ReleaseDataFlagOn() - self.blend_imagedata = blend_imagedata + #self.blend_imagedata = blend_imagedata + + + #blend_imagedata.GetExtent() + + # global values + CURSOR_X = 0 # SAGITAL + CURSOR_Y = 0 # CORONAL + CURSOR_Z = 0 # AXIAL + + CURSOR_VALUE = 4095 + CURSOR_RADIUS = 1000 + + cross = vtk.vtkImageCursor3D() + cross.GetOutput().ReleaseDataFlagOn() + cross.SetInput(blend_imagedata.GetOutput()) + cross.SetCursorPosition(CURSOR_X, CURSOR_Y, CURSOR_Z) + cross.SetCursorValue(CURSOR_VALUE) + cross.SetCursorRadius(CURSOR_RADIUS) + cross.Modified() + self.cross = cross + + cast = vtk.vtkImageCast() + cast.SetInput(cross.GetOutput()) + cast.GetOutput().SetUpdateExtentToWholeExtent() + cast.SetOutputScalarTypeToUnsignedChar() + cast.Update() + + self.blend_imagedata = cast + + + def UpdateCursorPosition(self, pubsub_evt): + + new_pos = pubsub_evt.data + self.cross.SetCursorPosition(new_pos) + self.cross.Modified() + self.blend_imagedata.Update() + ps.Publisher().sendMessage('Update slice viewer', None) + + def __create_background(self, imagedata): diff --git a/invesalius/data/viewer_slice.py b/invesalius/data/viewer_slice.py index 98966dd..858fe72 100755 --- a/invesalius/data/viewer_slice.py +++ b/invesalius/data/viewer_slice.py @@ -24,35 +24,49 @@ import wx import wx.lib.pubsub as ps import data.slice_ as sl - +import constants as const import project class Viewer(wx.Panel): def __init__(self, prnt, orientation='AXIAL'): wx.Panel.__init__(self, prnt, size=wx.Size(320, 300)) - + + colour = [255*c for c in const.ORIENTATION_COLOUR[orientation]] + self.SetBackgroundColour(colour) + self.__init_gui() - self.__config_interactor() self.orientation = orientation self.slice_number = 0 + # Interactor aditional style + self.mode = 'DEFAULT' + self.mouse_pressed = 0 + + # VTK pipeline and actors + self.__config_interactor() + self.pick = vtk.vtkCellPicker() + #self.cursor = + self.__bind_events() self.__bind_events_wx() def __init_gui(self): + interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) self.scroll = scroll sizer = wx.BoxSizer(wx.HORIZONTAL) - self.SetSizer(sizer) - - sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) sizer.Add(interactor, 1, wx.EXPAND|wx.GROW) - sizer.Fit(self) + + background_sizer = wx.BoxSizer(wx.HORIZONTAL) + background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) + background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) + self.SetSizer(background_sizer) + background_sizer.Fit(self) self.Layout() self.Update() @@ -61,7 +75,10 @@ class Viewer(wx.Panel): self.interactor = interactor def __config_interactor(self): + style = vtk.vtkInteractorStyleImage() + self.style = style + ren = vtk.vtkRenderer() interactor = self.interactor @@ -69,26 +86,116 @@ class Viewer(wx.Panel): interactor.GetRenderWindow().AddRenderer(ren) self.cam = ren.GetActiveCamera() - self.ren = ren - + + self.SetMode(self.mode) + + def SetMode(self, mode): + self.mode = mode + # All modes and bindings + action = {'DEFAULT': { + "MouseMoveEvent": self.OnCrossMove, + "LeftButtonPressEvent": self.OnMouseClick, + "LeftButtonReleaseEvent": self.OnMouseRelease + }, + 'EDITOR': { + "MouseMoveEvent": self.OnBrushMove, + "LeftButtonPressEvent": self.OnBrushClick, + "LeftButtonReleaseEvent": self.OnMouseRelease + } + } + + # Bind method according to current mode + style = self.style + style.AddObserver("MouseMoveEvent", action[mode]["MouseMoveEvent"]) + style.AddObserver("LeftButtonPressEvent", action[mode]["LeftButtonPressEvent"]) + style.AddObserver("LeftButtonReleaseEvent", action[mode]["LeftButtonReleaseEvent"]) + + def OnMouseClick(self, obj, evt_vtk): + self.mouse_pressed = 1 + + def OnMouseRelease(self, obj, evt_vtk): + self.mouse_pressed = 0 + + def OnBrushClick(self, obj, evt_vtk): + self.mouse_pressed = 1 + coord = self.GetCoordinate() + print "Edit pixel region based on origin:", coord + + def OnBrushMove(self, obj, evt_vtk): + coord = self.GetCoordinate() + if self.mouse_pressed: + print "Edit pixel region based on origin:", coord + + def OnCrossMove(self, obj, evt_vtk): + coord = self.GetCoordinate() + # Update position in other slices + if self.mouse_pressed: + ps.Publisher().sendMessage('Update cursor position in slice', coord) + ps.Publisher().sendMessage(('Set scroll position', 'SAGITAL'), coord[0]) + ps.Publisher().sendMessage(('Set scroll position', 'CORONAL'), coord[1]) + ps.Publisher().sendMessage(('Set scroll position', 'AXIAL'), coord[2]) + + def GetCoordinate(self): + + # Find position + mouse_x, mouse_y = self.interactor.GetEventPosition() + self.pick.Pick(mouse_x, mouse_y, 0, self.ren) + x, y, z = self.pick.GetPickPosition() + + # First we fix the position origin, based on vtkActor bounds + bounds = self.actor.GetBounds() + bound_xi, bound_xf, bound_yi, bound_yf, bound_zi, bound_zf = bounds + x = float(x - bound_xi) + y = float(y - bound_yi) + z = float(z - bound_zi) + + # Then we fix the porpotion, based on vtkImageData spacing + spacing_x, spacing_y, spacing_z = self.imagedata.GetSpacing() + x = x/spacing_x + y = y/spacing_y + z = z/spacing_z + + # Based on the current orientation, we define 3D position + coordinates = {"SAGITAL": [self.slice_number, y, z], + "CORONAL": [x, self.slice_number, z], + "AXIAL": [x, y, self.slice_number]} + coord = [int(coord) for coord in coordinates[self.orientation]] + + # According to vtkImageData extent, we limit min and max value + # If this is not done, a VTK Error occurs when mouse is pressed outside + # vtkImageData extent + extent = self.imagedata.GetWholeExtent() + extent_min = extent[0], extent[2], extent[4] + extent_max = extent[1], extent[3], extent[5] + for index in xrange(3): + if coord[index] > extent_max[index]: + coord[index] = extent_max[index] + elif coord[index] < extent_min[index]: + coord[index] = extent_min[index] + + print "New coordinate: ", coord + + return coord + def __bind_events(self): ps.Publisher().subscribe(self.LoadImagedata, 'Load slice to viewer') ps.Publisher().subscribe(self.SetColour, 'Change mask colour') ps.Publisher().subscribe(self.UpdateRender, 'Update slice viewer') - ps.Publisher().subscribe(self.SetScrollPosition, ('Set scroll position', + ps.Publisher().subscribe(self.ChangeSliceNumber, ('Set scroll position', self.orientation)) def __bind_events_wx(self): self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar) - #self.interactor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) def LoadImagedata(self, pubsub_evt): imagedata = pubsub_evt.data self.SetInput(imagedata) def SetInput(self, imagedata): + self.imagedata = imagedata + ren = self.ren interactor = self.interactor @@ -101,77 +208,72 @@ class Viewer(wx.Panel): actor.SetInput(slice_.GetOutput()) self.actor = actor + colour = const.ORIENTATION_COLOUR[self.orientation] + + text_property = vtk.vtkTextProperty() + text_property.SetFontSize(16) + text_property.SetFontFamilyToArial() + text_property.BoldOn() + text_property.SetColor(colour) + + # Text related to slice number shown + text_mapper = vtk.vtkTextMapper() + text_mapper.SetInput("%d"%(self.slice_number)) + text_mapper.GetTextProperty().ShallowCopy(text_property) + self.text_mapper = text_mapper + + text_actor = vtk.vtkActor2D() + text_actor.SetMapper(text_mapper) + text_actor.SetLayerNumber(1) + text_actor.GetPositionCoordinate().SetValue(self.GetSize()[0] - 80, 20) + text_actor.SetVisibility(1) + ren.AddActor(actor) + ren.AddActor(text_actor) self.__update_camera() max_slice_number = actor.GetSliceNumberMax() self.scroll.SetScrollbar(wx.SB_VERTICAL, 1, max_slice_number, max_slice_number) + self.SetScrollPosition(0) def SetOrientation(self, orientation): self.orientation = orientation self.__update_camera() def __update_camera(self): - cam = self.cam - orientation = self.orientation - if orientation == "AXIAL": - cam.SetFocalPoint(0, 0, 0) - cam.SetPosition(0, 0, 1) - cam.ComputeViewPlaneNormal() - cam.SetViewUp(0, 1, 0) - elif orientation == "CORONAL": - cam.SetFocalPoint(0, 0, 0) - cam.SetPosition(0, -1, 0) - cam.ComputeViewPlaneNormal() - cam.SetViewUp(0, 0, 1) - elif orientation == "SAGITAL": - cam.SetFocalPoint(0, 0, 0) - cam.SetPosition(1, 0, 0) - cam.ComputeViewPlaneNormal() - cam.SetViewUp(0, 0, 1) - + cam = self.cam + cam.SetFocalPoint(0, 0, 0) + cam.SetPosition(const.CAM_POSITION[self.orientation]) + cam.SetViewUp(const.CAM_VIEW_UP[self.orientation]) + cam.ComputeViewPlaneNormal() cam.OrthogonalizeViewUp() + cam.ParallelProjectionOn() + self.__update_display_extent() - cam.ParallelProjectionOn() + self.ren.ResetCamera() self.ren.Render() def __update_display_extent(self): - actor = self.actor - slice_number = self.slice_number - extent = self.imagedata.GetWholeExtent() - if self.orientation == "AXIAL": - xs = extent[1] - extent[0] + 1 - ys = extent[3] - extent[2] + 1 - actor.SetDisplayExtent(extent[0], extent[1], - extent[2], extent[3], - slice_number, slice_number) - elif self.orientation == "CORONAL": - xs = extent[1] - extent[0] + 1 - ys = extent[5] - extent[4] + 1 - actor.SetDisplayExtent(extent[0], extent[1], - slice_number,slice_number, - extent[4], extent[5]) - elif self.orientation == "SAGITAL": - xs = extent[3] - extent[2] + 1 - ys = extent[5] - extent[4] + 1 - actor.SetDisplayExtent(slice_number, slice_number, - extent[2], extent[3], - extent[4], extent[5]) + pos = self.slice_number + e = self.imagedata.GetWholeExtent() + + new_extent = {"SAGITAL": (pos, pos, e[2], e[3], e[4], e[5]), + "CORONAL": (e[0], e[1], pos, pos, e[4], e[5]), + "AXIAL": (e[0], e[1], e[2], e[3], pos, pos)} + + self.actor.SetDisplayExtent(new_extent[self.orientation]) self.ren.ResetCameraClippingRange() self.ren.Render() def UpdateRender(self, evt): self.interactor.Render() - def SetScrollPosition(self, pubsub_evt): - value = pubsub_evt.data - position = self.scroll.GetThumbPosition() - position += value + def SetScrollPosition(self, position): self.scroll.SetThumbPosition(position) self.OnScrollBar() @@ -183,9 +285,16 @@ class Viewer(wx.Panel): evt.Skip() def SetSliceNumber(self, index): + self.text_mapper.SetInput(str(index)) self.slice_number = index self.__update_display_extent() + def ChangeSliceNumber(self, pubsub_evt): + index = pubsub_evt.data + self.SetSliceNumber(index) + self.scroll.SetThumbPosition(index) + self.interactor.Render() + def SetColour(self, pubsub_evt): colour_wx = pubsub_evt.data colour_vtk = [colour/float(255) for colour in colour_wx] diff --git a/invesalius/data/viewer_volume.py b/invesalius/data/viewer_volume.py index 5d0ea12..b58fc94 100755 --- a/invesalius/data/viewer_volume.py +++ b/invesalius/data/viewer_volume.py @@ -61,6 +61,26 @@ class Viewer(wx.Panel): def __bind_events(self): ps.Publisher().subscribe(self.LoadActor, 'Load surface actor into viewer') ps.Publisher().subscribe(self.UpdateRender, 'Render volume viewer') + ps.Publisher().subscribe(self.ChangeBackgroundColour, + 'Change volume viewer background colour') + ps.Publisher().subscribe(self.ShowRaycastingVolume, + 'Show raycasting volume') + ps.Publisher().subscribe(self.HideRaycastingVolume, + 'Hide raycasting volume') + + + def ShowRaycastingVolume(self, pubsub_evt): + pass + + def HideRaycastingVolume(self, pubsub_evt): + pass + + + + def ChangeBackgroundColour(self, pubsub_evt): + colour = pubsub_evt.data + self.ren.SetBackground(colour) + self.UpdateRender() def LoadActor(self, pubsub_evt): actor = pubsub_evt.data @@ -76,7 +96,7 @@ class Viewer(wx.Panel): self.iren.Render() - def UpdateRender(self, evt_pubsub): + def UpdateRender(self, evt_pubsub=None): self.iren.Render() def CreatePlanes(self): diff --git a/invesalius/gui/default_viewers.py b/invesalius/gui/default_viewers.py index 9347ecc..5bf2f64 100755 --- a/invesalius/gui/default_viewers.py +++ b/invesalius/gui/default_viewers.py @@ -21,18 +21,6 @@ import wx.lib.agw.fourwaysplitter as fws import data.viewer_slice as slice_viewer import data.viewer_volume as volume_viewer -class SamplePane(wx.Panel): - """ - Just a simple test window to put into the splitter. - """ - def __init__(self, parent, colour, label): - wx.Panel.__init__(self, parent, style=wx.BORDER_SUNKEN) - self.SetBackgroundColour(colour) - wx.StaticText(self, -1, label, (5,5)) - - def SetOtherLabel(self, label): - wx.StaticText(self, -1, label, (5, 30)) - class Panel(wx.Panel): def __init__(self, parent): @@ -89,7 +77,8 @@ class Panel(wx.Panel): Name("Sagital Slice").Caption("Sagital slice"). MaximizeButton(True).CloseButton(False)) - self.aui_manager.AddPane(volume_viewer.Viewer(self), + self.aui_manager.AddPane(VolumeViewerCover(self), + #self.aui_manager.AddPane(volume_viewer.Viewer(self) wx.aui.AuiPaneInfo().Row(1).Name("Volume"). Bottom().Centre().Caption("Volume"). MaximizeButton(True).CloseButton(False)) @@ -150,8 +139,8 @@ class Panel(wx.Panel): Name("Sagittal Slice").Caption("Sagittal slice"). MaximizeButton(True).CloseButton(False)) - p4 = volume_viewer.Viewer(self) - aui_manager.AddPane(p4, + #p4 = volume_viewer.Viewer(self) + aui_manager.AddPane(VolumeViewerCover, wx.aui.AuiPaneInfo(). Name("Volume").Caption("Volume"). MaximizeButton(True).CloseButton(False)) @@ -163,3 +152,50 @@ class Panel(wx.Panel): aui_manager.Update() + +class VolumeViewerCover(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(volume_viewer.Viewer(self), 1, wx.EXPAND|wx.GROW) + sizer.Add(VolumeToolPanel(self), 0, wx.EXPAND) + self.SetSizer(sizer) + sizer.Fit(self) + +#import wx.lib.platebtn as pbtn +import wx.lib.buttons as btn +import wx.lib.pubsub as ps +import wx.lib.colourselect as csel + +class VolumeToolPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, size = (8,100)) + + BMP_RAYCASTING = wx.Bitmap("../icons/volume_raycasting.png", wx.BITMAP_TYPE_PNG) + BMP_RAYCASTING.SetWidth(22) + BMP_RAYCASTING.SetHeight(22) + + button_raycasting=btn.GenBitmapToggleButton(self, 1, BMP_RAYCASTING, size=(24,24)) + button_raycasting.Bind(wx.EVT_BUTTON, self.OnToggleRaycasting) + self.button_raycasting = button_raycasting + + button_colour= csel.ColourSelect(self, 111,colour=(0,0,0),size=(24,24)) + button_colour.Bind(csel.EVT_COLOURSELECT, self.OnSelectColour) + self.button_colour = button_colour + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(button_colour, 0, wx.ALL, 1) + sizer.Add(button_raycasting, 0, wx.ALL, 1) + self.SetSizer(sizer) + sizer.Fit(self) + + def OnSelectColour(self, evt): + colour = c = [i/255.0 for i in evt.GetValue()] + ps.Publisher().sendMessage('Change volume viewer background colour', colour) + + def OnToggleRaycasting(self, evt): + if self.button_raycasting.GetToggle(): + ps.Publisher().sendMessage('Show raycasting volume') + else: + ps.Publisher().sendMessage('Hide raycasting volume') diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index 4c4e2b2..a6086e0 100755 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -30,7 +30,8 @@ import default_viewers as viewers class Frame(wx.Frame): def __init__(self, prnt): wx.Frame.__init__(self, id=-1, name='', parent=prnt, - pos=wx.Point(0, 0), size=wx.Size(1024, 768), + pos=wx.Point(0, 0), + size=wx.Size(1024, 768), #size = wx.DisplaySize(), style=wx.DEFAULT_FRAME_STYLE, title='InVesalius 3.0') self.Center(wx.BOTH) self.SetIcon(wx.Icon("../icons/invesalius.ico", wx.BITMAP_TYPE_ICO)) -- libgit2 0.21.2