From e7230714f32688e72ac018372a2cba61659d1c4e Mon Sep 17 00:00:00 2001 From: "yorick.geoffre" Date: Mon, 12 Jun 2023 17:02:19 +0200 Subject: [PATCH] huge advancements (working themes, workin on noise for demo) --- colorattributer.cpp | 37 +++++++++++++++++ colorattributer.h | 23 +++++++++++ command.h | 3 +- datapollcommand.h | 4 +- perlin.cpp | 6 +++ perlin.h | 52 +++++++++++++++++++++++ pollingtimer.cpp | 42 ++++++++++++------- pollingtimer.h | 12 ++++-- qml/pages/FirstPage.qml | 53 ++++++++++++------------ src/MLX90640_API.cpp | 2 +- src/MLX90640_API.h | 23 +++++++---- src/i2cif.cpp | 20 +++++---- src/thermi2c.cpp | 91 ++++++++++++++++++++++++++++++----------- thermaldatarenderer.cpp | 21 +++++++++- thermaldatarenderer.h | 67 ++++++++++++++++++++++++++++-- thermi2c.pro | 4 ++ thermi2c.pro.user | 5 ++- 17 files changed, 368 insertions(+), 97 deletions(-) create mode 100644 colorattributer.cpp create mode 100644 colorattributer.h create mode 100644 perlin.cpp create mode 100644 perlin.h diff --git a/colorattributer.cpp b/colorattributer.cpp new file mode 100644 index 0000000..ae29f02 --- /dev/null +++ b/colorattributer.cpp @@ -0,0 +1,37 @@ +#include "colorattributer.h" + +Q_INVOKABLE QColor ColorAttributer::encode(const float& value) { + if(myColors.size() < 2) + return QColor(); + + float normalizedValue = (value - minValue) / (maxValue - minValue); + + auto upper = myColors.lower_bound(normalizedValue); + auto lower = upper; + if(upper == myColors.begin()) { + return upper->second; + } + else if(upper == myColors.end()) { + --lower; + return lower->second; + } + else { + --lower; + + float fraction = (normalizedValue - lower->first) / (upper->first - lower->first); + + return QColor::fromRgbF( + lower->second.redF() + fraction * (upper->second.redF() - lower->second.redF()), + lower->second.greenF() + fraction * (upper->second.greenF() - lower->second.greenF()), + lower->second.blueF() + fraction * (upper->second.blueF() - lower->second.blueF()) + ); + } +} + +void ColorAttributer::addColor(const float& value, const QColor& color) { + if (value >= 0.0 && value <= 1.0) { + myColors[value] = color; + } else { + return; //invalid color mapping (0->1) + } +} diff --git a/colorattributer.h b/colorattributer.h new file mode 100644 index 0000000..786687b --- /dev/null +++ b/colorattributer.h @@ -0,0 +1,23 @@ +#ifndef COLORATTRIBUTER_H +#define COLORATTRIBUTER_H + +#include +#include +#include +#include + +class ColorAttributer : public QObject +{ + Q_OBJECT +protected: + std::map myColors; + float minValue, maxValue; + QString myName; +public: + ColorAttributer(QString name, const float& minValue = -3000000.0f, const float& maxValue = -1000000.0f) : minValue(minValue), maxValue(maxValue), myName(name) {} + Q_INVOKABLE inline QString getName() const {return myName;} + Q_INVOKABLE void addColor(const float& value, const QColor& color); + Q_INVOKABLE QColor encode(const float& value); +}; + +#endif // COLORATTRIBUTER_H diff --git a/command.h b/command.h index bfd7e70..268c35a 100644 --- a/command.h +++ b/command.h @@ -3,8 +3,9 @@ #include -class Command +class Command : public QObject { + Q_OBJECT public: Command(); virtual void exec() = 0; diff --git a/datapollcommand.h b/datapollcommand.h index b5cc84d..bc6c34c 100644 --- a/datapollcommand.h +++ b/datapollcommand.h @@ -6,12 +6,12 @@ #include "command.h" #include "src/MLX90640_API.h" -class DataPollCommand : public QObject, public Command +class DtPollCommand : public Command { protected: MLX90640* _thermal; public: - DataPollCommand(MLX90640* thermal) : _thermal(thermal){} + DtPollCommand(MLX90640* thermal) : _thermal(thermal){} void exec() override {_thermal->getData();} }; diff --git a/perlin.cpp b/perlin.cpp new file mode 100644 index 0000000..a93a6f0 --- /dev/null +++ b/perlin.cpp @@ -0,0 +1,6 @@ +#include "perlin.h" + +Perlin::Perlin() +{ + +} diff --git a/perlin.h b/perlin.h new file mode 100644 index 0000000..5f92bbc --- /dev/null +++ b/perlin.h @@ -0,0 +1,52 @@ +#ifndef PERLIN_H +#define PERLIN_H + +#include +#include +#include + +class Perlin +{ +public: + Perlin(); + float interpolate(float a, float b, float x) { + float ft = x * 3.1415927; + float f = (1 - cos(ft)) * 0.5; + return a * (1 - f) + b * f; + } + float noise(int x, int y, float t, int seed) { + int n = x + y * 57 + int(t) * 131 + seed; + n = (n << 13) ^ n; + return (1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0); + } + + float perlinNoise(float x, float y, float t, int seed) { + int wholePartX = (int)x; + float fractionalPartX = x - wholePartX; + + int wholePartY = (int)y; + float fractionalPartY = y - wholePartY; + + float v1 = noise(wholePartX, wholePartY, t, seed); + float v2 = noise(wholePartX + 1, wholePartY, t, seed); + float v3 = noise(wholePartX, wholePartY + 1, t, seed); + float v4 = noise(wholePartX + 1, wholePartY + 1, t, seed); + + float i1 = interpolate(v1, v2, fractionalPartX); + float i2 = interpolate(v3, v4, fractionalPartX); + + return interpolate(i1, i2, fractionalPartY); + } + void fillWithPerlinNoise(QVector& vector, int width, int height, int seed) { + float t = 0.0f; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + float noise = perlinNoise(x / (float)width, y / (float)height, t, seed); + vector[(y * width + x)] = noise; + } + t += 0.01f; // flow speed variable + } + } +}; + +#endif // PERLIN_H diff --git a/pollingtimer.cpp b/pollingtimer.cpp index b3527fc..de78e3e 100644 --- a/pollingtimer.cpp +++ b/pollingtimer.cpp @@ -1,24 +1,36 @@ #include "pollingtimer.h" #include -void PollingTimer::start(){ - shouldRun = true; -} +#include "pollingtimer.h" +#include -void PollingTimer::stop(){ - shouldRun = false; +void PollingTimer::doLoop() { + while (shouldRun) { + auto start = std::chrono::high_resolution_clock::now(); + _c->exec(); + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start).count(); + if (elapsed < this->timeout) { + std::this_thread::sleep_for(std::chrono::milliseconds(this->timeout - elapsed)); + } + fprintf(stderr, "time elapsed: %lld",elapsed); + } } -void PollingTimer::doLoop(){ - std::chrono::steady_clock::time_point begin; - std::chrono::steady_clock::time_point end; +void PollingTimer::start() { + if (myThread != nullptr) { + stop(); + } + shouldRun = true; + myThread = new std::thread([this]() { this->doLoop(); }); +} - while(shouldRun){ - begin = std::chrono::steady_clock::now(); - _c->exec(); - end = std::chrono::steady_clock::now(); - std::this_thread::sleep_for( - std::chrono::microseconds((this->timeout) - - std::chrono::duration_cast(end - begin).count())); +void PollingTimer::stop() { + if (myThread != nullptr) { + shouldRun = false; + myThread->join(); + delete myThread; + myThread = nullptr; } } + diff --git a/pollingtimer.h b/pollingtimer.h index e0ebbcf..88ce5a5 100644 --- a/pollingtimer.h +++ b/pollingtimer.h @@ -4,23 +4,27 @@ #include #include #include +#include #include "command.h" -class PollingTimer +class PollingTimer : public QObject { + Q_OBJECT protected: std::atomic shouldRun; - std::thread* myThread; + std::thread* myThread = nullptr; Command* _c; unsigned int timeout; void doLoop(); public: + QThread workerThread; PollingTimer(Command* c) : shouldRun(false), _c(c){} - void start(); - void stop(); + Q_INVOKABLE void start(); + Q_INVOKABLE void stop(); + Q_INVOKABLE inline void setDelay(const unsigned int& timeout) {this->timeout = timeout;} }; #endif // POLLINGTIMER_H diff --git a/qml/pages/FirstPage.qml b/qml/pages/FirstPage.qml index c2a6720..8d01400 100644 --- a/qml/pages/FirstPage.qml +++ b/qml/pages/FirstPage.qml @@ -19,17 +19,14 @@ Page { onClicked: { if(mainPage.isInitialized){ mainPage.isInitialized = false; - pollingTimer.stop(); + polling_timer.stop(); }else{ mainPage.isInitialized = true; mlx90640.fuzzyInit(); - pollingTimer.start(); + polling_timer.start(); } } - TextArea{ - text: mlx90640.getImageVectAt(100) - } } Grid { @@ -42,36 +39,38 @@ Page { id: rectangle width: 15 height: 15 - property int rowIndex: Math.floor(index / 32) - property int columnIndex: index % 32 - property int invertedIndex: (23 - rowIndex) * 32 + columnIndex - property real value: mlx90640.getImageVectAt(invertedIndex) - property real minValue: -3000000.000000 - property real maxValue: -1000000.000000 - property real hue: 0.67 - ((value - minValue) / (maxValue - minValue) * 0.67) - property color sensorColor: Qt.hsva(hue, 1, 1, 1) - color: sensorColor + color: thermalRenderer.getDataForIndex(index) } } - Connections { - target: mlx90640 - onDataReady: { - // force the repeater to update its items - repeater.model = []; - repeater.model = 768; + target: thermalRenderer + onDataChanged: { + for (var i = 0; i < repeater.count; i++) { + var item = repeater.itemAt(i); + if (item) { + item.color = thermalRenderer.getDataForIndex(i); + } + } } } + } - Timer { - id: pollingTimer - interval: 70 - repeat: true - onTriggered: { - mlx90640.getData(); + ComboBox{ + id: themes + anchors.horizontalCenter: parent.horizontalCenter + label: "Theme: " + currentIndex: 2 + menu: ContextMenu + { + MenuItem { text: "hotiron" } + MenuItem { text: "rainbow" } + MenuItem { text: "gray" } } - } + onCurrentIndexChanged: { + thermalRenderer.setActiveAttributer(themes.currentIndex); + } + } } } } diff --git a/src/MLX90640_API.cpp b/src/MLX90640_API.cpp index 0bed6e6..1a10849 100644 --- a/src/MLX90640_API.cpp +++ b/src/MLX90640_API.cpp @@ -14,8 +14,8 @@ * limitations under the License. * */ -#include "mlx90640_I2C_Driver.h" #include "mlx90640_API.h" +#include "MLX90640_I2C_Driver.h" #include int MLX90640::mlx90640_DumpEE(uint8_t slaveAddr, uint16_t *eeData) diff --git a/src/MLX90640_API.h b/src/MLX90640_API.h index df5ac12..7a34172 100644 --- a/src/MLX90640_API.h +++ b/src/MLX90640_API.h @@ -22,6 +22,9 @@ #include #include #include + +#include "perlin.h" + #define mlx90640_DEV_ADDR 0x33 #define mlx90640_NO_ERROR 0 @@ -142,6 +145,7 @@ public: uint16_t outlierPixels[5]; } paramsMLX90640; + unsigned char slaveAddress = 0x33; paramsMLX90640 mlx90640; @@ -193,19 +197,21 @@ public: uint16_t mlx90640Frame[834]; float mlx90640Image[768]; int status; - //fprintf(stderr, "trigger measurement...\n"); //status = mlx90640_TriggerMeasurement (0x33); - fprintf(stderr, "sync frame...\n"); status = mlx90640_SynchFrame(0x33); status = mlx90640_GetFrameData (0x33, mlx90640Frame); - mlx90640_GetImage(mlx90640Frame, &mlx90640, mlx90640Image); - for (int i = 0; i < 767; ++i) { - imageVect[i] = mlx90640Image[i]; - fprintf(stderr, "ImageVect[%d]: %f\n", i, imageVect[i]); + if (status == 0) { + mlx90640_GetImage(mlx90640Frame, &mlx90640, mlx90640Image); + for (int i = 0; i < 767; ++i) { + imageVect[i] = mlx90640Image[i]; + } + } else { + Perlin perlin; + perlin.fillWithPerlinNoise(imageVect, 32, 24, 0xBEEF); } - emit dataReady(); + emit dataReady(imageVect); } Q_INVOKABLE float getImageVectAt(int index) { @@ -241,8 +247,9 @@ protected: int IsPixelBad(uint16_t pixel,paramsMLX90640 *params); int ValidateFrameData(uint16_t *frameData); int ValidateAuxData(uint16_t *auxData); + Perlin perlin; signals: - void dataReady(); + void dataReady(QVector data); void initializedChanged(); }; #endif diff --git a/src/i2cif.cpp b/src/i2cif.cpp index 90e2bbc..734f3a3 100644 --- a/src/i2cif.cpp +++ b/src/i2cif.cpp @@ -27,11 +27,13 @@ I2cif::I2cif(QObject *parent) : { m_probingResult = QStringList(); m_readResult = "Nothing yet"; + tohVddSet("on"); emit tohVddStatusChanged(); } I2cif::~I2cif() { + tohVddSet("off"); } @@ -112,7 +114,7 @@ Q_INVOKABLE void I2cif::i2cWrite(const uint8_t &slaveAddr, const uint16_t &write const char* devNameChar = "/dev/i2c-1"; - fprintf(stderr, "writing to address %02x: ", slaveAddr); + //fprintf(stderr, "writing to address %02x: ", slaveAddr); if ((file = open (devNameChar, O_RDWR)) < 0) { @@ -134,12 +136,12 @@ Q_INVOKABLE void I2cif::i2cWrite(const uint8_t &slaveAddr, const uint16_t &write buf[2] = (data >> 8) & 0xFF; // High byte buf[3] = data & 0xFF; // Low byte - fprintf(stderr, "Data to be written: "); + /*fprintf(stderr, "Data to be written: "); for (int i = 0; i < 4; i++) { fprintf(stderr, "%02x ", buf[i]); } fprintf(stderr, "\n"); - +*/ /* write the data */ if (write(file, buf, 4) != 4) { @@ -151,7 +153,7 @@ Q_INVOKABLE void I2cif::i2cWrite(const uint8_t &slaveAddr, const uint16_t &write close(file); - fprintf(stderr,"write ok\n"); + //fprintf(stderr,"write ok\n"); emit i2cWriteOk(); } @@ -190,7 +192,7 @@ void I2cif::i2cRead(const uint8_t &slaveAddr, const uint16_t &startAddress, cons i2c_data.msgs[1].addr = slaveAddr; i2c_data.msgs[1].flags = I2C_M_RD; - constexpr uint16_t ChunkSize = 32; // 64 bytes (32 uint16_t) + constexpr uint16_t ChunkSize = 32; // 192 bytes (96 uint16_t) uint16_t chunks = nMemAddressRead / ChunkSize; uint16_t remainder = nMemAddressRead % ChunkSize; @@ -216,21 +218,21 @@ void I2cif::i2cRead(const uint8_t &slaveAddr, const uint16_t &startAddress, cons return; } - fprintf(stderr, "read "); + //fprintf(stderr, "read "); for (uint16_t i = 0; i < length; ++i) { uint16_t index = chunk * ChunkSize + i; data[index] = ((buf[2 * index] << 8) == 0xFF ? 0x00 : (buf[2 * index] << 8)) | buf[2 * index + 1]; m_readResult = m_readResult + conv.toHex(data[index], 4) + " "; - fprintf(stderr, "%04x ", data[index]); + //fprintf(stderr, "%04x ", data[index]); } - fprintf(stderr, "\n"); + //fprintf(stderr, "\n"); // Wait 0.4ms before next chunk usleep(400); addr += ChunkSize; } - fprintf(stderr, "\n"); + //fprintf(stderr, "\n"); delete[] buf; emit i2cReadResultChanged(); close(file); diff --git a/src/thermi2c.cpp b/src/thermi2c.cpp index 1978f84..f71f06d 100644 --- a/src/thermi2c.cpp +++ b/src/thermi2c.cpp @@ -1,31 +1,76 @@ - #ifdef QT_QML_DEBUG - #include "conv.h" - #include "i2cif.h" - #include "mlx90640_API.h" +#include "i2cif.h" +#include "mlx90640_API.h" +#include "ColorAttributer.h" +#include "ThermalDataRenderer.h" +#include "DataPollCommand.h" +#include "PollingTimer.h" +#include "conv.h" +#include "datapollcommand.h" - #include - #endif +#include - #include +#include +#include - int main(int argc, char *argv[]) - { - qmlRegisterType("harbour.i2ctool.I2cif", 1, 0, "I2cif"); - qmlRegisterType("harbour.i2ctool.Conv", 1, 0, "Conv"); - qmlRegisterType("melexis.driver", 1, 0, "MLX90640"); +int main(int argc, char *argv[]) { + // Register types and create instances + qmlRegisterType("harbour.i2ctool.I2cif", 1, 0, "I2cif"); + qmlRegisterType("harbour.i2ctool.Conv", 1, 0, "Conv"); + qmlRegisterType("melexis.driver", 1, 0, "MLX90640"); + qmlRegisterType("my.app", 1, 0, "ThermalDataRenderer"); + //qmlRegisterType("my.app", 1, 0, "ColorAttributer"); - // Create instance of MLX90640 - MLX90640 mlx90640; + MLX90640 mlx90640; - // Initialize application and view - QGuiApplication app(argc, argv); - QQuickView view; + ThermalDataRenderer* thermalRenderer = new ThermalDataRenderer(); - MLX90640 thermal; - view.rootContext()->setContextProperty("mlx90640",&thermal); + auto hotIron = std::make_shared("hot iron"); + hotIron->addColor(0.0, QColor(0, 0, 0)); // Black + hotIron->addColor(0.35, QColor(128, 0, 128)); // Magenta + hotIron->addColor(0.5, QColor(255, 69, 0)); // OrangeRed + hotIron->addColor(1.0, QColor(255, 255, 0)); // Yellow - view.setSource(SailfishApp::pathToMainQml()); - view.show(); + auto rainbow = std::make_shared("rainbow"); + rainbow->addColor(0.0, QColor(255, 0, 0)); // Red + rainbow->addColor(0.33, QColor(0, 255, 0)); // Green + rainbow->addColor(0.67, QColor(0, 0, 255)); // Blue + rainbow->addColor(1.0, QColor(255, 0, 255)); // Magenta - return app.exec(); - } + auto grayscale = std::make_shared("grayscale"); + grayscale->addColor(0.0, QColor(0, 0, 0)); // Black + grayscale->addColor(0.25, QColor(75, 75, 75)); // dark gray + grayscale->addColor(0.5, QColor(150, 150, 150)); // gray + grayscale->addColor(0.75, QColor(200, 200, 200)); // light gray + grayscale->addColor(1.0, QColor(255, 255, 255)); // White + + thermalRenderer->addAttributer(hotIron); + thermalRenderer->addAttributer(rainbow); + thermalRenderer->addAttributer(grayscale); + + thermalRenderer->setActiveAttributer(2);//enable hotiron + + QGuiApplication app(argc, argv); + QQuickView view; + + MLX90640* thermal = new MLX90640(); + + DtPollCommand* dtpoll = new DtPollCommand(thermal); + PollingTimer* pt = new PollingTimer(dtpoll); + pt->moveToThread(&(pt->workerThread)); + pt->workerThread.start(); + + qRegisterMetaType>("QVector"); + //when a frame is ready, pass it to the renderer + QObject::connect(thermal, SIGNAL(dataReady(QVector)), thermalRenderer, SLOT(receiveNewData(QVector)), Qt::ConnectionType::DirectConnection); + + pt->setDelay(100); + + view.rootContext()->setContextProperty("polling_timer",pt); + view.rootContext()->setContextProperty("mlx90640", thermal); + view.rootContext()->setContextProperty("thermalRenderer", thermalRenderer); + + view.setSource(SailfishApp::pathToMainQml()); + view.show(); + + return app.exec(); +} diff --git a/thermaldatarenderer.cpp b/thermaldatarenderer.cpp index 8495fc5..ea0799a 100644 --- a/thermaldatarenderer.cpp +++ b/thermaldatarenderer.cpp @@ -1,6 +1,23 @@ #include "thermaldatarenderer.h" +#include +void ThermalDataRenderer::receiveNewData(QVector data) { + auto start = std::chrono::high_resolution_clock::now(); + //fprintf(stderr, "in renderer"); + //fprintf(stderr, "renderer received data of length: %d\n",data.length()); -ThermalDataRenderer::ThermalDataRenderer() -{ + if(activeAttributer >= attributers.size()){ + fprintf(stderr, "attr size mismatch: %d; %d",activeAttributer,attributers.size()); + activeAttributer = 0; + } + if(renderBuffer.size() < data.size()) + renderBuffer = QVector(data.size()+1); + + for (int i = 0; i < data.size() && i < renderBuffer.size(); ++i) { + renderBuffer[i] = attributers[activeAttributer]->encode(data[i]); + } + emit dataChanged(); + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start).count(); + //fprintf(stderr,"renderer time: %lld\n",elapsed); } diff --git a/thermaldatarenderer.h b/thermaldatarenderer.h index 178672b..42ad35d 100644 --- a/thermaldatarenderer.h +++ b/thermaldatarenderer.h @@ -4,15 +4,74 @@ #include #include #include +#include -class ThermalDataRenderer +#include +#include +#include "colorattributer.h" + +typedef std::shared_ptr ColorAttributerPtr; + +class ThermalDataRenderer : public QObject { + Q_OBJECT + bool direct = false; - QVector renderBuffer; + QVector renderBuffer; //size = 768 + QVector attributers; + unsigned int activeAttributer; float minValue = 0.0f; - float maxValue = 0.0f; + float maxValue = 1.0f; public: - ThermalDataRenderer(); + Q_PROPERTY(QVector> attributers READ getAttributers NOTIFY attributersChanged) + QThread workerThread; + ThermalDataRenderer() : renderBuffer(768){} + Q_INVOKABLE QColor getDataForIndex(const int& index){ + if(index < renderBuffer.length() && index >= 0) + { + return renderBuffer.at(index); + }else + return QColor(0,0,0); + } + + inline void addAttributer(ColorAttributerPtr attributer) { + attributers.push_back(attributer); + } + + inline QVector getAttributers(){ return attributers; } + + Q_INVOKABLE inline void setActiveAttributer(const int& index) { + if(index < attributers.size()) { + activeAttributer = index; + } + } + + inline unsigned int getActiveAttributer() const { + return activeAttributer; + } + + inline void setMinValue(float value) { + minValue = value; + } + + inline float getMinValue() const { + return minValue; + } + + inline void setMaxValue(float value) { + maxValue = value; + } + + inline float getMaxValue() const { + return maxValue; + } + +public: signals: + void attributersChanged(); + void dataChanged(); + +public slots: + void receiveNewData(QVector data); }; #endif // THERMALDATARENDERER_H diff --git a/thermi2c.pro b/thermi2c.pro index e11953d..ddb4b35 100644 --- a/thermi2c.pro +++ b/thermi2c.pro @@ -15,7 +15,9 @@ TARGET = thermi2c CONFIG += sailfishapp SOURCES += src/thermi2c.cpp \ + colorattributer.cpp \ command.cpp \ + perlin.cpp \ pollingtimer.cpp \ src/mlx90640_API.cpp \ src/mlx90640_I2C_Driver.cpp \ @@ -47,8 +49,10 @@ CONFIG += sailfishapp_i18n TRANSLATIONS += translations/thermi2c-de.ts HEADERS += \ + colorattributer.h \ command.h \ datapollcommand.h \ + perlin.h \ pollingtimer.h \ src/mlx90640_API.h \ src/mlx90640_I2C_Driver.h \ diff --git a/thermi2c.pro.user b/thermi2c.pro.user index 89ca146..b4ad8da 100644 --- a/thermi2c.pro.user +++ b/thermi2c.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -85,6 +85,9 @@ true + + true +