-
-
Notifications
You must be signed in to change notification settings - Fork 156
Added power spectral density curves at the spectrum chart #820
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
Changes from 19 commits
9ff6215
3e1a3a1
750991a
0bdf2f4
4bfe62f
9f229c2
9637f10
082512d
a1eeb26
f345f2f
d43fbe0
43dba39
0c6dd89
7825802
b3bcdc3
dc19b37
69dd67e
2f60284
8cb92b2
81a4abd
8350ecf
4e3ebea
d4e4ef8
9b7d065
15fd548
014a06e
2cde4dd
30f7758
47459e8
2eef77e
cab6757
011b977
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -106,6 +106,33 @@ GraphSpectrumCalc.dataLoadFrequency = function() { | |||||||||||||||||||||||||
| return fftData; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| GraphSpectrumCalc.dataLoadPSD = function(analyserZoomY) { | ||||||||||||||||||||||||||
| const flightSamples = this._getFlightSamplesFreq(false); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let pointsPerSegment = 512; | ||||||||||||||||||||||||||
| const multipler = Math.floor(1 / analyserZoomY); // 0. ... 10 | ||||||||||||||||||||||||||
| if (multipler == 0) { | ||||||||||||||||||||||||||
| pointsPerSegment = 256; | ||||||||||||||||||||||||||
| } else if(multipler > 1) { | ||||||||||||||||||||||||||
| pointsPerSegment *= 2 ** Math.floor(multipler / 2); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| pointsPerSegment = Math.min(pointsPerSegment, flightSamples.samples.length); | ||||||||||||||||||||||||||
| const overlapCount = pointsPerSegment / 2; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const psd = this._psd(flightSamples.samples, pointsPerSegment, overlapCount); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const psdData = { | ||||||||||||||||||||||||||
| fieldIndex : this._dataBuffer.fieldIndex, | ||||||||||||||||||||||||||
| fieldName : this._dataBuffer.fieldName, | ||||||||||||||||||||||||||
| psdLength : psd.psdOutput.length, | ||||||||||||||||||||||||||
| psdOutput : psd.psdOutput, | ||||||||||||||||||||||||||
| blackBoxRate : this._blackBoxRate, | ||||||||||||||||||||||||||
| minimum: psd.min, | ||||||||||||||||||||||||||
| maximum: psd.max, | ||||||||||||||||||||||||||
| maxNoiseIdx: psd.maxNoiseIdx, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| return psdData; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -283,7 +310,7 @@ GraphSpectrumCalc._getFlightChunks = function() { | |||||||||||||||||||||||||
| return allChunks; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| GraphSpectrumCalc._getFlightSamplesFreq = function() { | ||||||||||||||||||||||||||
| GraphSpectrumCalc._getFlightSamplesFreq = function(scaled = true) { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const allChunks = this._getFlightChunks(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -293,7 +320,11 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() { | |||||||||||||||||||||||||
| let samplesCount = 0; | ||||||||||||||||||||||||||
| for (const chunk of allChunks) { | ||||||||||||||||||||||||||
| for (const frame of chunk.frames) { | ||||||||||||||||||||||||||
| samples[samplesCount] = (this._dataBuffer.curve.lookupRaw(frame[this._dataBuffer.fieldIndex])); | ||||||||||||||||||||||||||
| if (scaled) { | ||||||||||||||||||||||||||
| samples[samplesCount] = (this._dataBuffer.curve.lookupRaw(frame[this._dataBuffer.fieldIndex])); | ||||||||||||||||||||||||||
demvlad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| samples[samplesCount] = frame[this._dataBuffer.fieldIndex]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid When -if (scaled) {
- samples[samplesCount] = (this._dataBuffer.curve.lookupRaw(frame[this._dataBuffer.fieldIndex]));
+if (scaled && this._dataBuffer.curve) {
+ samples[samplesCount] = this._dataBuffer.curve.lookupRaw(
+ frame[this._dataBuffer.fieldIndex]
+ );
} else {
samples[samplesCount] = frame[this._dataBuffer.fieldIndex];
}Preventing the null-dereference keeps existing FFT paths stable. 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
| samplesCount++; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
@@ -485,3 +516,107 @@ GraphSpectrumCalc._normalizeFft = function(fftOutput, fftLength) { | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return fftData; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Compute PSD for data samples by Welch method follow Python code | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| GraphSpectrumCalc._psd = function(samples, pointsPerSegment, overlapCount, scaling = 'density') { | ||||||||||||||||||||||||||
| // Compute FFT for samples segments | ||||||||||||||||||||||||||
| const fftOutput = this._fft_segmented(samples, pointsPerSegment, overlapCount); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const dataCount = fftOutput[0].length; | ||||||||||||||||||||||||||
| const segmentsCount = fftOutput.length; | ||||||||||||||||||||||||||
| const psdOutput = new Float64Array(dataCount); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Compute power scale coef | ||||||||||||||||||||||||||
| let scale = 1; | ||||||||||||||||||||||||||
| if (userSettings.analyserHanning) { | ||||||||||||||||||||||||||
| const window = Array(pointsPerSegment).fill(1); | ||||||||||||||||||||||||||
| this._hanningWindow(window, pointsPerSegment); | ||||||||||||||||||||||||||
| if (scaling == 'density') { | ||||||||||||||||||||||||||
| let skSum = 0; | ||||||||||||||||||||||||||
| for (const value of window) { | ||||||||||||||||||||||||||
| skSum += value ** 2; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| scale = 1 / (this._blackBoxRate * skSum); | ||||||||||||||||||||||||||
| } else if (scaling == 'spectrum') { | ||||||||||||||||||||||||||
| let sum = 0; | ||||||||||||||||||||||||||
| for (const value of window) { | ||||||||||||||||||||||||||
| sum += value; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| scale = 1 / sum ** 2; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } else if (scaling == 'density') { | ||||||||||||||||||||||||||
| scale = 1 / pointsPerSegment; | ||||||||||||||||||||||||||
| } else if (scaling == 'spectrum') { | ||||||||||||||||||||||||||
| scale = 1 / pointsPerSegment ** 2; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Compute average for scaled power | ||||||||||||||||||||||||||
| let min = 1e6, | ||||||||||||||||||||||||||
| max = -1e6; | ||||||||||||||||||||||||||
demvlad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| const maxFrequency = (this._blackBoxRate / 2.0); | ||||||||||||||||||||||||||
| const noise100HzIdx = 100 / maxFrequency * dataCount; | ||||||||||||||||||||||||||
demvlad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| const noise3HzIdx = 3 / maxFrequency * dataCount; | ||||||||||||||||||||||||||
| let maxNoiseIdx = 0; | ||||||||||||||||||||||||||
| let maxNoise = -100; | ||||||||||||||||||||||||||
| for (let i = 0; i < dataCount; i++) { | ||||||||||||||||||||||||||
| psdOutput[i] = 0.0; | ||||||||||||||||||||||||||
| for (let j = 0; j < segmentsCount; j++) { | ||||||||||||||||||||||||||
| let p = scale * fftOutput[j][i] ** 2; | ||||||||||||||||||||||||||
| if (i != dataCount - 1) { | ||||||||||||||||||||||||||
| p *= 2; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| psdOutput[i] += p; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const min_avg = 1e-5; // limit min value for -50db | ||||||||||||||||||||||||||
| let avg = psdOutput[i] / segmentsCount; | ||||||||||||||||||||||||||
| avg = Math.max(avg, min_avg); | ||||||||||||||||||||||||||
| psdOutput[i] = 10 * Math.log10(avg); | ||||||||||||||||||||||||||
| if (i > noise3HzIdx) { // Miss big zero freq magnitude | ||||||||||||||||||||||||||
| min = Math.min(psdOutput[i], min); | ||||||||||||||||||||||||||
| max = Math.max(psdOutput[i], max); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if (i > noise100HzIdx && psdOutput[i] > maxNoise) { | ||||||||||||||||||||||||||
| maxNoise = psdOutput[i]; | ||||||||||||||||||||||||||
| maxNoiseIdx = i; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const maxNoiseFrequency = maxNoiseIdx / dataCount * maxFrequency; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| psdOutput: psdOutput, | ||||||||||||||||||||||||||
| min: min, | ||||||||||||||||||||||||||
| max: max, | ||||||||||||||||||||||||||
| maxNoiseIdx: maxNoiseFrequency, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Compute FFT for samples segments by lenghts as n_per_seg with n_overlap overlap points count | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| GraphSpectrumCalc._fft_segmented = function(samples, n_per_seg, n_overlap) { | ||||||||||||||||||||||||||
| const samplesCount = samples.length; | ||||||||||||||||||||||||||
| let output = []; | ||||||||||||||||||||||||||
| for (let i = 0; i < samplesCount - n_per_seg; i += n_per_seg - n_overlap) { | ||||||||||||||||||||||||||
| const fftInput = samples.slice(i, i + n_per_seg); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (userSettings.analyserHanning) { | ||||||||||||||||||||||||||
| this._hanningWindow(fftInput, n_per_seg); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const fftComplex = this._fft(fftInput); | ||||||||||||||||||||||||||
| const magnitudes = new Float64Array(n_per_seg / 2); | ||||||||||||||||||||||||||
| for (let i = 0; i < n_per_seg / 2; i++) { | ||||||||||||||||||||||||||
| const re = fftComplex[2 * i]; | ||||||||||||||||||||||||||
| const im = fftComplex[2 * i + 1]; | ||||||||||||||||||||||||||
| magnitudes[i] = Math.hypot(re, im); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| output.push(magnitudes); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return output; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Segmented FFT calculation has logical and style issues.
- for (let i = 0; i < samplesCount - n_per_seg; i += n_per_seg - n_overlap) {
+ for (let i = 0; i <= samplesCount - n_per_seg; i += n_per_seg - n_overlap) {
const fftComplex = this._fft(fftInput);
const magnitudes = new Float64Array(n_per_seg / 2);
- for (let i = 0; i < n_per_seg / 2; i++) {
+ for (let j = 0; j < n_per_seg / 2; j++) {
- const re = fftComplex[2 * i];
- const im = fftComplex[2 * i + 1];
+ const re = fftComplex[2 * j];
+ const im = fftComplex[2 * j + 1];
- magnitudes[i] = Math.hypot(re, im);
+ magnitudes[j] = Math.hypot(re, im);
}
- GraphSpectrumCalc._fft_segmented = function(samples, n_per_seg, n_overlap) {
+ GraphSpectrumCalc._fftSegmented = function(samples, pointsPerSegment, overlapCount) {And update references elsewhere: - const fftOutput = this._fft_segmented(samples, pointsPerSegment, overlapCount);
+ const fftOutput = this._fftSegmented(samples, pointsPerSegment, overlapCount);
|
||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.