def register_stack_slices(stack): """ An utility to register slices in an image stack. The registration is performed by iterating over adjacent layers (0->1, 1->2,...). The shift obtained for each layer, is with respect to the image at idx=0. :param stack {Image}: a 3D image stack :return: the image shifts with respect to index 0 (in pixels). """ assert isinstance(stack, Image) assert stack.ndim == 3 shifts = np.zeros((stack.shape[0], 2), dtype=np.float) for f_idx, m_idx in zip(range(0, stack.shape[0] - 1), range(1, stack.shape[0])): fixed = Image(stack[f_idx], stack.spacing[1:]) moving = Image(stack[m_idx], stack.spacing[1:]) offset = registration.phase_correlation_registration(fixed, moving, resample=False) shifts[m_idx] = shifts[f_idx] + np.asarray(offset) return shifts
def calculate_two_image_frc(image1, image2, args, z_correction=1): """ A simple utility to calculate a regular FRC with a two image input :param image: the image as an Image object :param args: the parameters for the FRC calculation. See *miplib.ui.frc_options* for details :return: returns the FRC result as a FourierCorrelationData object """ assert isinstance(image1, Image) assert isinstance(image2, Image) assert image1.shape == image2.shape frc_data = FourierCorrelationDataCollection() spacing = image1.spacing if not args.disable_hamming: image1 = Image(windowing.apply_hamming_window(image1), spacing) image2 = Image(windowing.apply_hamming_window(image2), spacing) # Run FRC iterator = iterators.FourierRingIterator(image1.shape, args.d_bin) frc_task = FRC(image1, image2, iterator) frc_data[0] = frc_task.execute() # Analyze results analyzer = fsc_analysis.FourierCorrelationAnalysis(frc_data, image1.spacing[0], args) return analyzer.execute(z_correction=z_correction)[0]
def register_stack_slices(stack, reference=0): """ An utility to register slices in an image stack. :param stack {Image}: a 3D image stack :param reference: the index (z) of the image that is to be used as a reference for the registration :return: a 3D Image with each slice aligned """ assert isinstance(stack, Image) assert stack.ndim == 3 aligned = np.zeros(stack.shape, dtype=np.float32) spacing = stack.spacing for i in range(stack.shape[0]): if i == reference: aligned[i] = stack[i] else: aligned[i] = phase_correlation_registration( Image(stack[reference], stack.spacing[1:]), Image(stack[i], stack.spacing[1:])) return Image(aligned, spacing) # endregions
def wiener_deconvolution(image, psf, snr=30, add_pad=0): assert isinstance(image, Image) assert isinstance(psf, Image) image_s = Image(image.copy(), image.spacing) orig_shape = image.shape if image.ndim != psf.ndim: raise ValueError("Image and psf dimensions do not match") if psf.spacing != image.spacing: psf = imops.zoom_to_spacing(psf, image.spacing) if add_pad != 0: new_shape = list(i + 2 * add_pad for i in image_s.shape) image_s = imops.zero_pad_to_shape(image_s, new_shape) if psf.shape != image_s.shape: psf = imops.zero_pad_to_shape(psf, image_s.shape) psf /= psf.max() psf_f = fftn(fftshift(psf)) wiener = arrayops.safe_divide( np.abs(psf_f)**2 / (np.abs(psf_f)**2 + snr), psf_f) image_s = fftn(image_s) image_s = Image(np.abs(ifftn(image_s * wiener).real), image.spacing) return imops.remove_zero_padding(image_s, orig_shape)
def get_result(self, cast_to_8bit=False): """ Show fusion result. This is a temporary solution for now calling Fiji through ITK. An internal viewer would be preferable. """ if cast_to_8bit: result = self.estimate.copy() result *= (255.0 / result.max()) result[result < 0] = 0 return Image(result, self.voxel_size) return Image(self.estimate, self.voxel_size)
def noisy(image, noise_type): """ Parameters ---------- image : Input image data. Will be converted to float. noise_type : str One of the following strings, selecting the type of noise to add: 'gauss' Gaussian-distributed additive noise. 'poisson' Poisson-distributed noise generated from the data. 's&p' Replaces random pixels with 0 or 1. 'speckle' Multiplicative noise using out = image + n*image,where n is uniform noise with specified mean & variance. """ assert isinstance(image, Image) assert image.ndim < 4 spacing = image.spacing if noise_type == "gauss": mean = 0 var = 0.1 sigma = var**0.5 gauss = np.random.normal(mean, sigma, image.shape) gauss = gauss.reshape(image.shape) return Image(image + gauss, spacing) elif noise_type == "s&p": s_vs_p = 0.5 amount = 0.004 out = np.copy(image) # Salt mode num_salt = np.ceil(amount * image.size * s_vs_p) coords = [ np.random.randint(0, i - 1, int(num_salt)) for i in image.shape ] out[coords] = 1 # Pepper mode num_pepper = np.ceil(amount * image.size * (1. - s_vs_p)) coords = [ np.random.randint(0, i - 1, int(num_pepper)) for i in image.shape ] out[coords] = 0 return Image(out, spacing) elif noise_type == "poisson": vals = 2**np.ceil(np.log2(len(np.unique(image)))) return Image(np.random.poisson(image * vals) / float(vals), spacing) elif noise_type == "speckle": gauss = np.random.standard_normal(image.shape).reshape(image.shape) return Image(image + image * gauss, spacing)
def wiener_deconvolution(image, psf, snr=30, add_pad=0): """ A GPU accelerated implementation of a linear Wiener filter. Some effort is made to allow processing even relatively large images, but some kind of block-based processing (as in the RL implementation) may be required in some cases.""" assert isinstance(image, Image) assert isinstance(psf, Image) image_s = Image(image.copy(), image.spacing) orig_shape = image.shape if image.ndim != psf.ndim: raise ValueError("Image and psf dimensions do not match") if psf.spacing != image.spacing: psf = imops.zoom_to_spacing(psf, image.spacing) if add_pad != 0: new_shape = list(i + 2 * add_pad for i in image_s.shape) image_s = imops.zero_pad_to_shape(image_s, new_shape) if psf.shape != image_s.shape: psf = imops.zero_pad_to_shape(psf, image_s.shape) psf /= psf.max() psf = fftshift(psf) psf_dev = cp.asarray(psf.astype(np.complex64)) with get_fft_plan(psf_dev): psf_dev = fftn(psf_dev, overwrite_x=True) below = cp.asnumpy(psf_dev) psf_abs = cp.abs(psf_dev)**2 psf_abs /= (psf_abs + snr) above = cp.asnumpy(psf_abs) psf_abs = None psf_dev = None image_dev = cp.asarray(image_s.astype(np.complex64)) with get_fft_plan(image_dev): image_dev = fftn(image_dev, overwrite_x=True) wiener_dev = cp.asarray(arrayops.safe_divide(above, below)) image_dev *= wiener_dev result = cp.asnumpy(cp.abs(ifftn(image_dev, overwrite_x=True)).real) result = Image(result, image.spacing) return imops.remove_zero_padding(result, orig_shape)
def calculate_two_image_sectioned_fsc(image1, image2, args, z_correction=1): assert isinstance(image1, Image) assert isinstance(image2, Image) image1 = Image(windowing.apply_hamming_window(image1), image1.spacing) image2 = Image(windowing.apply_hamming_window(image2), image2.spacing) iterator = iterators.AxialExcludeHollowConicalFourierShellIterator( image1.shape, args.d_bin, args.d_angle, args.d_extract_angle) fsc_task = DirectionalFSC(image1, image2, iterator) data = fsc_task.execute() analyzer = fsc_analysis.FourierCorrelationAnalysis(data, image1.spacing[0], args) return analyzer.execute(z_correction=z_correction)
def __getitem__(self, item): gate, detector = item self.data.set_active_image(detector, gate, self.scale, self.kind) spacing = self.data.get_voxel_size() return Image(self.data[:], spacing)
def butterworth_fft_filter(image, threshold, n=3): """Create low-pass 2D Butterworth filter. :Parameters: size : tuple size of the filter cutoff : float relative cutoff frequency of the filter (0 - 1.0) n : int, optional order of the filter, the higher n is the sharper the transition is. :Returns: numpy.ndarray filter kernel in 2D centered """ if not 0 < threshold <= 1.0: raise ValueError('Cutoff frequency must be between 0 and 1.0') if not isinstance(n, int): raise ValueError('n must be an integer >= 1') assert isinstance(image, Image) spacing = image.spacing # Create Fourier grid r = indexers.SimplePolarIndexer(image.shape).r threshold *= image.shape[0] butter = 1.0 / (1.0 + (r / threshold) ** (2 * n)) # The filter fft_image = np.fft.fftshift(np.fft.fftn(image)) fft_image *= butter return Image(np.abs(np.fft.ifftn(fft_image).real), spacing)
def ideal_fft_filter(image, threshold, kind='low'): """ An ideal high/low pass frequency domain noise filter. :param image: an Image object :param threshold: threshold value [0,1], where 1 corresponds to the maximum frequency. :param kind: filter type 'low' for low-pass, 'high' for high pass :return: returns the filtered Image. """ assert isinstance(image, Image) spacing = image.spacing fft_image = np.fft.fftshift(np.fft.fftn(image)) if kind == 'low': indexer = indexers.PolarLowPassIndexer(image.shape) elif kind == 'high': indexer = indexers.PolarHighPassIndexer(image.shape) else: raise ValueError("Unknown filter kind: {}".format(kind)) r_max = floor(min(image.shape) / 2) fft_image *= indexer[threshold * r_max] return Image(np.abs(np.fft.ifftn(fft_image).real), spacing)
def translate_image(image, shift): """ Apply a circular shift to an image :param image: An Image object :param shift: The shift as a single numeric value :return: returns the translated image. """ fft_image = np.fft.fftshift(np.fft.fft2(image)) shape = fft_image.shape axes = (np.arange(-np.floor(i / 2.0), np.ceil(i / 2.0)) for i in shape) axes = (i / (2 * i.max()) for i in axes) y, x = np.meshgrid(*axes) xx = np.zeros(fft_image.shape, dtype=np.complex64) xx.real[:] = np.cos(2 * np.pi * shift * x) xx.imag[:] = np.sin(-2 * np.pi * shift * x) yy = np.zeros(fft_image.shape, dtype=np.complex64) yy.real[:] = np.cos(2 * np.pi * shift * y) yy.imag[:] = np.sin(-2 * np.pi * shift * y) multiplier = xx * yy result = np.abs(np.fft.ifftn(fft_image * multiplier).real) return Image(result, image.spacing)
def flip_image(image): assert isinstance(image, Image) indexer = (np.s_[::-1], ) * image.ndim return Image(image[indexer], image.spacing)
def read_airyscan_data(image_path, time_points=1, detectors=32): """ Read an Airyscan image. Arguments: image_path {string} -- Path to the file Keyword Arguments: time_points {int} -- Number of time points (if a time series) (default: {1}) detectors {int} -- Number of detectors (default: {32}) Returns: ArrayDetectorData -- Returns the Airyscan image in the internal format for ISM processing. """ # Open file data = pims.bioformats.BioformatsReader(image_path) # Get metadata spacing = [data.metadata.PixelsPhysicalSizeY(0), data.metadata.PixelsPhysicalSizeX(0)] # Initialize data container container = ArrayDetectorData(detectors, time_points) # Split time points data.bundle_axes = ['t', 'y', 'x'] data = np.stack(np.split(data[0], time_points)) # Save to data container for i in range(time_points): for j in range(detectors): container[i, j] = Image(data[i, j, :, :], spacing) return container
def __bioformats(filename, series=0, channel=0, return_itk=False): """ Read an image using the Bioformats importer. Good for most microscopy formats. :param filename: :param series: :param return_itk: :return: """ assert pims.bioformats.available(), "Please install jpype in order to use " \ "the bioformats reader." image = pims.bioformats.BioformatsReader(filename, series=series) # Get Pixel/Voxel size information if 'z' not in image.axes: spacing = (image.metadata.PixelsPhysicalSizeY(0), image.metadata.PixelsPhysicalSizeX(0)) else: spacing = (image.metadata.PixelsPhysicalSizeZ(0), image.metadata.PixelsPhysicalSizeY(0), image.metadata.PixelsPhysicalSizeX(0)) # Get color channel if 'c' in image.sizes: image.iter_axes = 'c' assert len(image) > channel image = image[channel] else: image = image[0] if return_itk: return itkutils.convert_from_numpy(image, spacing) else: return Image(image, spacing)
def gaussian_fft_filter(image, threshold): """ Create low-pass 2D Gaussian filter. :Parameters: size : tuple size of the filter cutoff : float relative cutoff frequency of the filter (0 - 1.0) n : int, optional order of the filter, the higher n is the sharper the transition is. :Returns: numpy.ndarray: filter kernel in 2D centered """ if not 0 < threshold <= 1.0: raise ValueError('Cutoff frequency must be between 0 and 1.0') assert isinstance(image, Image) spacing = image.spacing # Create Fourier grid r = indexers.SimplePolarIndexer(image.shape).r r /= image.shape[0] gauss = np.exp(-(r**2/(2*(threshold**2)))) fft_image = np.fft.fftshift(np.fft.fftn(image)) fft_image *= gauss return Image(np.abs(np.fft.ifftn(fft_image).real), spacing)
def shift(data, transforms): """ Resamples all the images in an ArrayDetectorData structure with the supplied transforms, and saves the result in a new ArrayDetectorData structure :param data: ArrayDetectorData object with images :param transforms: A list of transforms (Simple ITK), one for each image :return: ArrayDetectorDAta object with shifted images """ assert isinstance(transforms, list) and len(transforms) == data.ndetectors shifted = ArrayDetectorData(data.ndetectors, data.ngates) reference = Image(np.zeros(data[0, 0].shape, dtype=np.float64), data[0, 0].spacing) for gate in range(data.ngates): for i in range(data.ndetectors): image = itk.resample_image( itk.convert_to_itk_image(data[gate, i]), transforms[i], reference=itk.convert_to_itk_image(reference)) shifted[gate, i] = itk.convert_from_itk_image(image) return shifted
def xy(self): """Return a z slice of the PSF with rotational symmetries applied.""" data = mirror_symmetry(_psf.zr2zxy(self.data)) spacing = (self.spacing[1], self.spacing[1]) center = self.shape[0] - 1 return Image(data[center], spacing)
def phase_correlation_registration(fixed_image, moving_image, subpixel=100, verbose=False, resample=True): """ A simple Phase Correlation based image registration method. :param verbose: enable print functions :param subpixel: resampling factor; registration will be perfromed to 1/subpixel accuracy :param fixed_image: the reference image as MIPLIB Image object :param moving_image: the moving image as MIPLIB Image object :return: returns the SimpleITK transform """ assert isinstance(fixed_image, Image) assert isinstance(moving_image, Image) shift, error, diffphase = register_translation(fixed_image, moving_image, subpixel) scaled_shifts = list( -offset * spacing for offset, spacing in zip(shift, fixed_image.spacing)) if verbose: print(("Detected offset (y, x): {}".format(scaled_shifts))) if resample: resampled = np.abs( np.fft.ifftn(fourier_shift(np.fft.fftn(moving_image), shift)).real) return Image(resampled, fixed_image.spacing) else: return scaled_shifts
def get_result(self): """ Show fusion result. This is a temporary solution for now calling Fiji through ITK. An internal viewer would be preferable. """ return Image(self.estimate, self.image_spacing)
def calculate_one_image_sectioned_fsc(image, args, z_correction=1): """ A function to calculate one-image sectioned FSC. I assume here that prior to calling the function, the image is going to be in a correct shape, resampled to isotropic spacing and zero padded. If the image dimensions are wrong (not a cube) the function will return an error. :param image: a 3D image, with isotropic spacing and cubic shape :type image: Image :param options: options for the FSC calculation :type options: argparse options :param z_correction: correction, for anisotropic sampling. It is the ratio of axial vs. lateral spacing, defaults to 1 :type z_correction: float, optional :return: the resolution measurement results organized by rotation angle :rtype: FourierCorrelationDataCollection object """ assert isinstance(image, Image) assert all(s == image.shape[0] for s in image.shape) image1, image2 = imops.checkerboard_split(image) image1 = Image(windowing.apply_hamming_window(image1), image1.spacing) image2 = Image(windowing.apply_hamming_window(image2), image2.spacing) iterator = iterators.AxialExcludeHollowConicalFourierShellIterator( image1.shape, args.d_bin, args.d_angle, args.d_extract_angle) fsc_task = DirectionalFSC(image1, image2, iterator) data = fsc_task.execute() analyzer = fsc_analysis.FourierCorrelationAnalysis(data, image1.spacing[0], args) result = analyzer.execute(z_correction=z_correction) def func(x, a, b, c, d): return a * np.exp(c * (x - b)) + d params = [0.95988146, 0.97979108, 13.90441896, 0.55146136] for angle, dataset in result: point = dataset.resolution["resolution-point"][1] cut_off_correction = func(point, *params) dataset.resolution["spacing"] /= cut_off_correction dataset.resolution["resolution"] /= cut_off_correction return result
def rescale_to_8_bit(image): """ Converts an Image into 8-bit (typically for saving) :param image: an Image object :return: a 8-bit version of the Image """ assert isinstance(image, Image) return Image((image * (255.0 / image.max())).astype(np.uint8), image.spacing)
def shift_and_sum(data, transforms, photosensor=0, detectors=None, supersampling=1.0): """ Adaptive ISM pixel reassignment. Please use one of the functions above to figure out the shifts first, if you haven't already. :param supersampling: Insert a number != 1, if you want to rescale the result image to a different size. This might make sense, if you the original sampling has been sampled sparsely :param data: ArrayDetectorData object with all the individual images :param transforms: ITK spatial transformation that are to be used for the resampling :param photosensor: The photosensor index, if more than one :param detectors: a list of detectors to be included in the reconstruction. If None given (default), all the images will be used :return: reconstruction result Image """ assert isinstance(data, ArrayDetectorData) assert isinstance(transforms, list) and len(transforms) == data.ndetectors if supersampling != 1.0: new_shape = list( int(i * supersampling) for i in data[photosensor, 0].shape) new_spacing = list(i / supersampling for i in data[photosensor, 0].spacing) output = Image(np.zeros(new_shape, dtype=np.float64), new_spacing) else: output = Image(np.zeros(data[photosensor, 0].shape, dtype=np.float64), data[photosensor, 0].spacing) if detectors is None: detectors = list(range(data.ndetectors)) for i in detectors: image = itk.resample_image(itk.convert_to_itk_image(data[photosensor, i]), transforms[i], reference=itk.convert_to_itk_image(output)) output += itk.convert_from_itk_image(image) return output
def run_mean_smoothing(self, return_result=False): """ Mean smoothing is used to create a mask for the entropy calculation """ assert len(self.kernel_size) == len(self.dimensions) self.data_temp = ndimage.uniform_filter(self.data[:], size=self.kernel_size) if return_result: return Image(self.data_temp, self.spacing)
def volume(self): """Return a 3D volume of the PSF with all symmetries applied. The shape of the returned array is (2*self.shape[0]-1, 2*self.shape[1]-1, 2*self.shape[1]-1) """ data = mirror_symmetry(_psf.zr2zxy(self.data)) spacing = (self.spacing[0], self.spacing[1], self.spacing[1]) return Image(data, spacing)
def average_of_all(data_structure, channel=0, scale=100, image_type="original"): assert isinstance(data_structure, ImageData) n_views = data_structure.get_number_of_images(image_type) data_structure.set_active_image(0, channel, scale, image_type) pixel_size = data_structure.get_voxel_size() result = sum_of_all(data_structure, channel, scale, image_type) return Image(result / n_views, pixel_size)
def remove_zero_padding(image, shape): """ :param image: The zero padded image :param shape: The original image size (before padding) :return: """ assert isinstance(image, Image) assert len(shape) == image.ndim return Image(ndarray.contract_to_shape(image, shape), image.spacing)
def zoom_to_spacing(image, spacing, order=3, verbose=False): assert isinstance(image, Image) assert image.ndim == len(spacing) zoom = tuple(i / j for i, j in zip(image.spacing, spacing)) if verbose: print("The zoom is ", zoom) array = interpolation.zoom(image, zoom, order=order) return Image(array, spacing)
def read_carma_mat(filename): """ A simple implementation for the carma file import in Python :param filename: Path to the Carma .mat file :return: Returns a 2D nested list of miplib Image objects. The first dimension of the list corresponds to the Photosensors count and second, to the Detectors count """ assert filename.endswith(".mat") #measurement = "meas_" + filename.split('/')[-1].split('.')[0] data = loadmat(filename) # Find measurement name (in case someone renamed the file) for key in list(data.keys()): if 'meas_' in key: data = data[key] break # Get necessary metadata spacing = list(data['PixelSize'][0][0][0][::-1]) shape = list(data['Size'][0][0][0][::-1]) detectors = int(data['DetectorsCount'][0][0][0]) photosensors = len(data["PhotosensorsTime"][0][0][0]) # Initialize data container container = ArrayDetectorData(detectors, photosensors) # Read images for i in range(photosensors): for j in range(detectors): name = 'pixel_d{}_p{}'.format(j, i) if shape[0] == 1: container[i, j] = Image( np.transpose(data[name][0][0])[0], spacing[1:]) else: container[i, j] = Image(np.transpose(data[name][0][0]), spacing) return container
def get_8bit_result(self, denoise=False): """ Returns the current estimate (the fusion result) as an 8-bit uint, rescaled to the full 0-255 range. """ if denoise: image = medfilt(self.estimate) else: image = self.estimate image *= (255.0 / image.max()) image[image < 0] = 0 return Image(image.astype(numpy.uint8), self.image_spacing)