# -*- coding: utf-8 -*-
"""
Created on Wed Feb 12 15:04:13 2014
@author: dmonar0
"""
# 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 checkKineFile(kineFile):
if kineFile == None:
raise Exception('Kinematics file not set')
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:
def __init__(self):
# Data source files
self.kineFile = 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 = {}
def setKineFile(self, filePath):
self.kineFile = filePath
def getKineFile(self):
return self.kineFile
def setDataSourceProperties(self, **kwargs):
# Check reading method
if 'fromUSFile' in kwargs:
# Read US file
filePath = kwargs['fromUSFile']
print 'Getting US image properties from file {0}...'.format(filePath)
checkFilePath(filePath)
D, ds = readDICOM(filePath)
print 'US image properties got from file'
# Get US image properties
w = ds.Rows
checkImDim(w)
self.w = w
h = ds.Columns
checkImDim(h)
self.h = h
usFreq = int(ds.CineRate)
checkFreq(usFreq)
self.usFreq = usFreq
usTimeVector = (np.cumsum(ds.FrameTimeVector) / 1000).tolist()
checkTimeVector(usTimeVector)
self.usTimeVector = usTimeVector
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 kineFreq
if 'kineFreq' in kwargs:
kineFreq = kwargs['kineFreq']
checkFreq(kineFreq)
self.kineFreq = kineFreq
# Check frequencies ratio
#checkFreqRatio(self.usFreq, self.kineFreq)
def calculatePoseForUSProbe(self, mkrList=['M1','M3','M2','M4'], timeWin=None):
# Check input validity
checkMkrList(mkrList)
checkTimeWin(timeWin)
checkKineFile(self.kineFile)
try:
checkTimeVector(self.usTimeVector)
checkFreq(self.kineFreq)
print 'Kinematics data resampling will be based on US data time vector'
resampleStep = None
timeVector = self.usTimeVector
except:
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 C3D (http://b-tk.googlecode.com/svn/doc/Python/0.2/_getting_started.html)
fileName = self.kineFile
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')
# Define frames window for calculation
if timeWin <> None:
i1 = timeWin[0]
i2 = timeWin[1]
else:
i1 = 0
i2 = acq.GetPoint(mkrList[0]).GetValues().shape[0]
M1 = mkrList[0]
M2 = mkrList[1]
M3 = mkrList[2]
M4 = mkrList[3]
# Get relevant marker data (N x 3)
M1 = acq.GetPoint(M1).GetValues()[i1:i2,:] * scaleFactor
M2 = acq.GetPoint(M2).GetValues()[i1:i2,:] * scaleFactor
M3 = acq.GetPoint(M3).GetValues()[i1:i2,:] * scaleFactor
M4 = acq.GetPoint(M4).GetValues()[i1:i2,:] * scaleFactor
print 'C3D file read'
# 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
print 'US probe roto-translation matrix calculated'
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
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'
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
def getProbeCalibrationData(self):
return self.prRim, self.Tim, self.pixel2mmX, self.pixel2mmY, self.calib
def setProbeCalibrationData(self, prRim, Tim):
# Check input validity
checkIm2PrPose(prRim, Tim)
self.prRim, self.Tim = prRim, Tim
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)
def setUSFiles(self, usFiles):
checkUsFiles(usFiles)
self.usFiles = usFiles
def getUSFiles(self):
return self.usFiles
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
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)
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
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
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]
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'
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'
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
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]
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
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]