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 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.structure_modules import domain_clustering, myofibril_analysis
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), file_path=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) file_path : str, optional The file path 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.file_path}, \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 file_path is None: file_path = os.path.join(motion_obj.loi_folder, 'summary_loi.png') fig.savefig(file_path, dpi=PlotUtils.dpi) plt.show()
[docs] @staticmethod def plot_loi_detection(sarc_obj: Structure, frame: int = 0, file_path: 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. file_path: 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 file_path is not None: fig.savefig(file_path, dpi=300) plt.show()
[docs] @staticmethod def plot_image(ax: Axes, sarc_obj: Union[Structure, Motion], frame: int = 0, cmap: str = 'gray', 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])) _ = 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.035, location='lower right', scale_loc='top', font_properties={'size': PlotUtils.fontsize - 1})) ax.set_xticks([]) ax.set_yticks([]) if title is not None: 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.035, 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.035, 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] @staticmethod 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). """ if not os.path.exists(sarc_obj.file_zbands): raise FileNotFoundError(f"Z-bands file not found: {sarc_obj.file_zbands}") 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, *_ = domain_clustering.analyze_domains( domains, pos_vectors, sarcomere_orientation_vectors, sarcomere_length_vectors, size=sarc_obj.metadata.size, pixelsize=sarc_obj.metadata.pixelsize, dilation_radius=dilation_radius, area_min=area_min) 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 = myofibril_analysis.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: 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]].T, 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_domain_timeseries(ax: Axes, sarc_obj: Structure, t_lim: Tuple[float, float] = (0, 12), y_lim: Tuple[float, float] = (1.6, 2.2), n_rows: Optional[int] = None, show_contr: bool = True, use_median: bool = False): """ Plots domain sarcomere length time-series in a stacked multi-subplot layout. Each domain's sarcomere length time-series is shown in a separate row, with optional contraction period shading. Similar layout to plot_delta_slen for Motion objects. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The Structure object with domain motion analysis results. t_lim : tuple of float, optional The time limits for the plot in seconds. Defaults to (0, 12). y_lim : tuple of float, optional The y-axis limits for sarcomere length in µm. Defaults to (1.6, 2.2). n_rows : int or None, optional Number of domains to display. If None, shows all domains. Defaults to None. show_contr : bool, optional Whether to shade contraction periods. Defaults to True. use_median : bool, optional If True, use median sarcomere length instead of mean. Defaults to False. Raises ------ ValueError If domain motion analysis has not been run. """ # Validate prerequisites if 'domain_slen_timeseries' not in sarc_obj.data: raise ValueError("Domain motion analysis not run. Call analyze_domain_motion() first.") # Get data if use_median: slen_timeseries = sarc_obj.data['domain_slen_median_timeseries'] else: slen_timeseries = sarc_obj.data['domain_slen_timeseries'] n_domains, n_frames = slen_timeseries.shape time = np.arange(n_frames) * sarc_obj.metadata.frametime # Determine number of rows if n_rows is None: n_rows = n_domains n_rows = min(n_rows, n_domains) # Get contraction data if available domain_contr = sarc_obj.data.get('domain_contr', None) domain_labels_contr = sarc_obj.data.get('domain_labels_contr', None) # Calculate y-ticks y_range = y_lim[1] - y_lim[0] y_step = y_range / 4 yticks = [y_lim[0] + y_step, y_lim[0] + 2 * y_step, y_lim[0] + 3 * y_step] # Domain colormap cm = plt.cm.gist_rainbow(np.linspace(0, 1, n_domains)) # Create inset axes for each domain list_y = np.linspace(0, 1, num=n_rows, endpoint=False) for i, y in enumerate(list_y): domain_idx = n_rows - 1 - i # Reverse order so domain 1 is at bottom if domain_idx >= n_domains: continue ax_i = ax.inset_axes((0., y, 1, 1 / n_rows - 0.02)) ax_i.plot(time, slen_timeseries[domain_idx], c=cm[domain_idx], lw=0.8) ax_i.axhline(np.nanmean(slen_timeseries[domain_idx]), linewidth=0.5, linestyle=':', c='k') # Shade contraction periods if show_contr and domain_contr is not None: contr = domain_contr[domain_idx] ax_i.fill_between(time, y_lim[0], y_lim[1], where=contr, color='lavender', alpha=0.7) # Configure axes if i > 0: ax_i.set_xticks([]) else: PlotUtils.polish_xticks(ax_i, 2, 1) ax_i.set_ylim(y_lim) ax_i.set_xlim(t_lim) ax_i.set_yticks(yticks) ax_i.set_yticklabels([f'{yt:.2f}' for yt in yticks], fontsize='x-small') # Add domain label ax_i.text(0.02, 0.85, f'D{domain_idx + 1}', transform=ax_i.transAxes, fontsize='x-small', fontweight='bold', color=cm[domain_idx]) # Configure main axes ax.set_xlabel('Time [s]') ax.set_ylabel('Sarcomere length [µ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_domain_timeseries(ax: Axes, sarc_obj: Structure, t_lim: Tuple[float, float] = (0, 12), y_lim: Tuple[float, float] = (1.4, 2.2), show_contr: bool = True, show_average: bool = True, use_median: bool = False, domain_indices: Optional[list] = None): """ Plots domain sarcomere length time-series as overlaid trajectories. All domain time-series are plotted on the same axes with different colors, optionally with an average line and contraction period shading. Parameters ---------- ax : matplotlib.axes.Axes The axes to draw the plot on. sarc_obj : Structure The Structure object with domain motion analysis results. t_lim : tuple of float, optional The time limits for the plot in seconds. Defaults to (0, 12). y_lim : tuple of float, optional The y-axis limits for sarcomere length in µm. Defaults to (1.6, 2.2). show_contr : bool, optional Whether to shade contraction periods (uses union of all domain contractions). Defaults to True. show_average : bool, optional Whether to show the average across all domains. Defaults to True. use_median : bool, optional If True, use median sarcomere length instead of mean. Defaults to False. domain_indices : list or None, optional List of domain indices (0-based) to plot. If None, plots all domains. Defaults to None. Raises ------ ValueError If domain motion analysis has not been run. """ # Validate prerequisites if 'domain_slen_timeseries' not in sarc_obj.data: raise ValueError("Domain motion analysis not run. Call analyze_domain_motion() first.") # Get data if use_median: slen_timeseries = sarc_obj.data['domain_slen_median_timeseries'] else: slen_timeseries = sarc_obj.data['domain_slen_timeseries'] n_domains, n_frames = slen_timeseries.shape time = np.arange(n_frames) * sarc_obj.metadata.frametime # Select domains to plot if domain_indices is None: domain_indices = list(range(n_domains)) domain_indices = [i for i in domain_indices if 0 <= i < n_domains] # Get contraction data if available domain_contr = sarc_obj.data.get('domain_contr', None) # Shade contraction periods (union across selected domains) if show_contr and domain_contr is not None: any_contr = np.any(domain_contr[domain_indices], axis=0) ax.fill_between(time, y_lim[0], y_lim[1], where=any_contr, color='lavender', alpha=0.5) # Domain colormap cm = plt.cm.gist_rainbow(np.linspace(0, 1, n_domains)) # Plot individual domain trajectories for domain_idx in domain_indices: ax.plot(time, slen_timeseries[domain_idx], c=cm[domain_idx], lw=0.8, label=f'Domain {domain_idx + 1}', alpha=0.8) # Plot average trajectory if show_average and len(domain_indices) > 1: avg_slen = np.nanmean(slen_timeseries[domain_indices], axis=0) ax.plot(time, avg_slen, c='k', lw=2, linestyle='-', label='Average') # Configure axes ax.set_xlabel('Time [s]') ax.set_ylabel('Sarcomere length [µm]') ax.set_xlim(t_lim) ax.set_ylim(y_lim) PlotUtils.polish_xticks(ax, 2, 1) PlotUtils.polish_yticks(ax, 0.2, 0.1) # Add legend ax.legend(loc='upper right', fontsize='x-small')
[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.36, 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)