Source code for sarcasm.plots

# -*- coding: utf-8 -*-
# Copyright (c) 2025 University Medical Center Göttingen, Germany.
# All rights reserved.
#
# Patent Pending: DE 10 2024 112 939.5
# SPDX-License-Identifier: LicenseRef-Proprietary-See-LICENSE
#
# This software is licensed under a custom license. See the LICENSE file
# in the root directory for full details.
#
# **Commercial use is prohibited without a separate license.**
# Contact MBM ScienceBridge GmbH (https://sciencebridge.de/en/) for licensing.


import numbers
import os.path
import warnings
from typing import Union, Tuple, Optional, Literal

import matplotlib
import numpy as np
from matplotlib import pyplot as plt, transforms
from matplotlib.axes import Axes
from matplotlib.lines import Line2D
from matplotlib.ticker import FormatStrFormatter, MultipleLocator
from matplotlib_scalebar.scalebar import ScaleBar
from tifffile import tifffile

from sarcasm.feature_dict import structure_feature_dict
from sarcasm.motion import Motion
from sarcasm.plot_utils import PlotUtils
from sarcasm.structure import Structure
from sarcasm.utils import Utils


[docs] class Plots: """ Class with plotting functions for Structure and Motion objects """
[docs] @staticmethod def plot_stack_overlay(ax: Axes, sarc_obj: Union[Structure, Motion], frames, plot_func, offset=0.025, spine_color='w', xlim=None, ylim=None): """ Plot a stack of overlayed subplots on a given Axes object. Parameters ---------- ax : matplotlib.axes.Axes The Axes object on which the stack should be plotted. sarc_obj : Structure Data to be plotted in each subplot, which can be an instance of Structure or Motion. frames : list The frames at which the subplots should be created. plot_func : function The function used to plot the data in each subplot, e.g. offset : float, optional The offset between each subplot. Defaults to 0.025. spine_color : str, optional The color of the spines (borders) of each subplot. Defaults to 'w' (white). xlim : tuple, optional The x-axis limits for each subplot. Defaults to None. ylim : tuple, optional The y-axis limits for each subplot. Defaults to None. """ ax.axis('off') for i, t in enumerate(frames): ax_t = ax.inset_axes((0.1 + offset * i, 0.1 - offset * i, 0.8, 0.8)) plot_func(ax_t, sarc_obj, t) ax_t.spines['bottom'].set_color(spine_color) ax_t.spines['top'].set_color(spine_color) ax_t.spines['right'].set_color(spine_color) ax_t.spines['left'].set_color(spine_color) ax_t.set_xlim(xlim) ax_t.set_ylim(ylim)
[docs] @staticmethod def plot_loi_summary_motion(motion_obj: Motion, number_contr=0, t_lim=(0, 12), t_lim_overlay=(-0.1, 2.9), filename=None): """ Plots a summary of the motion of the line of interest (LOI). Parameters ---------- motion_obj : Motion The Motion object to plot. number_contr : int, optional The number of contractions to plot. Defaults to 0. t_lim : tuple of float, optional The time limits for the plot in seconds. Defaults to (0, 12). t_lim_overlay : tuple of float, optional The time limits for the overlay plots in seconds. Defaults to (-0.1, 2.9) filename : str, optional The filename to save the plot. Defaults to None. """ mosaic = """ aaaccc bbbccc dddeee dddfff """ fig, axs = plt.subplot_mosaic(mosaic, figsize=(PlotUtils.width_2cols, PlotUtils.width_2cols), constrained_layout=True) title = f'File: {motion_obj.filepath}, \nLOI: {motion_obj.loi_name}' fig.suptitle(title, fontsize=PlotUtils.fontsize) # A- image cell w/ LOI Plots.plot_image(axs['a'], motion_obj, show_loi=True) # B- U-Net cell w/ LOI Plots.plot_z_bands(axs['b'], motion_obj, show_loi=True) # C- kymograph and tracked z-lines Plots.plot_z_pos(axs['c'], motion_obj, t_lim=t_lim) # D- single sarcomere trajs (vel and delta slen) Plots.plot_delta_slen(axs['d'], motion_obj, t_lim=t_lim) # E- overlay delta slen Plots.plot_overlay_delta_slen(axs['e'], motion_obj, number_contr=number_contr, t_lim=t_lim_overlay) # F- overlay velocity Plots.plot_overlay_velocity(axs['f'], motion_obj, number_contr=number_contr, t_lim=t_lim_overlay) PlotUtils.label_all_panels(axs) if filename is None: filename = os.path.join(motion_obj.loi_folder, 'summary_loi.png') fig.savefig(filename, dpi=PlotUtils.dpi) plt.show()
[docs] @staticmethod def plot_loi_detection(sarc_obj: Structure, frame: int = 0, filepath: str = None, cmap_z_bands='Greys'): """ Plots all steps of automated LOI finding algorithm Parameters ---------- sarc_obj : Structure Instance of Structure class frame: int The time point to plot. filepath: str Path to save the plot. If None, plot is not saved. cmap_z_bands : str, optional Colormap of Z-bands. Defaults to 'Greys'. """ mosaic = """ ac bd """ fig, axs = plt.subplot_mosaic(mosaic, figsize=(PlotUtils.width_2cols, PlotUtils.width_1p5cols), constrained_layout=True) if isinstance(sarc_obj.data['params.analyze_sarcomere_vectors.frames'], int): frame = sarc_obj.data['params.analyze_sarcomere_vectors.frames'] elif sarc_obj.data['params.analyze_sarcomere_vectors.frames'] == 'all': frame = frame else: frame = sarc_obj.data['params.analyze_sarcomere_vectors.frames'][frame] Plots.plot_z_bands(axs['a'], sarc_obj, frame=frame, cmap=cmap_z_bands) Plots.plot_z_bands(axs['c'], sarc_obj, frame=frame, cmap=cmap_z_bands) Plots.plot_z_bands(axs['d'], sarc_obj, frame=frame, cmap=cmap_z_bands) for i, pos_vectors_i in enumerate(sarc_obj.data['loi_data']['lines_vectors']): axs['a'].plot(pos_vectors_i[:, 1], pos_vectors_i[:, 0], c='r', lw=0.2, alpha=0.6) axs['b'].hist(sarc_obj.data['loi_data']['hausdorff_dist_matrix'].reshape(-1), bins=100, color='k', alpha=0.75, rwidth=0.75) axs['b'].set_xlim(0, 400) axs['b'].set_xlabel('Hausdorff distance') axs['b'].set_ylabel('# LOI pairs') for i, (pos_vectors_i, label_i) in enumerate(zip(sarc_obj.data['loi_data']['lines_vectors'], sarc_obj.data['loi_data']['line_cluster'])): axs['c'].plot(pos_vectors_i[:, 1], pos_vectors_i[:, 0], c=plt.cm.jet(label_i / sarc_obj.data['loi_data']['n_lines_clusters']), lw=0.2) for i, line_i in enumerate(sarc_obj.data['loi_data']['loi_lines']): axs['d'].plot(line_i.T[1], line_i.T[0], lw=2, label=i) axs['d'].legend(loc='lower left', fontsize='xx-small') PlotUtils.label_all_panels(axs, offset=(0.05, 0.9)) axs['a'].set_title('1. Line growth', ha='left', x=0.02, fontsize=PlotUtils.fontsize + 1, fontweight='bold') axs['b'].set_title('2. Pair-wise Hausdorff distance', ha='left', x=0.02, fontsize=PlotUtils.fontsize + 1, fontweight='bold') axs['c'].set_title('3. Agglomerative clustering', ha='left', x=0.02, fontsize=PlotUtils.fontsize + 1, fontweight='bold') axs['d'].set_title('4. LOI lines', ha='left', x=0.02, fontsize=PlotUtils.fontsize + 1, fontweight='bold') if filepath is not None: fig.savefig(filepath, dpi=300) plt.show()
[docs] @staticmethod def plot_image(ax: Axes, sarc_obj: Union[Structure, Motion], frame: int = 0, cmap: str = 'grey', alpha: float = 1, clip_thrs: Tuple[float, float] = (1, 99), scalebar: bool = True, title: Union[None, str] = None, show_loi: bool = False, zoom_region: Tuple[int, int, int, int] = None, inset_bounds: Tuple[float, float, float, float] = (0.6, 0.6, 0.4, 0.4)): """ Plots microscopy raw image of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure or Motion The sarcomere object to plot. frame : int, optional The frame to plot. Defaults to 0. cmap : matplotlib.cm.Colormap, optional The colormap to use. Defaults to 'gray'. alpha : float, optional The transparency to use. Defaults to 1. clip_thrs : tuple, optional Clipping thresholds to normalize intensity, in percentiles. Defaults to (1, 99). scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The title for the plot. Defaults to None. show_loi : bool, optional Whether to show the line of interest (LOI). Defaults to True. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ img = sarc_obj.read_imgs(frames=frame) img = np.clip(img, np.percentile(img, clip_thrs[0]), np.percentile(img, clip_thrs[1])) plot = ax.imshow(img, cmap=cmap, alpha=alpha) if show_loi: Plots.plot_lois(ax, sarc_obj) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) ax_inset.imshow(img[y1:y2, x1:x2], cmap='gray') ax_inset.set_xticks([]) ax_inset.set_yticks([]) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='w') PlotUtils.change_color_spines(ax_inset, 'w') if scalebar: ax_inset.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1}))
[docs] @staticmethod def plot_z_bands(ax: plt.Axes, sarc_obj: Union[Structure, Motion], frame=0, cmap='Greys_r', zero_transparent=False, alpha=1, scalebar=True, title=None, color_scalebar='w', show_loi=False, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots the Z-bands of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure or Motion The sarcomere object to plot. frame : int, optional The frame to plot. Defaults to 0. cmap : matplotlib.cm.Colormap, optional Colormap to use. Defaults to 'Greys_r'. alpha : float, optional Alpha value to change opacity of image. Defaults to 1 scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The title for the plot. Defaults to None. show_loi : bool, optional Whether to show the line of interest (LOI). Defaults to True. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert os.path.exists(sarc_obj.file_zbands), ('Z-band mask not found. Run predict_z_bands first.') img = tifffile.imread(sarc_obj.file_zbands, key=frame) if zero_transparent: img = np.ma.masked_where(img < 0.05, img) ax.imshow(img, cmap=cmap, alpha=alpha) if show_loi: Plots.plot_lois(ax, sarc_obj) if scalebar: ax.add_artist( ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color=color_scalebar, sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) PlotUtils.change_color_spines(ax_inset, 'w') ax_inset.imshow(img[y1:y2, x1:x2], cmap=cmap, alpha=alpha) ax_inset.set_xticks([]) ax_inset.set_yticks([]) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='w')
[docs] def plot_z_bands_midlines(ax: plt.Axes, sarc_obj: Union[Structure, Motion], frame=0, cmap='berlin', alpha=1, scalebar=True, title=None, color_scalebar='w', show_loi=True, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots the Z-bands and midlines of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure or Motion The sarcomere object to plot. frame : int, optional The frame to plot. Defaults to 0. cmap : matplotlib.cm.Colormap, optional Colormap to use. Defaults to 'Blues_r'. alpha : float, optional Alpha value to change opacity of image. Defaults to 1 scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The title for the plot. Defaults to None. show_loi : bool, optional Whether to show the line of interest (LOI). Defaults to True. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert os.path.exists(sarc_obj.file_zbands), ('Z-band mask not found. Run predict_z_bands first.') zbands = tifffile.imread(sarc_obj.file_zbands, key=frame) midlines = tifffile.imread(sarc_obj.file_mbands, key=frame) joined = midlines - zbands ax.imshow(joined, cmap=cmap, alpha=alpha) if show_loi: Plots.plot_lois(ax, sarc_obj) if scalebar: ax.add_artist( ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color=color_scalebar, sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) PlotUtils.change_color_spines(ax_inset, 'w') ax_inset.imshow(joined[y1:y2, x1:x2], cmap=cmap, alpha=alpha) ax_inset.set_xticks([]) ax_inset.set_yticks([]) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='w') if scalebar: ax_inset.add_artist( ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color=color_scalebar, sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1}))
[docs] @staticmethod def plot_cell_mask(ax: Axes, sarc_obj: Union[Structure, Motion], frame=0, threshold=0.5, cmap='gray', alpha=1, scalebar=True, title=None): """ Plots the cell mask of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure or Motion The sarcomere object to plot. frame : int, optional The frame to plot. Defaults to 0. threshold : float, optional Binarization threshold to use for cell mask. Defaults to 0.5. cmap : matplotlib.colors.Colormap, optional The colormap to use. Defaults to 'gray' alpha : float, optional Transparency value to change opacity of mask. Defaults to 0.5. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The title for the plot. Defaults to None. """ assert os.path.exists(sarc_obj.file_cell_mask), ('Cell mask not found. Run predict_cell_mask first.') img = tifffile.imread(sarc_obj.file_cell_mask, key=frame) > threshold ax.imshow(img, cmap=cmap, alpha=alpha) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize)
[docs] @staticmethod def plot_z_segmentation(ax: Axes, sarc_obj: Structure, frame=0, scalebar=True, shuffle=True, title=None, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots the Z-band segmentation result of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. shuffle : bool, optional Whether to shuffle the labels. Defaults to True. title : str, optional The title for the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert 'z_labels' in sarc_obj.data, 'Z-bands not yet analyzed. Run analyze_z_bands first.' assert frame in sarc_obj.data['params.analyze_z_bands.frames'], f'Frame {frame} not yet analyzed.' labels = sarc_obj.data['z_labels'][frame].toarray() if shuffle: labels = Utils.shuffle_labels(labels) masked_labels = np.ma.masked_array(labels, mask=(labels == 0)) cmap = plt.cm.prism cmap.set_bad(color=(0, 0, 0, 0)) # Set color for masked values to transparent ax.imshow(masked_labels, cmap=cmap) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) ax_inset.imshow(masked_labels[y1:y2, x1:x2], cmap=cmap) ax_inset.set_xticks([]) ax_inset.set_yticks([]) if scalebar: ax_inset.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='k')
[docs] @staticmethod def plot_z_lateral_connections(ax: Axes, sarc_obj: Structure, frame=0, scalebar=True, markersize=1.5, markersize_inset=3, linewidth=0.25, linewidth_inset=0.5, plot_groups=True, shuffle=True, title=None, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots lateral Z-band connections of a Structure object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure object to plot. frame : int, optional The frame to plot. Defaults to 0. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. markersize : int, optional The size of the markers of the Z-band ends. Defaults to 5. markersize_inset : int, optional The size of the markers of the Z-band ends in the inset plot. Defaults to 5. linewidth : int, optional The width of the connection lines. Defaults to 0.25. linewidth : int, optional The width of the connection lines in the inset plot. Defaults to 0.5. plot_groups : bool Whether to show the Z-bands of each lateral group with the same color. Defaults to True. shuffle : bool, optional Whether to shuffle the labels. Defaults to True. title : str, optional The title for the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert 'z_labels' in sarc_obj.data, 'Z-bands not yet analyzed. Run analyze_z_bands first.' assert frame in sarc_obj.data['params.analyze_z_bands.frames'], f'Frame {frame} not yet analyzed.' labels = sarc_obj.data['z_labels'][frame].toarray() if plot_groups: groups = sarc_obj.data['z_lat_groups'][frame] labels_plot = np.zeros_like(labels) for i, group in enumerate(groups[1:]): mask = np.zeros_like(labels, dtype=bool) for label in group: mask += (labels == label + 1) labels_plot[mask] = i + 1 else: labels_plot = labels if shuffle: labels_plot = Utils.shuffle_labels(labels_plot) z_ends = sarc_obj.data['z_ends'][frame].astype('float32') / sarc_obj.metadata['pixelsize'] z_links = sarc_obj.data['z_lat_links'][frame] masked_labels = np.ma.masked_where(labels_plot == 0, labels_plot) cmap = plt.cm.prism cmap.set_bad(color=(0, 0, 0, 0)) ax.imshow(masked_labels, cmap=cmap) for (i, k, j, l) in z_links.T: ax.plot([z_ends[i, k, 1], z_ends[j, l, 1]], [z_ends[i, k, 0], z_ends[j, l, 0]], c='k', lw=linewidth, linestyle='-', alpha=1, zorder=2) ax.scatter(z_ends[:, 0, 1], z_ends[:, 0, 0], c='k', marker='.', s=markersize, zorder=3, edgecolors='none') ax.scatter(z_ends[:, 1, 1], z_ends[:, 1, 0], c='k', marker='.', s=markersize, zorder=3, edgecolors='none') if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) ax_inset.imshow(masked_labels, cmap=cmap) ax_inset.set_xticks([]) ax_inset.set_yticks([]) for (i, k, j, l) in z_links.T: ax_inset.plot([z_ends[i, k, 1], z_ends[j, l, 1]], [z_ends[i, k, 0], z_ends[j, l, 0]], c='k', lw=linewidth_inset, linestyle='-', alpha=0.8, zorder=2) ax_inset.scatter(z_ends[:, 0, 1], z_ends[:, 0, 0], c='k', marker='.', s=markersize_inset, zorder=3, edgecolors='none') ax_inset.scatter(z_ends[:, 1, 1], z_ends[:, 1, 0], c='k', marker='.', s=markersize_inset, zorder=3, edgecolors='none') ax_inset.set_xlim(x1, x2) ax_inset.set_ylim(y2, y1) if scalebar: ax_inset.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='k')
[docs] @staticmethod def plot_sarcomere_orientation_field(ax1: Axes, ax2: Axes, sarc_obj: Structure, frame=0, cmap='vanimo', scalebar=True, colorbar=True, shrink_colorbar=0.7, orient_colorbar='vertical', zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4),): """ Plots sarcomere orientation field of the sarcomere object. Parameters ---------- ax1 : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : object The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. colorbar : bool, optional Whether to add a colorbar to the plot. Defaults to True. shrink_colorbar : float, optional The factor by which to shrink the colorbar. Defaults to 0.7. orient_colorbar : str, optional The orientation of the colorbar ('horizontal' or 'vertical'). Defaults to 'vertical'. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert os.path.exists( sarc_obj.file_orientation), 'Sarcomere orientation map does not exist! Run predict_sarcomeres first.' orientation_field = tifffile.imread(sarc_obj.file_orientation, key=[frame * 2, frame * 2 + 1]) plot1 = ax1.imshow(orientation_field[0], cmap=cmap) plot2 = ax2.imshow(orientation_field[1], cmap=cmap) if scalebar: ax1.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax2.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax1.set_xticks([]) ax1.set_yticks([]) ax2.set_xticks([]) ax2.set_yticks([]) if colorbar: plt.colorbar(plot1, ax=ax1, label=r'X-Field', shrink=shrink_colorbar, orientation=orient_colorbar) plt.colorbar(plot2, ax=ax2, label=r'Y-Field', shrink=shrink_colorbar, orientation=orient_colorbar) if scalebar: ax1.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax2.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset1 = ax1.inset_axes(bounds=inset_bounds) ax_inset2 = ax2.inset_axes(bounds=inset_bounds) ax_inset1.imshow(orientation_field[0][y1:y2, x1:x2], cmap=cmap) ax_inset2.imshow(orientation_field[1][y1:y2, x1:x2], cmap=cmap) ax_inset1.set_xticks([]) ax_inset1.set_yticks([]) ax_inset2.set_xticks([]) ax_inset2.set_yticks([]) PlotUtils.change_color_spines(ax_inset1, c='w') PlotUtils.change_color_spines(ax_inset2, c='w') # Mark the zoomed region on the main plot PlotUtils.plot_box(ax1, xlim=(x1, x2), ylim=(y1, y2), c='w') PlotUtils.plot_box(ax2, xlim=(x1, x2), ylim=(y1, y2), c='w') if scalebar: ax_inset1.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax_inset2.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='w', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1}))
[docs] @staticmethod def plot_sarcomere_mask(ax: Axes, sarc_obj: Structure, frame=0, cmap='viridis', threshold=0.1, show_z_bands=False, alpha=0.5, cmap_z_bands='gray', alpha_z_bands=1, clip_thrs=(1, 99.9), title=None, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots binary mask of sarcomeres, derived from sarcomere vectors. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. cmap : str, optional The colormap to use. Defaults to 'viridis' show_z_bands : bool, optional Whether to show Z-bands. If False, the raw image is shown. Defaults to False. alpha : float, optional The transparency of sarcomere mask. Defaults to 0.5. cmap_z_bands : bool, optional Colormap for Z-bands. Defaults to 'gray'. alpha_z_bands : float, optional Alpha value of Z-bands. Defaults to 1. clip_thrs : tuple of float, optional Clipping threshold for image in background. Defaults to (1, 99.9). Only if show_z_bands is False. title : str, optional The title for the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert os.path.exists(sarc_obj.file_sarcomere_mask), ('No sarcomere masks stored. ' 'Run sarc_obj.analyze_sarcomere_vectors ') if show_z_bands: Plots.plot_z_bands(ax, sarc_obj, alpha=alpha_z_bands, frame=frame) else: Plots.plot_image(ax, sarc_obj, frame=frame, clip_thrs=clip_thrs) sarcomere_mask = tifffile.imread(sarc_obj.file_sarcomere_mask, key=frame) # binarize sarcomere mask if threshold is None: threshold = sarc_obj.data.get('params.analyze_sarcomere_vectors.threshold_sarcomere_mask') sarcomere_mask = sarcomere_mask > threshold # plot sarcomere mask sarcomere_mask = np.ma.masked_where(sarcomere_mask == 0, sarcomere_mask) cmap = plt.get_cmap(cmap) cmap.set_bad(color=(0, 0, 0, 0)) ax.imshow(sarcomere_mask, vmin=0, vmax=1, alpha=alpha, cmap=cmap) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) if show_z_bands: Plots.plot_z_bands(ax_inset, sarc_obj, alpha=alpha_z_bands, cmap=cmap_z_bands, frame=frame) else: Plots.plot_image(ax_inset, sarc_obj, frame=frame, clip_thrs=clip_thrs) ax_inset.set_ylim(y2, y1) ax_inset.set_xlim(x1, x2) ax_inset.set_xticks([]) ax_inset.set_yticks([]) ax_inset.imshow(sarcomere_mask, vmin=0, vmax=1, alpha=alpha, cmap=cmap) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='w') PlotUtils.change_color_spines(ax_inset, 'w')
[docs] @staticmethod def plot_sarcomere_vectors(ax: Axes, sarc_obj: Structure, frame=0, color_arrows='k', color_points='darkgreen', s_points=0.5, linewidths=0.5, s_points_inset=0.5, linewidths_inset=0.5, scalebar=True, legend=False, show_image=False, cmap_z_bands='Purples', alpha_z_bands=1, title=None, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots quiver plot reflecting local sarcomere length and orientation based on sarcomere vector analysis result of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. color_arrows : str, optional The color of the arrows. Defaults to 'mediumpurple'. color_points : str, optional The color of the points. Defaults to 'darkgreen'. s_points : float, optional The size of midline points. Defaults to 0.5. linewidths : float, optional The width of the arrow lines. Defaults to 0.0005. s_points_inset : float, optional The size of midline points. Defaults to 0.5. linewidths_inset : float, optional The width of the arrow lines in the inset plot. Defaults to 0.0001. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. legend : bool, optional Whether to add a legend to the plot. Defaults to False. show_image : bool, optional Whether to show the image (True) or the Z-bands (False). Defaults to False. cmap_z_bands : str, optional Colormap of Z-bands. Defaults to 'Greys'. alpha_z_bands : float, optional Alpha value of Z-bands. Defaults to 1. title : str, optional The title for the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert 'pos_vectors' in sarc_obj.data.keys(), ('Sarcomere vectors not yet calculated, ' 'run analyze_sarcomere_vectors first.') assert frame in sarc_obj.data['params.analyze_sarcomere_vectors.frames'], f'Frame {frame} not yet analyzed.' pos_vectors = sarc_obj.data['pos_vectors'][frame] / sarc_obj.metadata['pixelsize'] sarcomere_orientation_vectors = sarc_obj.data['sarcomere_orientation_vectors'][frame] sarcomere_length_vectors = sarc_obj.data['sarcomere_length_vectors'][frame] / sarc_obj.metadata[ 'pixelsize'] orientation_vectors = np.asarray( [np.cos(sarcomere_orientation_vectors), -np.sin(sarcomere_orientation_vectors)]) if show_image: Plots.plot_image(ax, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) else: Plots.plot_z_bands(ax, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) ax.plot([0, 1], [0, 1], c='k', label='Z-bands', lw=0.5) # adjust sarcomere lengths to appear correct in quiver plot half_length = sarcomere_length_vectors * 0.5 headaxislength = 4 ax.quiver(pos_vectors[:, 1], pos_vectors[:, 0], -orientation_vectors[0] * half_length, orientation_vectors[1] * half_length, width=linewidths, headaxislength=headaxislength, units='xy', angles='xy', scale_units='xy', scale=1, color=color_arrows, alpha=0.5, label='Sarcomere vectors') ax.quiver(pos_vectors[:, 1], pos_vectors[:, 0], orientation_vectors[0] * half_length, -orientation_vectors[1] * half_length, headaxislength=headaxislength, units='xy', angles='xy', scale_units='xy', scale=1, color=color_arrows, alpha=0.5, width=linewidths) ax.scatter(pos_vectors[:, 1], pos_vectors[:, 0], marker='.', c=color_points, edgecolors='none', s=s_points * 0.5, label='Midline pos_vectors') if legend: ax.legend(loc=3, fontsize=PlotUtils.fontsize - 2) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: linewidths *= 10 x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) if show_image: Plots.plot_image(ax_inset, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) else: Plots.plot_z_bands(ax_inset, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) ax_inset.plot([0, 1], [0, 1], c='k', label='Z-bands', lw=0.5) ax_inset.scatter(pos_vectors[:, 1], pos_vectors[:, 0], marker='.', c=color_points, edgecolors='none', s=s_points_inset, label='Midline points') ax_inset.quiver(pos_vectors[:, 1], pos_vectors[:, 0], -orientation_vectors[0] * half_length, orientation_vectors[1] * half_length, width=linewidths_inset, headaxislength=headaxislength, units='xy', angles='xy', scale_units='xy', scale=1, color=color_arrows, alpha=0.5, label='Sarcomere vectors') ax_inset.quiver(pos_vectors[:, 1], pos_vectors[:, 0], orientation_vectors[0] * half_length, -orientation_vectors[1] * half_length, headaxislength=headaxislength, units='xy', angles='xy', scale_units='xy', scale=1, color=color_arrows, alpha=0.5, width=linewidths_inset) ax_inset.set_xlim(x1, x2) ax_inset.set_ylim(y2, y1) ax_inset.set_xticks([]) ax_inset.set_yticks([]) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='k') if scalebar: ax_inset.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1, }))
[docs] @staticmethod def plot_sarcomere_domains(ax: Axes, sarc_obj: Structure, frame=0, alpha=0.5, cmap='gist_rainbow', scalebar=True, plot_raw_data=False, cmap_z_bands='Greys', alpha_z_bands=1, title=None): """ Plots the sarcomere domains of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. alpha : float, optional The transparency of the domain masks. Defaults to 0.3. cmap : str, optional The colormap to use. Defaults to 'gist_rainbow'. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. plot_raw_data : bool, optional Whether to plot the raw data. Defaults to False. cmap_z_bands : str, optional Colormap for Z-bands. Defaults to 'Greys'. alpha_z_bands : float, optional Transparency of Z-bands. Defaults to 1. title : str, optional The title for the plot. Defaults to None. """ assert 'n_domains' in sarc_obj.data.keys(), ('Sarcomere domains not analyzed. ' 'Run analyze_sarcomere_domains first.') assert frame in sarc_obj.data['params.analyze_sarcomere_domains.frames'], (f'Domains in frame {frame} are not yet ' f'analyzed.') domains = sarc_obj.data['domains'][frame] pos_vectors = sarc_obj.data['pos_vectors'][frame] sarcomere_orientation_vectors = sarc_obj.data['sarcomere_orientation_vectors'][frame] sarcomere_length_vectors = sarc_obj.data['sarcomere_length_vectors'][frame] area_min = sarc_obj.data['params.analyze_sarcomere_domains.area_min'] dilation_radius = sarc_obj.data['params.analyze_sarcomere_domains.dilation_radius'] domain_mask = sarc_obj._analyze_domains(domains, pos_vectors=pos_vectors, sarcomere_length_vectors=sarcomere_length_vectors, sarcomere_orientation_vectors=sarcomere_orientation_vectors, size=sarc_obj.metadata['size'], pixelsize=sarc_obj.metadata['pixelsize'], dilation_radius=dilation_radius, area_min=area_min)[0] domain_mask_masked = np.ma.masked_where(domain_mask == 0, domain_mask) cmap = plt.get_cmap(cmap) cmap.set_bad(color=(0, 0, 0, 0)) if plot_raw_data: Plots.plot_image(ax, sarc_obj, frame=frame, scalebar=False, alpha=alpha_z_bands, cmap=cmap_z_bands) else: Plots.plot_z_bands(ax, sarc_obj, cmap=cmap_z_bands, alpha=alpha_z_bands, frame=frame, scalebar=False) ax.imshow(domain_mask_masked, cmap=cmap, alpha=alpha, vmin=0, vmax=np.nanmax(domain_mask)) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_title(title, fontsize=PlotUtils.fontsize)
[docs] @staticmethod def plot_myofibril_lines(ax: Axes, sarc_obj: Structure , frame=0, show_z_bands=True, linewidth=1, color_lines='r', linewidth_inset=3, alpha=0.2, cmap_z_bands='Greys', alpha_z_bands=1, scalebar=True, title=None, zoom_region=None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots result of myofibril line growth algorithm of the sarcomere object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure or Motion The sarcomere object to plot. frame : int, optional The frame to plot. Defaults to 0. show_z_bands : bool Whether or not to show Z-bands. Defaults to True linewidth : float, optional The width of the lines. Defaults to 1. color_lines : str Color of lines. Defaults to 'r' linewidth_inset : float, optional Thickness of the lines in inset. Defaults to 1. alpha : float, optional The transparency of the lines. Defaults to 0.2. cmap_z_bands : str, optional Colormap of Z-bands. Defaults to 'Greys'. alpha_z_bands : float, optional Transparency of Z-bands. Defaults to 1. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The titlefor the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ assert 'myof_lines' in sarc_obj.data.keys(), ('Myofibrils not analyzed. ' 'Run analyze_myofibrils first.') assert frame in sarc_obj.data['params.analyze_myofibrils.frames'], f'Frame {frame} not yet analyzed.' if show_z_bands: Plots.plot_z_bands(ax, sarc_obj, cmap=cmap_z_bands, frame=frame, alpha=alpha_z_bands) else: Plots.plot_image(ax, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) lines = sarc_obj.data['myof_lines'][frame] pos_vectors = sarc_obj.data['pos_vectors_px'][frame] if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) for i, line_i in enumerate(lines): ax.plot(pos_vectors[line_i, 1], pos_vectors[line_i, 0], c=color_lines, alpha=alpha, lw=linewidth) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) if show_z_bands: Plots.plot_z_bands(ax_inset, sarc_obj, cmap=cmap_z_bands, frame=frame) else: Plots.plot_image(ax_inset, sarc_obj, frame=frame, cmap=cmap_z_bands) if scalebar: ax_inset.add_artist( ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) for i, line_i in enumerate(lines): ax_inset.plot(pos_vectors[line_i, 1], pos_vectors[line_i, 0], c='r', alpha=alpha, lw=linewidth_inset) ax_inset.set_xlim(x1, x2) ax_inset.set_ylim(y2, y1) ax_inset.set_xticks([]) ax_inset.set_yticks([]) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='k')
[docs] @staticmethod def plot_myofibril_length_map(ax: Axes, sarc_obj: Structure, frame=0, vmax=None, alpha=1, show_z_bands=False, cmap_z_bands='Greys', alpha_z_bands=1, colorbar=True, shrink_colorbar=0.7, orient_colorbar='vertical', scalebar=True, title=None, zoom_region: Tuple[int, int, int, int] = None, inset_bounds=(0.6, 0.6, 0.4, 0.4)): """ Plots the spatial map of myofibril lengths for a given frame. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The instance of Structure class to plot. frame : int, optional The frame to plot. Defaults to 0. vmax : float, optional Maximum value for the colormap. If None, the maximum value in the data is used. Defaults to None. alpha : float, optional Transparency of the colormap. Defaults to 1. show_z_bands : bool, optional Whether to show Z-band mask, else raw image is shown. Defaults to False. cmap_z_bands : str, optional Colormap of Z-bands. Defaults to 'Greys'. alpha_z_bands : float, optional Transparency of Z-bands or raw image. Defaults to 1. colorbar : bool, optional Whether to show the colorbar. Defaults to True. shrink_colorbar: float, optional Shrinkage of the colorbar. Defaults to 0.7. orient_colorbar : str, optional Orientation of the colorbar. Defaults to 'vertical'. scalebar : bool, optional Whether to add a scalebar to the plot. Defaults to True. title : str, optional The title for the plot. Defaults to None. zoom_region : tuple of int, optional The region to zoom in on, specified as (x1, x2, y1, y2). Defaults to None. inset_bounds : tuple of float, optional Bounds of inset axis, specified as (x0, y0, width, height). Defaults to (0.6, 0.6, 0.4, 0.4). """ # create myofibril length map assert 'myof_lines' in sarc_obj.data.keys(), ('Myofibrils not yet analyzed. ' 'Run analyze_myofibrils first.') assert frame in sarc_obj.data['params.analyze_myofibrils.frames'], f'Frame {frame} not yet analyzed.' myof_lines = sarc_obj.data['myof_lines'][frame] myof_lengths = sarc_obj.data['myof_length'][frame] pos_vectors = sarc_obj.data['pos_vectors'][frame] orientation_vectors = sarc_obj.data['sarcomere_orientation_vectors'][frame] length_vectors = sarc_obj.data['sarcomere_length_vectors'][frame] median_filter_radius = sarc_obj.data['params.analyze_myofibrils.median_filter_radius'] myof_length_map = sarc_obj.create_myofibril_length_map(myof_lines=myof_lines, myof_length=myof_lengths, pos_vectors=pos_vectors, sarcomere_orientation_vectors=orientation_vectors, sarcomere_length_vectors=length_vectors, size=sarc_obj.metadata['size'], pixelsize=sarc_obj.metadata['pixelsize'], median_filter_radius=median_filter_radius) if show_z_bands: Plots.plot_z_bands(ax, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) else: Plots.plot_image(ax, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) masked_myof_length_map = np.ma.masked_array(myof_length_map, mask=(myof_length_map == 0)) cmap = plt.cm.inferno cmap.set_bad(color=(0, 0, 0, 0)) # Set color for masked values to transparent vmin, vmax = 0, np.nanmax(myof_length_map) if vmax is None else vmax plot = ax.imshow(masked_myof_length_map, cmap=cmap, vmin=vmin, vmax=vmax, alpha=alpha) if scalebar: ax.add_artist(ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) if colorbar: cbar = plt.colorbar(mappable=plot, ax=ax, shrink=shrink_colorbar, orientation=orient_colorbar, label='Myofibril length [µm]') ax.set_xticks([]) ax.set_yticks([]) ax.set_title(title, fontsize=PlotUtils.fontsize) # Add inset axis if zoom_region is specified if zoom_region: x1, x2, y1, y2 = zoom_region ax_inset = ax.inset_axes(bounds=inset_bounds) if show_z_bands: Plots.plot_z_bands(ax_inset, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) else: Plots.plot_image(ax_inset, sarc_obj, frame=frame, cmap=cmap_z_bands, alpha=alpha_z_bands) ax_inset.imshow(masked_myof_length_map, cmap=cmap, alpha=alpha, vmin=vmin, vmax=vmax) ax_inset.set_xticks([]) ax_inset.set_yticks([]) ax_inset.set_xlim(x1, x2) ax_inset.set_ylim(y2, y1) if scalebar: ax_inset.add_artist( ScaleBar(sarc_obj.metadata['pixelsize'], units='µm', frameon=False, color='k', sep=1, height_fraction=0.02, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) # Mark the zoomed region on the main plot PlotUtils.plot_box(ax, xlim=(x1, x2), ylim=(y1, y2), c='k')
[docs] @staticmethod def plot_lois(ax: Axes, sarc_obj: Union[Structure, Motion], color='darkorange', linewidth=2, alpha=0.5): """ Plot all LOI lines for Structure object and LOI line Motion object. Parameters ---------- ax : matplotlib axis Axis on which to plot the LOI lines sarc_obj : Structure or Motion Object of Structure or Motion class color : str Color of lines linewidth : float Width of lines alpha : float Transparency of lines """ loi_lines = None if hasattr(sarc_obj, 'loi_data'): # Extract line data directly from sarc_obj.loi_data loi_lines = [sarc_obj.loi_data['line']] elif hasattr(sarc_obj, 'data') and 'loi_data' in sarc_obj.data: # Extract lines from sarc_obj.data['loi_data'] loi_lines = sarc_obj.data['loi_data'].get('loi_lines', []) if loi_lines is not None: # Plot each line for line in loi_lines: ax.plot(line.T[1], line.T[0], color=color, linewidth=linewidth, alpha=alpha) else: # Raise a warning if no LOI lines are found warnings.warn("No LOI lines found in the provided object.", UserWarning)
[docs] @staticmethod def plot_histogram_structure(ax: Axes, sarc_obj: Structure, feature: str, frame: int = 0, bins: int = 20, density: bool = False, range: Optional[tuple] = None, label: Optional[str] = None, ylabel: Optional[str] = None, rwidth: float = 0.6, color: str = 'darkslategray', edge_color: str = 'k', align: Literal['mid', 'left', 'right'] = 'mid', rotate_yticks: bool = False) -> None: """ Plots the histogram of a specified structural feature from a sarcomere object on a given Axes. Parameters ---------- ax : matplotlib.axes.Axes The axes on which to draw the histogram. sarc_obj : Structure The instance of Structure class to plot. feature : str The name of the structural feature to plot. frame : int, optional The frame index from which to extract the data. Defaults to 0. bins : int, optional The number of bins for the histogram. Defaults to 20. density : bool, optional If True, the histogram is normalized to show the probability density rather than raw counts. Defaults to False. range : tuple, optional The lower and upper range of the bins. If not provided, the range is determined from the data. label : str, optional The label for the x-axis. If not specified, a default label based on the feature will be used. ylabel : str, optional The label for the y-axis. Overrides the default label if provided. rwidth : float, optional The relative width of the histogram bars. Defaults to 0.7. color : str, optional The fill color of the histogram bars. Defaults to 'darkslategray'. edge_color : str, optional The color of the edges of the histogram bars. Defaults to 'k'. align : str, optional The alignment of the histogram bars. Defaults to 'mid'. rotate_yticks : bool, optional If True, rotates the y-axis tick labels by 90 degrees for improved readability. Defaults to False. """ data = sarc_obj.data[feature][frame] # Flatten data if it has more than one dimension if data.ndim > 1: data = data.flatten() # Remove NaN values from the data data = data[~np.isnan(data)] ax.hist( data, bins=bins, density=density, range=range, rwidth=rwidth, color=color, edgecolor=edge_color, align=align ) # Use a default label if none is provided if label is None: label = structure_feature_dict.get(feature, {}).get('name', feature) ax.set_xlabel(label) # Set y-axis label based on whether density is True ax.set_ylabel('Frequency' if density else 'Count') if ylabel is not None: ax.set_ylabel(ylabel) if rotate_yticks: ax.tick_params(axis='y', labelrotation=90) plt.setp(ax.get_yticklabels(), va='center') PlotUtils.remove_spines(ax)
[docs] @staticmethod def plot_z_pos(ax: Axes, motion_obj: Motion, number_contr=None, show_contr=True, show_kymograph=False, color='k', t_lim=(None, None), y_lim=(None, None)): """ Plots the z-band trajectories of the motion object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. motion_obj : Motion The motion object to plot. show_contr : bool, optional Whether to show the contractions. Defaults to True. show_kymograph : bool, optional Whether to show the kymograph. Defaults to False. color : str, optional The color of the plot. Defaults to 'k'. t_lim : tuple, optional The time limits for the plot. Defaults to (None, None). y_lim : tuple, optional The y limits for the plot. Defaults to (None, None). """ # plot limits and params if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: start_contr_t = motion_obj.loi_data['start_contr'][number_contr] tlim = (start_contr_t + t_lim[0], start_contr_t + t_lim[1]) idxlim = (int(tlim[0] / motion_obj.metadata['frametime']), int(tlim[1] / motion_obj.metadata['frametime'])) else: tlim, idxlim = (None, None), (None, None) if show_kymograph: ax.pcolorfast(motion_obj.loi_data['time'], motion_obj.loi_data['x_pos'], motion_obj.loi_data['y_int'].T, cmap='Greys') # get data time = motion_obj.loi_data['time'] z_pos = motion_obj.loi_data['z_pos'] # plot contraction cycles if show_contr: for start_i, time_i in zip(motion_obj.loi_data['start_contr'], motion_obj.loi_data['time_contr']): end_i = start_i + time_i if number_contr is not None: start_i -= tlim[0] end_i -= tlim[0] ax.fill_betweenx([0, 1], [start_i, start_i], [end_i, end_i], color='lavender', transform=transforms.blended_transform_factory(ax.transData, ax.transAxes)) # plot trajectories if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: ax.plot(time[:idxlim[1] - idxlim[0]], z_pos[:, idxlim[0]:idxlim[1]], linewidth=0.75, c=color) ax.set_xlim(0, tlim[1] - tlim[0]) else: ax.plot(time, z_pos.T, linewidth=0.75, c=color) ax.set_xlim(t_lim) ax.set_xlabel('Time [s]') ax.set_ylabel('Z-band position Z(t) [µm]') if y_lim == (None, None): ax.set_ylim(0, None) else: ax.set_ylim(y_lim) PlotUtils.polish_yticks(ax, 5, 2.5) PlotUtils.polish_xticks(ax, 2, 1)
[docs] @staticmethod def plot_delta_slen(ax: Axes, motion_obj: Motion, frame=None, t_lim=(0, 12), y_lim=(-0.3, 0.4), n_rows=6, n_start=1, show_contr=True): """ Plots the change in sarcomere length over time for a motion object. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. motion_obj : Motion The motion object to plot. frame : int or None, optional Show frame with vertical dashed line, in frames. Defaults to None. t_lim : tuple, optional The time limits for the plot. Defaults to (0, 12). y_lim : tuple, optional The y limits for the plot. Defaults to (-0.3, 0.4). n_rows : int, optional The number of rows for the plot. Defaults to 6. n_start : int, optional The starting index for the plot. Defaults to 1. show_contr : bool, optional Whether to show the systoles. Defaults to True. """ yticks = [-0.2, 0, 0.2] delta_slen = motion_obj.loi_data['delta_slen'] list_y = np.linspace(0, 1, num=n_rows, endpoint=False) for i, y in enumerate(list_y): ax_i = ax.inset_axes((0., y, 1, 1 / n_rows - 0.02)) ax_i.plot(motion_obj.loi_data['time'], delta_slen[i + n_start], c='k', lw=0.6) ax_i.axhline(0, linewidth=1, linestyle=':', c='k') if show_contr: for start_i, time_i in zip(motion_obj.loi_data['start_contr'], motion_obj.loi_data['time_contr']): end_i = start_i + time_i ax_i.fill_betweenx([-1, 1], [start_i, start_i], [end_i, end_i], color='lavender') if i > 0: ax_i.set_xticks([]) else: PlotUtils.polish_xticks(ax_i, 1, 0.5) if frame is not None: ax_i.axvline(motion_obj.loi_data['time'][frame], linestyle='--', c='k') ax_i.set_ylim(y_lim) ax_i.set_xlim(t_lim) ax_i.set_yticks(yticks) ax_i.set_yticklabels(yticks, fontsize='x-small') ax.set_xlabel('Time [s]') ax.set_ylabel('$\Delta$SL [µm]') ax.spines['bottom'].set_color('w') ax.spines['top'].set_color('w') ax.xaxis.label.set_color('k') ax.tick_params(axis='x', colors='w') ax.tick_params(axis='y', colors='w')
[docs] @staticmethod def plot_overlay_delta_slen(ax: Axes, motion_obj: Motion, number_contr=None, t_lim=(0, 1), y_lim=(-0.35, 0.5), show_contr=True): """ Plots the sarcomere length change over time for a motion object, overlaying multiple trajectories. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. motion_obj : Motion The motion object to plot. number_contr : int, optional The number of contractions to overlay. If None, all contractions are overlaid. Defaults to None. t_lim : tuple, optional The time limits for the plot. Defaults to (0, 1). y_lim : tuple, optional The y limits for the plot. Defaults to (-0.35, 0.45). show_contr : bool, optional Whether to show the contractions. Defaults to True. """ # plot limits and params if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: start_contr_t = motion_obj.loi_data['start_contr'][number_contr] tlim = (start_contr_t + t_lim[0], start_contr_t + t_lim[1]) idxlim = (int(tlim[0] / motion_obj.metadata['frametime']), int(tlim[1] / motion_obj.metadata['frametime'])) else: tlim, idxlim = (None, None), (None, None) # get data time = motion_obj.loi_data['time'] delta_slen = motion_obj.loi_data['delta_slen'] delta_slen_avg = motion_obj.loi_data['delta_slen_avg'] # plot contraction cycles if show_contr: for start_i, time_i in zip(motion_obj.loi_data['start_contr'], motion_obj.loi_data['time_contr']): end_i = start_i + time_i if number_contr is not None: start_i -= tlim[0] end_i -= tlim[0] ax.fill_betweenx([0, 1], [start_i, start_i], [end_i, end_i], color='lavender', transform=transforms.blended_transform_factory(ax.transData, ax.transAxes)) # colormap cm = plt.cm.nipy_spectral(np.linspace(0, 1, len(delta_slen))) ax.set_prop_cycle('color', list(cm)) # plot single and average trajectories if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: ax.plot(time[:idxlim[1] - idxlim[0]], delta_slen.T[idxlim[0]:idxlim[1]], linewidth=0.5) ax.plot(time[:idxlim[1] - idxlim[0]], delta_slen_avg[idxlim[0]:idxlim[1]], c='k', linewidth=2, linestyle='-') ax.set_xlim(0, tlim[1] - tlim[0]) else: ax.plot(time, delta_slen.T, linewidth=0.5) ax.plot(time, delta_slen_avg, c='k', linewidth=2, linestyle='-') ax.set_xlim(t_lim) ax.set_xlabel('Time [s]') ax.set_ylabel('$\Delta$SL [µm]') ax.set_ylim(y_lim) PlotUtils.polish_yticks(ax, 0.2, 0.1) PlotUtils.polish_xticks(ax, 0.5, 0.25)
[docs] @staticmethod def plot_overlay_velocity(ax, motion_obj: Motion, number_contr=None, t_lim=(0, 0.9), y_lim=(-9, 12), show_contr=True): """ Plots overlay of sarcomere velocity time series of the motion object Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. motion_obj : Motion The motion object to plot. number_contr : int, optional The number of contractions to overlay. If None, all contractions are overlaid. Defaults to None. t_lim : tuple, optional The time limits for the plot. Defaults to (0, 0.9). y_lim : tuple, optional The y limits for the plot. Defaults to (-7, 10). show_contr : bool, optional Whether to show the contractions. Defaults to True. """ # plot limits and params if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: start_contr_t = motion_obj.loi_data['start_contr'][number_contr] tlim = (start_contr_t + t_lim[0], start_contr_t + t_lim[1]) idxlim = (int(tlim[0] / motion_obj.metadata['frametime']), int(tlim[1] / motion_obj.metadata['frametime'])) else: tlim, idxlim = (None, None), (None, None) # get data time = motion_obj.loi_data['time'] vel = motion_obj.loi_data['vel'] vel_avg = motion_obj.loi_data['vel_avg'] # plot contraction cycles if show_contr: for start_i, time_i in zip(motion_obj.loi_data['start_contr'], motion_obj.loi_data['time_contr']): end_i = start_i + time_i if number_contr is not None: start_i -= tlim[0] end_i -= tlim[0] ax.fill_betweenx([0, 1], [start_i, start_i], [end_i, end_i], color='lavender', transform=transforms.blended_transform_factory(ax.transData, ax.transAxes)) # colormap cm = plt.cm.nipy_spectral(np.linspace(0, 1, len(vel))) ax.set_prop_cycle('color', list(cm)) # plot single and average trajectories if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: ax.plot(time[:idxlim[1] - idxlim[0]], vel.T[idxlim[0]:idxlim[1]], linewidth=0.5) ax.plot(time[:idxlim[1] - idxlim[0]], vel_avg[idxlim[0]:idxlim[1]], c='k', linewidth=2, linestyle='-') ax.set_xlim(0, tlim[1] - tlim[0]) else: ax.plot(time, vel.T, linewidth=0.5) ax.plot(time, vel_avg, c='k', linewidth=2, linestyle='-') ax.set_xlim(0, time.max()) ax.set_xlabel('Time [s]') ax.set_ylabel('V [µm/s]') ax.set_ylim(y_lim) ax.yaxis.set_major_locator(MultipleLocator(3)) ax.yaxis.set_major_formatter(FormatStrFormatter('%g')) ax.yaxis.set_minor_locator(MultipleLocator(1)) ax.xaxis.set_major_locator(MultipleLocator(0.5)) ax.xaxis.set_major_formatter(FormatStrFormatter('%g')) ax.xaxis.set_minor_locator(MultipleLocator(0.25))
[docs] @staticmethod def plot_phase_space(ax: Axes, motion_obj: Motion, t_lim=(0, 4), number_contr=None, frame=None): """ Plots sarcomere trajectory in length-change velocity phase space Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. motion_obj : Motion The motion object to plot. t_lim : tuple, optional The time limits for the plot. Defaults to (0, 4). number_contr : int, optional The number of contractions to overlay. If None, all contractions are overlaid. Defaults to None. frame : int, optional The frame number to plot the individual sarcomeres in phase space. Defaults to None. """ # get data delta_slen = motion_obj.loi_data['delta_slen'] vel = motion_obj.loi_data['vel'] delta_slen_avg = motion_obj.loi_data['delta_slen_avg'] vel_avg = motion_obj.loi_data['vel_avg'] # colormap cm = plt.cm.nipy_spectral(np.linspace(0, 1, len(delta_slen))) ax.set_prop_cycle('color', list(cm)) # plot limits and params if number_contr is not None and motion_obj.loi_data['n_contr'] > 0: start_contr_t = motion_obj.loi_data['start_contr'][number_contr] tlim = (start_contr_t + t_lim[0], start_contr_t + t_lim[1]) idxlim = (int(tlim[0] / motion_obj.metadata['frametime']), int(tlim[1] / motion_obj.metadata['frametime'])) else: tlim, idxlim = (None, None), (None, None) for i, (vel_i, delta_i) in enumerate(zip(vel, delta_slen)): ax.plot(vel_i[idxlim[0]:idxlim[1]], delta_i[idxlim[0]:idxlim[1]], c='r', alpha=0.35, lw=0.2, zorder=1) if isinstance(frame, numbers.Integral): ax.scatter(vel_i[frame], delta_i[frame], c=cm[i], s=10, zorder=2) ax.plot(vel_avg[idxlim[0]:idxlim[1]], delta_slen_avg[idxlim[0]:idxlim[1]], c='k', lw=1, label='Average') legend_elements = [Line2D([0], [0], color='k', lw=2), Line2D([0], [0], color='r', alpha=0.35, lw=0.5)] ax.legend(legend_elements, ['Average', 'Individual'], loc='upper right') PlotUtils.polish_xticks(ax, 5, 2.5) PlotUtils.polish_yticks(ax, 0.2, 0.1) ax.set_xlabel('Velocity $V$ [µm/s]', fontsize=PlotUtils.fontsize) ax.set_ylabel('Length change $\Delta SL$ [µm]', fontsize=PlotUtils.fontsize)
[docs] @staticmethod def plot_popping_events(motion_obj: Motion, save_name=None): """ Create binary event map of popping events of the motion object. Parameters ---------- motion_obj : Motion The motion object to plot. save_name : str, optional The name to save the plot as. If None, the plot is not saved. Defaults to None. """ popping_events = motion_obj.loi_data['popping_events'] prob_time = motion_obj.loi_data['popping_freq_time'] prob_sarcomeres = motion_obj.loi_data['popping_freq_sarcomeres'] left, width = 0.1, 0.65 bottom, height = 0.1, 0.65 spacing = 0.02 rect_scatter = (left, bottom, width, height) rect_histx = (left, bottom + height + spacing, width, 0.2) rect_histy = (left + width + spacing, bottom, 0.2, height) fig_events = plt.figure(figsize=(PlotUtils.width_1cols * 0.9, 3.)) ax = fig_events.add_axes(rect_scatter) ax_histx = fig_events.add_axes(rect_histx, sharex=ax) ax_histy = fig_events.add_axes(rect_histy, sharey=ax) ax_histx.tick_params(axis="x", labelbottom=False) ax_histy.tick_params(axis="y", labelleft=False) ax.pcolorfast(popping_events, cmap='Greys') ax_histx.bar(np.arange(len(prob_time)) + 0.5, prob_time, color='k', alpha=0.4) ax_histy.barh(np.arange(len(prob_sarcomeres)) + 0.5, prob_sarcomeres, color='k', alpha=0.4) ax.set_xlabel('Contraction cycle [#]') ax.set_ylabel('Sarcomere [#]') yticks = np.arange(len(prob_sarcomeres)) ax.set_yticks(yticks + 0.5) ax.set_yticklabels(yticks + 1) ax_histx.set_ylabel('$f_c(P)$') ax_histy.set_xlabel('$f_s(P)$') ax.set_ylim(0, None) ax.set_xlim(0, None) ax.grid() if save_name is not None: fig_events.savefig(save_name)