From f05554bf5530c9ce51ee2a6869c39a8fbedc9a3e Mon Sep 17 00:00:00 2001 From: Sebastian Weigand Date: Sat, 18 Jun 2022 21:47:12 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20plot=20function=20for=20guida?= =?UTF-8?q?nce=20spectra=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👌 Prevent crash of plot_fitted_traces when guidance spectrum in result * ✨ Added 'plot_guidance' function * 👌 Fall back to plot_guidance in plot_overview for guidance spectrum instead of crashing * 🩹 Respect figure_only argument for guidance case in plot_overview --- pyglotaran_extras/__init__.py | 2 + pyglotaran_extras/plotting/plot_guidance.py | 65 +++++++++++++++++++++ pyglotaran_extras/plotting/plot_overview.py | 15 +++-- pyglotaran_extras/plotting/plot_traces.py | 4 +- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 pyglotaran_extras/plotting/plot_guidance.py diff --git a/pyglotaran_extras/__init__.py b/pyglotaran_extras/__init__.py index 96dec3ad..e702f0d1 100644 --- a/pyglotaran_extras/__init__.py +++ b/pyglotaran_extras/__init__.py @@ -2,6 +2,7 @@ from pyglotaran_extras.io.load_data import load_data from pyglotaran_extras.io.setup_case_study import setup_case_study from pyglotaran_extras.plotting.plot_data import plot_data_overview +from pyglotaran_extras.plotting.plot_guidance import plot_guidance from pyglotaran_extras.plotting.plot_overview import plot_overview from pyglotaran_extras.plotting.plot_overview import plot_simple_overview from pyglotaran_extras.plotting.plot_traces import plot_fitted_traces @@ -15,6 +16,7 @@ "plot_simple_overview", "plot_fitted_traces", "select_plot_wavelengths", + "plot_guidance", ] __version__ = "0.6.0" diff --git a/pyglotaran_extras/plotting/plot_guidance.py b/pyglotaran_extras/plotting/plot_guidance.py new file mode 100644 index 00000000..ef955788 --- /dev/null +++ b/pyglotaran_extras/plotting/plot_guidance.py @@ -0,0 +1,65 @@ +"""Module containing guidance spectra plotting functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import matplotlib.pyplot as plt + +from pyglotaran_extras.io.load_data import load_data +from pyglotaran_extras.plotting.style import PlotStyle +from pyglotaran_extras.plotting.utils import add_cycler_if_not_none + +if TYPE_CHECKING: + from cycler import Cycler + from matplotlib.figure import Figure + from matplotlib.pyplot import Axes + + from pyglotaran_extras.types import DatasetConvertible + + +def plot_guidance( + result: DatasetConvertible, + figsize: tuple[int, int] = (15, 5), + title: str = "Guidance Overview", + y_label: str = "a.u.", + cycler: Cycler | None = PlotStyle().cycler, +) -> tuple[Figure, Axes]: + """Plot overview for a guidance spectrum. + + Parameters + ---------- + result: DatasetConvertible + Result from a pyglotaran optimization as dataset, Path or Result object. + figsize: tuple[int, int] + Size of the figure (N, M) in inches. Defaults to (15, 5) + title: str + Title to add to the figure. Defaults to "Guidance Overview" + y_label: str + Label used for the y-axis of each subplot. Defaults to "a.u." + cycler: Cycler | None + Plot style cycler to use. Defaults to PlotStyle().cycler. + + Returns + ------- + tuple[Figure, Axes] + Figure and axes which can then be refined by the user. + """ + res = load_data(result) + fig, axes = plt.subplots(1, 2, figsize=figsize) + + for axis in axes: + add_cycler_if_not_none(axis, cycler) + + res.data.plot(x="spectral", ax=axes[0], label="data") + res.fitted_data.plot(x="spectral", ax=axes[0], label="fit") + res.residual.plot(x="spectral", ax=axes[1], label="residual") + + for axis in axes: + axis.set_ylabel(y_label) + axes[0].legend() + axes[0].set_title("Fit quality") + axes[1].set_title("Residual") + fig.suptitle(title, fontsize=28) + plt.tight_layout() + + return fig, axes diff --git a/pyglotaran_extras/plotting/plot_overview.py b/pyglotaran_extras/plotting/plot_overview.py index 88e67a42..f41a18ca 100644 --- a/pyglotaran_extras/plotting/plot_overview.py +++ b/pyglotaran_extras/plotting/plot_overview.py @@ -11,6 +11,7 @@ from pyglotaran_extras.deprecation.deprecation_utils import PyglotaranExtrasApiDeprecationWarning from pyglotaran_extras.io.load_data import load_data from pyglotaran_extras.plotting.plot_concentrations import plot_concentrations +from pyglotaran_extras.plotting.plot_guidance import plot_guidance from pyglotaran_extras.plotting.plot_residual import plot_residual from pyglotaran_extras.plotting.plot_spectra import plot_spectra from pyglotaran_extras.plotting.plot_svd import plot_svd @@ -91,6 +92,12 @@ def plot_overview( """ res = load_data(result) + if res.coords["time"].values.size == 1: + fig, axes = plot_guidance(res) + if figure_only is True: + return fig + return fig, axes + # Plot dimensions M = 4 N = 3 @@ -125,12 +132,10 @@ def plot_overview( plot_residual( res, axes[1, 0], linlog=linlog, linthresh=linthresh, show_data=show_data, cycler=cycler ) - # plt.tight_layout(pad=3, w_pad=4.0, h_pad=4.0) - if figure_only is True: - warn(PyglotaranExtrasApiDeprecationWarning(FIG_ONLY_WARNING), stacklevel=2) - return fig - else: + if figure_only is False: return fig, axes + warn(PyglotaranExtrasApiDeprecationWarning(FIG_ONLY_WARNING), stacklevel=2) + return fig def plot_simple_overview( diff --git a/pyglotaran_extras/plotting/plot_traces.py b/pyglotaran_extras/plotting/plot_traces.py index 85bd5afd..32e33d14 100644 --- a/pyglotaran_extras/plotting/plot_traces.py +++ b/pyglotaran_extras/plotting/plot_traces.py @@ -80,6 +80,8 @@ def plot_data_and_fits( result_map = result_dataset_mapping(result) add_cycler_if_not_none(axis, cycler) for dataset_name in result_map.keys(): + if result_map[dataset_name].coords["time"].values.size == 1: + continue spectral_coords = result_map[dataset_name].coords["spectral"].values if spectral_coords.min() <= wavelength <= spectral_coords.max(): result_data = result_map[dataset_name].sel(spectral=[wavelength], method="nearest") @@ -135,7 +137,7 @@ def plot_fitted_traces( This avoids having the plot go to infinity around zero. Defaults to 1. divide_by_scale : bool Whether or not to divide the data by the dataset scale used for optimization. - . Defaults to True. + Defaults to True. per_axis_legend : bool Whether to use a legend per plot or for the whole figure. Defaults to False. figsize : tuple[int, int]