예제 #1
0
        def setup_plot_ed(axis, gs_spec):
            # set up a PlotEditor for the given axis

            # subplot grid, with larger height preference for plot for
            # each increased row to make sliders of approx equal size and
            # align top borders of top images
            rows_cols = gs_spec.get_rows_columns()
            extra_rows = rows_cols[3] - rows_cols[2]
            gs_plot = gridspec.GridSpecFromSubplotSpec(
                2,
                1,
                subplot_spec=gs_spec,
                height_ratios=(1, 10 + 14 * extra_rows),
                hspace=0.1 / (extra_rows * 1.4 + 1))

            # transform arrays to the given orthogonal direction
            ax = fig.add_subplot(gs_plot[1, 0])
            plot_support.hide_axes(ax)
            plane = config.PLANE[axis]
            arrs_3d, aspect, origin, scaling = \
                plot_support.setup_images_for_plane(
                    plane,
                    (self.image5d[0], self.labels_img, self.borders_img))
            img3d_transposed = arrs_3d[0]
            labels_img_transposed = libmag.get_if_within(arrs_3d, 1)
            borders_img_transposed = libmag.get_if_within(arrs_3d, 2)

            # slider through image planes
            ax_scroll = fig.add_subplot(gs_plot[0, 0])
            plane_slider = Slider(ax_scroll,
                                  plot_support.get_plane_axis(plane),
                                  0,
                                  len(img3d_transposed) - 1,
                                  valfmt="%d",
                                  valinit=0,
                                  valstep=1)

            # plot editor
            max_size = max_sizes[axis] if max_sizes else None
            plot_ed = plot_editor.PlotEditor(
                ax,
                img3d_transposed,
                labels_img_transposed,
                cmap_labels,
                plane,
                aspect,
                origin,
                self.update_coords,
                self.refresh_images,
                scaling,
                plane_slider,
                img3d_borders=borders_img_transposed,
                cmap_borders=cmap_borders,
                fn_show_label_3d=self.fn_show_label_3d,
                interp_planes=self.interp_planes,
                fn_update_intensity=self.update_color_picker,
                max_size=max_size,
                fn_status_bar=self.fn_status_bar)
            return plot_ed
예제 #2
0
 def update_btn(self):
     """Update text and color of button to interpolate planes.
     """
     if any(self.bounds):
         # show current values if any exist
         self.btn.label.set_text("Fill {} {}\nID {}".format(
             plot_support.get_plane_axis(self.plane), self.bounds,
             self.label_id))
         self.btn.label.set_fontsize("xx-small")
     enable_btn(self.btn, all(self.bounds))
예제 #3
0
        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)
예제 #4
0
 def __str__(self):
     return "{}: {} (ID: {})".format(
         plot_support.get_plane_axis(self.plane), self.bounds,
         self.label_id)
예제 #5
0
def stack_to_img(paths,
                 roi_offset,
                 roi_size,
                 series=None,
                 subimg_offset=None,
                 subimg_size=None,
                 animated=False,
                 suffix=None):
    """Build an image file from a stack of images in a directory or an 
    array, exporting as an animated GIF or movie for multiple planes or 
    extracting a single plane to a standard image file format.
    
    Writes the file to the parent directory of path.
    
    Args:
        paths (List[str]): Image paths, which can each be either an image 
            directory or a base path to a single image, including 
            volumetric images.
        roi_offset (Sequence[int]): Tuple of offset given in user order
            ``x,y,z``; defaults to None. Requires ``roi_size`` to not be None.
        roi_size (Sequence[int]): Size of the region of interest in user order 
            ``x,y,z``; defaults to None. Requires ``roi_offset`` to not be None.
        series (int): Image series number; defaults to None.
        subimg_offset (List[int]): Sub-image offset as (z,y,x) to load;
            defaults to None.
        subimg_size (List[int]): Sub-image size as (z,y,x) to load;
            defaults to None.
        animated (bool): True to export as an animated image; defaults to False.
        suffix (str): String to append to output path before extension; 
            defaults to None to ignore.

    """
    # set up figure layout for collages
    size = config.plot_labels[config.PlotLabels.LAYOUT]
    ncols, nrows = size if size else (1, 1)
    num_paths = len(paths)
    collage = num_paths > 1
    figs = {}

    for i in range(nrows):
        for j in range(ncols):
            n = i * ncols + j
            if n >= num_paths: break

            # load an image and set up its image stacker
            path_sub = paths[n]
            axs = []
            # TODO: test directory of images
            # TODO: consider not reloading first image
            np_io.setup_images(path_sub, series, subimg_offset, subimg_size)
            stacker = setup_stack(
                config.image5d,
                path_sub,
                offset=roi_offset,
                roi_size=roi_size,
                slice_vals=config.slice_vals,
                rescale=config.transform[config.Transforms.RESCALE],
                labels_imgs=(config.labels_img, config.borders_img))

            # add sub-plot title unless groups given as empty string
            title = None
            if config.groups:
                title = libmag.get_if_within(config.groups, n)
            elif num_paths > 1:
                title = os.path.basename(path_sub)

            if not stacker.images: continue
            ax = None
            for k in range(len(stacker.images[0])):
                # create or retrieve fig; animation has only 1 fig
                planei = 0 if animated else (stacker.img_slice.start +
                                             k * stacker.img_slice.step)
                fig_dict = figs.get(planei)
                if not fig_dict:
                    # set up new fig
                    fig, gs = plot_support.setup_fig(
                        nrows, ncols,
                        config.plot_labels[config.PlotLabels.SIZE])
                    fig_dict = {"fig": fig, "gs": gs, "imgs": []}
                    figs[planei] = fig_dict
                if ax is None:
                    # generate new axes for the gridspec position
                    ax = fig_dict["fig"].add_subplot(fig_dict["gs"][i, j])
                if title:
                    ax.title.set_text(title)
                axs.append(ax)

            # export planes
            plotted_imgs = stacker.build_stack(
                axs, config.plot_labels[config.PlotLabels.SCALE_BAR],
                size is None or ncols * nrows == 1)

            if animated:
                # store all plotted images in single fig
                fig_dict = figs.get(0)
                if fig_dict:
                    fig_dict["imgs"] = plotted_imgs
            else:
                # store one plotted image per fig; not used currently
                for fig_dict, img in zip(figs.values(), plotted_imgs):
                    fig_dict["imgs"].append(img)

    path_base = paths[0]
    for planei, fig_dict in figs.items():
        if animated:
            # generate animated image (eg animated GIF or movie file)
            animate_imgs(path_base, fig_dict["imgs"], config.delay,
                         config.savefig, suffix)
        else:
            # generate single figure with axis and plane index in filename
            if collage:
                # output filename as a collage of images
                if not os.path.isdir(path_base):
                    path_base = os.path.dirname(path_base)
                path_base = os.path.join(path_base, "collage")

            # insert mod as suffix, then add any additional suffix;
            # can use config.prefix_out for make_out_path prefix
            mod = "_plane_{}{}".format(
                plot_support.get_plane_axis(config.plane), planei)
            out_path = libmag.make_out_path(path_base, suffix=mod)
            if suffix:
                out_path = libmag.insert_before_ext(out_path, suffix)
            plot_support.save_fig(out_path,
                                  config.savefig,
                                  fig=fig_dict["fig"])
예제 #6
0
def setup_stack(
        image5d: np.ndarray,
        path: Optional[str] = None,
        offset: Optional[Sequence[int]] = None,
        roi_size: Optional[Sequence[int]] = None,
        slice_vals: Optional[Sequence[int]] = None,
        rescale: Optional[float] = None,
        labels_imgs: Optional[Sequence[np.ndarray]] = None) -> StackPlaneIO:
    """Set up a stack of images for export to file.
     
    Supports a stack of image files in a directory or a single volumetric image
    and associated labels images.
    
    Args:
        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.
    
    Returns:
        Stack builder instance.
    
    """
    print("Starting image stack setup")

    # build "z" slice, which will be applied to the transposed image
    interval = 1  # default to export each plane
    if offset is not None and roi_size is not None:
        # extract planes based on ROI settings

        # 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
        if slice_vals is not None and len(slice_vals) > 2:
            interval = slice_vals[2]
        size = roi_size[2]
        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:
        # build directly from slice vals, replacing start and step if None
        sl = slice(*slice_vals)
        sl = [sl.start, sl.stop, sl.step]
        if sl[0] is None:
            # default to start at beginning of stack
            sl[0] = 0
        if sl[2] is None:
            # default to interval/step of 1
            sl[2] = 1
        img_sl = slice(*sl)

    else:
        # default to take the whole image stack
        img_sl = slice(0, None, 1)

    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]
        _logger.info("Importing images from %s: %s", path, 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

    # store in stack worker
    stacker = StackPlaneIO()
    StackPlaneIO.interp_order = config.transform[
        config.Transforms.INTERPOLATION]
    stacker.images = extracted_planes
    stacker.fn_process = fnc
    stacker.rescale = rescale
    stacker.start_planei = start_planei
    stacker.origin = origin
    stacker.aspect = aspect
    stacker.cmaps_labels = cmaps_labels
    stacker.img_slice = img_sl

    return stacker
예제 #7
0
def stack_to_img(paths,
                 roi_offset,
                 roi_size,
                 series=None,
                 subimg_offset=None,
                 subimg_size=None,
                 animated=False,
                 suffix=None):
    """Build an image file from a stack of images in a directory or an 
    array, exporting as an animated GIF or movie for multiple planes or 
    extracting a single plane to a standard image file format.
    
    Writes the file to the parent directory of path.
    
    Args:
        paths (List[str]): Image paths, which can each be either an image 
            directory or a base path to a single image, including 
            volumetric images.
        roi_offset (Sequence[int]): Tuple of offset given in user order
            ``x,y,z``; defaults to None. Requires ``roi_size`` to not be None.
        roi_size (Sequence[int]): Size of the region of interest in user order 
            ``x,y,z``; defaults to None. Requires ``roi_offset`` to not be None.
        series (int): Image series number; defaults to None.
        subimg_offset (List[int]): Sub-image offset as (z,y,x) to load;
            defaults to None.
        subimg_size (List[int]): Sub-image size as (z,y,x) to load;
            defaults to None.
        animated (bool): True to export as an animated image; defaults to False.
        suffix (str): String to append to output path before extension; 
            defaults to None to ignore.

    """
    size = config.plot_labels[config.PlotLabels.LAYOUT]
    ncols, nrows = size if size else (1, 1)
    fig, gs = plot_support.setup_fig(
        nrows, ncols, config.plot_labels[config.PlotLabels.SIZE])
    plotted_imgs = None
    num_paths = len(paths)
    for i in range(nrows):
        for j in range(ncols):
            n = i * ncols + j
            if n >= num_paths: break
            ax = fig.add_subplot(gs[i, j])
            path_sub = paths[n]
            # TODO: test directory of images
            # TODO: avoid reloading first image
            np_io.setup_images(path_sub, series, subimg_offset, subimg_size)
            plotted_imgs = stack_to_ax_imgs(
                ax,
                config.image5d,
                path_sub,
                offset=roi_offset,
                roi_size=roi_size,
                slice_vals=config.slice_vals,
                rescale=config.transform[config.Transforms.RESCALE],
                labels_imgs=(config.labels_img, config.borders_img),
                multiplane=animated,
                fit=(size is None or ncols * nrows == 1))
    path_base = paths[0]
    if animated:
        # generate animated image (eg animated GIF or movie file)
        animate_imgs(path_base, plotted_imgs, config.delay, config.savefig,
                     suffix)
    else:
        # save image as single file
        if roi_offset:
            # get plane index from coordinate at the given axis in ROI offset
            planei = roi_offset[::-1][plot_support.get_plane_axis(
                config.plane, get_index=True)]
        else:
            # get plane index from slice start
            planei = config.slice_vals[0]
        if num_paths > 1:
            # output filename as a collage of images
            if not os.path.isdir(path_base):
                path_base = os.path.dirname(path_base)
            path_base = os.path.join(path_base, "collage")
        mod = "_plane_{}{}".format(plot_support.get_plane_axis(config.plane),
                                   planei)
        if suffix: path_base = libmag.insert_before_ext(path_base, suffix)
        plot_support.save_fig(path_base, config.savefig, mod)
예제 #8
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
예제 #9
0
def _build_stack(ax,
                 images,
                 process_fnc,
                 rescale=1,
                 aspect=None,
                 origin=None,
                 cmaps_labels=None,
                 scale_bar=True,
                 start_planei=0):
    """Builds a stack of Matploblit 2D images.
    
    Uses multiprocessing to load or resize each image.
    
    Args:
        images: Sequence of images. For import, each "image" is a path to 
            and image file. For export, each "image" is a sequence of 
            planes, with the first sequence assumed to an atlas, 
            followed by labels-based images, each consisting of 
            corresponding planes.
        process_fnc: Function to process each image through multiprocessing, 
            where the function should take an index and image and return the 
            index and processed plane.
        rescale (float): Rescale factor; defaults to 1.
        cmaps_labels: Sequence of colormaps for labels-based images; 
            defaults to None. Length should be equal to that of 
            ``images`` - 1.
        scale_bar: True to include scale bar; defaults to True.
        start_planei (int): Index of start plane, used for labeling the
            plane; defaults to 0. The plane is only annotated when
            :attr:`config.plot_labels[config.PlotLabels.TEXT_POS]` is given
            to specify the position of the text in ``x,y`` relative to the
            axes.
    
    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.
    
    """
    # number of image types (eg atlas, labels) and corresponding planes
    num_image_types = len(images)
    if num_image_types < 1: return None
    num_images = len(images[0])
    if num_images < 1: return None

    # Matplotlib figure for building the animation
    plot_support.hide_axes(ax)

    # import the images as Matplotlib artists via multiprocessing
    plotted_imgs = [None] * num_images
    img_shape = images[0][0].shape
    target_size = np.multiply(img_shape, rescale).astype(int)
    multichannel = images[0][0].ndim >= 3
    if multichannel:
        print("building stack for channel: {}".format(config.channel))
        target_size = target_size[:-1]
    StackPlaneIO.set_data(images)
    pool = chunking.get_mp_pool()
    pool_results = []
    for i in range(num_images):
        # add rotation argument if necessary
        pool_results.append(
            pool.apply_async(process_fnc, args=(i, target_size)))

    # setup imshow parameters
    colorbar = config.roi_profile["colorbar"]
    cmaps_all = [config.cmaps, *cmaps_labels]

    img_size = None
    text_pos = config.plot_labels[config.PlotLabels.TEXT_POS]
    for result in pool_results:
        i, imgs = result.get()
        if img_size is None: img_size = imgs[0].shape

        # 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,
                                              aspect,
                                              origin,
                                              imgs,
                                              None,
                                              cmaps_all,
                                              ignore_invis=True,
                                              check_single=True)
        if colorbar and len(ax_imgs) > 0 and len(ax_imgs[0]) > 0:
            # add colorbar with scientific notation if outside limits
            cbar = ax.figure.colorbar(ax_imgs[0][0], ax=ax, shrink=0.7)
            plot_support.set_scinot(cbar.ax, lbls=None, units=None)
        plotted_imgs[i] = 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),
                               start_planei + i),
                           transform=ax.transAxes,
                           color="w")
            plotted_imgs[i] = [*plotted_imgs[i], text]

    pool.close()
    pool.join()

    if scale_bar:
        plot_support.add_scale_bar(ax, 1 / rescale, config.plane)

    return plotted_imgs