# -*- coding: utf-8 -*-
"""
.. module:: voxel_array_utils
:synopsis: helper module for voxel-array
"""
import numpy as np
from scipy.interpolate import griddata
from scipy import ndimage as nd
import vtk
from vtk.util import numpy_support as nps
import SimpleITK as sitk
[docs]class VoxelArray3D(object):
"""
"""
def __init__(self, dataType=np.uint8, dims=(0,0,0), scales=(1.,1.,1.)):
"""Constructor
"""
self.dataType = dataType
self.xl = dims[0]
self.yl = dims[1]
self.zl = dims[2]
self.fx = scales[0]
self.fy = scales[1]
self.fz = scales[2]
self.V = np.zeros(self.xl*self.yl*self.zl, dtype=self.dataType)
def getDims(self):
return self.xl, self.yl, self.zl
def getDataByIdx(self, idx):
return self.V[idx]
def getDataByEdges(self, x0, x1, y0, y1, z0, z1):
V = self.getNumpyArray3D()
return V[x0:x1, y0:y1, z0:z1]
def getSubVoxelArray(self, x0, x1, y0, y1, z0, z1):
V = self.getDataByEdges(x0, x1, y0, y1, z0, z1)
newV = VoxelArray3D(dataType=self.dataType, dims=V.shape, scales=(self.fx,self.fy,self.fz))
newV.setAllDataByNumpy3D(V)
return newV
def setDataByIdx(self, idx, data):
self.V[idx] = data
def addConstByIdx(self, idx, c):
self.V[idx] += c
def setAllDataByNumpy1D(self, data):
self.V = data
self.dataType = self.V.dtype
def setAllDataByNumpy3D(self, data):
self.setAllDataByNumpy1D(data.ravel(order='F'))
def getNumpyArray1D(self):
return self.V
def getNumpyArray3D(self):
return np.reshape(self.getNumpyArray1D(), (self.xl,self.yl,self.zl), order='F')
def extend(self, xMin, xMax, yMin, yMax, zMin, zMax):
if xMin < 0 or xMax > self.xl-1 or yMin < 0 or yMax > self.yl-1 or zMin < 0 or zMax > self.zl-1:
xa, ya, za = np.min((0, xMin)), np.min((0, yMin)), np.min((0, zMin))
xb, yb, zb = np.max((self.xl-1, xMax)), np.max((self.yl-1, yMax)), np.max((self.zl-1, zMax))
xl, yl, zl = xb - xa + 1, yb - ya + 1, zb - za + 1
dx, dy, dz = self.xl, self.yl, self.zl
x, y, z = -xa, -ya, -za
newV = np.zeros((xl,yl,zl), dtype=self.dataType)
tempV = self.getNumpyArray3D()
newV[x:x+dx,y:y+dy,z:z+dz] = tempV
self.xl = xl
self.yl = yl
self.zl = zl
self.setAllDataByNumpy3D(newV)
def fillGaps(self, contVA, internalVA, method='VNN', blocksN=1, blockDir='X', distTh=None, maxS=3, minPct=0.):
V = self.getNumpyArray1D()
usedV = contVA.getNumpyArray1D() > 0
internalV = internalVA.getNumpyArray1D()
print 'Filling empty voxels ({0}), when possible ...'.format(method)
if blockDir == 'X':
bxl = np.ceil(self.xl / blocksN)
byl = self.yl
bzl = self.zl
elif blockDir == 'Y':
bxl = self.xl
byl = np.ceil(self.yl / blocksN)
bzl = self.zl
elif blockDir == 'Z':
bxl = self.xl
byl = self.yl
bzl = np.ceil(self.zl / blocksN)
# blockSize = bxl * byl * bzl
# if len(self.getDims()) > 1:
# sliceMethod = 'fast'
# else:
# sliceMethod = 'slow'
sliceMethod = 'slow'
for b in xrange(0, blocksN):
print 'Block {0} ...'.format(b+1)
# Initialize block indices
cLims = [None] * 3
if blockDir == 'X':
cLims[0] = [b*bxl, np.min([(b+1)*bxl,self.xl])]
cLims[1] = [0, self.yl]
cLims[2] = [0, self.zl]
if (b+1)*bxl > self.xl:
bxl = self.xl - b * bxl
elif blockDir == 'Y':
cLims[0] = [0, self.xl]
cLims[1] = [b*byl, np.min([(b+1)*byl,self.yl])]
cLims[2] = [0, self.zl]
if (b+1)*byl > self.yl:
byl = self.yl - b * byl
elif blockDir == 'Z':
cLims[0] = [0, self.xl]
cLims[1] = [0, self.yl]
cLims[2] = [b*bzl, np.min([(b+1)*bzl,self.zl])]
if (b+1)*bzl > self.zl:
bzl = self.zl - b * bzl
if sliceMethod == 'slow':
xc, yc, zc = getCubeCoords(cLims)
ind = xyz2idx(xc, yc, zc, self.xl, self.yl, self.zl)
idxBlock = np.zeros(np.prod(self.getDims()), dtype=np.bool)
idxBlock[ind] = True
if method == 'VNN':
# Apply VNN
# bzl = np.sum(idxBlock) / (bxl * byl)
if sliceMethod == 'slow':
reshV = np.reshape((~usedV & internalV)[idxBlock], (bzl,byl,bxl))
reshV2 = np.reshape(V[idxBlock], (bzl,byl,bxl))
elif sliceMethod == 'fast':
reshV = (~usedV & internalV)[cLims[2][0]:cLims[2][1],cLims[1][0]:cLims[1][1],cLims[0][0]:cLims[0][1]]
reshV2 = V[cLims[2][0]:cLims[2][1],cLims[1][0]:cLims[1][1],cLims[0][0]:cLims[0][1]]
np.set_printoptions(threshold=np.nan)
if distTh == None:
idxV = nd.distance_transform_edt(reshV, return_distances=False, return_indices=True)
else:
edt, idxV = nd.distance_transform_edt(reshV, return_distances=True, return_indices=True)
idxTh = np.nonzero(edt > distTh)
idxV[0][idxTh] = idxTh[0]
idxV[1][idxTh] = idxTh[1]
idxV[2][idxTh] = idxTh[2]
del edt, idxTh
if sliceMethod == 'slow':
V[idxBlock] = reshV2[tuple(idxV)].ravel()
usedV[idxBlock] = True
del idxBlock
elif sliceMethod == 'fast':
V[cLims[2][0]:cLims[2][1],cLims[1][0]:cLims[1][1],cLims[0][0]:cLims[0][1]] = reshV2[tuple(idxV)]
usedV[cLims[2][0]:cLims[2][1],cLims[1][0]:cLims[1][1],cLims[0][0]:cLims[0][1]] = True
del reshV, reshV2, idxV
# Print some info
pctInternalEmpty = 100.0 * np.sum(internalV & ~usedV) / np.sum(internalV)
print '\tEstimate of pct of internal empty voxels: ({0}% internal)'.format(pctInternalEmpty)
elif method == 'AVG_CUBE':
for S in np.arange(3, 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((~usedV) & idxBlock & 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, self.xl, self.yl, self.zl)
# Get values for neigbour voxels, empty or not
neighsV = V[idxNeighs]
neighsUsedV = 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(minPct * (S**3-1)) ).squeeze()
wMeanNum = np.sum(neighsUsedV * neighsV * distNeighs, axis=0).squeeze()
wMeanDen = np.sum(neighsUsedV * distNeighs, axis=0).squeeze()
V[idxEmpty[idxFillable]] = (wMeanNum[idxFillable] / wMeanDen[idxFillable]).round().astype(np.uint8)
usedV[idxEmpty[idxFillable]] = True
# Print some info
pctInternalEmpty = 100.0 * np.sum(internalV & ~usedV) / np.sum(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 getVtkImageData(self, scales, vtkDataType):
vtkV = nparray2vtkImageData(self.getNumpyArray1D(), (self.xl,self.yl,self.zl), scales, vtkDataType)
return vtkV
def getCoordsSmallestWrappingParallelepipedon(self, toExclude=0):
V = self.getNumpyArray3D()
x, y, z = np.nonzero(V <> toExclude)
xa, xb = x.min(), x.max()
ya, yb = y.min(), y.max()
za, zb = z.min(), z.max()
return xa, xb, ya, yb, za, zb
[docs]class VoxelArray3DFrom2DImages(VoxelArray3D):
"""
"""
def __init__(self, **kwargs):
"""Constructor
"""
super(self.__class__, self).__init__(**kwargs)
# Create voxel array for grey values
#self.V = VoxelArray3D(dataType=np.uint8, dims=(self.xl,self.yl,self.zl), scales=(self.fx,self.fy,self.fz))
# Create voxel array for grey values indicating hox many times a voxel
# has been written
self.contV = VoxelArray3D(dataType=np.uint8, dims=self.getDims())
# Create voxel array for bool values indicating if the voxel contains
# raw data
#self.usedV = self.contV
# Create voxel array for bool values indicating if the voxel belongs
# to the sequence of slices
self.internalV = VoxelArray3D(dataType=np.bool, dims=self.getDims())
# Create corner coordinates for previously written images
self.xcPrev, self.ycPrev, self.zcPrev = None, None, None
def getCounterVoxelArray(self):
return self.contV
def getSilhouetteVoxelArray(self):
return self.internalV
def setCounterVoxelArray(self, contV):
self.contV = contV
def setSilhouetteVoxelArray(self, internalV):
self.internalV = internalV
def getSubVoxelArray(self, x0, x1, y0, y1, z0, z1):
V = self.getDataByEdges(x0, x1, y0, y1, z0, z1)
contV = self.contV.getSubVoxelArray(x0, x1, y0, y1, z0, z1)
internalV = self.internalV.getSubVoxelArray(x0, x1, y0, y1, z0, z1)
newV = VoxelArray3DFrom2DImages(dataType=self.dataType, dims=V.shape, scales=(self.fx,self.fy,self.fz))
newV.setAllDataByNumpy3D(V)
newV.setCounterVoxelArray(contV)
newV.setSilhouetteVoxelArray(internalV)
return newV
def extend(self, xMin, xMax, yMin, yMax, zMin, zMax):
parent = super(self.__class__, self)
parent.extend(xMin, xMax, yMin, yMax, zMin, zMax)
self.contV.extend(xMin, xMax, yMin, yMax, zMin, zMax)
self.internalV.extend(xMin, xMax, yMin, yMax, zMin, zMax)
def writeImageByIdx(self, idxV, I, fillVoxMethod):
parent = super(self.__class__, self)
if fillVoxMethod == 'avg':
parent.setDataByIdx(idxV, parent.getDataByIdx(idxV) * (self.contV.getDataByIdx(idxV) / (self.contV.getDataByIdx(idxV) + 1)) + I.ravel() * (1. / (self.contV.getDataByIdx(idxV) + 1)) )
elif fillVoxMethod == 'last':
parent.setDataByIdx(idxV, I.ravel())
elif fillVoxMethod == 'max':
parent.setDataByIdx(idxV, np.maximum(parent.getDataByIdx(idxV), I.ravel()) )
self.contV.addConstByIdx(idxV, 1)
self.internalV.setDataByIdx(idxV, True)
def writeImageByCoords(self, coords, I, fillVoxMethod):
x, y, z = coords
idxV = xyz2idx(x, y, z, self.xl, self.yl, self.zl)
self.writeImageByIdx(idxV, I, fillVoxMethod)
def fillGaps(self, **kwargs):
parent = super(self.__class__, self)
parent.fillGaps(self.contV, self.internalV, **kwargs)
def updateWrapper(self, wrapper, corners, cornersPrev=None):
xc, yc, zc = corners
if self.xcPrev is None or self.ycPrev is None or self.zcPrev is None:
self.xcPrev, self.ycPrev, self.zcPrev = corners
if cornersPrev is None:
xcPrev, ycPrev, zcPrev = self.xcPrev, self.ycPrev, self.zcPrev
else:
xcPrev, ycPrev, zcPrev = cornersPrev
if 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 wrapper == 'convex_hull':
print 'Creating convex hull ...'
cCurrent = np.array((xc,yc,zc)).T
cPrev = np.array((xcPrev,ycPrev,zcPrev)).T
if not np.array_equal(cCurrent,cPrev):
cHull = np.vstack((cCurrent,cPrev))
try:
cInternal = getCoordsInConvexHull(cHull)
xcInternal, ycInternal, zcInternal = cInternal[:,0], cInternal[:,1], cInternal[:,2]
idxInternal = xyz2idx(xcInternal, ycInternal, zcInternal, self.xl, self.yl, self.zl)
self.internalV.setDataByIdx(idxInternal, True)
except:
print 'Error in creating convex hull coordinates'
else:
print 'The 2 slices are exactly overlapped. Impossible to create convex hull'
self.xcPrev = xc.copy()
self.ycPrev = yc.copy()
self.zcPrev = zc.copy()
[docs]def getCoordsInConvexHull(p):
"""Create the convex hull for a list of points and the list of coorindates internal to it.
Parameters
----------
p : np.ndarray
N x 3 list of coordinates for which to calculate the cinvex hull. Coordinates should be integer.
Returns
-------
np.ndarray
M x 3 array of coordinates, where M is the number of points internal to the convex hull.
"""
# Get minimum and maximum coordinates
xMin, yMin, zMin = p.min(axis=0)
xMax, yMax, zMax = p.max(axis=0)
# Create coordinates between maximim and minimum
xCube, yCube, zCube = getCubeCoords(([xMin,xMax],[yMin,yMax],[zMin,zMax]))
ci = np.array((xCube, yCube, zCube)).T
# Linear interpolation
vi = griddata(p, np.ones((p.shape[0],)), ci, method='linear', fill_value=0)
# Delete points outside the convex hull
idx = np.nonzero(vi == 0)[0]
cInternal = np.delete(ci, idx, axis=0)
return cInternal
[docs]def getCubeCoords(S):
"""Create cube or parallelepipedon coordinates.
Parameters
----------
S : mixed
Parallelepipedon or cube size.
If int, it represents the cube side, and must be an odd number.
The coordinates origin is in the center of the cube.
If list, it must contain 3 lists (for x, y and z), each one containing mininum and maximum coordinate values.
Returns
-------
list
List of 3 ``np.ndarray`` objects (for x, y and z), containing coordinate values into the parallelepipedon / cube.
"""
if hasattr(S,'__len__') == False:
l1, l2 = -(S-1)/2, (S-1)/2
xx, yy, zz = np.mgrid[l1:l2,l1:l2,l1:l2]
else:
xx, yy, zz = np.mgrid[S[0][0]:S[0][1],S[1][0]:S[1][1],S[2][0]:S[2][1]]
cx = xx.flatten()
cy = yy.flatten()
cz = zz.flatten()
return cx, cy, cz
[docs]def getSphereCoords(r):
"""Create sphere coordinates.
Parameters
----------
r : int
Radius.
Returns
-------
list
List of 3 ``np.ndarray`` objects (for x, y and z), containing coordinate values into sphere.
"""
x, y, z = getCubeCoords(2*r+1)
points = np.vstack((x, y, z)).T
distance = np.power(np.sum(np.power(points,2),axis=1),.5)
spherePoints = points[distance<=r]
cx = spherePoints[:,0].squeeze()
cy = spherePoints[:,1].squeeze()
cz = spherePoints[:,2].squeeze()
return cx, cy, cz
[docs]def createRandomSpheresIn3DVA(xl, yl, zl, N=100, rMax='small'):
"""Create voxel array containing spheres with random position and radius.
Spheres voxels have maximun gray level, the rest has minumum grey level.
There is no internal check about spheres physically nesting into each other.
Parameters
----------
xl : int
Voxel array size along x.
yl : int
Voxel array size along y.
zl : int
Voxel array size along z.
N : int
Number of spheres.
rMax : mixed
Maximum radius of the sphere.
If 'small', it is equivalent to 5% of the largest voxel array dimension.
If int, it is manually indicated.
Returns
-------
np.array(uint8)
Voxel array created.
"""
V = np.zeros((xl,yl,zl), dtype=np.uint8)
if rMax == 'small':
rMax = 0.05 * np.max((xl,yl,zl))
centers = np.random.uniform(0., high=1., size=(N,3)) * (xl,yl,zl)
radii = np.random.uniform(1, high=rMax, size=N)
for i in xrange(centers.shape[0]):
c = (getSphereCoords(radii[i]) + centers[i,:][:,None]).astype(np.int32)
x = c[0,:].squeeze()
y = c[1,:].squeeze()
z = c[2,:].squeeze()
idx = (x >= 0) & (x < xl) & (y >= 0) & (y < yl) & (z >= 0) & (z < zl)
V[x[idx], y[idx], z[idx]] = 255
return V
[docs]def idx2xyz(idx, xl, yl, zl):
"""Transform a list of indices of 1D array into coordinates of a 3D volume of certain sizes.
Parameters
----------
idx : np.ndarray
1D array to be converted. An increment of ``idx``
corresponds to a an increment of x. When reaching ``xl``, x is reset and
y is incremented of one. When reaching ``yl``, x and y are reset and z is
incremented.
xl, yl, zl : int
Sizes for 3D volume.
Returns
-------
list
List of 3 ``np.ndarray`` objects (for x, y and z), containing coordinate value.
"""
z = np.floor(idx / (xl*yl))
r = np.remainder(idx, xl*yl)
y = np.floor(r / xl)
x = np.remainder(r, xl)
return x, y, z
[docs]def xyz2idx(x, y, z, xl, yl, zl, idx='counter'):
"""Transform coordinates of a 3D volume of certain sizes into a list of indices of 1D array.
This is the opposite of function ``idx2xyz()``.
Parameters
----------
x, y, z : np.ndarray
Coordinates to be converted.
xl, yl, zl : int
Sizes for 3D volume.
idx: str
Str ing indicating output type.
If 'counter', the output is an array of voxel IDs, incrementing while x coordinate is incrementing.
If 'list', a list (z,y,x) is created.
Returns
-------
np.ndarray or list
Voxel indices.
"""
x[x >= xl] = xl-1
y[y >= yl] = yl-1
z[z >= zl] = zl-1
x[x < 0] = 0
y[y < 0] = 0
z[z < 0] = 0
if idx == 'counter':
idx = (x + y * xl + z * (xl * yl)).astype(np.int32)
elif idx == 'list':
idx = (z.astype(np.int32), y.astype(np.int32), x.astype(np.int32))
return idx
[docs]def nparray2vtkImageData(v, d, s, vtkScalarType):
"""Transform a 1D ``numpy`` array into ``vtk.vtkImageData`` object.
The object contains only one scalar component.
Parameters
----------
v : np.ndarray
1D array to convert.
d : list
3-elem list of sizes of the ``vtk.vtkImageData``.
s : list
3-elem list of spacing factors of the ``vtk.vtkImageData`` (see `here <http://www.vtk.org/doc/nightly/html/classvtkImageData.html#ab3288d13810266e0b30ba0632f7b5b0b>`_).
vtkScalarType :
Scalar type to be allocated (e.g. ``vtk.VTK_UNSIGNED_CHAR``).
Returns
-------
vtk.vtkImageData
object.
"""
# Create source
source = vtk.vtkImageData()
source.SetDimensions(int(d[0]), int(d[1]), int(d[2]))
if vtk.VTK_MAJOR_VERSION <= 5:
source.SetNumberOfScalarComponents(1)
source.SetScalarType(vtkScalarType)
source.AllocateScalars()
else:
source.AllocateScalars(vtkScalarType, 1);
source.SetSpacing(s[0], s[1], s[2])
# Copy numpy voxel array to vtkDataArray
dataArray = nps.numpy_to_vtk(v, deep=0, array_type=None)
source.GetPointData().GetScalars().DeepCopy(dataArray)
return source
[docs]def vtkImageData2vti(filePath, source):
"""Export a ``vtk.vtkImageData`` object to VTI file.
Parameters
----------
filePath : str
Full path for the VTI to be created.
source : vtk.vtkImageData
object.
"""
writer = vtk.vtkXMLImageDataWriter()
writer.SetFileName(filePath)
if vtk.VTK_MAJOR_VERSION <= 5:
writer.SetInput(source)
else:
writer.SetInputData(source)
writer.Write()
def compound3D(V1, V2, S1, S2, O):
# Show raw volumes
image1 = sitk.GetImageFromArray(V1)
image2 = sitk.GetImageFromArray(V2)
# Show raw solhouettes
content1 = sitk.GetImageFromArray(S1)
content2 = sitk.GetImageFromArray(S2)
# Set the 3D rigid registration framework
fixed = sitk.Cast(image1, sitk.sitkFloat32)
moving = sitk.Cast(image2, sitk.sitkFloat32)
fixedContent = sitk.Cast(content1, sitk.sitkFloat32)
movingContent = sitk.Cast(content2, sitk.sitkFloat32)
def command_iteration(method) :
print("{0:3} = {1:10.5f} : {2}".format(method.GetOptimizerIteration(),
method.GetMetricValue(),
method.GetOptimizerPosition()))
R = sitk.ImageRegistrationMethod()
#R.SetMetricAsCorrelation()
#R.SetMetricAsJointHistogramMutualInformation()
R.SetMetricAsMattesMutualInformation()
R.SetOptimizerAsRegularStepGradientDescent(learningRate=1.0,
minStep=1e-4,
numberOfIterations=500,
gradientMagnitudeTolerance=1e-3
)
R.SetOptimizerScalesFromJacobian()
#R.SetOptimizerScalesFromPhysicalShift()
# tx = sitk.VersorRigid3DTransform()
# tx = sitk.AffineTransform(3)
# transfromDomainMeshSize = (1,1,1)
# tx = sitk.BSplineTransformInitializer(fixed, transfromDomainMeshSize)
displacement_image = sitk.Image(fixed.GetSize(), sitk.sitkVectorFloat64)
tx = sitk.DisplacementFieldTransform(displacement_image)
R.SetInitialTransform(tx)
R.SetInterpolator(sitk.sitkLinear)
R.AddCommand( sitk.sitkIterationEvent, lambda: command_iteration(R) )
# Run registration
# R.SetMetricFixedMask(content1)
# R.SetMetricMovingMask(content2)
content12 = sitk.GetImageFromArray(S1 & S2)
R.SetMetricFixedMask(content12)
R.SetMetricMovingMask(content12)
try:
outTx = R.Execute(fixed, moving)
except Exception as e:
print 'General error during registration'
print e
outTx = tx
print("-------")
print(outTx)
print("Optimizer stop condition: {0}".format(R.GetOptimizerStopConditionDescription()))
print(" Iteration: {0}".format(R.GetOptimizerIteration()))
print(" Metric value: {0}".format(R.GetMetricValue()))
# Run resampler for image compounding
resampler = sitk.ResampleImageFilter()
resampler.SetReferenceImage(fixed)
resampler.SetInterpolator(sitk.sitkLinear)
resampler.SetDefaultPixelValue(0)
resampler.SetTransform(outTx)
movingRes = resampler.Execute(moving)
image2Res = sitk.Cast(movingRes, sitk.sitkUInt8)
# Run resampler for silhouette compounding
resampler = sitk.ResampleImageFilter()
resampler.SetReferenceImage(fixedContent)
resampler.SetInterpolator(sitk.sitkLinear)
resampler.SetDefaultPixelValue(0)
resampler.SetTransform(outTx)
movingContentRes = resampler.Execute(movingContent)
content2Res = sitk.Cast(movingContentRes, sitk.sitkUInt8)
# Create other useful images
V2r = sitk.GetArrayFromImage(image2Res)
B1 = S1 > 0
B2 = S2 > 0
B2r = sitk.GetArrayFromImage(content2Res) > 0
V12r = V1.copy()
V12r[B2r] = V2r[B2r] # puts image 2 on top of image 1
V2r1 = V2r.copy()
V2r1[B1] = V1[B1] # puts image 1 on top of image 2
V12rMax = np.maximum(V1, V2r)
V12 = V1.copy()
V12[B2] = V2[B2] # puts image 2 on top of image 1
V21 = V2.copy()
V21[B1] = V1[B1] # puts image 1 on top of image 2
V12Max = np.maximum(V1, V2)
# Show all data
I = sitk.Compose((
content1/2.+content2/2., # silhouettes overlapping
image1, # fixed
image2, # moving
image1, # fixed
image2Res, # result moving
sitk.GetImageFromArray(V12), # moving on top of fixed
sitk.GetImageFromArray(V12r), # result moving on top of fixed
sitk.GetImageFromArray(V21), # fixed on top of moving
sitk.GetImageFromArray(V2r1), # fixed on top of result moving
sitk.GetImageFromArray(V12Max), # element-wise max between fixed and moving
sitk.GetImageFromArray(V12rMax), # element-wise max between fixed and result moving
))
sitk.Show(I)
# Get data
tx = sitk.VersorRigid3DTransform(outTx.GetInverse())
# tx = sitk.VersorRigid3DTransform(outTx)
# tx = sitk.AffineTransform(outTx.GetInverse())
# tx = sitk.AffineTransform(outTx)
# See http://insightsoftwareconsortium.github.io/SimpleITK-Notebooks/22_Transforms.html#Global-Transformations
C = np.array(O)[:,None]
A = np.array(tx.GetMatrix()).reshape((3,3))[::-1,::-1]
t = np.array(tx.GetTranslation())[:,None][::-1]
c = np.array(tx.GetCenter())[:,None][::-1] + C
print A
print t
print c
return A, t, c