def swap(indices): arrs_3d_swapped = None arrs_1d_swapped = None if arrs_3d is not None: arrs_3d_swapped = [ None if arr is None else np.swapaxes(arr, *indices) for arr in arrs_3d] if arrs_1d is not None: arrs_1d_swapped = [ None if arr is None else libmag.swap_elements(np.copy(arr), *indices) for arr in arrs_1d] return arrs_3d_swapped, arrs_1d_swapped
def transpose_img(filename, series, plane=None, rescale=None, target_size=None): """Transpose Numpy NPY saved arrays into new planar orientations and rescaling or resizing. Rescaling/resizing take place in multiprocessing. Files are saved through memmap-based arrays to minimize RAM usage. Output filenames are based on the ``make_modifer_[task]`` functions. Currently transposes all channels, ignoring :attr:``config.channel`` parameter. Args: filename: Full file path in :attribute:cli:`filename` format. series: Series within multi-series file. plane: Planar orientation (see :attribute:plot_2d:`PLANES`). Defaults to None, in which case no planar transformation will occur. rescale: Rescaling factor; defaults to None. Takes precedence over ``target_size``. target_size (List[int]): Target shape in x,y,z; defaults to None, in which case the target size will be extracted from the register profile if available if available. """ if target_size is None: target_size = config.atlas_profile["target_size"] if plane is None and rescale is None and target_size is None: print("No transposition to perform, skipping") return time_start = time() # even if loaded already, reread to get image metadata # TODO: consider saving metadata in config and retrieving from there img5d = importer.read_file(filename, series) info = img5d.meta image5d = img5d.img sizes = info["sizes"] # make filenames based on transpositions modifier = "" if plane is not None: modifier = make_modifier_plane(plane) # either rescaling or resizing if rescale is not None: modifier += make_modifier_scale(rescale) elif target_size: # target size may differ from final output size but allows a known # size to be used for finding the file later modifier += make_modifier_resized(target_size) filename_image5d_npz, filename_info_npz = importer.make_filenames( filename, series, modifier=modifier) # TODO: image5d should assume 4/5 dimensions offset = 0 if image5d.ndim <= 3 else 1 multichannel = image5d.ndim >= 5 image5d_swapped = image5d if plane is not None and plane != config.PLANE[0]: # swap z-y to get (y, z, x) order for xz orientation image5d_swapped = np.swapaxes(image5d_swapped, offset, offset + 1) config.resolutions[0] = libmag.swap_elements(config.resolutions[0], 0, 1) if plane == config.PLANE[2]: # swap new y-x to get (x, z, y) order for yz orientation image5d_swapped = np.swapaxes(image5d_swapped, offset, offset + 2) config.resolutions[0] = libmag.swap_elements( config.resolutions[0], 0, 2) scaling = None if rescale is not None or target_size is not None: # rescale based on scaling factor or target specific size rescaled = image5d_swapped # TODO: generalize for more than 1 preceding dimension? if offset > 0: rescaled = rescaled[0] max_pixels = [100, 500, 500] sub_roi_size = None if target_size: # to avoid artifacts from thin chunks, fit image into even # number of pixels per chunk by rounding up number of chunks # and resizing each chunk by ratio of total size to chunk num target_size = target_size[::-1] # change to z,y,x shape = rescaled.shape[:3] num_chunks = np.ceil(np.divide(shape, max_pixels)) max_pixels = np.ceil(np.divide(shape, num_chunks)).astype(np.int) sub_roi_size = np.floor(np.divide(target_size, num_chunks)).astype(np.int) print("Resizing image of shape {} to target_size: {}, using " "num_chunks: {}, max_pixels: {}, sub_roi_size: {}".format( rescaled.shape, target_size, num_chunks, max_pixels, sub_roi_size)) else: print("Rescaling image of shape {} by factor of {}".format( rescaled.shape, rescale)) # rescale in chunks with multiprocessing sub_roi_slices, _ = chunking.stack_splitter(rescaled.shape, max_pixels) is_fork = chunking.is_fork() if is_fork: Downsampler.set_data(rescaled) sub_rois = np.zeros_like(sub_roi_slices) pool = chunking.get_mp_pool() pool_results = [] for z in range(sub_roi_slices.shape[0]): for y in range(sub_roi_slices.shape[1]): for x in range(sub_roi_slices.shape[2]): coord = (z, y, x) slices = sub_roi_slices[coord] args = [coord, slices, rescale, sub_roi_size, multichannel] if not is_fork: # pickle chunk if img not directly available args.append(rescaled[slices]) pool_results.append( pool.apply_async(Downsampler.rescale_sub_roi, args=args)) for result in pool_results: coord, sub_roi = result.get() print("replacing sub_roi at {} of {}".format( coord, np.add(sub_roi_slices.shape, -1))) sub_rois[coord] = sub_roi pool.close() pool.join() rescaled_shape = chunking.get_split_stack_total_shape(sub_rois) if offset > 0: rescaled_shape = np.concatenate(([1], rescaled_shape)) print("rescaled_shape: {}".format(rescaled_shape)) # rescale chunks directly into memmap-backed array to minimize RAM usage image5d_transposed = np.lib.format.open_memmap( filename_image5d_npz, mode="w+", dtype=sub_rois[0, 0, 0].dtype, shape=tuple(rescaled_shape)) chunking.merge_split_stack2(sub_rois, None, offset, image5d_transposed) if rescale is not None: # scale resolutions based on single rescaling factor config.resolutions = np.multiply(config.resolutions, 1 / rescale) else: # scale resolutions based on size ratio for each dimension config.resolutions = np.multiply(config.resolutions, (image5d_swapped.shape / rescaled_shape)[1:4]) sizes[0] = rescaled_shape scaling = importer.calc_scaling(image5d_swapped, image5d_transposed) else: # transfer directly to memmap-backed array image5d_transposed = np.lib.format.open_memmap( filename_image5d_npz, mode="w+", dtype=image5d_swapped.dtype, shape=image5d_swapped.shape) if plane == config.PLANE[1] or plane == config.PLANE[2]: # flip upside-down if re-orienting planes if offset: image5d_transposed[0, :] = np.fliplr(image5d_swapped[0, :]) else: image5d_transposed[:] = np.fliplr(image5d_swapped[:]) else: image5d_transposed[:] = image5d_swapped[:] sizes[0] = image5d_swapped.shape # save image metadata print("detector.resolutions: {}".format(config.resolutions)) print("sizes: {}".format(sizes)) image5d.flush() importer.save_image_info( filename_info_npz, info["names"], sizes, config.resolutions, info["magnification"], info["zoom"], *importer.calc_intensity_bounds(image5d_transposed), scaling, plane) print("saved transposed file to {} with shape {}".format( filename_image5d_npz, image5d_transposed.shape)) print("time elapsed (s): {}".format(time() - time_start))