summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt53
-rw-r--r--ImageViewer.qml121
-rw-r--r--Main.qml307
-rw-r--r--QmlCustomPlot.cpp335
-rw-r--r--QmlCustomPlot.h72
-rw-r--r--main.cpp150
-rw-r--r--request.js29
8 files changed, 1069 insertions, 0 deletions
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 <QDebug>
+
+#include <qcustomplot.h>
+
+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<QVector2D> points)
+{
+ QVector<double> x; //(qsizetype(points.count()));
+ QVector<double> 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<QLineF> 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<Points2D> 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<double> x; //(qsizetype(contourToAdd.count()));
+ QVector<double> 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 <QQuickPaintedItem>
+#include <qcustomplot.h>
+
+//class QCustomPlot;
+//class QCPAbstractPlottable;
+//class QCPItemPixmap;
+//class QCPGraph;
+using Points2D = QVector<QVector2D>;
+
+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<QVector2D> points);
+ void setLines(const QVector<QLineF> lines);
+ void setContours(const QVector<Points2D> 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<QCPGraph *> m_contourGraphs;
+
+ QVector<QCPItemLine *> 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 <QApplication>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QtConcurrent/QtConcurrentRun>
+#include <QTimer>
+
+#include "QmlCustomPlot.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+
+ qmlRegisterType<QmlCustomPlot>("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<qreal> x_profile;
+ QVector<qreal> y_profile;
+ QVector<qreal> 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<qreal> 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();
+ }
+}