Skip to content

The spectrum charts improvements. #833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 63 commits into
base: master
Choose a base branch
from

Conversation

demvlad
Copy link
Contributor

@demvlad demvlad commented May 13, 2025

This PR shows my questions and improving suggestions to compute spectrums data and the charts plot.
The spectrum improvement work started in the next draft PRs:
#819, #820, #821, #826, #827

There are my opinion about current spectrum in master branch after some research

  1. The fft call has any size input and 2*size output.
  2. The source code uses fixed 600000 (in case of my log rate settings) input and 1200000 output. The most values are zero in this case. I suspect that this is "FFT Zero padding" method to get high fft resolution. But 600000 points is too big values for our charts i think
  3. The trying to slice input by actual data size works, but has sometime very low ftt computing speed. (Added missing slices of data samples arrays in spectrum calc #819)
  4. The using of power 2 input size value gives fine speed (this PR).
  5. The FFT output has 2* input size. It contains Re, Im pairs of complex values parts sequence for two side symetrical spectrum. We need to use just half data output.
  6. The source code uses output Re Im values directly as separate value at the chart, what is wrong IMO. It uses output data what size is similar input data. The spectrum chart has 600000points for my log rate settings. The charts show sequence Re,Im,Re,Im... values. This is wrong. We should show magnitude values at the charts as Math.hypo(Re, Im). Magnitudes array has 1/4 input data size

I am not expert in FFT, i study it step by step til.
I would appreciate any help. @KarateBrot , @Quick-Flash , @ledvinap @ctzsnooze @mituritsyn

The previous improving PRs used actual samples count as fft input (#819). Its worked, but had slow hang fft computing sometime. This issue was reason of this PR.
This PR uses dynamical power at 2 fft input size. The samples input size extended to the next power at 2 value by using zero padding. Probably, it needs to limit minimal size value to prevent low fft resolution. It will give balance between speed and quality. Will look.
After improvement of fft computing, this PR joins the previous work:

  1. Resolved issue of magnitude computing at the spectrum charts (Resolved issue of magnitude computing at the spectrum charts #821 )
    The spectrum magnitude is computed by complex Re and Im value as Math.hypot(Re, Im)
    The log file:
    Uploading btfl_all.txt…
    The log files what had very slow spectrum drawing:
    Uploading slow_log.txt…
    log.txt
    Its work fast now.
    spectrum
    spectrum_rpm
    spectrum_thr

  2. Power spectral density curves (Added power spectral density curves at the spectrum chart #820). I tryed to implement Welch PSD computing method follow python SciPy library and Python Spectral Analysis tutorial.
    The PSD value shows in the standard dBm/Hz units. The charts show maximal noise point and have mouse cursor to meassure current mouse position..
    The vertical slider changes curves smoothing (changes samples per segment value in the Welch method).
    PSD_curve
    PSD_curve_nosmooth

  3. Power spectral density vs throttle and RPM heat map charts Added power spectral density charts #827
    The charts have mouse cursor to meassure current mouse position.. The heat map shows PSD values in the standard dBm/Hz units as red color map by using scaling: -40dBm/Hz is black and +10dBm/Hz is white colors. The logoryphmic PSD can show the all values range at the heat map chart.
    PSD_rpm
    PSD_thr

Summary by CodeRabbit

  • New Features
    • Added support for Power Spectral Density (PSD) analysis with new spectrum types: PSD, PSD vs Throttle, and PSD vs RPM.
    • Introduced interactive UI controls for adjusting PSD parameters (min, max, and limit dBm) with reset options.
    • Enhanced graph rendering to display PSD data with detailed scaling, grid lines, and frequency markers.
  • Improvements
    • Standardized FFT buffer sizes to powers of two and improved magnitude calculations for accuracy.
    • Updated heatmap scaling and color mapping for PSD visualizations.
    • Refined UI layout and styles for PSD controls and labels, including fullscreen mode adjustments.
    • Improved performance by limiting the number of spectrum lines rendered dynamically.
  • Bug Fixes
    • Corrected magnitude computations and bin indexing in frequency analysis for more precise spectral data.

Copy link

coderabbitai bot commented May 13, 2025

## Walkthrough

This change introduces comprehensive support for Power Spectral Density (PSD) analysis and visualization. It adds PSD computation using Welch's method, new PSD-related spectrum types, corresponding UI controls, and rendering logic. FFT buffer handling is standardized to power-of-two sizes, and magnitude calculations are corrected. The UI and CSS are updated to integrate PSD controls and visualizations.

## Changes

| File(s)                    | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| src/graph_spectrum_calc.js | Adds PSD computation via Welch’s method, new constants, PSD data loading methods, power-of-two FFT buffer sizing, corrected magnitude calculations, and clarifying comments. Exports and updates several methods and constants for PSD support.                                                                                                                                                                                                                                                                            |
| src/graph_spectrum_plot.js | Adds new PSD spectrum types, constants, state variables, setters, and rendering methods for PSD graphs and heatmaps. Updates graph drawing, heatmap, grid, and interaction logic to support PSD and related UI changes.                                                                                                                                                                                                                                                                                                    |
| src/graph_spectrum.js      | Integrates PSD spectrum types and controls into the analyser UI. Updates data loading, plotting, and event handling for PSD. Refactors `dataBuffer` to local scope. Adds handlers for new PSD sliders and updates spectrum type selection logic for PSD UI elements.                                                                                                                                                                                                                                                    |
| index.html                 | Adds PSD spectrum type options to the dropdown. Introduces new input fields and labels for PSD controls in the analyser section. Adjusts option ordering and adds new UI elements for PSD min/max/limit.                                                                                                                                                                                                                                                                                                                      |
| src/css/main.css           | Adds and adjusts styles for new PSD controls and labels in the analyser. Updates positioning and display rules for PSD-related UI elements.                                                                                                                                                                                                                                                                                                                                                                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant UI (index.html)
    participant Analyser (graph_spectrum.js)
    participant Calc (graph_spectrum_calc.js)
    participant Plot (graph_spectrum_plot.js)

    User->>UI (index.html): Selects PSD spectrum type / adjusts PSD sliders
    UI->>Analyser: Triggers spectrum type or slider change event
    Analyser->>Calc: Calls dataLoadPowerSpectralDensity* or dataLoadPSD
    Calc->>Calc: Computes FFT, applies Welch method, calculates PSD
    Calc-->>Analyser: Returns PSD data
    Analyser->>Plot: Calls plotSpectrum with PSD data and parameters
    Plot->>Plot: Renders PSD graph/heatmap with new min/max/low-level settings
    Plot-->>UI: Updates canvas with PSD visualization

Possibly related PRs

  • #826: The main PR extends and refines the PSD computation and visualization features initially introduced in this PR, adding Welch method PSD calculations, improved FFT buffer handling, and comprehensive UI and plotting support.
  • #830: This PR adds comprehensive PSD computation and rendering features, while the retrieved PR reverts all PSD-related code and UI changes, effectively undoing the PSD functionality.
  • #824: Reverts PSD-related additions and removals, opposing the PSD features introduced here, showing a direct code-level relation.

Suggested reviewers

  • haslinghuis
  • nerdCopter

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNxU3bABsvkCiQBHbGlcABpIcVwvOkgAIjgSe25RXApHRlhnXGR4Zl58KTYMbI1YyDlIbERKSCVmCS80egAKW0gzAA4AZi6ASnCBbHgvWmRsbnwsXhIJeHwq2qoAM1xrG2RmzoBGAE5wzoAmAAZ9joOt04OANkuAdl6I/EgyTIwGRMRksTTmWuo0BR5PDUOZYTD0WYONBeeAALxBkxQWFwsESACFGgwANYCfAAD0gABl8ERIAA1eAkADulA0MFROBR+D48CUxXgS0pOUQDmkkCp6lgEQZDGwFH8xUgADEpdAUHlokVcAiMOEqQgGEKqnyAZy8TF4BhPKtEHDEvglpArkcTjajjx8IbspBmktmcKSPA+F4SX5qB8SLhxBgiIh+n5pN5g6TDehII0KKR+HhjZBmPhEFF5JyfDEBSjILDKBn0Bh6IaSEtOQxKZKmEDleJJnToE8mrR/DzhUhwijEm0nRRFNh3sgAbRZBg0Mx4Ax0Nx8mhNcLqOhaEIqs6+9LZUjU6ai48PZByHjVhMaXwLREqU980Ki0OwNx24aiANoZgayHAcaVdCkhIGJwUA74AM7fAvDwUENCMABBRgKHUWcAL1FAxwXGEYgIY9XloGEf2vGU5XmXBjS4Q1MxIJp+EtKo3z9KkIwAkDcjQIhDWceQXwoZ1aC9FIvHkNBkE1LIHSdDZ1VnIVplmeZECExg0G1cs3mZfwxHQLwaAoKdo0gGwSHCABJX4JGhYIcmRBlPhSH4MiyMNewZNp62NPlmHYjB1GwJQx2QbcAFERxhJRMBPZlfmaABZahYA0WBZAmXAHmvbd3OiAliOTMi8BbBBRI0lJQQjWgRz5bdaH+ewzVLcs8iHKRAoZJdRSoBh5HS2yvlSADIWwaE4RVWCDASNZtMQJ5cm4RV2GQAB1EgvE1AByZA2CZeg3T4Kx8EvSAAGVeqoXwABEyFNXB5FaQ6zoedzgSbVUeCa1kGKsO7GDFZr+UFNcN0zFRonsdN8BRBimGKIdc3oCoASkXiUN8RTWVpSATNWdtkE+s7IFRagvO4RzeOQRGHBaocgxBkCbCsGLwiWajcDFBj021b6KCmrnIB217RCQRI2BEsUSCVOkEIwcHgcSJnqFF9Dj0R8QGAA1GlD4FFV0QBAVhaxJaDRX4qBDRJY34xAGH8GhIC2e1Dd+KjuGQJYh1+MAunto3KmJnCAGpjlqI3wjIYGGO1HmcIEQNdMgAahsPKXBfq/H4CIWAwAgqDnsgIhZCHP5lXKbN8FFU1CKwfiq0odhaqIKdfCdS71HkaIpC8RBRqlMU+z4F4vzF+akQYKClDXfifwBaoofodWahwnNY9I+N9rANvlsi5PY1xuOkEGmF4WexBwmhH0BR/CPAqeEg8RHvyzYwU1645FDJST6oU7dMv+Cwfw28wE0J1ULM1Fp3ekiQmBKAECJM2yB/BLCXAQfw9AcLVB4v6UCqR0iqxWt4FUKdZrgwMj6Dic4nRPCUPxVWNBtrDD5M0AABkQKg3BYAAH07LfEcGwnBDANBCEQAwlOTCWHsM4Vg5gbDCGaAEQw8MM03o/i8k6bQU41AwmuinEezgW6jXGipJkfAlxYillSaItBSDIBhDOGhO45Q32SEjD+IEpowhrM6JmdBoHYl5m7BQPgVDMmoMyMB40BxvFHnyG+05ZqJBIcgOokxMxUAMgogoMRHE7S8m8RIIFmBRngLE3eUID4qmQA4ZcIlME/HCHTBmwoqZRH7F9dqzUT5lnxszNMaBnZqn+koZUdD6AOMaPpUEyBupmwVIPYoMQ5Ys07KNOCtB+LPQAumDWPlD6lUrDtZ0ho75jyCodQkToFAYE5EQMUKpwgAHF1AAAlsACEgHBMQ4z+TMixEsM+Lt3QAGETIAHp/l4xAkoQhsglT81mNSY+1hrbiBqCBAiuAwBA1IGpGgzCbkpzSVIeg+QRBaWSSOBZiR7woHrsyWWdCxwdKhpc65R9RoAHlEanxcqgNoSgYTk1/P4VEj9JAfBOukTA0JZCmhyI1dJSp4UHNHgxfA3BxAziLPQHKB43zhCYOKFI3Tn4szHo9RsoIT60Asjk+gO946lJzpSpogNlQCBBibJMOsOTZHCO/WlOlKCQ0mKkSC8KQJEoNYaV2IksFiAVhsygWzcVPx8tWABSl+5vAYtuDExjcQEmJKSCk1JKCAEwCcpQDfASqEtK5SL51HIWkKNCwkB/ksHlfYRwXkXBuAZBbMuppETOHmB0iGOM7CGgkJBX6dQGg0RcTMZQvgoapHgIMJB8L4HRHeT+JgATcQpJFa9QQip0VToYiBCyYV8GTPlPkQo80OBGCgDOxoalkKn3kP4IIIQYioi8MTSlnY4QMSlD6agXQDjoHFGgKVvN3Q5UNMaNUGohRUiqVnAltRWY/iqkgftMEDBQBsazbIk4IxMGfhq48yaX6q0lPkZh0h6X0ClrYrWqxtzkCYjNOaxR8Gh1dXyBBmZqnYJYH+Z6Ll5jpxQKsLyn66AVQjK46CiI+Y62ZBx3ID9dKXs7oR2oYtZ3DLyJkA82EGS8EQcjOOllzSWj7B/fFMyPHug7JMRILMfKTwblKpAsEoAAEUhjYjAKBkSQoEDpxhOnWxU8A0KXKNgGMNlEhyTmAsSBFLUT+DscxStHS2IcSnC4HgTl+Q10qNUeg/FNJZnQOW+y6QJiSXCFZpAWaGRoSqfk5c5A6B0ACy2oNSE10hPOZyCgbAUGZA4wyLjt7eNmsRKgYTsc8k0uUxMYVUgkTHgAKomSG+NZWNm558GXTDAipId4Oz9KbewutnRoYSXhqo1Xnhnmbu3WQfSCwOCIJYo+R52zxhUMtZj5zTQazTJtp0QxxC7cuyGobL6aI8ZczEU0BI+3WwNt7T1KwHRXQ+c0D2JxdDHHCGAO24QKd+1p5AAOXtmAPD5tuU7OD7BhUoCfHw+0GJ2uGjna8vqU5RYzlnVTWA84F2qkXe8y8xIhk64kd1FLWQoiGwhaekxZ4874C98e2EniLxqMvM+a8F2+DF9vL6EhkB1J4D6b1R7ZgT1JJycgxAqAVnoMj3w6S+DiNOpvD+ZukIhmOyKRQavKyIOZAxNBzh/T++hKKRoOcL17wTvg8hhmqGp95nSv7Qo9RRMfthm7X0vIroJNqpR1BNQ6ljQBUiqZoghi1wZ8a2oljeHIF2a8O9YyCGJYj5awkeRMYYmL9eHd+RVJGaougpfrBfXNkbIFDzYSVB8s6P93Bw7VB5gMygM5yAoEtLb8pacU2v1WO6Oj5QzbUuQVhyPnvhi6TfENw6LBEhDt0JeQEkq9RNfhrpkh7B8xNQGJLxclsRTFzFMUktVh/BlEsBsAMAMNBse9XI7BoQpph5IkNpY89JhcdkqxNNykRwhQqljlTk6xJgmUD1Jg7lHlnlXl3kklPkKBvlfl2kGo70Mkhxx9HhIJf8DM0dhk8RHFtNZl6BKUZxxRE8fwEMjQ8BUC4MsCWNppZV71JQg9appkHRLwwALQwBINHxCAXwVkdVDMawPdIoONpoIl740wvQhxP89ttxbAhtiMkJSMsAAcgc5lv8A0fwxdo4z44wUROxYBII1J188YHdDJ6ZndwZ4VpJlxpDFtMd6Aql4sZ5uc0YLsRtIJrtjwxcfl8An88ALQlho8DZ950UDdDNk1F9kB2sPs+Y713ofxk8UkIFyjA9EYg5jZMAPUntz0OlqjwjP8Q4MBXhvxSQI5Ps5CyB3ghtyAKBaBW1VUahWMLNVx5lQEU5BgiBy8chwofB5ANNvA4YAx0FbF88q4mYJRVhbAZBNDjEkC6AUDcMqxZwox5BYxR1lJqhUcjNX11jGg/djxo5yBORH9LQd4jDTQTDXZAhgg3g7ixVfgqk+inCx8SpdsxcLwahhYHB0Ch5YwgYyxnB6AsD1BQ1+iv9/VNZmSo8DN/82AJt6JB1cRNDtwkJYsSiNZ1oX9Mh5JDcqk+13tV8/oCxcjtEkIlhZAGIWTeYsDuCwQGUklSiGIgDnN20Kk6CElvZGhoj9Nn1oSaIRkaUCiOSxldsgCA8/lNZbIDcFU3CnCAR1dxSahKVcBbxzlg1fAWtih4U10IxBlyAZtPNbxWjSj5xFxNQmiwcaARN6wbFrJDkscSBniGIsTv1cS44cYvoKgndeE8Ej5wh8VA0x5mQiBMAKC2CU541yDYQGJw0tJI0qBSVY1/Aht2VF0vAXJhjWAh5L1WQMFtwaBNQfIuc9U6tB9IcqgVBhgW5ngliB55VwhNISQtlVcJpCiO0BB6TxAAITTa5UEK1SxJUa1TjFkpC7TZ5pNYBVhD8jwvIsREgMBHBo4WQNDAFYw10CBH5ilV0QYwSGRDsABuJEZqcQFsnOfwKQaEGIf5Q6Q6PwbwPkBAJwvsZgQQ2I5wUgWxbC3CoPJCMeI3Q8qcssE3ZTJBRISEVdLc66bXFYZFTCDUnDBkKi4vPECcpIMCvACCmQUQacNijrQTes/Q4/GoJuDqB1f6HeAM90v/AAsM0beonmI4+gC/d0JAEApUoUfwaBcuUkECHM9QVOTMZkX7cy3OIYaqHJN3NGegREfIRxXOeyzwHWQEbJUYdAHivgSymBDMyYJSdA1RBidCykJiV1UuP8nmZwYYx+SCEgDQEhQzC44rUkIGGgdtWC/86kcA4vaIRARCxzD4BIh4l/CMdMTDd0fwWaJcPMf6XymoSgLw/GcESo90NDcgkMK+ZTKdc0PAekpwxKiqnM+aKE+oGE9sTsD7OqqHbK3K30UyqyYuZqgoZVMojAU7ASsWI8KGVxHKkal6RAEERAdUhKmYJKiMIIASeVDMtoI3RlL0abZiCcODIxBcJqFicKj0cPeAfUG08BYvBuCMWFJiH6qbGIKWcoH0bETUnkPauKw0Ui3w8dDCYQgoscbpIQd0DNd4aFHCbNTEHEfEIkX0ItS8MtSqqtPzUSHpTcjRLkfw3yQIxAMjdqzTYCd5XbDmG9ZzCESgAdLASlVGk/DLLoocQTZgBJQMFIXAsaQqCaQcYcUcAVEgIVU0XbEPcVXzGtG8qM+s305VVVXIM0TVXcBvd8BQfVLSLyI1dw01XFJoK1d4G1e3HPe1UqR1dcTcGWe7KYr1eFOYjkwNaGENUirLKyoWMgxNSTOue/OjerCmk8nNbEPNBmwtJK0tRrMCArR81AVWOtTinmp9MAQwAwEwKAMgbyy0AxQgUgHYovBa4oLgXgfgYQUkvkCoLLKgVQdQLQHQfQJu8AKAdwbkUsRkTusgZQWxXuzgRiDtJwUrUesglQNQTQbQXQBuowWe0wAwRACgBgIFHFVhDhfEnhdPfhRAR9WId+gwZtOCEyYgVeoY2eTtLiWiRyU2RAHtTKy8yABhAAOX2xijYTJEOjYTRBMmgcOiEVQFMVhNnHUHTVkOFtoDxtDIW0uuVCtqgbqTYTggAA0TIkHoBWUrA2EYo4IbBblUG2ErAgobB/kgpoHoAhEQIGEYoOHDouH/loAbA4G2FDo4IYorBCRjk2F/lWV9t+GGET5ctdbyp/aCpEgGFqhcBWU8BoBtMhFNoEjaEoa4x4Ea4PKcIOZtwVK4aGE2E2aT9TG2AbBJiSAhEereJZAlkTwKroINF5ALHFAoGKLoHqIKA9pLwDgyQ7MMGxwVlmLTVPN5sb5zx9pKBzClgwAQynhL1ghc5/B/Qrw+4ggAIcIAQOIpBgizRRoTIsAGF5c0BiQmgpQv0cTOoNG4xVSOR+LSQN7FZ2w5l3RtwHlMBvNSRz5aB9pUy3EfaR0GQRkcHVgcpO8iACxyLHBa5UB0D0lto/FXGpYpsE4SApQVgGEgmFsIn6A2n/hOnaBcYUnjcUEngMnjx4mahjp7IAILphVNFbp7oqsGIloVpYBJSHmXLqgiBoVzMApTDpbgGinygYFvLUsHzq0ahYRajfh59dUelkg4Z5Btw7qTDtmURSKABWI4AAUn4ERkaG4DpExmUh8BJoW14EkAwVhdcedloCEWyKFAyYrLxldhYDsQmSwCD1ZaPwGJIARcWqMBaagbYXaZee6exM2NkDJEQGof6e3Byk1CwKxHjDIB2aFEOeHUoVIoBAWwYSrFwDRGwGrgoEOjNCEWaFPByYOnSlvAeFQFUkBry0GA9dqiLEh2cCoClQPMLMxB7MSsS1qwNQ3KTGvDNYwAtepYSjy0Q00NQGsOfHGbJYUG4GGfsBiWqqRFqbB0THnmSmAmg0CcgDik9pNXT1rNKiruKjEGYo5hChHjRginOeimEfikSmSnBjkR/l/Cyjy3b2mqRCohomvBUBUxthKcbRhrUCwENCUAJBrMz1Kj5h3fsGVaVHwWV0sV8WlZqMmaeCHB0LXyFIZNK2b2xFSedRFr1Q9yEjpGMbyk2adrcR1A6VryQnrzNDHFyydU3GYs4wqojfeKjdyUg+8l8jHjzbuYqt5YshtgFc1eefwCaF+c9YrSBaun1cNfebLfaWhBJESxwlca1bI9oB1dLM6gNaNYGE0NNQ+i+j8uzYtZAjakcB7Z/E7CjGsijkohPg5UKv2spaKSYCwJKCMCeeVG1Z6b1YNbgEaWiCERxsgp1NeExWQzoSgfY/I9yco4Ba8Go5bgM9gCM98bGbSc+c5d8GQ6Yh3gebpG046Y46496do5sG4GYCERbMonsBwWVUtAd28YeyUPBndAmGqtIuC5eYo/+fLuc+ugNci+i888oWMOGGcEA7VdabYQotAzTk/MOhrekC48EY6Vcfq5iya5a8QC494/6YihVTWV8AMZwToD8ZT02mUtSfK5wmG9BA/WLhfC7EtiGhDFIutjFHjOremTg4+HA+8+Q7PBRYoHycKdDLzbDZyl2Rwb1fZdq668a9wGa72/67o9EoMcO4NaSagmkEEdbcVlvbmVOeiFwCEVY9IduYxke8DAa9i1e9iUQCsFZCChUIoANcOkDEjPB4TYWR29jZg325KP9qPG3EQX3l27U+HU0HAY1fHcueufB7TEDEsYOt+hNd3Cu/QWm4oHCA1hFSsStYLGcaW6qUyC8AcwZGXdWCu+aA83RS8vAPDFsv7edA9v3y9u7dPcRAcY/mHZ5zHainncyhvjKy9DAWA472F7oMw4JGqL07LJPbKSg1yTDqogKIYH/bfGq7GmIfw6QkI6FlZ8UGQEFcQGFeETYRdY4SvfYAm7yOhUheXB3md+ei4FcZj/haVAT/FZlfnflZJaTzj6jJcoW8mAAmmYwFmb+jLEWZAi270g2iw+NWkEQvD8j7z7W/MVO/QCU75E317A6ixA2kNCBS8gJB3dDTt7D1lkd86iRCPbZQ5R8FEuB/KTIeql2LqhylQ5qGdqPApJ5kDfwAtQDrxm9rUoLGT6FAed1TV8NU167dwR18gr18nNiXt6ilDXd9WEPZvhmL64cGk8L3syAA7OVxOKyRgAmCGaBpGKmnM+l/Q5Iu9qavaUQAmBd7XgHEBDfgHwE8CupZwO5SIFyCfTDZH4ZDVYNgN4h0AM+sDeBog2QaoN0G97X4AY2vq31RED9JrJIl4Qv0GEBmVtOQIAQfMM+lDGhnQzYQMMmGLDNhhwy4Y8M+GAjPbGwJvp30xEj9XgbIgEFJIKBIgqBiI2gYyNxGkjaRrI3kaKMkGKjNRkoNjAqCOBPSdQdwKforQ+BBmGKCHwKJecM+tyURPlwkT/Jn60TWJhR0SZ2Zmg5mXoEIkAAoBLGW27IA/WvfE/pAEACmROhzcEeC9BDCHwQ4L8E/AAhLg2zq8zujNB3GlAAAFoEsAAmpEMgAxC8+O8eiD+Bv4s8toLlI/ud3RbZ9a4SLFOIX24DpDWhZbbwb4PxL5C+EJHHThxzy5Udm4RXQ1s0AdxShKQIwaBjJXhQX5fupTAALww8vcLccIBP02GJAdhYAFpnsOug1C6h4mPAHyB3j7sduFQRBrZj+4TIxiprWAOazAZQB3BgwrwVA2yGsJchjgMYRoEKHTDHOhXWjoZyITRBmglwnzmHwmEhc7Ol4XIdCEhEDcw2cRGEbSmWi0ABhbPIYf8JGHcCQRYI+zmiKc6zCIuUXOEdEIREatyRqImYcC1o5Gsw2TuTkHiJcrpgkEDoWtt3zfAEjImyaeWLlnGDy4aBxInIaMOfpIjdOurXErxwWF9dlhtAVYWwHWGGgjhkAHYWcMND7DukeIHUScP1H75ZA8I7UF0Xs4dDQyu/d1mh3MzhAS2thayEhl/A3Dm+nbPkJSgN6jssA47XVBZxHqxh/+x7bXvgila/BH2msZ9na2FGzw78YoxIBKNTzDCZRpIuUU9wR69cuO4Q8bvQB2FYISA8IstvwDtoV9Ru3fBPjz3VoUBEKjfSCqpwFGHcjwCQ9oRYXRZ5sExmdZMT7ElG0B0xgI2US4Lq5w9uuL3XMV+mVGLC1RGo6QAcO1F2ZdRuwg0ddAOFoBjRy400RcjXHOVqxhYiIGkBLH0iyx5fFxgeMm5UBeeiFYHo81Rj+0fudmQRBdR0ENjAwcQqni2NnDMUKeAEdTsUB7GijyU/YtMdKOHGZjRx2YnrntxR60A0eXhTHtj0dDFASheIJAC0yPbwjGx5SVsYT1gy1NW8KMFrgoA05ASkxIE1MTQiHGwAgRzAEEWwgZ4HwrmKwV0CsCt54BsJYsY5nlm55Tc6xt464VmUf7Yc+QjQ0kH6PCgBiooiFBDpmAmQphNCV3PJJuNn6+JFRC/NPqCB7FEishJIrhPRLlFCtwhvXNrChOyBWBKAWPFVsUHCB9DW0GncIIKJ/A7DVobIGjqtHhH1Cvo4kyAM0NhaUp5mizZyTZQ6RH9OWGeFUDpL+F6SMxBkhiVnxL40ITJe3MyZJEsmeskpdkllj0gcnFAvJQkvkDlERB9DFWRVJKcgEpTnjK+MzOAoe3r4dIcJIk1vmA3fqxB66YAIwFfVUGcDTakiaRC/Tfof0v6P9LumvSxyANSsWbYMWAy1oGwOQlobRhVCKiNRDa32D4OMAmC8Qw24UkPABHcnblQWDwPqREGbbWRjwagjIkGDZjDhog7LLGCskphARgmTEE6ZAWkAZ8rArKBaNwyMFBQJGNgOCISDYRnQ+Gh0EyNAEqH9MGEuMBBvQweQ2BWU0AaAIo0G4ddYZDAupIN1/4nkyA6QMAcilAHb8QwgHMgZeQ9KGYEEUYDwj5HyQEk7euQdIHdin4pwAQE/RmY7HxLxgKwpEyUBlQ+ajQYm3GWZHpDViNg2KOiGWIiIvxvNwgrjCfrLKj5nxCQ1uN5q7zrZDgdGXVf7DHFRbsYkKQ0SUcpGbyVShUsOKuJSFoB6IGQGBMpg4MWBoBz4pIYjh2EdkAjYA7zbJq3WYp4QQYfnZIpVXek+lJ0WIBiGxyoBUhwRBXake7KER8wYZ3036WI3+mSMgZIMsGRDKhlR9XZVIMLvp0NaxyXKAINphHLVk/J2IWI1yPbkpg4iU4TuIOc01q45y85uJWOaJWgR8Ac5DEH0CqiB7BjvOE4KcDOBwRKQ0+HwGgMTHMznFnAf0WgAWBwjWJ7KnGQCubktAdyEkEc5EE8GEY0M/pAM6RoSFQZBRlGqjdRgmw6orE4w5eWeBPOAZ2w8OTEF2aXIpEsiaOhc1AGWJwgSgYcvhL6JdLPLSQbYBENLC7ni6YVvOJZcLinAdjb5d86uMcDFh244Q7YQcewBPLAQcsc5yABIkhHxZ8YCsUtJGFzmYSshuZg+IucDRVRIQMEzJZ0OJ0vYzhCmJsT4BlUlDv5Oc6yZwGlUrlCxNxHMtSZAvu4w0OFKMcbpHVgRoLHSyg6PisDOj/ANAyXHxnHJMoizrMiIBwAuE0x6J/eTEQ/JSQ8HE9dJFFXGHIDzGCLcSNQr+YGCQgLoA5LMioOYoX5hjsuFFI4d0xYBxQ68xEV0PP2coO44IiYfZvlIh5PAKK3SOvHlnsXZhfFKcERegHQlwcrkSocIL5JgbSMGBKDNBsor4BxKwxkhOaYyIjktyeOBc0ROYwyFYM2ohZOhTZ1Ll3QhEuIbKhFFrG6QrOy4AKiJGqDjVw5jsh5MzDijcBoeHLQghQgjlkxKAKsACCQvoDALKp/0WhRTP9m2oa51MDDvQCdxvSzp1s/Rpqwjl9LqAAy8pa0JDbcABxIOL3tUrjAlzHZaslpZQEQrqgyAR44IPWVWDaI8gyAYqYpNWD2LAwNIJ5fLMNBqyhGbCBWfUoBiIdy2rjZWarPBVxFpACREYBOWCLjd9YEBNsJACOA04bQ4isNrundBEwypejTpITB6RgKQYCCMQCZTgSKZSeUY22BoC6BHgtgGgFlXTx6VUgYoCkEgHtBJyTAjlbPVAF7KYrecNFW0ubOVX85fRNlyQeFBpidnqTuOwkDpHEr4VWJwcC+SlAsriAwKd8ZQcFEgA6pVtbUz49ABKthwcwJgfKzAtZSqIVU9FfAB5vpgKUcqyQEy5GD4NZCMF/uLQtnpUouWqp6U5YkblAxVgj8rxMlWOM0CUBUydIkAWlsG3IHUQ26V8yGjEFpYRBZwI/C1M6hPI9ziYKKDVcTmQiTBz0RM8Adsrtn8Ra43iLEMwjtblilw25VAAcnKYfZYw/eLllbCAhYB40LAzFRoB2BAojgGgW4MyuHWDrK1KlEIIqqgVWkN4XcpRBkI5UtNdIIQYpbIHeZUTmK8quxcuK1X75TZTyillzPemKwE5P0mwHvNTnAzQZaDTObczp5dpisEUntoiGI4UU3kLMaEHFETCGgVZNzYgvfH1gvSA5Mqgfti3rXjAVwqwK0XGClgYBrClrInM+sNCVqI8kOLtGlQ3mOy2YGQxVMBrtVSqUiKyppHXPSLgbzpM8EtXDS/Yj8w2i60kIPOnCECz4K3SHKxmXAYbl+Y5NfjNPHgG0jah6b+RERdrzrrs1tXSGopejgoao1sGxZeg7SaLtpvReztUn2nUjA5Z0trFBC6KUAskA8W9E1BcyYbDQbVSsBWHbQ4QABmYBiJdJE3eFC10RbuSSFnCjRzAlgOCMgOByoCDY6AlPMDiwH4NqB3lPAc8jcRECG0s0qAMsnK4kMdBACc6XYLUFcCDJUiF3HwK4CQADAkAKBqDKlBwR9shIaAMww4ZnQ0Q9AoGftiCgriwAAAFiODQz8thW4rcw13nlbKthIarSuLtjQyWG1Da9VI3gaHzoGx8qwfwxXGew7Q/AmLV51A14zzIpq2wcnP3nwNIZXDDBq026n2D76fU9LeDEy25actUDL6ZesG1py714MyGVwC6DQyMZ8MxGcjMUZcA6td2u6HDLYR1IuAtLNGfQH8AEz38DCKUDYCCiBYPtWMo8AcDllWATIoMmwIjKvUMDDoQUaAF9NQZKCcIVwAzLFpRoVVReKMcWbZmoWCYkt7suiVYBdwMIstx2wFRgEVnyzNx9OthDCvbhvNsdc2hbIY1jhOrlBZO/EhTtnbU7ctBjQMDFCBXFCL8kQuWYYzih4hcYzQCflLqgaGNiQVIFWazuKEs7lokQ9nXFoqo87bBfO7gQLvB5C7ClNyl+RCJjmiJmgdGCyIgH+S4A8QSuphIGBMWyAzFvil3a4rszuLmAni6Dt4scV+LEAASpJewB11QB9spyovLC2AmgIzdHKjdbx3dm27MA9ux3c7qESAAkwnN25zfFKem3XbpEiZ7+edSvGDsIQQdwTxR24Xbst6X9KekdIyALno5X7LcAAy6NeXpXFV7qgNQ7LXXpzluqiF0IT1bQG9WIA09J1EvU7vCCKNZQ4QKQeEAWiw7oADycIA8iCgmRbkDyaAIuIwBHCNxW4v7uEHE01DW99eqkMPsmVeAx9E+qfRntn1EggoC+mAIw2X2r719kATfdvt3377D9RogA+Jt7BZrkAOwhNTNsgDR6zlNmgyDzpwi+zJVWm2VQQn4m6RX6tevPW3Lz0brsDSegvaUocHQzL97ew5XLMv1crtQvKmjcQZzmrqa4mYDdcQc/VETf1xWADbjxwPzFQ9ZYX9dhv4FtSOpRgMMYlFwDMAvAQ09qSNN/rd1bEDgHel1EtDr86e2682S/EqgMhYgfU6AM2yx5bpcAZQdzKcv2hYAKg/23YqJpTgOJwQmpZ0OeIwPbhYgXHOkKkTqRlBzxIDO9vSovaxAugZQHCLEAOCxBwgsQBCe6FSJY8yI5ksoC1SEx+IfDQRo8LECuDBGU48KpA/YfVlljKULM3w/4aeCxBaWZQGIbEAo5HR7ykI1I6Uern0h3OqRkCNUZI1pEYosQCWHYRG5CQXI/gJAwBWYBAU9wmhLkSMEMVzbKUsO5ALEFKEUBVd6u5aLjCqNTHZd8x0ipMfNqUAxddOu6KkeojLhKUnCdQ0ZVxpGinJE80ijGpUhxqp+fOXwNKRPLaIuwsQGKh7u8BeBDo3asgK0eGz6pPgeubuRqsWWognMIFQxR3DbCjH/o4xuIIscZ13RCQGqhY2sYoAbHcYcJ6IvUY6SrHHylAGY7CrOionloqR8TTEFiCy7xiVRjY2SZWOnIbEZJrbMPVJkJAgTxoSHOJuJ6UQJlwEH0GNTRifYkABkKY7pUTr8x5DwMclk8CJIpiT8dbfTZ1WeBLYKZP1K5N4V2n3kDpILXGA8HcYeA0D0tdzZ/U83eaPkvmhwhgMC2WgqBtiDLuFsIHsAotpA6Bh5mJXr8f4SkNANXC0i76YohIOUyZugVPBWMah9UqWHkAWmYgVpggXOFtNIp9MghwjBfW20pa+pg0gwIIekNjT/629LtIoc8PSB2V8PT8gWjgiIm45OpHOCcrOU4Qm4OKG2K+BDUqnHOhmVkS6A1PIGIOCZL0K7U7DbYnCbpEYqEgZAzRNMwDBhM1qK0laDBIMirQgyq1BR2ujzUc61v62TnOt1WoRKQ0S39rtw0ic2fhTK6DYYa6NfqJLMExQNpgSwF8agCUCqkYgsTLCEYhJrrnbJKcaoCvygZg8hEV500x8n5ntVMQ2soUAwih5htcg+SF1JxUCZGAhZkAM6Kyhig+n20fMVPn2ZdAMJFj4us6NDOhNy76lcsqYziY10YXNTuWaoPoeAhhSMwNGmIMxuHlLcyqUDIDEWAFWRMmO3JseLAYYj4tpW52Fk/8eJU5cOOJZt4GWeQDCq5uTwRAw0memc7T1Z0z6e9oYFr7HtKM2c9DvktIMsZ2XU7UnOMGAzb1GcyGf0x2M2sTq+AUOQMS+C0ZC4AIMDE4R530rYpEE+KenkfUFK4lnFx2AbklKFsvyg1GboGctlHhNIEYmsx0lUO/y8Y7TfkGbIyhigPirZ89VpavUrab16c+9QZbpBBQGmPlssNEB5iUooEw6Unt5ayNzb2cDIdI2Bu9LU6oAqFxE8ifBXbqQNF+fhSapP0Qr5Jx4Zq3TLNUro10fIG9JbnnwdlVJ3FxYjtH1qkNfcpfXXgyEGvW5AyOSuzJt0rDxENDICg7UdpqtYW1ZjVzq3wu6utWXl7VrcDbP2viogwelYSTegvwpw5r7cBa/CjIDjW+Qk11RHYexZ3WN452Z4UdYb4rWEVa1q6XSGy1bXET+FuYw1Zj3CTtwn13wKn3qJVgU48CVayBoGlHaDAXm3zm52encWlN4qltKkC8B+wFm4WkgGABHY+IrFhjMnqiA7PnHqZU/SteBsvb6HSoJCQgUKt3LWoyeJIIgCDHYr1pNEIEISvca6VossmxGwUwvhAh9gOzrJjFh9kRCPLe41GZaBrVngyWoCqADzOLeaR4wCYYhslUHJQv3bJBCMpGcpaETugYZalz7fTDkR0gNj5mylUgjfA0qL5HwHSgwnfMmmAtHySlIBZ0HZL5QoFrmtuVYj6EJZxOkGEPPjrSoaAuJJ00kmyrPB0eK8QHJqWxbUJlwKVHxFXX7n9rA7WVO6SQhCVQNLqW1PqsyGh4On+AKt509ZrTu+yjqpNJ/CMV5gxFBy5KCZF7zFAv5ZAeuY8GWNxjooLL1YdO4QJAjGlZk1ZgjPqdeSGmeCxpz837Z4JBbxVYZsLRGci3Rn7TXzfjcafj2dhgGoZ0LTwGtORn2Q0Z5QfmdwCFnizyghM71PxJ8DYIsZiAJ1MvrsCGAPIcfqog0A/3X6KZ4aZ5tGl/0i88hrM8A2UMY25tAIISmkD9kUJDVjQeQLcwFPiahEat9tM0BviHJOsGYDXAWFuZPHu4PgN490YwBBRb41S0EHOyqS52LWlEHkwwk/LiHEoIkMAFMbACdqO47xjABw8QBcPizbR39gmSgZaBET7QIPDnLok6HkgWDpbJKW9uVhmeVqmjf2q2DHBuABIZBZ7B0cPdg0Wsm+bIFrbzdEYtFTJn5tjWgVTDElQdAuFiaGaVswJlypCdqtYnpj+0WY14Hp3bWcLwiNC5sYwursWHkjzx/0y52q4OzJLJx/YzbD1EXKvDxte4mVUFF6iteQgc7T0ZMmV28Bfyg02wbuJZ5BYZoPSx0fhhabYpZoNo+d0OsLykEG4cWueh1TlSfJg5C4WJhMAwBnEYSc0H0ciV41tLHR+EA6BHAKnt11R5ioMdfGuzvxn8OJvgtDwyr0gB+MyZQtmB/HeJjVdDM2d1X0L+JrwLs7wvePcThzudvk8zCmP/z6ABp9nDSwUWWnG3O2fIHxV89QIllggAWo6TRAic3T4mf6Gb64BYCZ1Ds95en7bQg06HW2AcBmc46CioG9B1I/E0aBSHLxihz2pM67mEDPJjavLYqD0241jqO500/UdPPQphKV8FHkgC132L0nfCpVMqxHMCUsknwDmdm6garnQ1IxL/xpkZdHnoIfJR5oXtSafNTwKqP5tYKQV17OA8MxFqjMkCDADp8gG/Y/pxnG6zdHcimo7oyHxp/uNtOwC4ARzMzQDPehrAPqT1j6M9TV3ZVwBsJWQiANhHNRpC0AOEyobaTPXPot0GAVwA4F0Aa1dAGAHQBrV7zQBbA0A8eA4GG6WBbAtgJAW4LcB2BdAdgZwWgLcC6AkAug9Aa13PUgA7ArgVwEgEcAYC0AOgZwWlmgCOBXBaAAgW4EcFrdLArgcbysFW7ti3ABASwWliQFpa0Augsbk+oYE1c9uDgtwOrTsDQAdArgaAHYL266AdAtgXQW4EsCTd1uS3tLAQAwFuBbBbgU72lk26WC0BA4ObiAJAFuAkAdgtLM4CQAOACBaAVwEt26auBzvaANoVQEcBWS0AdgtwA4AgiOAHAOgOwSlRmuPdQAlgWbuNwwHDckBa3BwWlme7rd+vp3Agd951SWAJuBABwa90cC2BNvrgA7r15AC6BoeDgd7roBW//cMA6tDAWlj246DdujgOwHYEcD7cMAjg8b5jz65rcCA4PwH0+pq79c7AtgdW84MG/vdNAS3HQLt9HCrA9uSAWwDDwcEA8LuugHbtj3VrHUgfIAI70t5B7g9AQR3ogOD0uCOA0fjgtLcd0W9pY+udgBwOrQIHk/hu6t+HzV9h5fdLAOgib+NwwBs+qw0AAbjdwu+jdKeAPBb+T+m8Y+QZNPJAat3VqZh1b43Mbg4F7yuAIJv3aburVsAYDIeu3Kbpd4+6Ajuergzn3N/J52CiAS3RwMd5B7jdUfBPNbnz3e4w/eJVYrH8N8W5nfFeT3sHpd1Z6De3BugtwFZNW8ncLvaANH2lje4EB1bJ3XQK4Om+w89AmPnX59FcEY9Tfav1wK4Bl7thZvbgrH1WFG92DVulgG7yd2N5ncqBlvzwYt8h9E/QII3VHurWG8TfxfZvOwez4N4y/ifaWGb+L0u6u+seD3c35d3QHvdXBaW4b24Je6LeqxGPSwdT9W9oBBvPYabnYA1qu9umrzZn2t128k9Mwy3OwL3jh4XcefJvl78z8m9oBMwNPfH3N4N6Y/0s6tKyW4NO6WBVulwT3qsDj/i90fp31oOrVcDo/dui3V3ud529W8Zf+vtLYT026XBbBP3bHz92cD/fxu/PNHwt171HdXe/3aAa4Gz6TcHBDfinr9xe46AMAf3RwSdyZ5vdPe6tTP2gLF+i+JBNPJn39+b7HexeY38v8N0l/vfuecPKyHt++8g9zev3Wbl5Jp6Zjm+D3SgK4AIC29VfCf77xj024ECTu9vs3uT1T/ncqB5PV3xjzR56Bye6ARwdt57A6BoBq3uHsN6rG49Q/BvlCKn926c+afC3UPgTwu5IDTe/3pv+H/W/OD2fZfibn7wwEbfHBY3WwK7zG7t+ZeF3Qnk72e62Aee2fxwOP3bDY9VvrgL7+LwIEXf9vNPTHs3+u97dNBCfev2gAu83ep/K/qPjN0uBndM+d3P3jHyu9jcXun/0Hyt+p5x+l/KvogVQFo4Ie3blsDLudntr4IIsvh0DIe7vkz7xeVXmm5we97lD7huaPpz5x+SbtVBT+dniAHw+G7ksDeedboV7RwmbnVo/et7vm76eRwGgCzeF/vF6T+mnhW7VQUCFQHBu1AZB5e8+AWgBhuL7m1DXAc7rF7qeRHvL5FeUXnH7UQI7vO7E+1oP65jOsHp7Bze8voN7puJAJO4puZXkzA7AV3lO7UQknlwETe3Ho77velXr27meingh5PuJbpu6xe37vZ7a+03qt5o+Qbge4OeSgFD6+u4nmgB7egvhh7y+0CPL6XuQbld4O+/rpR6Keaftu67+XQLr4Fu5Pou7S+SbpJ6TunblsABBNEJp5aOU7j650eJHtHBhufrkBBZuEGL27FuEbsUEJuPrpm4Z+GPksAkelvlm5levblo4ces3ve5Mem/qt5m+dnnR6ABfns760+J7koDS+UQeO71ulbqO5QBWXtF7TuhPs+7VQwnth7duuQYN75+CCGj6V+U3h55VumbisihupbtaCeB1Ad0CwBDWmm7xuTKpH7celfmIHiB3blG6FusgYN5AQhwdW6V+uwIB4neS/hH6n0BHra72uowE67PUxaK66t0A7kAA= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=betaflight/blackbox-log-viewer&utm_content=833):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@demvlad demvlad marked this pull request as draft May 13, 2025 14:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/graph_spectrum_calc.js (2)

112-118: Nit — typo and clearer variable naming

multipiler is miss-spelled in two places and the intent (“zoom multiplier”) is not immediately obvious. A single, well-named variable makes the block easier to read.

-const multipiler = Math.floor(1 / analyserZoomY); // 0. ... 10
-if (multipiler == 0) {
+const zoomMultiplier = Math.floor(1 / analyserZoomY); // 0 … 10
+if (zoomMultiplier === 0) {
   pointsPerSegment = 256;
-} else if (multipiler > 1) {
-  pointsPerSegment *= 2 ** Math.floor(multipiler / 2);
+} else if (zoomMultiplier > 1) {
+  pointsPerSegment *= 2 ** Math.floor(zoomMultiplier / 2);
 }

Also applies to: 120-124


159-165: Heavy allocations inside the FFT loop hurt performance

A new FFT instance and two large typed arrays are created for every sliding
window. For a 5-minute trace with the default settings this can run into
thousands of allocations and GC pauses.

Minimal fix (reuse across iterations):

-const fft = new FFT.complex(fftBufferSize, false);
-for (let fftChunkIndex = 0; …) {
-  const fftInput  = new Float64Array(fftBufferSize);
-  let   fftOutput = new Float64Array(fftBufferSize * 2);
+const fft       = new FFT.complex(fftBufferSize, false);
+const fftInput  = new Float64Array(fftBufferSize);
+const fftOutput = new Float64Array(fftBufferSize * 2);
+for (let fftChunkIndex = 0; …) {
   …
 }

The same input/output buffers can be safely overwritten each iteration, cutting
memory churn dramatically.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abfac2c and ec62340.

📒 Files selected for processing (1)
  • src/graph_spectrum_calc.js (9 hunks)
🔇 Additional comments (1)
src/graph_spectrum_calc.js (1)

347-353: Zero-padding check — please verify counts

samples.slice(0, fftBufferSize) returns a buffer that may be longer than the
actual data (samplesCount). Later, a Hann window is applied only to
samplesCount points, leaving the zero-padded tail untouched, which is fine.
Just make sure downstream code (plots, labels) interprets count correctly
(i.e. as pre-pad length).

@demvlad

This comment was marked as outdated.

@mituritsyn
Copy link
Contributor

mituritsyn commented May 13, 2025

I would appreciate any help. @KarateBrot , @Quick-Flash , @ledvinap @ctzsnooze @mituritsyn

You are doing a huge job and made several steps forward compared to me. I was always using Python libraries for FFT without deep diving into the math.

@demvlad
Copy link
Contributor Author

demvlad commented May 13, 2025

You are doing a huge job and made several steps forward compared to me. I was always using Python libraries for FFT without deep diving into the math.

You have much drone tuning experience! These improvements change spectrum views, will very helpful to know what is good.
This PR can compute spectrum very fast, but probably it can low spectrum resolution for short time intervals.

@demvlad demvlad marked this pull request as ready for review May 13, 2025 17:17
@demvlad demvlad force-pushed the fft_input_size_issue branch from ec62340 to 966e0cd Compare May 13, 2025 19:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/graph_spectrum_calc.js (3)

503-519: ⚠️ Potential issue

Incorrect FFT magnitude calculation in _normalizeFft.

Like the issue in the VS-X path, this function also calculates magnitude incorrectly by taking the absolute value of only the real part of each complex pair.

Implement the correct magnitude calculation as suggested in the previous review comments:

-const  fftLength = fftOutput.length / 2;
-// TODO: This is wrong spectrum magnitude calculation as abs of separate complex Re, Im values.
-// We should use hypot(Re, Im) instead of and return divide by 2 (fftOutput.length / 4) arrays size
-for (let i = 0; i < fftLength; i++) {
-  fftOutput[i] = Math.abs(fftOutput[i]);
-  if (i > noiseLowEndIdx && fftOutput[i] > maxNoise) {
-    maxNoise = fftOutput[i];
-    maxNoiseIdx = i;
-  }
-}
+const complexPairs = fftOutput.length / 2;
+const magnitudes = new Float64Array(complexPairs);
+for (let i = 0; i < complexPairs; i++) {
+  const re = fftOutput[2 * i];
+  const im = fftOutput[2 * i + 1];
+  magnitudes[i] = Math.hypot(re, im);
+  if (i > noiseLowEndIdx && magnitudes[i] > maxNoise) {
+    maxNoise = magnitudes[i];
+    maxNoiseIdx = i;
+  }
+}
+// Replace fftOutput with magnitudes for further processing
+fftOutput = magnitudes;

178-186: ⚠️ Potential issue

Incorrect FFT magnitude calculation flagged by TODO.

The current implementation only uses the real part of each complex pair and applies Math.abs() to that value alone, instead of computing the proper magnitude.

This is exactly the issue mentioned in the previous review comments. You should implement the magnitude calculation using Math.hypot(re, im) as already done in the new _fft_segmented method:

-fftOutput = fftOutput.slice(0, fftBufferSize); // The fft output contains two side spectrum, we use the first part only to get one side
-// TODO: This is wrong spectrum magnitude calculation as abs of separate complex Re, Im values.
-// We should use hypot(Re, Im) instead of and return divide by 2 (fftOutput.length / 4) arrays size
-// Use only abs values
-for (let i = 0; i < fftBufferSize; i++) {
-  fftOutput[i] = Math.abs(fftOutput[i]);
-  maxNoise = Math.max(fftOutput[i], maxNoise);
-}
+// Convert complex FFT output to magnitude spectrum
+const magnitudes = new Float64Array(fftBufferSize / 2);
+for (let i = 0; i < fftBufferSize / 2; i++) {
+  const re = fftOutput[2 * i];
+  const im = fftOutput[2 * i + 1];
+  magnitudes[i] = Math.hypot(re, im);
+  maxNoise = Math.max(magnitudes[i], maxNoise);
+}
+// Replace fftOutput with magnitudes for further processing
+fftOutput = magnitudes;

222-230: 🛠️ Refactor suggestion

Update matrix storage to use proper FFT magnitude size.

The matrixFftOutput array is sized to fftBufferSize * 2, which is twice as large as needed when using proper magnitude calculations.

After implementing the correct magnitude calculation, you should reduce the matrix size:

const fftData = {
  fieldIndex   : this._dataBuffer.fieldIndex,
  fieldName  : this._dataBuffer.fieldName,
-  fftLength  : fftBufferSize,
-  fftOutput  : matrixFftOutput,
+  fftLength  : fftBufferSize / 2, // One-sided spectrum length after magnitude calculation
+  fftOutput  : matrixFftOutput,   // This should also be updated to store magnitudes with half the size
  maxNoise   : maxNoise,
  blackBoxRate : this._blackBoxRate,
  vsRange    : { min: flightSamples.minValue, max: flightSamples.maxValue},
};
🧹 Nitpick comments (1)
src/graph_spectrum_calc.js (1)

162-169: Zero-padding implementation could be improved.

The current implementation uses a loop to copy samples into a power-of-two sized buffer, with a TODO comment about finding a better resize method.

For better performance, consider using TypedArray.set():

-//TODO: to find method to just resize samples array to fftBufferSize
-const samples = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
-for (let i = 0; i < fftChunkLength; i++) {
-  fftInput[i] = samples[i];
-}
+// Efficiently copy samples to power-of-two sized buffer (zero-padding)
+const samples = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
+fftInput.set(samples);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec62340 and 966e0cd.

📒 Files selected for processing (1)
  • src/graph_spectrum_calc.js (10 hunks)
🔇 Additional comments (9)
src/graph_spectrum_calc.js (9)

97-97: Good clarification in comment.

The comment clarifies that the Hanning window is applied only to the actual sample count values, not the entire buffer.


104-104: Parameter removed from _normalizeFft call.

The explicit fftLength parameter has been removed, which is good as the function now calculates this value internally.


109-140: Well-implemented PSD calculation using Welch's method.

The new dataLoadPSD method is a valuable addition that:

  1. Dynamically calculates segment size based on zoom level
  2. Ensures segment size is a power of two for optimal FFT performance
  3. Sets appropriate overlap for Welch's method
  4. Returns structured output with min/max values and max noise frequency

This implementation follows best practices for spectral density estimation.


149-150: Good optimization using power-of-two FFT buffer size.

Using a power-of-two size for FFT buffers is crucial for optimal FFT performance, as mentioned in the PR objectives. This change implements that optimization correctly.


197-197: Improved bin index calculation.

Changing from Math.floor to Math.round provides more accurate binning for frequency vs X charts.


328-351: Good optimization with power-of-two sample size.

Adding the scaled parameter and ensuring the returned samples are padded to a power-of-two length improves FFT performance. This is a well-implemented change.


465-468: Memory optimization by slicing arrays to actual sample count.

This change ensures that only the necessary data is returned, which is more memory-efficient.


535-619: Excellent implementation of Power Spectral Density calculation.

The new _psd method correctly implements Welch's method for PSD calculation with:

  1. Proper window scaling for both 'density' and 'spectrum' output types
  2. Correct averaging of scaled power values
  3. Conversion to dB scale
  4. Tracking of min/max values and max noise frequency

This is a significant improvement for spectrum analysis.


622-646: Correctly implemented FFT segmentation with proper magnitude calculation.

The _fft_segmented method:

  1. Handles overlapping segments properly
  2. Applies optional Hanning windowing
  3. Correctly calculates the magnitude of complex FFT outputs using Math.hypot(re, im)

This implementation addresses one of the key issues mentioned in the PR objectives.

@demvlad demvlad marked this pull request as draft May 13, 2025 19:16
@demvlad demvlad marked this pull request as ready for review May 13, 2025 19:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/graph_spectrum_calc.js (1)

151-156: 🛠️ Refactor suggestion

FFT matrix allocates 2 × more memory than required

Only magnitudeLength values are written and read, yet each row is created
with fftBufferSize * 2. This doubles RAM usage and cache traffic and makes
the later averaging loop iterate over thousands of untouched zeros.

-const matrixFftOutput = new Array(NUM_VS_BINS)
-  .fill(null)
-  .map(() => new Float64Array(fftBufferSize * 2));
+const matrixFftOutput = new Array(NUM_VS_BINS)
+  .fill(null)
+  .map(() => new Float64Array(magnitudeLength));
🧹 Nitpick comments (3)
src/graph_spectrum_plot.js (2)

372-381: ticksCount is forwarded unchanged – may still be zero

Even with the above guard, callers might still provide 0 explicitly.
Consider clamping inside _drawVerticalGridLines() or here:

this._drawVerticalGridLines(
  canvasCtx,
  LEFT,
  TOP,
  WIDTH,
  HEIGHT,
  minY,
  maxY,
  "dBm/Hz",
  Math.max(1, ticksCount),   // safe forwarding
);

1076-1094: Unused LEFT parameter – remove to simplify the signature

LEFT is never referenced inside _drawVerticalGridLines. Keeping unused
parameters adds noise and invites confusion.

-GraphSpectrumPlot._drawVerticalGridLines = function (
-  canvasCtx,
-  LEFT,
-  TOP,
-  WIDTH,
-  HEIGHT,
+GraphSpectrumPlot._drawVerticalGridLines = function (
+  canvasCtx,
+  /* LEFT */, /* TOP */,
+  WIDTH,
+  HEIGHT,
   minValue,
   maxValue,
   label,
   ticks = 5,
) {
src/graph_spectrum_calc.js (1)

543-574: Heavy window re-allocation inside _psd()

window is rebuilt for every PSD call. For large pointsPerSegment
(>= 4096) this allocates & initialises tens of kB unnecessarily.

Consider caching Hanning windows keyed by pointsPerSegment
(e.g. this._hanningCache[size]) to reuse the array.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 966e0cd and 5d2749a.

📒 Files selected for processing (2)
  • src/graph_spectrum_calc.js (10 hunks)
  • src/graph_spectrum_plot.js (14 hunks)
🔇 Additional comments (2)
src/graph_spectrum_plot.js (1)

336-341: ⚠️ Potential issue

Guard against ticksCount === 0 to avoid division-by-zero in scaling & grid drawing

If the PSD happens to be completely flat ( this._fftData.minimum === this._fftData.maximum ) the ticksCount becomes 0, which will later be
passed to _drawVerticalGridLines() and used as a divisor in HEIGHT / ticks.
That triggers an Infinity height, completely breaking the rendering.

-const ticksCount = (maxY - minY) / dbStep;
+let ticksCount = (maxY - minY) / dbStep;
+// Ensure at least one tick in degenerate cases
+if (ticksCount < 1) {
+  ticksCount = 1;
+}

Likely an incorrect or invalid review comment.

src/graph_spectrum_calc.js (1)

348-352: Zero-sample guard for FFT buffer sizing

When the selected time range is empty, samplesCount is 0;
Math.log2(0) is -Infinity, resulting in fftBufferSize = NaN and an
exception downstream.

-const fftBufferSize = Math.pow(2, Math.ceil(Math.log2(samplesCount)));
+const fftBufferSize = samplesCount > 0
+  ? Math.pow(2, Math.ceil(Math.log2(samplesCount)))
+  : 1; // minimal, prevents NaN

@demvlad
Copy link
Contributor Author

demvlad commented May 14, 2025

The next step will join #827

@demvlad demvlad marked this pull request as draft May 14, 2025 06:41
@demvlad
Copy link
Contributor Author

demvlad commented May 14, 2025

@haslinghuis @nerdCopter could you help. I can not resize Float32 array to fft size value.

resolved.

@nerdCopter
Copy link
Member

This also seems "fast" in the UI (more instantaneous than master).
(5d2749a8)

@demvlad demvlad marked this pull request as ready for review May 14, 2025 13:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/graph_spectrum_plot.js (1)

329-409: New method implementation for Power Spectral Density visualization looks good.

The PSD graph rendering is implemented with appropriate axes, scaling, and line drawing. It correctly handles the PSD data format and range.

However, there's one issue on line 359:

🧹 Nitpick comments (2)
index.html (1)

460-464: Verify UI and code alignment for new spectrum options
Ensure that the JavaScript handlers in src/graph_spectrum_calc.js, src/graph_spectrum_plot.js, and src/graph_spectrum.js correctly map the expanded spectrumTypeSelect values (2–6) to their respective FFT magnitude and PSD computation/rendering logic. Consider adding or updating integration tests to cover the new “Freq. vs RPM”, “Power spectral density”, “PSD vs Throttle”, and “PSD vs RPM” modes.

src/graph_spectrum_calc.js (1)

118-120: Add minimum size validation for FFT input.

The code should check for minimum size before calculating the FFT to prevent issues with very small or empty sample arrays.

if (pointsPerSegment > flightSamples.samples.length) {
-    pointsPerSegment = this.getNearPower2Value(flightSamples.samples.length);
+    // Ensure minimum size for FFT and handle empty arrays
+    if (flightSamples.samples.length === 0) {
+        pointsPerSegment = 256; // Default minimum size
+    } else {
+        pointsPerSegment = this.getNearPower2Value(flightSamples.samples.length);
+    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2749a and 7e9582e.

📒 Files selected for processing (4)
  • index.html (1 hunks)
  • src/graph_spectrum.js (2 hunks)
  • src/graph_spectrum_calc.js (13 hunks)
  • src/graph_spectrum_plot.js (21 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/graph_spectrum.js (2)
src/graph_spectrum_plot.js (2)
  • SPECTRUM_TYPE (18-26)
  • SPECTRUM_TYPE (18-26)
src/graph_spectrum_calc.js (2)
  • GraphSpectrumCalc (23-37)
  • GraphSpectrumCalc (23-37)
🔇 Additional comments (28)
src/graph_spectrum.js (3)

115-122: Implementation of new PSD spectrum types looks good.

The addition of PSD_VS_THROTTLE and PSD_VS_RPM cases correctly integrates the new spectrum types defined in the SPECTRUM_TYPE enum. For PSD_VS_RPM, I notice you're reusing the existing dataLoadFrequencyVsRpm method with a boolean parameter instead of creating a new method as you did with dataLoadPowerSpectralDensityVsThrottle.


127-130: LGTM for Power Spectral Density implementation.

The POWER_SPECTRAL_DENSITY case correctly passes the analyserZoomY value to the dataLoadPSD method, which aligns with the PR description of using this value to adjust PSD smoothing.


202-206: Appropriate handling of PSD recalculation on zoom change.

Good addition to recalculate PSD data when the Y zoom changes for the POWER_SPECTRAL_DENSITY type. This ensures that the visualization correctly reflects the user's adjustment of the smoothing parameter via the vertical slider.

src/graph_spectrum_plot.js (12)

3-3: Good addition: Importing NUM_VS_BINS constant from calculation module.

This import improves code organization by centralizing the configuration value in the calculation module and reusing it in the plot module.


14-16: LGTM: New constants for PSD y-axis scaling.

These constants provide appropriate defaults for dBm value ranges in PSD visualizations.


21-25: LGTM: New spectrum types for PSD visualizations.

The enum extension follows the existing pattern and adds clear descriptive types for the new PSD visualization modes.


177-183: LGTM: Added PSD_VS_THROTTLE and PSD_VS_RPM cases to draw method.

The implementation correctly reuses the existing _drawFrequencyVsXGraph method with a flag to enable PSD mode.


189-191: LGTM: Added POWER_SPECTRAL_DENSITY case to draw method.

The implementation correctly calls the new dedicated method for PSD visualization.


411-416: LGTM: Helper method for retrieving PSD value by frequency.

This utility method properly handles bounds checking and provides a clean interface for accessing PSD data in the visualization layer.


418-418: LGTM: Updated frequency vs X graph method with PSD support.

The added parameter allows reusing the existing code for both frequency and PSD visualizations.


492-525: LGTM: Enhanced heat map method with PSD scaling.

The conditional logic properly handles both frequency and PSD data types with appropriate scaling.


536-546: LGTM: New helper for retrieving values from matrix FFT data.

This utility method properly handles bounds checking and provides a clean interface for accessing matrix data.


1525-1534: LGTM: Enhanced mouse position display with PSD values.

This enhancement correctly shows PSD values at mouse cursor position, improving user experience.


1573-1583: LGTM: Enhanced mouse position display for PSD vs X graphs.

This enhancement correctly shows PSD values for PSD vs throttle/RPM graphs.


1284-1290: LGTM: Enhanced interest frequency labels with PSD values.

The conditional logic correctly formats labels for frequency and PSD visualization types.

src/graph_spectrum_calc.js (13)

21-21: LGTM: Exporting NUM_VS_BINS constant.

Making this constant available to other modules improves code organization.


91-93: LGTM: Helper function for nearest power of 2 calculation.

This utility function correctly calculates the nearest power of 2 value, which is important for optimal FFT performance.


100-100: LGTM: Apply Hanning window only to actual samples.

The update correctly ensures the window is only applied to actual data values, not padding zeros.


112-137: LGTM: New PSD calculation implementation.

The Power Spectral Density calculation using Welch's method is well-implemented with appropriate segmentation, overlap, and scaling.


147-148: LGTM: Improved FFT buffer sizing and magnitude length calculation.

The changes ensure buffer sizes are powers of two for optimal FFT performance and correctly derive the magnitude length.


170-178: LGTM: Corrected magnitude calculation.

The implementation now correctly calculates magnitude using both real and imaginary parts of the complex FFT output.


228-298: LGTM: New method for PSD vs throttle/RPM calculation.

This implementation correctly adapts the frequency vs X approach to work with PSD calculations.


304-306: LGTM: New method for PSD vs throttle.

This helper method correctly calls the underlying implementation with appropriate parameters.


308-314: LGTM: Updated RPM method to support both frequency and PSD.

The conditional logic effectively handles both data types with a single interface.


576-611: LGTM: Improved FFT normalization with correct magnitude calculation.

The implementation now correctly calculates magnitude using both real and imaginary parts of the complex FFT output.


613-700: LGTM: PSD calculation using Welch's method.

The implementation follows the correct mathematical approach for PSD calculation with proper scaling, windowing, and segment averaging.


705-727: LGTM: FFT segmentation for Welch's method.

This helper function correctly implements segmented FFT calculation with overlap support.


652-659: LGTM: Early exit for empty segments in PSD calculation.

The code correctly handles the edge case of no segments being processed.

@demvlad
Copy link
Contributor Author

demvlad commented May 14, 2025

Ready for review!

@demvlad demvlad changed the title The notice and suggestions about spectrum charts. The spectrum charts improvements. May 14, 2025
@Quick-Flash
Copy link

The source code uses output Re Im values directly as separate value at the chart, what is wrong IMO. It uses output data what size is similar input data. The spectrum chart has 600000points for my log rate settings. The charts is sequence Re,Im,Re,Im... values. This is wrong. We should show magnitude values at the charts as Math.hypo(Re, Im). Magnitudes array has 1/4 input data size

Was the code seriously doing this in the past? This is crazy and a much needed fix, thats a good find.

Copy link

sonarqubecloud bot commented Jun 9, 2025

Copy link

github-actions bot commented Jun 9, 2025

Preview URL: https://fb5a66be.betaflight-blackbox.pages.dev

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/graph_spectrum_plot.js (4)

395-400: First line segment starts at (0, 0) instead of the first data point – visible artefact

moveTo(0, 0) draws an unwanted diagonal from the canvas origin to the first
PSD point. Start the path at the correct y-coordinate instead.

-canvasCtx.moveTo(0, 0);
+const y0 = HEIGHT - (this._fftData.psdOutput[0] - minY) * scaleY;
+canvasCtx.moveTo(0, y0);

381-388: ticksCount can be zero → divide-by-zero downstream

When maxY === minY the expression

const ticksCount = (maxY - minY) / dbStep; // → 0

propagates to _drawVerticalGridLines, which divides by ticks when computing
HEIGHT / ticks.
Guard for this edge case:

-const ticksCount = (maxY - minY) / dbStep;
+let ticksCount = (maxY - minY) / dbStep;
+if (ticksCount < 1) ticksCount = 1; // avoid div/0 and ensure at least one grid line

1162-1172: Robustness of _drawVerticalGridLines with custom ticks

ticks is now an argument, but the loop uses HEIGHT / ticks and modulo math
without checking that ticks ≥ 1.
Consider early-returning when ticks <= 0 or coercing to Math.max(1, ticks)
to prevent runtime exceptions.


1023-1023: Remove console.warn from production code.

Replace with proper error handling or remove if this is an expected condition.

-    console.warn("Notch filter fieldName missing");
+    // Silently handle missing notch filter fieldName
🧹 Nitpick comments (1)
src/graph_spectrum_calc.js (1)

228-299: Well-implemented PSD analysis with proper binning and scaling.

The function correctly implements Welch's method for PSD computation with appropriate background values and matrix sizing. The parallel structure to the frequency analysis provides good consistency.

Consider making the background PSD value a named constant:

+ const BACKGROUND_PSD_VALUE_DB = -200;
  // Matrix where each row represents a bin of vs values, and the columns are amplitudes at frequencies
- const BACKGROUND_PSD_VALUE = -200;
+ const BACKGROUND_PSD_VALUE = BACKGROUND_PSD_VALUE_DB;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa94a6 and fb5a66b.

📒 Files selected for processing (5)
  • index.html (2 hunks)
  • src/css/main.css (5 hunks)
  • src/graph_spectrum.js (11 hunks)
  • src/graph_spectrum_calc.js (13 hunks)
  • src/graph_spectrum_plot.js (26 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/css/main.css
  • index.html
  • src/graph_spectrum.js
🧰 Additional context used
🧠 Learnings (2)
src/graph_spectrum_plot.js (1)
Learnt from: demvlad
PR: betaflight/blackbox-log-viewer#833
File: src/graph_spectrum.js:0-0
Timestamp: 2025-05-26T16:18:25.863Z
Learning: In src/graph_spectrum_calc.js, the user created dedicated PSD functions `dataLoadPowerSpectralDensityVsThrottle()` and `dataLoadPowerSpectralDensityVsRpm()` instead of using boolean parameters, following a consistent naming pattern for spectrum data loading methods.
src/graph_spectrum_calc.js (1)
Learnt from: demvlad
PR: betaflight/blackbox-log-viewer#833
File: src/graph_spectrum.js:0-0
Timestamp: 2025-05-26T16:18:25.863Z
Learning: In src/graph_spectrum_calc.js, the user created dedicated PSD functions `dataLoadPowerSpectralDensityVsThrottle()` and `dataLoadPowerSpectralDensityVsRpm()` instead of using boolean parameters, following a consistent naming pattern for spectrum data loading methods.
🧬 Code Graph Analysis (1)
src/graph_spectrum_plot.js (1)
src/graph_spectrum_calc.js (19)
  • i (175-175)
  • i (195-195)
  • i (202-202)
  • i (269-269)
  • i (276-276)
  • i (342-342)
  • i (348-348)
  • i (359-359)
  • i (464-464)
  • i (569-569)
  • i (606-606)
  • i (684-684)
  • i (730-730)
  • i (750-750)
  • j (204-204)
  • j (278-278)
  • j (686-686)
  • NUM_VS_BINS (22-22)
  • NUM_VS_BINS (22-22)
🔇 Additional comments (19)
src/graph_spectrum_plot.js (11)

3-3: LGTM! Good constant organization and imports.

The import of NUM_VS_BINS aligns with the PSD functionality, and the new constants provide sensible defaults for dBm range and performance optimization.

Also applies to: 15-15, 17-18


20-28: LGTM! Clear spectrum type organization.

The addition of PSD spectrum types to the enum is well-organized and maintains logical grouping.


54-56: LGTM! Proper state management with cache invalidation.

The PSD parameter setters follow the established pattern of invalidating caches only when values actually change, which is efficient.

Also applies to: 86-111


209-215: LGTM! Consistent switch case handling for new spectrum types.

The new case statements properly route PSD spectrum types to their respective drawing methods.

Also applies to: 221-223


262-270: LGTM! Smart performance optimization.

The dynamic step calculation effectively limits drawing operations to MAX_SPECTRUM_LINE_COUNT while maintaining visual quality. This should improve rendering performance for large datasets.


447-452: LGTM! Robust helper method with proper bounds checking.

The getPSDbyFreq method correctly handles frequency-to-index conversion with appropriate bounds checking and safe array access.


454-454: LGTM! Clean parameter addition with backward compatibility.

The drawPSD parameter with default false maintains backward compatibility while enabling PSD-specific behavior.

Also applies to: 466-466


501-525: LGTM! Consistent grid line handling for PSD spectrum types.

The conditional updates properly include the new PSD spectrum types with appropriate unit labels.


528-557: LGTM! Well-implemented PSD scaling logic.

The PSD-specific value clamping and scaling logic is correct, and the scale factor adjustment (1.3 to 1.1) is well-documented with proper reasoning.


573-583: LGTM! Robust matrix access with proper bounds checking.

The getValueFromMatrixFFT method correctly handles 2D indexing with appropriate bounds checking and uses the imported NUM_VS_BINS constant properly.


1298-1299: LGTM! Comprehensive PSD integration throughout the plotting system.

The updates to background gradient opacity, interest frequency labeling, and mouse position handling properly integrate PSD functionality with appropriate value displays and units.

Also applies to: 1320-1327, 1568-1577, 1590-1592, 1616-1626

src/graph_spectrum_calc.js (8)

20-22: Well-structured constants and exports.

The new constants are appropriately named and the export of NUM_VS_BINS provides good encapsulation for shared values across modules.


94-96: Function correctly implements power-of-2 sizing for FFT optimization.

The implementation properly returns the next power of 2, which is used correctly throughout the codebase for FFT buffer allocation with zero-padding.


115-141: Excellent implementation of PSD analysis using Welch's method.

The function properly handles segment sizing based on zoom level, implements appropriate overlap (50% when possible), and includes necessary bounds checking. The return structure provides comprehensive PSD data for visualization.


172-180: Critical fix: Correct FFT magnitude calculation implemented.

The use of Math.hypot(re, im) to compute the Euclidean norm of complex FFT output is mathematically correct and addresses the previous issue where only real parts were used.


191-191: Elegant bin index calculation eliminates bounds checking.

Using (NUM_VS_BINS - 1) as the multiplier ensures the result naturally falls within [0, NUM_VS_BINS-1] range, making the code cleaner and more robust.


606-614: Correct magnitude calculation and noise detection implemented.

The use of Math.hypot(re, im) for magnitude calculation is mathematically correct, and the noise detection logic properly identifies dominant frequencies above the low-end threshold.


630-719: Mathematically rigorous implementation of Welch's method for PSD computation.

This function correctly implements all aspects of Welch's method including:

  • Proper scaling for windowed/non-windowed cases
  • Correct one-sided spectrum conversion (doubling except DC and Nyquist)
  • Appropriate dB conversion with minimum value clamping
  • Comprehensive noise frequency detection

The implementation follows signal processing standards and provides robust PSD analysis.


721-759: Well-implemented segmented FFT processing for Welch's method.

The function correctly handles overlapping segments and includes a smart optimization for single-segment cases by extending to power-of-2 sizes. The magnitude calculation using Math.hypot is consistent and correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants