Commit e81ee349 authored by Elouan Wauquier's avatar Elouan Wauquier

Initial commit

parents
Pipeline #33436 passed with stage
in 36 seconds
# Any local working backup of a file
**/*.old
# Any build directory generated by Qt Creator
/build-*/
# The specific .pro.user file used by Qt Creator
**/*.pro.user*
# The build directory of the LaTeX report except .latexmkrc
/report/build/**
!/report/build/.latexmkrc
image: blang/latex:ctanfull
report:
script:
- apt-get update
- apt-get install -y python-pygments
- cd report/build
- latexmk -pdf ../tex/main.tex
- mv main.pdf ../../report.pdf
artifacts:
paths:
- "report.pdf"
expire_in: 1 weeks
tags:
- docker
This diff is collapsed.
#ifndef ANALYZER_H
#define ANALYZER_H
#include <QByteArray>
#include <QImage>
#include <QMap>
#include <QObject>
#include <QPainter>
#include <QPoint>
#include <QPointF>
#include <QVector2D>
#include <QtMath>
#include "fuzzylite/fl/Engine.h"
#include "EndingEngine.hpp"
#include "BifurcationEngine.hpp"
#include "Global.hpp"
// Classe chargée de l'analyse de l'empreinte digitale
class Analyzer : public QObject {
Q_OBJECT
public:
Analyzer();
QImage getMinutiaImage(MinutiaEngine::Engine engineType) const; // Génère une image représentant la position de la minutie reconnue par `engineType`
QImage getSectionImage(int ring, int section) const; // Retourne la section de l'image aux coordonnées spécifiées
void rasterize(const QImage& src, QImage *dest) const; // Réduit la résolution d'une image à la résolution de `dest`
void getAnalysisCoordinates(qreal x, qreal y, int *ring, int* section) const;
MinutiaEngine::Engine getEngine(const QString& engineName) const; // Retourne l'id du FIS `engineName`
QString getResultsStr() const; // Retourne le résultat de l'analyse
public slots:
void doLogPolarTransformation(const QImage& src, QPoint center, qreal angle); // Génère `m_transformedImage`
void doRidgeDistanceCalculation(const QImage& src, QPoint center, qreal angle); // Calcule la taille d'un sillon en pixels (`m_ridgeDistance`)
void doScanning(); // Réalise le scan de `m_transformedImage`, minutie par minutie
signals:
void imageTransformed(const QImage* result);
void scanningDone(const QImage* result);
protected:
const QImage *m_sourceImage; // Copie de l'image source
QImage *m_transformedImage; // Image transformée en coordonnées polaire logarithmiques
QImage *m_rasterizedSection; // Section de `m_transformedImage` fournie à un FIS
QPointF m_center; // Centre de `m_sourceImage` en pixels
qreal m_angle; // Angle de départ ($\theta_0$)
qreal m_radius; // Rayon $r$ utilisé pour la transformation polaire
qreal m_ridgeDistance; // Distance entre deux sillons en pixels
QVector<int> m_scanningDivision; // Tableau du nombre de scans pour chaque anneau
QMap<MinutiaEngine::Engine, MinutiaEngine*> m_engines; // Conteneur des FIS pour chaque minutie
QMap<MinutiaEngine::Engine, QColor*> m_minutiaColors; // Conteneur des couleurs associées à chaque minutie
QMap<MinutiaEngine::Engine, QImage*> m_minutiaImages; // Conteneur des images overlay des minuties
QVector<QVector<qreal>> m_results[static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE)]; // Tableau 2D (r, theta) par FIS
QPointF logPolarToCartesian(const QPointF& src) const; // Converti des coordonnées polaires logarithmiques en coordonnées cartésiennes
QPointF cartesianToLogPolar(const QPointF& src) const; // Converti des coordonnées cartésiennes en coordonnées polaires logarithmiques
static uint bilinearInterpolation(const QPointF& px, const QImage& src); // Effectue une interpolation bilinéaire
static void pasteOnImage(QImage& src1, const QImage& src2, QPoint pos); // Copie une image en nuances de gris sur une autre
};
#endif // ANALYZER_H
#include"BifurcationEngine.hpp"
BifurcationEngine::BifurcationEngine() : MinutiaEngine(dataSize()) {
setName("BifurcationMinutia");
fl::OutputVariable *angle = new fl::OutputVariable;
angle->setName("<angle>");
angle->setEnabled(true);
angle->setRange(0, 360);
angle->setLockValueInRange(true);
angle->setAggregation(new fl::Maximum);
angle->setDefuzzifier(new fl::WeightedAverage); // Non utilisé
angle->setDefaultValue(fl::nan);
angle->setLockPreviousValue(false);
angle->addTerm(new fl::ReverseTrapezoid("'0'", 0, 15, 345, 360));
angle->addTerm(new fl::Triangle("'1'", 12, 27, 45));
angle->addTerm(new fl::Triangle("'2'", 48, 63, 78));
angle->addTerm(new fl::Triangle("'3'", 75, 90, 105));
angle->addTerm(new fl::Triangle("'4'", 102, 117, 132));
angle->addTerm(new fl::Triangle("'5'", 138, 153, 168));
angle->addTerm(new fl::Triangle("'6'", 165, 180, 195));
angle->addTerm(new fl::Triangle("'7'", 192, 207, 222));
angle->addTerm(new fl::Triangle("'8'", 228, 243, 258));
angle->addTerm(new fl::Triangle("'9'", 255, 270, 285));
angle->addTerm(new fl::Triangle("'10'", 282, 297, 312));
angle->addTerm(new fl::Triangle("'11'", 318, 333, 358));
addOutputVariable(angle);
fl::RuleBlock *ruleBlock = new fl::RuleBlock;
ruleBlock->setName("BifurcationRule");
ruleBlock->setConjunction(new fl::Minimum);
ruleBlock->setDisjunction(new fl::DrasticSum);
ruleBlock->setImplication(new fl::Minimum);
ruleBlock->setActivation(new fl::General);
std::string antecedent1 = "if (<pixel_2x2> is 'white' or <pixel_2x2> is 'gray') "
"and <pixel_0x1> is 'black' "
"and <pixel_0x2> is 'black' "
"and <pixel_0x3> is 'black' "
"and <pixel_0x4> is 'black' "
"and <pixel_1x0> is 'black' "
"and (<pixel_1x1> is 'black' or <pixel_1x1> is 'gray') "
"and <pixel_1x4> is 'black' "
"and <pixel_2x4> is 'black' "
"and (<pixel_3x0> is 'white' or <pixel_3x0> is 'gray') "
"and (<pixel_3x2> is 'black' or <pixel_3x2> is 'gray' "
"or <pixel_3x3> is 'black' or <pixel_3x3> is 'gray') "
"and (<pixel_4x1> is 'black' or <pixel_4x2> is 'black' or <pixel_4x3> is 'black') ";
std::string antecedent2 = "if (<pixel_0x2> is 'black' or <pixel_0x2> is 'gray') "
"and <pixel_0x3> is 'black' "
"and <pixel_0x4> is 'black' "
"and (<pixel_1x0> is 'black' or <pixel_1x0> is 'gray') "
"and <pixel_1x1> is 'black' "
"and <pixel_1x2> is 'black' "
"and (<pixel_1x3> is 'black' or <pixel_1x3> is 'gray') "
"and <pixel_2x0> is 'black' "
"and (<pixel_2x1> is 'black' or <pixel_2x1> is 'gray') "
"and <pixel_2x3> is 'white' "
"and <pixel_2x4> is 'white' "
"and (<pixel_3x0> is 'black' or <pixel_3x0> is 'gray') "
"and <pixel_3x1> is 'black' "
"and <pixel_3x2> is 'black' "
"and (<pixel_3x3> is 'black' or <pixel_3x3> is 'gray') "
"and (<pixel_4x2> is 'black' or <pixel_4x2> is 'gray') "
"and <pixel_4x3> is 'black' "
"and <pixel_4x4> is 'black' ";
std::string valueRule1 = antecedent1 + "then <value> is 'value'";
std::string valueRule2 = antecedent1 + "then <value> is 'value'";
ruleBlock->addRule(fl::Rule::parse(valueRule1, this));
ruleBlock->addRule(fl::Rule::parse(valueRule2, this));
addRuleBlock(ruleBlock);
extendRule(valueRule1);
extendRule(valueRule2);
}
int BifurcationEngine::dataSize() const {
return 5;
}
MinutiaEngine::Engine BifurcationEngine::engineType() const {
return MinutiaEngine::Engine::Bifurcation;
}
void BifurcationEngine::processImage(const QImage *src) {
MinutiaEngine::processImage(src);
if (src->width() == dataSize() && src->height() == dataSize()) {
// Lance le calcul des règles floues
std::string status;
if (not isReady(&status)) {
throw fl::Exception("Error: BifurcationEngine is not ready:\n" + status, FL_AT);
}
this->process();
}
}
#ifndef BIFURCATIONENGINE_H
#define BIFURCATIONENGINE_H
#include <QVector>
#include "fuzzylite/fl/activation/General.h"
#include "fuzzylite/fl/defuzzifier/WeightedAverage.h"
#include "fuzzylite/fl/norm/s/DrasticSum.h"
#include "fuzzylite/fl/norm/s/Maximum.h"
#include "fuzzylite/fl/norm/t/BoundedDifference.h"
#include "fuzzylite/fl/norm/t/Minimum.h"
#include "fuzzylite/fl/rule/Antecedent.h"
#include "fuzzylite/fl/rule/Rule.h"
#include "fuzzylite/fl/rule/RuleBlock.h"
#include "fuzzylite/fl/term/Ramp.h"
#include "fuzzylite/fl/term/Triangle.h"
#include "fuzzylite/fl/variable/InputVariable.h"
#include "fuzzylite/fl/variable/OutputVariable.h"
#include "ReverseTrapezoid.hpp"
#include "MinutiaEngine.hpp"
// FIS détectant les bifurcations
class BifurcationEngine : public MinutiaEngine {
public:
BifurcationEngine();
int dataSize() const override;
MinutiaEngine::Engine engineType() const override;
void processImage(const QImage *src) override;
protected:
static constexpr int POINT_COUNT = 12; // Nombre de points du cercle externe
};
#endif // BIFURCATIONENGINE_H
#include "CircularProduct.hpp"
namespace fl {
std::string CircularProduct::className() const {
return "CircularProduct";
}
Complexity CircularProduct::complexity() const {
return Complexity().arithmetic(1);
}
scalar CircularProduct::compute(scalar a, scalar b) const {
a = 1 - a;
b = 1 - b;
return std::max(scalar(0.0), 1 - sqrt(a * a + b * b));
}
CircularProduct* CircularProduct::clone() const {
return new CircularProduct(*this);
}
TNorm* CircularProduct::constructor() {
return new CircularProduct;
}
}
#ifndef FL_CIRCULAR_PRODUCT_HPP
#define FL_CIRCULAR_PRODUCT_HPP
#include "fuzzylite/fl/norm/TNorm.h"
#include <math.h>
namespace fl {
class FL_API CircularProduct FL_IFINAL : public TNorm {
public:
std::string className() const FL_IOVERRIDE;
Complexity complexity() const FL_IOVERRIDE;
scalar compute(scalar a, scalar b) const FL_IOVERRIDE;
CircularProduct* clone() const FL_IOVERRIDE;
static TNorm* constructor();
};
}
#endif /* FL_CIRCULAR_PRODUCT_HPP */
#include "CompareEngine.hpp"
CompareEngine::CompareEngine() : fl::Engine() {
setName("Comparator");
const int ringCount = STEP_PER_RIDGE * RIDGE_COUNT / 2;
const int maxSectionCount = qCeil(2 * M_PI * (ringCount + CENTER_RADIUS_IGNORE * STEP_PER_RIDGE));
// Variables des `COMPARE_COUNT` minuties
for (int i(0); i < COMPARE_COUNT; ++i) {
fl::InputVariable *membership = new fl::InputVariable;
membership->setName("<membership_" + QString::number(i).toStdString() + ">");
membership->setEnabled(true);
membership->setRange(0, 1);
membership->setLockValueInRange(true);
membership->addTerm(new fl::Trapezoid("'uncertain'", 0, 0.05, 0.1, 0.2));
membership->addTerm(new fl::Ramp("'certain'", 0.1, 0.2));
addInputVariable(membership);
fl::InputVariable *ringDiff = new fl::InputVariable;
ringDiff->setName("<ringDiff_" + QString::number(i).toStdString() + ">");
ringDiff->setEnabled(true);
ringDiff->addTerm(new fl::Triangle("'same'", - COMPARE_RADIUS * STEP_PER_RIDGE, 0, COMPARE_RADIUS * STEP_PER_RIDGE));
addInputVariable(ringDiff);
fl::InputVariable *sectionDiff = new fl::InputVariable;
sectionDiff->setName("<sectionDiff_" + QString::number(i).toStdString() + ">");
sectionDiff->setEnabled(true);
sectionDiff->addTerm(new fl::Triangle("'same'", - COMPARE_RADIUS * STEP_PER_RIDGE, 0, COMPARE_RADIUS * STEP_PER_RIDGE));
addInputVariable(sectionDiff);
fl::InputVariable *type = new fl::InputVariable;
type->setName("<type_" + QString::number(i).toStdString() + ">");
type->setEnabled(true);
for (int j(0); j < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++j) {
std::vector<fl::Discrete::Pair> partition;
for (int k(0); k < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++k) {
if (k == j) {
partition.push_back(fl::Discrete::Pair(k, 1));
} else {
partition.push_back(fl::Discrete::Pair(k, 0));
}
}
type->addTerm(new fl::Discrete("'" + QString::number(j).toStdString() + "'", partition));
}
addInputVariable(type);
}
// Variables de la minutie de comparaison
fl::InputVariable *membership = new fl::InputVariable;
membership->setName("<membership>");
membership->setEnabled(true);
membership->setRange(0, 1);
membership->setLockValueInRange(true);
membership->addTerm(new fl::Trapezoid("'uncertain'", 0, 0.05, 0.1, 0.2));
membership->addTerm(new fl::Ramp("'certain'", 0.1, 0.2));
addInputVariable(membership);
fl::InputVariable *ring = new fl::InputVariable;
ring->setName("<ring>");
ring->setEnabled(true);
for (int j(0); j < ringCount; ++j) {
ring->addTerm(new fl::Discrete("'" + QString::number(j).toStdString() + "'"));
}
ring->setValue(0);
addInputVariable(ring);
fl::InputVariable *section = new fl::InputVariable;
section->setName("<section>");
section->setEnabled(true);
for (int j(0); j < maxSectionCount; ++j) {
section->addTerm(new fl::Discrete("'" + QString::number(j).toStdString() + "'"));
}
section->setValue(0);
addInputVariable(section);
fl::InputVariable *type = new fl::InputVariable;
type->setName("<type>");
type->setEnabled(true);
for (int j(0); j < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++j) {
std::vector<fl::Discrete::Pair> partition;
for (int k(0); k < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++k) {
if (k == j) {
partition.push_back(fl::Discrete::Pair(k, 1));
} else {
partition.push_back(fl::Discrete::Pair(k, 0));
}
}
type->addTerm(new fl::Discrete("'" + QString::number(j).toStdString() + "'", partition));
}
addInputVariable(type);
// Variable de sortie
fl::OutputVariable *output = new fl::OutputVariable;
output->setName("<proximity>");
output->setEnabled(true);
output->setAggregation(new fl::Maximum);
std::vector<fl::Discrete::Pair> partition;
partition.push_back(fl::Discrete::Pair(0, 1));
output->addTerm(new fl::Discrete("'close'", partition));
output->setDefuzzifier(new fl::WeightedAverage); // Non utilisé
addOutputVariable(output);
// Règles
fl::RuleBlock *ruleBlock = new fl::RuleBlock;
ruleBlock->setName("CompareRules");
ruleBlock->setEnabled(true);
ruleBlock->setConjunction(new fl::CircularProduct);
ruleBlock->setImplication(new fl::Minimum);
for (int i(0); i < COMPARE_COUNT; ++i) {
for (int type(0); type < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++type) {
std::string antecedent1 = "if <membership> is 'certain' "
"and <type> is '" + QString::number(type).toStdString() + "' "
"and <membership_" + QString::number(i).toStdString() + "> is 'certain' "
"and <type_" + QString::number(i).toStdString() + "> is '" + QString::number(type).toStdString() + "' "
"and <ringDiff_" + QString::number(i).toStdString() + "> is 'same' "
"and <sectionDiff_" + QString::number(i).toStdString() + "> is 'same' ";
std::string antecedent2 = "if <membership> is 'certain' "
"and <type> is '" + QString::number(type).toStdString() + "' "
"and <membership_" + QString::number(i).toStdString() + "> is 'uncertain' "
"and <type_" + QString::number(i).toStdString() + "> is '" + QString::number(type).toStdString() + "' "
"and <ringDiff_" + QString::number(i).toStdString() + "> is 'same' "
"and <sectionDiff_" + QString::number(i).toStdString() + "> is 'same' ";
std::string antecedent3 = "if <membership> is 'uncertain' "
"and <type> is '" + QString::number(type).toStdString() + "' "
"and <membership_" + QString::number(i).toStdString() + "> is 'uncertain' "
"and <type_" + QString::number(i).toStdString() + "> is '" + QString::number(type).toStdString() + "' "
"and <ringDiff_" + QString::number(i).toStdString() + "> is 'same' "
"and <sectionDiff_" + QString::number(i).toStdString() + "> is 'same' ";
ruleBlock->addRule(fl::Rule::parse(antecedent1 + "then <proximity> is 'close'", this));
ruleBlock->addRule(fl::Rule::parse(antecedent2 + "then <proximity> is 'close' with 0.5", this));
ruleBlock->addRule(fl::Rule::parse(antecedent3 + "then <proximity> is 'close' with 0.25", this));
}
}
addRuleBlock(ruleBlock);
}
void CompareEngine::setMinutia(int index, const MinutiaEngine::Minutia& minutia) {
std::string indexStr(QString::number(index).toStdString());
getInputVariable("<membership_" + indexStr + ">")->setValue(minutia.membership);
getInputVariable("<ringDiff_" + indexStr + ">")->setValue(minutia.ring);
getInputVariable("<sectionDiff_" + indexStr + ">")->setValue(minutia.section);
getInputVariable("<type_" + indexStr + ">")->setValue(static_cast<int>(minutia.type));
}
void CompareEngine::setDBMinutia(const MinutiaEngine::Minutia& minutia) {
for (int i(0); i < COMPARE_COUNT; ++i) {
std::string index(QString::number(i).toStdString());
int ringSrc, sectionSrc, ring, section;
ringSrc = static_cast<int>(getInputVariable("<ringDiff_" + index + ">")->getValue());
sectionSrc = static_cast<int>(getInputVariable("<sectionDiff_" + index + ">")->getValue());
ring = static_cast<int>(getInputVariable("<ring>")->getValue());
section = static_cast<int>(getInputVariable("<section>")->getValue());
getInputVariable("<ringDiff_" + index + ">")->setValue(ringSrc + ring - minutia.ring);
getInputVariable("<sectionDiff_" + index + ">")->setValue(sectionSrc + section - minutia.section);
}
getInputVariable("<membership>")->setValue(minutia.membership);
getInputVariable("<ring>")->setValue(minutia.ring);
getInputVariable("<section>")->setValue(minutia.section);
getInputVariable("<type>")->setValue(static_cast<int>(minutia.type));
}
#ifndef COMPARE_ENGINE_HPP
#define COMPARE_ENGINE_HPP
#include "fuzzylite/fl/Engine.h"
#include "fuzzylite/fl/norm/s/Maximum.h"
#include "fuzzylite/fl/norm/t/Minimum.h"
#include "fuzzylite/fl/rule/Rule.h"
#include "fuzzylite/fl/rule/RuleBlock.h"
#include "fuzzylite/fl/term/Discrete.h"
#include "fuzzylite/fl/term/Ramp.h"
#include "fuzzylite/fl/term/Trapezoid.h"
#include "fuzzylite/fl/term/Triangle.h"
#include "fuzzylite/fl/variable/InputVariable.h"
#include "fuzzylite/fl/variable/OutputVariable.h"
#include "CircularProduct.hpp"
#include <QString>
#include <QtMath>
#include "MinutiaEngine.hpp"
#include "Global.hpp"
// FIS comparant les résultats d'empreinte
class CompareEngine : public fl::Engine {
public:
CompareEngine();
void setMinutia(int index, const MinutiaEngine::Minutia& minutia);
void setDBMinutia(const MinutiaEngine::Minutia& minutia);
};
#endif // COMPARE_ENGINE_HPP
#include "EndingEngine.hpp"
EndingEngine::EndingEngine() : MinutiaEngine(dataSize()) {
setName("EndingMinutia");
fl::OutputVariable *angle = new fl::OutputVariable;
angle->setName("<angle>");
angle->setEnabled(true);
angle->setRange(0, 360);
angle->setLockValueInRange(true);
angle->setAggregation(new fl::Maximum);
angle->setDefuzzifier(new fl::WeightedAverage); // to be changed
angle->setDefaultValue(fl::nan);
angle->setLockPreviousValue(false);
angle->addTerm(new fl::ReverseTrapezoid("'0'", 0, 15, 345, 360));
angle->addTerm(new fl::Triangle("'1'", 12, 27, 45));
angle->addTerm(new fl::Triangle("'2'", 48, 63, 78));
angle->addTerm(new fl::Triangle("'3'", 75, 90, 105));
angle->addTerm(new fl::Triangle("'4'", 102, 117, 132));
angle->addTerm(new fl::Triangle("'5'", 138, 153, 168));
angle->addTerm(new fl::Triangle("'6'", 165, 180, 195));
angle->addTerm(new fl::Triangle("'7'", 192, 207, 222));
angle->addTerm(new fl::Triangle("'8'", 228, 243, 258));
angle->addTerm(new fl::Triangle("'9'", 255, 270, 285));
angle->addTerm(new fl::Triangle("'10'", 282, 297, 312));
angle->addTerm(new fl::Triangle("'11'", 318, 333, 358));
addOutputVariable(angle);
fl::RuleBlock *ruleBlock = new fl::RuleBlock;
ruleBlock->setName("EndingRule");
ruleBlock->setConjunction(new fl::BoundedDifference);
ruleBlock->setDisjunction(new fl::DrasticSum);
ruleBlock->setImplication(new fl::Minimum);
ruleBlock->setActivation(new fl::General);
std::string antecedent1 = "if (<pixel_0x1> is 'white' or <pixel_0x1> is 'gray') "
"and <pixel_0x2> is 'white' "
"and <pixel_0x3> is 'white' "
"and <pixel_1x4> is 'white' "
"and <pixel_2x0> is 'black' "
"and <pixel_2x2> is 'black' "
"and <pixel_2x4> is 'white' "
"and <pixel_3x4> is 'white' "
"and (<pixel_4x1> is 'white' or <pixel_4x1> is 'gray') "
"and <pixel_4x2> is 'white' "
"and <pixel_4x3> is 'white' ";
std::string antecedent2 = "if (<pixel_0x2> is 'white' or <pixel_0x2> is 'gray') "
"and <pixel_0x3> is 'white' "
"and <pixel_1x0> is 'black' "
"and <pixel_1x4> is 'white' "
"and <pixel_2x2> is 'black' "
"and <pixel_2x4> is 'white' "
"and (<pixel_3x0> is 'white' or <pixel_3x0> is 'gray') "
"and <pixel_3x4> is 'white' "
"and <pixel_4x1> is 'white' "
"and <pixel_4x2> is 'white' "
"and <pixel_4x3> is 'white' ";
std::string valueRule1 = antecedent1 + "then <value> is 'value'";
std::string valueRule2 = antecedent2 + "then <value> is 'value'";
ruleBlock->addRule(fl::Rule::parse(valueRule1, this));
ruleBlock->addRule(fl::Rule::parse(valueRule2, this));
addRuleBlock(ruleBlock);
extendRule(valueRule1);
extendRule(valueRule2);
}
int EndingEngine::dataSize() const {
return 5;
}
MinutiaEngine::Engine EndingEngine::engineType() const {
return MinutiaEngine::Engine::Ending;
}
void EndingEngine::processImage(const QImage *src) {
MinutiaEngine::processImage(src);
if (src->width() == dataSize() && src->height() == dataSize()) {
// Lance le calcul des règles floues
std::string status;
if (not isReady(&status)) {
throw fl::Exception("Error: EndingEngine is not ready:\n" + status, FL_AT);
}
this->process();
}