diff --git a/index.html b/index.html index 8cb41177..92a84645 100644 --- a/index.html +++ b/index.html @@ -505,21 +505,28 @@

Workspace

/> + + + - + - + + diff --git a/src/css/main.css b/src/css/main.css index cc4c66af..0fd5c0a3 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -692,7 +692,9 @@ html.has-analyser-fullscreen.has-analyser .analyser input#analyserMaxPSD::-webkit-inner-spin-button, .analyser input#analyserMaxPSD::-webkit-outer-spin-button, .analyser input#analyserLowLevelPSD::-webkit-inner-spin-button, -.analyser input#analyserLowLevelPSD::-webkit-outer-spin-button { +.analyser input#analyserLowLevelPSD::-webkit-outer-spin-button , +.analyser input#analyserSegmentLengthPowerAt2::-webkit-inner-spin-button, +.analyser input#analyserSegmentLengthPowerAt2::-webkit-outer-spin-button { -webkit-appearance: auto !important; -moz-appearance: auto !important; appearance: auto !important; @@ -708,6 +710,14 @@ html.has-analyser-fullscreen.has-analyser top: 30px; } +.analyser label#analyserMaxPSDLabel { + position:absolute; + color:gray; + left: 0px; + top: 30px; + font-size: 12px; +} + .analyser input#analyserMinPSD { width: 50px; height: 20px; @@ -715,6 +725,14 @@ html.has-analyser-fullscreen.has-analyser top: 55px; } +.analyser label#analyserMinPSDLabel { + position:absolute; + color:gray; + left: 0px; + top: 55px; + font-size: 12px; +} + .analyser input#analyserLowLevelPSD { width: 50px; height: 20px; @@ -722,28 +740,28 @@ html.has-analyser-fullscreen.has-analyser top: 80px; } -.analyser label#analyserMaxPSDLabel { +.analyser label#analyserLowLevelPSDLabel { position:absolute; color:gray; left: 0px; - top: 30px; + top: 80px; font-size: 12px; } -.analyser label#analyserMinPSDLabel { - position:absolute; - color:gray; +.analyser input#analyserSegmentLengthPowerAt2 { + width: 42px; + height: 20px; left: 0px; - top: 55px; - font-size: 12px; + top: 42px; } -.analyser label#analyserLowLevelPSDLabel { +.analyser label#analyserSegmentLengthPowerAt2Label { position:absolute; color:gray; + width: 50px; + height: 20px; left: 0px; - top: 80px; - font-size: 12px; + top: 20px; } .analyser input.onlyFullScreen { diff --git a/src/graph_spectrum.js b/src/graph_spectrum.js index 99355af8..7d216a08 100644 --- a/src/graph_spectrum.js +++ b/src/graph_spectrum.js @@ -17,7 +17,8 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { const that = this, prefs = new PrefStorage(), DEFAULT_PSD_HEATMAP_MIN = -40, - DEFAULT_PSD_HEATMAP_MAX = 10; + DEFAULT_PSD_HEATMAP_MAX = 10, + DEFAULT_PSD_SEGMENT_LENGTH_POWER = 9; let analyserZoomX = 1.0 /* 100% */, analyserZoomY = 1.0 /* 100% */, dataReload = false, @@ -36,6 +37,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { const analyserMinPSD = $("#analyserMinPSD"); const analyserMaxPSD = $("#analyserMaxPSD"); const analyserLowLevelPSD = $("#analyserLowLevelPSD"); + const analyserSegmentLengthPowerAt2 = $("#analyserSegmentLengthPowerAt2"); const spectrumToolbarElem = $("#spectrumToolbar"); @@ -124,6 +126,12 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { $("#analyserLowLevelPSDLabel", parentElem).css({ left: `${newSize.width - 155}px`, }); + $("#analyserSegmentLengthPowerAt2", parentElem).css({ + left: `${newSize.width - 57}px`, + }); + $("#analyserSegmentLengthPowerAt2Label", parentElem).css({ + left: `${newSize.width - 135}px`, + }); }; const dataLoad = function (fieldIndex, curve, fieldName) { @@ -154,6 +162,9 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { case SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY: fftData = GraphSpectrumCalc.dataLoadPSD(analyserZoomY); + if (fftData.maximalSegmentsLength > 0) { + analyserSegmentLengthPowerAt2.prop("max", Math.ceil(Math.log2(fftData.maximalSegmentsLength))); + } break; case SPECTRUM_TYPE.FREQUENCY: @@ -241,11 +252,6 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { debounce(100, function () { analyserZoomY = 1 / (analyserZoomYElem.val() / 100); GraphSpectrumPlot.setZoom(analyserZoomX, analyserZoomY); - // Recalculate PSD with updated samples per segment count - if (userSettings.spectrumType == SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY) { - dataLoad(); - GraphSpectrumPlot.setData(fftData, userSettings.spectrumType); - } that.refresh(); }), ) @@ -325,6 +331,25 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { }) .val(analyserMinPSD.val()); + GraphSpectrumCalc.setPointsPerSegmentPSD(2 ** DEFAULT_PSD_SEGMENT_LENGTH_POWER); + analyserSegmentLengthPowerAt2 + .on( + "input", + debounce(100, function () { + // Recalculate PSD with updated samples per segment count + GraphSpectrumCalc.setPointsPerSegmentPSD(2 ** Number.parseInt($(this).val())); + dataLoad(); + GraphSpectrumPlot.setData(fftData, userSettings.spectrumType); + that.refresh(); + }), + ) + .dblclick(function (e) { + if (e.ctrlKey) { + $(this).val(DEFAULT_PSD_SEGMENT_LENGTH_POWER).trigger("input"); + } + }) + .val(DEFAULT_PSD_SEGMENT_LENGTH_POWER); + // Spectrum type to show userSettings.spectrumType = userSettings.spectrumType || SPECTRUM_TYPE.FREQUENCY; @@ -349,10 +374,16 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { const psdHeatMapSelected = optionSelected === SPECTRUM_TYPE.PSD_VS_THROTTLE || optionSelected === SPECTRUM_TYPE.PSD_VS_RPM; + const psdCurveSelected = + optionSelected === SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY; overdrawSpectrumTypeElem.toggle(!pidErrorVsSetpointSelected); analyserZoomYElem.toggleClass( "onlyFullScreenException", - pidErrorVsSetpointSelected || psdHeatMapSelected, + pidErrorVsSetpointSelected || psdHeatMapSelected || psdCurveSelected, + ); + analyserSegmentLengthPowerAt2.toggleClass( + "onlyFullScreenException", + !psdCurveSelected, ); analyserLowLevelPSD.toggleClass( "onlyFullScreenException", @@ -378,6 +409,10 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { "onlyFullScreenException", !psdHeatMapSelected, ); + $("#analyserSegmentLengthPowerAt2Label").toggleClass( + "onlyFullScreenException", + !psdCurveSelected, + ); const showSpectrumsComparisonPanel = optionSelected === SPECTRUM_TYPE.FREQUENCY || optionSelected === SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY; diff --git a/src/graph_spectrum_calc.js b/src/graph_spectrum_calc.js index ba7267f7..4521b757 100644 --- a/src/graph_spectrum_calc.js +++ b/src/graph_spectrum_calc.js @@ -35,6 +35,7 @@ export const GraphSpectrumCalc = { _flightLog : null, _sysConfig : null, _motorPoles : null, + _pointsPerSegmentPSD : 64, }; GraphSpectrumCalc.initialize = function(flightLog, sysConfig) { @@ -112,20 +113,21 @@ GraphSpectrumCalc.dataLoadFrequency = function() { return fftData; }; +GraphSpectrumCalc.setPointsPerSegmentPSD = function(pointsCount) { + this._pointsPerSegmentPSD = pointsCount; +}; + GraphSpectrumCalc.dataLoadPSD = function(analyserZoomY) { const flightSamples = this._getFlightSamplesFreq(false); - const multiplier = Math.floor(1 / analyserZoomY); // 0. ... 10 - let pointsPerSegment = 2 ** (8 + multiplier); //256, 512, 1024 ... - - let overlapCount; - if (pointsPerSegment > flightSamples.samples.length) { - pointsPerSegment = flightSamples.samples.length; // Use actual sample length. It will transform to power at 2 value inside the _psd() - fft_segmented - overlapCount = 0; - } else { - overlapCount = pointsPerSegment / 2; - } - - const psd = this._psd(flightSamples.samples, pointsPerSegment, overlapCount); + const totalCount = flightSamples.count; // actual samples, not padded length + let pointsPerSegment = Math.min(this._pointsPerSegmentPSD, totalCount); + // Non-overlapping when single full-segment; otherwise 75% overlap + const overlapCount = (pointsPerSegment === totalCount) ? 0 : Math.floor(pointsPerSegment * 3 / 4); + // Avoid bias from zero-padded tail + const samplesForPsd = (flightSamples.samples.length === totalCount) + ? flightSamples.samples + : flightSamples.samples.slice(0, totalCount); + const psd = this._psd(samplesForPsd, pointsPerSegment, overlapCount); const psdData = { fieldIndex : this._dataBuffer.fieldIndex, @@ -136,6 +138,7 @@ GraphSpectrumCalc.dataLoadPSD = function(analyserZoomY) { minimum: psd.min, maximum: psd.max, maxNoiseFrequency: psd.maxNoiseFrequency, + maximalSegmentsLength: this.getNearPower2Value(totalCount), }; return psdData; };