def detect_sub_roi(cls, coord, offset, last_coord, denoise_max_shape, exclude_border, sub_roi, channel, img_path=None, coloc=False): """Perform 3D blob detection within a sub-ROI without accessing class attributes, such as for spawned multiprocessing. Args: coord (Tuple[int]): Coordinate of the sub-ROI in the order z,y,x. offset (Tuple[int]): Offset of the sub-ROI within the full ROI, in z,y,x. last_coord (:obj:`np.ndarray`): See attributes. denoise_max_shape (Tuple[int]): See attributes. exclude_border (bool): See attributes. sub_roi (:obj:`np.ndarray`): Array in which to perform detections. img_path (str): Path from which to load metadatat; defaults to None. If given, the command line arguments will be reloaded to set up the image and processing parameters. coloc (bool): True to perform blob co-localizations; defaults to False. channel (Sequence[int]): Sequence of channels, where None detects in all channels. Returns: Tuple[int], :obj:`np.ndarray`: The coordinate given back again to identify the sub-ROI position and an array of detected blobs. """ if img_path: # reload command-line parameters and image metadata, which is # required if run from a spawned (not forked) process cli.process_cli_args() _, orig_info = importer.make_filenames(img_path) importer.load_metadata(orig_info) print("detecting blobs in sub-ROI at {} of {}, offset {}, shape {}..." .format(coord, last_coord, tuple(offset.astype(int)), sub_roi.shape)) if denoise_max_shape is not None: # further split sub-ROI for preprocessing locally denoise_roi_slices, _ = chunking.stack_splitter( sub_roi.shape, denoise_max_shape) for z in range(denoise_roi_slices.shape[0]): for y in range(denoise_roi_slices.shape[1]): for x in range(denoise_roi_slices.shape[2]): denoise_coord = (z, y, x) denoise_roi = sub_roi[denoise_roi_slices[denoise_coord]] _logger.debug( f"preprocessing sub-sub-ROI {denoise_coord} of " f"{np.subtract(denoise_roi_slices.shape, 1)} " f"(shape {denoise_roi.shape} within sub-ROI shape " f"{sub_roi.shape})") denoise_roi = plot_3d.saturate_roi( denoise_roi, channel=channel) denoise_roi = plot_3d.denoise_roi( denoise_roi, channel=channel) # replace slices with denoised ROI denoise_roi_slices[denoise_coord] = denoise_roi # re-merge back into the sub-ROI merged_shape = chunking.get_split_stack_total_shape( denoise_roi_slices) merged = np.zeros( tuple(merged_shape), dtype=denoise_roi_slices[0, 0, 0].dtype) chunking.merge_split_stack2(denoise_roi_slices, None, 0, merged) sub_roi = merged if exclude_border is None: exclude = None else: exclude = np.array([exclude_border, exclude_border]) exclude[0, np.equal(coord, 0)] = 0 exclude[1, np.equal(coord, last_coord)] = 0 segments = detector.detect_blobs(sub_roi, channel, exclude) if coloc and segments is not None: # co-localize blobs and append to blobs array colocs = colocalizer.colocalize_blobs(sub_roi, segments) segments = np.hstack((segments, colocs)) #print("segs before (offset: {}):\n{}".format(offset, segments)) if segments is not None: # shift both coordinate sets (at beginning and end of array) to # absolute positioning, using the latter set to store shifted # coordinates based on duplicates and the former for initial # positions to check for multiple duplicates detector.shift_blob_rel_coords(segments, offset) detector.shift_blob_abs_coords(segments, offset) #print("segs after:\n{}".format(segments)) return coord, segments
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))