Source code for PyOptoUS.process

# -*- coding: utf-8 -*-
"""
.. module:: process
   :synopsis: Module for performing: US probe calibration; calibration quality assessment; voxel-array reconstruction

"""


# Modules importation
import btk
from kine import *
from voxel_array_utils import *
from image_utils import *
from segment import *
from calib import *
import numpy as np
from scipy import ndimage as nd
import time
import vtk
import pickle



def checkOdd(n):
    if n % 2 <> 0:
        return True
    return False
    
def checkInt(n):
    if abs(round(n)-n) == 0:
        return True
    return False

def checkFreq(f):
    if f == None:
        raise Exception('Acquisition frequency was not defined')
    if not checkInt(f) or f <= 0:
        raise Exception('Acquisition frequency must be integer and positive')

def checkFreqRatio(f1, f2):
    if not checkInt(f2/f1) and not checkInt(f2/f1):
        raise Exception('Frequencies ratio must be integer')

def checkMkrList(mkrList):
    if len(set(mkrList)) < 4:
        raise Exception('There must be at least 4 markers')

def checkKineFiles(kineFiles, L=None):
    if kineFiles == None:
        raise Exception('Kinematics files were not set')
    if L <> None and len(kineFiles) <> L:
        raise Exception('Number of kinematics files must be {0}'.format(L))

def checkUsFiles(usFiles, L=None):
    if usFiles == None:
        raise Exception('US files were not set')
    if L <> None and len(usFiles) <> L:
        raise Exception('Number of US files must be {0}'.format(L))

def checkTimeWin(timeWin):
    if timeWin <> None:
        if len(timeWin) <> 2:
            raise Exception('Time window must contain 2 values')
        if timeWin[0] < 0 or not checkInt(timeWin[0]):
            raise Exception('First time window value must be integer and positive or zero')
        if timeWin[1] < 0 or not checkInt(timeWin[1]):
            raise Exception('Second time window value must be integer and positive or zero')

def checkIm2PrPose(prRim, Tim):
    if prRim == None or Tim == None:
         raise Exception('US probe calibration was not performed')        
    
    if len(prRim.shape) <> 2 or prRim.shape[0] <> 3 or prRim.shape[1] <> 3:
        raise Exception('US image-to-probe rotation matrix must be a 3 x 3 matrix')
    
    if len(Tim.shape) <> 1 or Tim.shape[0] <> 3:
        raise Exception('US image-to-probe position vector must be a 3 elements vector')

def checkPr2GlPose(Rpr, Tpr):
    if Rpr == None or Tpr == None:
         raise Exception('US probe pose computation was not performed')

    if len(Rpr.shape) <> 3 or Rpr.shape[1] <> 3 or Rpr.shape[2] <> 3:
        raise Exception('Probe-to-global rotation matrix must be a N x 3 x 3 matrix')

    if len(Tpr.shape) <> 2 or Tpr.shape[1] <> 3:
        raise Exception('Probe-to-global position vector must be a N x 3 matrix')

def checkIm2GlPose(R):
    if R == None:
        raise Exception('Pose for US images was not calculated')

def checkFeature(feature):
    if feature not in ['2_points_on_line', '1_point']:
        raise Exception('Feature not supported')

def checkSegmentation(segmentation):
    if segmentation not in ['manual']:
        raise Exception('Segmentation not supported')

def checkPhantom(phantom):
    if phantom not in ['single_wall']:
        raise Exception('Phantom not supported')

def checkFeatures(features):
    if features == None or len(features) == 0:
        raise Exception('Features from US images were not extracted')

def checkImDim(d):
    if d == None:
        raise Exception('At least one US image dimensions was not set')
    if not checkInt(d) or d <= 0:
        raise Exception('US image dimensions must be integer and positive')

def checkPixel2mm(pixel2mm):
    if pixel2mm == None:
        raise Exception('US image pixel-to-mm ratio was not set')
    if pixel2mm <= 0:
        raise Exception('US image pixel-to-mm ratio must be positive')

def checkFxyz(fxyz):
    if fxyz == None:
        raise Exception('Voxel array scaling factors were not set')
    if len(fxyz) <> 3:
        raise Exception('Voxel array scaling factors must be exactly 3')
    for i in xrange(0,3):
        if fxyz[i] <= 0:
            raise Exception('All voxel array scaling factors must be positive')  

def checkWrapper(wrapper):
    if wrapper == None:
        raise Exception('Wrapping method was not set')
    if wrapper not in ['parallelepipedon', 'convex_hull']:
        raise Exception('Wrapping method not supported')

def checkStep(step):
    if step == None:
        raise Exception('Wrapping creation step was not set')
    if not checkInt(step) or step <= 0:
        raise Exception('Wrapping creation step must be integer and positive')

def checkV(V):
    if V == None:
        raise Exception('Voxel array initialization was not performed')

def checkMethod(method):
    if method == None:
        raise Exception('Gaps filling method was not set')
    if method not in ['VNN', 'AVG_CUBE']:
        raise Exception('Gaps filling method not supported')

def checkBlocksN(blocksN):
    if blocksN == None:
        raise Exception('Blocks number was not set')
    if not checkInt(blocksN) or blocksN <= 0:
        raise Exception('Blocks number must be integer and positive or zero')

def checkMaxS(maxS):
    if maxS == None:
        raise Exception('Max search cube side was not set')
    if not checkInt(maxS) or not checkOdd(maxS) or maxS <= 0:
        raise Exception('Max search cube side must be integer, positive and odd')

def checkMinPct(minPct):
    if minPct == None:
        raise Exception('Acceptability percentage was not set')            
    if minPct < 0:
        raise Exception('Acceptability percentage must be positive or zero')

def checkSxyz(sxyz):
    if sxyz == None:
        raise Exception('vtkImageData spacing factors were not set')
    if len(sxyz) <> 3:
        raise Exception('vtkImageData spacing factors must be exactly 3')
    for i in xrange(0,3):
        if sxyz[i] <= 0:
            raise Exception('All vtkImageData spacing factors must be positive')
            
def checkFilePath(p):
    if p == None:
        raise Exception('File path was not set')
    if len(p) == 0:
        raise Exception('File path cannot be empty')

def checkPrecType(p):
    if p == None:
        raise Exception('Precision type was not set')
    if p not in ['RP']:
        raise Exception('Precision type not supported')

def checkAccType(a):
    if a == None:
        raise Exception('Accuracy type was not set')
    if a not in ['DA']:
        raise Exception('Accuracy type not supported')

def checkDist(d):
    if d == None:
        raise Exception('Distance was not set')
    if not checkInt(step) or step <= 0:
        raise Exception('Distance must be integer and positive')

def checkTimeVector(t):
    if t == None:
        raise Exception('Time vector was not set')    
    if len(t) == 0:
        raise Exception('Time vector cannot be empty') 
#    if t[0] <> 0:
#        raise Exception('First time element must be 0') 


def setInsideRange(v, bound, stepBase):
    while True:
        if v <= bound and v >= -bound:
            break
        step = -np.sign(v) * stepBase
        v += step
    return v
        

# Process class


[docs]class Process: """Class for performing: US probe calibration; calibration quality assessment; voxel-array reconstruction """ def __init__(self): # Data source files self.kineFiles = None self.usFiles = None # US images parameters self.w = None self.h = None self.pixel2mmX = None self.pixel2mmY = None self.usFreq = None self.usTimeVector = None # Kinematics file properties self.kineFreq = None # US probe-to-lab attitube self.Rpr = None self.Tpr = None # Image-to-US probe attitude self.prRim = None self.Tim = None # Calibration results self.calib = None # Image-to-lab attitude self.R = None # Voxel array parameters self.xl = None self.yl = None self.zl = None self.xo = None self.yo = None self.zo = None self.fx = None self.fy = None self.fz = None # US images alignment parameters self.wrapper = None self.step = None # Voxel array data self.V = None self.contV = None self.usedV = None self.internalV = None # vtkImageData properties self.sx = None self.sy = None self.sz = None # Gaps filling parameters self.method = None self.blocksN = None self.maxS = None self.minPct = None # Features extracted from US images self.features = None # Precisions container self.prec = {}
[docs] def setKineFiles(self, kineFiles): checkKineFiles(kineFiles) self.kineFiles = kineFiles
[docs] def getKineFiles(self): return self.kineFiles
[docs] def setUSFiles(self, usFiles): checkUsFiles(usFiles) self.usFiles = usFiles
[docs] def getUSFiles(self): return self.usFiles
[docs] def setDataSourceProperties(self, **kwargs): # Check kineFreq if 'kineFreq' in kwargs: kineFreq = kwargs['kineFreq'] checkFreq(kineFreq) self.kineFreq = kineFreq # Check reading method if 'fromUSFile' in kwargs: # Read US files filePaths = kwargs['fromUSFile'].split(';') usTimeVector = [] for i in xrange(0, len(filePaths)): filePath = filePaths[i] print 'Getting US image properties from file {0}...'.format(filePath) checkFilePath(filePath) D, ds = readDICOM(filePath) # Get w w = ds.Rows checkImDim(w) if i == 0: wPrev = w else: if w <> wPrev: raise Exception('w changes across files') # Get h h = ds.Columns checkImDim(h) if i == 0: hPrev = h else: if h <> hPrev: raise Exception('h changes across files') # Get USFreq usFreq = int(ds.CineRate) checkFreq(usFreq) if i == 0: usFreqPrev = usFreq else: if usFreq <> usFreqPrev: raise Exception('USFreq changes across files') # Get USTimeVector if 'FrameTimeVector' in ds: usTimeVectorTemp = ds.FrameTimeVector checkTimeVector(usTimeVectorTemp) Nf = int(ds.NumberOfFrames) if len(usTimeVectorTemp) <> Nf: usTimeVector = usTimeVectorTemp print 'FrameTimeVector length is different than the number of frames for file {0}, so it is probably a cut file. Final FrameTimeVector is set to this one'.format(filePath) else: checkFreq(self.kineFreq) usTimeVectorTemp[0] = 1. / self.kineFreq usTimeVector.extend(usTimeVectorTemp) print 'FrameTimeVector extended with the one from file {0}'.format(filePath) else: usTimeVector = None print 'FrameTimeVector not existing in file {0}, so it is forced to not exist for the merged file'.format(filePath) del D, ds if usTimeVector <> None: usTimeVector = (np.cumsum(usTimeVector) / 1000).tolist() # Get properties self.w = wPrev # print wPrev # print hPrev self.h = hPrev self.usFreq = usFreqPrev self.usTimeVector = usTimeVector print 'US image properties got from files' else: # Check w if 'w' in kwargs: w = kwargs['w'] checkImDim(w) self.w = w # Check h if 'h' in kwargs: h = kwargs['h'] checkImDim(h) self.h = h # Check USFreq if 'USFreq' in kwargs: usFreq = kwargs['USFreq'] checkFreq(usFreq) self.usFreq = usFreq # Check USTimeVector if 'USTimeVector' in kwargs: usTimeVector = kwargs['USTimeVector'] checkTimeVector(usTimeVector) self.usTimeVector = usTimeVector # Check pixel2mmX if 'pixel2mmX' in kwargs: pixel2mmX = kwargs['pixel2mmX'] checkPixel2mm(pixel2mmX) self.pixel2mmX = pixel2mmX # Check pixel2mmY if 'pixel2mmY' in kwargs: pixel2mmY = kwargs['pixel2mmY'] checkPixel2mm(pixel2mmY) self.pixel2mmY = pixel2mmY # Check frequencies ratio #checkFreqRatio(self.usFreq, self.kineFreq)
[docs] def calculatePoseForUSProbe(self, mkrList=['M1','M3','M2','M4'], timeWin=None): # Check input validity checkMkrList(mkrList) checkTimeWin(timeWin) checkKineFiles(self.kineFiles) if self.usTimeVector <> None: try: # print self.usTimeVector # print self.kineFreq checkTimeVector(self.usTimeVector) checkFreq(self.kineFreq) print 'Kinematics data resampling will be based on US data time vector' resampleStep = None timeVector = self.usTimeVector except: print 'Kinematics data resampling cannot be based on US data time vector' else: try: checkFreq(self.kineFreq) checkFreq(self.usFreq) resampleStep = self.kineFreq / self.usFreq timeVector = None print 'Kinematics data resampling will be based on US and kinematics frequency' except: raise Exception('Impossible to resample kinematics data') # Read C3Ds (http://b-tk.googlecode.com/svn/doc/Python/0.2/_getting_started.html) fileNames = self.kineFiles M1 = np.zeros((0,3)) M2 = np.zeros((0,3)) M3 = np.zeros((0,3)) M4 = np.zeros((0,3)) for fileName in fileNames: print 'Reading C3D file {0} ...'.format(fileName) reader = btk.btkAcquisitionFileReader() reader.SetFilename(fileName) reader.Update() acq = reader.GetOutput() # Convert points unit to mm pointUnit = acq.GetPointUnit() if pointUnit == 'mm': scaleFactor = 1. elif pointUnit == 'm': scaleFactor = 1000. else: raise Exception('Point unit not recognized') M1Name = mkrList[0] M2Name = mkrList[1] M3Name = mkrList[2] M4Name = mkrList[3] # Get relevant marker data (N x 3) M1 = np.vstack((M1, acq.GetPoint(M1Name).GetValues() * scaleFactor)) M2 = np.vstack((M2, acq.GetPoint(M2Name).GetValues() * scaleFactor)) M3 = np.vstack((M3, acq.GetPoint(M3Name).GetValues() * scaleFactor)) M4 = np.vstack((M4, acq.GetPoint(M4Name).GetValues() * scaleFactor)) print 'C3D file read' # Extracting time window, if necessary if timeWin <> None: i1 = timeWin[0] i2 = timeWin[1] M1 = M1[i1:i2,:] M2 = M1[i1:i2,:] M3 = M1[i1:i2,:] M4 = M1[i1:i2,:] # Resampling markers data to US frequency print 'Reseampling markers data to US frequency...' # import matplotlib.pyplot as plt # plt.plot(M1[:,1]) # plt.hold(True) M1 = resampleMarker(M1, step=resampleStep, x=timeVector, origFreq=self.kineFreq) # plt.plot(M1[:,1]) # plt.show() M2 = resampleMarker(M2, step=resampleStep, x=timeVector, origFreq=self.kineFreq) M3 = resampleMarker(M3, step=resampleStep, x=timeVector, origFreq=self.kineFreq) M4 = resampleMarker(M4, step=resampleStep, x=timeVector, origFreq=self.kineFreq) print 'Frames number after resampling: {0}'.format(M1.shape[0]) print 'Markers data resampled' print 'Calculating US probe roto-translation matrix for all time frames ...' # Create probe reference frame (N x 3 x 3) # M34 = M4 + (M3 - M4) / 2 # M12 = M1 + (M2 - M1) / 2 # Zpr = getVersor(np.cross(M3 - M1, M4 - M2)) # Xtemp = getVersor(np.cross(M34 - M12, Zpr)) # Ypr = getVersor(np.cross(Zpr, Xtemp)) # Xpr = getVersor(np.cross(Ypr, Zpr)) Xpr = getVersor(M4 - M3) Zpr = getVersor(np.cross(Xpr, M2 - M3)) Ypr = getVersor(np.cross(Zpr, Xpr)) # Create probe reference frame (N x 3 x 3) Rpr = np.array((Xpr.T, Ypr.T, Zpr.T)) # 3 x 3 x N Rpr = np.transpose(Rpr, (2,1,0)) # N x 3 x 3 self.Rpr = Rpr #print np.dot(Rpr[1,:,:],Rpr[1,:,:].T) # Define translation for probe # self.Tpr = M1 self.Tpr = M3# + 1.5 * (M1 - M3) print 'US probe roto-translation matrix calculated'
[docs] def calculatePoseForUSImages(self): # Check input validity checkIm2PrPose(self.prRim, self.Tim) checkPr2GlPose(self.Rpr, self.Tpr) print 'Calculating US images roto-translation matrix for all time frames ...' # Calculate rotation matrix for pixel to world R = np.dot(self.Rpr, self.prRim) # N x 3 x 3 T = np.dot(self.Rpr, self.Tim) + self.Tpr # Create affine transformation matrix (N x 4 x 4) Nf = self.Tpr.shape[0] R = np.concatenate((R, np.reshape(T,(Nf,3,1))), axis=2) b = np.tile(np.array((0,0,0,1)), (Nf,1)) R = np.concatenate((R, np.reshape(b,(Nf,1,4))), axis=1) print 'US images roto-translation matrix calculated' self.R = R
[docs] def extractFeatureFromUSImages(self, feature='2_points_on_line', segmentation='manual', featuresFile=None): # Check input validity checkFeature(feature) checkSegmentation(segmentation) if featuresFile <> None: checkFilePath(featuresFile) checkUsFiles(self.usFiles, L=1) # Read DICOM file print 'Reading DICOM file {0} ...'.format(self.usFiles[0]) D, ds = readDICOM(self.usFiles[0]) I = pixelData2grey(D) # supposing D is "small" and fits in memory print 'Number of frames: {0}'.format(I.shape[0]) print 'DICOM file read' # Perform feature extraction print 'Extracting features...' if featuresFile == None: if feature == '2_points_on_line': if segmentation == 'manual': ui = SegmentUI(I, Nclicks=2) self.features = ui.getData() if feature == '1_point': if segmentation == 'manual': ui = SegmentUI(I, Nclicks=1) self.features = ui.getData() else: # Read file with open(featuresFile, "rb") as f: self.features = pickle.load(f) print 'Features extracted'
[docs] def calibrateProbe(self, init, phantom='single_wall', fixed=[], correctResults=False): # Check input validity checkPhantom(phantom) checkFeatures(self.features) checkPr2GlPose(self.Rpr, self.Tpr) # checkFreq(self.usFreq) # checkFreq(self.kineFreq) # checkFreqRatio(self.usFreq, self.kineFreq) # Create expressions print 'Defining symbolic expressions...' eq, J, prTi, syms, allVariables, mus = createCalibEquations() variables = allVariables[:] print 'Expressions defined' # Set to 0 some variables depending on phantom if phantom == 'single_wall': # Select equation eq = eq[2,0] # select 3rd equation J = J[2,:] # select 3d equation J.col_del(8) # delete derivatives for x2 J.col_del(8) # delete derivatives for y2 J.col_del(9) # delete derivatives for alpha2 # Set to 0 some variables eq = eq.subs([(syms['x2'],0),(syms['y2'],0),(syms['alpha2'],0)]) J = J.subs([(syms['x2'],0),(syms['y2'],0),(syms['alpha2'],0)]) del syms['x2'], syms['y2'], syms['alpha2'] variables.remove('x2') variables.remove('y2') variables.remove('alpha2') # Delete unwanted variables for f in fixed: J.col_del(variables.index(f)) eq = eq.subs([(syms[f],init[f])]) J = J.subs([(syms[f],init[f])]) del syms[f] variables.remove(f) # Check variables init values if set(set(variables)).issubset(init.keys()) == False: raise Exception('Some variables were not initialized') initValues = [init[variables[i]] for i in xrange(0,len(variables))] # Calculate frequencies ratio # freqRatio = self.kineFreq / self.usFreq # Solve the equations print 'List of variables: {0}'.format(variables) print 'List of initial values: {0}'.format(initValues) print 'Solving equations system...' # sol = solveEquations(eq, J, syms, variables, initValues, self.Rpr[::freqRatio,:,:], self.Tpr[::freqRatio,:], self.features) sol, kond = solveEquations(eq, J, syms, variables, initValues, self.Rpr, self.Tpr, self.features) print 'Iterations terminated ({0})'.format(sol.message) if sol.success: # Show conditioning number print 'Condition number: %d' % kond # Create solution dictionary print 'System succesfully solved' x = {} for v in allVariables: if v in variables: x[v] = sol.x[variables.index(v)] else: if v in init: x[v] = init[v] else: x[v] = 0. # Correct results if wanted if correctResults: print 'Correcting results...' for a in ['alpha1', 'beta1', 'gamma1', 'alpha2', 'beta2', 'gamma2']: x[a] = setInsideRange(x[a], np.pi, 2*np.pi) val = setInsideRange(x['beta1'], np.pi/2, np.pi) if val <> x['beta1']: x['beta1'] = val x['alpha1'] += np.pi x['gamma1'] += np.pi if x['sy'] < 0: x['gamma1'] += np.pi x['sy'] = -x['sy'] if x['sx'] < 0: x['alpha1'] += np.pi x['beta1'] = -x['beta1'] x['gamma1'] = np.pi - x['gamma1'] x['sx'] = -x['sx'] for a in ['alpha1', 'gamma1', 'alpha2', 'gamma2']: x[a] = setInsideRange(x[a], np.pi, 2*np.pi) print 'Results corrected' # Calculate image-to-probe attitude subs = {} subs['x1'] = x['x1'] subs['y1'] = x['y1'] subs['z1'] = x['z1'] subs['alpha1'] = x['alpha1'] subs['beta1'] = x['beta1'] subs['gamma1'] = x['gamma1'] prTi = prTi.evalf(subs=subs) prTi = np.array(prTi).astype(np.float) prRim = prTi[0:3,0:3] Tim = prTi[0:3,3].squeeze() # Extract pixem2mm values sx = x['sx'] sy = x['sy'] # Print results rad2deg = 180. / np.pi for v, mu in zip(allVariables, mus): if mu == 'rad': val = x[v] * rad2deg mu = 'deg' else: val = x[v] print v + (': %f ' % val) + mu else: raise Exception('System not succesfully solved' ) # Set data internally self.prRim = prRim self.Tim = Tim self.pixel2mmX = sx self.pixel2mmY = sy self.calib = sol
[docs] def getProbeCalibrationData(self): return self.prRim, self.Tim, self.pixel2mmX, self.pixel2mmY, self.calib
[docs] def setProbeCalibrationData(self, prRim, Tim): # Check input validity checkIm2PrPose(prRim, Tim) self.prRim, self.Tim = prRim, Tim
[docs] def initVoxelArray(self, fxyz=(1,1,1)): # Check input validity checkFxyz(fxyz) self.fx, self.fy, self.fz = fxyz[0], fxyz[1], fxyz[2] checkIm2GlPose(self.R) checkImDim(self.w) checkImDim(self.h) checkPixel2mm(self.pixel2mmX) checkPixel2mm(self.pixel2mmY) # Calculate volume dimensions print 'Calculating voxel array dimension ...' pc = createImageCorners(self.w, self.h, self.pixel2mmX, self.pixel2mmY) pcg = np.dot(self.R,pc) # N x 4 xmin, xmax = np.amin(pcg[:,0]), np.amax(pcg[:,0]) ymin, ymax = np.amin(pcg[:,1]), np.amax(pcg[:,1]) zmin, zmax = np.amin(pcg[:,2]), np.amax(pcg[:,2]) # Calculate voxel array size self.xl = (np.round(self.fx * xmax) - np.round(self.fx * xmin)) + 1 self.yl = (np.round(self.fy * ymax) - np.round(self.fy * ymin)) + 1 self.zl = (np.round(self.fz * zmax) - np.round(self.fz * zmin)) + 1 self.xo = np.round(self.fx * xmin) self.yo = np.round(self.fy * ymin) self.zo = np.round(self.fz * zmin) print 'Voxel array dimension: {0} x {1} x {2}'.format(self.xl,self.yl,self.zl) # Create voxel array for grey values self.V = np.zeros(self.xl*self.yl*self.zl, dtype=np.uint8) # Create voxel array for grey values indicating hox many times a voxel # has been written self.contV = np.zeros(self.V.shape, dtype=np.uint8) # Create voxel array for bool values indicating if the voxel contains # raw data self.usedV = np.zeros(self.V.shape, dtype=np.bool) # Create voxel array for bool values indicating if the voxel belongs # to the seauence of slices (or near surrondings) self.internalV = np.zeros(self.V.shape, dtype=np.bool)
[docs] def setUSImagesAlignmentParameters(self, **kwargs): # Check wrapper if 'wrapper' in kwargs: wrapper = kwargs['wrapper'] checkWrapper(wrapper) self.wrapper = wrapper # Check step if 'step' in kwargs: step = kwargs['step'] checkStep(step) self.step = step
[docs] def alignUSImages(self): # Check input validity checkImDim(self.w) checkImDim(self.h) checkPixel2mm(self.pixel2mmX) checkPixel2mm(self.pixel2mmY) # checkFreq(self.usFreq) # checkFreq(self.kineFreq) # checkFreqRatio(self.usFreq, self.kineFreq) checkUsFiles(self.usFiles) checkIm2GlPose(self.R) checkFxyz([self.fx, self.fy, self.fz]) # xl, xo checkV(self.V) checkV(self.contV) checkV(self.usedV) checkV(self.internalV) checkWrapper(self.wrapper) checkStep(self.step) # Create pixel coordinates (in mm) in image reference frame print 'Creating pixel 3D coordinates in image reference frame ...' Np = self.h * self.w x = np.linspace(0,self.w-1,self.w) * self.pixel2mmX y = np.linspace(0,self.h-1,self.h) * self.pixel2mmY xv, yv = np.meshgrid(x, y) xv = np.reshape(xv.ravel(), (1,Np)) yv = np.reshape(yv.ravel(), (1,Np)) zv = np.zeros((1,Np)) b = np.ones((1,Np)) p = np.concatenate((xv,yv,zv,b), axis=0) # 4 x Np print 'Pixel 3D coordinates calculated' # Calculate image corners coordinates pc = createImageCorners(self.w, self.h, self.pixel2mmX, self.pixel2mmY) # Calculate frequencies ratio # freqRatio = self.kineFreq / self.usFreq # Calculate position for all the pixels, for all the time instant t = time.time() fileNames = self.usFiles ioffset = 0 for f in xrange(0,len(fileNames)): # Read DICOM file print 'Reading DICOM file {0} ...'.format(fileNames[f]) D, ds = readDICOM(fileNames[f]) print 'DICOM file read' #print D.shape Ni = D.shape[1] for i in xrange(0,Ni): # Create gray values #I = (.2126*D[0,i,:,:]+.7152*D[1,i,:,:]+.0722*D[2,i,:,:]).squeeze().astype(np.uint8) #I = rgb2grey(D[0,i,:,:].squeeze(), D[1,i,:,:].squeeze(), D[2,i,:,:].squeeze()) #print D.shape I = pixelData2grey(D[:,i,:,:]) #print I.shape print 'Inserting oriented slice for instant {0}/{1} ...'.format(i+1,Ni) # Fill voxel array with grey values # iR = freqRatio*(i+ioffset) iR = i + ioffset pg = np.dot(self.R[iR,:,:],p) # mm x = (np.round(pg[0,:] * self.fx) - self.xo).squeeze() # 1 x Np y = (np.round(pg[1,:] * self.fy) - self.yo).squeeze() z = (np.round(pg[2,:] * self.fz) - self.zo).squeeze() idxV = xyz2idx(x, y, z, self.xl, self.yl, self.zl).astype(np.int32) self.V[idxV] = (self.contV[idxV] * self.V[idxV]) / (self.contV[idxV] + 1) + I.ravel().squeeze() / (self.contV[idxV] + 1) self.contV[idxV] += 1 self.usedV[idxV] = True if self.wrapper == 'parallelepipedon': xc = x yc = y zc = z elif self.wrapper == 'convex_hull': # Calculate coordinates of image corners pcg = np.dot(self.R[iR,:,:],pc) # mm xc = (np.round(pcg[0,:] * self.fx) - self.xo).squeeze() # 1 x 4 yc = (np.round(pcg[1,:] * self.fy) - self.yo).squeeze() zc = (np.round(pcg[2,:] * self.fz) - self.zo).squeeze() # Create wrapper if i == 0: xcPrev = xc.copy() ycPrev = yc.copy() zcPrev = zc.copy() continue if i < Ni-1: if i % self.step: continue if self.wrapper == 'parallelepipedon': print 'Creating parallelepipedon ...' xcMin, xcMax = np.min((xc.min(),xcPrev.min())), np.max((xc.max(),xcPrev.max())) ycMin, ycMax = np.min((yc.min(),ycPrev.min())), np.max((yc.max(),ycPrev.max())) zcMin, zcMax = np.min((zc.min(),zcPrev.min())), np.max((zc.max(),zcPrev.max())) xcInternal, ycInternal, zcInternal = getCubeCoords(([xcMin,xcMax],[ycMin,ycMax],[zcMin,zcMax])) elif self.wrapper == 'convex_hull': print 'Creating convex hull ...' cCurrent = np.array((xc,yc,zc)).T cPrev = np.array((xcPrev,ycPrev,zcPrev)).T cHull = np.vstack((cCurrent,cPrev)) cInternal = getCoordsInConvexHull(cHull) xcInternal, ycInternal, zcInternal = cInternal[:,0], cInternal[:,1], cInternal[:,2] idxInternal = xyz2idx(xcInternal, ycInternal, zcInternal, self.xl, self.yl, self.zl).squeeze().astype(np.int32) self.internalV[idxInternal] = True xcPrev = xc.copy() ycPrev = yc.copy() zcPrev = zc.copy() ioffset += Ni del D, I, idxV, self.contV, xcPrev, ycPrev, zcPrev elapsed = time.time() - t print 'Elapsed time: {0} s'.format(elapsed) idxEmptyN = np.sum(~self.usedV) pctEmpty = 100.0 * idxEmptyN / self.V.shape[0] print 'Pct of empty voxels: ({0}% total)'.format(pctEmpty) pctInternal = 100.0 * np.sum(self.internalV) / self.V.shape[0] print 'Estimate of pct of internal voxels: ({0}% total)'.format(pctInternal) pctInternalEmpty = 100.0 * np.sum(self.internalV & ~self.usedV) / np.sum(self.internalV) print 'Estimate of pct of internal empty voxels: ({0}% internal)'.format(pctInternalEmpty)
[docs] def setGapFillingParameters(self, **kwargs): # Check method if 'method' in kwargs: method = kwargs['method'] checkMethod(method) self.method = method # Check blocksN if 'blocksN' in kwargs: blocksN = kwargs['blocksN'] checkBlocksN(blocksN) self.blocksN = blocksN # Check maxS if 'maxS' in kwargs: maxS = kwargs['maxS'] checkMaxS(maxS) self.maxS = maxS # Check minPct if 'minPct' in kwargs: minPct = kwargs['minPct'] checkMinPct(minPct) self.minPct = minPct
[docs] def fillGaps(self): # Check input validity checkMethod(self.method) checkBlocksN(self.blocksN) if self.method == 'AVG_CUBE': checkMaxS(self.maxS) checkMinPct(self.minPct) checkV(self.V) checkV(self.usedV) checkV(self.internalV) # xl, xo print 'Filling empty voxels ({0}), when possible ...'.format(self.method) bxl = self.xl byl = self.yl bzl = np.ceil(self.zl / self.blocksN) blockSize = bxl * byl * bzl for b in xrange(0, self.blocksN): print 'Block {0} ...'.format(b+1) # Initialize block indices idxBlock = np.zeros(self.V.shape, dtype=np.bool) idxBlock[b*blockSize : np.min([(b+1)*blockSize,self.V.shape[0]])] = True if self.method == 'VNN': # Apply VNN bzl = np.sum(idxBlock) / (bxl * byl) reshV = np.reshape((~self.usedV & self.internalV)[idxBlock], (bxl,byl,bzl)) reshV2 = np.reshape(self.V[idxBlock], (bxl,byl,bzl)) np.set_printoptions(threshold=np.nan) idxV = nd.distance_transform_edt(reshV, return_distances=False, return_indices=True) self.V[idxBlock] = reshV2[tuple(idxV)].ravel() self.usedV[idxBlock] = True # Print some info pctInternalEmpty = 100.0 * np.sum(self.internalV & ~self.usedV) / np.sum(self.internalV) print '\tEstimate of pct of internal empty voxels: ({0}% internal)'.format(pctInternalEmpty) elif self.method == 'AVG_CUBE': for S in np.arange(3, self.maxS+1, 2): if b == 0: # Generate voxel coordinates for the search cube xCube, yCube, zCube = getCubeCoords(S) # Remove central voxel of the cube idxCentral = np.nonzero((xCube == 0) & (yCube == 0) & (zCube == 0))[0] xCube = np.delete(xCube, idxCentral)[:, None] yCube = np.delete(yCube, idxCentral)[:, None] zCube = np.delete(zCube, idxCentral)[:, None] # Calculate distance from each vixel to central voxel distNeighs = (xCube**2 + yCube**2 + zCube**2)**(0.5) idxSort = np.argsort(distNeighs) distNeighs = 1. / distNeighs[idxSort,:] idxEmpty = np.nonzero((~self.usedV) & idxBlock & self.internalV)[0] # time bottleneck # Get coordinates of empty voxels xn, yn, zn = idx2xyz(idxEmpty, self.xl, self.yl, self.zl) xn = np.tile(xn, (S**3-1,1)) yn = np.tile(yn, (S**3-1,1)) zn = np.tile(zn, (S**3-1,1)) idxNeighs = xyz2idx(xn+xCube,yn+yCube,zn+zCube, xl, yl, zl).astype(np.int32) # Get values for neigbour voxels, empty or not neighsV = self.V[idxNeighs] neighsUsedV = self.usedV[idxNeighs] del idxNeighs # Sort by distance neighsV = neighsV[idxSort,:] neighsUsedV = neighsUsedV[idxSort,:] # Fill some empty voxels idxFillable = (np.sum(neighsUsedV, axis=0) >= np.round(self.minPct * (S**3-1)) ).squeeze() wMeanNum = np.sum(neighsUsedV * neighsV * distNeighs, axis=0).squeeze() wMeanDen = np.sum(neighsUsedV * distNeighs, axis=0).squeeze() self.V[idxEmpty[idxFillable]] = (wMeanNum[idxFillable] / wMeanDen[idxFillable]).round().astype(np.uint8) self.usedV[idxEmpty[idxFillable]] = True # Print some info pctInternalEmpty = 100.0 * np.sum(self.internalV & ~self.usedV) / np.sum(self.internalV) print '\tEstimate of pct of internal empty voxels after filling with cube of side {0}: ({1}% internal)'.format(S, pctInternalEmpty) # Delete biggest arrays in inner loop del idxEmpty, neighsV, neighsUsedV, idxFillable, wMeanNum, wMeanDen print 'Empty voxels filled when possible' return pctInternalEmpty
[docs] def setVtkImageDataProperties(self, **kwargs): # Check sxyz if 'sxyz' in kwargs: sxyz = kwargs['sxyz'] checkSxyz(sxyz) self.sx, self.sy, self.sz = sxyz[0], sxyz[1], sxyz[2]
[docs] def exportVoxelArrayToVTI(self, outFile): # Check input validity checkFilePath(outFile) checkSxyz([self.sx, self.sy, self.sz]) checkV(self.V) # xl # Create vtkImageData object for grey values voxel array print 'Creating vtkImageData object for grey values voxel array...' vtkV = nparray2vtkImageData(self.V, (self.xl,self.yl,self.zl), (self.sx,self.sy,self.sz), vtk.VTK_UNSIGNED_CHAR) print 'vtkImageData object created' # Write grey values voxel array to file print 'Saving VTI file for grey values voxel array {0} ...'.format(outFile) vtkImageData2vti(outFile, vtkV) print 'VTI file saved'
[docs] def exportVoxelArraySilhouetteToVTI(self, outFile): # Check input validity checkFilePath(outFile) checkSxyz([self.sx, self.sy, self.sz]) checkV(self.internalV) # Create vtkImageData object for silhouette voxel array print 'Creating vtkImageData object for grey values voxel array...' vtkInternalV = nparray2vtkImageData(255*self.internalV.astype(np.uint8), (self.xl,self.yl,self.zl), (self.sx,self.sy,self.sz), vtk.VTK_UNSIGNED_CHAR) print 'vtkImageData object created' # Write silhouette voxel array to file print 'Saving VTI file for silhouette voxel array {0} ...'.format(outFile) vtkImageData2vti(outFile, vtkInternalV) print 'VTI file saved'
[docs] def calculateProbeCalibrationPrecision(prec='RP'): # Check input validity checkPrecType(prec) checkPixel2mm(self.pixel2mmX) checkPixel2mm(self.pixel2mmY) # checkFreq(self.usFreq) # checkFreq(self.kineFreq) # checkFreqRatio(self.usFreq, self.kineFreq) checkIm2GlPose(self.R) checkFeatures(self.features) # Calculate frequencies ratio # freqRatio = self.kineFreq / self.usFreq # Calculate precision if prec == 'RP': print 'Calculating reconstruction precision...' # precValue = calculateRP(self.R[::freqRatio,:,:], self.pixel2mmX, self.pixel2mmY, self.features) precValue = calculateRP(self.R, self.pixel2mmX, self.pixel2mmY, self.features) print 'Precision calculated' # Set data internally self.prec[prec] = precValue
[docs] def getProbeCalibrationPrecision(prec='RP'): # Check input validity checkPrecType(prec) # Get precision if prec not in self.prec: raise Exception('This precision type was not calculated yet') return self.prec[prec]
[docs] def calculateProbeCalibrationAccuracy(acc='DA', L=80): # Check input validity checkAccType(acc) if acc == 'DA': checkDist(L) checkPixel2mm(self.pixel2mmX) checkPixel2mm(self.pixel2mmY) # checkFreq(self.usFreq) # checkFreq(self.kineFreq) # checkFreqRatio(self.usFreq, self.kineFreq) checkIm2GlPose(self.R) checkFeatures(self.features) # Calculate frequencies ratio # freqRatio = self.kineFreq / self.usFreq # Calculate precision if acc == 'DA': print 'Calculating distance accuracy...' # accValue = calculateDA(self.R[::freqRatio,:,:], self.pixel2mmX, self.pixel2mmY, self.features, L) accValue = calculateDA(self.R, self.pixel2mmX, self.pixel2mmY, self.features, L) print 'Accuracy calculated' # Set data internally self.acc[acc] = accValue
[docs] def getProbeCalibrationAccuracy(acc='DA'): # Check input validity checkAccType(acc) # Get accuracy if acc not in self.acc: raise Exception('This accuracy type was not calculated yet') return self.acc[acc]