def reg_planes_to_img(imgs, path=None, ax=None): """Export registered image single planes to a single figure. Simplified export tool taking a single plane from each registered image type, overlaying in a single figure, and exporting to file. Args: imgs (List[:obj:`np.ndarray`]): Sequence of image planes to display. The first image is assumed to be greyscale, the second is labels, and any subsequent images are borders. path (str): Output base path, which will be combined with :attr:`config.savefig`; defaults to None to not save. ax (:obj:`matplotlib.image.Axes`): Axes on which to plot; defaults to False, in which case a new figure and axes will be generated. """ if ax is None: # set up new figure with single subplot fig, gs = plot_support.setup_fig( 1, 1, config.plot_labels[config.PlotLabels.SIZE]) ax = fig.add_subplot(gs[0, 0]) stacker = StackPlaneIO() StackPlaneIO.interp_order = config.transform[ config.Transforms.INTERPOLATION] stacker.images = [img[None] for img in imgs] stacker.fn_process = StackPlaneIO.process_plane stacker.cmaps_labels = _setup_labels_cmaps(imgs) plotted_imgs = stacker.build_stack(ax, scale_bar=False) ax_img = plotted_imgs[0][0] aspect, origin = plot_support.get_aspect_ratio(config.plane) plot_support.fit_frame_to_image(ax_img.figure, ax_img.get_array().shape, aspect) if path: plot_support.save_fig(path, config.savefig)
def build_stack(self, axs: List, scale_bar: bool = True, fit: bool = False) -> Optional[List]: """Builds a stack of Matploblit 2D images. Uses multiprocessing to load or resize each image. Args: axs: Sub-plot axes. scale_bar: True to include scale bar; defaults to True. fit: True to fit the figure frame to the resulting image. Returns: :List[List[:obj:`matplotlib.image.AxesImage`]]: Nested list of axes image objects. The first list level contains planes, and the second level are channels within each plane. """ def handle_extracted_plane(): # get sub-plot and hide x/y axes ax = axs if libmag.is_seq(ax): ax = axs[imgi] plot_support.hide_axes(ax) # multiple artists can be shown at each frame by collecting # each group of artists in a list; overlay_images returns # a nested list containing a list for each image, which in turn # contains a list of artists for each channel ax_imgs = plot_support.overlay_images(ax, self.aspect, self.origin, imgs, None, cmaps_all, ignore_invis=True, check_single=True) if (colorbar is not None and len(ax_imgs) > 0 and len(ax_imgs[0]) > 0 and imgi == 0): # add colorbar with scientific notation if outside limits cbar = ax.figure.colorbar(ax_imgs[0][0], ax=ax, **colorbar) plot_support.set_scinot(cbar.ax, lbls=None, units=None) plotted_imgs[imgi] = np.array(ax_imgs).flatten() if libmag.is_seq(text_pos) and len(text_pos) > 1: # write plane index in axes rather than data coordinates text = ax.text(*text_pos[:2], "{}-plane: {}".format( plot_support.get_plane_axis(config.plane), self.start_planei + imgi), transform=ax.transAxes, color="w") plotted_imgs[imgi] = [*plotted_imgs[imgi], text] if scale_bar: plot_support.add_scale_bar(ax, 1 / self.rescale, config.plane) # number of image types (eg atlas, labels) and corresponding planes num_image_types = len(self.images) if num_image_types < 1: return None num_images = len(self.images[0]) if num_images < 1: return None # import the images as Matplotlib artists via multiprocessing plotted_imgs: List = [None] * num_images img_shape = self.images[0][0].shape target_size = np.multiply(img_shape, self.rescale).astype(int) multichannel = self.images[0][0].ndim >= 3 if multichannel: print("building stack for channel: {}".format(config.channel)) target_size = target_size[:-1] # setup imshow parameters colorbar = config.roi_profile["colorbar"] cmaps_all = [config.cmaps, *self.cmaps_labels] text_pos = config.plot_labels[config.PlotLabels.TEXT_POS] StackPlaneIO.set_data(self.images) pool_results = None pool = None multiprocess = self.rescale != 1 if multiprocess: # set up multiprocessing initializer = None initargs = None if not chunking.is_fork(): # set up labels image as a shared array for spawned mode initializer, initargs = StackPlaneIO.build_pool_init( OrderedDict([(i, img) for i, img in enumerate(self.images)])) pool = chunking.get_mp_pool(initializer, initargs) pool_results = [] for i in range(num_images): # add rotation argument if necessary args = (i, target_size) if pool is None: # extract and handle without multiprocessing imgi, imgs = self.fn_process(*args) handle_extracted_plane() else: # extract plane in multiprocessing pool_results.append( pool.apply_async(self.fn_process, args=args)) if multiprocess: # handle multiprocessing output for result in pool_results: imgi, imgs = result.get() handle_extracted_plane() pool.close() pool.join() if fit and plotted_imgs: # fit each figure to its first available image for ax_img in plotted_imgs: # images may be flattened AxesImage, array of AxesImage and # Text, or None if alpha set to 0 if libmag.is_seq(ax_img): ax_img = ax_img[0] if isinstance(ax_img, AxesImage): plot_support.fit_frame_to_image(ax_img.figure, ax_img.get_array().shape, self.aspect) return plotted_imgs
def stack_to_ax_imgs(ax, image5d, path=None, offset=None, roi_size=None, slice_vals=None, rescale=None, labels_imgs=None, multiplane=False, fit=False): """Export a stack of images in a directory or a single volumetric image and associated labels images to :obj:`matplotlib.image.AxesImage` objects for export. Args: ax (:obj:`plt.Axes`): Matplotlib axes on which to plot images. image5d: Images as a 4/5D Numpy array (t,z,y,x[c]). Can be None if ``path`` is set. path: Path to an image directory from which all files will be imported in Python sorted order, taking precedence over ``imaged5d``; defaults to None. offset: Tuple of offset given in user order (x, y, z); defaults to None. Requires ``roi_size`` to not be None. roi_size: Size of the region of interest in user order (x, y, z); defaults to None. Requires ``offset`` to not be None. slice_vals: List from which to construct a slice object to extract only a portion of the image. Defaults to None, which will give the whole image. If ``offset`` and ``roi_size`` are also given, ``slice_vals`` will only be used for its interval term. rescale: Rescaling factor for each image, performed on a plane-by-plane basis; defaults to None, in which case 1.0 will be used. labels_imgs: Sequence of labels-based images as a Numpy z,y,x arrays, typically including labels and borders images; defaults to None. multiplane: True to extract the images as an animated GIF or movie file; False to extract a single plane only. Defaults to False. fit (bool): True to fit the figure frame to the resulting image. Returns: List[:obj:`matplotlib.image.AxesImage`]: List of image objects. """ print("Starting image stack export") # build "z" slice, which will be applied to the transposed image; # reduce image to 1 plane if in single mode interval = 1 if offset is not None and roi_size is not None: # transpose coordinates to given plane _, arrs_1d = plot_support.transpose_images( config.plane, arrs_1d=[offset[::-1], roi_size[::-1]]) offset = arrs_1d[0][::-1] roi_size = arrs_1d[1][::-1] # ROI offset and size take precedence over slice vals except # for use of the interval term interval = None if slice_vals is not None and len(slice_vals) > 2: interval = slice_vals[2] size = roi_size[2] if multiplane else 1 img_sl = slice(offset[2], offset[2] + size, interval) if interval is not None and interval < 0: # reverse start/stop order to iterate backward img_sl = slice(img_sl.stop, img_sl.start, interval) print("using ROI offset {}, size {}, {}".format(offset, size, img_sl)) elif slice_vals is not None: # build directly from slice vals unless not an animation if multiplane: img_sl = slice(*slice_vals) else: # single plane only for non-animation img_sl = slice(slice_vals[0], slice_vals[0] + 1) else: # default to take the whole image stack img_sl = slice(None, None) if rescale is None: rescale = 1.0 aspect = None origin = None cmaps_labels = [] extracted_planes = [] start_planei = 0 if path and os.path.isdir(path): # builds animations from all files in a directory planes = sorted(glob.glob(os.path.join(path, "*")))[::interval] print(planes) fnc = StackPlaneIO.import_img extracted_planes.append(planes) else: # load images from path and extract ROI based on slice parameters, # assuming 1st image is atlas, 2nd and beyond are labels-based imgs = [image5d] if labels_imgs is not None: for img in labels_imgs: if img is not None: imgs.append(img[None]) _setup_labels_cmaps(imgs, cmaps_labels) main_shape = None # z,y,x shape of 1st image for img in imgs: sl = img_sl img_shape = img.shape[1:4] if main_shape: if main_shape != img_shape: # scale slice bounds to the first image's shape scaling = np.divide(img_shape, main_shape) axis = plot_support.get_plane_axis(config.plane, True) sl = libmag.scale_slice(sl, scaling[axis], img_shape[axis]) else: main_shape = img_shape planes, aspect, origin = plot_support.extract_planes( img, sl, plane=config.plane) if offset is not None and roi_size is not None: # get ROI using transposed coordinates on transposed planes; # returns list planes = planes[:, offset[1]:offset[1] + roi_size[1], offset[0]:offset[0] + roi_size[0]] extracted_planes.append(planes) fnc = StackPlaneIO.process_plane if img_sl.start: start_planei = img_sl.start # export planes plotted_imgs = _build_stack( ax, extracted_planes, fnc, rescale, aspect=aspect, origin=origin, cmaps_labels=cmaps_labels, scale_bar=config.plot_labels[config.PlotLabels.SCALE_BAR], start_planei=start_planei) if fit and plotted_imgs: # fit frame to first plane's first available image ax_img = None for ax_img in plotted_imgs[0]: # images may be None if alpha set to 0 if ax_img is not None: break if ax_img is not None: plot_support.fit_frame_to_image(ax_img.figure, ax_img.get_array().shape, aspect) return plotted_imgs