From ce03d5bff5ca0c06ac884628c8ef65b902de669f Mon Sep 17 00:00:00 2001 From: Nikita Kostovsky Date: Sat, 9 Nov 2024 17:28:58 +0100 Subject: Initial commit --- .gitignore | 2 + CMakeLists.txt | 53 +++++++++ ImageViewer.qml | 121 ++++++++++++++++++++ Main.qml | 307 +++++++++++++++++++++++++++++++++++++++++++++++++ QmlCustomPlot.cpp | 335 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ QmlCustomPlot.h | 72 ++++++++++++ main.cpp | 150 ++++++++++++++++++++++++ request.js | 29 +++++ 8 files changed, 1069 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 ImageViewer.qml create mode 100644 Main.qml create mode 100644 QmlCustomPlot.cpp create mode 100644 QmlCustomPlot.h create mode 100644 main.cpp create mode 100644 request.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f6596f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.user* +build* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..61c0414 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.16) + +project(eurydice VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS + Widgets + Quick) +# find_package(qcustomplot REQUIRED) + +qt_standard_project_setup(REQUIRES 6.5) + +qt_add_executable(appeurydice + main.cpp + QmlCustomPlot.h + QmlCustomPlot.cpp +) + +qt_add_qml_module(appeurydice + URI eurydice + VERSION 1.0 + QML_FILES + Main.qml + QML_FILES request.js + QML_FILES ImageViewer.qml +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(appeurydice PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appeurydice + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(appeurydice + PRIVATE + Qt6::Widgets + Qt6::Quick + qcustomplot-qt6 +) +target_compile_definitions(app${PROJECT_NAME} PRIVATE QCUSTOMPLOT_USE_LIBRARY) + +include(GNUInstallDirs) +install(TARGETS appeurydice + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/ImageViewer.qml b/ImageViewer.qml new file mode 100644 index 0000000..30a28e2 --- /dev/null +++ b/ImageViewer.qml @@ -0,0 +1,121 @@ +import QtQuick +import QtQuick.Controls + +Page { + id: root + + implicitWidth: 550 + implicitHeight: 300 + + contentItem: Control { + id: container + + property real iscale: 1 + property size fitSize: { + const ss = image.sourceSize; + const ratio = ss.width / ss.height; + return ratio < width/height ? Qt.size(height * ratio, height) : Qt.size(width, width / ratio); + } + + contentItem: Flickable { + id: flickable + + clip: true + + leftMargin: Math.max(0, width - contentWidth) / 2 // Centering the content + topMargin: Math.max(0, height - contentHeight) / 2 + + contentWidth: container.fitSize.width + contentHeight: container.fitSize.height + + rebound: Transition {} + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: ScrollBar {} + ScrollBar.horizontal: ScrollBar {} + + + Image { + id: image + + cache: false + smooth: false + asynchronous: true + retainWhileLoading: true + + source: apiRoot + "/sensor/image" + + width: flickable.contentWidth + height: flickable.contentHeight + + function updateImage() { + if (image.source == apiRoot + "/sensor/image") + image.source = apiRoot + "/sensor/image2" + else + image.source = apiRoot + "/sensor/image" + + // console.log("\nflickImg.width: ", flickImg.width, + // "\nflickImg.height: ", flickImg.height, + // "\nimg.width: ", img.width, + // "\nimg.width: ", img.height, + // "\nimg.width: ", img.scale, + // "\nimg.imgRatio: ", img.imgRatio, + // "\n----------------------------------------" + // ); + } + + Shortcut { + sequence: "ctrl+m" + onActivated: smooth = !smooth + } + + onStatusChanged: { + // console.log(image.source) + if (status == Image.Ready) { + updateImage(); + + ++imageFpsTimer.fpsCounter; + } + } + + MouseArea { + hoverEnabled: true + anchors.fill: parent + onWheel: function(e) { + const { width, height } = container.fitSize; + const mousePos = Qt.point(mouseX, mouseY); + + // container.iscale += e.angleDelta.y / 120; + + if (e.angleDelta.y < 0) { + container.iscale /= 1.2 + } else { + container.iscale *= 1.2 + } + + flickable.resizeContent(width * container.iscale, height * container.iscale, mousePos); + flickable.returnToBounds(); + } + } + + Timer { + id: imageFpsTimer + + interval: 1000 + repeat: true + running: true + + property int fpsCounter: 0 + + onTriggered: { + fpsLabel.text = qsTr("Fps") + ": " + fpsCounter + fpsCounter = 0 + + image.updateImage() + } + } + } + } + + background: Rectangle { color: "#121314" } + } +} diff --git a/Main.qml b/Main.qml new file mode 100644 index 0000000..a5415c2 --- /dev/null +++ b/Main.qml @@ -0,0 +1,307 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material +import QtQuick.Layouts +import QmlCustomPlot 1.0 + +import "request.js" as XHR + +ApplicationWindow { + id: mainWindowRoot + + width: 640 + height: 480 + visible: true + // visibility: Window.Maximized + visibility: ApplicationWindow.Maximized + + title: qsTr("Hello World") + + // // pistache + // // readonly property string apiRoot: "http://rpi5:8080/v1" + // // qhttpserver + readonly property string apiRoot: "http://rpi5:8081/v1" + + Shortcut { + sequence: "ctrl+q" + onActivated: mainWindowRoot.close() + } + + Material.theme: Material.Dark + Material.accent: Material.Indigo + + function requestExposureTime(type, uri) { + console.log("request url: ", apiRoot + uri); + + XHR.sendRequest("GET", apiRoot + uri, function(response) { + + if (response.status != "200") { + console.log("response status: ", response.status); + return; + } + + let isPlainText = response.contentType.length === 0 + + if (!isPlainText) { + console.log("invalid response: ", response); + return; + } + + exposureTimeSpinBox.value = parseInt(response.content) + }); + } + + function readParams() { + var url = apiRoot + "/sensor/params" + console.log("readParams:", url); + + XHR.sendRequest("GET", url, function(response) { + + if (response.status != "200") { + console.log("response status: ", response.status); + return; + } + + let isPlainText = response.contentType.length === 0 + + if (!isPlainText) { + console.log("invalid response: ", response); + return; + } + + var json = JSON.parse(response.content); + + console.log("readParams result:", json) + + if (exposureTimeSpinBox.value != json.exposureTime) { + console.log("update exposureTimeSpinBox to", json.exposureTime) + exposureTimeSpinBox.value = json.exposureTime; + } + + if (laserLevelSpinBox.value != json.laserLevel) { + console.log("update laserLevelSpinBox to", json.laserLevel) + laserLevelSpinBox.value = json.laserLevel; + } + + if (enableAutoExposureCheckbox.checked != (json.aeEnable == "true")) { + console.log("update enableAutoExposureCheckbox to", json.aeEnable) + enableAutoExposureCheckbox.checked = json.aeEnable == "true"; + } + }); + } + + Component.onCompleted: readParams() + + function writeParams() { + var url = apiRoot + "/sensor/params"; + console.log("writeParams:", url); + + var json = new Object(); + json["aeEnable"] = enableAutoExposureCheckbox.checked; + json["exposureTime"] = exposureTimeSpinBox.value; + json["laserLevel"] = laserLevelSpinBox.value; + console.log(JSON.stringify(json)); + + XHR.sendRequest("POST", url, function(response) { + + if (response.status != "200") { + console.log("response status: ", response.status); + return; + } + + let isPlainText = response.contentType.length === 0; + + if (!isPlainText) { + console.log("invalid response: ", response); + return; + } + + console.log("writeParams result:", response.content); + + // var json = JSON.parse(response.content); + }, JSON.stringify(json)); + } + + RowLayout { + id: horizontalLayout + + anchors.fill: parent + + spacing: 0 + + Pane { + id: pane + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: 150 + + ColumnLayout { + id: columnLayout + + anchors.fill: parent + + function color_by_value(value) { + const limit = 0.02; + + if (value >= limit) { + return "red"; + } + + return "green"; + } + + Label { + id: fpsLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + color: "green" + } + + Label { + id: maxDiffLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + color: columnLayout.color_by_value(max_diff) + text: "max diff: " + max_diff.toFixed(3) + } + + Label { + id: maxDiffIdxLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + text: "max diff: " + max_diff_idx + } + + Label { + id: minDiffLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + color: columnLayout.color_by_value(-min_diff) + text: "min diff: " + min_diff.toFixed(3) + } + + Label { + id: minDiffIdxLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + text: "min diff: " + min_diff_idx + } + + Label { + id: avgDiffLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + color: columnLayout.color_by_value(avg_diff) + text: "avg diff: " + avg_diff.toFixed(3) + } + + Label { + id: medianhDiffLabel + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + color: columnLayout.color_by_value(median_diff) + text: "med. diff: " + median_diff.toFixed(3) + } + + Label { + text: qsTr("Exposure time (us):") + } + + SpinBox { + id: exposureTimeSpinBox + + Layout.fillWidth: true + + from: 10 + to: 30000 + stepSize: 100 + editable: true + + value: 200 + onValueChanged: writeParams() + } + + SpinBox { + id: laserLevelSpinBox + + Layout.fillWidth: true + + from: 500 + to: 30000 + stepSize: 250 + editable: true + + value: 1500 + onValueChanged: writeParams() + } + + CheckBox { + id: enableAutoExposureCheckbox + + text: qsTr("Auto exposure") + + onCheckedChanged: writeParams() + } + + Item { + Layout.fillHeight: true + } + } + } + + ImageViewer { + id: image + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: 200 + } + + Item { + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: 900 + Layout.minimumHeight: 600 + + Rectangle { + color: "red" + opacity: 0.1 + z: 101 + } + + QmlCustomPlot { + id: qmlPlot + + anchors.fill: parent + + Label { + anchors { + top: parent.top + topMargin: 8 * 2 + horizontalCenter: parent.horizontalCenter + } + text: qmlPlot.fps + } + + plot: myPlot + // Component.onCompleted: initCustomPlot() + } + } + } +} diff --git a/QmlCustomPlot.cpp b/QmlCustomPlot.cpp new file mode 100644 index 0000000..48ec917 --- /dev/null +++ b/QmlCustomPlot.cpp @@ -0,0 +1,335 @@ +#include "QmlCustomPlot.h" + +#include + +#include + +QmlCustomPlot::QmlCustomPlot(QQuickItem* parent) + : QQuickPaintedItem(parent) + , m_timerId(0) +{ + setFlag(QQuickItem::ItemHasContents, true); + setAcceptedMouseButtons(Qt::AllButtons); + + connect(this, &QQuickPaintedItem::widthChanged, + this, &QmlCustomPlot::updateCustomPlotSize); + connect(this, &QQuickPaintedItem::heightChanged, + this, &QmlCustomPlot::updateCustomPlotSize); + + auto t = new QTimer(this); + t->setInterval(1000); + t->setTimerType(Qt::VeryCoarseTimer); + t->setSingleShot(false); + + connect(t, &QTimer::timeout, this, [&](){ + setFps(m_fpsCounter); + m_fpsCounter = 0; + }); + + t->start(); +} + +QmlCustomPlot::~QmlCustomPlot() +{ + delete m_plot; + m_plot = nullptr; + + if(m_timerId != 0) + { + killTimer(m_timerId); + } +} + +void QmlCustomPlot::initCustomPlot() +{ + setPlot(new QCustomPlot()); +} + +void QmlCustomPlot::setPlot(QCustomPlot *plot) +{ + // m_plot = new QCustomPlot(); + m_plot = plot; + + m_plot->setBackgroundScaled(true); + m_plot->setBackgroundScaledMode(Qt::KeepAspectRatio); + + updateCustomPlotSize(); + m_plot->xAxis->setLabel("x"); + m_plot->yAxis->setLabel("y"); + m_plot->xAxis->setRange(-50, 50); + m_plot->yAxis->setRange(0, 100); + m_plot ->setInteractions(QCP::iRangeDrag | + QCP::iRangeZoom | + QCP::iSelectPlottables | + QCP::iSelectItems | + QCP::iSelectOther); + + if(m_profileGraph == nullptr) + { + m_profileGraph = m_plot->addGraph(); + + m_profileGraph->setLineStyle(QCPGraph::lsNone); + m_profileGraph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 2)); + m_profileGraph->setPen(QPen(QColor(Qt::red))); + } + + startTimer(1000 / 24); + + connect(m_plot, &QCustomPlot::afterReplot, this, &QmlCustomPlot::onCustomReplot); + + m_plot->replot(); +} + +void QmlCustomPlot::setPoints(const QVector points) +{ + QVector x; //(qsizetype(points.count())); + QVector y; //(qsizetype(points.count())); + + for(const auto & p : points) + { + // break; + if(qFuzzyIsNull(p.x()) && qFuzzyIsNull(p.y())) + { + qt_noop(); + } + + x << p.x(); + y << p.y(); + + } + + m_profileGraph->setData(x, y); + + // setLines({ + // QLineF(rand() % 100 - 50, rand() % 100, rand() % 100 - 50, rand() % 100), + // QLineF(rand() % 100 - 50, rand() % 100, rand() % 100 - 50, rand() % 100) + // }); +} + +void QmlCustomPlot::setLines(const QVector lines) +{ + while(m_lineItems.count() > lines.count()) + { + m_plot->removeItem(m_lineItems.takeLast()); + } + + m_lineItems.reserve(lines.count()); + + const QCPLineEnding lineEnding(QCPLineEnding::esNone); + + for(int i = 0; i < lines.count(); i++) + { + // const QPen linePen(QColor(Qt::green), 4); + // const QColor color(Qt::green); + // QRgb green = QRandomGenerator::generate(); + // QRgb blue = QRandomGenerator::generate(); + const QColor color { QRgb { QRandomGenerator::global()->generate() } }; + const QPen linePen(color, 4); + QCPItemLine * l; + + if(i >= m_lineItems.count()) + { + l = new QCPItemLine(m_plot); + l->setPen(linePen); + l->setHead(lineEnding); + l->setTail(lineEnding); + + m_lineItems << l; + } + else + { + l = m_lineItems[i]; + } + + const QPointF offset(0, 2); + + const auto & lineToSet = lines.at(i); + l->start->setCoords(lineToSet.p1() + offset); + l->end->setCoords(lineToSet.p2() + offset); + } +} + +void QmlCustomPlot::setContours(const QVector contours) +{ + qDebug() << "contours count" << contours.count(); + + while(m_contourGraphs.count() > contours.count()) + { + m_plot->removeGraph(m_contourGraphs.takeLast()); + } + + m_contourGraphs.reserve(contours.count()); + + for(int i = 0; i < contours.count(); i++) + { + const QColor color(rand() % 0xff, rand() % 0xff, rand() % 0xff); + const QPen contourPen(color, 4); + + QCPGraph * c; + + // create new + if(i >= m_contourGraphs.count()) + { + c = m_plot->addGraph(); + + c->setLineStyle(QCPGraph::lsNone); + c->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 2)); + c->setPen(contourPen); + + m_contourGraphs << c; + } + // use existing graph object + else + { + c = m_contourGraphs[i]; + } + + // set data + const auto & contourToAdd = contours.at(i); + + QVector x; //(qsizetype(contourToAdd.count())); + QVector y; //(qsizetype(contourToAdd.count())); + + for(const auto & p : contourToAdd) + { + x << p.x(); + y << p.y() + 5; + } + + c->setData(x, y); + } +} + +void QmlCustomPlot::setFps(int fps) +{ + m_fps = fps; + emit fpsChanged(); +} + +//void QmlCustomPlot::setPlot(QCustomPlot *plot) +//{ + +//} + +void QmlCustomPlot::paint(QPainter* painter) +{ + if(m_plot) + { + + QPixmap picture(boundingRect().size().toSize()); + QCPPainter qcpPainter(&picture); + + m_plot->toPainter(&qcpPainter); + + // m_bg->topLeft->setCoords(m_plot->xAxis->range().minRange, m_plot->yAxis->range().minRange); + // m_bg->bottomRight->setCoords(m_plot->xAxis->range().maxRange, m_plot->yAxis->range().maxRange); + // m_bg->topLeft->setCoords(0, 0); + // m_bg->bottomRight->setCoords(640, 480); + + painter->drawPixmap(QPoint(), picture); + + // qDebug() << m_plot->axisRect(); + } +} + +QCustomPlot *QmlCustomPlot::plot() const +{ + return m_plot; +} + +int QmlCustomPlot::fps() const +{ + return m_fps; +} + +void QmlCustomPlot::mousePressEvent(QMouseEvent* event) +{ + routeMouseEvents(event); +} + +void QmlCustomPlot::mouseReleaseEvent(QMouseEvent* event) +{ + routeMouseEvents(event); +} + +void QmlCustomPlot::mouseMoveEvent(QMouseEvent* event) +{ + routeMouseEvents(event); +} + +void QmlCustomPlot::mouseDoubleClickEvent(QMouseEvent* event) +{ + qDebug() << Q_FUNC_INFO; + routeMouseEvents(event); +} + +void QmlCustomPlot::wheelEvent(QWheelEvent *event) +{ + routeWheelEvents(event); +} + +void QmlCustomPlot::timerEvent(QTimerEvent *event) +{ + // static double t, U; + // U = ((double)rand() / RAND_MAX) * 5; + // m_plot->graph(0)->addData(t, U); + // qDebug() << Q_FUNC_INFO << QString("Adding dot t = %1, S = %2").arg(t).arg(U); + // t++; + m_plot->replot(); +} + +void QmlCustomPlot::graphClicked(QCPAbstractPlottable* plottable) +{ + qDebug() << Q_FUNC_INFO << QString("Clicked on graph '%1 ").arg(plottable->name()); +} + +void QmlCustomPlot::routeMouseEvents(QMouseEvent* event) +{ + if(m_plot) + { + QMouseEvent* newEvent = new QMouseEvent( + event->type(), + event->position(), + event->scenePosition(), + event->globalPosition(), + event->button(), + event->buttons(), + event->modifiers(), + event->source()); + QCoreApplication::postEvent(m_plot, newEvent); + } +} + +void QmlCustomPlot::routeWheelEvents(QWheelEvent* event) +{ + if(m_plot) + { + QWheelEvent* newEvent = new QWheelEvent( + event->position(), + event->globalPosition(), + event->pixelDelta(), + event->angleDelta(), + event->buttons(), + event->modifiers(), + event->phase(), + false); + QCoreApplication::postEvent(m_plot, newEvent); + } +} + +void QmlCustomPlot::updateCustomPlotSize() +{ + if(m_plot) + { + const QRect r(0, 0, int(width()), int(height())); + m_plot->setGeometry(r); + m_plot->setViewport(r); + } +} + +void QmlCustomPlot::onCustomReplot() +{ + // qDebug() << Q_FUNC_INFO; + m_fpsCounter++; + update(); +} diff --git a/QmlCustomPlot.h b/QmlCustomPlot.h new file mode 100644 index 0000000..c56038c --- /dev/null +++ b/QmlCustomPlot.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +//class QCustomPlot; +//class QCPAbstractPlottable; +//class QCPItemPixmap; +//class QCPGraph; +using Points2D = QVector; + +class QmlCustomPlot : public QQuickPaintedItem +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QCustomPlot* plot READ plot WRITE setPlot NOTIFY plotChanged) + Q_PROPERTY(int fps READ fps WRITE setFps NOTIFY fpsChanged) + +public: + QmlCustomPlot(QQuickItem* parent = nullptr); + ~QmlCustomPlot() Q_DECL_OVERRIDE; + + void paint(QPainter* painter) Q_DECL_OVERRIDE; + + Q_INVOKABLE void initCustomPlot(); + +public: + QCustomPlot* plot() const; + int fps() const; + +public slots: + void setPlot(QCustomPlot *plot); + void setPoints(const QVector points); + void setLines(const QVector lines); + void setContours(const QVector contours); + void setFps(int fps); + +signals: + void plotChanged(); + void fpsChanged(); + +protected: + void routeMouseEvents(QMouseEvent* event); + void routeWheelEvents(QWheelEvent* event); + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent *event) override; + + void timerEvent(QTimerEvent *event) override; + +private: + QCustomPlot* m_plot = nullptr; + int m_timerId; + QCPItemPixmap* m_bg = nullptr; + int m_fps = 0; + int m_fpsCounter = 0; + + QCPGraph * m_profileGraph = nullptr; + QVector m_contourGraphs; + + QVector m_lineItems; + +private slots: + void graphClicked(QCPAbstractPlottable * plottable); + void onCustomReplot(); + void updateCustomPlotSize(); +}; + +//Q_DECLARE_METATYPE(QCustomPlot) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..6ea0a15 --- /dev/null +++ b/main.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "QmlCustomPlot.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QQmlApplicationEngine engine; + + qmlRegisterType("QmlCustomPlot", 1, 0, "QmlCustomPlot"); + + auto plot = new QCustomPlot(); + plot->setOpenGl(true); + engine.rootContext()->setContextProperty("myPlot", plot); + engine.rootContext()->setContextProperty("max_diff", 0.0); + engine.rootContext()->setContextProperty("avg_diff", 0.0); + engine.rootContext()->setContextProperty("median_diff", 0.0); + + QVector x_profile; + QVector y_profile; + QVector prev_y_profile; + + for(int x = -640; x < 640; ++x) + { + double y = double(QRandomGenerator::global()->bounded(0, 80000)) / 100.; + + x_profile << x; + y_profile << y; + } + prev_y_profile = y_profile; + + auto graph = plot->addGraph(); + graph->setPen(QPen(QBrush(Qt::red), 2)); + graph->setLineStyle(QCPGraph::lsLine); + graph->setScatterStyle(QCPScatterStyle::ssDot); + graph->setData(x_profile, y_profile); + + QNetworkRequest request(QUrl("http://rpi5:8081/v1/pixels")); + auto manager = new QNetworkAccessManager(&app); + + QTimer pixelsAutoRestartTimer(&app); + pixelsAutoRestartTimer.setInterval(1000); + pixelsAutoRestartTimer.setSingleShot(false); + QObject::connect(&pixelsAutoRestartTimer, &QTimer::timeout, + [&](){ + manager->get(request); + }); + + QObject::connect(manager, &QNetworkAccessManager::finished, + [&](QNetworkReply *reply) { + // qDebug() << "replyFinished"; + if (reply->error()) { + qDebug() << "replyFinished:" << reply->errorString(); + return; + } + + auto jsonPixels = QJsonDocument::fromJson(reply->readAll()) + .object()["pixels"].toArray(); + + // y_profile.clear(); + + for (int i = 0; i < jsonPixels.count(); ++i) { + y_profile[i] = jsonPixels[i].toDouble(); + } + + graph->setData(x_profile, y_profile); + + QVector y_diff(y_profile.count()); + qreal max_diff { 0. }; + qreal min_diff { 1000000. }; + size_t max_diff_idx = 0; + size_t min_diff_idx = 0; + qreal avg_diff { 0. }; + + for (size_t i = 0; i < y_profile.count(); ++i) { + y_diff[i] = y_profile[i] - prev_y_profile[i]; + + if (y_diff[i] > max_diff) { + max_diff = y_diff[i]; + max_diff_idx = i; + } + + if (y_diff[i] < min_diff) { + min_diff = y_diff[i]; + min_diff_idx = i; + } + + avg_diff += fabs(y_diff[i]); + } + + avg_diff /= y_diff.size(); + + // for (size_t i = 0; i < y_profile.size(); ++i) { + // if + // } + + // // const auto max_diff = std::max_element(y_diff.begin(), y_diff.end()); + // const auto avg_diff = std::accumulate(y_diff.begin(), y_diff.end(), 0.0) / y_diff.size(); + std::sort(y_diff.begin(), y_diff.end()); + const auto median_diff = y_diff[y_diff.size() * 3 / 4]; + + engine.rootContext()->setContextProperty("max_diff", max_diff); + engine.rootContext()->setContextProperty("max_diff_idx", int(max_diff_idx)); + engine.rootContext()->setContextProperty("min_diff", min_diff); + engine.rootContext()->setContextProperty("min_diff_idx", int(min_diff_idx)); + engine.rootContext()->setContextProperty("avg_diff", avg_diff); + engine.rootContext()->setContextProperty("median_diff", median_diff); + + manager->get(request); + pixelsAutoRestartTimer.start(); + + prev_y_profile = y_profile; + }); + + manager->get(request); + pixelsAutoRestartTimer.start(); + + // auto pixelsRequestFuture = QtConcurrent::run([&](){ + // QThread::sleep(1); + // // x_profile.clear(); + // y_profile.clear(); + + // for(int x = -640; x < 640; ++x) + // { + // double y = double(QRandomGenerator::global()->bounded(0, 80000)) / 100.; + + // // x_profile << x; + // y_profile << y; + // } + + // graph->setData(x_profile, y_profile); + // }); + + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &app, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + engine.loadFromModule("eurydice", "Main"); + + return app.exec(); +} diff --git a/request.js b/request.js new file mode 100644 index 0000000..f8998d3 --- /dev/null +++ b/request.js @@ -0,0 +1,29 @@ +function sendRequest(method, url, callback, data) +{ + let request = new XMLHttpRequest(); + + request.onreadystatechange = function() { + if (request.readyState === XMLHttpRequest.DONE) { + let response = { + status : request.status, + headers : request.getAllResponseHeaders(), + contentType : request.responseType, + content : request.response + }; + + callback(response); + } + } + + request.open(method, url); + + if (data) { + // TODO: understand these headers + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.setRequestHeader("Content-length", data.length); + request.setRequestHeader("Connection", "close"); + request.send(data); + } else { + request.send(); + } +} -- cgit v1.2.3-70-g09d2