From c783de425a505d4f0de31a8809b39958997b7dca Mon Sep 17 00:00:00 2001 From: yair Date: Fri, 14 Nov 2025 14:21:40 +0200 Subject: [PATCH] feat: Add CSV logging and analysis tools for rollingsum plugin - Add csv-file property to log frame statistics - Create analyze_sma.py for automated CSV analysis with visualizations - Add comprehensive ROLLINGSUM_GUIDE.md documentation - Include debugging guide and threshold recommendations - Uses uv for Python dependency management --- ROLLINGSUM_GUIDE.md | 340 +++++++++++++++++++++++++++++++++ analyze_sma.py | 184 ++++++++++++++++++ gst/rollingsum/gstrollingsum.c | 80 +++++++- gst/rollingsum/gstrollingsum.h | 3 + 4 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 ROLLINGSUM_GUIDE.md create mode 100644 analyze_sma.py diff --git a/ROLLINGSUM_GUIDE.md b/ROLLINGSUM_GUIDE.md new file mode 100644 index 0000000..f154214 --- /dev/null +++ b/ROLLINGSUM_GUIDE.md @@ -0,0 +1,340 @@ +# GStreamer Rolling Sum Plugin Guide + +## Overview + +The `rollingsum` plugin analyzes video frames in real-time by tracking the mean pixel intensity of a specific column across frames. It maintains a rolling window of these values and can drop frames that deviate significantly from the rolling mean, useful for detecting and filtering unstable or anomalous frames. + +## How It Works + +1. **Column Analysis**: Extracts mean pixel intensity from a specified vertical column +2. **Rolling Window**: Maintains a circular buffer of recent column means +3. **Deviation Detection**: Calculates how much each frame deviates from the rolling mean +4. **Frame Filtering**: Optionally drops frames exceeding the deviation threshold +5. **CSV Logging**: Records all frame statistics for analysis + +## Plugin Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `window-size` | int | 1000 | Number of frames in rolling window (1-100000) | +| `column-index` | int | 1 | Which vertical column to analyze (0-based) | +| `stride` | int | 1 | Row sampling stride (1 = every row) | +| `threshold` | double | 0.5 | Normalized deviation threshold for dropping frames (0.0-1.0) | +| `csv-file` | string | NULL | Path to CSV file for logging (NULL = no logging) | + +### Understanding Normalized Deviation + +- **Range**: 0.0 to 1.0 +- **Calculation**: `absolute_deviation / 255.0` (for 8-bit video) +- **Meaning**: Fraction of the full pixel range + - `0.001` = deviation of ~0.255 pixel values + - `0.01` = deviation of ~2.55 pixel values + - `0.1` = deviation of ~25.5 pixel values + +## Basic Usage + +### Simple Pipeline + +```bash +gst-launch-1.0 idsueyesrc config-file=config.ini ! \ + videoconvert ! \ + video/x-raw,format=GRAY8 ! \ + rollingsum window-size=1000 column-index=1 threshold=0.0002 ! \ + autovideosink +``` + +### With CSV Logging + +```bash +gst-launch-1.0 idsueyesrc config-file=config.ini exposure=0.5 ! \ + videoconvert ! \ + video/x-raw,format=GRAY8 ! \ + rollingsum window-size=1000 column-index=1 threshold=0.0002 csv-file=output.csv ! \ + fakesink +``` + +## Debugging + +### Enable Debug Output + +Use the `GST_DEBUG` environment variable to see detailed plugin operation: + +#### Windows PowerShell + +```powershell +$env:GST_DEBUG="rollingsum:5"; gst-launch-1.0 [pipeline...] +``` + +#### Windows CMD + +```cmd +set GST_DEBUG=rollingsum:5 && gst-launch-1.0 [pipeline...] +``` + +#### Linux/Mac + +```bash +GST_DEBUG=rollingsum:5 gst-launch-1.0 [pipeline...] +``` + +### Debug Levels + +| Level | Output | +|-------|--------| +| `rollingsum:1` | Errors only | +| `rollingsum:2` | Warnings | +| `rollingsum:3` | Info messages (file open/close) | +| `rollingsum:4` | Debug (caps negotiation) | +| `rollingsum:5` | Log (all frame processing) | + +### Example Debug Output + +``` +0:00:04.029432200 DEBUG rollingsum gstrollingsum.c:436: Extracted column mean: 10.07 +0:00:04.032257100 DEBUG rollingsum gstrollingsum.c:466: Frame 1: mean=10.07, rolling_mean=10.07, deviation=0.00 (normalized=0.0000) +``` + +**Key Fields:** +- `Frame N`: Frame number +- `mean`: Current frame's column mean +- `rolling_mean`: Average of last N frames (window-size) +- `deviation`: Absolute difference +- `normalized`: Deviation as fraction of 255 + +### Common Debug Scenarios + +#### 1. Verify Plugin Loaded + +```bash +gst-inspect-1.0 rollingsum +``` + +Should show plugin details. If not found, check `GST_PLUGIN_PATH`. + +#### 2. Check CSV File Creation + +Look for this in debug output: +``` +INFO rollingsum: Opened CSV file: output.csv +``` + +#### 3. Monitor Frame Drops + +Look for: +``` +DEBUG rollingsum: Dropping frame 42: deviation 0.0005 > threshold 0.0002 +``` + +#### 4. Verify Caps Negotiation + +``` +DEBUG rollingsum: set_caps +DEBUG rollingsum: Video format: GRAY8, 1224x1026 +``` + +## CSV Analysis + +### CSV Format + +The output CSV contains: + +```csv +frame,column_mean,rolling_mean,deviation,normalized_deviation,dropped +1,10.071150,10.071150,0.000000,0.000000,0 +2,10.059454,10.065302,0.005848,0.000023,0 +... +``` + +### Analyze Results + +Use the included analysis script: + +```bash +uv run analyze_sma.py output.csv +``` + +**Output includes:** +- Statistical summary (min/max/mean/std) +- Threshold recommendations based on percentiles +- Standard deviation-based suggestions +- Visualization plots (`output_analysis.png`) + +### Interpreting Results + +The analysis provides threshold recommendations: + +| Percentile | Description | Use Case | +|------------|-------------|----------| +| 99th | Drops top 1% | Very conservative, catch only extreme outliers | +| 95th | Drops top 5% | Conservative, good for quality control | +| 90th | Drops top 10% | Balanced, moderate filtering | +| 75th | Drops top 25% | Aggressive, maximum quality | + +## Recommended Thresholds + +Based on analysis of stable camera footage: + +### For General Use + +```bash +# Conservative (1-2% frame drop) +threshold=0.0003 + +# Moderate (5-10% frame drop) +threshold=0.0002 + +# Aggressive (20-25% frame drop) +threshold=0.0001 +``` + +### For Specific Scenarios + +**High-speed acquisition** (minimal processing): +```bash +window-size=100 threshold=0.0005 +``` + +**Quality-focused** (stable scenes): +```bash +window-size=1000 threshold=0.0001 +``` + +**Real-time monitoring** (fast response): +```bash +window-size=50 threshold=0.0002 +``` + +## Troubleshooting + +### No frames being dropped (threshold too high) + +**Symptom**: `dropped` column always 0 in CSV + +**Solution**: +1. Run with CSV logging +2. Analyze with `uv run analyze_sma.py output.csv` +3. Use recommended threshold from 90th-99th percentile + +### Too many frames dropped (threshold too low) + +**Symptom**: Most frames have `dropped=1`, choppy video + +**Solution**: +1. Increase threshold (try doubling current value) +2. Check if column_index is appropriate +3. Verify video is stable (not shaking/moving) + +### CSV file not created + +**Check**: +1. File path is writable +2. Look for "Opened CSV file" in debug output (`GST_DEBUG=rollingsum:3`) +3. Verify csv-file property is set correctly + +### Column index out of range + +**Symptom**: +``` +WARNING rollingsum: Column index 1000 >= width 1224, using column 0 +``` + +**Solution**: Set `column-index` to value < video width + +### Inconsistent results + +**Possible causes**: +1. Window size too small (< 50 frames) +2. Sampling moving/dynamic content +3. Column contains edge/artifact data + +**Solutions**: +- Increase `window-size` to 500-1000 +- Choose different `column-index` (avoid edges) +- Use `stride=2` or higher for faster processing + +## Performance Tips + +1. **Larger window = more stable** but slower to adapt to scene changes +2. **Stride > 1** reduces computation but less accurate column mean +3. **CSV logging** has minimal performance impact +4. **Debug level 5** can produce massive logs, use only when needed + +## Integration Examples + +### Python Script Control + +```python +import subprocess + +# Run pipeline with CSV logging +subprocess.run([ + 'gst-launch-1.0', + 'idsueyesrc', 'config-file=config.ini', + '!', 'videoconvert', + '!', 'video/x-raw,format=GRAY8', + '!', 'rollingsum', + 'window-size=1000', + 'column-index=1', + 'threshold=0.0002', + 'csv-file=output.csv', + '!', 'fakesink' +]) + +# Analyze results +subprocess.run(['uv', 'run', 'analyze_sma.py', 'output.csv']) +``` + +### Adaptive Threshold + +Use analysis results to set optimal threshold for next run: + +```python +import pandas as pd + +# Analyze previous run +df = pd.read_csv('output.csv') +recommended_threshold = df['normalized_deviation'].quantile(0.95) + +print(f"Recommended threshold: {recommended_threshold:.6f}") +``` + +## Developer Notes + +### Adding New Features + +Key files: +- [`gst/rollingsum/gstrollingsum.c`](gst/rollingsum/gstrollingsum.c) - Main implementation +- [`gst/rollingsum/gstrollingsum.h`](gst/rollingsum/gstrollingsum.h) - Header/structures +- [`gst/rollingsum/CMakeLists.txt`](gst/rollingsum/CMakeLists.txt) - Build config + +### Rebuild After Changes + +```bash +.\build.ps1 # Windows +./build.sh # Linux +``` + +### Testing + +```bash +# Quick test +gst-inspect-1.0 rollingsum + +# Full pipeline test with debug +$env:GST_DEBUG="rollingsum:5" +gst-launch-1.0 videotestsrc ! rollingsum ! fakesink +``` + +## References + +- [DESIGN_ROLLINGSUM.md](DESIGN_ROLLINGSUM.md) - Design document +- [analyze_sma.py](analyze_sma.py) - Analysis tool +- GStreamer documentation: https://gstreamer.freedesktop.org/documentation/ + +## Support + +For issues or questions: +1. Enable debug output (`GST_DEBUG=rollingsum:5`) +2. Generate CSV log and analyze +3. Check this guide's troubleshooting section +4. Review debug output for errors/warnings \ No newline at end of file diff --git a/analyze_sma.py b/analyze_sma.py new file mode 100644 index 0000000..a2582e3 --- /dev/null +++ b/analyze_sma.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# /// script +# dependencies = [ +# "pandas>=2.0.0", +# "matplotlib>=3.7.0", +# "numpy>=1.24.0", +# ] +# /// + +""" +Rolling Sum Analysis Tool +Analyzes CSV output from the GStreamer rollingsum plugin +Usage: uv run analyze_sma.py [csv_file] +""" + +import sys +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np +from pathlib import Path + + +def analyze_csv(csv_file: str = "output.csv"): + """Analyze the rolling sum CSV data and generate insights.""" + + # Read the CSV + try: + df = pd.read_csv(csv_file) + except FileNotFoundError: + print(f"Error: CSV file '{csv_file}' not found.") + sys.exit(1) + + print("=" * 80) + print(f"ROLLING SUM ANALYSIS - {csv_file}") + print("=" * 80) + print() + + # Basic statistics + print("DATASET OVERVIEW:") + print(f" Total frames: {len(df)}") + print(f" Frames dropped: {df['dropped'].sum()}") + print(f" Frames kept: {(df['dropped'] == 0).sum()}") + print(f" Drop rate: {df['dropped'].mean() * 100:.2f}%") + print() + + # Column mean statistics + print("COLUMN MEAN STATISTICS:") + print(f" Min: {df['column_mean'].min():.6f}") + print(f" Max: {df['column_mean'].max():.6f}") + print(f" Range: {df['column_mean'].max() - df['column_mean'].min():.6f}") + print(f" Mean: {df['column_mean'].mean():.6f}") + print(f" Std Dev: {df['column_mean'].std():.6f}") + print() + + # Deviation statistics + print("DEVIATION STATISTICS:") + print(f" Min deviation: {df['deviation'].min():.6f}") + print(f" Max deviation: {df['deviation'].max():.6f}") + print(f" Mean deviation: {df['deviation'].mean():.6f}") + print(f" Std dev of deviations: {df['deviation'].std():.6f}") + print() + + # Normalized deviation statistics + print("NORMALIZED DEVIATION STATISTICS:") + print(f" Min: {df['normalized_deviation'].min():.8f}") + print(f" Max: {df['normalized_deviation'].max():.8f}") + print(f" Mean: {df['normalized_deviation'].mean():.8f}") + print(f" Median: {df['normalized_deviation'].median():.8f}") + print(f" 95th percentile: {df['normalized_deviation'].quantile(0.95):.8f}") + print(f" 99th percentile: {df['normalized_deviation'].quantile(0.99):.8f}") + print() + + # Threshold recommendations + print("THRESHOLD RECOMMENDATIONS:") + print(" (Based on normalized deviation percentiles)") + print() + + percentiles = [50, 75, 90, 95, 99] + for p in percentiles: + threshold = df['normalized_deviation'].quantile(p / 100) + frames_dropped = (df['normalized_deviation'] > threshold).sum() + drop_rate = (frames_dropped / len(df)) * 100 + print(f" {p}th percentile: threshold={threshold:.8f}") + print(f" → Would drop {frames_dropped} frames ({drop_rate:.1f}%)") + print() + + # Suggest optimal thresholds based on standard deviations + mean_norm_dev = df['normalized_deviation'].mean() + std_norm_dev = df['normalized_deviation'].std() + + print("STANDARD DEVIATION-BASED THRESHOLDS:") + for n in [1, 2, 3]: + threshold = mean_norm_dev + (n * std_norm_dev) + frames_dropped = (df['normalized_deviation'] > threshold).sum() + drop_rate = (frames_dropped / len(df)) * 100 + print(f" Mean + {n}σ: threshold={threshold:.8f}") + print(f" → Would drop {frames_dropped} frames ({drop_rate:.1f}%)") + print() + + # Create visualizations + create_plots(df, csv_file) + + print("=" * 80) + print("PLOTS SAVED:") + print(f" - {csv_file.replace('.csv', '_analysis.png')}") + print("=" * 80) + + +def create_plots(df: pd.DataFrame, csv_file: str): + """Create analysis plots.""" + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f'Rolling Sum Analysis - {csv_file}', fontsize=16, fontweight='bold') + + # Plot 1: Column mean over time + ax1 = axes[0, 0] + ax1.plot(df['frame'], df['column_mean'], label='Column Mean', linewidth=1) + ax1.plot(df['frame'], df['rolling_mean'], label='Rolling Mean', linewidth=1, alpha=0.7) + ax1.set_xlabel('Frame') + ax1.set_ylabel('Pixel Value') + ax1.set_title('Column Mean vs Rolling Mean') + ax1.legend() + ax1.grid(True, alpha=0.3) + + # Plot 2: Deviation over time + ax2 = axes[0, 1] + ax2.plot(df['frame'], df['deviation'], linewidth=1, color='orange') + ax2.axhline(y=df['deviation'].mean(), color='r', linestyle='--', + label=f'Mean: {df["deviation"].mean():.4f}') + ax2.axhline(y=df['deviation'].quantile(0.95), color='g', linestyle='--', + label=f'95th: {df["deviation"].quantile(0.95):.4f}') + ax2.set_xlabel('Frame') + ax2.set_ylabel('Absolute Deviation') + ax2.set_title('Deviation from Rolling Mean') + ax2.legend() + ax2.grid(True, alpha=0.3) + + # Plot 3: Normalized deviation distribution + ax3 = axes[1, 0] + ax3.hist(df['normalized_deviation'], bins=50, edgecolor='black', alpha=0.7) + ax3.axvline(x=df['normalized_deviation'].mean(), color='r', linestyle='--', + label=f'Mean: {df["normalized_deviation"].mean():.6f}') + ax3.axvline(x=df['normalized_deviation'].median(), color='g', linestyle='--', + label=f'Median: {df["normalized_deviation"].median():.6f}') + ax3.set_xlabel('Normalized Deviation') + ax3.set_ylabel('Frequency') + ax3.set_title('Normalized Deviation Distribution') + ax3.legend() + ax3.grid(True, alpha=0.3, axis='y') + + # Plot 4: Cumulative distribution + ax4 = axes[1, 1] + sorted_norm_dev = np.sort(df['normalized_deviation']) + cumulative = np.arange(1, len(sorted_norm_dev) + 1) / len(sorted_norm_dev) * 100 + ax4.plot(sorted_norm_dev, cumulative, linewidth=2) + + # Mark percentiles + for p in [50, 75, 90, 95, 99]: + threshold = df['normalized_deviation'].quantile(p / 100) + ax4.axvline(x=threshold, color='red', linestyle=':', alpha=0.5) + ax4.text(threshold, p, f'{p}th', rotation=90, va='bottom', ha='right', fontsize=8) + + ax4.set_xlabel('Normalized Deviation') + ax4.set_ylabel('Cumulative Percentage (%)') + ax4.set_title('Cumulative Distribution Function') + ax4.grid(True, alpha=0.3) + + plt.tight_layout() + + # Save the plot + output_file = csv_file.replace('.csv', '_analysis.png') + plt.savefig(output_file, dpi=150, bbox_inches='tight') + print(f"\nāœ“ Saved analysis plot to: {output_file}\n") + + +if __name__ == "__main__": + csv_file = sys.argv[1] if len(sys.argv) > 1 else "output.csv" + + if not Path(csv_file).exists(): + print(f"Error: File '{csv_file}' not found.") + print(f"Usage: uv run analyze_sma.py [csv_file]") + sys.exit(1) + + analyze_csv(csv_file) \ No newline at end of file diff --git a/gst/rollingsum/gstrollingsum.c b/gst/rollingsum/gstrollingsum.c index fa56f28..eb532bd 100644 --- a/gst/rollingsum/gstrollingsum.c +++ b/gst/rollingsum/gstrollingsum.c @@ -38,6 +38,7 @@ #include "gstrollingsum.h" #include #include +#include enum { @@ -46,6 +47,7 @@ enum PROP_COLUMN_INDEX, PROP_STRIDE, PROP_THRESHOLD, + PROP_CSV_FILENAME, PROP_LAST }; @@ -53,6 +55,7 @@ enum #define DEFAULT_PROP_COLUMN_INDEX 1 #define DEFAULT_PROP_STRIDE 1 #define DEFAULT_PROP_THRESHOLD 0.5 +#define DEFAULT_PROP_CSV_FILENAME NULL /* Supported video formats */ #define SUPPORTED_CAPS \ @@ -108,6 +111,18 @@ gst_rolling_sum_dispose (GObject * object) GST_DEBUG ("dispose"); + /* Close CSV file if open */ + if (filter->csv_file) { + fclose (filter->csv_file); + filter->csv_file = NULL; + } + + /* Free CSV filename */ + if (filter->csv_filename) { + g_free (filter->csv_filename); + filter->csv_filename = NULL; + } + gst_rolling_sum_reset (filter); /* chain up to the parent class */ @@ -161,6 +176,13 @@ gst_rolling_sum_class_init (GstRollingSumClass * klass) G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING)); + g_object_class_install_property (gobject_class, PROP_CSV_FILENAME, + g_param_spec_string ("csv-file", "CSV File", + "Path to CSV file for logging frame data (NULL = no logging)", + DEFAULT_PROP_CSV_FILENAME, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | + GST_PARAM_MUTABLE_READY)); + gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_rolling_sum_sink_template)); gst_element_class_add_pad_template (gstelement_class, @@ -187,6 +209,7 @@ gst_rolling_sum_init (GstRollingSum * filter) filter->column_index = DEFAULT_PROP_COLUMN_INDEX; filter->stride = DEFAULT_PROP_STRIDE; filter->threshold = DEFAULT_PROP_THRESHOLD; + filter->csv_filename = NULL; filter->ring_buffer = NULL; filter->ring_index = 0; @@ -194,6 +217,7 @@ gst_rolling_sum_init (GstRollingSum * filter) filter->rolling_mean = 0.0; filter->rolling_sum = 0.0; filter->info_set = FALSE; + filter->csv_file = NULL; gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE); @@ -228,6 +252,40 @@ gst_rolling_sum_set_property (GObject * object, guint prop_id, case PROP_THRESHOLD: filter->threshold = g_value_get_double (value); break; + case PROP_CSV_FILENAME: + { + const gchar *filename = g_value_get_string (value); + + /* Close old file if open */ + if (filter->csv_file) { + fclose (filter->csv_file); + filter->csv_file = NULL; + } + + /* Free old filename */ + if (filter->csv_filename) { + g_free (filter->csv_filename); + filter->csv_filename = NULL; + } + + /* Set new filename and open file */ + if (filename && filename[0] != '\0') { + filter->csv_filename = g_strdup (filename); + filter->csv_file = fopen (filter->csv_filename, "w"); + + if (filter->csv_file) { + /* Write CSV header */ + fprintf (filter->csv_file, "frame,column_mean,rolling_mean,deviation,normalized_deviation,dropped\n"); + fflush (filter->csv_file); + GST_INFO_OBJECT (filter, "Opened CSV file: %s", filter->csv_filename); + } else { + GST_ERROR_OBJECT (filter, "Failed to open CSV file: %s", filter->csv_filename); + g_free (filter->csv_filename); + filter->csv_filename = NULL; + } + } + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -255,6 +313,9 @@ gst_rolling_sum_get_property (GObject * object, guint prop_id, GValue * value, case PROP_THRESHOLD: g_value_set_double (value, filter->threshold); break; + case PROP_CSV_FILENAME: + g_value_set_string (value, filter->csv_filename); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -367,8 +428,12 @@ gst_rolling_sum_transform_ip (GstBaseTransform * trans, GstBuffer * buf) gdouble frame_mean, deviation, old_value; gint effective_window_size; + GST_DEBUG_OBJECT (filter, "transform_ip called, frame_count=%d", filter->frame_count); + /* Extract column mean from current frame */ frame_mean = gst_rolling_sum_extract_column_mean (filter, buf); + + GST_DEBUG_OBJECT (filter, "Extracted column mean: %.2f", frame_mean); /* Store in ring buffer */ old_value = filter->ring_buffer[filter->ring_index]; @@ -395,7 +460,7 @@ gst_rolling_sum_transform_ip (GstBaseTransform * trans, GstBuffer * buf) /* Normalize deviation (assuming 8-bit equivalent range) */ gdouble normalized_deviation = deviation / 255.0; - GST_LOG_OBJECT (filter, + GST_DEBUG_OBJECT (filter, "Frame %d: mean=%.2f, rolling_mean=%.2f, deviation=%.2f (normalized=%.4f)", filter->frame_count, frame_mean, filter->rolling_mean, deviation, normalized_deviation); @@ -404,10 +469,23 @@ gst_rolling_sum_transform_ip (GstBaseTransform * trans, GstBuffer * buf) filter->ring_index = (filter->ring_index + 1) % filter->window_size; /* Decision: drop or pass frame */ + gboolean dropped = FALSE; if (normalized_deviation > filter->threshold) { GST_DEBUG_OBJECT (filter, "Dropping frame %d: deviation %.4f > threshold %.4f", filter->frame_count, normalized_deviation, filter->threshold); + dropped = TRUE; + } + + /* Write to CSV if file is open */ + if (filter->csv_file) { + fprintf (filter->csv_file, "%d,%.6f,%.6f,%.6f,%.6f,%d\n", + filter->frame_count, frame_mean, filter->rolling_mean, + deviation, normalized_deviation, dropped ? 1 : 0); + fflush (filter->csv_file); + } + + if (dropped) { return GST_BASE_TRANSFORM_FLOW_DROPPED; } diff --git a/gst/rollingsum/gstrollingsum.h b/gst/rollingsum/gstrollingsum.h index 02d7193..7545c80 100644 --- a/gst/rollingsum/gstrollingsum.h +++ b/gst/rollingsum/gstrollingsum.h @@ -22,6 +22,7 @@ #include #include +#include G_BEGIN_DECLS @@ -54,6 +55,7 @@ struct _GstRollingSum gint column_index; /* Which column to analyze (0-based) */ gint stride; /* Row sampling stride */ gdouble threshold; /* Deviation threshold for dropping frames */ + gchar *csv_filename; /* CSV output filename (NULL = no CSV) */ /* State */ gdouble *ring_buffer; /* Circular buffer of column means */ @@ -61,6 +63,7 @@ struct _GstRollingSum gint frame_count; /* Total frames processed */ gdouble rolling_mean; /* Current rolling mean */ gdouble rolling_sum; /* Current rolling sum for efficient mean update */ + FILE *csv_file; /* CSV file handle */ /* Video format info */ GstVideoInfo video_info;