editor.py 8.22 KB
#--------------------------------------------------------------------------
# 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 math
from wx.lib.pubsub import pub as Publisher
import vtk

AXIAL = 2
CORONAL = 1
SAGITAL = 0

class Editor:
    """
    To Use:
    
    editor = Editor()
    editor.SetInteractor(self.interector)
    editor.SetOperationType(2, (50,1200)) #threshold 50, 1200
    editor.SetInput(img_original.GetOutput(), img_threshold.GetOutput())
    editor.Render()
    """
    
    def __init__(self):
        
        self.interactor = None
        self.image_original = None
        self.image_threshold = None
        self.render = None
    
        self.lut = vtk.vtkLookupTable()
        self.lut_original = vtk.vtkLookupTable()
        self.image_color = vtk.vtkImageMapToColors()
        self.blend = blend = vtk.vtkImageBlend()
        self.map = map = vtk.vtkImageMapper()
        
        self.actor = actor = vtk.vtkImageActor()
        self.actor2 = actor2 = vtk.vtkImageActor()
        self.actor3 = actor3 = vtk.vtkImageActor()
        
        self.image_color_o = vtk.vtkImageMapToColors()
        
        self.operation_type = 0
        self.w = None
   
        self.slice = 0
        self.clicked = 0
        self.orientation = AXIAL

        self.w = (200, 1200)
    
        #self.plane_widget_x = vtk.vtkImagePlaneWidget()
    
        #self.actor.PickableOff()
    
    def SetInteractor(self, interactor):
        
        self.interactor = interactor
        self.render = interactor.GetRenderWindow().GetRenderers().GetFirstRenderer()          

        istyle = vtk.vtkInteractorStyleImage()
        istyle.SetInteractor(interactor)
        istyle.AutoAdjustCameraClippingRangeOn()
        interactor.SetInteractorStyle(istyle)
        
        istyle.AddObserver("LeftButtonPressEvent", self.Click)
        istyle.AddObserver("LeftButtonReleaseEvent", self.Release)
        istyle.AddObserver("MouseMoveEvent",self.Moved)

        pick = self.pick = vtk.vtkCellPicker()

    def SetActor(self, actor):
        self.actor = actor

    def SetImage(self, image):
        self.image = image

    def SetCursor(self, cursor):
        self.cursor = cursor
        self.cursor.CursorCircle(self.render)

    def SetOrientation(self, orientation):
        self.orientation = orientation

    def Click(self, obj, evt):
        self.clicked = 1

    def Release(self, obj, evt):
        self.clicked = 0

    def Moved(self, obj, evt):
        pos = self.interactor.GetEventPosition()
        wx = pos[0] #- last[0]
        wy = pos[1] #- last[1]
        self.pick.Pick(wx, wy, 0, self.render)
        x,y,z = self.pick.GetPickPosition()

        self.cursor.SetPosition(x, y, z)  
        self.cursor.Update()    
        if self.clicked == 1:
        #    op = self.rbOptions.GetSelection()
        #    a = (int(self.txtThresI.GetValue()), int(self.txtThresF.GetValue()))
        #    self.editor.SetOperationType(op, a)
            wx, wy, wz = self.From3dToImagePixel(pos, (x, y, z))
            print "Comecei"
            self.DoOperation(wx, wy, wz)
            print "Terminei"
            Publisher.sendMessage('Update images', self.image)
            Publisher.sendMessage('Update viewer', None)

        #self.cursor.Update()
        obj.OnMouseMove()

        self.interactor.Render()

    def From3dToImagePixel(self, mPos, pPos):
        """
        mPos - The mouse position in the screen position.
        pPos - the pick position in the 3d world
        """
        x, y, z = pPos
        bounds = self.actor.GetBounds()
        print bounds

        #c = vtk.vtkCoordinate()
        #c.SetCoordinateSystemToWorld()
        #c.SetValue(bounds[::2])
        #xi, yi = c.GetComputedViewportValue(self.render)

        #c.SetValue(bounds[1::2])
        #xf, yf = c.GetComputedViewportValue(self.render)
        
        xi, xf, yi, yf, zi, zf = bounds
        #c.SetValue(x, y, z)
        #wx, wy = c.GetComputedViewportValue(self.render)

        wx = x - xi
        wy = y - yi
        wz = z - zi
        
        dx = xf - xi
        dy = yf - yi
        dz = zf - zi

        try:
            wx = (wx * self.image.GetDimensions()[0]) / dx
        except ZeroDivisionError:
            wx = self.slice 
        try:
            wy = (wy * self.image.GetDimensions()[1]) / dy
        except ZeroDivisionError:
            wy = self.slice
        try:
            wz = (wz * self.image.GetDimensions()[2]) / dz
        except ZeroDivisionError:
            wz = self.slice
        
        return wx, wy, wz

    def SetInput(self, image_original, image_threshold):
    
        self.image_original = image_original
        self.image_threshold = image_threshold
   
    def SetSlice(self, a):
        self.slice = a

    def ChangeShowSlice(self, value):
        self.map.SetZSlice(value) 
        self.interactor.Render()

    def ErasePixel(self, x, y, z):
        """
        Deletes pixel, it is necessary to pass x, y and z.
        """
        self.image.SetScalarComponentFromDouble(x, y, z, 0, 0)
        self.image.Update()
        
    def FillPixel(self, x, y, z, colour = 3200):
        """
        Fill pixel, it is necessary to pass x, y and z 
        """

        self.image.SetScalarComponentFromDouble(x, y, z, 0, colour)
        
    def PixelThresholdLevel(self, x, y, z):
        """
        Erase or Fill with Threshold level
        """
        pixel_colour = self.image.GetScalarComponentAsDouble(x, y, z, 0)
        
        thres_i = self.w[0]
        thres_f = self.w[1]
    
        if (pixel_colour >= thres_i) and (pixel_colour <= thres_f):
            
            if (pixel_colour <= 0):
                self.FillPixel(x, y, z, 1)
            else:
                self.FillPixel(x, y, z, pixel_colour)
    
        else:
            self.ErasePixel(x, y, z)

    
    def DoOperation(self, xc, yc, zc):
        """
        This method scans the circle line by line.
        Extracted equation. 
        http://www.mathopenref.com/chord.html
        """
        extent = self.image.GetWholeExtent()
        cursor = self.cursor
        b = [0,0,0,0,0,0]
        self.actor.GetDisplayBounds(b)
        print "To Paint", xc, yc, zc
        print "Bounds", b
        print "dimensions", self.image.GetDimensions()
        xs, ys, zs = self.image.GetSpacing()
        try:
            zs = (b[-1]-b[-2]) / self.image.GetDimensions()[2]
        except ZeroDivisionError:
            pass
        print xs, ys, zs
        if self.orientation == AXIAL:
            o = lambda x, y: (xc + x, y + yc, zc)
        elif self.orientation == CORONAL:
            o = lambda x, y: (xc + x, yc, zc + y/zs)
        elif self.orientation == SAGITAL:
            o = lambda x, y: (xc, yc + x, zc + y/zs)

        if (self.operation_type == 0):
            operation = self.ErasePixel
        elif(self.operation_type == 1):
            operation = self.FillPixel
        else:
            operation = self.PixelThresholdLevel
        try:
            [operation(*o(k, yi)) for k, yi in cursor.GetPoints()]
        except:
            pass
        #if extent[0] <= k+xc <= extent[1] \
                #and extent[2] <= yi+yc <=extent[3]]
        
    def SetOperationType(self, operation_type = 0, 
                   w = (100,1200)):
        """
        Set Operation Type
        0 -> Remove
        1 -> Add
        2 -> Add or Remove with Threshold Level
        """            
        self.operation_type = operation_type
        self.w = w #threshold_value