Module libquantum.plot_templates.plot_time_frequency_picks

This module contains time frequency plots for picks

Expand source code
"""
This module contains time frequency plots for picks
"""

import enum
from typing import List, Tuple
import math
import numpy as np
from matplotlib.collections import QuadMesh
from matplotlib.colorbar import Colorbar
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable, AxesDivider
from dataclasses import dataclass
import redvox.common.date_time_utils as dt
from libquantum import scales


class FigureAttributes:
    """
    This is the most basic parent class -- sets the plot canvas as well as figure handling functions. This is where
    figure size, font style and sizes, line weights and colors, etc. are established. All subsequent plot classes
    will inherit these attributes, overriding them if necessary.

    Attributes
    __________

    fig_size_ratio: 2d array of figure width, height ratio
    fontsize1_scale: int, scale for fontsize level 1 (titles, axes labels...)
    fontsize2: int, scale for fontsize level 2 (legend labels, ticks...)
    line_color: string, color for line in plot
    line_style: string, style of line in plot
    """

    def __init__(self, fig_size_ratio=np.array([640, 400]), fontsize1_scale=5, fontsize2_scale=4, line_color='k',
                 line_style='-'):

        self.fig_scale = 2.0
        self.fig_dpi = 300
        self.ratio = fig_size_ratio
        self.font_size_1st_level = np.rint(self.fig_scale * fontsize1_scale)
        self.font_size_2nd_level = np.rint(self.fig_scale * fontsize2_scale)
        self.line_color = line_color
        self.line_style = line_style
        self.fig_aspect_ratio = np.rint(self.fig_scale * self.ratio)  # was 640, 360, ratio 16:9
        self.fig_face_color = "w"
        self.fig_edge_color = self.fig_face_color
        self.fig_size = self.fig_aspect_ratio / self.fig_dpi
        self.font_color = "k"
        self.font_weight = "normal"
        self.line_weight = np.rint(self.fig_scale * 1)
        self.tick_size = self.font_size_2nd_level
        self.legend_label_size = self.font_size_2nd_level

        self.fig = None


class AspectRatioType(enum.Enum):
    """
    Enumeration denoting aspect ratios
    """
    R640x360 = 1
    R1280x720 = 2
    R1920x1080 = 3
    R2560x1440 = 4
    R3840x2160 = 5


class FigureParameters:
    """
    This class encapsulates the logic of computing figure size
    """

    def __init__(self, aspect_ratio: AspectRatioType):
        if aspect_ratio == AspectRatioType.R640x360:
            self.width = 640
            self.height = 360
            self.scale_factor = 1.0 / 3.0
        elif aspect_ratio == AspectRatioType.R1280x720:
            self.width = 1280
            self.height = 720
            self.scale_factor = 2.0 / 3.0
        elif aspect_ratio == AspectRatioType.R1920x1080:
            self.width = 1920
            self.height = 1080
            self.scale_factor = 1.25
        elif aspect_ratio == AspectRatioType.R2560x1440:
            self.width = 2560
            self.height = 1440
            self.scale_factor = 4.0 / 3.0
        else:
            self.width = 3840
            self.height = 2160
            self.scale_factor = 2.0

        scale = self.scale_factor * self.height / 8
        self.figure_size_x = int(self.width / scale)
        self.figure_size_y = int(self.height / scale)
        self.text_size = int(2.0 * self.height / scale)


# Set Aspect Ratio
@dataclass
class AudioParams:
    fill_gaps: bool = True
    figure_parameters: FigureParameters = FigureParameters(AspectRatioType.R1920x1080)


def origin_time_correction(time_input: np.ndarray,
                           start_time_epoch: float,
                           units_time: str) -> Tuple[str, np.ndarray]:
    """
    Sanitize time

    :param time_input: array with timestamps
    :param start_time_epoch: start time in epoch UTC
    :param units_time: units of time
    :return: time label and time elapsed from start
    """

    # Sanitizing/scrubbing time is a key function.
    # Elapsed time from start of the record. Watch out with start alignment.
    time_elapsed_from_start = time_input - time_input[0]

    if start_time_epoch != time_input[0]:
        time_from_epoch_start = time_input[0] - start_time_epoch
    if start_time_epoch == 0:
        # Time sanitized if no input provided
        time_label: str = f"Time ({units_time})"
    else:
        start_datetime_epoch = dt.datetime.utcfromtimestamp(start_time_epoch)
        dt_str: str = start_datetime_epoch.strftime("%Y-%m-%d %H:%M:%S")
        time_label: str = f"Time ({units_time}) from UTC {dt_str}"

    return time_label, time_elapsed_from_start


def mesh_time_frequency_edges(frequency: np.ndarray,
                              time: np.ndarray,
                              frequency_ymin: float,
                              frequency_ymax: float,
                              frequency_scaling: str = "linear") -> Tuple[np.ndarray, np.ndarray, float, float]:
    """
    Find time and frequency edges for plotting

    :param frequency: array with frequencies
    :param time: array with timestamps
    :param frequency_ymin: minimum frequency for y-axis
    :param frequency_ymax: maximum frequency for y-axis
    :param frequency_scaling: "log" or "linear". Default is "linear"
    :return: min and max frequency for plot, time and frequency edges
    """

    if frequency_ymin > frequency_ymax:
        print("Higher frequency must be greater than lower frequency")
    if frequency[2] < frequency[1]:
        print("Frequency must be increasing, flip it")
    if time[2] < time[1]:
        print("Time must be increasing, flip it")

    t_half_bin: float = np.abs(time[2]-time[1]) / 2.
    t_edge: np.ndarray = np.append(time[0]-t_half_bin, time + t_half_bin)

    if frequency_scaling == "log":
        k_edge: float = np.sqrt(frequency[-1]/frequency[-2])
        f_edge: np.ndarray = np.append(frequency/k_edge, k_edge*frequency[-1])
    else:
        f_half_bin: float = (frequency[2]-frequency[1]) / 2.
        f_edge: np.ndarray = np.append(frequency[0]-f_half_bin, frequency + f_half_bin)

    # Initialize
    frequency_fix_ymin = 1.*frequency_ymin
    frequency_fix_ymax = 1.*frequency_ymax

    if frequency_ymin < f_edge[1]:
        frequency_fix_ymin = f_edge[0]
    if frequency_fix_ymin <= 0 and frequency_scaling == "log":
        frequency_fix_ymin = f_edge[1]
    if frequency_ymax > f_edge[-1]:
        frequency_fix_ymax = f_edge[-1]

    return t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax


def mesh_colormap_limits(mesh_array: np.ndarray,
                         colormap_scaling: str = "auto",
                         color_range: float = 16.):
    """
    Find colormap limits for plotting

    :param mesh_array: array with mesh
    :param colormap_scaling: one of: "auto" (max/min of input mesh), "range" (max of input mesh minus color range given)
        or "abs" (absolute max/min of input mesh)
    :param color_range: default is 16.0
    :return: colormap min and max values
    """
    if colormap_scaling == "auto":
        color_max = np.max(mesh_array)
        color_min = np.min(mesh_array)
    elif colormap_scaling == "range":
        color_max = np.max(mesh_array)
        color_min = color_max - color_range
    else:
        print("Specify mesh color limits, using min max")
        color_max = np.max(np.abs(mesh_array))
        color_min = np.min(np.abs(mesh_array))

    return color_min, color_max


def plot_wf_mesh_scatter_vert(redvox_id: str,
                              wf_panel_2_sig: np.ndarray,
                              wf_panel_2_time: np.ndarray,
                              mesh_time: np.ndarray,
                              mesh_frequency: np.ndarray,
                              scatter_time: np.ndarray,
                              scatter_frequency: np.ndarray,
                              mesh_panel_1_trf: np.ndarray,
                              mesh_panel_0_tfr: np.ndarray,
                              params_tfr=AudioParams(),
                              frequency_scaling: str = "log",
                              mesh_shading: str = "auto",
                              mesh_panel_1_colormap_scaling: str = "auto",
                              mesh_panel_1_color_max: float = 15.,
                              mesh_panel_1_color_range: float = 15.,
                              mesh_panel_1_color_min: float = 0.,
                              mesh_panel_0_colormap_scaling: str = "auto",
                              mesh_panel_0_color_max: float = 15.,
                              mesh_panel_0_color_range: float = 15.,
                              mesh_panel_0_color_min: float = 0.,
                              start_time_epoch: float = 0.,
                              frequency_hz_ymin: float = scales.Slice.FU,
                              frequency_hz_ymax: float = scales.Slice.F0,
                              waveform_color: str = "midnightblue",
                              mesh_colormap: str = "inferno",
                              units_time: str = "s",
                              units_frequency: str = "Hz",
                              wf_panel_2_units: str = "Norm",
                              mesh_panel_1_cbar_units: str = "bits",
                              mesh_panel_0_cbar_units: str = "bits",
                              figure_title: str = "Time-Frequency Representation",
                              figure_title_show: bool = True) -> None:
    """
    Plot 3 vertical panels - scatter (top panel), mesh (middle panel) and signal waveform (bottom panel)

    :param redvox_id: name of station
    :param wf_panel_2_sig: array with signal waveform for bottom panel
    :param wf_panel_2_time: array with signal timestamps for bottom panel
    :param mesh_time: array with mesh time
    :param mesh_frequency: array with mesh frequencies
    :param scatter_time: array with time for scatter plot
    :param scatter_frequency: array with frequencies for scatter plot
    :param mesh_panel_1_trf: array with mesh tfr data for mesh plot (middle panel)
    :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel)
    :param params_tfr: parameters for tfr. Check AudioParams().
    :param frequency_scaling: "log" or "linear". Default is "log"
    :param mesh_shading: type of mesh shading, one of "auto", "gouraud" or "else". Default is "auto"
    :param mesh_panel_1_colormap_scaling: color scaling for mesh plot (middle panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_1_color_max, mesh_panel_1_color_range, mesh_panel_1_color_min). Default is "auto"
    :param mesh_panel_1_color_max: maximum value for color scaling for mesh plot (middle panel). Default is 15.0
    :param mesh_panel_1_color_range: range between maximum and minimum values in color scaling for mesh plot
        (middle panel). Default is 15.0
    :param mesh_panel_1_color_min: minimum value for color scaling for mesh plot (middle panel). Default is 0.0
    :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto"
    :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0
    :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot
        (top panel). Default is 15.0
    :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0
    :param start_time_epoch: start time in epoch UTC. Default is 0.0
    :param frequency_hz_ymin: minimum frequency for y-axis
    :param frequency_hz_ymax: maximum frequency for y-axis
    :param waveform_color: color of waveform for bottom panel. Default is "midnightblue"
    :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno"
    :param units_time: units of time. Default is "s"
    :param units_frequency: units of frequency. Default is "Hz"
    :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm"
    :param mesh_panel_1_cbar_units: units of colorbar for mesh plot (middle panel). Default is "bits"
    :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits"
    :param figure_title: title of figure. Default is "Time-Frequency Representation"
    :param figure_title_show: show title if True. Default is True
    :return: plot
    """

    # Scatter inherits mesh colormap, but can have its own dynamic range
    # This is the template for the TFR workhorse. Creating a TFR class would be practical.

    # Time zeroing and scrubbing, if needed
    time_label, wf_panel_2_elapsed_time = \
        origin_time_correction(wf_panel_2_time, start_time_epoch, units_time)

    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!
    t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax = \
        mesh_time_frequency_edges(frequency=mesh_frequency, time=mesh_time,
                                  frequency_ymin=frequency_hz_ymin,
                                  frequency_ymax=frequency_hz_ymax,
                                  frequency_scaling=frequency_scaling)

    # Figure starts here
    fig_ax_tuple: Tuple[plt.Figure, List[plt.Axes]] = \
        plt.subplots(3, 1,
                     figsize=(params_tfr.figure_parameters.figure_size_x,
                              params_tfr.figure_parameters.figure_size_y),
                     sharex='col')
    fig: plt.Figure = fig_ax_tuple[0]
    axes: List[plt.Axes] = fig_ax_tuple[1]
    mesh_panel_0: plt.Axes = axes[0]
    mesh_panel_1: plt.Axes = axes[1]
    wf_panel_2: plt.Axes = axes[2]
    # bottom_panel_picker: plt.Axes = axes[3]

    # Top panel mesh --------------------------
    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!

    # Display preference
    wf_panel_2_time_xmin: int = wf_panel_2_elapsed_time[0]
    wf_panel_2_time_xmax: int = t_edge[-1]

    # Override, default is autoscaling to min and max values
    if mesh_panel_1_colormap_scaling == "auto":
        mesh_panel_1_color_min, mesh_panel_1_color_max = mesh_colormap_limits(mesh_panel_1_trf,
                                                                              mesh_panel_1_colormap_scaling,
                                                                              mesh_panel_1_color_range)
    elif mesh_panel_1_colormap_scaling == "range":
        mesh_panel_1_color_min, mesh_panel_1_color_max = mesh_colormap_limits(mesh_panel_1_trf,
                                                                              mesh_panel_1_colormap_scaling,
                                                                              mesh_panel_1_color_range)
    else:
        "Mesh 1 color scaling with user inputs"

    if mesh_panel_0_colormap_scaling == "auto":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    elif mesh_panel_0_colormap_scaling == "range":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    else:
        "Mesh 0 color scaling with user inputs"

    # Setup color map ticks
    all_cbar_ticks_lens: List[int] = [
        len(str(math.ceil(mesh_panel_0_color_min))),
        len(str(math.floor(mesh_panel_0_color_max))),
        len(str(math.ceil(mesh_panel_1_color_min))),
        len(str(math.floor(mesh_panel_1_color_max)))
    ]
    max_cbar_tick_len: int = sorted(all_cbar_ticks_lens)[-1]
    cbar_tick_fmt: str = f"%-{max_cbar_tick_len}s"

    if mesh_shading == "auto":
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(mesh_time,
                                                           mesh_frequency,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           shading=mesh_shading,
                                                           snap=True)
    elif mesh_shading == "gouraud":
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(mesh_time,
                                                           mesh_frequency,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           shading=mesh_shading,
                                                           snap=True)
    else:
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(t_edge,
                                                           f_edge,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           snap=True)

    mesh_panel_1_div: AxesDivider = make_axes_locatable(mesh_panel_1)
    mesh_panel_1_cax: plt.Axes = mesh_panel_1_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_1_cbar: Colorbar = fig.colorbar(pcolormesh_mid, cax=mesh_panel_1_cax,
                                               ticks=[math.ceil(mesh_panel_1_color_min),
                                                      math.floor(mesh_panel_1_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_1_cbar.set_label(mesh_panel_1_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_1_cax.tick_params(labelsize='large')

    mesh_panel_1.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_1.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_1.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_1.margins(x=0)
    mesh_panel_1.set_yscale(frequency_scaling)
    mesh_panel_1.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_1.tick_params(axis='y', labelsize='large')

    # Top panel --------------------------
    mesh_panel_0.scatter(scatter_time,
                         scatter_frequency,
                         c=mesh_panel_0_tfr,
                         vmin=mesh_panel_0_color_min,
                         vmax=mesh_panel_0_color_max,
                         cmap=mesh_colormap,
                         s=10,
                         alpha=0.1,
                         snap=True)

    mesh_panel_0_div: AxesDivider = make_axes_locatable(mesh_panel_0)
    mesh_panel_0_cax: plt.Axes = mesh_panel_0_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_0_cbar: Colorbar = fig.colorbar(pcolormesh_mid, cax=mesh_panel_0_cax,
                                               ticks=[math.ceil(mesh_panel_0_color_min),
                                                      math.floor(mesh_panel_0_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_0_cbar.set_label(mesh_panel_0_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0_cax.tick_params(labelsize='large')
    if figure_title_show:
        mesh_panel_0.set_title(f"{figure_title} at Station {redvox_id}")
    mesh_panel_0.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_0.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_0.set_yscale(frequency_scaling)
    mesh_panel_0.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_0.tick_params(axis='y', labelsize='large')

    # Waveform panel
    wf_panel_2.plot(wf_panel_2_elapsed_time, wf_panel_2_sig, color=waveform_color)
    wf_panel_2.set_ylabel(wf_panel_2_units, size=params_tfr.figure_parameters.text_size)
    wf_panel_2.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    wf_panel_2.tick_params(axis='x', which='both', bottom=True, labelbottom=True, labelsize='large')
    wf_panel_2.grid(True)
    wf_panel_2.tick_params(axis='y', labelsize='large')
    wf_panel_2.ticklabel_format(style="sci", scilimits=(0, 0), axis="y")
    wf_panel_2.yaxis.get_offset_text().set_x(-0.034)
    wf_panel_2_div: AxesDivider = make_axes_locatable(wf_panel_2)
    wf_panel_2_cax: plt.Axes = wf_panel_2_div.append_axes("right", size="1%", pad="0.5%")
    wf_panel_2_cax.axis("off")

    fig.text(.5, .01, time_label, ha='center', size=params_tfr.figure_parameters.text_size)

    fig.align_ylabels(axes)
    fig.tight_layout()
    fig.subplots_adjust(bottom=.1, hspace=0.13)


def plot_wf_scatter_vert(redvox_id: str,
                         wf_panel_2_sig: np.ndarray,
                         wf_panel_2_time: np.ndarray,
                         mesh_time: np.ndarray,
                         mesh_frequency: np.ndarray,
                         mesh_panel_0_tfr: np.ndarray,
                         params_tfr=AudioParams(),
                         frequency_scaling: str = "log",
                         mesh_panel_0_colormap_scaling: str = "auto",
                         mesh_panel_0_color_max: float = 15,
                         mesh_panel_0_color_range: float = 15,
                         mesh_panel_0_color_min: float = 0,
                         start_time_epoch: float = 0,
                         frequency_hz_ymin: float = scales.Slice.FU,
                         frequency_hz_ymax: float = scales.Slice.F0,
                         waveform_color: str = "midnightblue",
                         mesh_colormap: str = "inferno",
                         units_time: str = "s",
                         units_frequency: str = "Hz",
                         wf_panel_2_units: str = "Norm",
                         mesh_panel_0_cbar_units: str = "bits",
                         figure_title: str = "Time-Frequency Representation",
                         figure_title_show: bool = True) -> None:
    """
    Plot 2 vertical panels - scatter (top panel) and signal waveform (bottom panel)

    :param redvox_id: name of station
    :param wf_panel_2_sig: array with signal waveform for bottom panel
    :param wf_panel_2_time: array with signal timestamps for bottom panel
    :param mesh_time: array with mesh time
    :param mesh_frequency: array with mesh frequencies
    :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel)
    :param params_tfr: parameters for tfr. Check AudioParams().
    :param frequency_scaling: "log" or "linear". Default is "log"
    :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto"
    :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0
    :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot
        (top panel). Default is 15.0
    :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0
    :param start_time_epoch: start time in epoch UTC. Default is 0.0
    :param frequency_hz_ymin: minimum frequency for y-axis
    :param frequency_hz_ymax: maximum frequency for y-axis
    :param waveform_color: color of waveform for bottom panel. Default is "midnightblue"
    :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno"
    :param units_time: units of time. Default is "s"
    :param units_frequency: units of frequency. Default is "Hz"
    :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm"
    :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits"
    :param figure_title: title of figure
    :param figure_title_show: show title if True. Default is True
    :return: plot
    """

    # This is the template for the TFR workhorse. Creating a TFR class may be practical.
    # Time zeroing and scrubbing, if needed
    time_label, wf_panel_2_elapsed_time = \
        origin_time_correction(wf_panel_2_time, start_time_epoch, units_time)

    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!
    t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax = \
        mesh_time_frequency_edges(frequency=mesh_frequency, time=mesh_time,
                                  frequency_ymin=frequency_hz_ymin,
                                  frequency_ymax=frequency_hz_ymax,
                                  frequency_scaling=frequency_scaling)

    # Figure starts here
    fig_ax_tuple: Tuple[plt.Figure, List[plt.Axes]] = \
        plt.subplots(2, 1,
                     figsize=(params_tfr.figure_parameters.figure_size_x,
                              params_tfr.figure_parameters.figure_size_y),
                     sharex='col')
    fig: plt.Figure = fig_ax_tuple[0]
    axes: List[plt.Axes] = fig_ax_tuple[1]
    mesh_panel_0: plt.Axes = axes[0]
    wf_panel_2: plt.Axes = axes[1]
    # bottom_panel_picker: plt.Axes = axes[3]

    # Top panel mesh --------------------------
    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!

    # Display preference
    wf_panel_2_time_xmin: int = wf_panel_2_elapsed_time[0]
    wf_panel_2_time_xmax: int = t_edge[-1]

    if mesh_panel_0_colormap_scaling == "auto":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    elif mesh_panel_0_colormap_scaling == "range":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    else:
        "Mesh 0 color scaling with user inputs"

    # Setup color map ticks
    all_cbar_ticks_lens: List[int] = [
        len(str(math.ceil(mesh_panel_0_color_min))),
        len(str(math.floor(mesh_panel_0_color_max)))]
    max_cbar_tick_len: int = sorted(all_cbar_ticks_lens)[-1]
    cbar_tick_fmt: str = f"%-{max_cbar_tick_len}s"

    pcolormesh_top = mesh_panel_0.pcolormesh(mesh_time,
                                             mesh_frequency,
                                             mesh_panel_0_tfr,
                                             vmin=mesh_panel_0_color_min,
                                             vmax=mesh_panel_0_color_max,
                                             cmap=mesh_colormap,
                                             snap=True)

    mesh_panel_0_div: AxesDivider = make_axes_locatable(mesh_panel_0)
    mesh_panel_0_cax: plt.Axes = mesh_panel_0_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_0_cbar: Colorbar = fig.colorbar(pcolormesh_top, cax=mesh_panel_0_cax,
                                               ticks=[math.ceil(mesh_panel_0_color_min),
                                                      math.floor(mesh_panel_0_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_0_cbar.set_label(mesh_panel_0_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0_cax.tick_params(labelsize='large')
    if figure_title_show:
        mesh_panel_0.set_title(f"{figure_title} at Station {redvox_id}")
    mesh_panel_0.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_0.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_0.set_yscale(frequency_scaling)
    mesh_panel_0.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_0.tick_params(axis='y', labelsize='large')

    # Waveform panel
    wf_panel_2.plot(wf_panel_2_elapsed_time, wf_panel_2_sig, color=waveform_color)
    wf_panel_2.set_ylabel(wf_panel_2_units, size=params_tfr.figure_parameters.text_size)
    wf_panel_2.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    wf_panel_2.tick_params(axis='x', which='both', bottom=True, labelbottom=True, labelsize='large')
    wf_panel_2.grid(True)
    wf_panel_2.tick_params(axis='y', labelsize='large')
    wf_panel_2.ticklabel_format(style="sci", scilimits=(0, 0), axis="y")
    wf_panel_2.yaxis.get_offset_text().set_x(-0.034)

    wf_panel_2_div: AxesDivider = make_axes_locatable(wf_panel_2)
    wf_panel_2_cax: plt.Axes = wf_panel_2_div.append_axes("right", size="1%", pad="0.5%")
    wf_panel_2_cax.axis("off")

    fig.text(.5, .01, time_label, ha='center', size=params_tfr.figure_parameters.text_size)

    fig.align_ylabels(axes)
    fig.tight_layout()
    fig.subplots_adjust(bottom=.1, hspace=0.13)

Functions

def mesh_colormap_limits(mesh_array: numpy.ndarray, colormap_scaling: str = 'auto', color_range: float = 16.0)

Find colormap limits for plotting

:param mesh_array: array with mesh :param colormap_scaling: one of: "auto" (max/min of input mesh), "range" (max of input mesh minus color range given) or "abs" (absolute max/min of input mesh) :param color_range: default is 16.0 :return: colormap min and max values

Expand source code
def mesh_colormap_limits(mesh_array: np.ndarray,
                         colormap_scaling: str = "auto",
                         color_range: float = 16.):
    """
    Find colormap limits for plotting

    :param mesh_array: array with mesh
    :param colormap_scaling: one of: "auto" (max/min of input mesh), "range" (max of input mesh minus color range given)
        or "abs" (absolute max/min of input mesh)
    :param color_range: default is 16.0
    :return: colormap min and max values
    """
    if colormap_scaling == "auto":
        color_max = np.max(mesh_array)
        color_min = np.min(mesh_array)
    elif colormap_scaling == "range":
        color_max = np.max(mesh_array)
        color_min = color_max - color_range
    else:
        print("Specify mesh color limits, using min max")
        color_max = np.max(np.abs(mesh_array))
        color_min = np.min(np.abs(mesh_array))

    return color_min, color_max
def mesh_time_frequency_edges(frequency: numpy.ndarray, time: numpy.ndarray, frequency_ymin: float, frequency_ymax: float, frequency_scaling: str = 'linear') ‑> Tuple[numpy.ndarray, numpy.ndarray, float, float]

Find time and frequency edges for plotting

:param frequency: array with frequencies :param time: array with timestamps :param frequency_ymin: minimum frequency for y-axis :param frequency_ymax: maximum frequency for y-axis :param frequency_scaling: "log" or "linear". Default is "linear" :return: min and max frequency for plot, time and frequency edges

Expand source code
def mesh_time_frequency_edges(frequency: np.ndarray,
                              time: np.ndarray,
                              frequency_ymin: float,
                              frequency_ymax: float,
                              frequency_scaling: str = "linear") -> Tuple[np.ndarray, np.ndarray, float, float]:
    """
    Find time and frequency edges for plotting

    :param frequency: array with frequencies
    :param time: array with timestamps
    :param frequency_ymin: minimum frequency for y-axis
    :param frequency_ymax: maximum frequency for y-axis
    :param frequency_scaling: "log" or "linear". Default is "linear"
    :return: min and max frequency for plot, time and frequency edges
    """

    if frequency_ymin > frequency_ymax:
        print("Higher frequency must be greater than lower frequency")
    if frequency[2] < frequency[1]:
        print("Frequency must be increasing, flip it")
    if time[2] < time[1]:
        print("Time must be increasing, flip it")

    t_half_bin: float = np.abs(time[2]-time[1]) / 2.
    t_edge: np.ndarray = np.append(time[0]-t_half_bin, time + t_half_bin)

    if frequency_scaling == "log":
        k_edge: float = np.sqrt(frequency[-1]/frequency[-2])
        f_edge: np.ndarray = np.append(frequency/k_edge, k_edge*frequency[-1])
    else:
        f_half_bin: float = (frequency[2]-frequency[1]) / 2.
        f_edge: np.ndarray = np.append(frequency[0]-f_half_bin, frequency + f_half_bin)

    # Initialize
    frequency_fix_ymin = 1.*frequency_ymin
    frequency_fix_ymax = 1.*frequency_ymax

    if frequency_ymin < f_edge[1]:
        frequency_fix_ymin = f_edge[0]
    if frequency_fix_ymin <= 0 and frequency_scaling == "log":
        frequency_fix_ymin = f_edge[1]
    if frequency_ymax > f_edge[-1]:
        frequency_fix_ymax = f_edge[-1]

    return t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax
def origin_time_correction(time_input: numpy.ndarray, start_time_epoch: float, units_time: str) ‑> Tuple[str, numpy.ndarray]

Sanitize time

:param time_input: array with timestamps :param start_time_epoch: start time in epoch UTC :param units_time: units of time :return: time label and time elapsed from start

Expand source code
def origin_time_correction(time_input: np.ndarray,
                           start_time_epoch: float,
                           units_time: str) -> Tuple[str, np.ndarray]:
    """
    Sanitize time

    :param time_input: array with timestamps
    :param start_time_epoch: start time in epoch UTC
    :param units_time: units of time
    :return: time label and time elapsed from start
    """

    # Sanitizing/scrubbing time is a key function.
    # Elapsed time from start of the record. Watch out with start alignment.
    time_elapsed_from_start = time_input - time_input[0]

    if start_time_epoch != time_input[0]:
        time_from_epoch_start = time_input[0] - start_time_epoch
    if start_time_epoch == 0:
        # Time sanitized if no input provided
        time_label: str = f"Time ({units_time})"
    else:
        start_datetime_epoch = dt.datetime.utcfromtimestamp(start_time_epoch)
        dt_str: str = start_datetime_epoch.strftime("%Y-%m-%d %H:%M:%S")
        time_label: str = f"Time ({units_time}) from UTC {dt_str}"

    return time_label, time_elapsed_from_start
def plot_wf_mesh_scatter_vert(redvox_id: str, wf_panel_2_sig: numpy.ndarray, wf_panel_2_time: numpy.ndarray, mesh_time: numpy.ndarray, mesh_frequency: numpy.ndarray, scatter_time: numpy.ndarray, scatter_frequency: numpy.ndarray, mesh_panel_1_trf: numpy.ndarray, mesh_panel_0_tfr: numpy.ndarray, params_tfr=AudioParams(fill_gaps=True, figure_parameters=<libquantum.plot_templates.plot_time_frequency_picks.FigureParameters object>), frequency_scaling: str = 'log', mesh_shading: str = 'auto', mesh_panel_1_colormap_scaling: str = 'auto', mesh_panel_1_color_max: float = 15.0, mesh_panel_1_color_range: float = 15.0, mesh_panel_1_color_min: float = 0.0, mesh_panel_0_colormap_scaling: str = 'auto', mesh_panel_0_color_max: float = 15.0, mesh_panel_0_color_range: float = 15.0, mesh_panel_0_color_min: float = 0.0, start_time_epoch: float = 0.0, frequency_hz_ymin: float = 3.469446951953614e-18, frequency_hz_ymax: float = 1e+42, waveform_color: str = 'midnightblue', mesh_colormap: str = 'inferno', units_time: str = 's', units_frequency: str = 'Hz', wf_panel_2_units: str = 'Norm', mesh_panel_1_cbar_units: str = 'bits', mesh_panel_0_cbar_units: str = 'bits', figure_title: str = 'Time-Frequency Representation', figure_title_show: bool = True) ‑> NoneType

Plot 3 vertical panels - scatter (top panel), mesh (middle panel) and signal waveform (bottom panel)

:param redvox_id: name of station :param wf_panel_2_sig: array with signal waveform for bottom panel :param wf_panel_2_time: array with signal timestamps for bottom panel :param mesh_time: array with mesh time :param mesh_frequency: array with mesh frequencies :param scatter_time: array with time for scatter plot :param scatter_frequency: array with frequencies for scatter plot :param mesh_panel_1_trf: array with mesh tfr data for mesh plot (middle panel) :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel) :param params_tfr: parameters for tfr. Check AudioParams(). :param frequency_scaling: "log" or "linear". Default is "log" :param mesh_shading: type of mesh shading, one of "auto", "gouraud" or "else". Default is "auto" :param mesh_panel_1_colormap_scaling: color scaling for mesh plot (middle panel). One of: "auto", "range" or "else" (use inputs given in mesh_panel_1_color_max, mesh_panel_1_color_range, mesh_panel_1_color_min). Default is "auto" :param mesh_panel_1_color_max: maximum value for color scaling for mesh plot (middle panel). Default is 15.0 :param mesh_panel_1_color_range: range between maximum and minimum values in color scaling for mesh plot (middle panel). Default is 15.0 :param mesh_panel_1_color_min: minimum value for color scaling for mesh plot (middle panel). Default is 0.0 :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else" (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto" :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0 :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot (top panel). Default is 15.0 :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0 :param start_time_epoch: start time in epoch UTC. Default is 0.0 :param frequency_hz_ymin: minimum frequency for y-axis :param frequency_hz_ymax: maximum frequency for y-axis :param waveform_color: color of waveform for bottom panel. Default is "midnightblue" :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno" :param units_time: units of time. Default is "s" :param units_frequency: units of frequency. Default is "Hz" :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm" :param mesh_panel_1_cbar_units: units of colorbar for mesh plot (middle panel). Default is "bits" :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits" :param figure_title: title of figure. Default is "Time-Frequency Representation" :param figure_title_show: show title if True. Default is True :return: plot

Expand source code
def plot_wf_mesh_scatter_vert(redvox_id: str,
                              wf_panel_2_sig: np.ndarray,
                              wf_panel_2_time: np.ndarray,
                              mesh_time: np.ndarray,
                              mesh_frequency: np.ndarray,
                              scatter_time: np.ndarray,
                              scatter_frequency: np.ndarray,
                              mesh_panel_1_trf: np.ndarray,
                              mesh_panel_0_tfr: np.ndarray,
                              params_tfr=AudioParams(),
                              frequency_scaling: str = "log",
                              mesh_shading: str = "auto",
                              mesh_panel_1_colormap_scaling: str = "auto",
                              mesh_panel_1_color_max: float = 15.,
                              mesh_panel_1_color_range: float = 15.,
                              mesh_panel_1_color_min: float = 0.,
                              mesh_panel_0_colormap_scaling: str = "auto",
                              mesh_panel_0_color_max: float = 15.,
                              mesh_panel_0_color_range: float = 15.,
                              mesh_panel_0_color_min: float = 0.,
                              start_time_epoch: float = 0.,
                              frequency_hz_ymin: float = scales.Slice.FU,
                              frequency_hz_ymax: float = scales.Slice.F0,
                              waveform_color: str = "midnightblue",
                              mesh_colormap: str = "inferno",
                              units_time: str = "s",
                              units_frequency: str = "Hz",
                              wf_panel_2_units: str = "Norm",
                              mesh_panel_1_cbar_units: str = "bits",
                              mesh_panel_0_cbar_units: str = "bits",
                              figure_title: str = "Time-Frequency Representation",
                              figure_title_show: bool = True) -> None:
    """
    Plot 3 vertical panels - scatter (top panel), mesh (middle panel) and signal waveform (bottom panel)

    :param redvox_id: name of station
    :param wf_panel_2_sig: array with signal waveform for bottom panel
    :param wf_panel_2_time: array with signal timestamps for bottom panel
    :param mesh_time: array with mesh time
    :param mesh_frequency: array with mesh frequencies
    :param scatter_time: array with time for scatter plot
    :param scatter_frequency: array with frequencies for scatter plot
    :param mesh_panel_1_trf: array with mesh tfr data for mesh plot (middle panel)
    :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel)
    :param params_tfr: parameters for tfr. Check AudioParams().
    :param frequency_scaling: "log" or "linear". Default is "log"
    :param mesh_shading: type of mesh shading, one of "auto", "gouraud" or "else". Default is "auto"
    :param mesh_panel_1_colormap_scaling: color scaling for mesh plot (middle panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_1_color_max, mesh_panel_1_color_range, mesh_panel_1_color_min). Default is "auto"
    :param mesh_panel_1_color_max: maximum value for color scaling for mesh plot (middle panel). Default is 15.0
    :param mesh_panel_1_color_range: range between maximum and minimum values in color scaling for mesh plot
        (middle panel). Default is 15.0
    :param mesh_panel_1_color_min: minimum value for color scaling for mesh plot (middle panel). Default is 0.0
    :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto"
    :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0
    :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot
        (top panel). Default is 15.0
    :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0
    :param start_time_epoch: start time in epoch UTC. Default is 0.0
    :param frequency_hz_ymin: minimum frequency for y-axis
    :param frequency_hz_ymax: maximum frequency for y-axis
    :param waveform_color: color of waveform for bottom panel. Default is "midnightblue"
    :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno"
    :param units_time: units of time. Default is "s"
    :param units_frequency: units of frequency. Default is "Hz"
    :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm"
    :param mesh_panel_1_cbar_units: units of colorbar for mesh plot (middle panel). Default is "bits"
    :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits"
    :param figure_title: title of figure. Default is "Time-Frequency Representation"
    :param figure_title_show: show title if True. Default is True
    :return: plot
    """

    # Scatter inherits mesh colormap, but can have its own dynamic range
    # This is the template for the TFR workhorse. Creating a TFR class would be practical.

    # Time zeroing and scrubbing, if needed
    time_label, wf_panel_2_elapsed_time = \
        origin_time_correction(wf_panel_2_time, start_time_epoch, units_time)

    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!
    t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax = \
        mesh_time_frequency_edges(frequency=mesh_frequency, time=mesh_time,
                                  frequency_ymin=frequency_hz_ymin,
                                  frequency_ymax=frequency_hz_ymax,
                                  frequency_scaling=frequency_scaling)

    # Figure starts here
    fig_ax_tuple: Tuple[plt.Figure, List[plt.Axes]] = \
        plt.subplots(3, 1,
                     figsize=(params_tfr.figure_parameters.figure_size_x,
                              params_tfr.figure_parameters.figure_size_y),
                     sharex='col')
    fig: plt.Figure = fig_ax_tuple[0]
    axes: List[plt.Axes] = fig_ax_tuple[1]
    mesh_panel_0: plt.Axes = axes[0]
    mesh_panel_1: plt.Axes = axes[1]
    wf_panel_2: plt.Axes = axes[2]
    # bottom_panel_picker: plt.Axes = axes[3]

    # Top panel mesh --------------------------
    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!

    # Display preference
    wf_panel_2_time_xmin: int = wf_panel_2_elapsed_time[0]
    wf_panel_2_time_xmax: int = t_edge[-1]

    # Override, default is autoscaling to min and max values
    if mesh_panel_1_colormap_scaling == "auto":
        mesh_panel_1_color_min, mesh_panel_1_color_max = mesh_colormap_limits(mesh_panel_1_trf,
                                                                              mesh_panel_1_colormap_scaling,
                                                                              mesh_panel_1_color_range)
    elif mesh_panel_1_colormap_scaling == "range":
        mesh_panel_1_color_min, mesh_panel_1_color_max = mesh_colormap_limits(mesh_panel_1_trf,
                                                                              mesh_panel_1_colormap_scaling,
                                                                              mesh_panel_1_color_range)
    else:
        "Mesh 1 color scaling with user inputs"

    if mesh_panel_0_colormap_scaling == "auto":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    elif mesh_panel_0_colormap_scaling == "range":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    else:
        "Mesh 0 color scaling with user inputs"

    # Setup color map ticks
    all_cbar_ticks_lens: List[int] = [
        len(str(math.ceil(mesh_panel_0_color_min))),
        len(str(math.floor(mesh_panel_0_color_max))),
        len(str(math.ceil(mesh_panel_1_color_min))),
        len(str(math.floor(mesh_panel_1_color_max)))
    ]
    max_cbar_tick_len: int = sorted(all_cbar_ticks_lens)[-1]
    cbar_tick_fmt: str = f"%-{max_cbar_tick_len}s"

    if mesh_shading == "auto":
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(mesh_time,
                                                           mesh_frequency,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           shading=mesh_shading,
                                                           snap=True)
    elif mesh_shading == "gouraud":
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(mesh_time,
                                                           mesh_frequency,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           shading=mesh_shading,
                                                           snap=True)
    else:
        pcolormesh_mid: QuadMesh = mesh_panel_1.pcolormesh(t_edge,
                                                           f_edge,
                                                           mesh_panel_1_trf,
                                                           vmin=mesh_panel_1_color_min,
                                                           vmax=mesh_panel_1_color_max,
                                                           cmap=mesh_colormap,
                                                           snap=True)

    mesh_panel_1_div: AxesDivider = make_axes_locatable(mesh_panel_1)
    mesh_panel_1_cax: plt.Axes = mesh_panel_1_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_1_cbar: Colorbar = fig.colorbar(pcolormesh_mid, cax=mesh_panel_1_cax,
                                               ticks=[math.ceil(mesh_panel_1_color_min),
                                                      math.floor(mesh_panel_1_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_1_cbar.set_label(mesh_panel_1_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_1_cax.tick_params(labelsize='large')

    mesh_panel_1.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_1.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_1.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_1.margins(x=0)
    mesh_panel_1.set_yscale(frequency_scaling)
    mesh_panel_1.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_1.tick_params(axis='y', labelsize='large')

    # Top panel --------------------------
    mesh_panel_0.scatter(scatter_time,
                         scatter_frequency,
                         c=mesh_panel_0_tfr,
                         vmin=mesh_panel_0_color_min,
                         vmax=mesh_panel_0_color_max,
                         cmap=mesh_colormap,
                         s=10,
                         alpha=0.1,
                         snap=True)

    mesh_panel_0_div: AxesDivider = make_axes_locatable(mesh_panel_0)
    mesh_panel_0_cax: plt.Axes = mesh_panel_0_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_0_cbar: Colorbar = fig.colorbar(pcolormesh_mid, cax=mesh_panel_0_cax,
                                               ticks=[math.ceil(mesh_panel_0_color_min),
                                                      math.floor(mesh_panel_0_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_0_cbar.set_label(mesh_panel_0_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0_cax.tick_params(labelsize='large')
    if figure_title_show:
        mesh_panel_0.set_title(f"{figure_title} at Station {redvox_id}")
    mesh_panel_0.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_0.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_0.set_yscale(frequency_scaling)
    mesh_panel_0.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_0.tick_params(axis='y', labelsize='large')

    # Waveform panel
    wf_panel_2.plot(wf_panel_2_elapsed_time, wf_panel_2_sig, color=waveform_color)
    wf_panel_2.set_ylabel(wf_panel_2_units, size=params_tfr.figure_parameters.text_size)
    wf_panel_2.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    wf_panel_2.tick_params(axis='x', which='both', bottom=True, labelbottom=True, labelsize='large')
    wf_panel_2.grid(True)
    wf_panel_2.tick_params(axis='y', labelsize='large')
    wf_panel_2.ticklabel_format(style="sci", scilimits=(0, 0), axis="y")
    wf_panel_2.yaxis.get_offset_text().set_x(-0.034)
    wf_panel_2_div: AxesDivider = make_axes_locatable(wf_panel_2)
    wf_panel_2_cax: plt.Axes = wf_panel_2_div.append_axes("right", size="1%", pad="0.5%")
    wf_panel_2_cax.axis("off")

    fig.text(.5, .01, time_label, ha='center', size=params_tfr.figure_parameters.text_size)

    fig.align_ylabels(axes)
    fig.tight_layout()
    fig.subplots_adjust(bottom=.1, hspace=0.13)
def plot_wf_scatter_vert(redvox_id: str, wf_panel_2_sig: numpy.ndarray, wf_panel_2_time: numpy.ndarray, mesh_time: numpy.ndarray, mesh_frequency: numpy.ndarray, mesh_panel_0_tfr: numpy.ndarray, params_tfr=AudioParams(fill_gaps=True, figure_parameters=<libquantum.plot_templates.plot_time_frequency_picks.FigureParameters object>), frequency_scaling: str = 'log', mesh_panel_0_colormap_scaling: str = 'auto', mesh_panel_0_color_max: float = 15, mesh_panel_0_color_range: float = 15, mesh_panel_0_color_min: float = 0, start_time_epoch: float = 0, frequency_hz_ymin: float = 3.469446951953614e-18, frequency_hz_ymax: float = 1e+42, waveform_color: str = 'midnightblue', mesh_colormap: str = 'inferno', units_time: str = 's', units_frequency: str = 'Hz', wf_panel_2_units: str = 'Norm', mesh_panel_0_cbar_units: str = 'bits', figure_title: str = 'Time-Frequency Representation', figure_title_show: bool = True) ‑> NoneType

Plot 2 vertical panels - scatter (top panel) and signal waveform (bottom panel)

:param redvox_id: name of station :param wf_panel_2_sig: array with signal waveform for bottom panel :param wf_panel_2_time: array with signal timestamps for bottom panel :param mesh_time: array with mesh time :param mesh_frequency: array with mesh frequencies :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel) :param params_tfr: parameters for tfr. Check AudioParams(). :param frequency_scaling: "log" or "linear". Default is "log" :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else" (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto" :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0 :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot (top panel). Default is 15.0 :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0 :param start_time_epoch: start time in epoch UTC. Default is 0.0 :param frequency_hz_ymin: minimum frequency for y-axis :param frequency_hz_ymax: maximum frequency for y-axis :param waveform_color: color of waveform for bottom panel. Default is "midnightblue" :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno" :param units_time: units of time. Default is "s" :param units_frequency: units of frequency. Default is "Hz" :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm" :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits" :param figure_title: title of figure :param figure_title_show: show title if True. Default is True :return: plot

Expand source code
def plot_wf_scatter_vert(redvox_id: str,
                         wf_panel_2_sig: np.ndarray,
                         wf_panel_2_time: np.ndarray,
                         mesh_time: np.ndarray,
                         mesh_frequency: np.ndarray,
                         mesh_panel_0_tfr: np.ndarray,
                         params_tfr=AudioParams(),
                         frequency_scaling: str = "log",
                         mesh_panel_0_colormap_scaling: str = "auto",
                         mesh_panel_0_color_max: float = 15,
                         mesh_panel_0_color_range: float = 15,
                         mesh_panel_0_color_min: float = 0,
                         start_time_epoch: float = 0,
                         frequency_hz_ymin: float = scales.Slice.FU,
                         frequency_hz_ymax: float = scales.Slice.F0,
                         waveform_color: str = "midnightblue",
                         mesh_colormap: str = "inferno",
                         units_time: str = "s",
                         units_frequency: str = "Hz",
                         wf_panel_2_units: str = "Norm",
                         mesh_panel_0_cbar_units: str = "bits",
                         figure_title: str = "Time-Frequency Representation",
                         figure_title_show: bool = True) -> None:
    """
    Plot 2 vertical panels - scatter (top panel) and signal waveform (bottom panel)

    :param redvox_id: name of station
    :param wf_panel_2_sig: array with signal waveform for bottom panel
    :param wf_panel_2_time: array with signal timestamps for bottom panel
    :param mesh_time: array with mesh time
    :param mesh_frequency: array with mesh frequencies
    :param mesh_panel_0_tfr: array with mesh tfr data for scatter plot (top panel)
    :param params_tfr: parameters for tfr. Check AudioParams().
    :param frequency_scaling: "log" or "linear". Default is "log"
    :param mesh_panel_0_colormap_scaling: color scaling for scatter plot (top panel). One of: "auto", "range" or "else"
        (use inputs given in mesh_panel_0_color_max, mesh_panel_0_color_range, mesh_panel_0_color_min). Default is "auto"
    :param mesh_panel_0_color_max: maximum value for color scaling for scatter plot (top panel). Default is 15.0
    :param mesh_panel_0_color_range:range between maximum and minimum values in color scaling for scatter plot
        (top panel). Default is 15.0
    :param mesh_panel_0_color_min: minimum value for color scaling for scatter plot (top panel). Default is 0.0
    :param start_time_epoch: start time in epoch UTC. Default is 0.0
    :param frequency_hz_ymin: minimum frequency for y-axis
    :param frequency_hz_ymax: maximum frequency for y-axis
    :param waveform_color: color of waveform for bottom panel. Default is "midnightblue"
    :param mesh_colormap: a Matplotlib Colormap instance or registered colormap name. Default is "inferno"
    :param units_time: units of time. Default is "s"
    :param units_frequency: units of frequency. Default is "Hz"
    :param wf_panel_2_units: units of waveform plot (bottom panel). Default is "Norm"
    :param mesh_panel_0_cbar_units: units of colorbar for scatter plot (top panel). Default is "bits"
    :param figure_title: title of figure
    :param figure_title_show: show title if True. Default is True
    :return: plot
    """

    # This is the template for the TFR workhorse. Creating a TFR class may be practical.
    # Time zeroing and scrubbing, if needed
    time_label, wf_panel_2_elapsed_time = \
        origin_time_correction(wf_panel_2_time, start_time_epoch, units_time)

    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!
    t_edge, f_edge, frequency_fix_ymin, frequency_fix_ymax = \
        mesh_time_frequency_edges(frequency=mesh_frequency, time=mesh_time,
                                  frequency_ymin=frequency_hz_ymin,
                                  frequency_ymax=frequency_hz_ymax,
                                  frequency_scaling=frequency_scaling)

    # Figure starts here
    fig_ax_tuple: Tuple[plt.Figure, List[plt.Axes]] = \
        plt.subplots(2, 1,
                     figsize=(params_tfr.figure_parameters.figure_size_x,
                              params_tfr.figure_parameters.figure_size_y),
                     sharex='col')
    fig: plt.Figure = fig_ax_tuple[0]
    axes: List[plt.Axes] = fig_ax_tuple[1]
    mesh_panel_0: plt.Axes = axes[0]
    wf_panel_2: plt.Axes = axes[1]
    # bottom_panel_picker: plt.Axes = axes[3]

    # Top panel mesh --------------------------
    # Time is in the center of the window, frequency is in the fft coefficient center.
    # pcolormesh must provide corner coordinates, so there will be an offset from step noverlap step size.
    # frequency and time must be increasing!

    # Display preference
    wf_panel_2_time_xmin: int = wf_panel_2_elapsed_time[0]
    wf_panel_2_time_xmax: int = t_edge[-1]

    if mesh_panel_0_colormap_scaling == "auto":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    elif mesh_panel_0_colormap_scaling == "range":
        mesh_panel_0_color_min, mesh_panel_0_color_max = mesh_colormap_limits(mesh_panel_0_tfr,
                                                                              mesh_panel_0_colormap_scaling,
                                                                              mesh_panel_0_color_range)
    else:
        "Mesh 0 color scaling with user inputs"

    # Setup color map ticks
    all_cbar_ticks_lens: List[int] = [
        len(str(math.ceil(mesh_panel_0_color_min))),
        len(str(math.floor(mesh_panel_0_color_max)))]
    max_cbar_tick_len: int = sorted(all_cbar_ticks_lens)[-1]
    cbar_tick_fmt: str = f"%-{max_cbar_tick_len}s"

    pcolormesh_top = mesh_panel_0.pcolormesh(mesh_time,
                                             mesh_frequency,
                                             mesh_panel_0_tfr,
                                             vmin=mesh_panel_0_color_min,
                                             vmax=mesh_panel_0_color_max,
                                             cmap=mesh_colormap,
                                             snap=True)

    mesh_panel_0_div: AxesDivider = make_axes_locatable(mesh_panel_0)
    mesh_panel_0_cax: plt.Axes = mesh_panel_0_div.append_axes("right", size="1%", pad="0.5%")
    mesh_panel_0_cbar: Colorbar = fig.colorbar(pcolormesh_top, cax=mesh_panel_0_cax,
                                               ticks=[math.ceil(mesh_panel_0_color_min),
                                                      math.floor(mesh_panel_0_color_max)],
                                               format=cbar_tick_fmt)
    mesh_panel_0_cbar.set_label(mesh_panel_0_cbar_units, rotation=270, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0_cax.tick_params(labelsize='large')
    if figure_title_show:
        mesh_panel_0.set_title(f"{figure_title} at Station {redvox_id}")
    mesh_panel_0.set_ylabel(units_frequency, size=params_tfr.figure_parameters.text_size)
    mesh_panel_0.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    mesh_panel_0.set_ylim(frequency_fix_ymin, frequency_fix_ymax)
    mesh_panel_0.set_yscale(frequency_scaling)
    mesh_panel_0.tick_params(axis='x', which='both', bottom=False, labelbottom=False)
    mesh_panel_0.tick_params(axis='y', labelsize='large')

    # Waveform panel
    wf_panel_2.plot(wf_panel_2_elapsed_time, wf_panel_2_sig, color=waveform_color)
    wf_panel_2.set_ylabel(wf_panel_2_units, size=params_tfr.figure_parameters.text_size)
    wf_panel_2.set_xlim(wf_panel_2_time_xmin, wf_panel_2_time_xmax)
    wf_panel_2.tick_params(axis='x', which='both', bottom=True, labelbottom=True, labelsize='large')
    wf_panel_2.grid(True)
    wf_panel_2.tick_params(axis='y', labelsize='large')
    wf_panel_2.ticklabel_format(style="sci", scilimits=(0, 0), axis="y")
    wf_panel_2.yaxis.get_offset_text().set_x(-0.034)

    wf_panel_2_div: AxesDivider = make_axes_locatable(wf_panel_2)
    wf_panel_2_cax: plt.Axes = wf_panel_2_div.append_axes("right", size="1%", pad="0.5%")
    wf_panel_2_cax.axis("off")

    fig.text(.5, .01, time_label, ha='center', size=params_tfr.figure_parameters.text_size)

    fig.align_ylabels(axes)
    fig.tight_layout()
    fig.subplots_adjust(bottom=.1, hspace=0.13)

Classes

class AspectRatioType (value, names=None, *, module=None, qualname=None, type=None, start=1)

Enumeration denoting aspect ratios

Expand source code
class AspectRatioType(enum.Enum):
    """
    Enumeration denoting aspect ratios
    """
    R640x360 = 1
    R1280x720 = 2
    R1920x1080 = 3
    R2560x1440 = 4
    R3840x2160 = 5

Ancestors

  • enum.Enum

Class variables

var R1280x720
var R1920x1080
var R2560x1440
var R3840x2160
var R640x360
class AudioParams (fill_gaps: bool = True, figure_parameters: FigureParameters = <libquantum.plot_templates.plot_time_frequency_picks.FigureParameters object>)

AudioParams(fill_gaps: bool = True, figure_parameters: libquantum.plot_templates.plot_time_frequency_picks.FigureParameters = )

Expand source code
class AudioParams:
    fill_gaps: bool = True
    figure_parameters: FigureParameters = FigureParameters(AspectRatioType.R1920x1080)

Class variables

var figure_parametersFigureParameters
var fill_gaps : bool
class FigureAttributes (fig_size_ratio=array([640, 400]), fontsize1_scale=5, fontsize2_scale=4, line_color='k', line_style='-')

This is the most basic parent class – sets the plot canvas as well as figure handling functions. This is where figure size, font style and sizes, line weights and colors, etc. are established. All subsequent plot classes will inherit these attributes, overriding them if necessary.

Attributes


fig_size_ratio: 2d array of figure width, height ratio fontsize1_scale: int, scale for fontsize level 1 (titles, axes labels…) fontsize2: int, scale for fontsize level 2 (legend labels, ticks…) line_color: string, color for line in plot line_style: string, style of line in plot

Expand source code
class FigureAttributes:
    """
    This is the most basic parent class -- sets the plot canvas as well as figure handling functions. This is where
    figure size, font style and sizes, line weights and colors, etc. are established. All subsequent plot classes
    will inherit these attributes, overriding them if necessary.

    Attributes
    __________

    fig_size_ratio: 2d array of figure width, height ratio
    fontsize1_scale: int, scale for fontsize level 1 (titles, axes labels...)
    fontsize2: int, scale for fontsize level 2 (legend labels, ticks...)
    line_color: string, color for line in plot
    line_style: string, style of line in plot
    """

    def __init__(self, fig_size_ratio=np.array([640, 400]), fontsize1_scale=5, fontsize2_scale=4, line_color='k',
                 line_style='-'):

        self.fig_scale = 2.0
        self.fig_dpi = 300
        self.ratio = fig_size_ratio
        self.font_size_1st_level = np.rint(self.fig_scale * fontsize1_scale)
        self.font_size_2nd_level = np.rint(self.fig_scale * fontsize2_scale)
        self.line_color = line_color
        self.line_style = line_style
        self.fig_aspect_ratio = np.rint(self.fig_scale * self.ratio)  # was 640, 360, ratio 16:9
        self.fig_face_color = "w"
        self.fig_edge_color = self.fig_face_color
        self.fig_size = self.fig_aspect_ratio / self.fig_dpi
        self.font_color = "k"
        self.font_weight = "normal"
        self.line_weight = np.rint(self.fig_scale * 1)
        self.tick_size = self.font_size_2nd_level
        self.legend_label_size = self.font_size_2nd_level

        self.fig = None
class FigureParameters (aspect_ratio: AspectRatioType)

This class encapsulates the logic of computing figure size

Expand source code
class FigureParameters:
    """
    This class encapsulates the logic of computing figure size
    """

    def __init__(self, aspect_ratio: AspectRatioType):
        if aspect_ratio == AspectRatioType.R640x360:
            self.width = 640
            self.height = 360
            self.scale_factor = 1.0 / 3.0
        elif aspect_ratio == AspectRatioType.R1280x720:
            self.width = 1280
            self.height = 720
            self.scale_factor = 2.0 / 3.0
        elif aspect_ratio == AspectRatioType.R1920x1080:
            self.width = 1920
            self.height = 1080
            self.scale_factor = 1.25
        elif aspect_ratio == AspectRatioType.R2560x1440:
            self.width = 2560
            self.height = 1440
            self.scale_factor = 4.0 / 3.0
        else:
            self.width = 3840
            self.height = 2160
            self.scale_factor = 2.0

        scale = self.scale_factor * self.height / 8
        self.figure_size_x = int(self.width / scale)
        self.figure_size_y = int(self.height / scale)
        self.text_size = int(2.0 * self.height / scale)