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
Example #3
0
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