import piel.types
from piel.types import Unit
from piel.visual.plot.core import save
from piel.visual.plot.position import create_plot_containers
from piel.visual.plot.table import create_axes_parameters_tables_separate
from piel.visual.plot.basic import plot_simple
from piel.visual.style import secondary_color_palette
from piel.types.experimental import OscilloscopeMeasurementDataCollection
from piel.analysis.signals.time import (
extract_rising_edges,
resize_data_time_signal_units,
)
from typing import Optional, Literal, List, Union, Dict, Any
import logging
logger = logging.getLogger(__name__)
[docs]
def plot_oscilloscope_signals_time(
data_collection: OscilloscopeMeasurementDataCollection,
parameters_list: Optional[List] = None,
xlabel: Union[str, Unit] = None,
ylabel: Union[str, Unit] = None,
figure_title: Optional[str] = None,
create_parameters_tables: bool = True,
axes_subtitle_list: Optional[List[str]] = None,
label_per_axes: bool = False,
label_style: Literal["label_per_axes", "label_per_figure"] = "label_per_figure",
plot_kwargs: Optional[Dict[str, Any]] = None,
figure_kwargs: Optional[Dict[str, Any]] = None,
legend_kwargs: Optional[Dict[str, Any]] = None,
rising_edges_kwargs: Optional[Dict[str, Any]] = None,
smallest_range_s: tuple[float, float] = None,
*args,
**kwargs,
):
"""
Generates a series of plots representing oscilloscope waveforms over time, where each subplot corresponds
to a measurement in the given data collection.
----------
data_collection : OscilloscopeMeasurementDataCollection
A collection of oscilloscope measurement data, containing multiple waveforms per measurement.
parameters_list : List[str], optional
A list of parameter labels for the subplots. Defaults to indices if None.
measurement_section : List[str], optional
List of sections of the measurement for further categorization.
xlabel : str or piel.types.Unit, optional
The label for the x-axis. If a `piel.types.Unit` object is passed, data correction is applied based on the unit.
ylabel : str or piel.types.Unit, optional
The label for the y-axis. If a `piel.types.Unit` object is passed, data correction is applied based on the unit.
figure_title : str, optional
The title of the figure. Defaults to the name of the data collection.
create_parameters_tables : bool, optional
If True, creates tables of parameters for each axis. Defaults to True.
axes_subtitle_list : List[str], optional
A list of subtitles for each axis.
label_per_axes : bool, optional
If True, the x and y labels will be set individually for each axis. Defaults to False.
label_style : Literal["label_per_axes", "label_per_figure"], default="label_per_figure"
Determines whether labels are applied per axes or per figure.
plot_kwargs : dict, optional
Customization options for plotting the waveforms (e.g., line styles, colors).
figure_kwargs : dict, optional
Customization options for the figure (e.g., figsize, dpi).
legend_kwargs : dict, optional
Customization options for the legend (e.g., location, fontsize).
rising_edges_kwargs : dict, optional
Customization options for plotting rising edges (e.g., linestyle, color).
*args, **kwargs :
Additional arguments for plot customization, figure saving, or debugging.
Returns:
-------
fig : matplotlib.figure.Figure
The figure object containing the plots.
axs : list of matplotlib.axes.Axes
List of axes corresponding to the subplots.
Notes:
-----
- The function handles multiple waveforms per measurement, plotting each waveform within its respective subplot.
- If units are passed for `xlabel` or `ylabel`, a correction factor is applied to adjust the plotted data.
- Parameter tables can be created for each subplot based on the `parameters_list`.
- Rising edges can be optionally highlighted if `rising_edges_kwargs` is provided.
"""
if parameters_list is None:
parameters_list = []
if plot_kwargs is None:
plot_kwargs = {}
if legend_kwargs is None:
legend_kwargs = {"loc": "upper right"}
if figure_kwargs is None:
figure_kwargs = {}
if isinstance(xlabel, str):
xlabel_str = xlabel
x_unit = piel.types.s
elif isinstance(xlabel, Unit):
xlabel_str = xlabel.label
x_unit = xlabel
else:
xlabel_str = r"Time $s$"
x_unit = piel.types.s
if isinstance(ylabel, str):
ylabel_str = ylabel
y_unit = piel.types.V
elif isinstance(ylabel, Unit):
ylabel_str = ylabel.label
y_unit = ylabel
else:
ylabel_str = r"Voltage $V$"
y_unit = piel.types.V
if figure_title is None:
figure_title = getattr(data_collection, "name", "Oscilloscope Measurement")
fig, axs = create_plot_containers(
data_collection.collection,
sharex=True,
**figure_kwargs,
)
parameter_tables_list = []
for i, osc_measurement_data in enumerate(data_collection.collection):
if osc_measurement_data.waveform_list is None:
logger.warning(f"No waveforms found for measurement {i}. Skipping.")
axs[i].set_visible(False)
continue
ax = axs[i]
parameter_tables_list.append(
parameters_list[i] if parameters_list else f"Measurement {i + 1}"
)
for waveform in osc_measurement_data.waveform_list:
if (waveform.time_s is None) or (waveform.data is None):
logger.warning(
f"Empty waveform '{waveform.data_name}' in measurement {i}. Skipping."
)
continue
# Resize waveform data based on units
resized_waveform = resize_data_time_signal_units(
waveform,
time_unit=x_unit,
data_unit=y_unit,
)
start_time = resized_waveform.time_s[0]
end_time = resized_waveform.time_s[-1]
current_range = end_time - start_time
# Update the smallest range if the current one is smaller
if smallest_range_s is None or current_range < smallest_range_s:
smallest_range_s = current_range
smallest_start = start_time
smallest_end = end_time
# Plot the waveform
plot_simple(
resized_waveform.time_s,
resized_waveform.data,
fig=fig,
axs=[ax],
label=waveform.data_name,
plot_kwargs=plot_kwargs,
legend_kwargs=legend_kwargs,
figure_kwargs=figure_kwargs,
)
# Optionally plot rising edges
if rising_edges_kwargs is not None:
if not rising_edges_kwargs:
rising_edges_kwargs = {"linestyle": "--"}
rising_edges = extract_rising_edges(resized_waveform)
for edge in rising_edges:
plot_simple(
edge.time_s,
edge.data,
fig=fig,
axs=[ax],
# label=f"{waveform.data_name} Rising Edge",
plot_kwargs={
"color": secondary_color_palette[i],
**rising_edges_kwargs,
},
legend_kwargs=legend_kwargs,
figure_kwargs=figure_kwargs,
)
if label_per_axes:
ax.set_ylabel(ylabel_str)
ax.set_xlabel(xlabel_str)
elif label_style == "label_per_figure":
if i == len(data_collection.collection) - 1:
ax.set_xlabel(xlabel_str)
ax.set_ylabel(ylabel_str)
# Set the x-limits to the smallest range found across all waveforms
if smallest_start is not None and smallest_end is not None:
for ax in axs:
if ax.get_visible():
ax.set_xlim([smallest_start, smallest_end])
else:
logger.warning("No valid waveforms found to set x-limits.")
# Create parameter tables if required
if create_parameters_tables and parameters_list:
if len(parameters_list) == len(data_collection.collection):
try:
create_axes_parameters_tables_separate(
fig=fig, axs=axs, parameter_tables_list=parameter_tables_list
)
except Exception as e:
if kwargs.get("debug", False):
raise e
else:
logger.error(f"Failed to create parameter tables: {e}")
# Add subtitles to axes if provided
if axes_subtitle_list:
for i, subtitle in enumerate(axes_subtitle_list):
if i < len(axs):
axs[i].set_title(subtitle, loc="left")
# Set the main title of the figure
fig.suptitle(figure_title)
# Apply label style
if label_style == "label_per_figure":
fig.supxlabel(xlabel_str)
fig.supylabel(ylabel_str)
# Save the figure
save(fig, **kwargs)
return fig, axs
#
# # Example Usage
# if __name__ == "__main__":
# from piel.types.units import s, V
# from piel.types.signal.time_data import TimeSignalData
#
# # Create sample data
# waveform1 = TimeSignalData(
# time_s=[0, 1, 2, 3, 4, 5],
# data=[0, 1, 0, 1, 0, 1],
# data_name="Channel 1",
# time_s_unit=s,
# data_unit=V
# )
#
# waveform2 = TimeSignalData(
# time_s=[0, 1, 2, 3, 4, 5],
# data=[0, 0.5, 0, 0.5, 0, 0.5],
# data_name="Channel 2",
# time_s_unit=s,
# data_unit=V
# )
#
# osc_measurement = OscilloscopeMeasurementDataCollection(
# collection=[
# OscilloscopeMeasurementData(
# measurements=None,
# waveform_list=[waveform1, waveform2]
# )
# ]
# )
#
# # Plot the data
# fig, axs = plot_oscilloscope_signals_time(
# data_collection=osc_measurement,
# xlabel="Time (s)",
# ylabel="Voltage (V)",
# figure_title="Oscilloscope Waveforms",
# plot_kwargs={"linewidth": 2},
# rising_edges_kwargs={"linestyle": "--"},
# debug=True
# )