def downsample_stack_file(cls, stack_file, side, output_stack_file=None, mask_file=None): if output_stack_file is None: output_stack_file = set_output_name(stack_file, 'downsampled') if os.path.exists(output_stack_file): raise FileExistsError( f"output file '{yellow(output_stack_file)}' already exists!") if mask_file: if not os.path.exists(mask_file): logger.error(f"mask file {yellow(mask_file)} doesn't exist!") mask = load_stack_from_file(mask_file) else: mask = None stack = load_stack_from_file(stack_file) downsampled_stack = cls.downsample(stack, side, compute_fx=False, stack=True, mask=mask) logger.info( f"downsampled stack from size {stack.shape} to {downsampled_stack.shape}." f" saving to {yellow(output_stack_file)}..") with mrcfile.new(output_stack_file) as mrc_fh: mrc_fh.set_data(downsampled_stack) logger.debug(f"saved to {output_stack_file}")
def crop_stack_file(cls, stack_file, size, output_stack_file=None, fill_value=None): if output_stack_file is None: output_stack_file = set_output_name(stack_file, 'cropped') if os.path.exists(output_stack_file): raise FileExistsError( f"output file '{yellow(output_stack_file)}' already exists!") stack = load_stack_from_file(stack_file) fill_value = fill_value or PreProcessorConfig.crop_stack_fill_value cropped_stack = cls.crop_stack(stack, size, fill_value=fill_value) action = 'cropped' if size < stack.shape[1] else 'padded' logger.info( f"{action} stack from size {stack.shape} to size {cropped_stack.shape}." f" saving to {yellow(output_stack_file)}..") with mrcfile.new(output_stack_file) as mrc: mrc.set_data(cropped_stack) logger.debug(f"saved to {output_stack_file}")
def view_stack(stack, numslices=16, startslice=1, nrows=4, ncols=4): if numslices > nrows * ncols: raise ValueError("nrows*ncols should be at least numslices" " to accomodate all images.") logger.debug("Shape of loaded map: " + str(stack.shape)) plt.figure(figsize=(4, 4)) j = 0 dx = 0.95 / ncols dy = 0.95 / nrows for col in range(ncols): for row in range(nrows): if j < numslices: x0 = col * 1.0 / ncols y0 = 1 - row * 1.0 / nrows - dy # Flip y direction to fill image grid from top ax = plt.axes([x0, y0, dx, dy]) plt.imshow(stack[j, ...]) plt.gray() plt.axis("off") ax.text(0.1, 0.9, str(j + startslice), fontsize=10, horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, color='red', bbox=dict(facecolor='yellow', alpha=0.15)) j += 1 plt.show()
def fortran_to_c(stack): """ Convert Fortran-contiguous array to C-contiguous array. """ if stack.flags.f_contiguous: logger.debug(f"loading F-contiguous stack") return stack.T if stack.flags.f_contiguous else stack
def global_phaseflip_stack(ctx_obj): """ Apply global phase-flip to an MRC stack """ logger.debug("calculating global phaseflip..") ctx_obj.stack = PreProcessor.global_phaseflip_stack(ctx_obj.stack)
def crop(mat, n, stack=False, fill_value=None): """ Reduce the size of a vector, square or cube 'mat' by cropping (or increase the size by padding with fill_value, by default zero) to a final size of n, (n x n), or (n x n x n) respectively. This is the analogue of downsample, but it doesn't change magnification. If mat is 2-dimensional and n is a vector, m is cropped to n=[mat_x mat_y]. The function handles odd and even-sized arrays correctly. The center of an odd array is taken to be at (n+1)/2, and an even array is n/2+1. If flag is_stack is set to True, then a 3D array 'mat' is treated as a stack of 2D images, and each image is cropped to (n x n). For 2D images, the input image doesn't have to be square. * The original MATLAB function supported cropping to non-square matrices. As real-world uses will always crop to square (n, n), we don't support it with Python. Args: mat (numpy.array): Vector, 2D array, stack of 2D arrays or a 3D array n (int): Size of desired cropped vector, side of 2D array or side of 3D array stack (bool): Set to True in order to handle a 3D mat as a stack of 2D fill_value (:obj:`int`, optional): Padding value. Defaults to 0. Returns: numpy.array: Cropped or padded mat to size of n, (n x n) or (n x n x n) TODO: change name to 'resize'? crop/pad is confusing b/c either you save different TODO: output names or you stick to a misleading name like cropped.mrc for padded stack """ num_dimensions = len(mat.shape) if num_dimensions not in [1, 2, 3]: raise DimensionsIncompatible( "cropping/padding failed! number of dimensions is too big!" f" ({num_dimensions} while max is 3).") if num_dimensions == 2 and 1 in mat.shape: num_dimensions = 1 if fill_value is None: fill_value = PreProcessorConfig.crop_stack_fill_value if num_dimensions == 1: # mat is a vector mat = np.reshape(mat, [mat.size, 1]) # force a column vector ns = math.floor(mat.size / 2) - math.floor( n / 2) # shift term for scaling down if ns >= 0: # cropping return mat[ns:ns + n].astype('float32') else: # padding result_mat = fill_value * np.ones([n, 1], dtype=complex) result_mat[-ns:mat.size - ns] = mat return result_mat.astype('float32') elif num_dimensions == 2: # mat is 2D image mat_x, mat_y = mat.shape # start_x = math.floor(mat_x / 2) - math.floor(n / 2) # shift term for scaling down start_x = mat_x / 2 - n / 2 # shift term for scaling down # start_y = math.floor(mat_y / 2) - math.floor(n / 2) start_y = mat_y / 2 - n / 2 if start_x >= 0 and start_y >= 0: # cropping start_x, start_y = math.floor(start_x), math.floor(start_y) logger.debug(f'crop:cropping from {mat.shape} to {n}..') return mat[start_x:start_x + int(n), start_y:start_y + int(n)].astype('float32') elif start_x < 0 and start_y < 0: # padding logger.debug('crop:padding..') start_x, start_y = math.floor(start_x), math.floor(start_y) result_mat = fill_value * np.ones([n, n], dtype=complex) result_mat[-start_x:mat_x - start_x, -start_y:mat_y - start_y] = mat return result_mat.astype('float32') else: raise DimensionsIncompatible( "Can't crop and pad simultaneously!") else: # mat is 3D or a stack of 2D images if stack: # break down the stack and treat each image as an individual image # then return the cropped stack result_mat = np.zeros([mat.shape[0], n, n], dtype='float32') for img in range(mat.shape[0]): # TODO iterate instead of using recursion. this is too memoery-expensive result_mat[img, :, :] = PreProcessor.crop( mat[img, :, :], n) return result_mat.astype('float32') else: # this is a 3D structure # crop/pad mat into a new smaller/bigger cell - 'destination cell' from_shape = np.array(mat.shape) to_shape = np.array((n, n, n)) ns = np.floor(from_shape / 2) - np.floor(to_shape / 2) ns, to_shape = ns.astype(int), to_shape.astype( int) # can't slice later with float if np.all(ns >= 0): # crop return mat[ns[0]:ns[0] + to_shape[0], ns[1]:ns[1] + to_shape[1], ns[2]:ns[2] + to_shape[2]] elif np.all(ns <= 0): # pad result_mat = fill_value * np.ones([n, n, n], dtype=complex) result_mat[-ns[0]:from_shape[0] - ns[0], -ns[1]:from_shape[2] - ns[1], -ns[2]:from_shape[2] - ns[2]] = mat return result_mat.astype('float32') else: raise DimensionsIncompatible( "Can't crop and pad simultaneously!")