#!/usr/bin/env python # -*- coding: utf-8 -*- #-------------------------------------------------------------------------- # 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 collections import itertools import tempfile import numpy as np import vtk from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor import styles import wx from wx.lib.pubsub import pub as Publisher try: from agw import floatspin as FS except ImportError: # if it's not there locally, try the wxPython lib. import wx.lib.agw.floatspin as FS import constants as const import cursor_actors as ca import data.slice_ as sl import data.vtk_utils as vtku import project import slice_data as sd import utils import session as ses from data import converters from data import measures ID_TO_TOOL_ITEM = {} STR_WL = "WL: %d WW: %d" ORIENTATIONS = { "AXIAL": const.AXIAL, "CORONAL": const.CORONAL, "SAGITAL": const.SAGITAL, } class ContourMIPConfig(wx.Panel): def __init__(self, prnt, orientation): wx.Panel.__init__(self, prnt) self.mip_size_spin = wx.SpinCtrl(self, -1, min=1, max=240, initial=const.PROJECTION_MIP_SIZE) self.mip_size_spin.SetValue(const.PROJECTION_MIP_SIZE) self.mip_size_spin.SetToolTip(wx.ToolTip(_("Number of slices used to compound the visualization."))) w, h = self.mip_size_spin.GetTextExtent('M') self.mip_size_spin.SetMinSize((5 * w + 10, -1)) self.mip_size_spin.SetMaxSize((5 * w + 10, -1)) self.border_spin = FS.FloatSpin(self, -1, min_val=0, max_val=10, increment=0.1, value=const.PROJECTION_BORDER_SIZE, digits=1, agwStyle=FS.FS_LEFT) self.border_spin.SetToolTip(wx.ToolTip(_("Controls the sharpness of the" " contour. The greater the" " value, the sharper the" " contour."))) w, h = self.border_spin.GetTextExtent('M') self.border_spin.SetMinSize((5 * w + 10, -1)) self.border_spin.SetMaxSize((5 * w + 10, -1)) self.inverted = wx.CheckBox(self, -1, _("Inverted order")) self.inverted.SetToolTip(wx.ToolTip(_("If checked, the slices are" " traversed in descending" " order to compound the" " visualization instead of" " ascending order."))) txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL) self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness")) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(txt_mip_size, 0, wx.EXPAND | wx.ALL, 2) sizer.Add(self.mip_size_spin, 0, wx.EXPAND) sizer.AddSpacer((10, 0)) sizer.Add(self.txt_mip_border, 0, wx.EXPAND | wx.ALL, 2) sizer.Add(self.border_spin, 0, wx.EXPAND) sizer.AddSpacer((10, 0)) sizer.Add(self.inverted, 0, wx.EXPAND) self.SetSizer(sizer) sizer.Fit(self) self.Layout() self.Update() self.SetAutoLayout(1) self.orientation = orientation self.canvas = None self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize) self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder) self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted) Publisher.subscribe(self._set_projection_type, 'Set projection type') def OnSetMIPSize(self, evt): val = self.mip_size_spin.GetValue() Publisher.sendMessage('Set MIP size %s' % self.orientation, val) def OnSetMIPBorder(self, evt): val = self.border_spin.GetValue() Publisher.sendMessage('Set MIP border %s' % self.orientation, val) def OnCheckInverted(self, evt): val = self.inverted.GetValue() Publisher.sendMessage('Set MIP Invert %s' % self.orientation, val) def _set_projection_type(self, pubsub_evt): tprojection = pubsub_evt.data if tprojection in (const.PROJECTION_MIDA, const.PROJECTION_CONTOUR_MIDA): self.inverted.Enable() else: self.inverted.Disable() if tprojection in (const.PROJECTION_CONTOUR_MIP, const.PROJECTION_CONTOUR_MIDA): self.border_spin.Enable() self.txt_mip_border.Enable() else: self.border_spin.Disable() self.txt_mip_border.Disable() class CanvasRendererCTX: def __init__(self, evt_renderer, canvas_renderer): """ A Canvas to render over a vtktRenderer. Params: evt_renderer: a vtkRenderer which this class is going to watch for any render event to update the canvas content. canvas_renderer: the vtkRenderer where the canvas is going to be added. This class uses wx.GraphicsContext to render to a vtkImage. TODO: Verify why in Windows the color are strange when using transparency. TODO: Add support to evento (ex. click on a square) """ self.canvas_renderer = canvas_renderer self.evt_renderer = evt_renderer self._size = self.canvas_renderer.GetSize() self.draw_list = [] self.gc = None self.last_cam_modif_time = -1 self.modified = True self._init_canvas() evt_renderer.AddObserver("StartEvent", self.OnPaint) def _init_canvas(self): w, h = self._size self._array = np.zeros((h, w, 4), dtype=np.uint8) self._cv_image = converters.np_rgba_to_vtk(self._array) self.mapper = vtk.vtkImageMapper() self.mapper.SetInputData(self._cv_image) self.mapper.SetColorWindow(255) self.mapper.SetColorLevel(128) self.actor = vtk.vtkActor2D() self.actor.SetPosition(0, 0) self.actor.SetMapper(self.mapper) self.actor.GetProperty().SetOpacity(0.99) self.canvas_renderer.AddActor2D(self.actor) self.rgb = np.zeros((h, w, 3), dtype=np.uint8) self.alpha = np.zeros((h, w, 1), dtype=np.uint8) self.bitmap = wx.EmptyBitmapRGBA(w, h) self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha) def _resize_canvas(self, w, h): self._array = np.zeros((h, w, 4), dtype=np.uint8) self._cv_image = converters.np_rgba_to_vtk(self._array) self.mapper.SetInputData(self._cv_image) self.mapper.Update() self.rgb = np.zeros((h, w, 3), dtype=np.uint8) self.alpha = np.zeros((h, w, 1), dtype=np.uint8) self.bitmap = wx.EmptyBitmapRGBA(w, h) self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha) self.modified = True def OnPaint(self, evt, obj): size = self.canvas_renderer.GetSize() w, h = size if self._size != size: self._size = size self._resize_canvas(w, h) cam_modif_time = self.evt_renderer.GetActiveCamera().GetMTime() if (not self.modified) and cam_modif_time == self.last_cam_modif_time: return self.last_cam_modif_time = cam_modif_time self._array[:] = 0 coord = vtk.vtkCoordinate() self.image.SetDataBuffer(self.rgb) self.image.SetAlphaBuffer(self.alpha) self.image.Clear() gc = wx.GraphicsContext.Create(self.image) gc.SetAntialiasMode(0) self.gc = gc font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) # font.SetWeight(wx.BOLD) font = gc.CreateFont(font, (0, 0, 255)) gc.SetFont(font) pen = wx.Pen(wx.Colour(255, 0, 0, 128), 2, wx.SOLID) brush = wx.Brush(wx.Colour(0, 255, 0, 128)) gc.SetPen(pen) gc.SetBrush(brush) gc.Scale(1, -1) modified = False for d in self.draw_list: d.draw_to_canvas(gc, self) modified = True gc.Destroy() self.gc = None if modified: self.bitmap = self.image.ConvertToBitmap() self.bitmap.CopyToBuffer(self._array, wx.BitmapBufferFormat_RGBA) self._cv_image.Modified() self.modified = False def calc_text_size(self, text, font=None): """ Given an unicode text and a font returns the width and height of the rendered text in pixels. Params: text: An unicode text. font: An wxFont. Returns: A tuple with width and height values in pixels """ if self.gc is None: return None gc = self.gc if font is None: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) _font = gc.CreateFont(font) gc.SetFont(_font) w, h = gc.GetTextExtent(text) return w, h def draw_line(self, pos0, pos1, arrow_start=False, arrow_end=False, colour=(255, 0, 0, 128), width=2, style=wx.SOLID): """ Draw a line from pos0 to pos1 Params: pos0: the start of the line position (x, y). pos1: the end of the line position (x, y). arrow_start: if to draw a arrow at the start of the line. arrow_end: if to draw a arrow at the end of the line. colour: RGBA line colour. width: the width of line. style: default wx.SOLID. """ if self.gc is None: return None gc = self.gc p0x, p0y = pos0 p1x, p1y = pos1 p0y = -p0y p1y = -p1y pen = wx.Pen(wx.Colour(*colour), width, wx.SOLID) pen.SetCap(wx.CAP_BUTT) gc.SetPen(pen) path = gc.CreatePath() path.MoveToPoint(p0x, p0y) path.AddLineToPoint(p1x, p1y) gc.StrokePath(path) font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font = gc.CreateFont(font) gc.SetFont(font) w, h = gc.GetTextExtent("M") p0 = np.array((p0x, p0y)) p3 = np.array((p1x, p1y)) if arrow_start: v = p3 - p0 v = v / np.linalg.norm(v) iv = np.array((v[1], -v[0])) p1 = p0 + w*v + iv*w/2.0 p2 = p0 + w*v + (-iv)*w/2.0 path = gc.CreatePath() path.MoveToPoint(p0) path.AddLineToPoint(p1) path.MoveToPoint(p0) path.AddLineToPoint(p2) gc.StrokePath(path) if arrow_end: v = p3 - p0 v = v / np.linalg.norm(v) iv = np.array((v[1], -v[0])) p1 = p3 - w*v + iv*w/2.0 p2 = p3 - w*v + (-iv)*w/2.0 path = gc.CreatePath() path.MoveToPoint(p3) path.AddLineToPoint(p1) path.MoveToPoint(p3) path.AddLineToPoint(p2) gc.StrokePath(path) def draw_circle(self, center, radius, width=2, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)): """ Draw a circle centered at center with the given radius. Params: center: (x, y) position. radius: float number. width: line width. line_colour: RGBA line colour fill_colour: RGBA fill colour. """ if self.gc is None: return None gc = self.gc pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID) gc.SetPen(pen) brush = wx.Brush(wx.Colour(*fill_colour)) gc.SetBrush(brush) cx, cy = center cy = -cy path = gc.CreatePath() path.AddCircle(cx, cy, 2.5) gc.StrokePath(path) gc.FillPath(path) def draw_rectangle(self, pos, width, height, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)): """ Draw a rectangle with its top left at pos and with the given width and height. Params: pos: The top left pos (x, y) of the rectangle. width: width of the rectangle. height: heigth of the rectangle. line_colour: RGBA line colour. fill_colour: RGBA fill colour. """ if self.gc is None: return None gc = self.gc px, py = pos gc.SetPen(wx.Pen(line_colour)) gc.SetBrush(wx.Brush(fill_colour)) gc.DrawRectangle(px, py, width, height) def draw_text(self, text, pos, font=None, txt_colour=(255, 255, 255)): """ Draw text. Params: text: an unicode text. pos: (x, y) top left position. font: if None it'll use the default gui font. txt_colour: RGB text colour """ if self.gc is None: return None gc = self.gc if font is None: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font = gc.CreateFont(font, txt_colour) gc.SetFont(font) px, py = pos gc.DrawText(text, px, py) def draw_text_box(self, text, pos, font=None, txt_colour=(255, 255, 255), bg_colour=(128, 128, 128, 128), border=5): """ Draw text inside a text box. Params: text: an unicode text. pos: (x, y) top left position. font: if None it'll use the default gui font. txt_colour: RGB text colour bg_colour: RGBA box colour border: the border size. """ if self.gc is None: return None gc = self.gc if font is None: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) _font = gc.CreateFont(font, txt_colour) gc.SetFont(_font) w, h = gc.GetTextExtent(text) px, py = pos py = -py # Drawing the box cw, ch = w + border * 2, h + border * 2 self.draw_rectangle((px, py), cw, ch, bg_colour, bg_colour) # Drawing the text tpx, tpy = px + border, py + border self.draw_text(text, (tpx, tpy), font, txt_colour) def draw_arc(self, center, p0, p1, line_colour=(255, 0, 0, 128), width=2): """ Draw an arc passing in p0 and p1 centered at center. Params: center: (x, y) center of the arc. p0: (x, y). p1: (x, y). line_colour: RGBA line colour. width: width of the line. """ if self.gc is None: return None gc = self.gc pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID) gc.SetPen(pen) c = np.array(center) v0 = np.array(p0) - c v1 = np.array(p1) - c c[1] = -c[1] v0[1] = -v0[1] v1[1] = -v1[1] s0 = np.linalg.norm(v0) s1 = np.linalg.norm(v1) a0 = np.arctan2(v0[1] , v0[0]) a1 = np.arctan2(v1[1] , v1[0]) if (a1 - a0) % (np.pi*2) < (a0 - a1) % (np.pi*2): sa = a0 ea = a1 else: sa = a1 ea = a0 path = gc.CreatePath() path.AddArc((c[0], c[1]), min(s0, s1), sa, ea) gc.StrokePath(path) 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) # Interactor additional style self._number_slices = const.PROJECTION_MIP_SIZE self._mip_inverted = False self.style = None self.last_position_mouse_move = () self.state = const.STATE_DEFAULT self.overwrite_mask = False # All renderers and image actors in this viewer self.slice_data_list = [] self.slice_data = None self.slice_actor = None self.interpolation_slice_status = True # The layout from slice_data, the first is number of cols, the second # is the number of rows self.layout = (1, 1) self.orientation_texts = [] self.measures = measures.MeasureData() self.actors_by_slice_number = collections.defaultdict(list) self.renderers_by_slice_number = {} self.orientation = orientation self.slice_number = 0 self.scroll_enabled = True self.__init_gui() self._brush_cursor_op = const.DEFAULT_BRUSH_OP self._brush_cursor_size = const.BRUSH_SIZE self._brush_cursor_colour = const.BRUSH_COLOUR self._brush_cursor_type = const.DEFAULT_BRUSH_OP self.cursor = None self.wl_text = None self.on_wl = False self.on_text = False # VTK pipeline and actors self.__config_interactor() self.cross_actor = vtk.vtkActor() self.__bind_events() self.__bind_events_wx() self._flush_buffer = False def __init_gui(self): self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) self.scroll = scroll self.mip_ctrls = ContourMIPConfig(self, self.orientation) self.mip_ctrls.Hide() sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.interactor, 1, wx.EXPAND|wx.GROW) sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) background_sizer = wx.BoxSizer(wx.VERTICAL) background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) #background_sizer.Add(self.mip_ctrls, 0, wx.EXPAND|wx.GROW|wx.ALL, 2) self.SetSizer(background_sizer) background_sizer.Fit(self) self.Layout() self.Update() self.SetAutoLayout(1) self.pick = vtk.vtkWorldPointPicker() self.interactor.SetPicker(self.pick) def OnContextMenu(self, evt): if (self.last_position_mouse_move ==\ self.interactor.GetLastEventPosition()): self.menu.caller = self self.PopupMenu(self.menu) evt.Skip() def SetPopupMenu(self, menu): self.menu = menu def SetLayout(self, layout): self.layout = layout if (layout == (1,1)) and self.on_text: self.ShowTextActors() else: self.HideTextActors(change_status=False) slice_ = sl.Slice() self.LoadRenderers(slice_.GetOutput()) self.__configure_renderers() self.__configure_scroll() def HideTextActors(self, change_status=True): if self.wl_text: self.wl_text.Hide() [t.Hide() for t in self.orientation_texts] self.interactor.Render() if change_status: self.on_text = False def ShowTextActors(self): if self.on_wl and self.wl_text: self.wl_text.Show() [t.Show() for t in self.orientation_texts] self.Update() self.interactor.Render() self.on_text = True def __set_layout(self, pubsub_evt): layout = pubsub_evt.data self.SetLayout(layout) def __config_interactor(self): style = vtk.vtkInteractorStyleImage() interactor = self.interactor interactor.SetInteractorStyle(style) def SetInteractorStyle(self, state): cleanup = getattr(self.style, 'CleanUp', None) if cleanup: self.style.CleanUp() del self.style style = styles.get_style(state)(self) setup = getattr(style, 'SetUp', None) if setup: style.SetUp() self.style = style self.interactor.SetInteractorStyle(style) self.interactor.Render() self.state = state def UpdateWindowLevelValue(self, pubsub_evt): window, level = pubsub_evt.data self.acum_achange_window, self.acum_achange_level = (window, level) self.SetWLText(window, level) slc = sl.Slice() slc._update_wwwl_widget_nodes(window, level) Publisher.sendMessage('Update all slice') Publisher.sendMessage('Update clut imagedata widget') def UpdateWindowLevelText(self, pubsub_evt): window, level = pubsub_evt.data self.acum_achange_window, self.acum_achange_level = (window, level) self.SetWLText(window, level) self.interactor.Render() def OnClutChange(self, evt): Publisher.sendMessage('Change colour table from background image from widget', evt.GetNodes()) slc = sl.Slice() Publisher.sendMessage('Update window level value', (slc.window_width, slc.window_level)) def SetWLText(self, window_width, window_level): value = STR_WL%(window_level, window_width) if (self.wl_text): self.wl_text.SetValue(value) #self.interactor.Render() def EnableText(self): if not (self.wl_text): proj = project.Project() colour = const.ORIENTATION_COLOUR[self.orientation] # Window & Level text self.wl_text = vtku.Text() self.SetWLText(proj.level, proj.window) # Orientation text if self.orientation == 'AXIAL': values = [_('R'), _('L'), _('A'), _('P')] elif self.orientation == 'SAGITAL': values = [_('P'), _('A'), _('T'), _('B')] else: values = [_('R'), _('L'), _('T'), _('B')] left_text = self.left_text = vtku.TextZero() left_text.ShadowOff() left_text.SetColour(colour) left_text.SetPosition(const.TEXT_POS_VCENTRE_LEFT) left_text.SetVerticalJustificationToCentered() left_text.SetValue(values[0]) right_text = self.right_text = vtku.TextZero() right_text.ShadowOff() right_text.SetColour(colour) right_text.SetPosition(const.TEXT_POS_VCENTRE_RIGHT_ZERO) right_text.SetVerticalJustificationToCentered() right_text.SetJustificationToRight() right_text.SetValue(values[1]) up_text = self.up_text = vtku.TextZero() up_text.ShadowOff() up_text.SetColour(colour) up_text.SetPosition(const.TEXT_POS_HCENTRE_UP) up_text.SetJustificationToCentered() up_text.SetValue(values[2]) down_text = self.down_text = vtku.TextZero() down_text.ShadowOff() down_text.SetColour(colour) down_text.SetPosition(const.TEXT_POS_HCENTRE_DOWN_ZERO) down_text.SetJustificationToCentered() down_text.SetVerticalJustificationToBottom() down_text.SetValue(values[3]) self.orientation_texts = [left_text, right_text, up_text, down_text] self.slice_data.renderer.AddActor(self.wl_text.actor) self.slice_data.renderer.AddActor(left_text.actor) self.slice_data.renderer.AddActor(right_text.actor) self.slice_data.renderer.AddActor(up_text.actor) self.slice_data.renderer.AddActor(down_text.actor) def RenderTextDirection(self, directions): # Values are on ccw order, starting from the top: self.up_text.SetValue(directions[0]) self.left_text.SetValue(directions[1]) self.down_text.SetValue(directions[2]) self.right_text.SetValue(directions[3]) self.interactor.Render() def ResetTextDirection(self, cam): # Values are on ccw order, starting from the top: if self.orientation == 'AXIAL': values = [_("A"), _("R"), _("P"), _("L")] elif self.orientation == 'CORONAL': values = [_("T"), _("R"), _("B"), _("L")] else: # 'SAGITAL': values = [_("T"), _("P"), _("B"), _("A")] self.RenderTextDirection(values) self.interactor.Render() def UpdateTextDirection(self, cam): croll = cam.GetRoll() if (self.orientation == 'AXIAL'): if (croll >= -2 and croll <= 1): self.RenderTextDirection([_("A"), _("R"), _("P"), _("L")]) elif(croll > 1 and croll <= 44): self.RenderTextDirection([_("AL"), _("RA"), _("PR"), _("LP")]) elif(croll > 44 and croll <= 88): self.RenderTextDirection([_("LA"), _("AR"), _("RP"), _("PL")]) elif(croll > 89 and croll <= 91): self.RenderTextDirection([_("L"), _("A"), _("R"), _("P")]) elif(croll > 91 and croll <= 135): self.RenderTextDirection([_("LP"), _("AL"), _("RA"), _("PR")]) elif(croll > 135 and croll <= 177): self.RenderTextDirection([_("PL"), _("LA"), _("AR"), _("RP")]) elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): self.RenderTextDirection([_("P"), _("L"), _("A"), _("R")]) elif(croll >= -177 and croll <= -133): self.RenderTextDirection([_("PR"), _("LP"), _("AL"), _("RA")]) elif(croll >= -132 and croll <= -101): self.RenderTextDirection([_("RP"), _("PL"), _("LA"), _("AR")]) elif(croll >= -101 and croll <= -87): self.RenderTextDirection([_("R"), _("P"), _("L"), _("A")]) elif(croll >= -86 and croll <= -42): self.RenderTextDirection([_("RA"), _("PR"), _("LP"), _("AL")]) elif(croll >= -41 and croll <= -2): self.RenderTextDirection([_("AR"), _("RP"), _("PL"), _("LA")]) elif(self.orientation == "CORONAL"): if (croll >= -2 and croll <= 1): self.RenderTextDirection([_("T"), _("R"), _("B"), _("L")]) elif(croll > 1 and croll <= 44): self.RenderTextDirection([_("TL"), _("RT"), _("BR"), _("LB")]) elif(croll > 44 and croll <= 88): self.RenderTextDirection([_("LT"), _("TR"), _("RB"), _("BL")]) elif(croll > 89 and croll <= 91): self.RenderTextDirection([_("L"), _("T"), _("R"), _("B")]) elif(croll > 91 and croll <= 135): self.RenderTextDirection([_("LB"), _("TL"), _("RT"), _("BR")]) elif(croll > 135 and croll <= 177): self.RenderTextDirection([_("BL"), _("LT"), _("TR"), _("RB")]) elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): self.RenderTextDirection([_("B"), _("L"), _("T"), _("R")]) elif(croll >= -177 and croll <= -133): self.RenderTextDirection([_("BR"), _("LB"), _("TL"), _("RT")]) elif(croll >= -132 and croll <= -101): self.RenderTextDirection([_("RB"), _("BL"), _("LT"), _("TR")]) elif(croll >= -101 and croll <= -87): self.RenderTextDirection([_("R"), _("B"), _("L"), _("T")]) elif(croll >= -86 and croll <= -42): self.RenderTextDirection([_("RT"), _("BR"), _("LB"), _("TL")]) elif(croll >= -41 and croll <= -2): self.RenderTextDirection([_("TR"), _("RB"), _("BL"), _("LT")]) elif(self.orientation == "SAGITAL"): if(croll >= -101 and croll <= -87): self.RenderTextDirection([_("T"), _("P"), _("B"), _("A")]) elif(croll >= -86 and croll <= -42): self.RenderTextDirection([_("TA"), _("PT"), _("BP"), _("AB")]) elif(croll >= -41 and croll <= -2): self.RenderTextDirection([_("AT"), _("TP"), _("PB"), _("BA")]) elif (croll >= -2 and croll <= 1): self.RenderTextDirection([_("A"), _("T"), _("P"), _("B")]) elif(croll > 1 and croll <= 44): self.RenderTextDirection([_("AB"), _("TA"), _("PT"), _("BP")]) elif(croll > 44 and croll <= 88): self.RenderTextDirection([_("BA"), _("AT"), _("TP"), _("PB")]) elif(croll > 89 and croll <= 91): self.RenderTextDirection([_("B"), _("A"), _("T"), _("P")]) elif(croll > 91 and croll <= 135): self.RenderTextDirection([_("BP"), _("AB"), _("TA"), _("PT")]) elif(croll > 135 and croll <= 177): self.RenderTextDirection([_("PB"), _("BA"), _("AT"), _("TP")]) elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): self.RenderTextDirection([_("P"), _("B"), _("A"), _("T")]) elif(croll >= -177 and croll <= -133): self.RenderTextDirection([_("PT"), _("BP"), _("AB"), _("TA")]) elif(croll >= -132 and croll <= -101): self.RenderTextDirection([_("TP"), _("PB"), _("BA"), _("AT")]) def Reposition(self, slice_data): """ Based on code of method Zoom in the vtkInteractorStyleRubberBandZoom, the of vtk 5.4.3 """ ren = slice_data.renderer size = ren.GetSize() ren.ResetCamera() ren.GetActiveCamera().Zoom(1.0) self.interactor.Render() def ChangeBrushColour(self, pubsub_evt): vtk_colour = pubsub_evt.data[3] self._brush_cursor_colour = vtk_colour if (self.cursor): for slice_data in self.slice_data_list: slice_data.cursor.SetColour(vtk_colour) def SetBrushColour(self, pubsub_evt): colour_wx = pubsub_evt.data colour_vtk = [colour/float(255) for colour in colour_wx] self._brush_cursor_colour = colour_vtk if self.slice_data.cursor: self.slice_data.cursor.SetColour(colour_vtk) def Navigation(self, pubsub_evt): # Get point from base change x, y, z = pubsub_evt.data coord_cross = x, y, z position = self.slice_data.actor.GetInput().FindPoint(x, y, z) coord_cross = self.slice_data.actor.GetInput().GetPoint(position) coord = self.calcultate_scroll_position(position) Publisher.sendMessage('Update cross position', coord_cross) self.ScrollSlice(coord) self.interactor.Render() def ScrollSlice(self, coord): if self.orientation == "AXIAL": Publisher.sendMessage(('Set scroll position', 'SAGITAL'), coord[0]) Publisher.sendMessage(('Set scroll position', 'CORONAL'), coord[1]) elif self.orientation == "SAGITAL": Publisher.sendMessage(('Set scroll position', 'AXIAL'), coord[2]) Publisher.sendMessage(('Set scroll position', 'CORONAL'), coord[1]) elif self.orientation == "CORONAL": Publisher.sendMessage(('Set scroll position', 'AXIAL'), coord[2]) Publisher.sendMessage(('Set scroll position', 'SAGITAL'), coord[0]) def get_slice_data(self, render): #for slice_data in self.slice_data_list: #if slice_data.renderer is render: #return slice_data # WARN: Return the only slice_data used in this slice_viewer. return self.slice_data def calcultate_scroll_position(self, x, y): # Based in the given coord (x, y), returns a list with the scroll positions for each # orientation, being the first position the sagital, second the coronal # and the last, axial. if self.orientation == 'AXIAL': axial = self.slice_data.number coronal = y sagital = x elif self.orientation == 'CORONAL': axial = y coronal = self.slice_data.number sagital = x elif self.orientation == 'SAGITAL': axial = y coronal = x sagital = self.slice_data.number return sagital, coronal, axial def calculate_matrix_position(self, coord): x, y, z = coord xi, xf, yi, yf, zi, zf = self.slice_data.actor.GetBounds() if self.orientation == 'AXIAL': mx = round((x - xi)/self.slice_.spacing[0], 0) my = round((y - yi)/self.slice_.spacing[1], 0) elif self.orientation == 'CORONAL': mx = round((x - xi)/self.slice_.spacing[0], 0) my = round((z - zi)/self.slice_.spacing[2], 0) elif self.orientation == 'SAGITAL': mx = round((y - yi)/self.slice_.spacing[1], 0) my = round((z - zi)/self.slice_.spacing[2], 0) return mx, my def get_coordinate_cursor(self, mx, my, picker=None): """ Given the mx, my screen position returns the x, y, z position in world coordinates. Parameters mx (int): x position. my (int): y position picker: the picker used to get calculate the voxel coordinate. Returns: world coordinate (x, y, z) """ if picker is None: picker = self.pick slice_data = self.slice_data renderer = slice_data.renderer picker.Pick(mx, my, 0, renderer) x, y, z = picker.GetPickPosition() bounds = self.slice_data.actor.GetBounds() if bounds[0] == bounds[1]: x = bounds[0] elif bounds[2] == bounds[3]: y = bounds[2] elif bounds[4] == bounds[5]: z = bounds[4] return x, y, z def get_coordinate_cursor_edition(self, slice_data, picker=None): # Find position actor = slice_data.actor slice_number = slice_data.number if picker is None: picker = self.pick x, y, z = picker.GetPickPosition() # First we fix the position origin, based on vtkActor bounds bounds = 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) dx = bound_xf - bound_xi dy = bound_yf - bound_yi dz = bound_zf - bound_zi dimensions = self.slice_.matrix.shape try: x = (x * dimensions[2]) / dx except ZeroDivisionError: x = slice_number try: y = (y * dimensions[1]) / dy except ZeroDivisionError: y = slice_number try: z = (z * dimensions[0]) / dz except ZeroDivisionError: z = slice_number return x, y, z def get_voxel_coord_by_screen_pos(self, mx, my, picker=None): """ Given the (mx, my) screen position returns the voxel coordinate of the volume at (that mx, my) position. Parameters: mx (int): x position. my (int): y position picker: the picker used to get calculate the voxel coordinate. Returns: voxel_coordinate (x, y, z): voxel coordinate inside the matrix. Can be used to access the voxel value inside the matrix. """ if picker is None: picker = self.pick wx, wy, wz = self.get_coordinate_cursor(mx, my, picker) x, y, z = self.get_voxel_coord_by_world_pos(wx, wy, wz) return (x, y, z) def get_voxel_coord_by_world_pos(self, wx, wy, wz): """ Given the (x, my) screen position returns the voxel coordinate of the volume at (that mx, my) position. Parameters: wx (float): x position. wy (float): y position wz (float): z position Returns: voxel_coordinate (x, y, z): voxel coordinate inside the matrix. Can be used to access the voxel value inside the matrix. """ px, py = self.get_slice_pixel_coord_by_world_pos(wx, wy, wz) x, y, z = self.calcultate_scroll_position(px, py) return (x, y, z) def get_slice_pixel_coord_by_screen_pos(self, mx, my, picker=None): """ Given the (mx, my) screen position returns the pixel coordinate of the slice at (that mx, my) position. Parameters: mx (int): x position. my (int): y position picker: the picker used to get calculate the pixel coordinate. Returns: voxel_coordinate (x, y): voxel coordinate inside the matrix. Can be used to access the voxel value inside the matrix. """ if picker is None: picker = self.pick wx, wy, wz = self.get_coordinate_cursor(mx, my, picker) return self.get_slice_pixel_coord_by_world_pos(wx, wy, wz) return px, py def get_slice_pixel_coord_by_world_pos(self, wx, wy, wz): """ Given the (wx, wy, wz) world position returns the pixel coordinate of the slice at (that mx, my) position. Parameters: mx (int): x position. my (int): y position picker: the picker used to get calculate the pixel coordinate. Returns: voxel_coordinate (x, y): voxel coordinate inside the matrix. Can be used to access the voxel value inside the matrix. """ coord = wx, wy, wz px, py = self.calculate_matrix_position(coord) return px, py def get_coord_inside_volume(self, mx, my, picker=None): if picker is None: picker = self.pick slice_data = self.slice_data renderer = slice_data.renderer coord = self.get_coordinate_cursor(picker) position = slice_data.actor.GetInput().FindPoint(coord) if position != -1: coord = slice_data.actor.GetInput().GetPoint(position) return coord def __bind_events(self): Publisher.subscribe(self.LoadImagedata, 'Load slice to viewer') Publisher.subscribe(self.SetBrushColour, 'Change mask colour') Publisher.subscribe(self.UpdateRender, 'Update slice viewer') Publisher.subscribe(self.UpdateRender, 'Update slice viewer %s' % self.orientation) Publisher.subscribe(self.UpdateCanvas, 'Redraw canvas') Publisher.subscribe(self.UpdateCanvas, 'Redraw canvas %s' % self.orientation) Publisher.subscribe(self.ChangeSliceNumber, ('Set scroll position', self.orientation)) Publisher.subscribe(self.__update_cross_position, 'Update cross position') Publisher.subscribe(self.Navigation, 'Co-registered Points') ### # Publisher.subscribe(self.ChangeBrushColour, # 'Add mask') Publisher.subscribe(self.UpdateWindowLevelValue, 'Update window level value') Publisher.subscribe(self.UpdateWindowLevelText, 'Update window level text') #Publisher.subscribe(self._set_cross_visibility,\ # 'Set cross visibility') ### Publisher.subscribe(self.__set_layout, 'Set slice viewer layout') Publisher.subscribe(self.OnSetInteractorStyle, 'Set slice interaction style') Publisher.subscribe(self.OnCloseProject, 'Close project data') ##### Publisher.subscribe(self.OnShowText, 'Show text actors on viewers') Publisher.subscribe(self.OnHideText, 'Hide text actors on viewers') Publisher.subscribe(self.OnExportPicture,'Export picture to file') Publisher.subscribe(self.SetDefaultCursor, 'Set interactor default cursor') Publisher.subscribe(self.AddActors, 'Add actors ' + str(ORIENTATIONS[self.orientation])) Publisher.subscribe(self.RemoveActors, 'Remove actors ' + str(ORIENTATIONS[self.orientation])) Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') Publisher.subscribe(self.ReloadActualSlice, 'Reload actual slice') Publisher.subscribe(self.ReloadActualSlice, 'Reload actual slice %s' % self.orientation) Publisher.subscribe(self.OnUpdateScroll, 'Update scroll') # MIP Publisher.subscribe(self.OnSetMIPSize, 'Set MIP size %s' % self.orientation) Publisher.subscribe(self.OnSetMIPBorder, 'Set MIP border %s' % self.orientation) Publisher.subscribe(self.OnSetMIPInvert, 'Set MIP Invert %s' % self.orientation) Publisher.subscribe(self.OnShowMIPInterface, 'Show MIP interface') Publisher.subscribe(self.OnSetOverwriteMask, "Set overwrite mask") Publisher.subscribe(self.RefreshViewer, "Refresh viewer") Publisher.subscribe(self.SetInterpolatedSlices, "Set interpolated slices") Publisher.subscribe(self.UpdateInterpolatedSlice, "Update Slice Interpolation") def RefreshViewer(self, pubsub_evt): self.Refresh() def SetDefaultCursor(self, pusub_evt): self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) def OnExportPicture(self, pubsub_evt): Publisher.sendMessage('Begin busy cursor') view_prop_list = [] view_prop_list.append(self.slice_data.box_actor) self.slice_data.renderer.RemoveViewProp(self.slice_data.box_actor) id, filename, filetype = pubsub_evt.data dict = {"AXIAL": const.AXIAL, "CORONAL": const.CORONAL, "SAGITAL": const.SAGITAL} if id == dict[self.orientation]: if filetype == const.FILETYPE_POV: renwin = self.interactor.GetRenderWindow() image = vtk.vtkWindowToImageFilter() image.SetInput(renwin) writer = vtk.vtkPOVExporter() writer.SetFilePrefix(filename.split(".")[0]) writer.SetRenderWindow(renwin) writer.Write() else: ren = self.slice_data.renderer #Use tiling to generate a large rendering. image = vtk.vtkRenderLargeImage() image.SetInput(ren) image.SetMagnification(1) image.Update() image = image.GetOutput() # write image file if (filetype == const.FILETYPE_BMP): writer = vtk.vtkBMPWriter() elif (filetype == const.FILETYPE_JPG): writer = vtk.vtkJPEGWriter() elif (filetype == const.FILETYPE_PNG): writer = vtk.vtkPNGWriter() elif (filetype == const.FILETYPE_PS): writer = vtk.vtkPostScriptWriter() elif (filetype == const.FILETYPE_TIF): writer = vtk.vtkTIFFWriter() filename = "%s.tif"%filename.strip(".tif") writer.SetInputData(image) writer.SetFileName(filename) writer.Write() for actor in view_prop_list: self.slice_data.renderer.AddViewProp(actor) Publisher.sendMessage('End busy cursor') def OnShowText(self, pubsub_evt): self.ShowTextActors() def OnHideText(self, pubsub_evt): self.HideTextActors() def OnCloseProject(self, pubsub_evt): self.CloseProject() def CloseProject(self): for slice_data in self.slice_data_list: del slice_data self.slice_data_list = [] self.layout = (1, 1) self.orientation_texts = [] self.slice_number = 0 self.cursor = None self.wl_text = None self.pick = vtk.vtkWorldPointPicker() def OnSetInteractorStyle(self, pubsub_evt): state = pubsub_evt.data self.SetInteractorStyle(state) if (state != const.SLICE_STATE_EDITOR): Publisher.sendMessage('Set interactor default cursor') def __bind_events_wx(self): self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar) self.scroll.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScrollBarRelease) #self.scroll.Bind(wx.EVT_SCROLL_ENDSCROLL, self.OnScrollBarRelease) self.interactor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.interactor.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) self.interactor.Bind(wx.EVT_SIZE, self.OnSize) def LoadImagedata(self, pubsub_evt): mask_dict = pubsub_evt.data self.SetInput(mask_dict) def LoadRenderers(self, imagedata): number_renderers = self.layout[0] * self.layout[1] diff = number_renderers - len(self.slice_data_list) if diff > 0: for i in xrange(diff): slice_data = self.create_slice_window(imagedata) self.slice_data_list.append(slice_data) elif diff < 0: to_remove = self.slice_data_list[number_renderers::] for slice_data in to_remove: self.interactor.GetRenderWindow().RemoveRenderer(slice_data.renderer) self.slice_data_list = self.slice_data_list[:number_renderers] def __configure_renderers(self): proportion_x = 1.0 / self.layout[0] proportion_y = 1.0 / self.layout[1] # The (0,0) in VTK is in bottom left. So the creation from renderers # must be # in inverted order, from the top left to bottom right w, h = self.interactor.GetRenderWindow().GetSize() w *= proportion_x h *= proportion_y n = 0 for j in xrange(self.layout[1]-1, -1, -1): for i in xrange(self.layout[0]): slice_xi = i*proportion_x slice_xf = (i+1)*proportion_x slice_yi = j*proportion_y slice_yf = (j+1)*proportion_y position = (slice_xi, slice_yi, slice_xf, slice_yf) slice_data = self.slice_data_list[n] slice_data.renderer.SetViewport(position) # Text actor position x, y = const.TEXT_POS_LEFT_DOWN slice_data.text.SetPosition((x+slice_xi,y+slice_yi)) slice_data.SetCursor(self.__create_cursor()) slice_data.SetSize((w, h)) self.__update_camera(slice_data) style = 0 if j == 0: style = style | sd.BORDER_DOWN if j == self.layout[1] - 1: style = style | sd.BORDER_UP if i == 0: style = style | sd.BORDER_LEFT if i == self.layout[0] - 1: style = style | sd.BORDER_RIGHT slice_data.SetBorderStyle(style) n += 1 def __create_cursor(self): cursor = ca.CursorCircle() cursor.SetOrientation(self.orientation) #self.__update_cursor_position([i for i in actor_bound[1::2]]) cursor.SetColour(self._brush_cursor_colour) cursor.SetSpacing(self.slice_.spacing) cursor.Show(0) self.cursor_ = cursor return cursor def SetInput(self, mask_dict): self.slice_ = sl.Slice() max_slice_number = sl.Slice().GetNumberOfSlices(self.orientation) self.scroll.SetScrollbar(wx.SB_VERTICAL, 1, max_slice_number, max_slice_number) self.slice_data = self.create_slice_window() self.slice_data.SetCursor(self.__create_cursor()) self.cam = self.slice_data.renderer.GetActiveCamera() self.__build_cross_lines() self.canvas = CanvasRendererCTX(self.slice_data.renderer, self.slice_data.canvas_renderer) # Set the slice number to the last slice to ensure the camera if far # enough to show all slices. self.set_slice_number(max_slice_number - 1) self.__update_camera() self.slice_data.renderer.ResetCamera() self.interactor.GetRenderWindow().AddRenderer(self.slice_data.renderer) self.interactor.Render() self.EnableText() self.wl_text.Hide() ## Insert cursor self.SetInteractorStyle(const.STATE_DEFAULT) def __build_cross_lines(self): renderer = self.slice_data.overlay_renderer cross = vtk.vtkCursor3D() cross.AllOff() cross.AxesOn() self.cross = cross c = vtk.vtkCoordinate() c.SetCoordinateSystemToWorld() cross_mapper = vtk.vtkPolyDataMapper() cross_mapper.SetInputConnection(cross.GetOutputPort()) #cross_mapper.SetTransformCoordinate(c) p = vtk.vtkProperty() p.SetColor(1, 0, 0) cross_actor = vtk.vtkActor() cross_actor.SetMapper(cross_mapper) cross_actor.SetProperty(p) cross_actor.VisibilityOff() # Only the slices are pickable cross_actor.PickableOff() self.cross_actor = cross_actor renderer.AddActor(cross_actor) def __update_cross_position(self, pubsub_evt): pos = pubsub_evt.data self.cross.SetFocalPoint(pos) def _set_cross_visibility(self, visibility): self.cross_actor.SetVisibility(visibility) def _set_editor_cursor_visibility(self, visibility): for slice_data in self.slice_data_list: slice_data.cursor.actor.SetVisibility(visibility) def SetOrientation(self, orientation): self.orientation = orientation for slice_data in self.slice_data_list: self.__update_camera(slice_data) def create_slice_window(self): renderer = vtk.vtkRenderer() renderer.SetLayer(0) cam = renderer.GetActiveCamera() canvas_renderer = vtk.vtkRenderer() canvas_renderer.SetLayer(1) canvas_renderer.SetActiveCamera(cam) canvas_renderer.SetInteractive(0) canvas_renderer.PreserveDepthBufferOn() overlay_renderer = vtk.vtkRenderer() overlay_renderer.SetLayer(2) overlay_renderer.SetActiveCamera(cam) overlay_renderer.SetInteractive(0) self.interactor.GetRenderWindow().SetNumberOfLayers(3) self.interactor.GetRenderWindow().AddRenderer(overlay_renderer) self.interactor.GetRenderWindow().AddRenderer(canvas_renderer) self.interactor.GetRenderWindow().AddRenderer(renderer) actor = vtk.vtkImageActor() self.slice_actor = actor # TODO: Create a option to let the user set if he wants to interpolate # the slice images. if int(ses.Session().slice_interpolation) == 1: actor.InterpolateOff() else: actor.InterpolateOn() slice_data = sd.SliceData() slice_data.SetOrientation(self.orientation) slice_data.renderer = renderer slice_data.canvas_renderer = canvas_renderer slice_data.overlay_renderer = overlay_renderer slice_data.actor = actor slice_data.SetBorderStyle(sd.BORDER_ALL) renderer.AddActor(actor) renderer.AddActor(slice_data.text.actor) renderer.AddViewProp(slice_data.box_actor) return slice_data def UpdateInterpolatedSlice(self, pub_sub): if self.slice_actor != None: if ses.Session().slice_interpolation: self.slice_actor.InterpolateOff() else: self.slice_actor.InterpolateOn() self.interactor.Render() def SetInterpolatedSlices(self, pub_sub): self.interpolation_slice_status = status = pub_sub.data if self.slice_actor != None: if status == True: self.slice_actor.InterpolateOn() else: self.slice_actor.InterpolateOff() self.interactor.Render() def __update_camera(self): orientation = self.orientation proj = project.Project() orig_orien = proj.original_orientation self.cam.SetFocalPoint(0, 0, 0) self.cam.SetViewUp(const.SLICE_POSITION[orig_orien][0][self.orientation]) self.cam.SetPosition(const.SLICE_POSITION[orig_orien][1][self.orientation]) #self.cam.ComputeViewPlaneNormal() #self.cam.OrthogonalizeViewUp() self.cam.ParallelProjectionOn() def __update_display_extent(self, image): self.slice_data.actor.SetDisplayExtent(image.GetExtent()) self.slice_data.renderer.ResetCameraClippingRange() def UpdateRender(self, evt): self.interactor.Render() def UpdateCanvas(self, evt=None): cp_draw_list = self.canvas.draw_list[:] self.canvas.draw_list = [] # Removing all measures for i in cp_draw_list: if not isinstance(i, (measures.AngularMeasure, measures.LinearMeasure)): self.canvas.draw_list.append(i) # Then add all needed measures for (m, mr) in self.measures.get(self.orientation, self.slice_data.number): if m.visible: self.canvas.draw_list.append(mr) self.canvas.modified = True self.interactor.Render() def __configure_scroll(self): actor = self.slice_data_list[0].actor number_of_slices = self.layout[0] * self.layout[1] max_slice_number = actor.GetSliceNumberMax()/ \ number_of_slices if actor.GetSliceNumberMax()% number_of_slices: max_slice_number += 1 self.scroll.SetScrollbar(wx.SB_VERTICAL, 1, max_slice_number, max_slice_number) self.set_scroll_position(0) @property def number_slices(self): return self._number_slices @number_slices.setter def number_slices(self, val): if val != self._number_slices: self._number_slices = val buffer_ = self.slice_.buffer_slices[self.orientation] buffer_.discard_buffer() def set_scroll_position(self, position): self.scroll.SetThumbPosition(position) self.OnScrollBar() def UpdateSlice3D(self, pos): original_orientation = project.Project().original_orientation pos = self.scroll.GetThumbPosition() Publisher.sendMessage('Change slice from slice plane',\ (self.orientation, pos)) def OnScrollBar(self, evt=None, update3D=True): pos = self.scroll.GetThumbPosition() self.set_slice_number(pos) if update3D: self.UpdateSlice3D(pos) if self.state == const.SLICE_STATE_CROSS: # Update other slice's cross according to the new focal point from # the actual orientation. focal_point = self.cross.GetFocalPoint() Publisher.sendMessage('Update cross position', focal_point) Publisher.sendMessage('Update slice viewer') else: self.interactor.Render() if evt: if self._flush_buffer: self.slice_.apply_slice_buffer_to_mask(self.orientation) evt.Skip() def OnScrollBarRelease(self, evt): pos = self.scroll.GetThumbPosition() evt.Skip() def OnKeyDown(self, evt=None, obj=None): pos = self.scroll.GetThumbPosition() skip = True min = 0 max = self.slice_.GetMaxSliceNumber(self.orientation) projections = {wx.WXK_NUMPAD0 : const.PROJECTION_NORMAL, wx.WXK_NUMPAD1 : const.PROJECTION_MaxIP, wx.WXK_NUMPAD2 : const.PROJECTION_MinIP, wx.WXK_NUMPAD3 : const.PROJECTION_MeanIP, wx.WXK_NUMPAD4 : const.PROJECTION_MIDA, wx.WXK_NUMPAD5 : const.PROJECTION_CONTOUR_MIP, wx.WXK_NUMPAD6 : const.PROJECTION_CONTOUR_MIDA,} if self._flush_buffer: self.slice_.apply_slice_buffer_to_mask(self.orientation) if (evt.GetKeyCode() == wx.WXK_UP and pos > min): self.OnScrollForward() self.OnScrollBar() elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max): self.OnScrollBackward() self.OnScrollBar() elif (evt.GetKeyCode() == wx.WXK_NUMPAD_ADD): actual_value = self.mip_ctrls.mip_size_spin.GetValue() self.mip_ctrls.mip_size_spin.SetValue(actual_value + 1) if self.mip_ctrls.mip_size_spin.GetValue() != actual_value: self.number_slices = self.mip_ctrls.mip_size_spin.GetValue() self.ReloadActualSlice() elif (evt.GetKeyCode() == wx.WXK_NUMPAD_SUBTRACT): actual_value = self.mip_ctrls.mip_size_spin.GetValue() self.mip_ctrls.mip_size_spin.SetValue(actual_value - 1) if self.mip_ctrls.mip_size_spin.GetValue() != actual_value: self.number_slices = self.mip_ctrls.mip_size_spin.GetValue() self.ReloadActualSlice() elif evt.GetKeyCode() in projections: self.slice_.SetTypeProjection(projections[evt.GetKeyCode()]) Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()]) Publisher.sendMessage('Reload actual slice') skip = False self.UpdateSlice3D(pos) self.interactor.Render() if evt and skip: evt.Skip() def OnScrollForward(self, evt=None, obj=None): if not self.scroll_enabled: return pos = self.scroll.GetThumbPosition() min = 0 if(pos > min): if self._flush_buffer: self.slice_.apply_slice_buffer_to_mask(self.orientation) pos = pos - 1 self.scroll.SetThumbPosition(pos) self.OnScrollBar() def OnScrollBackward(self, evt=None, obj=None): if not self.scroll_enabled: return pos = self.scroll.GetThumbPosition() max = self.slice_.GetMaxSliceNumber(self.orientation) if(pos < max): if self._flush_buffer: self.slice_.apply_slice_buffer_to_mask(self.orientation) pos = pos + 1 self.scroll.SetThumbPosition(pos) self.OnScrollBar() def OnSize(self, evt): w, h = evt.GetSize() w = float(w) h = float(h) if self.slice_data: self.slice_data.SetSize((w, h)) evt.Skip() def OnSetMIPSize(self, pubsub_evt): val = pubsub_evt.data self.number_slices = val self.ReloadActualSlice() def OnSetMIPBorder(self, pubsub_evt): val = pubsub_evt.data self.slice_.n_border = val buffer_ = self.slice_.buffer_slices[self.orientation] buffer_.discard_buffer() self.ReloadActualSlice() def OnSetMIPInvert(self, pubsub_evt): val = pubsub_evt.data self._mip_inverted = val buffer_ = self.slice_.buffer_slices[self.orientation] buffer_.discard_buffer() self.ReloadActualSlice() def OnShowMIPInterface(self, pubsub_evt): value = pubsub_evt.data if value: if not self.mip_ctrls.Shown: self.mip_ctrls.Show() self.GetSizer().Add(self.mip_ctrls, 0, wx.EXPAND|wx.GROW|wx.ALL, 2) self.Layout() else: self.mip_ctrls.Hide() self.GetSizer().Detach(self.mip_ctrls) self.Layout() def OnSetOverwriteMask(self, pubsub_evt): value = pubsub_evt.data self.overwrite_mask = value def set_slice_number(self, index): inverted = self.mip_ctrls.inverted.GetValue() border_size = self.mip_ctrls.border_spin.GetValue() image = self.slice_.GetSlices(self.orientation, index, self.number_slices, inverted, border_size) self.slice_data.actor.SetInputData(image) for actor in self.actors_by_slice_number[self.slice_data.number]: self.slice_data.renderer.RemoveActor(actor) for actor in self.actors_by_slice_number[index]: self.slice_data.renderer.AddActor(actor) for (m, mr) in self.measures.get(self.orientation, self.slice_data.number): try: self.canvas.draw_list.remove(mr) except ValueError: pass for (m, mr) in self.measures.get(self.orientation, index): if m.visible: self.canvas.draw_list.append(mr) if self.slice_._type_projection == const.PROJECTION_NORMAL: self.slice_data.SetNumber(index) else: max_slices = self.slice_.GetMaxSliceNumber(self.orientation) end = min(max_slices, index + self.number_slices - 1) self.slice_data.SetNumber(index, end) self.__update_display_extent(image) self.cross.SetModelBounds(self.slice_data.actor.GetBounds()) def ChangeSliceNumber(self, pubsub_evt): index = pubsub_evt.data #self.set_slice_number(index) self.scroll.SetThumbPosition(index) pos = self.scroll.GetThumbPosition() self.set_slice_number(pos) self.interactor.Render() def ReloadActualSlice(self, pubsub_evt=None): pos = self.scroll.GetThumbPosition() self.set_slice_number(pos) self.interactor.Render() def OnUpdateScroll(self, pubsub_evt): max_slice_number = sl.Slice().GetNumberOfSlices(self.orientation) self.scroll.SetScrollbar(wx.SB_VERTICAL, 1, max_slice_number, max_slice_number) def OnSwapVolumeAxes(self, pubsub_evt): # Adjusting cursor spacing to match the spacing from the actual slice # orientation axis0, axis1 = pubsub_evt.data cursor = self.slice_data.cursor spacing = cursor.spacing if (axis0, axis1) == (2, 1): cursor.SetSpacing((spacing[1], spacing[0], spacing[2])) elif (axis0, axis1) == (2, 0): cursor.SetSpacing((spacing[2], spacing[1], spacing[0])) elif (axis0, axis1) == (1, 0): cursor.SetSpacing((spacing[0], spacing[2], spacing[1])) self.slice_data.renderer.ResetCamera() def AddActors(self, pubsub_evt): "Inserting actors" actors, n = pubsub_evt.data pos = self.scroll.GetThumbPosition() #try: #renderer = self.renderers_by_slice_number[n] #for actor in actors: #renderer.AddActor(actor) #except KeyError: #pass if pos == n: for actor in actors: self.slice_data.renderer.AddActor(actor) self.actors_by_slice_number[n].extend(actors) def RemoveActors(self, pubsub_evt): "Remove a list of actors" actors, n = pubsub_evt.data try: renderer = self.renderers_by_slice_number[n] except KeyError: for actor in actors: self.actors_by_slice_number[n].remove(actor) self.slice_data.renderer.RemoveActor(actor) else: for actor in actors: # Remove the actor from the renderer renderer.RemoveActor(actor) # and remove the actor from the actor's list self.actors_by_slice_number[n].remove(actor)