Beispiel #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
def fit_frame_to_image(fig, shape, aspect):
    """Compress figure to fit image only.

    Use :attr:`config.plot_labels[config.PlotLabels.PADDING]` to configure
    figure padding, which will turn off the constrained layout.
    
    Args:
        fig: Figure to compress.
        shape: Shape of image to which the figure will be fit.
        aspect: Aspect ratio of image.
    """
    pad = config.plot_labels[config.PlotLabels.PADDING]
    if aspect is None:
        aspect = 1
    img_size_inches = np.divide(shape, fig.dpi)  # convert to inches
    print("image shape: {}, img_size_inches: {}, aspect: {}".format(
        shape, img_size_inches, aspect))
    if aspect > 1:
        fig.set_size_inches(img_size_inches[1], img_size_inches[0] * aspect)
    else:
        # multiply both sides by 1 / aspect => number > 1 to enlarge
        fig.set_size_inches(img_size_inches[1] / aspect, img_size_inches[0])
    if pad:
        # use neg padding to remove thin left border that sometimes appears;
        # NOTE: this setting will turn off constrained layout
        fig.tight_layout(pad=libmag.get_if_within(pad, 0, 0))
    print("fig size: {}".format(fig.get_size_inches()))
Beispiel #3
0
def rotate90(roi: np.ndarray,
             rotate: int,
             axes: Optional[Sequence[int]] = None,
             multichannel: bool = False) -> np.ndarray:
    """Rotate an image by increments of 90 degrees.
    
    Serves as a wrapper for :meth:`numpy.rot90` with default rotation in
    the xy plane.
    
    Args:
        roi: Image as a 3D+/-channel array. Can be None to return as-is.
        rotate: Number of times to rotate 90 degrees.
        axes: Sequence of two axes defining the plane to rotate; defaults to
            None to use ``[-2, -1]``, the 2nd to last and last axes.
        multichannel: True if the image is multichannel; defaults to False.
            Only used if ``axes`` contains negative axis indices.

    Returns:
        Rotated image.

    """
    if rotate is None:
        return roi
    if axes is None:
        # default to using the last 2 axes (xy plane)
        ax = [-2, -1]
    else:
        ax = list(axes)
    for i, a in enumerate(ax):
        if a < 0:
            # wrap neg axes to the end of the axes
            ax[i] += roi.ndim
            if multichannel:
                # skip the channel axis
                ax[i] -= 1
    roi = np.rot90(roi, libmag.get_if_within(rotate, 0), ax)
    return roi
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"])
Beispiel #5
0
    def show_overview(self):
        """Show the main 2D plane, taken as a z-plane."""
        # assume colorbar already shown if set and image previously displayed
        colorbar = (config.roi_profile["colorbar"]
                    and len(self.axes.images) < 1)
        self.axes.clear()
        self.hline = None
        self.vline = None

        # prep 2D image from main image, assumed to be an intensity image
        imgs2d = [self._get_img2d(0, self.img3d, self.max_intens_proj)]
        self._channels = [config.channel]
        cmaps = [config.cmaps]
        alphas = [config.alphas[0]]
        shapes = [self._img3d_shapes[0][1:3]]
        vmaxs = [None]
        vmins = [None]
        if self._plot_ax_imgs:
            # use settings from previously displayed images if available
            vmaxs[0] = [a.ax_img.norm.vmax for a in self._plot_ax_imgs[0]]
            vmins[0] = [a.ax_img.norm.vmin for a in self._plot_ax_imgs[0]]

        if self.img3d_labels is not None:
            # prep labels with discrete colormap and prior alpha if available
            imgs2d.append(self._get_img2d(1, self.img3d_labels))
            self._channels.append([0])
            cmaps.append(self.cmap_labels)
            alphas.append(self._ax_img_labels.get_alpha() if self.
                          _ax_img_labels else self.alpha)
            shapes.append(self._img3d_shapes[1][1:3])
            vmaxs.append(None)
            vmins.append(None)

        if self.img3d_borders is not None:
            # prep borders image, which may have an extra channels
            # dimension for multiple sets of borders
            img2d = self._get_img2d(2, self.img3d_borders)
            channels = img2d.ndim if img2d.ndim >= 3 else 1
            for i, channel in enumerate(range(channels - 1, -1, -1)):
                # show first (original) borders image last so that its
                # colormap values take precedence to highlight original bounds
                img_add = img2d[..., channel] if channels > 1 else img2d
                imgs2d.append(img_add)
                self._channels.append([0])
                cmaps.append(self.cmap_borders[channel])
                alphas.append(libmag.get_if_within(config.alphas, 2 + i, 1))
                shapes.append(self._img3d_shapes[2][1:3])
                vmaxs.append(None)
                vmins.append(None)

        if self.img3d_extras is not None:
            for i, img in enumerate(self.img3d_extras):
                # prep additional intensity image
                imgi = 3 + i
                imgs2d.append(self._get_img2d(imgi, img))
                self._channels.append([0])
                cmaps.append(("Greys", ))
                alphas.append(0.4)
                shapes.append(self._img3d_shapes[imgi][1:3])
                vmaxs.append(None)
                vmins.append(None)

        # overlay all images and set labels for footer value on mouseover;
        # if first time showing image, need to check for images with single
        # value since they fail to update on subsequent updates for unclear
        # reasons
        ax_imgs = plot_support.overlay_images(
            self.axes,
            self.aspect,
            self.origin,
            imgs2d,
            self._channels,
            cmaps,
            alphas,
            vmins,
            vmaxs,
            check_single=(self._ax_img_labels is None))
        if colorbar:
            self.axes.figure.colorbar(ax_imgs[0][0], ax=self.axes)
        self.axes.format_coord = pixel_display.PixelDisplay(
            imgs2d, ax_imgs, shapes, cmap_labels=self.cmap_labels)

        # trigger actual display through slider or call update directly
        if self.plane_slider:
            self.plane_slider.set_val(self.coord[0])
        else:
            self._update_overview(self.coord[0])
        self.show_roi()
        if self.scale_bar:
            plot_support.add_scale_bar(self.axes, self._downsample[0])

        # store displayed images
        if len(ax_imgs) > 1: self._ax_img_labels = ax_imgs[1][0]
        self._plot_ax_imgs = [[PlotAxImg(img) for img in imgs]
                              for imgs in ax_imgs]

        if self.xlim is not None and self.ylim is not None:
            # restore pan/zoom view
            self.axes.set_xlim(self.xlim)
            self.axes.set_ylim(self.ylim)
        if not self.connected:
            # connect once get AxesImage
            self.connect()
        # text label with color for visibility on axes plus fig background
        self.region_label = self.axes.text(0,
                                           0,
                                           "",
                                           color="k",
                                           bbox=dict(facecolor="xkcd:silver",
                                                     alpha=0.5))
        self.circle = None
def imshow_multichannel(ax,
                        img2d,
                        channel,
                        cmaps,
                        aspect,
                        alpha=None,
                        vmin=None,
                        vmax=None,
                        origin=None,
                        interpolation=None,
                        norms=None,
                        nan_color=None,
                        ignore_invis=False):
    """Show multichannel 2D image with channels overlaid over one another.

    Applies :attr:`config.transform` with :obj:`config.Transforms.ROTATE`
    to rotate images. If not available, also checks the first element in
    :attr:``config.flip`` to rotate the image by 180 degrees.
    
    Applies :attr:`config.transform` with :obj:`config.Transforms.FLIP_HORIZ`
    and :obj:`config.Transforms.FLIP_VERT` to invert images.

    Args:
        ax: Axes plot.
        img2d: 2D image either as 2D (y, x) or 3D (y, x, channel) array.
        channel: Channel to display; if None, all channels will be shown.
        cmaps: List of colormaps corresponding to each channel. Colormaps 
            can be the names of specific maps in :mod:``config``.
        aspect: Aspect ratio.
        alpha (float, List[float]): Transparency level for all channels or 
            sequence of levels for each channel. If any value is 0, the
            corresponding image will not be output. Defaults to None to use 1.
        vmin (float, List[float]): Scalar or sequence of vmin levels for
            all channels; defaults to None.
        vmax (float, List[float]): Scalar or sequence of vmax levels for
            all channels; defaults to None.
        origin: Image origin; defaults to None.
        interpolation: Type of interpolation; defaults to None.
        norms: List of normalizations, which should correspond to ``cmaps``.
        nan_color (str): String of color to use for NaN values; defaults to
            None to leave these pixels empty.
        ignore_invis (bool): True to give None instead of an ``AxesImage``
            object that would be invisible; defaults to False.
    
    Returns:
        List of ``AxesImage`` objects.
    """
    # assume that 3D array has a channel dimension
    multichannel, channels = plot_3d.setup_channels(img2d, channel, 2)
    img = []
    num_chls = len(channels)
    if alpha is None:
        alpha = 1
    if num_chls > 1 and not libmag.is_seq(alpha):
        # if alphas not explicitly set per channel, make all channels more
        # translucent at a fixed value that is higher with more channels
        alpha /= np.sqrt(num_chls + 1)

    # transform image based on config parameters
    rotate = config.transform[config.Transforms.ROTATE]
    if rotate is not None:
        last_axis = img2d.ndim - 1
        if multichannel:
            last_axis -= 1
        # use first rotation value
        img2d = np.rot90(img2d, libmag.get_if_within(rotate, 0),
                         (last_axis - 1, last_axis))

    for chl in channels:
        img2d_show = img2d[..., chl] if multichannel else img2d
        cmap = None if cmaps is None else cmaps[chl]
        norm = None if norms is None else norms[chl]
        cmap = colormaps.get_cmap(cmap)
        if cmap is not None and nan_color:
            # given color for masked values such as NaNs to distinguish from 0
            cmap.set_bad(color=nan_color)
        # get setting corresponding to the channel index, or use the value
        # directly if it is a scalar
        vmin_plane = libmag.get_if_within(vmin, chl)
        vmax_plane = libmag.get_if_within(vmax, chl)
        alpha_plane = libmag.get_if_within(alpha, chl)
        img_chl = None
        if not ignore_invis or alpha_plane > 0:
            # skip display if alpha is 0 to avoid outputting a hidden image
            # that may show up in other renderers (eg PDF viewers)
            img_chl = ax.imshow(img2d_show,
                                cmap=cmap,
                                norm=norm,
                                aspect=aspect,
                                alpha=alpha_plane,
                                vmin=vmin_plane,
                                vmax=vmax_plane,
                                origin=origin,
                                interpolation=interpolation)
        img.append(img_chl)

    # flip horizontally or vertically by inverting axes
    if config.transform[config.Transforms.FLIP_HORIZ]:
        if not ax.xaxis_inverted():
            ax.invert_xaxis()
    if config.transform[config.Transforms.FLIP_VERT]:
        inverted = ax.yaxis_inverted()
        if (origin in (None, "lower") and inverted) or (origin == "upper"
                                                        and not inverted):
            # invert only if inversion state is same as expected from origin
            # to avoid repeated inversions with repeated calls
            ax.invert_yaxis()

    return img
Beispiel #7
0
    def show_overview(self):
        """Show the main 2D plane, taken as a z-plane."""
        self.axes.clear()
        self.hline = None
        self.vline = None

        # prep 2D image from main image, assumed to be an intensity image,
        # with settings for each channel within this main image
        imgs2d = [self._get_img2d(0, self.img3d, self.max_intens_proj)]
        self._channels = [config.channel]
        cmaps = [config.cmaps]
        alphas = [config.alphas[0]]
        alpha_blends = [None]
        shapes = [self._img3d_shapes[0][1:3]]
        vmaxs = [None]
        vmins = [None]
        brightnesses = [None]
        contrasts = [None]
        if self._plot_ax_imgs:
            # use vmin/vmax from norm values in previously displayed images
            # if available; None specifies auto-scaling
            vmaxs[0] = [
                p.vmax if p.vmax is None else p.ax_img.norm.vmax
                for p in self._plot_ax_imgs[0]
            ]
            vmins[0] = [
                p.vmin if p.vmin is None else p.ax_img.norm.vmin
                for p in self._plot_ax_imgs[0]
            ]

            # use opacity, brightness, anc contrast from prior images
            alphas[0] = [p.alpha for p in self._plot_ax_imgs[0]]
            alpha_blends[0] = [p.alpha_blend for p in self._plot_ax_imgs[0]]
            brightnesses[0] = [p.brightness for p in self._plot_ax_imgs[0]]
            contrasts[0] = [p.contrast for p in self._plot_ax_imgs[0]]

        if self.img3d_labels is not None:
            # prep labels with discrete colormap and prior alpha if available
            imgs2d.append(self._get_img2d(1, self.img3d_labels))
            self._channels.append([0])
            cmaps.append(self.cmap_labels)
            alphas.append(self._ax_img_labels.get_alpha() if self.
                          _ax_img_labels else self.alpha)
            alpha_blends.append(None)
            shapes.append(self._img3d_shapes[1][1:3])
            vmaxs.append(None)
            vmins.append(None)

        if self.img3d_borders is not None:
            # prep borders image, which may have an extra channels
            # dimension for multiple sets of borders
            img2d = self._get_img2d(2, self.img3d_borders)
            channels = img2d.ndim if img2d.ndim >= 3 else 1
            for i, channel in enumerate(range(channels - 1, -1, -1)):
                # show first (original) borders image last so that its
                # colormap values take precedence to highlight original bounds
                img_add = img2d[..., channel] if channels > 1 else img2d
                imgs2d.append(img_add)
                self._channels.append([0])
                cmaps.append(self.cmap_borders[channel])

                # get alpha for last corresponding borders plane if available
                ax_img = libmag.get_if_within(self._plot_ax_imgs, 2 + i, None)
                alpha = (ax_img[i].alpha if ax_img else libmag.get_if_within(
                    config.alphas, 2 + i, 1))
                alphas.append(alpha)
                alpha_blends.append(None)

                shapes.append(self._img3d_shapes[2][1:3])
                vmaxs.append(None)
                vmins.append(None)

        if self.img3d_extras is not None:
            for i, img in enumerate(self.img3d_extras):
                # prep additional intensity image
                imgi = 3 + i
                imgs2d.append(self._get_img2d(imgi, img))
                self._channels.append([0])
                cmaps.append(("Greys", ))
                alphas.append(0.4)
                alpha_blends.append(None)
                shapes.append(self._img3d_shapes[imgi][1:3])
                vmaxs.append(None)
                vmins.append(None)

        # overlay all images and set labels for footer value on mouseover;
        # if first time showing image, need to check for images with single
        # value since they fail to update on subsequent updates for unclear
        # reasons
        ax_imgs = plot_support.overlay_images(
            self.axes,
            self.aspect,
            self.origin,
            imgs2d,
            self._channels,
            cmaps,
            alphas,
            vmins,
            vmaxs,
            check_single=(self._ax_img_labels is None),
            alpha_blends=alpha_blends)

        # add or update colorbar
        colobar_prof = config.roi_profile["colorbar"]
        if self._colorbar:
            self._colorbar.update_normal(ax_imgs[0][0])
        elif colobar_prof:
            # store colorbar since it's tied to the artist, which will be
            # replaced with the next display and cannot be further accessed
            self._colorbar = self.axes.figure.colorbar(ax_imgs[0][0],
                                                       ax=self.axes,
                                                       **colobar_prof)

        # display coordinates and label values for each image
        self.axes.format_coord = pixel_display.PixelDisplay(
            imgs2d, ax_imgs, shapes, cmap_labels=self.cmap_labels)

        # trigger actual display through slider or call update directly
        if self.plane_slider:
            self.plane_slider.set_val(self.coord[0])
        else:
            self._update_overview(self.coord[0])
        self.show_roi()
        if self.scale_bar:
            plot_support.add_scale_bar(self.axes, self._downsample[0])

        # store displayed images in the PlotAxImg container class and update
        # displayed brightness/contrast
        if len(ax_imgs) > 1: self._ax_img_labels = ax_imgs[1][0]
        self._plot_ax_imgs = []
        for i, imgs in enumerate(ax_imgs):
            plot_ax_imgs = []
            for j, img in enumerate(imgs):
                plot_ax_img = PlotAxImg(img)
                if i == 0:
                    # specified vmin/vmax, in contrast to the AxesImages's
                    # norm, which holds the values used for the displayed image
                    plot_ax_img.vmin = libmag.get_if_within(vmins[i], j)
                    plot_ax_img.vmax = libmag.get_if_within(vmaxs[i], j)

                    # set brightness/contrast
                    self.change_brightness_contrast(
                        plot_ax_img, libmag.get_if_within(brightnesses[i], j),
                        libmag.get_if_within(contrasts[i], j))
                plot_ax_img.alpha = libmag.get_if_within(alphas[i], j)
                plot_ax_img.alpha_blend = libmag.get_if_within(
                    alpha_blends[i], j)
                plot_ax_imgs.append(plot_ax_img)
            self._plot_ax_imgs.append(plot_ax_imgs)

        if self.xlim is not None and self.ylim is not None:
            # restore pan/zoom view
            self.axes.set_xlim(self.xlim)
            self.axes.set_ylim(self.ylim)
        if not self.connected:
            # connect once get AxesImage
            self.connect()
        # text label with color for visibility on axes plus fig background
        self.region_label = self.axes.text(0,
                                           0,
                                           "",
                                           color="k",
                                           bbox=dict(facecolor="xkcd:silver",
                                                     alpha=0.5))
        self.circle = None