def fourier_rotate_vol(v, angle): """ """ if v.dtype != np.dtype('float32') or np.isfortran(v) is False: v = np.array(v, dtype="float32", order="F") # get the rotation matrix from pytom.basic.structures import Rotation rot = Rotation(angle) m = rot.toMatrix() m = m.transpose() # get the invert rotation matrix m = np.array([m.getRow(0), m.getRow(1), m.getRow(2)], dtype="float32") m = m.flatten() dim_x, dim_y, dim_z = v.shape # # Do the ifftshift first and then fftshift! # v = np.fft.fftshift(np.fft.ifftshift(v)) # linearize it v = v.reshape((v.size)) # allocate the memory for the result res = np.zeros(v.size * 2, dtype="float32") # call the low level c function swig_nufft.fourier_rotate_vol(v, dim_x, dim_y, dim_z, m, res) res = res[::2] + 1j * res[1::2] res = res.reshape((dim_x, dim_y, dim_z), order='F') # inverse fft ans = np.fft.fftshift(np.real(np.fft.ifftn(np.fft.ifftshift(res)))) return np.array(ans, dtype="float32", order="F")
def nextRotation(self): """ nextRotation : @return: [z1 z2 x] for the next rotation or [None,None,None] after all rotations were sampled @author: Friedrich Foerster @change: Local Rotation had a bug causing too large rotations in Phi @date: 07/07/2014 """ if self._finished: return [None, None, None] from math import sin, ceil, pi, sqrt, atan2 #,modf from pytom.basic.structures import Rotation from pytom.angles.angleFnc import matToZXZ phi = self._currentZ1 if self._currentX == 0: npsi = 1 dpsi = 360. else: dpsi = self._increment / sin( float(self._currentX * self._increment) / 180. * pi) npsi = ceil(360. / dpsi) #make dpsi equidistant again dpsi = 360. / npsi localRotation = Rotation(z1=phi - self._currentZ2 * dpsi, z2=self._currentZ2 * dpsi, x=self._currentX * self._increment, paradigm='ZXZ') globalMatrix = localRotation.toMatrix() * self._startMatrix [phi, psi, theta] = matToZXZ(globalMatrix) if self._currentZ2 >= npsi - 1: self._currentZ2 = 0 if self._currentX >= ceil(self._shells / 2): self._currentX = 0 if self._currentZ1 >= self._shells * self._increment: self._finished = True return [self._startZ1, self._startZ2, self._startX] else: self._currentZ1 = self._currentZ1 + self._increment else: self._currentX = self._currentX + 1 else: self._currentZ2 = self._currentZ2 + 1 return [phi % 360, psi % 360, theta % 360]
def __init__(self, rotation, translation): from pytom.basic.structures import Rotation, Shift if rotation.__class__ == Rotation: pass elif rotation.__class__ == list: rotation = Rotation(rotation) else: raise RuntimeError("Rotation should be of type list or pytom.basic.structures.Rotation!") if not isinstance(translation, (Shift, list)): raise RuntimeError("Translation should be of type list or pytom.basic.structures.Shift!") super(TransformationMatrix, self).__init__(4,4) self.setRotationCoef(rotation.toMatrix()) self.setTranslationCoef(translation)
def fourier_rotate_vol(v, angle): """Be careful with rotating a odd-sized volume, since nfft will not give correct result! """ # get the rotation matrix from pytom.basic.structures import Rotation rot = Rotation(angle) m = rot.toMatrix() m = m.transpose() # get the invert rotation matrix m = np.array([m.getRow(0), m.getRow(1), m.getRow(2)], dtype="float32") m = m.flatten() # prepare the volume from pytom_numpy import vol2npy v = vol2npy(v) dim_x, dim_y, dim_z = v.shape # twice shift means no shift! # v = np.fft.fftshift(v) # linearize it v = v.reshape((v.size)) # allocate the memory for the result res = np.zeros(v.size * 2, dtype="float32") # call the low level c function swig_nufft.fourier_rotate_vol(v, dim_x, dim_y, dim_z, m, res) res = res[::2] + 1j * res[1::2] res = res.reshape((dim_x, dim_y, dim_z), order='F') # inverse fft ans = np.fft.ifftshift(np.real(np.fft.ifftn(np.fft.ifftshift(res)))) # transfer to pytom volume format from sh.frm import np2vol res = np2vol(ans) return res
def fourier_2d1d_iter_reconstruct(projections, tilt_angles, iteration=10, err=None): """Do the reconstruction on a projection list using direct Fourier inversion (2D+1D iterative). For noisy situation, the #iter should be small @param projections: projections @param tilt_angles: tilt angles of projections @param err: the percentage of allowed error """ from pytom.basic.structures import Rotation if len(projections) != len(tilt_angles): raise Exception("Length of projections and tilt angles not consistent!") freal = None # Fourier coefficients fimag = None kx = None # nodes positions kz = None weights = None # weights for i in range(len(tilt_angles)): proj = projections[i] if len(proj.shape) == 3: NX, NY, NZ = proj.shape assert NZ == 1 and NX == NY # for now only supports the square proj = proj.reshape((NX, NY)) # make it 2D elif len(proj.shape) == 2: NX, NY = proj.shape NZ = 1 else: raise Exception("Projection shape incorrect!") if i == 0: # nodes in 0 degree x, y = np.meshgrid(np.array(np.linspace(-0.5, 0.5, NX, endpoint=False), dtype="float32"), np.array(np.linspace(-0.5, 0.5, NY, endpoint=False), dtype="float32")) x = x.reshape((1, x.size)) y = y.reshape((1, y.size)) z = np.zeros(x.size, dtype="float32") xyz = np.vstack((x,y,z)) freal = np.zeros((NX, NY, len(tilt_angles)), dtype="float32") fimag = np.zeros((NX, NY, len(tilt_angles)), dtype="float32") # do the 2D Fourier transform on each projection fproj = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(proj))) freal[:,:,i] = np.array(np.real(fproj), dtype="float32") fimag[:,:,i] = np.array(np.imag(fproj), dtype="float32") # calculate the nodes positions ang = tilt_angles[i] # get the tilt angle of this projection rot = Rotation([270, 90, ang]) # rotate around y axis m = rot.toMatrix() m = np.array([m.getRow(0), m.getRow(1), m.getRow(2)], dtype="float32") kk = np.dot(m, xyz) kk = np.array(kk, dtype="float32") if kx is None: kx = kk[0][:NX] kz = kk[2][:NX] else: kx = np.hstack((kx, kk[0][:NX])) kz = np.hstack((kz, kk[2][:NX])) # compute the weights if i == 0: ang_interval = tilt_angles[1]-tilt_angles[0] elif i == len(tilt_angles)-1: ang_interval = tilt_angles[-1]-tilt_angles[-2] else: ang_interval = (tilt_angles[i+1] - tilt_angles[i-1])/2 w = np.abs(ang_interval/180.*np.pi*(np.array(list(range(NX//2, -NX//2, -1)), dtype="float32")*2)/(NX//2)**2) w[NX//2] = np.pi/(4*len(tilt_angles)*(NX//2)**2) # take care of the duplicates if weights is None: weights = w.reshape((w.size)) else: weights = np.hstack((weights, w.reshape((w.size)))) # change the Y and Z axis freal = np.swapaxes(freal, 1,2) freal = freal.reshape((freal.size), order="F") fimag = np.swapaxes(fimag, 1,2) fimag = fimag.reshape((fimag.size), order="F") # construct the damping factors damping = np.ones((NX, NX), dtype="float32") # radius = 25 # sigma = 5 # [xx, yy] = np.mgrid[0:NX, 0:NX] # rr = np.sqrt((xx-NX/2)**2+(yy-NX/2)**2) # ind = rr>radius # damping[ind] = np.exp(-((rr[ind] - radius)/sigma)**2/2) damping = damping.reshape((damping.size)) # estimate the variance of the noise if err and err > 0: data = np.array(projections) var_all = np.var(data) var_err = err*var_all threshold = var_err else: threshold = -1 # reconstruct M = freal.size Z = NX res_real = np.zeros(NX*NY*Z, dtype="float32") res_imag = np.zeros(NX*NY*Z, dtype="float32") weights = np.float32(weights) # weird! print(weights.shape, weights.dtype) swig_nufft.fourier_2d1d_iter_reconstruct(freal, fimag, NX, Z, M, weights, kz, kx, res_real, res_imag, iteration, threshold, damping) # resize and return the real part res = res_real.reshape((NX,NY,Z), order="F") # weird! res = np.swapaxes(res, 1,2) # weird! return res
def fourier_3d_iter_reconstruct(projections, tilt_angles, iteration=10): """Do the reconstruction on a projection list using direct Fourier inversion (3D iterative). @param projections: projections @param tilt_angles: tilt angles of projections """ from pytom.basic.structures import Rotation if len(projections) != len(tilt_angles): raise Exception("Length of projections and tilt angles not consistent!") freal = None # Fourier coefficients fimag = None kx = None # nodes positions ky = None kz = None weights = None # weights for i in range(len(tilt_angles)): proj = projections[i] NX, NY, NZ = proj.shape assert NZ == 1 and NX == NY # for now only supports the square proj = proj.reshape((NX, NY)) # make it 2D if i == 0: # nodes in 0 degree x, y = np.meshgrid(np.array(np.linspace(-0.5, 0.5, NX, endpoint=False), dtype="float32"), np.array(np.linspace(-0.5, 0.5, NY, endpoint=False), dtype="float32")) x = x.reshape((1, x.size)) y = y.reshape((1, y.size)) z = np.zeros(x.size, dtype="float32") xyz = np.vstack((x,y,z)) # do the 2D Fourier transform on each projection fproj = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(proj))) fproj_real = np.array(np.real(fproj), dtype="float32") fproj_imag = np.array(np.imag(fproj), dtype="float32") if freal is None: freal = fproj_real.reshape((fproj_real.size), order='F') # to be consistent with the order of kx,ky,kz fimag = fproj_imag.reshape((fproj_imag.size), order='F') else: freal = np.hstack((freal, fproj_real.reshape((fproj_real.size), order='F') )) fimag = np.hstack((fimag, fproj_imag.reshape((fproj_imag.size), order='F') )) # calculate the nodes positions ang = tilt_angles[i] # get the tilt angle of this project rot = Rotation([270, 90, ang]) # rotate around y axis m = rot.toMatrix() m = np.array([m.getRow(0), m.getRow(1), m.getRow(2)], dtype="float32") kk = np.dot(m, xyz) if kx is None: kx = kk[0] ky = kk[1] kz = kk[2] else: kx = np.hstack((kx, kk[0])) ky = np.hstack((ky, kk[1])) kz = np.hstack((kz, kk[2])) # compute the weights if i == 0: ang_interval = tilt_angles[1]-tilt_angles[0] elif i == len(tilt_angles)-1: ang_interval = tilt_angles[-1]-tilt_angles[-2] else: ang_interval = (tilt_angles[i+1] - tilt_angles[i-1])/2 w = np.abs(ang_interval/360.*(np.array(list(range(NX//2, -NX//2, -1)), dtype="float32")*2)) wei = np.tile(w, (NY, 1)) if weights is None: weights = wei.reshape((wei.size)) else: weights = np.hstack((weights, wei.reshape((wei.size)))) # reconstruct M = freal.size Z = NX res_real = np.zeros(NX*NY*Z, dtype="float32") res_imag = np.zeros(NX*NY*Z, dtype="float32") weights = np.float32(weights) swig_nufft.fourier_3d_iter_reconstruct(freal, fimag, NX, Z, M, weights, kx, ky, kz, res_real, res_imag, iteration) # resize and return the real part res = res_real.reshape((NX,NY,Z)) return res
def fourier_2d1d_gridding_reconstruct(projections, tilt_angles): """Do the reconstruction on a projection list using direct Fourier inversion (2D+1D gridding). @param projections: projections @param tilt_angles: tilt angles of projections """ from pytom.basic.structures import Rotation if len(projections) != len(tilt_angles): raise Exception("Length of projections and tilt angles not consistent!") freal = None # Fourier coefficients fimag = None kx = None # nodes positions kz = None weights = None # weights for i in range(len(tilt_angles)): proj = projections[i] NX, NY, NZ = proj.shape assert NZ == 1 and NX == NY # for now only supports the square proj = proj.reshape((NX, NY)) # make it 2D if i == 0: # nodes in 0 degree x, y = np.meshgrid(np.array(np.linspace(-0.5, 0.5, NX, endpoint=False), dtype="float32"), np.array(np.linspace(-0.5, 0.5, NY, endpoint=False), dtype="float32")) x = x.reshape((1, x.size)) y = y.reshape((1, y.size)) z = np.zeros(x.size, dtype="float32") xyz = np.vstack((x,y,z)) freal = np.zeros((NX, NY, len(tilt_angles)), dtype="float32") fimag = np.zeros((NX, NY, len(tilt_angles)), dtype="float32") # do the 2D Fourier transform on each projection fproj = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(proj))) freal[:,:,i] = np.array(np.real(fproj), dtype="float32") fimag[:,:,i] = np.array(np.imag(fproj), dtype="float32") # calculate the nodes positions ang = tilt_angles[i] # get the tilt angle of this project rot = Rotation([270, 90, ang]) # rotate around y axis m = rot.toMatrix() m = np.array([m.getRow(0), m.getRow(1), m.getRow(2)], dtype="float32") kk = np.dot(m, xyz) if kx is None: kx = kk[0][:NX] kz = kk[2][:NX] else: kx = np.hstack((kx, kk[0][:NX])) kz = np.hstack((kz, kk[2][:NX])) # compute the weights if i == 0: ang_interval = tilt_angles[1]-tilt_angles[0] elif i == len(tilt_angles)-1: ang_interval = tilt_angles[-1]-tilt_angles[-2] else: ang_interval = (tilt_angles[i+1] - tilt_angles[i-1])/2 w = np.abs(ang_interval/180.*np.pi*(np.array(list(range(NX//2, -NX//2, -1)), dtype="float32")*2)/(NX//2)**2) w[NX//2] = np.pi/(4*len(tilt_angles)*(NX//2)**2) # take care of the duplicates if weights is None: weights = w.reshape((w.size)) else: weights = np.hstack((weights, w.reshape((w.size)))) # change the Y and Z axis freal = np.swapaxes(freal, 1,2) freal = freal.reshape((freal.size), order="F") fimag = np.swapaxes(fimag, 1,2) fimag = fimag.reshape((fimag.size), order="F") # reconstruct M = freal.size Z = NX res_real = np.zeros(NX*NY*Z, dtype="float32") res_imag = np.zeros(NX*NY*Z, dtype="float32") weights = np.float32(weights) # weird! swig_nufft.fourier_2d1d_gridding_reconstruct(freal, fimag, NX, Z, M, weights, kz, kx, res_real, res_imag) # resize and return the real part res = res_real.reshape((NX,NY,Z), order="F") # weird! res = np.swapaxes(res, 1,2) # weird! return res
class LocalSampling(AngleObject): """ LocalSampling: Angular sampling identical to the AV3 package. \ See http://pytom.org/doc/pytom/alignment.html for more information. """ def __init__(self, shells=3, increment=3, z1Start=0.0, z2Start=0.0, xStart=0.0): """ @param shells: Number of shells to be scanned @param increment: Angular increment used @param z1Start: Start angle for Z1 rotation @param z2Start: Start angle for Z2 rotation @param xStart: Start angle for X rotation @author: Thomas Hrabe """ from pytom.basic.structures import Rotation self._shells = float(shells) if increment == 0.0: raise ValueError('LocalSampling : Increment is 0!') else: self._increment = float(increment) if shells == 0: raise ValueError('LocalSampling : Shells is 0!') self.setStartRotation(Rotation(z1=z1Start, z2=z2Start, x=xStart)) # initialize final rotation around z-axis of REFERENCE self.reset() def setStartRotation(self, startRotation): """ setStartRotation: Sets start rotation. @param startRotation: The start rotation. @type startRotation: L{pytom.basic.structures.Rotation} @return: Reference to current object @author: FF """ from pytom.basic.structures import Rotation self._startZ1 = float(startRotation[0]) self._startZ2 = float(startRotation[1]) self._startX = float(startRotation[2]) self._startRotation = Rotation(z1=self._startZ1, z2=self._startZ2, x=self._startX, paradigm='ZXZ') self._startMatrix = self._startRotation.toMatrix() self.reset() return self def getNumberShells(self): """ get number of shells for alignment @return: number of shells @rtype: L{int} """ return self._shells def setNumberShells(self, shells): """ set number of shells @param shells: number of shells @type shells: L{int} """ self._shells = shells def getIncrement(self): """ get angular increment @return: angular increment @rtype: L{float} """ return self._increment def setIncrement(self, increment): """ set angular increment @param increment: new increment in deg @type increment: L{float} @author: FF """ self._increment = increment def nextRotation(self): """ nextRotation : @return: [z1 z2 x] for the next rotation or [None,None,None] after all rotations were sampled @author: Friedrich Foerster @change: Local Rotation had a bug causing too large rotations in Phi @date: 07/07/2014 """ if self._finished: return [None, None, None] from math import sin, ceil, pi, sqrt, atan2 #,modf from pytom.basic.structures import Rotation from pytom.angles.angleFnc import matToZXZ phi = self._currentZ1 if self._currentX == 0: npsi = 1 dpsi = 360. else: dpsi = self._increment / sin( float(self._currentX * self._increment) / 180. * pi) npsi = ceil(360. / dpsi) #make dpsi equidistant again dpsi = 360. / npsi localRotation = Rotation(z1=phi - self._currentZ2 * dpsi, z2=self._currentZ2 * dpsi, x=self._currentX * self._increment, paradigm='ZXZ') globalMatrix = localRotation.toMatrix() * self._startMatrix [phi, psi, theta] = matToZXZ(globalMatrix) if self._currentZ2 >= npsi - 1: self._currentZ2 = 0 if self._currentX >= ceil(self._shells / 2): self._currentX = 0 if self._currentZ1 >= self._shells * self._increment: self._finished = True return [self._startZ1, self._startZ2, self._startX] else: self._currentZ1 = self._currentZ1 + self._increment else: self._currentX = self._currentX + 1 else: self._currentZ2 = self._currentZ2 + 1 return [phi % 360, psi % 360, theta % 360] def focusRotation(self, rotation=None, refinementAngle=10): """ focusRotation: Will focus this object on a given rotation by tightening the scan area. @param rotation: The rotation to focus on @type rotation: [phi,psi,theta] list @return: New LocaLSampling object @rtype: L{pytom.angles.localSampling.LocalSampling} @author: Thomas Hrabe """ if not rotation: rotation = [0, 0, 0] if not refinementAngle: refinementAngle = 10 return LocalSampling(self._shells, refinementAngle, rotation[0], rotation[1], rotation[2]) def distanceFunction(self, rotation): """ distanceFunction: determines the distance of of given rotation to old rotation @param rotation: rotation @type rotation: L{pytom.basic.structures.Rotation} @return: proposed increment for next iteration @author: FF """ from pytom.tools.maths import rotation_distance increment = rotation_distance(ang1=self._startRotation, ang2=rotation) #from math import sqrt,ceil #sqrt2 = 1.4142135623730951 ##must modulo 360 each value for avoiding negative values #deltaTheta = (self._startX % 360) - (rotation[2] % 360) #deltaPhi = (self._startZ1 % 360) - (rotation[0] % 360) # #increment = sqrt(pow(deltaTheta,2)+pow(deltaPhi,2)) / sqrt2 return increment def toXML(self): from lxml import etree try: angles_element = etree.Element("Angles", Type='AV3Sampling') angles_element.set("Increment", str(self._increment)) angles_element.set("Shells", str(self._shells)) angles_element.set("Phi_old", str(self._startZ1)) angles_element.set("Psi_old", str(self._startZ2)) angles_element.set("Theta_old", str(self._startX)) angles_element.set("ShellsParameter", str(self._shellsParameter)) angles_element.set("IncrementParameter", str(self._incrementParameter)) except: angles_element = etree.Element("Angles", Type='LocalSampling') angles_element.set("Increment", str(self._increment)) angles_element.set("Shells", str(self._shells)) angles_element.set("StartZ1", str(self._startZ1)) angles_element.set("StartZ2", str(self._startZ2)) angles_element.set("StartX", str(self._startX)) return angles_element def fromXML(self, xmlObj=-1): """ fromXML : Assigns values to job attributes from XML object @param xmlObj: A xml object @author: Thomas Hrabe """ from lxml.etree import _Element from pytom.basic.structures import Rotation if xmlObj.__class__ != _Element or xmlObj.get('Type') not in [ 'LocalSampling', 'Equidistant', 'AV3Sampling' ]: raise TypeError( 'You must provide a valid XML-LocalSampling object.') if xmlObj.get('Type') == 'Equidistant': xmlObj = xmlObj.xpath('Parameters')[0] try: self._increment = float(xmlObj.get('Increment')) except TypeError: self._increment = float(xmlObj.get('AngleIncrement')) try: self._shells = float(xmlObj.get('Shells')) except TypeError: self._shells = float(xmlObj.get('NumberShells')) try: self._startZ1 = float(xmlObj.get('StartZ1')) self.av3 = False except: self._startZ1 = float(xmlObj.get('Phi_old')) self.av3 = True try: self._startZ2 = float(xmlObj.get('StartZ2')) except: self._startZ2 = float(xmlObj.get('Psi_old')) try: self._startX = float(xmlObj.get('StartX')) except: self._startX = float(xmlObj.get('Theta_old')) if self.av3: try: self._shellsParameter = int(xmlObj.get('ShellsParameter')) except TypeError: raise Exception('No ShellsParameter Defined') try: self._incrementParameter = int( xmlObj.get('IncrementParameter')) except TypeError: raise Exception('No IncrementParameter Defined') self.reset() self.setStartRotation(startRotation=Rotation( z1=self._startZ1, z2=self._startZ2, x=self._startX)) def numberRotations(self): """ numberRotations: Returns the total number of rotations for the current object @author: Thomas Hrabe """ ang = [0, 0, 0] counter = 0 while not ang == [None, None, None]: ang = self.nextRotation() counter = counter + 1 self.reset() return counter def reset(self): """ reset: Resets the object that nextRotation would return the same sequence of results again @author: FF """ self._currentZ1 = -1. * self._shells * self._increment self._currentZ2 = 0 self._currentX = 0 self._finished = False #self._startRotation = None def copy(self, rotation): """ copy: Copies the current angle object to a new one with same settings but different starting rotation @param rotation: The other starting rotation @author: Thomas Hrabe """ if rotation.__class__ == list: return LocalSampling(self._shells, self._increment, rotation[0], rotation[1], rotation[2]) else: return LocalSampling(self._shells, self._increment, rotation.getPhi(), rotation.getPsi(), rotation.getTheta())
def general_transform(v, rot=None, shift=None, scale=None, order=[0, 1, 2]): """Perform general transformation using 3rd order spline interpolation. @param v: volume @type v: L{pytom_volume.vol} @param rot: rotate @type rot: pytom.basic.structures.Rotate or list @param shift: shift @type shift: pytom.basic.structures.Shift or list @param scale: scale / magnification along each dimension @type scale: list @param order: the order in which the three operations are performed (smaller means first) @type order: list @return: pytom_volume """ from pytom.basic.structures import Rotation, Shift from pytom_volume import vol if not isinstance(v,vol): raise TypeError('general_transform: v must be of type pytom_volume.vol! Got ' + str(v.__class__) + ' instead!') if rot is None: rot = Rotation(0.,0.,0.) if rot.__class__ == list and len(rot) == 3: rot = Rotation(rot[0], rot[1], rot[2]) if shift is None: shift = Shift(0.,0.,0.) if shift.__class__ == list and len(shift) == 3: shift = Shift(shift[0], shift[1], shift[2]) if scale is None: scale = [1.0, 1.0, 1.0] if scale.__class__ == list and len(scale) == 3: #check if int(v.sizeX()*scale[0]) < 1 or int(v.sizeY()*scale[1]) < 1 or int(v.sizeZ()*scale[2]) < 1: raise Exception("Scale not possible! Please check all the dimension after scaling is bigger than 1!") else: raise Exception("Scale parameter invalid! Should be a list of 3 values!") from pytom_volume import vol, general_transform from pytom.tools.maths import Matrix # invert matrix rotM = rot.toMatrix(True) shiftM = shift.toMatrix() scaleM = Matrix(4,4) scaleM[0,0] = scale[0] scaleM[1,1] = scale[1] scaleM[2,2] = scale[2] scaleM[3,3] = 1 # multiply them according to the order all_mtx = [None, None, None] try: if order[2] > order[0]: # rotation first rotCenter1 = Shift(-int(v.sizeX()/2), -int(v.sizeY()/2), -int(v.sizeZ()/2)).toMatrix() rotCenter2 = Shift(int(v.sizeX()/2), int(v.sizeY()/2), int(v.sizeZ()/2)).toMatrix() else: # scale first, so the center is different rotCenter1 = Shift(-int(v.sizeX()*scale)/2, -int(v.sizeY()*scale)/2, -int(v.sizeZ()*scale)/2).toMatrix() rotCenter2 = Shift(int(v.sizeX()*scale)/2, int(v.sizeY()*scale)/2, int(v.sizeZ()*scale)/2).toMatrix() all_mtx[order[0]] = rotCenter2 * (rotM * rotCenter1) # for the rotation center! all_mtx[order[1]] = shiftM all_mtx[order[2]] = scaleM except: raise Exception("The given order is wrong! Should be a list of 0,1,2!") mtx = all_mtx[2] * (all_mtx[1] * all_mtx[0]) res = vol(int(v.sizeX()*scale[0]), int(v.sizeY()*scale[1]), int(v.sizeZ()*scale[2])) general_transform(v, res, mtx._matrix) return res