Skip to content

Plotting helpers

Package Module scubas.plotlib Class PlotArtifacts Method / Function update_rc_params, plot_transfer_function, potential_along_section, cable_potential

Return type

Plot helpers return a PlotArtifacts object to make figure/axes reuse and custom post-processing easier.

scubas.plotlib now offers lightweight plotting utilities that return a typed PlotArtifacts dataclass containing the Figure and Axes objects. The helpers respect optional “science” styling, accept numpy arrays directly, and summarise the most common inspection plots used in the cable/conductivity workflows.

Quick start

import numpy as np
from scubas.plotlib import PlotArtifacts, plot_transfer_function

class DummyTF:
    freq = np.logspace(-4, -2, 5)
    E2B = np.exp(1j * np.linspace(0, np.pi / 2, 5))

artifacts = plot_transfer_function(DummyTF())
assert isinstance(artifacts, PlotArtifacts)
artifacts.figure.savefig("transfer_function.png")

Similar helpers are available for plotting along-section potentials and whole Cable potentials.

API reference

scubas.plotlib.PlotArtifacts dataclass

Container for plot objects returned to callers.

Source code in scubas/plotlib.py
@dataclass(frozen=True)
class PlotArtifacts:
    """
    Container for plot objects returned to callers.
    """

    figure: plt.Figure
    axes: Any

scubas.plotlib.update_rc_params(params=None, science=False, latex=False)

Update matplotlib rcParams, optionally enabling the science style.

Source code in scubas/plotlib.py
def update_rc_params(
    params: Optional[Mapping[str, Any]] = None,
    science: bool = False,
    latex: bool = False,
) -> None:
    """
    Update matplotlib rcParams, optionally enabling the ``science`` style.
    """
    if science:
        try:
            import scienceplots  # type: ignore  # noqa: F401

            plt.style.use(["science", "ieee"])
        except (ImportError, OSError) as exc:
            logger.warning(
                "SciencePlots styles unavailable; falling back to default Matplotlib styling (%s)",
                exc,
            )
        finally:
            plt.rcParams.update(
                {
                    "figure.figsize": np.array([8, 6]),
                    "text.usetex": latex,
                    "font.family": "sans-serif",
                    "font.sans-serif": [
                        "Tahoma",
                        "DejaVu Sans",
                        "Lucida Grande",
                        "Verdana",
                    ],
                    "font.size": 10,
                }
            )
    if params:
        plt.rcParams.update(params)

scubas.plotlib.plot_transfer_function(Tx, xlim=(1e-06, 0.01), ylim=(0.001, 1.0), science=False)

Plot amplitude and phase of an E2B transfer function.

Source code in scubas/plotlib.py
def plot_transfer_function(
    Tx: Any,
    xlim: Sequence[float] = (1e-6, 1e-2),
    ylim: Sequence[float] = (1e-3, 1e0),
    science: bool = False,
) -> PlotArtifacts:
    """
    Plot amplitude and phase of an ``E2B`` transfer function.
    """
    update_rc_params(science=science)
    fig = plt.figure(dpi=180, figsize=(3, 2.5))
    ax_amp = fig.add_subplot(111)
    ax_amp.loglog(Tx.freq, np.abs(Tx.E2B), "r", lw=0.6, ls="-")
    ax_amp.set_xlabel(r"Frequency [Hz]")
    ax_amp.set_ylabel(r"Amplitude [mV/km/nT]", color="r")
    ax_amp.set_xlim(xlim)
    ax_amp.set_ylim(ylim)

    ax_phase = ax_amp.twinx()
    ax_phase.semilogx(Tx.freq, np.angle(Tx.E2B, deg=True), "b", lw=0.6, ls="-")
    ax_phase.set_ylabel(r"Phase [degree, $^\circ$]", color="b")
    ax_phase.set_ylim(-90, 90)
    ax_phase.set_yticks([-90, -60, -30, 0, 30, 60, 90])
    ax_phase.set_yticklabels([-90, -60, -30, 0, 30, 60, 90])
    ax_phase.set_xlim(xlim)
    return PlotArtifacts(figure=fig, axes=ax_phase)

scubas.plotlib.potential_along_section(V, x, sec=None, Vi=None, Vk=None, Z=None, Y=None, gma=None, Z0=None, science=False)

Visualise along-section potential with optional annotations.

Source code in scubas/plotlib.py
def potential_along_section(
    V: Sequence[float],
    x: Sequence[float],
    sec: Optional[int] = None,
    Vi: Optional[float] = None,
    Vk: Optional[float] = None,
    Z: Optional[float] = None,
    Y: Optional[float] = None,
    gma: Optional[float] = None,
    Z0: Optional[float] = None,
    science: bool = False,
) -> PlotArtifacts:
    """
    Visualise along-section potential with optional annotations.
    """
    update_rc_params(
        {"xtick.labelsize": 12, "ytick.labelsize": 12, "font.size": 12},
        science,
    )
    fig, ax = plt.subplots(
        nrows=1,
        ncols=1,
        dpi=150,
        figsize=(6, 3),
        sharex="all",
        sharey="all",
    )
    ax.set_ylabel("Voltage, V")
    ax.set_xlabel("Cable Length, km")
    ax.plot(x, V, "k", lw=0.8, ls="-")

    Z_scaled = Z * 1e3 if Z is not None else None
    Y_scaled = Y * 1e3 if Y is not None else None
    gma_scaled = gma * 1e3 if gma is not None else None

    details = []
    if sec is not None:
        details.append(f"Along: Bin{sec:02d}")
    if Vi is not None and Vk is not None:
        details.append(rf"$V_i,V_k\sim {Vi:.1f} V, {Vk:.1f} V$")
    if Z_scaled is not None and Y_scaled is not None:
        details.append(
            rf"$Z,Y\sim$ {frexp102str(Z_scaled)} $\Omega/km$, "
            rf"{frexp102str(Y_scaled)} $\mho/km$"
        )
    if gma_scaled is not None and Z0 is not None:
        details.append(
            rf"$\gamma,Z_0\sim$ {frexp102str(gma_scaled)} /km, {frexp102str(Z0)} $\Omega$"
        )
    details.append(f"L={np.max(x):.0f} km")

    ax.text(
        0.05,
        0.95,
        "\n".join(details),
        ha="left",
        va="top",
        transform=ax.transAxes,
        fontsize="small",
    )
    ax.set_xlim(x[0], x[-1])
    return PlotArtifacts(figure=fig, axes=ax)

scubas.plotlib.cable_potential(V, x, science=False, ylim=(-50, 50))

Plot potential along the full cable.

Source code in scubas/plotlib.py
def cable_potential(
    V: Sequence[float],
    x: Sequence[float],
    science: bool = False,
    ylim: Sequence[float] = (-50, 50),
) -> PlotArtifacts:
    """
    Plot potential along the full cable.
    """
    update_rc_params(
        {"xtick.labelsize": 12, "ytick.labelsize": 12, "font.size": 12},
        science,
    )
    fig, ax = plt.subplots(
        nrows=1,
        ncols=1,
        dpi=150,
        figsize=(6, 3),
        sharex="all",
        sharey="all",
    )
    ax.set_ylabel("Earth potential, V")
    ax.set_xlabel("Distance, km")
    ax.plot(x, V, "k", lw=0.8, ls="-")
    ax.set_xlim(x[0], x[-1])
    ax.set_ylim(ylim)
    return PlotArtifacts(figure=fig, axes=ax)