Skip to content

Bathymetry Plotting

sensingpy.bathymetry.plot provides specialised plotting classes and helper functions for visualising bathymetry calibration and validation results.


CalibrationPlot

CalibrationPlot

CalibrationPlot(title_font_size: int = 30, label_font_size: int = 20, tick_font_size: int = 15, legend_font_size: int = 20, font_family: str = 'Times New Roman')

Bases: object

Class for creating standardized bathymetry calibration plots.

This class provides methods for plotting calibration data for satellite-derived bathymetry models, including regression lines, scatterplots, and statistics. It handles consistent styling and formatting across plots.

Parameters:

Name Type Description Default
title_font_size int

Font size for plot titles, by default 30

30
label_font_size int

Font size for axis labels, by default 20

20
tick_font_size int

Font size for axis ticks, by default 15

15
legend_font_size int

Font size for legend text, by default 20

20
font_family str

Font family for all text elements, by default 'Times New Roman'

'Times New Roman'

Constructor with the size of the texts

Source code in sensingpy/bathymetry/plot.py
def __init__(
    self,
    title_font_size: int = 30,
    label_font_size: int = 20,
    tick_font_size: int = 15,
    legend_font_size: int = 20,
    font_family: str = "Times New Roman",
):
    """Constructor with the size of the texts"""

    self.legend_font_size = legend_font_size
    self.label_font_size = label_font_size
    self.tick_font_size = tick_font_size
    self.title_font_size = title_font_size
    self.font_family = font_family

Attributes

legend_font_size instance-attribute
legend_font_size = legend_font_size
label_font_size instance-attribute
label_font_size = label_font_size
tick_font_size instance-attribute
tick_font_size = tick_font_size
title_font_size instance-attribute
title_font_size = title_font_size
font_family instance-attribute
font_family = font_family

Functions

add_calibration_scatter
add_calibration_scatter(model: LinearModel, x: ndarray, y: ndarray, ax: Axes, c: str = 'g', **kwargs) -> Axes

Plot regression data with model fit line and statistics.

Parameters:

Name Type Description Default
model LinearModel

Calibrated linear model with slope, intercept and R² values

required
x ndarray

x-axis values (predictor variables)

required
y ndarray

y-axis values (observed depths)

required
ax Axes

Matplotlib axes on which to plot

required
c str

Color for scatter points, by default 'g'

'g'
**kwargs

Additional keyword arguments passed to scatter()

{}

Returns:

Type Description
Axes

Matplotlib axes with plotted data

Notes

The plot includes the best-fit line, R² value, linear equation, and sample size in the legend.

Source code in sensingpy/bathymetry/plot.py
def add_calibration_scatter(
    self,
    model: LinearModel,
    x: np.ndarray,
    y: np.ndarray,
    ax: Axes,
    c: str = "g",
    **kwargs,
) -> Axes:
    """
    Plot regression data with model fit line and statistics.

    Parameters
    ----------
    model : LinearModel
        Calibrated linear model with slope, intercept and R² values
    x : np.ndarray
        x-axis values (predictor variables)
    y : np.ndarray
        y-axis values (observed depths)
    ax : Axes
        Matplotlib axes on which to plot
    c : str, optional
        Color for scatter points, by default 'g'
    **kwargs
        Additional keyword arguments passed to scatter()

    Returns
    -------
    Axes
        Matplotlib axes with plotted data

    Notes
    -----
    The plot includes the best-fit line, R² value, linear equation,
    and sample size in the legend.
    """

    ax.tick_params(axis="both", which="major", labelsize=self.tick_font_size)
    ax.tick_params(axis="both", which="minor", labelsize=self.tick_font_size)

    x_range = np.linspace(np.nanmin(x), np.nanmax(x), 100).reshape(-1, 1)
    y_pred = model.predict(x_range)

    ax.plot(x_range, y_pred, "--k", label=f"$R² = {model.r_square:.4f}$")
    ax.scatter(
        x,
        y,
        label=f"$y = {model.slope:.4f}x {model.intercept:+.4f}$",
        c=c,
        **kwargs,
    )
    ax.scatter(x[0], y[0], label=f"$n = {x.size}$", alpha=0)
    ax.grid()

    return ax
add_legend
add_legend(ax: Axes) -> Axes

Add formatted legend to axes.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axes to which legend will be added

required

Returns:

Type Description
Axes

Matplotlib axes with legend

Notes

The legend is formatted according to the font settings specified during class initialization, with handles hidden.

Source code in sensingpy/bathymetry/plot.py
def add_legend(self, ax: Axes) -> Axes:
    """
    Add formatted legend to axes.

    Parameters
    ----------
    ax : Axes
        Matplotlib axes to which legend will be added

    Returns
    -------
    Axes
        Matplotlib axes with legend

    Notes
    -----
    The legend is formatted according to the font settings specified
    during class initialization, with handles hidden.
    """

    legend = ax.legend(
        loc="upper left",
        handlelength=0,
        handletextpad=0,
        prop={"size": self.legend_font_size, "family": self.font_family},
    )
    for item in legend.legend_handles:
        item.set_visible(False)

    return ax
add_labels
add_labels(ax: Axes, title: str = None, xlabel: str = None, ylabel: str = None) -> Axes

Add formatted title and axis labels to plot.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axes to which labels will be added

required
title str

Plot title, by default None

None
xlabel str

x-axis label, by default None

None
ylabel str

y-axis label, by default None

None

Returns:

Type Description
Axes

Matplotlib axes with labels

Notes

Labels are formatted according to the font settings specified during class initialization.

Source code in sensingpy/bathymetry/plot.py
def add_labels(
    self, ax: Axes, title: str = None, xlabel: str = None, ylabel: str = None
) -> Axes:
    """
    Add formatted title and axis labels to plot.

    Parameters
    ----------
    ax : Axes
        Matplotlib axes to which labels will be added
    title : str, optional
        Plot title, by default None
    xlabel : str, optional
        x-axis label, by default None
    ylabel : str, optional
        y-axis label, by default None

    Returns
    -------
    Axes
        Matplotlib axes with labels

    Notes
    -----
    Labels are formatted according to the font settings specified
    during class initialization.
    """

    if title is not None:
        ax.set_title(
            title,
            fontdict={"size": self.title_font_size, "family": self.font_family},
        )
    if xlabel is not None:
        ax.set_xlabel(
            xlabel,
            fontdict={"size": self.label_font_size, "family": self.font_family},
        )
    if ylabel is not None:
        ax.set_ylabel(
            ylabel,
            fontdict={"size": self.label_font_size, "family": self.font_family},
        )

    return ax

ValidationPlot

ValidationPlot

ValidationPlot(title_font_size: int = 30, label_font_size: int = 20, tick_font_size: int = 15, legend_font_size: int = 20, font_family: str = 'Times New Roman')

Bases: object

Class for creating standardized bathymetry validation plots.

This class provides methods for visualizing and evaluating the performance of satellite-derived bathymetry models, including density scatter plots and error histograms with statistical metrics.

Parameters:

Name Type Description Default
title_font_size int

Font size for plot titles, by default 30

30
label_font_size int

Font size for axis labels, by default 20

20
tick_font_size int

Font size for axis ticks, by default 15

15
legend_font_size int

Font size for legend text, by default 20

20
font_family str

Font family for all text elements, by default 'Times New Roman'

'Times New Roman'

Constructor with the size of the texts

Source code in sensingpy/bathymetry/plot.py
def __init__(
    self,
    title_font_size: int = 30,
    label_font_size: int = 20,
    tick_font_size: int = 15,
    legend_font_size: int = 20,
    font_family: str = "Times New Roman",
):
    """Constructor with the size of the texts"""

    self.legend_font_size = legend_font_size
    self.label_font_size = label_font_size
    self.tick_font_size = tick_font_size
    self.title_font_size = title_font_size
    self.font_family = font_family

Attributes

legend_font_size instance-attribute
legend_font_size = legend_font_size
label_font_size instance-attribute
label_font_size = label_font_size
tick_font_size instance-attribute
tick_font_size = tick_font_size
title_font_size instance-attribute
title_font_size = title_font_size
font_family instance-attribute
font_family = font_family

Functions

add_densed_scatter
add_densed_scatter(summary: ValidationSummary, ax: Axes, s: float = 5, cmap: str = 'viridis_r', vmin: float = None, vmax: float = None, x_min: int = None, x_max: int = None, step=2, density: Mapping[str, Any] = None) -> Tuple[Axes, Any]

Create a density-colored scatter plot comparing modeled vs. in-situ depths.

Parameters:

Name Type Description Default
summary ValidationSummary

Validation summary object containing model predictions and ground truth

required
ax Axes

Matplotlib axes on which to plot

required
s float

Point size for scatter plot, by default 5

5
cmap str

Matplotlib colormap for density representation, by default 'viridis_r'

'viridis_r'
vmin float

Minimum value for colormap normalization, by default None

None
vmax float

Maximum value for colormap normalization, by default None

None
density Mapping[str, Any]

Settings for density calculation, by default None. Format: {'method': 'precise'|'approximate', 'bins': int}

None

Returns:

Type Description
Tuple[Axes, Any]

Matplotlib axes with plot and the colorbar mappable object

Notes

Point color represents data density, helping visualize the distribution of points in crowded scatter plots. A 1:1 line is added to show perfect agreement between model and observations.

Source code in sensingpy/bathymetry/plot.py
def add_densed_scatter(
    self,
    summary: ValidationSummary,
    ax: Axes,
    s: float = 5,
    cmap: str = "viridis_r",
    vmin: float = None,
    vmax: float = None,
    x_min: int = None,
    x_max: int = None,
    step=2,
    density: Mapping[str, Any] = None,
) -> Tuple[Axes, Any]:
    """
    Create a density-colored scatter plot comparing modeled vs. in-situ depths.

    Parameters
    ----------
    summary : ValidationSummary
        Validation summary object containing model predictions and ground truth
    ax : Axes
        Matplotlib axes on which to plot
    s : float, optional
        Point size for scatter plot, by default 5
    cmap : str, optional
        Matplotlib colormap for density representation, by default 'viridis_r'
    vmin : float, optional
        Minimum value for colormap normalization, by default None
    vmax : float, optional
        Maximum value for colormap normalization, by default None
    density : Mapping[str, Any], optional
        Settings for density calculation, by default None.
        Format: {'method': 'precise'|'approximate', 'bins': int}

    Returns
    -------
    Tuple[Axes, Any]
        Matplotlib axes with plot and the colorbar mappable object

    Notes
    -----
    Point color represents data density, helping visualize the distribution
    of points in crowded scatter plots. A 1:1 line is added to show perfect
    agreement between model and observations.
    """

    x, y, z, norm = self.__select_density_method(summary, density)

    if x_min is None:
        x_min = np.floor(min(x.min(), y.min()))
    if x_max is None:
        x_max = np.ceil(max(x.max(), y.max()))

    ax.set_aspect("equal", adjustable="box")
    ticks = np.arange(x_min, x_max, step)
    ax.set_xticks(ticks)
    ax.set_yticks(ticks)

    mappable = ax.scatter(
        x, y, c=z, s=s, cmap=cmap, vmin=vmin, vmax=vmax, norm=norm
    )
    ax.plot([x_min, x_max], [x_min, x_max], "--k", alpha=0.75, zorder=9)
    ax.set_xlim(x_min, x_max)
    ax.set_ylim(x_min, x_max)
    self.add_labels(
        ax, title="SDB vs In Situ", xlabel="In Situ (m)", ylabel="SDB (m)"
    )

    fig = ax.figure
    divider = make_axes_locatable(ax)
    cax = divider.append_axes(
        "right", size="4%", pad=0.08
    )  # ancho y separación ajustables
    colorbar = fig.colorbar(mappable, cax=cax)

    colorbar.ax.tick_params(
        axis="both", which="major", labelsize=self.tick_font_size
    )
    colorbar.ax.tick_params(
        axis="both", which="minor", labelsize=self.tick_font_size
    )

    ax.tick_params(axis="both", which="major", labelsize=self.tick_font_size)
    ax.tick_params(axis="both", which="minor", labelsize=self.tick_font_size)

    return ax, colorbar
add_residuals
add_residuals(summary: ValidationSummary, ax: Axes, x_lim: int = 5, metrics: List[str] = None, **hist_kwargs) -> Axes

Create a histogram of model residuals with statistical metrics.

Parameters:

Name Type Description Default
summary ValidationSummary

Validation summary object containing error statistics

required
ax Axes

Matplotlib axes on which to plot

required
x_lim int

Symmetric x-axis limit for histogram, by default 5

5
metrics List[str]

List of metric names to display in legend, by default None

None
**hist_kwargs

Additional keyword arguments passed to seaborn's histplot()

{}

Returns:

Type Description
Axes

Matplotlib axes with plotted histogram

Notes

The histogram shows the distribution of residual errors (in-situ minus model predictions), with a kernel density estimate overlay. Statistical metrics are shown in a text box on the plot.

Source code in sensingpy/bathymetry/plot.py
def add_residuals(
    self,
    summary: ValidationSummary,
    ax: Axes,
    x_lim: int = 5,
    metrics: List[str] = None,
    **hist_kwargs,
) -> Axes:
    """
    Create a histogram of model residuals with statistical metrics.

    Parameters
    ----------
    summary : ValidationSummary
        Validation summary object containing error statistics
    ax : Axes
        Matplotlib axes on which to plot
    x_lim : int, optional
        Symmetric x-axis limit for histogram, by default 5
    metrics : List[str], optional
        List of metric names to display in legend, by default None
    **hist_kwargs
        Additional keyword arguments passed to seaborn's histplot()

    Returns
    -------
    Axes
        Matplotlib axes with plotted histogram

    Notes
    -----
    The histogram shows the distribution of residual errors (in-situ minus
    model predictions), with a kernel density estimate overlay. Statistical
    metrics are shown in a text box on the plot.
    """

    if metrics is None:
        metrics = []

    ax = sns.histplot(
        summary.error,
        ax=ax,
        kde=True,
        color="skyblue",
        edgecolor="black",
        **hist_kwargs,
    )
    ax.set_xlim(-x_lim, x_lim)

    ax.tick_params(axis="both", which="major", labelsize=self.tick_font_size)
    ax.tick_params(axis="both", which="minor", labelsize=self.tick_font_size)

    self.add_labels(ax, title="SDB - In Situ", xlabel="Residual error (m)")

    legend = "\n".join(
        [f"N = {summary.N}"]
        + [f"{metric} = {summary[metric]:.3f} (m)" for metric in metrics]
    )
    background = {"facecolor": "white", "alpha": 0.3, "boxstyle": "round,pad=0.3"}
    ax.text(
        0.1,
        0.95,
        legend,
        fontsize=self.legend_font_size,
        transform=ax.transAxes,
        verticalalignment="top",
        bbox=background,
    )

    return ax
add_labels
add_labels(ax: Axes, title: str = None, xlabel: str = None, ylabel: str = None) -> Axes

Add formatted title and axis labels to plot.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axes to which labels will be added

required
title str

Plot title, by default None

None
xlabel str

x-axis label, by default None

None
ylabel str

y-axis label, by default None

None

Returns:

Type Description
Axes

Matplotlib axes with labels

Notes

Labels are formatted according to the font settings specified during class initialization.

Source code in sensingpy/bathymetry/plot.py
def add_labels(
    self, ax: Axes, title: str = None, xlabel: str = None, ylabel: str = None
) -> Axes:
    """
    Add formatted title and axis labels to plot.

    Parameters
    ----------
    ax : Axes
        Matplotlib axes to which labels will be added
    title : str, optional
        Plot title, by default None
    xlabel : str, optional
        x-axis label, by default None
    ylabel : str, optional
        y-axis label, by default None

    Returns
    -------
    Axes
        Matplotlib axes with labels

    Notes
    -----
    Labels are formatted according to the font settings specified
    during class initialization.
    """

    if title is not None:
        ax.set_title(
            title,
            fontdict={"size": self.title_font_size, "family": self.font_family},
        )
    if xlabel is not None:
        ax.set_xlabel(
            xlabel,
            fontdict={"size": self.label_font_size, "family": self.font_family},
        )
    if ylabel is not None:
        ax.set_ylabel(
            ylabel,
            fontdict={"size": self.label_font_size, "family": self.font_family},
        )

    return ax

get_precise_density

get_precise_density

get_precise_density(X: ndarray, y: ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Any]

Calculate accurate point density using Gaussian kernel density estimation.

Parameters:

Name Type Description Default
X ndarray

x-coordinates (in-situ depth values)

required
y ndarray

y-coordinates (modeled depth values)

required

Returns:

Type Description
Tuple[ndarray, ndarray, ndarray, Any]

x-values, y-values, density values, and colormap normalization

Notes

This method uses scipy's gaussian_kde for accurate kernel density estimation. It provides smooth density estimates but can be computationally intensive for large datasets.

Source code in sensingpy/bathymetry/plot.py
def get_precise_density(
    X: np.ndarray, y: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Any]:
    """
    Calculate accurate point density using Gaussian kernel density estimation.

    Parameters
    ----------
    X : np.ndarray
        x-coordinates (in-situ depth values)
    y : np.ndarray
        y-coordinates (modeled depth values)

    Returns
    -------
    Tuple[np.ndarray, np.ndarray, np.ndarray, Any]
        x-values, y-values, density values, and colormap normalization

    Notes
    -----
    This method uses scipy's gaussian_kde for accurate kernel density
    estimation. It provides smooth density estimates but can be computationally
    intensive for large datasets.
    """

    xy = np.vstack([X, y])
    density = gaussian_kde(xy)(xy)

    idx = density.argsort()
    X, y, density = X[idx], y[idx], density[idx]
    norm = Normalize(vmin=np.min(density), vmax=np.max(density))

    return X, y, density, norm

get_approximate_density

get_approximate_density

get_approximate_density(X: ndarray, y: ndarray, bins: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Any]

Calculate approximate point density using 2D histogram and interpolation.

Parameters:

Name Type Description Default
X ndarray

x-coordinates (in-situ depth values)

required
y ndarray

y-coordinates (modeled depth values)

required
bins int

Number of bins for the 2D histogram

required

Returns:

Type Description
Tuple[ndarray, ndarray, ndarray, Any]

x-values, y-values, density values, and colormap normalization

Notes

This method uses numpy's histogram2d and scipy's interpn for faster but less accurate density estimation. It's more suitable for large datasets where performance is a concern.

Source code in sensingpy/bathymetry/plot.py
def get_approximate_density(
    X: np.ndarray, y: np.ndarray, bins: int
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Any]:
    """
    Calculate approximate point density using 2D histogram and interpolation.

    Parameters
    ----------
    X : np.ndarray
        x-coordinates (in-situ depth values)
    y : np.ndarray
        y-coordinates (modeled depth values)
    bins : int
        Number of bins for the 2D histogram

    Returns
    -------
    Tuple[np.ndarray, np.ndarray, np.ndarray, Any]
        x-values, y-values, density values, and colormap normalization

    Notes
    -----
    This method uses numpy's histogram2d and scipy's interpn for faster but
    less accurate density estimation. It's more suitable for large datasets
    where performance is a concern.
    """

    data, x_e, y_e = np.histogram2d(X, y, bins=bins, density=True)
    density = interpn(
        (0.5 * (x_e[1:] + x_e[:-1]), 0.5 * (y_e[1:] + y_e[:-1])),
        data,
        np.vstack([X, y]).T,
        method="splinef2d",
        bounds_error=False,
    )

    density[np.where(np.isnan(density))] = 0.0
    density[density < 0] = 0.0

    idx = density.argsort()
    X, y, density = X[idx], y[idx], density[idx]

    norm = Normalize(vmin=np.min(density), vmax=np.max(density))

    return X, y, density, norm

match_subplot_sizes

match_subplot_sizes

match_subplot_sizes(base: Axes, other: Axes, tight: bool = False) -> None
Source code in sensingpy/bathymetry/plot.py
def match_subplot_sizes(base: Axes, other: Axes, tight: bool = False) -> None:
    fig = base.figure

    if tight:
        fig.tight_layout()

    fig.canvas.draw()
    pos0 = base.get_position()
    pos1 = other.get_position()
    other.set_position([pos1.x0, pos0.y0, pos1.width, pos0.height])