From 28373dfad9a80953e09c0ddcdc8b670654077ea0 Mon Sep 17 00:00:00 2001 From: daquintero Date: Fri, 20 Sep 2024 00:44:00 +0200 Subject: [PATCH] Process towards further signal analysis --- .../08a_pcb_interposer_characterisation.py | 57 +++++++++++++- .../0/instance.json | 2 + .../1/instance.json | 2 + .../2/instance.json | 2 + .../3/instance.json | 2 + .../experiment.json | 8 ++ .../0/instance.json | 2 + .../1/instance.json | 2 + .../2/instance.json | 2 + .../3/instance.json | 2 + .../experiment.json | 8 ++ .../pcb_rf_vna_measurement/0/instance.json | 5 +- .../pcb_rf_vna_measurement/1/instance.json | 5 +- .../pcb_rf_vna_measurement/experiment.json | 10 ++- piel/analysis/metrics/__init__.py | 1 + piel/analysis/metrics/statistics.py | 78 +++++++++++++++++++ piel/analysis/signals/time_data/__init__.py | 5 ++ piel/analysis/signals/time_data/metrics.py | 68 ++++++++++++++++ piel/analysis/signals/time_data/transform.py | 36 +++++++++ piel/types/metrics.py | 20 +++++ piel/visual/__init__.py | 1 + piel/visual/plot/signals/__init__.py | 1 + piel/visual/plot/signals/overlay.py | 48 ++++++++++++ 23 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 piel/analysis/metrics/__init__.py create mode 100644 piel/analysis/metrics/statistics.py create mode 100644 piel/analysis/signals/time_data/metrics.py create mode 100644 piel/analysis/signals/time_data/transform.py create mode 100644 piel/visual/plot/signals/__init__.py create mode 100644 piel/visual/plot/signals/overlay.py diff --git a/docs/examples/08a_pcb_interposer_characterisation/08a_pcb_interposer_characterisation.py b/docs/examples/08a_pcb_interposer_characterisation/08a_pcb_interposer_characterisation.py index 84e5bd82..4a1c166b 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/08a_pcb_interposer_characterisation.py +++ b/docs/examples/08a_pcb_interposer_characterisation/08a_pcb_interposer_characterisation.py @@ -2,6 +2,7 @@ import piel import piel.experimental as pe +import piel.analysis.signals.time_data as tda from piel.models.physical.electrical import E8364A, cables from datetime import datetime @@ -642,8 +643,58 @@ def calibration_propagation_delay_experiment( # `piel.analysis` also provides some functionality to analyse the corresponding time-data accordingly. # -# For example, we might want to extract only the rising edge section of the signals: +# For example, we might want to extract only the rising edge section of one of the measured signals: -piel.analysis.signals.time_data +example_signal = calibration_propagation_delay_experiment_data.data.collection[ + 3 +].dut_waveform +example_signal_measurements = ( + calibration_propagation_delay_experiment_data.data.collection[3].measurements +) + +help(tda.extract_rising_edges) + +example_signal_rising_edge_list = tda.extract_rising_edges( + signal=example_signal, lower_threshold_ratio=0.1, upper_threshold_ratio=0.9 +) + +# We can, for example, plot all these signals overlaid on top of each other - easily by just offsetting to a base reference time: -dir(piel) +offset_example_signal_rising_edge_list = tda.offset_time_signals( + example_signal_rising_edge_list +) + +piel.visual.plot.signals.plot_multi_data_time_signal( + offset_example_signal_rising_edge_list +) + +# We can technically also extract some statistical metrics from this data which we could use to compare with the measured statistics: + +offset_example_signal_rising_edge_metrics = tda.extract_rising_edge_statistical_metrics( + offset_example_signal_rising_edge_list +) +offset_example_signal_rising_edge_metrics.table + +# | | Metric | Value | +# |---:|:-------------------|------------:| +# | 0 | Value | 0.020914 | +# | 1 | Mean | 0.020914 | +# | 2 | Min | -0.108703 | +# | 3 | Max | 0.150609 | +# | 4 | Standard Deviation | 0.00727889 | +# | 5 | Count | 17 | + +# We could now compare this to the metrics the oscilloscope calculated for us previously: + +example_signal_measurements["pk-pk_ch2__v"].table + +# | | Metric | Value | +# |---:|:-------------------|:-------------------| +# | 0 | Value | 0.274796879094793 | +# | 1 | Mean | 0.276517693380144 | +# | 2 | Min | 0.02445312536438 | +# | 3 | Max | 0.345625005150214 | +# | 4 | Standard Deviation | 0.0039634275277739 | +# | 5 | Count | 9.91k | + +# We can see the measurements are approximately close enough which is pretty cool! I would still trust the device measurements more, but with this functionality it is possible to compare a given waveform to a stastistical output from a machine. diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/0/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/0/instance.json index 0ee5dae2..c43d1816 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/0/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/0/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/1/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/1/instance.json index a730dabf..6d7a8e75 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/1/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/1/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/2/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/2/instance.json index 5f5974e0..1ab07b09 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/2/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/2/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/3/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/3/instance.json index 52df5bd4..9d8bffd6 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/3/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/3/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/experiment.json b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/experiment.json index dd4e9d98..41828d32 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/experiment.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/calibration_multi_frequency_through_propagation_measurement/experiment.json @@ -86,6 +86,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -116,6 +117,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -216,6 +218,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -246,6 +249,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -346,6 +350,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -376,6 +381,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -476,6 +482,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -506,6 +513,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/0/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/0/instance.json index 2fdd35a3..96616670 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/0/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/0/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/1/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/1/instance.json index e197316f..115e933a 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/1/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/1/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/2/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/2/instance.json index 35939781..bd317dd8 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/2/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/2/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/3/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/3/instance.json index ce7b455a..44d8c5ec 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/3/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/3/instance.json @@ -81,6 +81,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -111,6 +112,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/experiment.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/experiment.json index 441c6d05..7444034b 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/experiment.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_multi_frequency_through_propagation_measurement/experiment.json @@ -86,6 +86,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -116,6 +117,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -222,6 +224,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -252,6 +255,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -358,6 +362,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -388,6 +393,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -494,6 +500,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -524,6 +531,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/0/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/0/instance.json index 31300c33..200b128a 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/0/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/0/instance.json @@ -163,6 +163,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -193,6 +194,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -223,6 +225,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -234,7 +237,7 @@ "goal": "", "parameters": {}, "index": 0, - "date_configured": "2024-09-19 23:02:49.605687", + "date_configured": "2024-09-20 00:34:16.673180", "date_measured": "", "measurement_configuration_list": [ { diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/1/instance.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/1/instance.json index e4d62f81..cbc91594 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/1/instance.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/1/instance.json @@ -163,6 +163,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -193,6 +194,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -223,6 +225,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -234,7 +237,7 @@ "goal": "", "parameters": {}, "index": 1, - "date_configured": "2024-09-19 23:02:49.605836", + "date_configured": "2024-09-20 00:34:16.673332", "date_measured": "", "measurement_configuration_list": [ { diff --git a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/experiment.json b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/experiment.json index b4b4e1ea..b858e9df 100644 --- a/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/experiment.json +++ b/docs/examples/08a_pcb_interposer_characterisation/data/pcb_rf_vna_measurement/experiment.json @@ -168,6 +168,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -198,6 +199,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -228,6 +230,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -239,7 +242,7 @@ "goal": "", "parameters": {}, "index": 0, - "date_configured": "2024-09-19 23:02:49.605687", + "date_configured": "2024-09-20 00:34:16.673180", "date_measured": "", "measurement_configuration_list": [ { @@ -422,6 +425,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -452,6 +456,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -482,6 +487,7 @@ "min": 0, "max": 0, "standard_deviation": 0, + "count": null, "unit": { "name": "second", "datum": "second", @@ -493,7 +499,7 @@ "goal": "", "parameters": {}, "index": 1, - "date_configured": "2024-09-19 23:02:49.605836", + "date_configured": "2024-09-20 00:34:16.673332", "date_measured": "", "measurement_configuration_list": [ { diff --git a/piel/analysis/metrics/__init__.py b/piel/analysis/metrics/__init__.py new file mode 100644 index 00000000..c5530411 --- /dev/null +++ b/piel/analysis/metrics/__init__.py @@ -0,0 +1 @@ +from .statistics import aggregate_scalar_metrics_list diff --git a/piel/analysis/metrics/statistics.py b/piel/analysis/metrics/statistics.py new file mode 100644 index 00000000..2b4198b6 --- /dev/null +++ b/piel/analysis/metrics/statistics.py @@ -0,0 +1,78 @@ +import numpy as np +from piel.types import ScalarMetrics + + +def aggregate_scalar_metrics_list(metrics_list: list[ScalarMetrics]) -> ScalarMetrics: + """ + Aggregates a list of ScalarMetrics into a single ScalarMetrics instance. + + The aggregation is performed as follows: + - mean: Weighted mean based on count. + - min: Minimum of all min values. + - max: Maximum of all max values. + - standard_deviation: Combined standard deviation considering individual means and counts. + - count: Sum of all counts. + - unit: Must be consistent across all ScalarMetrics. + + Args: + metrics_list (List[ScalarMetrics]): List of ScalarMetrics instances to aggregate. + + Returns: + ScalarMetrics: A single ScalarMetrics instance representing the aggregated metrics. + + Raises: + ValueError: If the input list is empty or units are inconsistent. + """ + if not metrics_list: + raise ValueError("The metrics_list is empty.") + + # Extract necessary values + means = [] + min_values = [] + max_values = [] + for metric in metrics_list: + if metric.mean is None: + raise ValueError(f"ScalarMetrics '{metric}' must have 'mean' defined.") + if metric.min is None or metric.max is None: + raise ValueError( + f"ScalarMetrics '{metric}' must have 'min' and 'max' defined." + ) + means.append(metric.mean) + min_values.append(metric.min) + max_values.append(metric.max) + + total_count = len(means) + if total_count == 0: + raise ValueError("Total count is zero, cannot compute aggregated metrics.") + + # Compute aggregated mean + aggregated_mean = sum(means) / total_count + + # Compute aggregated standard deviation + if total_count > 1: + variance = sum((m - aggregated_mean) ** 2 for m in means) / (total_count - 1) + aggregated_std_dev = np.sqrt(variance) + else: + aggregated_std_dev = 0.0 # Standard deviation is zero if only one metric + + # Compute aggregated min and max + aggregated_min = min(min_values) + aggregated_max = max(max_values) + + # Aggregate value: set to aggregated mean (or any other logic as needed) + aggregated_value = aggregated_mean + + # Create the aggregated ScalarMetrics instance + aggregated_metrics = ScalarMetrics( + value=aggregated_value, + mean=aggregated_mean, + min=aggregated_min, + max=aggregated_max, + standard_deviation=aggregated_std_dev, + count=total_count, + unit=metrics_list[0].unit, + ) + + # TODO using the last metrics list for now + + return aggregated_metrics diff --git a/piel/analysis/signals/time_data/__init__.py b/piel/analysis/signals/time_data/__init__.py index 0faa0b9f..98bc2b48 100644 --- a/piel/analysis/signals/time_data/__init__.py +++ b/piel/analysis/signals/time_data/__init__.py @@ -1 +1,6 @@ from .transition import extract_rising_edges +from .transform import offset_time_signals +from .metrics import ( + extract_rising_edge_metrics_list, + extract_rising_edge_statistical_metrics, +) diff --git a/piel/analysis/signals/time_data/metrics.py b/piel/analysis/signals/time_data/metrics.py new file mode 100644 index 00000000..d5d93151 --- /dev/null +++ b/piel/analysis/signals/time_data/metrics.py @@ -0,0 +1,68 @@ +import numpy as np +from piel.types import MultiDataTimeSignal, ScalarMetrics +from piel.types.units import s +from piel.analysis.metrics import aggregate_scalar_metrics_list + + +def extract_rising_edge_metrics_list( + multi_data_time_signal: MultiDataTimeSignal, +) -> list[ScalarMetrics]: + """ + Extracts scalar metrics from a collection of rising edge signals. Standard deviation is not calculated as this just + computes individual metrics list. + + Args: + multi_data_time_signal (List[DataTimeSignalData]): A list of rising edge signals. + + Returns: + List[ScalarMetrics]: A list of ScalarMetrics instances containing the extracted metrics. + """ + if not multi_data_time_signal: + raise ValueError("The multi_signal list is empty.") + + metrics_list = [] + + for signal in multi_data_time_signal: + if not signal.data: + raise ValueError(f"Signal '{signal.data_name}' has an empty data array.") + + data_array = np.array(signal.data) + + mean_val = float(np.mean(data_array)) + min_val = float(np.min(data_array)) + max_val = float(np.max(data_array)) + std_dev = None + count = None + + # Assuming 'value' is the mean; adjust if different meaning is intended + scalar_metric = ScalarMetrics( + value=mean_val, + mean=mean_val, + min=min_val, + max=max_val, + standard_deviation=std_dev, + count=count, + unit=s, + ) + + metrics_list.append(scalar_metric) + + return metrics_list + + +def extract_rising_edge_statistical_metrics( + multi_data_time_signal: MultiDataTimeSignal, +) -> ScalarMetrics: + """ + Extracts scalar metrics from a collection of rising edge signals. + + Args: + multi_data_time_signal (List[DataTimeSignalData]): A list of rising edge signals. + + Returns: + ScalarMetrics: Aggregated ScalarMetrics instance containing the extracted metrics. + + """ + metrics_list = extract_rising_edge_metrics_list(multi_data_time_signal) + aggregate_metrics = aggregate_scalar_metrics_list(metrics_list) + return aggregate_metrics diff --git a/piel/analysis/signals/time_data/transform.py b/piel/analysis/signals/time_data/transform.py new file mode 100644 index 00000000..5b487746 --- /dev/null +++ b/piel/analysis/signals/time_data/transform.py @@ -0,0 +1,36 @@ +import numpy as np +from piel.types import MultiDataTimeSignal, DataTimeSignalData + + +def offset_time_signals(multi_signal: MultiDataTimeSignal) -> MultiDataTimeSignal: + """ + Offsets the time_s array of each DataTimeSignalData in the MultiDataTimeSignal to start at 0. + + Args: + multi_signal (MultiDataTimeSignal): List of rising edge signals. + + Returns: + MultiDataTimeSignal: New list with offset time_s arrays. + """ + offset_signals = [] + for signal in multi_signal: + if not signal.time_s: + raise ValueError(f"Signal '{signal.data_name}' has an empty time_s array.") + + # Convert to numpy arrays for efficient computation + time = np.array(signal.time_s) + data = np.array(signal.data) + + # Calculate the offset (start time) + offset = time[0] + + # Apply the offset + offset_time = time - offset + + # Create a new DataTimeSignalData instance with the offset time + offset_signal = DataTimeSignalData( + time_s=offset_time.tolist(), data=data.tolist(), data_name=signal.data_name + ) + offset_signals.append(offset_signal) + + return offset_signals diff --git a/piel/types/metrics.py b/piel/types/metrics.py index 9a00293f..cbd1dc39 100644 --- a/piel/types/metrics.py +++ b/piel/types/metrics.py @@ -1,3 +1,4 @@ +import pandas as pd from piel.types.connectivity.abstract import Instance from piel.types.core import NumericalTypes from piel.types.units import Unit, ratio @@ -13,4 +14,23 @@ class ScalarMetrics(Instance): min: NumericalTypes | None = None max: NumericalTypes | None = None standard_deviation: NumericalTypes | None = None + count: NumericalTypes | None = None unit: Unit = ratio + + @property + def table(self): + # Create a dictionary with the scalar metrics + data = { + "Metric": ["Value", "Mean", "Min", "Max", "Standard Deviation", "Count"], + "Value": [ + self.value, + self.mean, + self.min, + self.max, + self.standard_deviation, + self.count, + ], + } + # Convert to a pandas DataFrame + df = pd.DataFrame(data) + return df diff --git a/piel/visual/__init__.py b/piel/visual/__init__.py index f0b933c7..3db7cbf2 100644 --- a/piel/visual/__init__.py +++ b/piel/visual/__init__.py @@ -10,6 +10,7 @@ create_axes_parameters_table_overlay, create_axes_parameters_tables_separate, ) +from .plot import signals from .data_conversion import append_row_to_dict, points_to_lines_fixed_transient from .style import activate_piel_styles from .signals import * diff --git a/piel/visual/plot/signals/__init__.py b/piel/visual/plot/signals/__init__.py new file mode 100644 index 00000000..3811189f --- /dev/null +++ b/piel/visual/plot/signals/__init__.py @@ -0,0 +1 @@ +from .overlay import plot_multi_data_time_signal diff --git a/piel/visual/plot/signals/overlay.py b/piel/visual/plot/signals/overlay.py new file mode 100644 index 00000000..6c8ccc5a --- /dev/null +++ b/piel/visual/plot/signals/overlay.py @@ -0,0 +1,48 @@ +from piel.types import MultiDataTimeSignal +import numpy as np +import matplotlib.pyplot as plt + + +def plot_multi_data_time_signal( + multi_signal: MultiDataTimeSignal, figsize: tuple = (10, 6) +): + """ + Plots all rising edge signals on the same figure with a shared x-axis. + + Args: + multi_signal (List[DataTimeSignalData]): List of rising edge signals. + figsize (tuple): Size of the plot (width, height) in inches. Default is (10, 6). + + Returns: + None + """ + if not multi_signal: + raise ValueError("The multi_signal list is empty.") + + plt.figure(figsize=figsize) + + for signal in multi_signal: + if not signal.time_s: + raise ValueError(f"Signal '{signal.data_name}' has an empty time_s array.") + + time = np.array(signal.time_s) + data = np.array(signal.data) + + plt.plot(time, data, label=signal.data_name) + + # Remove axis labels + plt.xlabel("") + plt.ylabel("") + + # Optionally, remove ticks + plt.xticks([]) + plt.yticks([]) + + # Optionally, remove the legend if labels are not needed + plt.legend().set_visible(False) + + # Add grid if desired + plt.grid(True, which="both", linestyle="--", linewidth=0.5) + + plt.title("Rising Edge Signals") + plt.show()