{"id":48773497,"url":"https://github.com/ricardofrantz/dsgbr","last_synced_at":"2026-04-13T11:30:25.397Z","repository":{"id":338259025,"uuid":"1157162446","full_name":"ricardofrantz/dsgbr","owner":"ricardofrantz","description":"Dual Savitzky-Golay Baseline Ratio (DSGBR) spectral peak detector","archived":false,"fork":false,"pushed_at":"2026-02-13T16:38:51.000Z","size":8465,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-14T00:02:45.758Z","etag":null,"topics":["peak-detection","power-spectral-density","python","savitzky-golay","scipy","signal-processing","spectral-analysis"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/dsgbr/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ricardofrantz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-13T14:01:46.000Z","updated_at":"2026-02-13T16:38:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"99e4f2d2-9abf-4aa6-80c6-38ca3cf81789","html_url":"https://github.com/ricardofrantz/dsgbr","commit_stats":null,"previous_names":["ricardofrantz/dsgbr"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ricardofrantz/dsgbr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricardofrantz%2Fdsgbr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricardofrantz%2Fdsgbr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricardofrantz%2Fdsgbr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricardofrantz%2Fdsgbr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ricardofrantz","download_url":"https://codeload.github.com/ricardofrantz/dsgbr/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricardofrantz%2Fdsgbr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31751075,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T09:16:15.125Z","status":"ssl_error","status_checked_at":"2026-04-13T09:16:05.023Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["peak-detection","power-spectral-density","python","savitzky-golay","scipy","signal-processing","spectral-analysis"],"created_at":"2026-04-13T11:30:24.515Z","updated_at":"2026-04-13T11:30:25.391Z","avatar_url":"https://github.com/ricardofrantz.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DSGBR\n\n[![CI](https://github.com/ricardofrantz/dsgbr/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ricardofrantz/dsgbr/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/ricardofrantz/dsgbr/branch/master/graph/badge.svg)](https://codecov.io/gh/ricardofrantz/dsgbr)\n[![PyPI](https://img.shields.io/pypi/v/dsgbr.svg)](https://pypi.org/project/dsgbr/)\n[![Python](https://img.shields.io/pypi/pyversions/dsgbr.svg)](https://pypi.org/project/dsgbr/)\n[![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](LICENSE)\n\n**Dual Savitzky-Golay Baseline Ratio (DSGBR)** is a spectral peak detector\nfor frequency-domain signals. It was designed for robust detection in dense,\nnoisy power spectra common in fluid dynamics, vibration analysis, and other\nexperimental sciences.\n\n## Algorithm\n\n```\nPSD ──► SEARCH (short-scale SG smooth)\n            │\n            ▼\n        BASELINE (long-scale SG smooth)\n            │\n            ▼\n        RATIO = SEARCH / BASELINE\n            │\n            ▼\n        peaks where RATIO ≥ threshold\n            │\n            ├──► spacing rules\n            ├──► ULF guardrail\n            └──► band selection (if \u003e max_peaks)\n            │\n            ▼\n        (peak_frequencies, peak_heights)\n```\n\nThe detector builds a short-scale **SEARCH** signal and a longer-scale\n**BASELINE** signal using Savitzky-Golay filtering. A peak is accepted\nwhen `SEARCH / BASELINE` exceeds a configurable ratio threshold, subject\nto spacing constraints and an ultra-low-frequency guardrail.\n\n## Install\n\n```bash\npip install dsgbr\n```\n\nFor development:\n\n```bash\ngit clone https://github.com/ricardofrantz/dsgbr.git\ncd dsgbr\nuv pip install -e \".[dev]\"\n```\n\n## Quick start\n\n```python\nimport numpy as np\nfrom dsgbr import dsgbr_detector\n\n# Synthetic PSD with known peaks\nfrequencies = np.linspace(0.001, 1.0, 2048)\npsd = np.ones_like(frequencies)\npsd[400] = 12.0  # inject a peak\npsd[1200] = 8.0  # inject another\n\npeak_f, peak_h = dsgbr_detector(\n    frequencies, psd,\n    case_info={\"ratio_threshold\": 1.5, \"baseline_window\": 61},\n)\nprint(f\"Detected {peak_f.size} peaks at f = {peak_f}\")\n```\n\n## Configuration\n\nAll parameters are set through `DetectionConfig` or passed as a dictionary\nvia the `case_info` argument. Short aliases (RT, SW, BWF, etc.) are\nsupported for concise configuration.\n\n| Parameter              | Alias | Default      | Description                                  |\n| ---------------------- | ----- | ------------ | -------------------------------------------- |\n| `ratio_threshold`      | RT    | 1.8          | Min SEARCH/BASELINE ratio for acceptance     |\n| `smooth_window`        | SW    | 3            | Savitzky-Golay window for SEARCH (odd, \u003e= 3) |\n| `baseline_window_frac` | BWF   | 0.001        | Baseline window as fraction of data length   |\n| `distance_low`         | DL    | 2            | Min bin separation below `switch_frequency`  |\n| `distance_high`        | DH    | 1            | Min bin separation above `switch_frequency`  |\n| `switch_frequency`     | SF    | 0.02         | Frequency threshold for spacing rules        |\n| `max_peaks`            | MP    | 25           | Maximum peaks returned                       |\n| `smooth_polyorder`     | —     | 2            | Polynomial order for SG filter               |\n| `smooth_on_log`        | —     | True         | Smooth log10(PSD) instead of linear          |\n| `baseline_window`      | —     | None         | Fixed baseline window (overrides BWF)        |\n| `baseline_on_log`      | —     | True         | Baseline smoothing in log domain             |\n| `band_strategy`        | —     | proportional | Band allocation: proportional or equal       |\n| `n_bands`              | —     | 10           | Number of logarithmic frequency bands        |\n| `ulf_fmax`             | —     | 0.001        | ULF band upper frequency limit               |\n| `ulf_min_q`            | —     | 9.0          | Minimum Q-factor for ULF peaks               |\n| `ulf_max_points`       | —     | 5            | Maximum ULF peaks to retain                  |\n\n## Advanced usage\n\n### Support series for visualization\n\n```python\nfrom dsgbr import compute_support_series\n\nsupport = compute_support_series(frequencies, psd, case_info={\"RT\": 2.0})\n\n# Plot SEARCH vs BASELINE overlay\nimport matplotlib.pyplot as plt\nplt.semilogy(frequencies, support[\"search_series\"], label=\"SEARCH\")\nplt.semilogy(frequencies, support[\"baseline_series\"], label=\"BASELINE\")\nplt.semilogy(frequencies, support[\"rthreshold\"], \"--\", label=\"Threshold\")\nplt.legend()\nplt.show()\n```\n\n### Band-balanced peak selection\n\n```python\nfrom dsgbr import select_peaks_by_frequency_bands\n\n# Reduce 100 peaks to 15, spread across frequency bands\nsel_f, sel_h = select_peaks_by_frequency_bands(\n    peak_frequencies, peak_heights,\n    max_peaks=15, strategy=\"proportional\", n_bands=8,\n)\n```\n\n### Configuration via dataclass\n\n```python\nfrom dsgbr import DetectionConfig\n\ncfg = DetectionConfig(ratio_threshold=2.5, smooth_window=7, max_peaks=10)\nprint(cfg.to_metadata())\n```\n\n## API reference\n\n| Function / Class                                                         | Description                                  |\n| ------------------------------------------------------------------------ | -------------------------------------------- |\n| `dsgbr_detector(f, psd, *, case_info, return_support)`                   | Main detection pipeline                      |\n| `compute_support_series(f, psd, case_info)`                              | Return intermediate arrays for visualization |\n| `select_peaks_by_frequency_bands(f, h, *, max_peaks, strategy, n_bands)` | Band-balanced down-selection                 |\n| `find_nearest_frequency(target, frequencies, heights)`                   | Closest detected frequency lookup            |\n| `DetectionConfig`                                                        | Frozen dataclass with 17 parameters          |\n| `detect_peaks_case_adaptive(...)`                                        | Deprecated alias for `dsgbr_detector`        |\n| `DSGBR_PARAM_ALIASES`                                                    | Short-to-long parameter name mapping         |\n\n## Examples\n\nSee [`examples/`](examples/) for runnable scripts:\n\n- **`basic_usage.py`** — minimal detection example\n- **`parameter_tuning.py`** — sweep ratio_threshold, compare peak counts\n- **`visualization.py`** — SEARCH/BASELINE overlay plot\n\n## How it works\n\nDSGBR applies two Savitzky-Golay passes at different scales to separate\nsharp spectral peaks from the slowly varying baseline. The ratio between\nthese two series naturally highlights peaks above the local background,\nmaking the detector robust to spectral slope and broadband noise. For a\ndetailed description, see [`docs/algorithm.md`](docs/algorithm.md).\n\n## Citation\n\nIf you use DSGBR in your research, please cite:\n\n```bibtex\n@software{dsgbr2026,\n  author = {Frantz, Ricardo},\n  title = {{DSGBR}: Dual Savitzky--Golay Baseline Ratio spectral peak detector},\n  year = {2026},\n  url = {https://github.com/ricardofrantz/dsgbr},\n}\n```\n\n## License\n\nBSD 3-Clause. See [LICENSE](LICENSE).\n\n## Contributing\n\nContributions are welcome. Please open an issue to discuss changes before\nsubmitting a pull request. Run the full QA suite before submitting:\n\n```bash\nuv pip install -e \".[dev]\"\npre-commit run --all-files\npytest --cov=dsgbr\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricardofrantz%2Fdsgbr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fricardofrantz%2Fdsgbr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricardofrantz%2Fdsgbr/lists"}