def circle_filter(sizeX, sizeY, radiusCutoff): """ circleFilter: NEEDS Documentation @param sizeX: NEEDS Documentation @param sizeY: NEEDS Documentation @param radiusCutoff: NEEDS Documentation """ X, Y = xp.meshgrid( xp.arange(-sizeX // 2 + sizeX % 2, sizeX // 2 + sizeX % 2), xp.arange(-sizeY // 2 + sizeY % 2, sizeY // 2 + sizeY % 2)) R = xp.sqrt(X**2 + Y**2) filter = xp.zeros((sizeX, sizeY), dtype=xp.float32) filter[R <= radiusCutoff] = 1 return filter
def taper_edges(image, width): """ taper edges of image (or volume) with cos function @param image: input image (or volume) @type image: ndarray @param width: width of edge @type width: int @return: image with smoothened edges, taper_mask @rtype: array-like @author: GvdS """ dims = list(image.shape) + [0] val = xp.cos(xp.arange(1, width + 1) * xp.pi / (2. * (width))) taperX = xp.ones((dims[0]), dtype=xp.float32) taperY = xp.ones((dims[1])) taperX[:width] = val[::-1] taperX[-width:] = val taperY[:width] = val[::-1] taperY[-width:] = val if dims[2] > 1: taperZ = xp.ones((dims[2])) taperZ[:width] = val[::-1] taperZ[-width:] = val Z, X, Y = xp.meshgrid(taperX, taperY, taperZ) taper_mask = X * (X < Y) * (X < Z) + Y * (Y <= X) * (Y < Z) + Z * ( Z <= Y) * (Z <= X) else: X, Y = xp.meshgrid(taperY, taperX) taper_mask = X * (X < Y) + Y * (Y <= X) return image * taper_mask, taper_mask
def ellipse_filter(sizeX, sizeY, radiusCutoffX, radiusCutoffY): """ circleFilter: NEEDS Documentation @param sizeX: NEEDS Documentation @param sizeY: NEEDS Documentation @param radiusCutoff: NEEDS Documentation """ X, Y = xp.meshgrid( xp.arange(-sizeY // 2 + sizeY % 2, sizeY // 2 + sizeY % 2), xp.arange(-sizeX // 2 + sizeX % 2, sizeX // 2 + sizeX % 2)) R = xp.sqrt((X / radiusCutoffX)**2 + (Y / radiusCutoffY)**2) filter = xp.zeros((sizeX, sizeY), dtype=xp.float32) #print(filter.shape, R.shape) filter[R <= 1] = 1 return filter
def exact_filter(tilt_angles, tiltAngle, sX, sY, sliceWidth=1, arr=[]): """ exactFilter: Generates the exact weighting function required for weighted backprojection - y-axis is tilt axis Reference : Optik, Exact filters for general geometry three dimensional reconstuction, vol.73,146,1986. @param tilt_angles: list of all the tilt angles in one tilt series @param titlAngle: tilt angle for which the exact weighting function is calculated @param sizeX: size of weighted image in X @param sizeY: size of weighted image in Y @return: filter volume """ import numpy as xp # Calculate the relative angles in radians. diffAngles = (xp.array(tilt_angles) - tiltAngle) * xp.pi / 180. # Closest angle to tiltAngle (but not tiltAngle) sets the maximal frequency of overlap (Crowther's frequency). # Weights only need to be calculated up to this frequency. sampling = xp.min(xp.abs(diffAngles)[xp.abs(diffAngles) > 0.001]) crowtherFreq = min(sX // 2, xp.int32(xp.ceil(sliceWidth / xp.sin(sampling)))) arrCrowther = xp.matrix( xp.abs(xp.arange(-crowtherFreq, min(sX // 2, crowtherFreq + 1)))) # Calculate weights wfuncCrowther = 1. / (xp.clip( 1 - xp.array(xp.matrix(xp.abs(xp.sin(diffAngles))).T * arrCrowther)**2, 0, 2)).sum(axis=0) # Create full with weightFunc wfunc = xp.ones((sX, sY), dtype=xp.float32) # row_stack is not implemented in cupy weightingFunc = xp.column_stack(([ (wfuncCrowther), ] * (sY))).T wfunc[:, sX // 2 - crowtherFreq:sX // 2 + min(sX // 2, crowtherFreq + 1)] = weightingFunc return wfunc
def ramp_filter(sizeX, sizeY, crowtherFreq=None): """ rampFilter: Generates the weighting function required for weighted backprojection - y-axis is tilt axis @param sizeX: size of weighted image in X @param sizeY: size of weighted image in Y @param crowtherFreq: size of weighted image in Y @return: filter volume """ if crowtherFreq is None: crowtherFreq = sizeX // 2 rampLine = xp.abs(xp.arange(-sizeX // 2, sizeX // 2)) / crowtherFreq rampLine[rampLine > 1] = 1 rampfilter = xp.column_stack(([ (rampLine), ] * (sizeY))).T return rampfilter
def invert_WedgeSum(invol, r_max=None, lowlimit=0., lowval=0.): """ invert wedge sum - avoid division by zero and boost of high frequencies @param invol: input volume @type invol: L{pytom_volume.vol} or L{pytom_volume.vol_comp} @param r_max: radius @type r_max: L{int} @param lowlimit: lower limit - all values below this value that lie in the specified radius will be replaced \ by lowval @type lowlimit: L{float} @param lowval: replacement value @type lowval: L{float} @author: FF """ from math import sqrt if not r_max: r_max = invol.shape[1] // 2 - 1 dx, dy, dz = invol.shape if dz != dx: X, Y, Z = xp.meshgrid(xp.arange(-dx // 2, dx // 2 + dx % 2), xp.arange(-dy // 2, dy // 2 + dy % 2), xp.arange(0, dz)) invol = xp.fft.fftshift(invol, axes=(0, 1)) else: X, Y, Z = xp.meshgrid(xp.arange(-dx // 2, dx // 2 + dx % 2), xp.arange(-dy // 2, dy // 2 + dy % 2), xp.arange(-dz // 2, dz // 2 + dz % 2)) R = xp.sqrt(X**2 + Y**2 + Z**2).astype(xp.int32) invol_out = invol.copy().astype(xp.float32) invol_out[invol < lowlimit] = lowval invol_out = 1. / invol_out invol_out[R >= r_max] = 0 if dx != dz: invol_out = xp.fft.fftshift(invol_out, axes=(0, 1)) return invol_out
def profile2FourierVol(profile, dim=None, reduced=False): """ create Volume from 1d radial profile, e.g., to modulate signal with \ specific function such as CTF or FSC. Simple linear interpolation is used\ for sampling. @param profile: profile @type profile: 1-d L{pytom_volume.vol} or 1-d python array @param dim: dimension of (cubic) output @type dim: L{int} @param reduced: If true reduced Fourier representation (N/2+1, N, N) is generated. @type reduced: L{bool} @return: 3-dim complex volume with spherically symmetrical profile @rtype: L{pytom_volume.vol} @author: FF """ if dim is None: try: dim = [ 2 * profile.shape[0], ] * 3 except: dim = [ 2 * len(profile), ] * 3 is3D = (len(dim) == 3) nx, ny = dim[:2] if reduced: if is3D: nz = int(dim[2] // 2) + 1 else: ny = int(ny // 2) + 1 else: if is3D: nz = dim[2] try: r_max = profile.shape[0] - 1 except: r_max = len(profile) - 1 if len(dim) == 3: if reduced: X, Y, Z = xp.meshgrid(xp.arange(-nx // 2, nx // 2 + nx % 2), xp.arange(-ny // 2, ny // 2 + ny % 2), xp.arange(0, nz)) else: X, Y, Z = xp.meshgrid(xp.arange(-nx // 2, nx // 2 + nx % 2), xp.arange(-ny // 2, ny // 2 + ny % 2), xp.arange(-nz // 2, nz // 2 + nz % 2)) R = xp.sqrt(X**2 + Y**2 + Z**2) else: if reduced: X, Y = xp.meshgrid(xp.arange(-nx // 2, ny // 2 + ny % 2), xp.arange(0, ny)) else: X, Y = xp.meshgrid(xp.arange(-nx // 2, nx // 2 + nx % 2), xp.arange(-ny // 2, ny // 2 + ny % 2)) R = xp.sqrt(X**2 + Y**2) IR = xp.floor(R).astype(xp.int64) valIR_l1 = IR.copy() valIR_l2 = valIR_l1 + 1 val_l1, val_l2 = xp.zeros_like(X, dtype=xp.float64), xp.zeros_like( X, dtype=xp.float64) l1 = R - IR.astype(xp.float32) l2 = 1 - l1 try: profile = xp.array(profile) except: import numpy profile = xp.array(numpy.array(profile)) for n in xp.arange(r_max): val_l1[valIR_l1 == n] = profile[n] val_l2[valIR_l2 == n + 1] = profile[n + 1] val_l1[IR == r_max] = profile[n + 1] val_l2[IR == r_max] = profile[n + 1] val_l1[R > r_max] = 0 val_l2[R > r_max] = 0 fkernel = l2 * val_l1 + l1 * val_l2 if reduced: fkernel = xp.fft.fftshift(fkernel, axes=(0, 1)) else: fkernel = xp.fft.fftshift(fkernel) return fkernel
def create_asymmetric_wedge(angle1, angle2, cutoffRadius, sizeX, sizeY, sizeZ, smooth, rotation=None): '''This function returns an asymmetric wedge object. @param angle1: angle of wedge1 in degrees @type angle1: int @param angle2: angle of wedge2 in degrees @type angle2: int @param cutOffRadius: radius from center beyond which the wedge is set to zero. @type cutOffRadius: int @param sizeX: the size of the box in x-direction. @type sizeX: int @param sizeY: the size of the box in y-direction. @type sizeY: int @param sizeZ: the size of the box in z-direction. @type sizeZ: int @param smooth: smoothing parameter that defines the amount of smoothing at the edge of the wedge. @type smooth: float @return: 3D array determining the wedge object. @rtype: ndarray of xp.float64''' range_angle1Smooth = smooth / xp.sin(angle1 * xp.pi / 180.) range_angle2Smooth = smooth / xp.sin(angle2 * xp.pi / 180.) wedge = xp.zeros((sizeX, sizeY, sizeZ // 2 + 1)) if rotation is None: z, y, x = xp.meshgrid( xp.arange(-sizeX // 2 + sizeX % 2, sizeX // 2 + sizeX % 2), xp.arange(-sizeY // 2 + sizeY % 2, sizeY // 2 + sizeY % 2), xp.arange(0, sizeZ // 2 + 1)) else: cx, cy, cz = [s // 2 for s in (sizeX, sizeY, sizeZ)] grid = xp.mgrid[-cx:sizeX - cx, -cy:sizeY - cy, :sizeZ // 2 + 1] phi, the, psi = rotation phi = -float(phi) * xp.pi / 180.0 the = -float(the) * xp.pi / 180.0 psi = -float(psi) * xp.pi / 180.0 sin_alpha = xp.sin(phi) cos_alpha = xp.cos(phi) sin_beta = xp.sin(the) cos_beta = xp.cos(the) sin_gamma = xp.sin(psi) cos_gamma = xp.cos(psi) # Calculate inverse rotation matrix Inv_R = xp.zeros((3, 3), dtype='float32') Inv_R[0, 0] = cos_alpha * cos_gamma - cos_beta * sin_alpha \ * sin_gamma Inv_R[0, 1] = -cos_alpha * sin_gamma - cos_beta * sin_alpha \ * cos_gamma Inv_R[0, 2] = sin_beta * sin_alpha Inv_R[1, 0] = sin_alpha * cos_gamma + cos_beta * cos_alpha \ * sin_gamma Inv_R[1, 1] = -sin_alpha * sin_gamma + cos_beta * cos_alpha \ * cos_gamma Inv_R[1, 2] = -sin_beta * cos_alpha Inv_R[2, 0] = sin_beta * sin_gamma Inv_R[2, 1] = sin_beta * cos_gamma Inv_R[2, 2] = cos_beta temp = grid.reshape((3, grid.size // 3)) temp = xp.dot(Inv_R, temp) grid = xp.reshape(temp, grid.shape) y = grid[0, :, :, :] z = grid[1, :, :, :] x = grid[2, :, :, :] r = xp.sqrt(x**2 + y**2 + z**2) wedge[xp.tan(angle1 * xp.pi / 180) < y / x] = 1 wedge[xp.tan(-angle2 * xp.pi / 180) > y / x] = 1 wedge[sizeX // 2, :, 0] = 1 if smooth: area = xp.abs(x - (y / xp.tan(angle1 * xp.pi / 180))) <= range_angle1Smooth strip = 1 - (xp.abs(x - (y / xp.tan(angle1 * xp.pi / 180.))) * xp.sin(angle1 * xp.pi / 180.) / smooth) wedge += (strip * area * (1 - wedge) * (y > 0)) area2 = xp.abs(x + (y / xp.tan(angle2 * xp.pi / 180))) <= range_angle2Smooth strip2 = 1 - (xp.abs(x + (y / xp.tan(angle2 * xp.pi / 180.))) * xp.sin(angle2 * xp.pi / 180.) / smooth) wedge += (strip2 * area2 * (1 - wedge) * (y <= 0)) wedge[r > cutoffRadius] = 0 return xp.fft.fftshift(wedge, axes=(0, 1))
def randomizePhaseBeyondFreq(volume, frequency): '''This function randomizes the phases beyond a given frequency, while preserving the Friedel symmetry. @param volume: target volume @type volume: 3d ndarray @param frequency: frequency in pixel beyond which phases are randomized. @type frequency: int @return: 3d volume. @rtype: 3d ndarray @author: GvdS''' from numpy.fft import ifftn, fftshift, fftn, rfftn, irfftn from numpy import angle, abs, exp, meshgrid, arange, sqrt, pi, rot90 threeD = len(volume.shape) == 3 twoD = len(volume.shape) == 2 if not twoD and not threeD: raise Exception( 'Invalid volume dimensions: either supply 3D or 2D ndarray') if twoD: dx, dy = volume.shape else: dx, dy, dz = volume.shape ft = xp.fft.rfftn(volume) phase = xp.angle(ft) amplitude = xp.abs(ft) rnda = generate_random_phases_3d( amplitude.shape, reduced_complex=True) # (ranf((dx,dy,dz//2+1)) * pi * 2) - pi if twoD: X, Y = xp.meshgrid(xp.arange(-dx // 2, dx // 2 + dx % 2), xp.arange(-dy // 2, dy // 2 + dy % 2)) RF = xp.sqrt(X**2 + Y**2).astype(int) R = xp.fft.fftshift(RF)[:, :dy // 2 + 1] else: X, Y, Z = xp.meshgrid(xp.arange(-dx // 2, dx // 2), xp.arange(-dy // 2, dy // 2), xp.arange(-dz // 2, dz // 2)) RF = xp.sqrt(X**2 + Y**2 + Z**2) # .astype(int) R = xp.fft.fftshift(RF)[:, :, :dz // 2 + 1] # centralslice = fftshift(rnda[:,:,0]) # cc = dx//2 # ccc= (dx-1)//2 # centralslice[cc, cc-ccc:cc] = centralslice[cc,-ccc:][::-1]*-1 # centralslice[cc-ccc:cc,cc-ccc:] = rot90(centralslice[-ccc:,cc-ccc:],2)*-1 # rnda[:,:,0] = fftshift(centralslice) rnda[R <= frequency] = 0 phase[R > frequency] = 0 phase += rnda image = xp.fft.irfftn((amplitude * xp.exp(1j * phase)), s=volume.shape) if xp.abs(image.imag).sum() > 1E-8: raise Exception( 'Imaginary part is non-zero. Failed to centro-summetrize the phases.' ) return image.real