Skip to content

Commit

Permalink
Export of spectrum analyzer data to Csv file (betaflight#779)
Browse files Browse the repository at this point in the history
* The 'Convert to Csv' button is added at the spectrum analyzer chart

* The spectrum export Worker is added

* The spectrum exporter is added

* The exportSpectrumToCSV method is added into graph_spectrum

* The action handler is addedto spectrum export button

* The spectrum export button is enabled for spectrum by frequency only

* The 4 spaces tabs are replaced to 2 space tab

* unused library link is removed

* Code issues are resolved

* Code issues are resolved

* Code style improvement

* The export buttons tooltip text is changed

Co-authored-by: Mark Haslinghuis <[email protected]>

* Code style improvement

Co-authored-by: Mark Haslinghuis <[email protected]>

* Code style improvement

Co-authored-by: Mark Haslinghuis <[email protected]>

* Code style improvement

* Update index.html

---------

Co-authored-by: Mark Haslinghuis <[email protected]>
  • Loading branch information
demvlad and haslinghuis authored Oct 22, 2024
1 parent ec62bca commit 3269938
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 61 deletions.
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,9 @@ <h4>Workspace</h4>
<option value="5">Auto</option>
</select>
</div>

<div id="spectrumExport" data-toggle="tooltip" title="Export spectrum to CSV">
<button id="btn-spectrum-export" type="button">Export to CSV</button>
</div>
<div id="analyserResize" class="btn-nobg view-analyser-fullscreen" data-toggle="tooltip" title="Zoom Analyser Window">
<span class="glyphicon glyphicon-resize-full"></span>
<span class="glyphicon glyphicon-resize-small"></span>
Expand Down
102 changes: 51 additions & 51 deletions public/js/webworkers/csv-export-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,59 @@ importScripts("/js/lodash.min.js");

onmessage = function(event) {

/**
* Converts `null` and other empty non-numeric values to empty string.
*
* @param {object} value is not a number
* @returns {string}
*/
function normalizeEmpty(value) {
return !!value ? value : "";
}
/**
* Converts `null` and other empty non-numeric values to empty string.
*
* @param {object} value is not a number
* @returns {string}
*/
function normalizeEmpty(value) {
return !!value ? value : "";
}

/**
* @param {array} columns
* @returns {string}
*/
function joinColumns(columns) {
return _(columns)
.map(value =>
_.isNumber(value)
? value
: stringDelim + normalizeEmpty(value) + stringDelim)
.join(opts.columnDelimiter);
}
/**
* @param {array} columns
* @returns {string}
*/
function joinColumns(columns) {
return _(columns)
.map(value =>
_.isNumber(value)
? value
: stringDelim + normalizeEmpty(value) + stringDelim)
.join(opts.columnDelimiter);
}

/**
* Converts `null` entries in columns and other empty non-numeric values to NaN value string.
*
* @param {array} columns
* @returns {string}
*/
function joinColumnValues(columns) {
return _(columns)
.map(value =>
(_.isNumber(value) || _.value)
? value
: "NaN")
.join(opts.columnDelimiter);
}
/**
* Converts `null` entries in columns and other empty non-numeric values to NaN value string.
*
* @param {array} columns
* @returns {string}
*/
function joinColumnValues(columns) {
return _(columns)
.map(value =>
(_.isNumber(value) || _.value)
? value
: "NaN")
.join(opts.columnDelimiter);
}

let opts = event.data.opts,
stringDelim = opts.quoteStrings
? opts.stringDelimiter
: "",
mainFields = _([joinColumns(event.data.fieldNames)])
.concat(_(event.data.frames)
.flatten()
.map(row => joinColumnValues(row))
.value())
.join("\n"),
headers = _(event.data.sysConfig)
.map((value, key) => joinColumns([key, value]))
.join("\n"),
result = headers + "\n" + mainFields;
let opts = event.data.opts,
stringDelim = opts.quoteStrings
? opts.stringDelimiter
: "",
mainFields = _([joinColumns(event.data.fieldNames)])
.concat(_(event.data.frames)
.flatten()
.map(row => joinColumnValues(row))
.value())
.join("\n"),
headers = _(event.data.sysConfig)
.map((value, key) => joinColumns([key, value]))
.join("\n"),
result = headers + "\n" + mainFields;

postMessage(result);

postMessage(result);

};
14 changes: 14 additions & 0 deletions public/js/webworkers/spectrum-export-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
onmessage = function(event) {
const columnDelimiter = event.data.opts.columnDelimiter;
const fftOutput = event.data.fftOutput;
const spectrumDataLength = fftOutput.length / 2;
const frequencyStep = 0.5 * event.data.blackBoxRate / spectrumDataLength;

let outText = "freq" + columnDelimiter + "value" + "\n";
for (let index = 0; index < spectrumDataLength; index += 10) {
const frequency = frequencyStep * index;
outText += frequency.toString() + columnDelimiter + fftOutput[index].toString() + "\n";
}

postMessage(outText);
};
26 changes: 22 additions & 4 deletions src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -629,10 +629,28 @@ html.has-analyser-fullscreen.has-analyser
color: black;
}

.analyser #analyserResize:hover {
color: white;
cursor: pointer;
animation: ease-in 500ms;
.analyser:hover .non-shift #spectrumExport {
opacity: 1;
height: auto;
transition: opacity 500ms ease-in;
}

.analyser #spectrumExport {
height: 0;
width: 200px;
overflow: hidden;
opacity: 0;
left: 270px;
float: left;
z-index: 9;
position: absolute;
font-size: 9px;
}

.analyser #spectrumExport select {
border-radius: 3px;
padding: 0px 5px;
color: black;
}

.analyser input#analyserZoomX {
Expand Down
11 changes: 9 additions & 2 deletions src/graph_spectrum.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SPECTRUM_OVERDRAW_TYPE,
} from "./graph_spectrum_plot";
import { PrefStorage } from "./pref_storage";
import { SpectrumExporter } from "./spectrum-exporter";

export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
const ANALYSER_LARGE_LEFT_MARGIN = 10,
Expand Down Expand Up @@ -95,7 +96,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
left: `${newSize.width - 20}px`,
});
$("#analyserResize", parentElem).css({
left: `${newSize.width - 28}px`,
left: `${newSize.width - 20}px`,
});
};

Expand Down Expand Up @@ -201,7 +202,7 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {

spectrumTypeElem
.change(function () {
let optionSelected = parseInt(spectrumTypeElem.val(), 10);
const optionSelected = parseInt(spectrumTypeElem.val(), 10);

if (optionSelected != userSettings.spectrumType) {
userSettings.spectrumType = optionSelected;
Expand All @@ -224,6 +225,8 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
"onlyFullScreenException",
pidErrorVsSetpointSelected
);

$("#btn-spectrum-export").attr("disabled", optionSelected != 0);
})
.change();

Expand Down Expand Up @@ -282,6 +285,10 @@ export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) {
prefs.set("userSettings", data);
});
}

this.exportSpectrumToCSV = function(onSuccess, options) {
SpectrumExporter(fftData, options).dump(onSuccess);
};
} catch (e) {
console.log(`Failed to create analyser... error:${e}`);
}
Expand Down
22 changes: 19 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { throttle } from "throttle-debounce";
import { MapGrapher } from "./graph_map.js";
import { FlightLogGrapher } from "./grapher.js";
import { FlightLogVideoRenderer } from "./flightlog_video_renderer.js";
import { VideoExportDialog } from "./video_export_dialog.js";
import { UserSettingsDialog } from "./user_settings_dialog.js";
import { GraphConfigurationDialog } from "./graph_config_dialog.js";
import { HeaderDialog } from "./header_dialog.js";
Expand Down Expand Up @@ -1144,17 +1143,27 @@ function BlackboxLogViewer() {
"csv",
"text/csv",
file,
performance.now()
performance.now(),
);
CsvExporter(flightLog, options).dump(onSuccess);
}

function exportSpectrumToCsv(file, options = {}) {
const onSuccess = createExportCallback(
"csv",
"text/csv",
file,
performance.now(),
);
graph.getAnalyser().exportSpectrumToCSV(onSuccess, options);
}

function exportGpx(file) {
const onSuccess = createExportCallback(
"gpx",
"GPX File",
file,
performance.now()
performance.now(),
);
GpxExporter(flightLog).dump(onSuccess);
}
Expand Down Expand Up @@ -1720,6 +1729,13 @@ function BlackboxLogViewer() {
exportCsv();
e.preventDefault();
});

$("#btn-spectrum-export").click(function (e) {
setGraphState(GRAPH_STATE_PAUSED);
exportSpectrumToCsv("bf_spectrum");
e.preventDefault();
});

$(".btn-gpx-export").click(function (e) {
setGraphState(GRAPH_STATE_PAUSED);
exportGpx();
Expand Down
42 changes: 42 additions & 0 deletions src/spectrum-exporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @typedef {object} ExportOptions
* @property {string} columnDelimiter
* @property {string} stringDelimiter
* @property {boolean} quoteStrings
*/

/**
* @constructor
* @param {object} fftOutput
* @param {ExportOptions} [opts={}]
*/
export function SpectrumExporter(fftData, opts = {}) {
opts = _.merge(
{
columnDelimiter: ",",
quoteStrings: true,
},
opts,
);

/**
* @param {function} success is a callback triggered when export is done
*/
function dump(success) {
const worker = new Worker("/js/webworkers/spectrum-export-worker.js");

worker.onmessage = (event) => {
success(event.data);
worker.terminate();
};

worker.postMessage({fftOutput: fftData.fftOutput,
blackBoxRate: fftData.blackBoxRate,
opts: opts});
}

// exposed functions
return {
dump: dump,
};
}

0 comments on commit 3269938

Please sign in to comment.