Skip to content

Power spectral density curves settings improvement #844

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

Draft
wants to merge 35 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6db3827
Added graph_imported_curves module for import curves
demvlad Jul 9, 2025
ddf5d63
Spectrum export/import code refactoring
demvlad Jul 9, 2025
79b5c83
Added export/import PSD curves data
demvlad Jul 9, 2025
1647d06
Added drawing of imported PSD curves
demvlad Jul 9, 2025
6b580c6
Resolved code issues
demvlad Jul 9, 2025
c96f430
Resolved code issues
demvlad Jul 9, 2025
48444a1
Code style improvement
demvlad Jul 10, 2025
10364a2
Resolved issue wrong frequency definition in exported file
demvlad Jul 10, 2025
622177f
Code improvement
demvlad Jul 10, 2025
340a7b6
The legend is moved to right-up corner
demvlad Jul 10, 2025
cc01fee
The exported spectrums .csv file name is log name with _sp, _psd postfix
demvlad Jul 10, 2025
d26c67e
Resolved issue of imported curves frequency scaling
demvlad Jul 10, 2025
c3c1df2
Code refactoring: PLOTTED_BLACKBOX_RATE is replace to MAXIMAL_PLOTTED…
demvlad Jul 11, 2025
5f255ac
Analyser legend position is added in user settings
demvlad Jul 11, 2025
62aa2ac
Added check of valid userSetting in _drawLegend method
demvlad Jul 11, 2025
4e81572
Added warning for specrum type what have not spectrum import
demvlad Jul 11, 2025
0cc128c
Added newline on EOF in src/graph_imported_curves.js
demvlad Jul 14, 2025
2caf5de
Changed mouse cursor info for single and multiple PSD curves
demvlad Jul 15, 2025
8812bae
Code style improvement
demvlad Jul 15, 2025
d25b398
The mouse position cursor improvement for PSD curves
demvlad Jul 15, 2025
a98744b
Code style improvement
demvlad Jul 15, 2025
82125da
Code style improvement
demvlad Jul 15, 2025
2595258
Code style improvement
demvlad Jul 15, 2025
bc30951
Resolved const issue
demvlad Jul 15, 2025
bde5775
The PSD heatmap low(cut) level is set like min value after min change
demvlad Jul 4, 2025
a6ceec0
The input number element is added at html and css instead of slider f…
demvlad Jul 4, 2025
0208d5e
The input number element for psd segment length input is placed at fo…
demvlad Jul 4, 2025
ec62b9d
Added update events handler to multiple/divide by 2 the PSD segment l…
demvlad Jul 4, 2025
2476e1d
Added points per segment count change from html input
demvlad Jul 4, 2025
88a00b0
Edited html input element
demvlad Jul 4, 2025
32b7eb0
Added computing of maximal points per segment PSD values
demvlad Jul 4, 2025
93eb8aa
Thde labels color is changed
demvlad Jul 4, 2025
5ee8dfb
Added missing comma
demvlad Jul 4, 2025
c0089f8
The segmentLenghtPSD variables name is changed to segmentLengthPSD
demvlad Jul 4, 2025
5513d86
The PSD segment length input elements are replaced
demvlad Jul 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ <h4>Workspace</h4>
/>
<input id="analyserZoomY" class="onlyFullScreen" type="range" name="analyserZoomY" value="100" min="10" max="1000" step="10" list="analyserZoomYTicks"
/>
<input id="analyserSegmentLengthPSD" class="onlyFullScreen" type="number" name="analyserSegmentLengthPSD" value="512" min="64" max="1048576" step="1"
/>
<input id="analyserLowLevelPSD" class="onlyFullScreen" type="number" name="analyserLowLevelPSD" value="-40" min="-40" max="10" step="5"
/>
<input id="analyserMaxPSD" class="onlyFullScreen" type="number" name="analyserMaxPSD" value="10" min="-35" max="100" step="5"
Expand All @@ -508,6 +510,9 @@ <h4>Workspace</h4>
<label id="analyserLowLevelPSDLabel" name="analyserLowLevelPSDLabel" class="onlyFullScreen" >
Limit&nbsp;dBm
</label>
<label id="analyserSegmentLengthPSDLabel" name="analyserSegmentLengthPSDLabel" class="onlyFullScreen" >
Segment&nbsp;length
</label>

<datalist id="analyserZoomXTicks">
<option>100</option>
Expand Down Expand Up @@ -2989,6 +2994,29 @@ <h4 class="modal-title">Advanced User Settings</h4>
<p>%</p>
</td>
</tr>
<tr>
<td>
<label>Legend</label>
</td>
<td class="position">
<label>Top</label>
<input name="analyser-legend-top" type="number" step="1" min="0"
max="100" />
<p>%</p>
</td>
<td class="position">
<label>Left</label>
<input name="analyser-legend-left" type="number" step="1" min="0"
max="100" />
<p>%</p>
</td>
<td class="position">
<label>Width</label>
<input name="analyser-legend-width" type="number" step="1" min="0"
max="100" />
<p>%</p>
</td>
</tr>
</table>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions public/js/webworkers/spectrum-export-worker.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
onmessage = function(event) {
const columnDelimiter = event.data.opts.columnDelimiter;
const fftOutput = event.data.fftOutput;
const spectrumDataLength = fftOutput.length / 2;
const spectrumDataLength = fftOutput.length;
const frequencyStep = 0.5 * event.data.blackBoxRate / spectrumDataLength;

let outText = "freq" + columnDelimiter + "value" + "\n";
for (let index = 0; index < spectrumDataLength; index += 10) {
let outText = "x" + columnDelimiter + "y" + "\n";
for (let index = 0; index < spectrumDataLength; index++) {
const frequency = frequencyStep * index;
outText += frequency.toString() + columnDelimiter + fftOutput[index].toString() + "\n";
}
Expand Down
20 changes: 19 additions & 1 deletion src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,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#analyserSegmentLengthPSD::-webkit-inner-spin-button,
.analyser input#analyserSegmentLengthPSD::-webkit-outer-spin-button {
-webkit-appearance: auto !important;
-moz-appearance: auto !important;
appearance: auto !important;
Expand Down Expand Up @@ -744,6 +746,22 @@ html.has-analyser-fullscreen.has-analyser
font-size: 12px;
}

.analyser input#analyserSegmentLengthPSD {
width: 80px;
height: 20px;
left: 0px;
top: 50px;
}

.analyser label#analyserSegmentLengthPSDLabel {
position:absolute;
color:gray;
width: 50px;
height: 20px;
left: 0px;
top: 28px;
}

.analyser input.onlyFullScreen {
display: none;
padding: 3px;
Expand Down
75 changes: 75 additions & 0 deletions src/graph_imported_curves.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export function ImportedCurves(curvesChanged) {
const maxImportCount = 5;
this._curvesData = [];
const _that = this;
this.minX = Number.MAX_VALUE;
this.maxX = -Number.MAX_VALUE;
this.minY = Number.MAX_VALUE;
this.maxY = -Number.MAX_VALUE;

this.curvesCount = function() {
return this._curvesData.length;
};

this.importCurvesFromCSV = function(files) {
let importsLeft = maxImportCount - this._curvesData.length;

for (const file of files) {
if (importsLeft-- == 0) {
break;
}
const reader = new FileReader();
reader.onload = function (e) {
try {
const stringRows = e.target.result.split("\n");

const header = stringRows[0].split(",");
if (header.length != 2 || header[0] != "x" || header[1] != "y") {
throw new SyntaxError("Wrong curves CSV data format");
}
Comment on lines +14 to +29
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance CSV validation and error handling.

The CSV parsing logic has several areas for improvement:

  1. The header validation could be more flexible
  2. Missing validation for numeric values
  3. Consider trimming whitespace from CSV values
 const header = stringRows[0].split(",");
-if (header.length != 2 || header[0] != "x" || header[1] != "y") {
+if (header.length != 2 || header[0].trim() != "x" || header[1].trim() != "y") {
   throw new SyntaxError("Wrong curves CSV data format");
 }

Additionally, consider adding validation for the parsed numeric values:

 const curvesData = stringRows.map( function(row) {
   const data = row.split(","),
-        x = parseFloat(data[0]),
-        y = parseFloat(data[1]);
+        x = parseFloat(data[0].trim()),
+        y = parseFloat(data[1].trim());
+  if (isNaN(x) || isNaN(y)) {
+    throw new SyntaxError(`Invalid numeric values in CSV: ${row}`);
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/graph_imported_curves.js around lines 14 to 29, improve the CSV
validation by making the header check more flexible to allow for whitespace and
case variations, trim whitespace from all CSV values before processing, and add
validation to ensure that the parsed x and y values are numeric. Update the code
to trim each header and data value, verify the header columns
case-insensitively, and check that each x and y value can be converted to a
valid number, throwing an error if not.


stringRows.shift();
//remove bad last row
if (stringRows.at(-1) == "") {
stringRows.pop();
}

const curvesData = stringRows.map( function(row) {
const data = row.split(","),
x = parseFloat(data[0]),
y = parseFloat(data[1]);
_that.minX = Math.min(x, _that.minX);
_that.maxX = Math.max(x, _that.maxX);
_that.minY = Math.min(y, _that.minY);
_that.maxY = Math.max(y, _that.maxY);
return {
x: x,
y: y,
};
});

const curve = {
name: file.name.split('.')[0],
points: curvesData,
};
_that._curvesData.push(curve);
curvesChanged();
} catch (e) {
alert('Curves data import error: ' + e.message);
return;
}
};

reader.readAsText(file);
}
};

this.removeCurves = function() {
this._curvesData.length = 0;
this.minX = Number.MAX_VALUE;
this.maxX = -Number.MAX_VALUE;
this.minY = Number.MAX_VALUE;
this.maxY = -Number.MAX_VALUE;
curvesChanged();
};
}
123 changes: 76 additions & 47 deletions src/graph_spectrum.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 512;
let analyserZoomX = 1.0 /* 100% */,
analyserZoomY = 1.0 /* 100% */,
dataReload = false,
Expand All @@ -35,6 +36,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
const analyserMinPSD = $("#analyserMinPSD");
const analyserMaxPSD = $("#analyserMaxPSD");
const analyserLowLevelPSD = $("#analyserLowLevelPSD");
const analyserSegmentLengthPSD = $("#analyserSegmentLengthPSD");


const spectrumToolbarElem = $("#spectrumToolbar");
Expand Down Expand Up @@ -117,6 +119,12 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
$("#analyserLowLevelPSDLabel", parentElem).css({
left: `${newSize.width - 155}px`,
});
$("#analyserSegmentLengthPSD", parentElem).css({
left: `${newSize.width - 120}px`,
});
$("#analyserSegmentLengthPSDLabel", parentElem).css({
left: `${newSize.width - 135}px`,
});
};

const dataLoad = function (fieldIndex, curve, fieldName) {
Expand Down Expand Up @@ -147,6 +155,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {

case SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY:
fftData = GraphSpectrumCalc.dataLoadPSD(analyserZoomY);
analyserSegmentLengthPSD.prop("max", fftData.maximalSegmentsLength);
break;

case SPECTRUM_TYPE.FREQUENCY:
Expand Down Expand Up @@ -213,11 +222,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();
}),
)
Expand Down Expand Up @@ -246,9 +250,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
saveOneUserSetting("psdHeatmapMin", min);
analyserLowLevelPSD.prop("min", min);
analyserMaxPSD.prop("min", min + 5);
if (analyserLowLevelPSD.val() < min) {
analyserLowLevelPSD.val(min).trigger("input");
}
analyserLowLevelPSD.val(min).trigger("input");
that.refresh();
}),
)
Expand Down Expand Up @@ -297,6 +299,42 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
})
.val(analyserMinPSD.val());

let segmentLengthPSD = DEFAULT_PSD_SEGMENT_LENGTH;
GraphSpectrumCalc.setPointsPerSegmentPSD(segmentLengthPSD);
analyserSegmentLengthPSD
.on(
"input",
function () {
const currentValue = parseInt($(this).val());
if (currentValue > segmentLengthPSD) {
segmentLengthPSD *= 2;
} else if (currentValue < segmentLengthPSD){
segmentLengthPSD /= 2;
}
$(this).val(segmentLengthPSD);
// Recalculate PSD with updated samples per segment count
GraphSpectrumCalc.setPointsPerSegmentPSD(segmentLengthPSD);
dataLoad();
GraphSpectrumPlot.setData(fftData, userSettings.spectrumType);
that.refresh();
},
)
.dblclick(function (e) {
if (e.ctrlKey) {
segmentLengthPSD = DEFAULT_PSD_SEGMENT_LENGTH;
$(this).val(DEFAULT_PSD_SEGMENT_LENGTH).trigger("input");
}
})
.val(DEFAULT_PSD_SEGMENT_LENGTH);

analyserSegmentLengthPSD
.on(
"keydown",
function (e) {
e.preventDefault();
},
);

// Spectrum type to show
userSettings.spectrumType =
userSettings.spectrumType || SPECTRUM_TYPE.FREQUENCY;
Expand All @@ -321,10 +359,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,
);
analyserSegmentLengthPSD.toggleClass(
"onlyFullScreenException",
!psdCurveSelected,
);
analyserLowLevelPSD.toggleClass(
"onlyFullScreenException",
Expand All @@ -350,8 +394,14 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
"onlyFullScreenException",
!psdHeatMapSelected,
);
$("#analyserSegmentLengthPSDLabel").toggleClass(
"onlyFullScreenException",
!psdCurveSelected,
);


$("#spectrumComparison").css("visibility", (optionSelected == 0 ? "visible" : "hidden"));
const showSpectrumsComparisonPanel = optionSelected === SPECTRUM_TYPE.FREQUENCY || optionSelected === SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY;
$("#spectrumComparison").css("visibility", (showSpectrumsComparisonPanel ? "visible" : "hidden"));
})
.change();

Expand Down Expand Up @@ -416,48 +466,27 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
};

this.importSpectrumFromCSV = function(files) {
const maxImportCount = 5;
let importsLeft = maxImportCount - GraphSpectrumPlot.getImportedSpectrumCount();
GraphSpectrumPlot.importCurvesFromCSV(files);
};

for (const file of files) {
if (importsLeft-- == 0) {
break;
}
const reader = new FileReader();
reader.onload = function (e) {
try {
const stringRows = e.target.result.split("\n");

const header = stringRows[0].split(",");
if (header.length != 2 || header[0] != "freq" || header[1] != "value") {
throw new SyntaxError("Wrong spectrum CSV data format");
}

stringRows.shift();
const spectrumData = stringRows.map( function(row) {
const data = row.split(",");
return {
freq: parseFloat(data[0]),
value: parseFloat(data[1]),
};
});

GraphSpectrumPlot.addImportedSpectrumData(spectrumData, file.name);
} catch (e) {
alert('Spectrum data import error: ' + e.message);
return;
}
};
this.removeImportedSpectrums = function() {
GraphSpectrumPlot.removeImportedCurves();
};

reader.readAsText(file);
this.getExportedFileName = function() {
let fileName = $(".log-filename").text().split(".")[0];
switch (userSettings.spectrumType) {
case SPECTRUM_TYPE.FREQUENCY:
fileName = fileName + "_sp";
break;
case SPECTRUM_TYPE.POWER_SPECTRAL_DENSITY:
fileName = fileName + "_psd";
break;
}
return fileName;
};

} catch (e) {
console.error(`Failed to create analyser... error: ${e}`);
}

this.clearImportedSpectrums = function() {
GraphSpectrumPlot.clearImportedSpectrums();
};
}
Loading