SonataSmooth is a compound of "sonata" and "smooth". A sonata is a musical form in which multiple movements blend into a single, harmonious whole-here serving as a metaphor for different smoothing algorithms working together in concert. "Smooth" highlights the sleek process of gently removing noise from data.
Key components
- Sonata : Evokes the idea of various movements (algorithms) uniting to produce a harmonious result
- Smooth : Emphasizes the smoothing function that gracefully eliminates data noise
True to its name, SonataSmooth embodies the philosophy of applying multiple techniques in harmony to process data as smoothly and clearly as a piece of music.
SonataSmooth is a C# .NET Windows Forms application for efficient noise reduction and smoothing of numerical datasets. It supports multiple data input methods, including manual entry, clipboard paste, and drag-and-drop, with robust validation and parsing. Users can apply a variety of advanced filtering algorithms such as Rectangular Mean, Binomial Average, Binomial Median, Gaussian Weighted Median, Gaussian, and Savitzky-Golay filters, customizing parameters as needed. The application features a responsive, user-friendly interface with real-time progress feedback and batch editing capabilities. Designed for flexibility and performance.
SonataSmooth is specialized for 1D data smoothing and noise reduction.
While limited to single‑dimension datasets, it can be applied across a wide range of domains where sequential numeric signals require pre-processing or refinement :
-
Machine Learning / Deep Learning Pre-processing
- Clean raw training data by removing spikes, jitter, or irregular noise
- Improve model accuracy by stabilizing time‑series inputs such as sensor readings or financial signals
-
IoT and Sensor Data Processing
- Smooth noisy sensor outputs (temperature, humidity, vibration, etc.) for reliable monitoring
- Enhance anomaly detection by reducing false positives caused by random fluctuations
-
Financial and Economic Time Series
- Filter out short‑term volatility in stock prices, currency exchange rates, or transaction volumes
- Prepare stable datasets for forecasting models
-
Scientific and Experimental Measurements
- Refine laboratory or field data (e.g., spectroscopy, environmental monitoring) to highlight meaningful trends
- Reduce measurement noise without distorting underlying patterns
-
Signal Processing (1D only)
- Smooth audio waveform amplitudes or other linear signals
- Preserve essential features while reducing background noise
-
Data Visualization & Reporting
- Generate clearer charts and plots by removing distracting noise
- Provide stakeholders with more interpretable datasets
Important
Most components of this project are licensed under MIT.
However, the Binomial Weighted Median Filter (based on Pascal’s Triangle coefficients) is patent-pending and has special restrictions :
✅ Free for non-commercial research, education, and personal projects
❌ Commercial use, reproduction, or distribution requires prior written consent from the patent holder
Caution
Notwithstanding any rights granted under the MIT License applicable to the majority of this project, the algorithm known as the Binomial Weighted Median Filter, derived from Pascal’s Triangle and its coefficients (the "Restricted Component") is subject to additional limitations.
Commercial use, reproduction, distribution, or incorporation of the Restricted Component into products or services is strictly prohibited without the prior written consent of the patent holder.
Any commercial exploitation - including but not limited to sale, licensing, or revenue-generating activities : requires a separate agreement with the patent holder.
Non-commercial research, educational activities, and personal use are expressly permitted, provided that such use does not constitute or support a commercial activity.
By accessing or using the Restricted Component, you acknowledge its patent-pending status and agree to comply with these restrictions.
This restriction applies retroactively and prospectively to all commits, contributions, and versions of this repository, regardless of the date of disclosure or commit history.
Click to Expand
Minor bugs fixed.
Explained NoiseReductionKernelWidth and updated algorithm details in README.md.
Async & Parallel Processing
Batch UI Updates
Stepwise ProgressBar Feedback
True Symmetric Binomial-Weighted Median Filter
ListBox Selection & Deletion Optimization
Regex Performance Tuning
UI-Thread Responsiveness
Median Filter Bias (Fixed the original code's one-sided kernel bug to correctly include both left and right neighbors in the weighted median.)
Binomial Coefficient Indexing (Resolved mis-mapping by removing unnecessary sort / reverse and using symmetric indexing (binom[k + w]).
UI Flicker Prevention (Added BeginUpdate / EndUpdate around all ListBox modifications to eliminate redraw artifacts.)
Overhauled the graphical user interface.
Fixed an issue where the application became unresponsive when calibrating large datasets (over 100,000 entries) with the Noise Reduction Kernel Width set to 7 or higher using the Weighted Median method.
Fixed an issue where the txtVariable textbox was not being cleared after its contents were added to the ListBox.
Reimplemented and optimized the weighted-median calibration algorithm's procedures, reducing processing time by more than a factor of 16.
Fixed a bug in the median-based calibration algorithm that prevented it from producing correct corrected values.
Minor bugs fixed.
Added Gaussian Filter mode that computes and applies a normalized 1D Gaussian kernel with mirror-mode boundary handling in parallel.
Improved tooltips and labels : clarified filter options and renamed "Clear Selection" to "Deselect All".
Fixed ListBox2 update to clear old items before adding new results, ensuring the correct order and smooth refresh.
Repositioned the listbox control buttons and added descriptive icons to each button.
Added required font files to the Resources / Fonts directory.
Added functionality to edit selected items in the Initial Dataset. (supports both single and multiple item edits)
(The number of selected items for editing is now displayed in the StatusBar.)
Updated copy behavior : even when items aren't fully selected, pressing the copy button or using the shortcut (Ctrl + C) will copy all entries.
(If only some items in the listbox are selected, only those selected items will be copied.)
Minor bugs fixed.
Removed beep sound during various operations (such as adding or editing items) via keyboard input (e.g. Enter) in the listbox.
Improved processing and response speed when performing "Select All" followed by "Delete All".
Minor bugs fixed.
Improved bulk deletion performance in ListBox by optimizing the deletion logic for full selection scenarios.
Added a fast path to instantly clear all items and reset the progress bar when all entries are selected.
Ensured the progress bar and UI remain responsive during partial deletions of large datasets.
Refactored
frmModify'sOK_Button_Clickto apply ListBox updates in configurable batches (default 1000) for improved bulk-edit performance.
Addedawait Task.Yield()between batches and InvokeRequired / Invoke checks to ensure smooth, thread-safe UI responsiveness during large updates.
Collapsed final reselection, EndUpdate, focus shift, progress-bar reset, and dialog close into a single synchronized UI call.
Renamed all code elements, file names, and app metadata to reflect the 'SonataSmooth' project name.
Implemented structured export initialization with improved parameter validation and error handling for both
.CSVandExcel (.xlsx)modes.
Enhanced export logic to support large dataset segmentation and metadata embedding (title, kernel width, polynomial order, timestamp).
Integrated parallel filtering for all export modes usingParallel.For, significantly improving performance for large datasets.
Added automatic chart generation in Excel export to visualize filter results across multiple smoothing algorithms.
Improved UI responsiveness during export operations usingTask.Yield()and progress reporting.
Added support for mirrored boundary handling in Gaussian and Savitzky-Golay filters during export.
Minor improvements to input parsing, filter selection logic, and export configuration synchronization.
AddedFrmAboutdisplaying app version and copyright information.
AddedBuy Me a Coffeefeature via PayPal, integrated directly into theFrmAboutto support the developer.
Added execution instructions and initial setup requirements to the README.md, including.NET Frameworkversion, platform dependencies, and environment configuration.
Minor bugs fixed.
All UI data is accessed only on the UI thread, heavy calculations and file writing are parallelized in the background, progress is safely reported to the UI, large exports are split automatically.
UI remains responsive, large data is processed quickly and safely, and all UI / COM access is thread-safe.
Synchronize selection and scroll position between the two listboxes. (These buttons are only enabled when the Initial Dataset and Refined Dataset listboxes have the same number of items.)
Adjusted TabIndex to ensure controls are selected in the correct order.
Minor bugs fixed.
A program selection screen appears when opening a saved file with no associated application.
Include an ARM64-native variant of the executable.
Renamed "kernel width" to "kernel radius" throughout the UI and code, while still computing the actual width internally as
2 × {radius} + 1..
Refactored variable names and code references to use "Kernel Radius (r)" instead of "Kernel Width (w)".
Minor bugs fixed.
Minor bugs fixed.
Implemented explicit COM object cleanup (
Application,Workbook,Worksheet) withMarshal.ReleaseComObjectand forced GC to prevent memory leaks and lingering background Excel processes.
Minor bugs fixed.
Unified "filter calculation" logic into a shared method used by both
ExportCsvAsyncandExportExcelAsync.
Minor bugs fixed.
Refactored UI controls and variable names across the application, applying clear naming conventions (e.g.,
lblVersion→lblAppVersion,listBox1→lbInitData,ProgressBar1→pbModify).
Added mouse Hover / Leave event handlers to multiple forms (e.g.,FrmExportSettings,FrmModify) to provide real-time tooltip descriptions for each option and button.
Improved DPI scaling support : dynamically adjusted ProgressBar and StatusLabel sizes inFrmModifyusingGraphics.DpiX / Graphics.DpiY.
Enhanced status display during multi-item modifications : status bar messages now update dynamically based on the number of selected items.
Optimized data modification logic : sorted selected item indices and improved UI update performance.
Fixed a bug wherePolyOrderwas incorrectly initialized with thekernelRadiusvalue.
Cleaned up control metadata and resource key names in.resxfiles.
Added musical-themed metadata to Excel exports : document properties now include dynamic titles, categories, and keywords inspired by classical composition (e.g., "SonataSmooth Overture", "Concerto of smoothing movements").
Embedded randomized musical phrases in the Comments field (e.g., "Adagio in Data Minor", "Fugue of Filters") to enrich exported files with playful orchestral flair.
Introduced hidden Easter egg : when exactly four smoothing methods are applied, a special comment is added - "Hidden Movement Unlocked : The Quartet of Filters has performed in perfect harmony."
Expanded error handling for Excel interop and file export issues, including user-friendly messages and fallback guidance (e.g., Office download prompt on COMException).
Minor bug fixes and code cleanup.
Minor bugs fixed.
Minor bugs fixed.
Introduced an
isRefinedLoadingflag and bulk-disable logic inbtnCalibrate_Clickto lock out all data and export controls during asynchronous loading of the refined dataset, preventing flicker and invalid user actions
DefinedRecommendedMinRadius/RecommendedMaxRadiusandRecommendedMinPolyOrder/RecommendedMaxPolyOrderconstants and displayed them in dynamic status-bar tooltips when hovering over radius and polynomial-order selectors.
Added aShowStatusMessage(string)helper to simplify and unify status-bar updates from anywhere in the form.
Centralized all filter computations in a newApplySmoothingmethod that produces rectangular, binomial, median, Gaussian, and Savitzky-Golay outputs in one pass, then refactored CSV export to reuse it and eliminate duplicate loops.
Minor bugs fixed.
Refreshed the graphical user interface with subtle enhancements.
Improved status label messaging logic : Added dynamic singular / plural phrasing and corrected punctuation for clearer user feedback.
Minor bugs fixed.
Enhanced
InvertMatrixby adding partial pivoting and a dynamic, scale-based tolerance to safeguard Gauss-Jordan inversion against division-by-zero and severe rounding errors.
Refined singular-matrix handling to detect sub-threshold pivots (usingmaxRow × 1e - 12vs.Double.Epsilon) and emit a controlled zero matrix or exception instead of propagating NaN / ∞.
Retained mirror-padding in Savitzky-Golay filtering, ensuring exact central coefficients and smooth, distortion-free continuity at both ends.
Refactored inversion routine for clarity : consolidated row-swap, pivot selection, scale calculation, and error handling into a single coherent block, removing hard-coded thresholds.
Performed minor code cleanups to improve readability and enforce consistent formatting.
Minor bugs fixed.
Implemented dataset title validation : including checks for length, invalid characters, and reserved names. Alongside dynamic placeholder behavior and conditional enabling of the export button.
Added KeyDown handler (FrmExportSettings_KeyDown) to close dialog on ESC;KeyPreviewset totruein Designer.
Minor bugs fixed.
Implemented unified parameter sourcing for export (initial design used status labels); current implementation reads Kernel Radius / Polynomial Order directly from the ComboBoxes (
cbxKernelRadius,cbxPolyOrder) at export time to avoid stale UI state. Future refactor may restore label sourcing.
Minor bugs fixed.
Added boundary handling options for Savitzky-Golay filter :
Symmetric(Mirror),Replicate(Nearest),Zero-Pad,BoundaryModeenum andGetIndexmethod updated to support all three modes.
ComboBox (cbxBoundaryMethod) selection now determines edge handling for Savitzky-Golay smoothing.
ApplySmoothingand calibration logic refactored to useGetIndexandboundaryModefor Savitzky-Golay filter, ensuring correct edge behavior per user selection.
Export routines (CSV / Excel) now display and save the selected boundary method for the Savitzky-Golay filter.
UI status label and ComboBox text synchronized for boundary method display.
Other smoothing methods remain unchanged; the boundary option applies only to the Savitzky-Golay filter.
Minor bug fixes and code cleanup.
Boundary Handling Options can now be configured and applied to all Smoothing (Calibration) Methods.
Minor bug fixes and user interface enhancements.
With the transition to ARM64 native based on
.NET Framework 4.8.1, PCs running in this environment now deliver improved performance.
Added support for
Adaptivemode in Boundary Handling Method configuration.
Derivative ordercontrol is now available exclusively for theSavitzky-Golaysmoothing method.
Minor bug fixes, performance improvements, and user interface enhancements.
Revised and updated
README.mddocumentation.
Minor bugs fixed.
Minor bugs fixed.
Added
Alpha Blendto runtime smoothing and export pipelines (applies to Binomial Averaging, Binomial Median, Gaussian; excluded for Rectangular / Savitzky‑Golay). Alpha clamped to 0.00 - 1.00 and synchronized with Export Settings.
CSV / Excel exports updated : conditional "Alpha Blend" metadata, unified single‑pass filtering, large dataset segmentation, and chart generation preserved.
Excel export UX improved : progress bar switches to marquee while the Save dialog is open; SaveAs simulates progress to 95% and completes at 100%; opens an unsaved workbook for manual save when no path is chosen.
Ensured consistent Excel SaveFileDialog filter string : "Excel Workbook (.xlsx)|.xlsx".
Minor performance and UI responsiveness improvements.
Minor bugs fixed.
Newly introduced
.slnxformat : enhanced scalability and compatibility compared to the traditional.sln, supporting large‑scale project management and multi‑platform build environments.
Motivation : to maintain a consistent project structure across diverse runtime / plugin environments and minimize format conflicts during team collaboration.
Purpose : to unify solution definitions under a single format, improving maintenance efficiency and enabling smoother integration with future automation pipelines and cloud workflows.
Application preferences are now automatically saved when the
SonataSmoothapplicationclosesor when the user presses theSave buttonin the settings panel. These values are seamlessly reloaded at startup, allowing users to continue their work without interruption and ensuring a consistent environment across sessions.
Introduced the Gaussian Weighted Median Filter (GWMF), providing advanced smoothing and correction capabilities. This method enhances data fidelity by reducing noise while preserving critical signal characteristics, offering more accurate results for complex workflows.
A newRestore to Defaultbutton has been added to the settings interface, enabling users to quickly revert configurations to their original baseline values. This improves usability and reduces friction when experimenting with different settings.
Various minor bugs have been fixed, and overall usability has been refined to deliver a smoother, more reliable user experience.
Store and restore
BoundaryMethodusing text values to ensure consistent display across sessions.
When "Open after save" is unchecked, a "Excel export completed." message box is shown after successful Excel (.xlsx) export, consistent with the existing CSV export completion behavior.
On first launch after upgrade, compatible settings are migrated once viaHasUpgradedSettings, and subsequent launches preserve user‑modified values through normal save / load.
Minor bugs fixed.
Introduced more hidden Easter eggs.
RefinedAssemblyDescriptionto emphasize SonataSmooth's advanced filtering and key features.
Minor bugs fixed.
Minor bugs fixed.
Minor bugs fixed.
Improved Excel export error handling : now distinguishes between Excel not installed, COM activation failure, and other interop errors, providing more specific user guidance.
Minor bugs fixed.
Overhauled the graphical user interface.
Gaussian filters (Gaussian Weighted Median / Gaussian Filters) now use user‑selectedSigma Factor((2r + 1) / 1.0 … 12.0) for flexible smoothing.
CSV / Excel export metadata now correctly reflects the actual Sigma Factor used in calculations.
Exported data for Gaussian Weighted Median and Gaussian filters is always computed using the selectedSigma Factorvalue.
Settings load / save unified for all smoothing parameters, with safe defaults on errors.
Unified the logic for loading and saving user settings for all smoothing parameters, includingSigma Factor, to ensure all values are handled consistently and reliably.
Improved error handling for corrupted or missing settings files, always reverting to safe defaults.
Code refactoring for clarity and reliability.
Minor bugs fixed.
Minor bugs fixed.
Minor bugs fixed.
- Windows Operating System
- Windows 11 recommended, compatible with Windows 10
- .NET Framework 4.8 or .NET Framework 4.8.1
- Runtime : .NET Framework 4.8.1 (native ARM64 supported on Windows 11)
- Architectures : ARM64 (native), x64 (native), x86 (native)
- Verified on ARM64 and x64 devices (native builds)
- Visual Studio 2026 (for development)
- Development environment is recommended to use Visual Studio 2022 or newer
- Microsoft Office (Excel)
- Excel 2013 Service Pack 1 (clean install) verified to work without additional configuration.
- Later versions (Excel 2016, 2019, 2021, 2024, Microsoft 365) also confirmed stable.
- Minimum recommended version : Excel 2019 or later for long-term support and reliability.
- Bitness must match : x86 app with 32‑bit Office, or x64 app with 64‑bit Office.
- CSV export does not require Microsoft Office.
Note
Any native dependencies (P / Invoke / COM) require ARM64 builds
System.Windows.FormsSystem.Threading.TasksSystem.LinqMicrosoft.Office.Interop.Excel(for Excel export)
- Clone or download the repository.
- Open the solution file (
.slnx) in Visual Studio. - Add necessary references if required.
- Build the project.
- Run the application.
On first launch after an application upgrade, compatible settings are migrated and normalized once. Subsequent launches use normal save / load persistence.
Key points :
- One‑time migration flag :
HasUpgradedSettings - Boundary Method stored and restored by text (e.g., "Symmetric", "Replicate", "Adaptive", "ZeroPad"); normalized strings ensure consistent display (aliases like "Zero Padding" → "ZeroPad")
- Clamping / normalization :
AlphaBlendclamped to [0.0, 1.0]DerivOrderclamped to [0, 10]SigmaFactorclamped to [1.0, 12.0]
- Settings are saved automatically on app close and when pressing the Save button in the settings panel.
The export title (Excel sheet name & metadata) is validated :
- Max length : 31 characters
- Disallowed characters :
: \ / ? * [ ]and all OS-invalid filename characters - Reserved DOS names rejected (CON, PRN, AUX, NUL, COM1 - COM9, LPT1 - LPT9)
Invalid input reverts to the placeholder and shows a warning dialog. The placeholder text "Click here to enter a title for your dataset." is restored whenever validation fails or the field is cleared. On invalid or cleared input the placeholder is restored with centered alignment and gray foreground; valid titles display left-aligned with normal system text color.
Export Enablement Rules :
- Export button is enabled when the Initial Dataset contains at least one entry AND the title field is non‑placeholder. Full title validation runs when the field loses focus; invalid titles are reverted to the placeholder and export becomes disabled.
- Clearing all Initial Dataset items or reverting the title to the placeholder immediately disables export.
- Launch the Application : Run the compiled
.exefile or start the project from Visual Studio. - Input Data : Enter numeric values manually, paste from clipboard, or drag-and-drop text / HTML.
- Select Filter : Choose exactly one smoothing method for calibration using the radio buttons (
rbtnRect,rbtnAvg,rbtnMed,rbtnGaussMed,rbtnGauss,rbtnSG). In the Export Settings dialog you can enable multiple methods (chbRect,chbAvg,chbMed,chbGaussMed,chbGauss,chbSG) for batch export. - Calibrate : Click the 'Calibrate' button (
btnCalibrate) to apply the selected filter(s). - Review Results : View the smoothed output in the "Refined Dataset" listbox (
lbRefinedData). - Edit Data : Use the "Modify Selected Entries" dialog (
btnInitEdit) to batch-edit selected items in the initial dataset. - Export : Click Export (
btnExport) to save results as.CSVorExcel (.xlsx), with optional chart visualization. Configure export options in the "Export Configuration" dialog (btnExportSettings).
Note
You do not need to run "Calibrate" before exporting. Both CSV and Excel exports recompute the selected filters directly from the Initial Dataset.
- Initial Dataset ListBox :
lbInitData - Refined Dataset ListBox :
lbRefinedData - Kernel Radius ComboBox :
cbxKernelRadius - Polynomial Order ComboBox :
cbxPolyOrder - Derivative Order ComboBox :
cbxDerivOrder - Boundary Method ComboBox :
cbxBoundaryMethod - Calibration Method CheckBoxes :
chbRect,chbAvg,chbMed,chbGaussMed,chbGauss,chbSG - Edit Button :
btnInitEdit - Initial ↔ Refined Selection Sync Buttons :
btnInitSelectSync,btnRefSelectSync - Dataset Title TextBox :
txtDatasetTitle - Export Buttons :
btnExport,btnExportSettings - ProgressBar :
pbMain,pbModify - Count Labels :
lblInitCnt,lblRefCnt - StatusStrip & Labels :
statStripMain,slblCalibratedType,slblKernelRadius,tlblPolyOrder,slblPolyOrder,tlblDerivativeOrder,slblDerivativeOrder,tlblBoundaryMethod,slblBoundaryMethod,tlblAlphaBlend,slblAlphaBlend,slblDesc,tlblSeparator1,tlblSeparator2,tlblSeparator3,tlblSeparator4,tlblSeparator5 - Other Controls : All controls use clear, descriptive names matching their function in the codebase.
Visibility : tlblPolyOrder, slblPolyOrder, tlblDerivativeOrder, slblDerivativeOrder are only shown when Savitzky-Golay is active.
This guide explains how different noise filters work with different types of signals. It also simply introduces Pascal's Triangle.
| Signal Pattern | Rectangular Averaging | Binomial Averaging | Binomial Median Filtering | Gaussian Weighted Median Filtering | Gaussian Filtering | Savitzky‑Golay Filtering |
|---|---|---|---|---|---|---|
| Occasional random noise | OK | Good | Very Good | Excellent | Good | Very Good |
| Frequent random noise | Poor | Fair | Excellent | Fair | Fair | Fair |
| Large slow trend changes | Poor | Good | Good | Good | Good | Excellent |
| Sudden spikes (sharp single jumps, occasional) | Poor | Fair | Excellent | Very Good | Fair | Fair |
| Sudden spikes (sharp single jumps, frequent/consecutive) | Poor | Fair | Fair | Excellent | Fair | Fair |
| Regular large-amplitude waves | Poor | Fair | Fair | Good | Fair | Excellent |
| Step changes (sudden level shifts) | Poor | Fair | Excellent | Very Good | Fair | Fair |
| Mixed-frequency oscillations | Poor | Good | Fair | Good | Good | Excellent |
| Periodic high-frequency noise (steady tone) | Excellent | Fair | Fair | Good | Good | Good |
| Slowly drifting baseline with tiny jitter | Good | Good | Excellent | Very Good | Good | Very Good |
| Natural signal flow with smooth curves & gentle noise | Fair | Good | Good | Excellent | Excellent | Very Good |
| Stable periodic signal with moderate high-frequency noise | Fair | Excellent | Fair | Good | Good | Good |
- Rectangular Averaging : Simple but effective for steady high‑frequency noise suppression.
- Binomial Averaging : A balanced middle ground, especially strong for periodic signals with moderate noise.
- Binomial Median Filtering : The most robust across datasets and metrics; excels at handling spikes (both occasional and frequent), frequent noise, and step changes, making it the overall best performer, but less consistent for consecutive spikes.
- Gaussian Weighted Median Filtering (GWMF) : A hybrid combining Gaussian smoothness with median robustness; excellent for occasional spikes and smooth‑curve preservation.
- Gaussian Filtering : Produces smooth curves and natural signal flow, but struggles with abrupt changes and extreme outliers.
- Savitzky‑Golay Filtering : Excels at preserving waveforms, trends, and mixed frequencies; ideal for scientific data and smooth curve analysis.
Overall : Binomial Median Filtering demonstrates the highest aggregate performance across diverse signal types and evaluation metrics. GWMF provides a versatile alternative, especially strong for occasional spikes and noisy smooth‑curve scenarios, while other filters retain niche strengths in specific contexts.
Pascal's Triangle is a triangle of numbers built like this :
- Start with
1at the top. - Each new row adds two numbers from the row above to get a new one.
- The edges of each row are always
1.
Row 1 : 1
Row 2 : 1 1
Row 3 : 1 2 1
Row 4 : 1 3 3 1
Row 5 : 1 4 6 4 1
Filters like Binomial Averaging use rows from Pascal's Triangle as weights. Bigger numbers in the middle give more importance to center values when filtering.
-
Rectangular Averaging (Moving Average)
Adds up a group of points and divides by how many there are. It's simple and fast. Best for removing constant buzz or small jitters. -
Binomial Averaging
Like a moving average, but with weights from Pascal's Triangle. The center gets more focus. Keeps the shape of your signal better while smoothing small noise. -
Binomial Median Filtering
Sorts nearby values and picks the middle one, using extra weight for the center. Removes sharp spikes while keeping the signal shape. -
Gaussian Weighted Median Filtering (GWMF)
Computes a median using Gaussian weights within the kernel window. It preserves smooth curves with robustness to occasional spikes. In Adaptive mode, the kernel length W shrinks at edges, σ is recomputed asW / sigmaFactor, and weights are normalized; alpha blending applies at runtime. -
Gaussian Filtering
Uses a bell-shaped curve for weights. Very smooth, but may let sharp jumps stay. -
Savitzky‑Golay Filtering
Fits tiny curves to chunks of data. Keeps wave shapes and slow changes almost perfectly, but not as strong for sudden spikes.
Let's say we use the 5th row : 1 4 6 4 1
- Total =
1+4+6+4+1=16 - Weights = [
1 / 16,4 / 16,6 / 16,4 / 16,1 / 16] → [0.0625,0.25,0.375,0.25,0.0625] - Data Window = [
2,5,1,3,4]
Apply the weights :
2 × 0.0625 + 5 × 0.25 + 1 × 0.375 + 3 × 0.25 + 4 × 0.0625 = 2.75
That final value (2.75) becomes your new filtered point.
Edge handling determines which values are used when the kernel window extends beyond the first or last element.
| Mode | Also Known As | Formula / Mapping (index transform) | Behavior | Pros | Cons |
|---|---|---|---|---|---|
| Symmetric | Mirror | i < 0 → -i - 1 i ≥ n → 2n - i - 1 |
Reflects across boundary | Smooth continuity; preserves slope | Can amplify extreme edge values if outlier |
| Replicate | Nearest | i < 0 → 0 i ≥ n → n - 1 |
Clamps to nearest endpoint | Stable on plateaus; simple | Flattens curvature; may bias means |
| Adaptive | Edge-aware | Window dynamically trimmed or shifted; index sampling in SG may be asymmetric | Uses only in-range samples (non-SG) or shifts asymmetric SG window to keep length | Removes artificial padding; minimizes bias; accurate near edges | Varies window support; derivative order may be limited at extreme edges |
| Zero Padding | Constant 0 | i < 0 or i ≥ n → 0 | Outside treated as zero | Highlights decay / boundary contrast | Artificial dips; energy loss |
Display names in exports :
- Symmetric → "Symmetric (Mirror)"
- Replicate → "Replicate (Nearest)"
- ZeroPad → "Zero Padding"
- Adaptive → "Adaptive"
public enum BoundaryMode { Symmetric, Replicate, Adaptive, ZeroPad }
private double GetValueWithBoundary(double[] data, int idx, BoundaryMode mode)
{
int n = data.Length;
switch (mode)
{
case BoundaryMode.Symmetric :
if (idx < 0)
idx = -idx - 1;
else if (idx >= n)
idx = 2 * n - idx - 1;
if (idx < 0)
return 0;
return data[idx];
case BoundaryMode.Replicate :
if (idx < 0)
idx = 0;
else if (idx >= n)
idx = n - 1;
return data[idx];
case BoundaryMode.ZeroPad :
if (idx < 0 || idx >= n)
return 0.0;
return data[idx];
case BoundaryMode.Adaptive :
// Single-sample access : treat like symmetric (window logic handled separately)
if (idx < 0)
idx = -idx - 1;
else if (idx >= n)
idx = 2 * n - idx - 1;
if (idx < 0)
return 0;
return data[idx];
default :
if (idx < 0)
idx = -idx - 1;
else if (idx >= n)
idx = 2 * n - idx - 1;
if (idx < 0)
return 0;
return data[idx];
}
}Non‑Adaptive paths (Rect, Avg, Med, GaussMed, Gauss, SG) fetch samples through a unified accessor GetValueWithBoundary(data, idx, mode).
Adaptive paths differ per filter :
Rect,Avg,Med,GaussMed,Gauss: the window is trimmed to in‑range samples and processed directly without padding.Rect: computes the simple mean of the truncated window (W), averaging only the in-range samples (no zero / repeat padding).Avg: recomputes a local binomial row for truncatedW, normalizes by its sum, and averages (no sort).Med: recomputes local binomial weights for truncatedW, sorts by value, and selects the weighted median (handles even / odd total weight).GaussMed: recomputes local Gaussian weights for truncatedWwithσ = W / Sigma Factor(Sigma Factor is always clamped to [1.0, 12.0]), normalizes; sorts(value, weight)pairs by value and selects the weighted median where cumulative weight ≥ 1 / 2.Gauss: recomputes a Gaussian kernel for truncatedWwithσ = W / Sigma Factor(Sigma Factor is always clamped to [1.0, 12.0]), normalizes, then computes a weighted average (convolution‑like sum, no sort).
SG: keeps the intended window length (2r + 1) by shifting an asymmetric window near edges. If full support cannot be met, the effective polynomial order is clamped toeffPoly = min(polyOrder, W - 1); a runtime check throws whenderivOrder > effPoly. Asymmetric coefficients are recomputed per(left, right)shape and cached.
Note
GetIndex exists for compatibility; current code paths use GetValueWithBoundary (non‑Adaptive) or direct in‑range indexing (Adaptive).
- Symmetric : recommended default for smooth analytical signals (mirror mapping).
- Replicate : suitable for stepwise / plateau sensor data (nearest endpoint).
- ZeroPad : emphasizes decay / contrast at boundaries.
- Adaptive : edge‑aware; window trimming or shifting per filter (see below).
Auto‑switching behavior (suppressed if _userSelectedBoundary is true; _suppressAutoBoundary prevents feedback loops):
- Rectangular selected → Boundary Method set to "Replicate"
- Binomial Average / Binomial Median / Gaussian Weighted Median / Gaussian selected → Boundary Method set to "Symmetric"
- Savitzky-Golay selected → Boundary Method set to "Adaptive"
-
Rectangular (Moving Average) :
- Window shrinks at edges :
W = left + right + 1 - Averages only in‑range samples (no zero / replicate bias)
- Window shrinks at edges :
-
Binomial Average :
- Computes a fresh binomial row for the truncated
WusingCalcBinomialCoefficients(W) - Normalizes by the local sum; does not slice the full
2r + 1row
- Computes a fresh binomial row for the truncated
-
Binomial Median (Weighted Median with binomial weights) :
- Recomputes local binomial weights for truncated
W - Performs weighted median over strictly in‑range values sorted by value
- Recomputes local binomial weights for truncated
-
Gaussian Weighted Median :
- Recomputes local Gaussian weights for truncated
Wwithσ = W / sigmaFactor - Sorts
(value, weight)pairs by value; selects the smallest index where cumulative weight ≥ half of total
- Recomputes local Gaussian weights for truncated
-
Gaussian :
- Recomputes a Gaussian kernel for truncated
Wwithσ = W / sigmaFactor - Coefficients are normalized (sum = 1); no padding distortions
- Recomputes a Gaussian kernel for truncated
-
Savitzky-Golay (Smoothing or Derivative) :
- Attempts to retain window length
2r + 1by shifting left / right near edges - Effective polynomial order:
effPoly = min(polyOrder, W - 1) - Throws
InvalidOperationExceptionifderivOrder > effPoly - Uses asymmetric coefficients from least‑squares on the shifted grid (recomputed for each
(left, right)shape)
- Attempts to retain window length
SonataSmooth provides a robust, user-friendly interface for entering and managing numerical datasets :
- Manual Entry : Users can type values directly into the input box and add them to the Initial Dataset with a button click or by pressing Enter.
- Clipboard Paste : Numeric values can be pasted from the clipboard; the app uses optimized regular expressions to extract numbers, even from mixed or formatted text.
- Drag & Drop : Supports dropping plain text, CSV, or HTML-formatted data; HTML tags are stripped and all valid numbers are parsed.
- Batch Addition : Large datasets are added in batches to the ListBox, with real-time progress feedback and smooth UI updates. (Batch Addition detail : items are appended in chunks of 1000 per AddItemsInBatches to keep UI responsive.)
- Validation : All input is validated for numeric format; errors are reported with clear messages and invalid entries are rejected.
- Selection & Editing : Items can be selected, deselected, edited (single or multiple), deleted, or copied to the clipboard. Selection operations are optimized for large lists.
- Copy Behavior : If no items (or all items) are selected, copying exports the entire list; if a partial selection exists, only the selected items are copied. (Applies to both Initial and Refined datasets.)
- Clear All Behavior : Clearing all items in the Initial Dataset also clears the Refined Dataset and resets related status labels and placeholders.
- Delete Selected Behavior : When deleting only selected items, the Initial Dataset removes those items and the Refined Dataset is preserved. If all items are selected (i.e., selected count equals total count), the operation behaves like Clear All : the Refined Dataset is cleared and the dataset title is reset to the placeholder (centered, gray).
- Selection Operation Cancellation : "Select All" uses internal
CancellationTokenSourceinstances (_ctsInitSelectAll,_ctsRefSelectAll) so starting a new selection immediately cancels any in‑progress selection. The UI remains responsive and can be retriggered to interrupt long operations.
When large batches are added (paste / drag-drop / bulk append), the list auto-scrolls to the newest appended item (TopIndex set to last) to provide immediate visual confirmation.
- Defines how many data points on each side of the target point are included in the smoothing window.
- The kernel width is calculated as
(2 × radius) + 1. - For Gaussian-based filters, the standard deviation (σ) is computed as :
σ = (2 × radius + 1) / Sigma Factorwhere Sigma Factor is user-selectable (default : 6.0, range : 1.0 - 12.0). - Recommended range : 3 to 7.
- If the kernel window is larger than the dataset, the app will show an error and prevent calibration / export.
Kernel radius specifies how many data points on each side of the center element are included in the filtering window. The total window length (kernel width) is calculated as 2 × r + 1.
The total window length (kernel width) is calculated as :
For example, if (r = 2) :
This means your median (or any other sliding-window) filter will span 5 consecutive samples at each position.
- Sigma Factor is a user-configurable parameter (default : 6.0, clamped to [1.0, 12.0]) that determines the standard deviation (σ) of the Gaussian kernel used in both Gaussian Weighted Median Filtering and Gaussian Filtering. In Symmetric mode, σ = (2 × radius + 1) / Sigma Factor; in Adaptive mode, σ = W / Sigma Factor (W : window length at each index). The actual Sigma Factor value is always reflected in all calculations and in the export metadata (CSV / Excel).
- Symmetric mode : σ = (2 × radius + 1) / Sigma Factor
- Adaptive mode : σ = W / Sigma Factor (where W is the window length at each index)
- The Sigma Factor value is always reflected in all calculations and in the export metadata (CSV / Excel).
- The value is clamped to the range [1.0, 12.0] for stability and consistency.
- Specifies the degree of the polynomial used to fit the data within each smoothing window (used only for Savitzky-Golay filtering).
- Recommended range : 2 to 6.
- Must be strictly less than the kernel window size; otherwise, an error is shown.
Polynomial order polyOrder specifies the highest degree of the polynomial fitted to the data within each smoothing window. A higher order can capture more complex curvature but may also overfit noise.
The polynomial order is defined as :
polyOrder = degree of the polynomial
For example, if (polyOrder = 2) :
This means the filter will fit a 2nd-degree polynomial (a parabola) across each window of data points.
polyOrdermust be less than the window length (2 ×r+ 1) to ensure a well-posed fitting problem.- Increasing
polyOrderimproves flexibility but risks ringing artifacts at the boundaries. - Common practice is to start with
polyOrder= 2 or 3 and adjust based on how well features are preserved versus noise reduction. - Always validate smoothing performance on representative signal segments before batch processing.
(Displayed only when Savitzky-Golay is selected.)
- Defines which derivative of the fitted local polynomial is evaluated.
- Constraint :
derivOrder ≤ polyOrder - Effective clamp (Adaptive edges) :
effPoly = min(polyOrder, W - 1); runtime validation enforcesderivOrder ≤ effPoly. - Scaling : coefficients multiplied by
factorial(derivOrder) / delta^derivOrder(delta = 1.0). - Recommended range : 0 - 3 (higher orders amplify noise sharply).
Typical uses :
- 0 : Smoothing (baseline SG)
- 1 : Slope estimation
- 2 : Curvature / peak detection
- 3 : Inflection diagnostics (only with sufficiently wide windows)
- Window size :
windowSize = 2 × radius + 1 - Constraint :
windowSize ≤ dataCount - Polynomial constraint (SG only) :
polyOrder < windowSize - Derivative constraint (SG only) :
derivOrder ≤ polyOrder - Adaptive SG edge constraint : if effPoly = min(polyOrder, W - 1) then
derivOrder ≤ effPoly
Failure triggers explicit error dialogs matching runtime validation.
Caution : If the dataset count is smaller than the required window size ((2 × radius) + 1), calibration / export is aborted with a dialog instead of attempting partial smoothing.
- Regex-based Parsing : Uses compiled regular expressions to efficiently extract numbers from any text source.
- Batch UI Updates : ListBox modifications (add, delete, edit) are performed in batches to prevent flicker and maintain responsiveness.
- Progress Feedback : ProgressBar and status labels provide immediate feedback during bulk operations.
- Error Handling : All parsing and input errors are caught and reported to the user, preventing silent failures.
- Parameter Validation : Kernel radius and polynomial order are validated before any smoothing operation.
// Manual entry
private void btnInitAdd_Click(object sender, EventArgs e)
{
if (double.TryParse(txtInitAdd.Text, out double value))
{
lbInitData.Items.Add(value);
lblInitCnt.Text = "Count : " + lbInitData.Items.Count;
slblDesc.Visible = true;
slblDesc.Text = $"Value '{value}' has been added to Initial Dataset.";
}
else
{
txtInitAdd.Focus();
txtInitAdd.SelectAll();
}
UpdatelbInitDataBtnsState(null, EventArgs.Empty);
UpdatelbRefinedDataBtnsState(null, EventArgs.Empty);
txtInitAdd.Text = String.Empty;
}
// Clipboard paste
private async void btnInitPaste_Click(object sender, EventArgs e)
{
string text = Clipboard.GetText();
var matches = clipboardRegex.Matches(text)
.Cast<Match>()
.Where(m => !string.IsNullOrEmpty(m.Value))
.ToArray();
double[] values = await Task.Run(() =>
matches.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.Select(m => double.Parse(m.Value, NumberStyles.Any, CultureInfo.InvariantCulture))
.ToArray()
);
lbInitData.BeginUpdate();
lbInitData.Items.AddRange(values.Cast<object>().ToArray());
lbInitData.EndUpdate();
lblInitCnt.Text = $"Count : {lbInitData.Items.Count}";
UpdateStatusLabel(beforeCount);
}
// Drag & drop
private async void lbInitData_DragDrop(object sender, DragEventArgs e)
{
string raw = GetDropText(e);
if (string.IsNullOrWhiteSpace(raw)) return;
if (raw.IndexOf("<html", StringComparison.OrdinalIgnoreCase) >= 0)
raw = await Task.Run(() => htmlTagRegex.Replace(raw, " "));
double[] parsed = await Task.Run(() =>
numberRegex.Matches(raw)
.Cast<Match>()
.AsParallel()
.AsOrdered()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.Select(m => double.TryParse(m.Value.Replace(",", "").Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out double d) ? d : double.NaN)
.Where(d => !double.IsNaN(d))
.ToArray()
);
await AddItemsInBatches(lbInitData, parsed, progressReporter, 60);
lblInitCnt.Text = "Count : " + lbInitData.Items.Count;
UpdateStatusLabel(beforeCount);
}
// Parameter validation
// Result wrapper (mirrors FrmMain.cs)
private struct OperationResult
{
public bool Success { get; }
public string Error { get; }
private OperationResult(bool success, string error)
{
Success = success;
Error = error;
}
public static OperationResult OK() => new OperationResult(true, null);
public static OperationResult Fail(string error) => new OperationResult(false, error);
}
// Parameter validation
private OperationResult ValidateSmoothingParameters(int dataCount, int w, int polyOrder)
{
int windowSize = 2 * w + 1;
bool useSG = rbtnSG != null && rbtnSG.Checked;
if (windowSize > dataCount)
{
var msg =
"Kernel radius is too large.\n\n" +
"Window size formula : (2 × radius) + 1\n" +
$"Current : (2 × {w}) + 1 = {windowSize}\n" +
$"Data count : {dataCount}\n\n" +
"Rule : windowSize ≤ dataCount";
return OperationResult.Fail(msg);
}
if (useSG && polyOrder >= windowSize)
{
var msg =
"Polynomial order must be smaller than the window size.\n\n" +
"Rule : polyOrder < windowSize\n" +
$"Current polyOrder : {polyOrder}\n" +
$"Window size : {windowSize}\n\n" +
"Tip : windowSize = (2 × radius) + 1";
return OperationResult.Fail(msg);
}
return OperationResult.OK();
}Alpha α blends the original sample with the filtered output for selected methods :
- Applicable to: Binomial Averaging, Binomial Median, Gaussian Weighted Median Filtering (GWMF), Gaussian
- Not applied to: Rectangular, Savitzky‑Golay (including derivatives)
- Formula per element i :
output[i] = α * filtered[i] + (1 - α) * input[i] - Range : 0.00 – 1.00 (clamped)
- UI binding :
cbxAlphaandlblAlphaare enabled only forrbtnAvg,rbtnMed,rbtnGauss,rbtnGaussMed
Runtime usage in smoothing (extended to GWMF) :
double a = alpha;
if (a < 0.0)
{
a = 0.0;
}
else if (a > 1.0)
{
a = 1.0;
}
// Binomial Average (blended)
binomAvg[i] = a * filtered + (1.0 - a) * input[i];
// Binomial Median (blended)
median[i] = a * filtered + (1.0 - a) * input[i];
// Gaussian Weighted Median (blended)
gaussMedian[i] = a * filtered + (1.0 - a) * input[i];
// Gaussian (blended)
gauss[i] = a * filtered + (1.0 - a) * input[i];Alpha enablement and synchronization with Export Settings :
- UI binding :
cbxAlphaandlblAlphaare enabled forrbtnAvg,rbtnMed,rbtnGauss,rbtnGaussMed.
private void UpdateAlphaEnablement()
{
bool alphaRelevant = rbtnAvg.Checked || rbtnMed.Checked || rbtnGaussMed.Checked || rbtnGauss.Checked;
lblAlpha.Enabled = alphaRelevant;
cbxAlpha.Enabled = alphaRelevant;
if (cbxAlpha.Enabled && string.IsNullOrWhiteSpace(cbxAlpha.Text))
{
cbxAlpha.Text = "1.00";
}
}
private void cbxAlpha_SelectedIndexChanged(object sender, EventArgs e)
{
double alpha = ParseAlphaOrDefault(cbxAlpha.Text, 1.0);
cbxAlpha.Text = alpha.ToString("0.00", CultureInfo.InvariantCulture);
if (settingsForm.cbxAlpha != null)
{
settingsForm.cbxAlpha.Text = cbxAlpha.Text;
}
UpdateAlphaEnablement();
}Boundary-method auto-switching and Alpha enablement hooks :
private void rbtnAvg_CheckedChanged(object sender, EventArgs e)
{
if (rbtnAvg.Checked)
{
SetAlphaEnabled(true);
SetBoundaryMethod("Symmetric");
}
UpdateAlphaEnablement();
}
private void rbtnMed_CheckedChanged(object sender, EventArgs e)
{
if (rbtnMed.Checked)
{
SetAlphaEnabled(true);
SetBoundaryMethod("Symmetric");
}
UpdateAlphaEnablement();
}
private void rbtnGauss_CheckedChanged(object sender, EventArgs e)
{
if (rbtnGauss.Checked)
{
SetAlphaEnabled(true);
SetBoundaryMethod("Symmetric");
}
UpdateAlphaEnablement();
}Leverage all CPU cores without blocking the UI. The smoothing kernels are executed with Parallel.For (small arrays run serially), writing per-index results to distinct buffers. PLINQ is used only in input parsing (clipboard / drag-drop), not in the smoothing pass.
When the user clicks "Calibrate", the application processes the input data using the selected filter. The computation is parallelized using Parallel.For (falls back to serial for small n).
- When the user calibrates or exports data, the selected filters are applied to each data point in parallel.
- Each filter uses its own kernel (window) and boundary handling (selected boundary mode, including Adaptive).
- All filter results are computed in a single pass, leveraging all available CPU cores.
- The UI remains responsive, and progress is reported in real time.
Adaptive mode introduces per-index dynamic window sizing or window shifting (Savitzky-Golay). Parallel execution remains safe because each index writes to distinct output arrays and uses only read-only input. Minimum size for parallel dispatch : if n < 2000 a serial loop is used to avoid Parallel.For overhead (threshold hard-coded in ApplySmoothing).
Leverage all CPU cores to avoid blocking the UI. PLINQ's .AsOrdered() preserves the original order, and .WithDegreeOfParallelism matches the number of logical processors.
Note
The smoothing pass uses only Parallel.For; PLINQ is used solely for input parsing, not for kernel execution.
- Parallelization : Uses
Parallel.Forfor the smoothing pass. PLINQ is used only for input parsing (clipboard / drag‑drop), not during kernel execution. - Single-Pass Multi-Filter : All enabled filters are computed together, minimizing memory usage and maximizing throughput.
- Boundary Handling : Edge behavior is configurable (Symmetric / Replicate / Zero‑Pad / Adaptive) for every smoothing method.
- Thread-Safe UI : Progress bars and status labels are updated safely from background threads.
private (double[] Rect, double[] Binom, double[] Median, double[] GaussMed, double[] Gauss, double[] SG)
ApplySmoothing(
double[] input,
int r,
int polyOrder,
int derivOrder,
double delta,
BoundaryMode boundaryMode,
bool doRect,
bool doAvg,
bool doMed,
bool doGaussMed,
bool doGauss,
bool doSG,
double alpha = 1.0,
double sigmaFactor = 6.0)
{
var vr = ValidateSmoothingParameters(input?.Length ?? 0, r, polyOrder);
if (!vr.Success)
throw new InvalidOperationException(vr.Error);
// Savitzky-Golay constraint : derivative order must not exceed polynomial order
if (doSG && derivOrder > polyOrder)
{
throw new InvalidOperationException(
$"Derivative order must be ≤ polynomial order.\n\n" +
$"Current derivativeOrder : {derivOrder}\n" +
$"Current polyOrder : {polyOrder}");
}
int n = input.Length;
int windowSize = 2 * r + 1;
// Precompute weight vectors where applicable
long[] binom = (doAvg || doMed) ? CalcBinomialCoefficients(windowSize) : null;
double[] gaussCoeffsForMedian = doGaussMed ? ComputeGaussianCoefficients(windowSize, (windowSize, (2.0 * r + 1) / sigmaFactor) : null;
double[] gaussCoeffs = doGauss ? ComputeGaussianCoefficients(windowSize, (windowSize, (2.0 * r + 1) / sigmaFactor) : null;
// SG symmetric coefficients (used only when not in Adaptive boundary mode)
double[] sgSmoothCoeffs = (doSG && derivOrder == 0 && boundaryMode != BoundaryMode.Adaptive)
? ComputeSavitzkyGolayCoefficients(windowSize, polyOrder, derivOrder: 0, delta: 1.0)
: null;
double[] sgDerivCoeffs = (doSG && derivOrder > 0 && boundaryMode != BoundaryMode.Adaptive)
? ComputeSavitzkyGolayCoefficients(windowSize, polyOrder, derivOrder, delta)
: null;
var rect = new double[n];
var binomAvg = new double[n];
var median = new double[n];
var gaussMedian = new double[n];
var gauss = new double[n];
var sg = new double[n];
// Precompute constants for averaging
double invRectDiv = (doRect && windowSize > 0) ? 1.0 / windowSize : 0.0;
double binomSum = 0.0;
if (doAvg && binom != null)
{
for (int i = 0; i < binom.Length; i++)
binomSum += binom[i];
}
// Unified boundary-aware sample accessor
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
// Clamp alpha once for the entire pass
double a = alpha;
if (a < 0.0) a = 0.0;
else if (a > 1.0) a = 1.0;
// Adaptive window helper (trim to available samples)
void GetAdaptiveWindow(int center, out int left, out int right, out int start)
{
left = Math.Min(r, center);
right = Math.Min(r, n - 1 - center);
start = center - left;
}
bool useParallel = n >= 2000;
Action<int> smoothingAction = i =>
{
// Rectangular (no blending)
if (doRect)
{
if (boundaryMode == BoundaryMode.Adaptive)
{
GetAdaptiveWindow(i, out int left, out int right, out int start);
int W = left + right + 1;
double sum = 0.0;
for (int pos = 0; pos < W; pos++)
sum += input[start + pos];
rect[i] = W > 0 ? sum / W : 0.0;
}
else
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
sum += Sample(i + k);
rect[i] = sum * invRectDiv;
}
}
// Binomial Average (with blending)
if (doAvg && binom != null)
{
double filtered;
if (boundaryMode == BoundaryMode.Adaptive)
{
GetAdaptiveWindow(i, out int left, out int right, out int start);
int W = left + right + 1;
if (W < 1)
{
filtered = 0.0;
}
else
{
long[] localBinom = CalcBinomialCoefficients(W);
double localSum = 0.0;
for (int j = 0; j < W; j++) localSum += localBinom[j];
double sum = 0.0;
for (int pos = 0; pos < W; pos++)
sum += input[start + pos] * localBinom[pos];
filtered = localSum > 0 ? sum / localSum : 0.0;
}
}
else
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
sum += Sample(i + k) * binom[k + r];
filtered = binomSum > 0 ? sum / binomSum : 0.0;
}
binomAvg[i] = a * filtered + (1.0 - a) * input[i];
}
// Binomial Median (with blending)
if (doMed && binom != null)
{
double filtered;
if (boundaryMode == BoundaryMode.Adaptive)
{
GetAdaptiveWindow(i, out int left, out int right, out int start);
int W = left + right + 1;
if (W < 1)
{
filtered = 0.0;
}
else
{
long[] localBinom = CalcBinomialCoefficients(W);
var pairs = new List<(double Value, long Weight)>(W);
for (int pos = 0; pos < W; pos++)
pairs.Add((input[start + pos], localBinom[pos]));
if (pairs.Count == 0)
{
filtered = 0.0;
}
else
{
pairs.Sort((aV, bV) => aV.Value.CompareTo(bV.Value));
long totalWeight = 0;
for (int j = 0; j < pairs.Count; j++)
totalWeight += pairs[j].Weight;
if (totalWeight <= 0)
{
filtered = 0.0;
}
else
{
bool even = (totalWeight & 1L) == 0;
long half = totalWeight / 2;
long accum = 0;
filtered = pairs[pairs.Count - 1].Value;
for (int j = 0; j < pairs.Count; j++)
{
accum += pairs[j].Weight;
if (accum > half)
{
filtered = pairs[j].Value;
break;
}
if (even && accum == half)
{
double nextVal = (j + 1 < pairs.Count) ? pairs[j + 1].Value : pairs[j].Value;
filtered = (pairs[j].Value + nextVal) / 2.0;
break;
}
}
}
}
}
}
else
{
filtered = WeightedMedianAt(input, i, r, binom, boundaryMode, a);
}
median[i] = a * filtered + (1.0 - a) * input[i];
}
// Gaussian Weighted Median (with blending)
if (doGaussMed && gaussCoeffsForMedian != null)
{
double filtered;
if (boundaryMode == BoundaryMode.Adaptive)
{
int left = Math.Min(r, i);
int right = Math.Min(r, n - 1 - i);
int start = i - left;
int W = left + right + 1;
if (W < 1)
{
filtered = 0.0;
}
else
{
double sigmaLocal = W / sigmaFactor;
double[] localGauss = ComputeGaussianCoefficients(W, sigmaLocal);
var values = new double[W];
var wts = new double[W];
for (int pos = 0; pos < W; pos++)
{
int absIdx = start + pos;
values[pos] = input[absIdx];
wts[pos] = localGauss[pos]; // weights centered in the local window
}
Array.Sort(values, wts);
double total = 0.0;
for (int j = 0; j < W; j++) total += wts[j];
if (total > 0.0)
{
double half = total / 2.0;
double accum = 0.0;
int sel = W - 1;
for (int j = 0; j < W; j++)
{
accum += wts[j];
if (accum >= half) { sel = j; break; }
}
filtered = values[sel];
}
else
{
filtered = 0.0;
}
}
}
else
{
int W = 2 * r + 1;
var values = new double[W];
var wts = new double[W];
for (int k = -r; k <= r; k++)
{
int idx = i + k;
values[k + r] = Sample(idx);
wts[k + r] = gaussCoeffsForMedian[k + r];
}
Array.Sort(values, wts);
double total = 0.0;
for (int j = 0; j < W; j++) total += wts[j];
if (total > 0.0)
{
double half = total / 2.0;
double accum = 0.0;
int sel = W - 1;
for (int j = 0; j < W; j++)
{
accum += wts[j];
if (accum >= half) { sel = j; break; }
}
filtered = values[sel];
}
else
{
filtered = Sample(i);
}
}
gaussMedian[i] = a * filtered + (1.0 - a) * input[i];
}
// Gaussian (with blending)
if (doGauss && gaussCoeffs != null)
{
double filtered;
if (boundaryMode == BoundaryMode.Adaptive)
{
GetAdaptiveWindow(i, out int left, out int right, out int start);
int W = left + right + 1;
if (W < 1)
{
filtered = 0.0;
}
else
{
double sigmaLocal = W / sigmaFactor;
double[] localGauss = ComputeGaussianCoefficients(W, sigmaLocal);
double sum = 0.0;
for (int pos = 0; pos < W; pos++)
sum += localGauss[pos] * input[start + pos];
filtered = sum;
}
}
else
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
sum += gaussCoeffs[k + r] * Sample(i + k);
filtered = sum;
}
gauss[i] = a * filtered + (1.0 - a) * input[i];
}
// Savitzky-Golay (no blending)
if (doSG)
{
if (boundaryMode == BoundaryMode.Adaptive)
{
int desiredW = windowSize;
int left = Math.Min(r, i);
int right = desiredW - 1 - left;
// Shift window to stay within bounds
if (i + right > n - 1)
{
int shift = (i + right) - (n - 1);
right -= shift;
left += shift;
}
if (left < 0) left = 0;
if (right < 0) right = 0;
double sum = 0.0;
if (derivOrder == 0)
{
var coeffs = ComputeSGCoefficientsAsymmetric(left, right, polyOrder);
for (int k = -left; k <= right; k++)
sum += coeffs[k + left] * input[i + k];
}
else
{
int W = left + right + 1;
int effPoly = Math.Min(polyOrder, W - 1);
if (derivOrder > effPoly)
{
throw new InvalidOperationException(
$"Edge-adaptive window too small for derivative (W = {W}, effPoly = {effPoly}, derivative = {derivOrder}).");
}
var coeffs = ComputeSGCoefficientsAsymmetricDerivative(left, right, effPoly, derivOrder, delta);
for (int k = -left; k <= right; k++)
sum += coeffs[k + left] * input[i + k];
}
sg[i] = sum;
}
else
{
if (derivOrder == 0 && sgSmoothCoeffs != null)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
sum += sgSmoothCoeffs[k + r] * Sample(i + k);
sg[i] = sum;
}
else if (derivOrder > 0 && sgDerivCoeffs != null)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
sum += sgDerivCoeffs[k + r] * Sample(i + k);
sg[i] = sum;
}
}
}
};
// Dispatch : parallel for large arrays; serial for small to avoid overhead
if (useParallel)
{
Parallel.For(0, n, smoothingAction);
}
else
{
for (int i = 0; i < n; i++)
smoothingAction(i);
}
return (rect, binomAvg, median, gaussMedian, gauss, sg);
}Note
Derivative support and adaptive asymmetric SG handling are integrated into this unified pass. Parallelization is skipped for very small arrays (n < 2000) to avoid overhead.
A simple sliding-window average over 2 × r + 1 points honoring the selected Boundary Mode (Symmetric, Replicate, Zero‑Pad). In Adaptive mode, the window shrinks at edges and averages only in‑range samples (no zero or repeat padding is used).
Every neighbor contributes equally (uniform weights).
- Uniform Weights : Each value in the window contributes equally.
- Noise Reduction : Smooths out short-term fluctuations.
if (useRect)
{
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
double invRectDiv = 1.0 / (2 * r + 1);
for (int i = 0; i < input.Length; i++)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
{
sum += Sample(i + k);
}
rectFiltered[i] = sum * invRectDiv;
}
}Averages values in the window, but each value is weighted according to binomial coefficients, giving more importance to central values.
A discrete approximation of Gaussian smoothing (binomial coefficients approximate a normal distribution).
- Binomial Weights : Central values have higher influence.
- Smoother Output : Reduces noise while maintaining signal shape.
else if (useAvg)
{
long[] binom = CalcBinomialCoefficients(2 * r + 1);
double binomSum = binom.Sum();
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
for (int i = 0; i < input.Length; i++)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
{
sum += Sample(i + k) * binom[k + r];
}
binomFiltered[i] = sum / binomSum;
}
}In Adaptive mode, the kernel width shrinks at edges and a NEW binomial row of length W is computed (CalcBinomialCoefficients(W)), ensuring correct symmetric weighting rather than truncating the full-length coefficients.
Computes the median of values in the window, weighted by binomial coefficients, to reduce noise while preserving edges.
Note
Labeled internally as "Weighted Median" (using binomial weights). README refers to it as "Binomial Median Filtering" - both denote the same weighted-median operation.
Median filtering is robust against outliers; binomial weights bias the median toward center points.
- Weighted Median : Each value's influence is determined by its weight.
- Edge Preservation : More robust to outliers than mean filters.
else if (useMed)
{
long[] binom = CalcBinomialCoefficients(2 * r + 1);
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
for (int i = 0; i < input.Length; i++)
{
var pairs = new List<(double Value, long Weight)>();
for (int k = -r; k <= r; k++)
{
pairs.Add((Sample(i + k), binom[k + r]));
}
pairs.Sort((a, b) => a.Value.CompareTo(b.Value));
long totalWeight = pairs.Sum(p => p.Weight);
long half = totalWeight / 2;
bool even = (totalWeight & 1) == 0;
long acc = 0;
for (int j = 0; j < pairs.Count; j++)
{
acc += pairs[j].Weight;
if (acc > half)
{
medianFiltered[i] = pairs[j].Value;
break;
}
if (even && acc == half)
{
double next = (j + 1 < pairs.Count) ? pairs[j + 1].Value : pairs[j].Value;
medianFiltered[i] = (pairs[j].Value + next) / 2.0;
break;
}
}
}
}Adaptive recalculates local binomial weights for truncated W and performs weighted median over only in-range samples - removing edge padding bias.
(Implementation detail : An adaptive path also exists inside WeightedMedianAt that keeps full window length by sliding; ApplySmoothing intentionally bypasses it and uses the truncated‑recompute strategy documented above.)
Computes the median within the kernel window using Gaussian weights centered at the target index. Unlike a mean filter, the median is robust to outliers; the Gaussian weights emphasize values closer to the center, improving fidelity on smooth curves while suppressing occasional spikes.
- Window length : W = 2 × r + 1 (symmetric) or W = left + right + 1 (Adaptive edges)
- Weights : Gaussian kernel
g[k] = exp(-(k^2) / (2σ^2))normalized to sum to 1- Symmetric mode : σ = (2 × radius + 1) / Sigma Factor
- Adaptive mode : σ = W / Sigma Factor (W: window length at each index, recomputed per index)
- Sigma Factor is user-configurable (default: 6.0), clamped to [1.0, 12.0], and applies to both Gaussian Weighted Median and Gaussian Filtering. The actual value used is always reflected in export metadata and calculations.
- Weighted median selection :
- Sort pairs
(value, weight)by value ascending - Accumulate weights until reaching at least half of the total (≥ Σw / 2)
- The value at that position is the weighted median
- Sort pairs
- Boundary handling :
- Non-Adaptive :
Sample(i + k)usesGetValueWithBoundaryfor Symmetric / Replicate / ZeroPad - Adaptive : window truncates to available samples; weights are recomputed for W, and only in-range samples are used (no padding)
- Non-Adaptive :
- Alpha blend (runtime, if enabled):
output[i] = α × filtered[i] + (1 − α) × input[i]with α ∈ [0.00, 1.00]
GWMF combines the robustness of median filtering with the smoothness bias of Gaussian weighting :
- Robustness : The median is insensitive to extreme outliers compared to means.
- Locality : Gaussian weights peak at the center, ensuring nearby samples dominate.
- Edge correctness : Adaptive recomputes σ and weights for truncated windows, avoiding artificial padding biases.
- Stability : In symmetric mode weights are centered; in Adaptive mode weights remain centered in the local truncated window.
Mathematically :
- Kernel positions k ∈ [−r, …, r] (symmetric) or k ∈ [−left, …, right] (adaptive shift / truncate)
- Unnormalized weights : w(k) = exp(−k² / (2σ²))
- Normalization : g(k) = w(k) / Σ_j w(j)
- Weighted median v* solves min over threshold : find smallest index in sorted values s.t. Σ_{j ≤ *} g_sorted(j) ≥ 1 / 2
Alpha blend preserves a tunable fraction of the original signal, preventing over-smoothing :
- α = 0 → original
- α = 1 → fully filtered
- 0 < α < 1 → convex combination
Symmetric window (uses boundary accessor and precomputed Gaussian weights) :
// Symmetric GWMF inside ApplySmoothing (FrmMain.cs)
int W = 2 * r + 1;
var values = new double[W];
var wts = new double[W];
for (int k = -r; k <= r; k++)
{
int idx = i + k;
values[k + r] = Sample(idx); // GetValueWithBoundary
wts[k + r] = gaussCoeffsForMedian[k + r]; // normalized Gaussian weights (σ = (2r + 1) / sigmaFactor)
}
// Sort by value ascending, carrying weights alongside
Array.Sort(values, wts);
// Select weighted median : cumulative weight crosses half
double total = 0.0;
for (int j = 0; j < W; j++)
{
total += wts[j];
}
double filtered;
if (total > 0.0)
{
double half = total / 2.0;
double accum = 0.0;
int sel = W - 1;
for (int j = 0; j < W; j++)
{
accum += wts[j];
if (accum >= half)
{
sel = j;
break;
}
}
filtered = values[sel];
}
else
{
filtered = Sample(i); // fallback: original (degenerate weights)
}
// Alpha blend (applies when GWMF is enabled)
gaussMedian[i] = a * filtered + (1.0 - a) * input[i];Adaptive window (recomputes σ and weights for truncated window W at each index) :
// Adaptive GWMF inside ApplySmoothing (FrmMain.cs)
int left = Math.Min(r, i);
int right = Math.Min(r, n - 1 - i);
int start = i - left;
int W = left + right + 1;
double filtered;
if (W < 1)
{
filtered = 0.0;
}
else
{
double sigmaLocal = W / sigmaFactor;
double[] localGauss = ComputeGaussianCoefficients(W, sigmaLocal); // normalized per-window
var values = new double[W];
var wts = new double[W];
for (int pos = 0; pos < W; pos++)
{
int absIdx = start + pos;
values[pos] = input[absIdx]; // direct in-range access (Adaptive avoids padding)
wts[pos] = localGauss[pos];
}
Array.Sort(values, wts);
double total = 0.0;
for (int j = 0; j < W; j++)
{
total += wts[j];
}
if (total > 0.0)
{
double half = total / 2.0;
double accum = 0.0;
int sel = W - 1;
for (int j = 0; j < W; j++)
{
accum += wts[j];
if (accum >= half)
{
sel = j;
break;
}
}
filtered = values[sel];
}
else
{
filtered = 0.0;
}
}
// Alpha blend (applies when GWMF is enabled)
gaussMedian[i] = a * filtered + (1.0 - a) * input[i];Note
- Weight generation strictly follows
ComputeGaussianCoefficients(length, sigma)(positive σ, normalized coefficients, sum = 1). - BoundaryMode is honored via
GetValueWithBoundaryin non-Adaptive paths; Adaptive uses direct in-range slicing and per-window σ to avoid distortions. - Alpha blending is clamped once (a ∈ [0, 1]) and applied to GWMF alongside Binomial Average, Binomial Median, and Gaussian filters.
- Export metadata includes "Alpha Blend" when any of Avg / Med / GaussMed / Gauss are selected. In Excel, the condition is
(doAvg || doMed || doGaussMed || doGauss); in CSV, the header row uses the same condition.
Applies a normalized 1D Gaussian kernel honoring the selected Boundary Mode (Symmetric, Replicate, ZeroPad, Adaptive). In Adaptive mode the kernel length W shrinks and σ = W / sigmaFactor is recomputed.
Gaussian weights emphasize central values, producing smooth results.
The standard deviation (σ) for the Gaussian kernel is calculated as :
- Symmetric mode : σ = (2 × radius + 1) / Sigma Factor
- Adaptive mode : σ = W / Sigma Factor (W : window length at each index)
- Sigma Factor is user-configurable (default : 6.0), clamped to [1.0, 12.0], and applies to both Gaussian Weighted Median and Gaussian Filtering. The actual value used is always reflected in export metadata and calculations.
if (useGauss)
{
double sigma = (2.0 * r + 1) / sigmaFactor;
// SigmaFactor is user-configurable (default : 6.0, range : 1.0 - 12.0)
double[] gaussCoeffs = ComputeGaussianCoefficients(2 * r + 1, sigma);
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
for (int i = 0; i < input.Length; i++)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
{
sum += gaussCoeffs[k + r] * Sample(i + k);
}
gaussFiltered[i] = sum;
}
}Adaptive mode recomputes a Gaussian kernel of length W with σ = W / Sigma Factor (where Sigma Factor is clamped to [1.0, 12.0]). This ensures the relative spread remains consistent across varying window sizes and avoids artificial flattening. The actual Sigma Factor used is always reflected in export metadata and calculations.
A fixed-size window of length 2 × r + 1 slides over the 1D signal.
At each position :
- Out‑of‑bounds indices are handled according to the selected Boundary Mode. With Adaptive (auto‑selected when SG is chosen), the window is shifted asymmetrically near edges to maintain length without artificial padding; non‑Adaptive modes map indices using Mirror (Symmetric), Replicate (Nearest), or Zero‑Pad.
- Each sample in the window is multiplied by its precomputed Savitzky‑Golay coefficient (derived from polynomial least‑squares fitting), and the weighted sum gives the smoothed output at the central point. (When Adaptive is selected, the window is shifted asymmetrically near edges and coefficients are recomputed from edge-specific least‑squares fits with caching.)
This method preserves important features such as peaks and edges better than simple moving averages.
Savitzky‑Golay filtering performs a least‑squares fit of a low‑degree polynomial to the samples in the window, then evaluates the polynomial at the center.
Unlike Gaussian filtering, the weights are not based on a bell‑shaped curve, but are determined analytically to minimize the mean‑squared error for the chosen polynomial degree.
- Polynomial Fitting : Fits a polynomial of specified degree within the window.
- Feature Preservation : Retains higher‑order moments (e.g., slope, curvature) while reducing high‑frequency noise.
else if (useSG)
{
double[] sgCoeffs = ComputeSavitzkyGolayCoefficients(2 * r + 1, polyOrder, derivOrder : 0, delta : 1.0);
double Sample(int idx) => GetValueWithBoundary(input, idx, boundaryMode);
for (int i = 0; i < input.Length; i++)
{
double sum = 0.0;
for (int k = -r; k <= r; k++)
{
sum += sgCoeffs[k + r] * Sample(i + k);
}
sgFiltered[i] = sum;
}
}Note
This snippet shows the non‑Adaptive, smoothing‑only path. Runtime uses the derivative‑capable overload and switches to asymmetric coefficients near edges when Adaptive is selected.
Used only when Savitzky-Golay is selected.
| Value | Meaning | Typical Use |
|---|---|---|
| 0 | Smoothing (original SG) | Noise reduction while preserving shape |
| 1 | First derivative (slope) | Trend / rate-of-change detection |
| 2 | Second derivative (curvature) | Peak / inflectioN/Acceleration analysis |
| 3 | Third derivative | Specialized scientific diagnostics (use cautiously) |
Rules & Validation :
derivOrder ≤ polyOrder(enforced during calibration & export).- In adaptive edge windows : effective polynomial
effPoly = min(polyOrder, W - 1); ifderivOrder > effPoly, an error is raised. - Coefficient scaling : derivative coefficients are multiplied by
factorial(derivOrder) / delta^derivOrder(delta = 1.0in current implementation). - For
derivOrder == 0, coefficients are normalized to sum to 1 (DC preservation). ForderivOrder > 0, no DC normalization (derivative sums ≠ 1 by design).
Guidance :
- Start with 0 (smoothing) or 1 (slope).
- Higher orders rapidly amplify noise - ensure sufficient radius (larger window) before using 2 or 3.
- Avoid derivative orders close to polyOrder when the dataset is short or radius is minimal.
- For Adaptive edge windows, the effective polynomial order is clamped : effPoly = min(polyOrder, W - 1). Derivative coefficients are scaled by factorial(derivOrder) / delta^derivOrder after construction, matching the symmetric path.
To avoid recomputing edge-specific polynomial fits repeatedly, two in-memory caches are maintained :
_sgAsymCoeffCachekeyed by(left, right, effectivePolyOrder)for smoothing (derivOrder = 0)_sgAsymDerivCoeffCachekeyed by(left, right, effectivePolyOrder, derivOrder, deltaBits)for derivatives
Cache keys :
- Smoothing : (left, right, effPoly)
- Derivative : (left, right, effPoly, derivOrder, deltaBits) where deltaBits = BitConverter.DoubleToInt64Bits(delta)
These caches drastically reduce overhead on large datasets with repeated edge window shapes.
After filtering, the results array is handed to AddItemsInBatches, which inserts items into lbRefinedData in chunks. This avoids freezing the UI and allows incremental progress updates. Finally, controls are reset.
Batch updates and progress reporting keep the UI responsive. A finally block ensures the progressbar always resets on completion or error.
- Batch UI Update : Efficiently updates the list box.
- Progress Feedback : Shows operation progress to the user.
- Status Reporting : Updates labels and enables / disables controls.
lbRefinedData.BeginUpdate();
lbRefinedData.Items.Clear();
await AddItemsInBatches(lbRefinedData, results, progressReporter, 60);
lbRefinedData.EndUpdate();
lblRefCnt.Text = $"Count : {lbRefinedData.Items.Count}";
slblCalibratedType.Text = useRect ? "Rectangular Average"
: useMed ? "Weighted Median"
: useAvg ? "Binomial Average"
: useSG ? "Savitzky-Golay Filter"
: useGauss ? "Gaussian Filter"
: "Unknown";
slblKernelRadius.Text = r.ToString();Buttons btnInitSelectSync and btnRefSelectSync synchronize selected indices and scroll positions between Initial and Refined datasets.
Enablement rule :
- Both listboxes must contain the same number of items.
- At least one item is selected in the source listbox.
Behavior :
- Clears target selection.
- Copies all selected indices.
- Sets
TopIndexof target to match source for scroll alignment. Status label displays a synchronized item count message.
-
Select All : updates
pbMainfrom 0% - 100% using dynamic intervals- Report interval :
reportInterval = max(1, count / 100) - UI yield interval :
yieldInterval = max(1, count / 1000)withawait Task.Yield() - Cancelable : a new Select All starts cancels the previous via
_ctsInitSelectAll/_ctsRefSelectAll - Completion : briefly holds 100%, then resets to 0
- Report interval :
-
Deselect All : step‑based progress (0 → selectedCount)
- Increments the progress bar per deselected item with a small delay (
await Task.Delay(10)) - Resets to 0 on completion
- Increments the progress bar per deselected item with a small delay (
This keeps very large list operations smooth and clearly communicated without blocking the UI thread.
Clicking "Clear Refined" removes all entries from the Refined Dataset and resets status indicators :
slblCalibratedType→ "--"slblKernelRadius→ "--"- SG parameter labels hidden :
tlblPolyOrder,slblPolyOrder, and the separator label;slblPolyOrdertext is set to "--"
The progress bar briefly shows 100% and then returns to 0% to signal completion.
Calculates binomial coefficients for a given window size, which are used as weights in the binomial average and weighted median filters.
- Pascal's Triangle : Each coefficient is the sum of the two above it, or mathematically, C(n, k) = n! / (k!(n - k)!).
- Symmetry : The coefficients are symmetric and always sum to a power of two.
private static long[] CalcBinomialCoefficients(int length)
{
if (length < 1)
throw new ArgumentException("length must be ≥ 1", nameof(length));
// Limit 'length' to ensure the sum can be safely calculated within the 64-bit long range
// (Condition : 2 ^ (length - 1) ≤ 2 ^ 62)
if (length > 63)
throw new ArgumentOutOfRangeException(nameof(length),
"length must be ≤ 63 to avoid 64-bit weight sum overflow (2 ^ (length - 1) <= 2 ^ 62). Reduce kernel radius.");
var c = new long[length];
c[0] = 1; // The first coefficient is always 1
try
{
checked // Throw an exception if an overflow occurs
{
for (int i = 1; i < length; i++)
c[i] = c[i - 1] * (length - i) / i;
}
}
catch (OverflowException ex)
{
throw new InvalidOperationException(
$"Binomial coefficient overflow for length = {length}. Try a smaller kernel radius.", ex);
}
return c;
}- This function generates the coefficients for the (length - 1) th row of Pascal's triangle, which are used as weights for the filters.
- Maximum supported window length for binomial weighting is 63 (length ≤ 63) to prevent 64‑bit overflow of the cumulative weight (2^(length − 1) ≤ 2^62). Attempts above this throw an exception.
Constructs a Vandermonde matrix for the window, computes its normal equations, inverts the Gram matrix, and multiplies back by the transposed design matrix. The first row of the resulting "smoother matrix" yields the filter coefficients.
Savitzky-Golay filters derive from least‐squares polynomial fitting.
- Build matrix A where each row contains powers of the relative offset within the window.
- Form the normal equations (AᵀA), invert them, and multiply by Aᵀ to get the pseudoinverse.
- The convolution coefficients for smoothing (value at central point) are the first row of this pseudoinverse.
private static double[] ComputeSavitzkyGolayCoefficients(
int windowSize,
int polyOrder,
int derivOrder,
double delta)
{
if (windowSize <= 0)
throw new ArgumentOutOfRangeException(nameof(windowSize), "windowSize must be > 0.");
if ((windowSize & 1) == 0)
throw new ArgumentException("windowSize must be odd (2 * r + 1).", nameof(windowSize));
if (polyOrder < 0)
throw new ArgumentOutOfRangeException(nameof(polyOrder), "polyOrder must be ≥ 0.");
if (polyOrder >= windowSize)
throw new ArgumentException("polyOrder must be < windowSize.", nameof(polyOrder));
if (derivOrder < 0)
throw new ArgumentOutOfRangeException(nameof(derivOrder), "derivOrder must be ≥ 0.");
if (derivOrder > polyOrder)
throw new ArgumentException("derivOrder must be ≤ polyOrder.");
if (delta <= 0)
throw new ArgumentOutOfRangeException(nameof(delta), "delta must be > 0.");
int m = polyOrder;
int half = windowSize / 2;
// Design matrix
var A = new double[windowSize, m + 1];
for (int i = -half; i <= half; i++)
{
double x = i;
double p = 1.0;
for (int j = 0; j <= m; j++)
{
A[i + half, j] = p;
p *= x;
}
}
// Normal equations (A^T A)
var ATA = new double[m + 1, m + 1];
for (int i = 0; i <= m; i++)
{
for (int j = 0; j <= m; j++)
{
double s = 0;
for (int k = 0; k < windowSize; k++)
s += A[k, i] * A[k, j];
ATA[i, j] = s;
}
}
var invATA = InvertMatrixStrict(ATA);
// A^T
var AT = new double[m + 1, windowSize];
for (int i = 0; i <= m; i++)
{
for (int k = 0; k < windowSize; k++)
AT[i, k] = A[k, i];
}
// Coefficient row for derivOrder
var h = new double[windowSize];
for (int k = 0; k < windowSize; k++)
{
double sum = 0;
for (int j = 0; j <= m; j++)
sum += invATA[derivOrder, j] * AT[j, k];
h[k] = sum;
}
if (derivOrder == 0)
{
// DC normalization for smoothing
double total = 0;
for (int i = 0; i < windowSize; i++)
total += h[i];
if (Math.Abs(total) < 1e-20)
throw new InvalidOperationException("Computed Savitzky-Golay coefficients sum to ~ 0.");
for (int i = 0; i < windowSize; i++)
h[i] /= total;
}
else
{
// Derivative scaling : factorial(d) / Δ^d
double scale = FactorialAsDouble(derivOrder) / Math.Pow(delta, derivOrder);
for (int i = 0; i < windowSize; i++)
h[i] *= scale;
}
return h;
}Note
- Implementation uses InvertMatrixStrict with partial pivoting and a dynamic tolerance 1e - 14 × rowScale for stability.
- Derivative-capable version in runtime adds derivOrder & delta parameters; see full adaptive + derivative implementation in the unified ApplySmoothing section above.
SonataSmooth uses a robust numerical matrix inversion routine for Savitzky-Golay filter coefficient calculation.
This routine applies Gauss-Jordan elimination with partial pivoting and a dynamic, scale-based tolerance to ensure numerical stability and prevent division-by-zero or propagation of NaN / Infinity values.
- For each column, the row with the largest absolute value is selected as the pivot and swapped to the top.
- The pivot row is normalized, and all other rows are updated to eliminate the current column.
- If the pivot is below a dynamic threshold (based on the row's scale and machine epsilon), the matrix is considered singular and an InvalidOperationException is thrown.
- Partial Pivoting : Improves numerical stability by always using the largest available pivot.
- Dynamic Tolerance : Prevents catastrophic errors by comparing the pivot to a scaled threshold, not a fixed value.
- Singular Matrix Handling : If the matrix cannot be inverted (pivot too small), an InvalidOperationException is thrown to prevent propagating errors.
This approach ensures that Savitzky-Golay and other matrix-based filters remain stable and reliable, even for ill-conditioned or nearly singular matrices.
private static double[,] InvertMatrixStrict(double[,] a)
{
int n = a.GetLength(0);
if (a.GetLength(1) != n)
throw new ArgumentException("Matrix must be square.", nameof(a));
var aug = new double[n, 2 * n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
aug[i, j] = a[i, j];
aug[i, n + i] = 1.0;
}
for (int i = 0; i < n; i++)
{
int maxRow = i;
double maxVal = Math.Abs(aug[i, i]);
for (int r = i + 1; r < n; r++)
{
double v = Math.Abs(aug[r, i]);
if (v > maxVal) { maxVal = v; maxRow = r; }
}
if (maxRow != i)
{
for (int c = 0; c < 2 * n; c++)
{
double tmp = aug[i, c];
aug[i, c] = aug[maxRow, c];
aug[maxRow, c] = tmp;
}
}
double pivot = aug[i, i];
double rowScale = 0;
for (int c = i; c < n; c++)
rowScale = Math.Max(rowScale, Math.Abs(aug[i, c]));
double tol = Math.Max(rowScale * 1e-14, double.Epsilon);
if (Math.Abs(pivot) < tol)
throw new InvalidOperationException("Matrix is singular or ill-conditioned for inversion.");
for (int c = 0; c < 2 * n; c++)
aug[i, c] /= pivot;
for (int r = 0; r < n; r++)
{
if (r == i) continue;
double factor = aug[r, i];
if (Math.Abs(factor) < 1e-20) continue;
for (int c = 0; c < 2 * n; c++)
aug[r, c] -= factor * aug[i, c];
}
}
var inv = new double[n, n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
inv[i, j] = aug[i, j + n];
return inv;
}- This function is used internally for Savitzky-Golay coefficient calculation and any other matrix inversion needs in the application.
- It is robust against singular and ill-conditioned matrices, ensuring reliable filter performance.
Note
- All UI controls and code elements use clear, descriptive names for maintainability.
- All heavy computations and file operations are performed asynchronously and in parallel for maximum responsiveness.
- All UI updates and COM object accesses are performed on the UI thread for safety.
- Status messages and tooltips are dynamically updated to guide the user through each operation.
When the user selects the CSV export option and clicks Export, the application :
- Reads the initial dataset and selected smoothing parameters (Radius, Polynomial / Derivative for SG, Boundary Method, Alpha).
- Applies all enabled filters (Rectangular, Binomial Average, Weighted Median, Gaussian, Savitzky‑Golay) in a single
ApplySmoothingpass that includes thealphaparameter for blending. - Splits the output into multiple CSV files if the dataset exceeds Excel's row limit.
- Writes metadata, parameters, and results to each file in a structured format.
- When output is split, files are named using the pattern : {baseName}_Part{X}.csv.
- The SaveFileDialog defaults the file name to the dataset title : "{DatasetTitle}.csv".
- While the SaveFileDialog is open, the main progress bar switches to marquee mode; it is restored afterward.
All enabled filter outputs are computed in one ApplySmoothing call (no per‑filter recomputation), then written.
- Alpha blend usage : applied only to Binomial Averaging, Binomial Median, and Gaussian; not applied to Rectangular or Savitzky‑Golay.
- Formula per element i : output[i] = α × filtered[i] + (1 − α) × input[i]
- Modular columns per filter, scalable multi‑part output, and embedded metadata for reproducibility.
- Conditional header rows include "Alpha Blend" only when it is relevant (Binomial Average / Binomial Median / Gaussian selected).
Compute with Alpha (single pass) :
var (rectAvgResult,
binomAvgResult,
medianResult,
gaussMedResult,
gaussResult,
sgResult) = ApplySmoothing(
initialData,
r,
polyOrder,
derivOrder,
delta,
boundaryMode,
doRect,
doAvg,
doMed,
doGaussMed,
doGauss,
doSG,
alpha,
sigmaFactor
);Header layout per part (Alpha row is conditional) :
await sw.WriteLineAsync(excelTitle);
await sw.WriteLineAsync($"Part {part + 1} of {partCount}");
await sw.WriteLineAsync(string.Empty);
await sw.WriteLineAsync("Smoothing Parameters");
await sw.WriteLineAsync($"Kernel Radius : {r}");
await sw.WriteLineAsync($"Kernel Width : {kernelWidth}");
await sw.WriteLineAsync($"Boundary Method : {GetBoundaryMethodText(boundaryMode)}");
if (doAvg || doMed || doGaussMed || doGauss)
await sw.WriteLineAsync($"Alpha Blend : {alpha}");
if (doGauss || doGaussMed)
await sw.WriteLineAsync($"Gaussian Sigma : {kernelWidth} ÷ {sigmaFactor} = {(kernelWidth / sigmaFactor).ToString("0.###", CultureInfo.InvariantCulture)}");
if (doSG)
await sw.WriteLineAsync($"Polynomial Order : {polyOrder}");
if (doSG)
await sw.WriteLineAsync($"Derivative Order : {derivOrder}");
await sw.WriteLineAsync(string.Empty);
await sw.WriteLineAsync($"Generated : {DateTime.Now.ToString("G", CultureInfo.CurrentCulture)}");
await sw.WriteLineAsync(string.Empty);
// Column headers
await sw.WriteLineAsync(string.Join(",", columns.Select(c => c.Header)));
// Internal CSV export (collects UI state; no external parameters)
private async Task ExportCsvAsync()
{
// 1. Initialize progress bar (pbMain).
// 2. Parse UI values : radius (r), polyOrder, derivOrder (if SG), boundaryMode.
// 3. Validate with ValidateSmoothingParameters (OperationResult).
// 4. Enforce derivative rule (derivOrder ≤ polyOrder if SG).
// 5. Compute all enabled filters in one pass :
// var (rect, binomAvg, median, gaussMed, gauss, sg) = ApplySmoothing(...);
// 6. Prepare columns list :
// ("Initial Dataset"), plus each enabled filter.
// 7. Multi-part writing logic (splits if row threshold exceeded):
// Header layout per part :
// Title
// Part X of Y
// (blank)
// Smoothing Parameters
// Kernel Radius : r
// Kernel Width : (2 * r + 1)
// Boundary Method : {Adaptive|Symmetric|Replicate|Zero Padding}
// [Alpha Blend : alpha] (if Binomial Average / Binomial Median / Gaussian Weighted Median / Gaussian)
// [Polynomial Order : polyOrder] (if SG)
// [Derivative Order : derivOrder] (if SG)
// (blank)
// Generated : timestamp
// (blank)
// CSV header row : Initial Dataset, then enabled filter column headers
// Data rows : ONLY numeric values per column (no index)
// 8. Progress reported (0 - 100) as rows flush.
// 9. Optional auto-open of generated file(s).
// CSV export always prompts for a target file via a SaveFileDialog; the base file name is used for all parts when the dataset is split.
}Columns include :
- Initial Dataset
- Rectangular Averaging (no alpha blend)
- Binomial Averaging (alpha‑blended)
- Binomial Median Filtering (alpha‑blended)
- Gaussian Weighted Median Filtering (alpha‑blended)
- Gaussian Filtering (alpha‑blended)
- Savitzky–Golay Filtering (no alpha blend)
Progress is reported 0 - 100 as rows are written; the bar resets to 0 when done. Optional auto‑open for generated file(s) is supported.
When the user selects Excel export and clicks Export, the application :
- Reads the initial dataset and all selected parameters from the UI.
- Applies all enabled filters in one pass with
ApplySmoothing(Alpha is applied only to Binomial Average / Binomial Median / Gaussian). - Writes each filter result to its own column in a new worksheet.
- Embeds metadata at the top : title, kernel radius, kernel width, boundary method, alpha (conditional), polynomial / derivative (for SG).
- Automatically generates a line chart across the written columns.
- Cleans up all COM objects and runs GC to avoid lingering Excel processes.
Save behavior and progress :
- If "Open file after save" is unchecked, a SaveFileDialog is shown. While it is open, the main progress bar switches to marquee mode (restored afterward).
- If a path is chosen, SaveAs is executed. During SaveAs, the progress bar is simulated to 95% using a timer, then set to 100% on completion, and the workbook is closed without showing Excel.
- If no path is chosen (or saving is skipped), Excel is shown with the unsaved workbook (
excel.Visible = true) so the user can save manually. - A previously considered "open as temporary file" path is currently commented out; the active behavior is "open in Excel without saving."
Additional details :
- Worksheet name is set to the dataset title (txtDatasetTitle.Text).
- Line chart axes are titled "Value" (Y) and "Sequence Number" (X) for clearer interpretation.
- Excel export opens an interactive Excel instance with the new workbook; the application does not auto‑save a .xlsx file. Save the workbook from Excel when ready.
- Parallel Filtering : All smoothing algorithms are applied in parallel for speed and consistency.
- Structured Output : Each filter result is placed in its own column, with clear headers and parameter info.
- Musical Metadata : Excel document properties and comments are dynamically set with musical phrases and filter info for a unique, playful touch.
- Chart Generation : A line chart is automatically created to compare all filter outputs visually.
- Robust COM Cleanup : All Excel interop objects are released and garbage collected to avoid memory leaks.
- Metadata Rows : A1 Title, A3 label, A4 Radius, A5 Width, A6 Boundary, A7 Alpha Blend, A8 Gaussian Sigma, A9 Polynomial (or N/A), A10 Derivative (or N/A).
- Excel Built-in Document Properties set : Title, Category, Author, Last Author, Keywords, Subject, Comments (random musical phrase + quartet Easter egg when exactly four methods are enabled).
Note
The "Generated" timestamp row is written in CSV export; it is not written as a worksheet row in Excel export.
Alpha metadata row :
ws.Cells[row++, 1] = (doAvg || doMed || doGauss || doGaussMed)
? $"Alpha Blend : {alpha.ToString("0.00", CultureInfo.InvariantCulture)}"
: "Alpha Blend : N/A";
if (doGauss || doGaussMed)
{
int kernelWidth = 2 * r + 1;
ws.Cells[row++, 1] = $"Gaussian Sigma : {kernelWidth} ÷ {sigmaFactor} = {(kernelWidth / sigmaFactor).ToString("0.###", CultureInfo.InvariantCulture)}";
}
if (doSG)
{
ws.Cells[row++, 1] = $"Polynomial Order : {polyOrder}";
ws.Cells[row++, 1] = $"Derivative Order : {derivOrder}";
}SaveFileDialog with marquee while open :
var prevStyle = pbMain.Style;
var prevValue = pbMain.Value;
int prevMarquee = pbMain.MarqueeAnimationSpeed;
pbMain.Style = ProgressBarStyle.Marquee;
pbMain.MarqueeAnimationSpeed = 30;
try
{
using (var sfd = new SaveFileDialog()
{
Title = "Save Excel Workbook",
Filter = "Excel Workbook (*.xlsx)|*.xlsx",
FileName = $"{txtDatasetTitle.Text}.xlsx",
AddExtension = true,
DefaultExt = "xlsx",
OverwritePrompt = true
})
{
if (sfd.ShowDialog(this) == DialogResult.OK)
{
savePath = sfd.FileName;
}
}
}
finally
{
pbMain.MarqueeAnimationSpeed = prevMarquee;
pbMain.Style = prevStyle;
pbMain.Value = prevValue;
}Save‑or‑open workflow :
bool promptForSave = settingsForm.chbOpenFile != null
&& !settingsForm.chbOpenFile.Checked;
string savePath = null;
// SaveFileDialog (marquee) shown only when promptForSave is true…
bool willShowExcel = string.IsNullOrWhiteSpace(savePath);
if (!string.IsNullOrWhiteSpace(savePath))
{
var prevStyle = pbMain.Style;
var prevValue = pbMain.Value;
pbMain.Style = ProgressBarStyle.Continuous;
pbMain.Minimum = 0;
pbMain.Maximum = 100;
pbMain.Value = 0;
var saveProgressTimer = new System.Windows.Forms.Timer { Interval = 120 };
saveProgressTimer.Tick += (s, e) =>
{
try
{
if (pbMain.Value < 95)
{
pbMain.Value = Math.Min(95, pbMain.Value + 3);
}
}
catch
{
// Ignore UI race
}
};
saveProgressTimer.Start();
try
{
excel.DisplayAlerts = false;
wb.SaveAs(savePath, Excel.XlFileFormat.xlOpenXMLWorkbook);
wb.Saved = true;
saveProgressTimer.Stop();
pbMain.Value = 100;
willShowExcel = false;
}
catch (Exception sx)
{
saveProgressTimer.Stop();
MessageBox.Show(
$"Failed to save workbook : \n{sx.Message}",
"Save Error",
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
pbMain.Value = 0;
// Previously considered : willShowExcel = true; (currently commented out)
}
finally
{
excel.DisplayAlerts = true;
await Task.Delay(150);
pbMain.Style = prevStyle;
pbMain.Value = 0;
}
}
if (willShowExcel)
{
excel.Visible = true; // user saves manually
}
else
{
try
{
wb.Close(false);
}
catch
{
// ignore
}
try
{
excel.Quit();
}
catch
{
// ignore
}
bool openAfter = settingsForm.chbOpenFile != null && settingsForm.chbOpenFile.Checked;
if (!openAfter)
{
MessageBox.Show(
this,
"Excel export completed.",
"Export Excel",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
}Completion message :
- When "Open after save" is unchecked in Export Settings, a completion dialog is shown after a successful
.xlsxexport :- Message : "Excel export completed."
- CSV export already shows a similar completion message.
This ensures consistent UX between CSV and Excel modes.
Note
The code sample omits the BuiltinDocumentProperties musical metadata and Easter egg block for brevity; runtime sets Title, Category, Author, Subject, Keywords, Comments with randomized phrases and quartet unlock message when exactly four methods are enabled.
private async void ExportExcelAsync()
{
// Parse UI
int r = int.TryParse(cbxKernelRadius.Text, out var rr) ? rr : 2;
int polyOrder = int.TryParse(cbxPolyOrder.Text, out var po) ? po : 2;
int derivOrder = (cbxDerivOrder != null && int.TryParse(cbxDerivOrder.Text, out var d)) ? d : 0;
var boundaryMode = GetBoundaryMode();
bool doRect = settingsForm.chbRect.Checked;
bool doAvg = settingsForm.chbAvg.Checked;
bool doMed = settingsForm.chbMed.Checked;
bool doGaussMed = settingsForm.chbGaussMed.Checked;
bool doGauss = settingsForm.chbGauss.Checked;
bool doSG = settingsForm.chbSG.Checked;
var initialData = lbInitData.Items
.Cast<object>()
.Select(x => double.TryParse(x?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : 0.0)
.ToArray();
// Validate
var vr = ValidateSmoothingParameters(initialData.Length, r, polyOrder);
if (!vr.Success)
{
ShowError("Export Parameter Error", vr.Error);
return;
}
if (doSG && derivOrder > polyOrder)
{
ShowError("Export Parameter Error", "Derivative order must be ≤ polynomial order.");
return;
}
// Compute filters
var (rect, binomAvg, median, gaussMed, gauss, sg) =
ApplySmoothing(initialData, r, polyOrder, derivOrder, delta, boundaryMode, doRect, doAvg, doMed, doGaussMed, doGauss, doSG);
// Start Excel
Excel.Application excel = null;
Excel.Workbook wb = null;
Excel.Worksheet ws = null;
var comStack = new Stack<object>();
try
{
excel = new Excel.Application();
comStack.Push(excel);
wb = excel.Workbooks.Add();
comStack.Push(wb);
ws = (Excel.Worksheet)wb.Worksheets[1];
comStack.Push(ws);
// Metadata
ws.Cells[1, 1] = txtDatasetTitle.Text;
ws.Cells[3, 1] = "Smoothing Parameters";
ws.Cells[4, 1] = $"Kernel Radius : {r}";
ws.Cells[5, 1] = $"Kernel Width : {2 * r + 1}";
ws.Cells[6, 1] = $"Boundary Method : {GetBoundaryMethodText(boundaryMode)}";
ws.Cells[7, 1] = (doAvg || doMed || doGauss || doGaussMed)
? $"Alpha Blend : {alpha.ToString("0.00", CultureInfo.InvariantCulture)}"
: "Alpha Blend : N/A";
ws.Cells[8, 1] = doSG
? $"Polynomial Order : {polyOrder}"
: "Polynomial Order : N/A";
ws.Cells[9, 1] = doSG
? $"Derivative Order : {derivOrder}"
: "Derivative Order : N/A";
// Sections (C column base)
int col = 3;
async Task WriteSection(string title, double[] data, bool enabled)
{
if (!enabled) return;
ws.Cells[3, col] = title;
for (int i = 0; i < data.Length; i++)
ws.Cells[4 + i, col] = data[i];
col += 2;
await Task.Yield();
}
await WriteSection("Initial Dataset", initialData, true);
await WriteSection("Rectangular Averaging", rect, doRect);
await WriteSection("Binomial Averaging", binomAvg, doAvg);
await WriteSection("Binomial Median Filtering", median, doMed);
await WriteSection("Gaussian Filtering", gauss, doGauss);
await WriteSection("Savitzky-Golay Filtering", sg, doSG);
// Chart (simplified)
var charts = (Excel.ChartObjects)ws.ChartObjects();
comStack.Push(charts);
var chartObj = charts.Add(ws.Cells[3, col + 1].Left, ws.Cells[3, col + 1].Top, 900, 600);
comStack.Push(chartObj);
var chart = chartObj.Chart;
comStack.Push(chart);
chart.ChartType = Excel.XlChartType.xlLine;
chart.HasTitle = true;
chart.ChartTitle.Text = txtDatasetTitle.Text;
// (Series union logic omitted for brevity)
excel.Visible = true;
}
catch (Exception ex)
{
ShowError("Export Error", ex.Message);
}
finally
{
while (comStack.Count > 0)
{
var o = comStack.Pop();
try
{
if (o != null && System.Runtime.InteropServices.Marshal.IsComObject(o))
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(o);
}
catch
{
}
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}COM cleanup and Office guidance (unchanged):
- All COM RCWs released via
FinalReleaseComObject+ double GC waits. - On
COMException, the user is prompted to open the Microsoft 365 download page.
This behavior ensures :
- Alpha is calculated and documented consistently in both CSV and Excel exports.
- The SaveFileDialog uses marquee mode while open; SaveAs simulates progress deterministically.
- If not saved, Excel opens interactively with the unsaved workbook.
- The temporary‑file open path is noted as currently commented out in the code.
Both CSV and Excel exports now include :
- Kernel Radius and computed Kernel Width
- Sigma Factor (actual value used in calculation, as selected by the user and clamped to [1.0, 12.0])
- Computed Gaussian Sigma (σ = kernel width / Sigma Factor, or W / Sigma Factor in Adaptive mode; always matches the runtime calculation)
- Polynomial Order (if SG selected)
- Derivative Order (if SG selected)
- Boundary Method (reflects selected mode, including Adaptive)
- Timestamp
Excel additionally embeds musical-themed properties and (if four methods selected) the hidden "Quartet" comment.
Specific exception handling paths :
- COMException : Excel not installed / interop failure → guidance dialog.
- ArgumentException / InvalidCastException : malformed property assignments.
- UnauthorizedAccessException : insufficient file or registry permission.
- PathTooLongException, DirectoryNotFoundException, IOException, OutOfMemoryException, BadImageFormatException : environment and file-system issues.
All COM RCWs are released via FinalRelease and followed by dual GC cycles to prevent orphaned Excel processes.
- Manual Entry : Users can type numeric values into a textbox and press Enter or click the Add button to insert them into the dataset.
- Clipboard Paste : Pressing Ctrl + V or clicking the Paste button automatically parses and adds numeric values from the clipboard.
- Drag & Drop : Users can drag HTML or plain text into the list box; the application extracts and adds valid numeric entries.
- Regex-Based Filtering : Regular expressions are used to clean HTML tags and extract numbers, allowing flexible input formats.
When the user clicks Calibrate Button :
- All input values are converted to a double[] array.
- Kernel radius and polynomial order are parsed from combo-boxes.
- The selected filter is applied via Parallel.For (falls back to serial for small datasets).
- Results are added to the output list box in batches, with progress feedback.
- ProgressBar (
pbMain) is reused across smoothing and export operations (0 - 100), reset to 0 after completion or cancellation. - If no items exist in the Initial Dataset, calibration / export handlers exit immediately after resetting progress.
- During calibration the following controls are temporarily disabled and restored afterward : btnCalibrate, btnInitClear, btnInitEdit, btnInitPaste, btnInitDelete, btnInitSelectAll, btnInitSelectSync, btnRefClear, btnRefSelectSync, btnRefSelectAll.
- Rectangular Mean : Computes the average of values within a fixed-size window.
- Weighted Median : Uses binomial coefficients as weights to compute a robust median.
- Binomial Average : Applies Pascal's triangle coefficients for a Gaussian-like smoothing.
- Savitzky-Golay : Constructs a Vandermonde matrix and performs least-squares polynomial fitting.
- Gaussian Filter : Generates a normalized Gaussian kernel and applies it honoring the selected Boundary Mode (Adaptive recomputes σ with the truncated window).
- All filtering operations are executed using
Parallel.Forto utilize all CPU cores. PLINQ is used only during input parsing (clipboard / drag‑drop), not in the smoothing pass. Progress<T>is used to update the UI progress bar in real-time.BeginUpdate/EndUpdateprevent flickering during listbox updates.Task.Yield()ensures the UI thread remains responsive during long operations.
- Filtered results are saved in separate columns.
- Excel export includes automatic chart generation.
- Large datasets are split into multiple files if needed.
- Users can configure filters, kernel radius, polynomial order, and automatically open the file after save options.
Ctrl+C: CopyCtrl+V: PasteCtrl+A: Select AllCtrl+Delete: Delete AllDelete: Delete selected itemsF2: Edit selected item(s)Esc: Deselect all
- Supports data input via manual entry, clipboard paste, and drag-and-drop.
- Automatically parses and validates numeric data, removing non-numeric content (e.g., HTML tags).
- Stores data as high-precision double values for accurate processing.
- Implements multiple noise reduction algorithms : Rectangular Mean, Weighted Median, Binomial Average, Savitzky-Golay, and Gaussian filters.
- Utilizes Parallel.For for smoothing (and PLINQ only for parsing large text inputs).
- Calculates binomial coefficients using Pascal's Triangle for weighted filters.
- Displays processed results in a separate output list for further use.
- Intuitive Windows Forms interface with controls for data entry, editing, and deletion.
- Provides options to select noise reduction algorithm and configure parameters (kernel radius, polynomial order).
- Supports keyboard shortcuts for common actions (delete, copy, paste, select all, etc.).
- Real-time feedback through progress bars and status labels during operations.
- Allows batch selection, editing, and clearing of data points.
- Responsive UI with asynchronous updates to maintain smooth user experience.
- Users can choose the filter type and adjust kernel radius and polynomial order via combo boxes.
- Input validation prevents invalid parameter configurations.
- Modular code structure allows easy addition or extension of filters and configuration options.
- UI is designed to accommodate future enhancements and custom settings.
This application provides a robust and user-friendly environment for noise reduction analysis on numerical datasets. By combining flexible data input methods, a responsive and informative user interface, and efficient parallel processing of advanced filtering algorithms, it enables users to quickly and accurately process their data. The use of Pascal's Triangle for binomial weighting, along with support for a variety of filters, ensures both mathematical rigor and practical versatility.
In particular :
- Uniform mean filtering provides a fast, simple way to suppress random fluctuations.
- Weighted median filtering adds robustness against outliers by privileging central values.
- Binomial averaging approximates a Gaussian blur, yielding gentle, natural-looking smoothing.
- Savitzky-Golay smoothing fits a local low-order polynomial via least-squares, preserving peaks and higher-order signal characteristics while reducing noise.
Beyond the choice of filter, the implementation harnesses Parallel.For for smoothing (and PLINQ only for large-text parsing), and incremental batch updates with a progress reporter keep the application responsive even on large datasets. The adjustable kernel radius and polynomial order give users fine-grained control over the degree and nature of smoothing.
Together, these design decisions ensure that noisy inputs are transformed into clearer, more consistent signals, empowering downstream analysis, visualization, or automated decision-making with higher confidence in the results.
This project is licensed under the MIT License. See the LICENSE file for details.
Copyright ⓒ HappyBono 2025 - 2026. All Rights Reserved.