Commit 765897a98c2e3054a06ecfce05645cdf8878aa55
1 parent
c290359f
more code
Showing
34 changed files
with
1486 additions
and
1 deletions
app/openbr-gui/algorithm.cpp
0 → 100644
| 1 | +#include <QStringList> | |
| 2 | +#include <openbr.h> | |
| 3 | + | |
| 4 | +#include "algorithm.h" | |
| 5 | + | |
| 6 | +/**** ALGORITHM ****/ | |
| 7 | +/*** PUBLIC ***/ | |
| 8 | +br::Algorithm::Algorithm(QWidget *parent) | |
| 9 | + : QComboBox(parent) | |
| 10 | +{ | |
| 11 | + setToolTip("Algorithm"); | |
| 12 | + connect(this, SIGNAL(currentIndexChanged(QString)), this, SLOT(setAlgorithm(QString))); | |
| 13 | +} | |
| 14 | + | |
| 15 | +/*** PUBLIC SLOTS ***/ | |
| 16 | +bool br::Algorithm::addAlgorithm(const QString &algorithm, const QString &displayName) | |
| 17 | +{ | |
| 18 | + static QStringList availableAlgorithms; | |
| 19 | + if (availableAlgorithms.isEmpty()) | |
| 20 | + availableAlgorithms = QString(br_objects("Abbreviation", ".*", false)).split("\n"); | |
| 21 | + | |
| 22 | + if (!availableAlgorithms.contains(algorithm)) | |
| 23 | + return false; | |
| 24 | + | |
| 25 | + if (displayName.isEmpty()) { | |
| 26 | + addItem(algorithm); | |
| 27 | + } else { | |
| 28 | + displayNames.insert(displayName, algorithm); | |
| 29 | + addItem(displayName); | |
| 30 | + } | |
| 31 | + return true; | |
| 32 | +} | |
| 33 | + | |
| 34 | +/*** PRIVATE SLOTS ***/ | |
| 35 | +void br::Algorithm::setAlgorithm(QString algorithm) | |
| 36 | +{ | |
| 37 | + if (displayNames.contains(algorithm)) | |
| 38 | + algorithm = displayNames[algorithm]; | |
| 39 | + | |
| 40 | + br_set_property("algorithm", qPrintable(algorithm)); | |
| 41 | + emit newAlgorithm(algorithm); | |
| 42 | +} | |
| 43 | + | |
| 44 | +#include "moc_algorithm.cpp" | ... | ... |
app/openbr-gui/algorithm.h
0 → 100644
| 1 | +#ifndef __ALGORITHM_H | |
| 2 | +#define __ALGORITHM_H | |
| 3 | + | |
| 4 | +#include <QComboBox> | |
| 5 | +#include <QString> | |
| 6 | +#include <QWidget> | |
| 7 | +#include <openbr_export.h> | |
| 8 | + | |
| 9 | +namespace br | |
| 10 | +{ | |
| 11 | + | |
| 12 | +class BR_EXPORT_GUI Algorithm : public QComboBox | |
| 13 | +{ | |
| 14 | + Q_OBJECT | |
| 15 | + QHash<QString, QString> displayNames; | |
| 16 | + | |
| 17 | +public: | |
| 18 | + explicit Algorithm(QWidget *parent = 0); | |
| 19 | + | |
| 20 | +public slots: | |
| 21 | + bool addAlgorithm(const QString &algorithm, const QString &displayName = ""); | |
| 22 | + | |
| 23 | +private slots: | |
| 24 | + void setAlgorithm(QString algorithm); | |
| 25 | + | |
| 26 | +signals: | |
| 27 | + void newAlgorithm(QString algorithm); | |
| 28 | +}; | |
| 29 | + | |
| 30 | +} | |
| 31 | + | |
| 32 | +#endif // __ALGORITHM_H | ... | ... |
app/openbr-gui/classifier.cpp
0 → 100644
| 1 | +#include <QtConcurrentRun> | |
| 2 | +#include <openbr_plugin.h> | |
| 3 | + | |
| 4 | +#include "classifier.h" | |
| 5 | + | |
| 6 | +using namespace br; | |
| 7 | + | |
| 8 | +/**** CLASSIFIER ****/ | |
| 9 | +/*** PUBLIC ***/ | |
| 10 | +Classifier::Classifier(QWidget *parent) | |
| 11 | + : QLabel(parent) | |
| 12 | +{ | |
| 13 | + setAlignment(Qt::AlignCenter); | |
| 14 | + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); | |
| 15 | + connect(this, SIGNAL(newClassification(QString,QString)), this, SLOT(setClassification(QString,QString))); | |
| 16 | +} | |
| 17 | + | |
| 18 | +void Classifier::setAlgorithm(const QString &algorithm) | |
| 19 | +{ | |
| 20 | + this->algorithm = algorithm; | |
| 21 | + _classify(File()); // Trigger algorithm initialization | |
| 22 | +} | |
| 23 | + | |
| 24 | +/*** PUBLIC SLOTS ***/ | |
| 25 | +void Classifier::classify(const File &file) | |
| 26 | +{ | |
| 27 | + QtConcurrent::run(this, &Classifier::_classify, file); | |
| 28 | +} | |
| 29 | + | |
| 30 | +/*** PRIVATE SLOTS ***/ | |
| 31 | +void Classifier::setClassification(const QString &key, const QString &value) | |
| 32 | +{ | |
| 33 | + if (key.isEmpty()) clear(); | |
| 34 | + else setText(QString("<b>%1: </b>%2").arg(key, value)); | |
| 35 | +} | |
| 36 | + | |
| 37 | +/*** PRIVATE ***/ | |
| 38 | +void Classifier::_classify(File file) | |
| 39 | +{ | |
| 40 | + file.setBool("forceEnrollment"); | |
| 41 | + | |
| 42 | + QString key, value; | |
| 43 | + foreach (const File &f, Enroll(file.flat(), File("[algorithm=" + algorithm + "]"))) { | |
| 44 | + qDebug() << f.flat(); | |
| 45 | + if (!f.contains("Label")) | |
| 46 | + continue; | |
| 47 | + | |
| 48 | + if (algorithm == "GenderClassification") { | |
| 49 | + key = "Gender"; | |
| 50 | + value = (f.getInt("Label", 0) == 0 ? "Male" : "Female"); | |
| 51 | + } else if (algorithm == "AgeRegression") { | |
| 52 | + key = "Age"; | |
| 53 | + value = QString::number(int(f.getFloat("Label", 0)+0.5)) + " Years"; | |
| 54 | + } else { | |
| 55 | + key = algorithm; | |
| 56 | + value = f.getString("Label"); | |
| 57 | + } | |
| 58 | + break; | |
| 59 | + } | |
| 60 | + | |
| 61 | + emit newClassification(key, value); | |
| 62 | +} | |
| 63 | + | |
| 64 | +#include "moc_classifier.cpp" | ... | ... |
app/openbr-gui/classifier.h
0 → 100644
| 1 | +#ifndef __CLASSIFIER_H | |
| 2 | +#define __CLASSIFIER_H | |
| 3 | + | |
| 4 | +#include <QLabel> | |
| 5 | +#include <QWidget> | |
| 6 | +#include <QString> | |
| 7 | +#include <openbr_plugin.h> | |
| 8 | + | |
| 9 | +namespace br | |
| 10 | +{ | |
| 11 | + | |
| 12 | +class BR_EXPORT_GUI Classifier : public QLabel | |
| 13 | +{ | |
| 14 | + Q_OBJECT | |
| 15 | + QString algorithm; | |
| 16 | + | |
| 17 | +public: | |
| 18 | + explicit Classifier(QWidget *parent = 0); | |
| 19 | + void setAlgorithm(const QString &algorithm); | |
| 20 | + | |
| 21 | +public slots: | |
| 22 | + void classify(const br::File &file); | |
| 23 | + | |
| 24 | +private slots: | |
| 25 | + void setClassification(const QString &key, const QString &value); | |
| 26 | + | |
| 27 | +private: | |
| 28 | + void _classify(br::File file); | |
| 29 | + | |
| 30 | +signals: | |
| 31 | + void newClassification(QString key, QString value); | |
| 32 | +}; | |
| 33 | + | |
| 34 | +} // namespace br | |
| 35 | + | |
| 36 | +#endif // __CLASSIFIER_H | ... | ... |
app/openbr-gui/dataset.cpp
0 → 100644
| 1 | +#include <QDir> | |
| 2 | +#include <QRegExp> | |
| 3 | +#include <QStringList> | |
| 4 | +#include <openbr.h> | |
| 5 | + | |
| 6 | +#include "dataset.h" | |
| 7 | + | |
| 8 | +/**** DATASET ****/ | |
| 9 | +/*** PUBLIC ***/ | |
| 10 | +br::Dataset::Dataset(QWidget *parent) | |
| 11 | + : QComboBox(parent) | |
| 12 | +{ | |
| 13 | + setToolTip("Dataset"); | |
| 14 | + connect(this, SIGNAL(currentIndexChanged(QString)), this, SLOT(datasetChangedTo(QString))); | |
| 15 | +} | |
| 16 | + | |
| 17 | +/*** PUBLIC SLOTS ***/ | |
| 18 | +static bool compareDatasets(const QString &a, const QString &b) | |
| 19 | +{ | |
| 20 | + static QHash<QString,int> knownDatasets; | |
| 21 | + if (knownDatasets.isEmpty()) { | |
| 22 | + knownDatasets["MEDS"] = 0; | |
| 23 | + knownDatasets["PCSO"] = 1; | |
| 24 | + knownDatasets["Good"] = 2; | |
| 25 | + knownDatasets["Bad"] = 3; | |
| 26 | + knownDatasets["Ugly"] = 4; | |
| 27 | + } | |
| 28 | + | |
| 29 | + if (!knownDatasets.contains(b)) return knownDatasets.contains(a); | |
| 30 | + if (!knownDatasets.contains(a)) return false; | |
| 31 | + return knownDatasets[a] < knownDatasets[b]; | |
| 32 | +} | |
| 33 | + | |
| 34 | +void br::Dataset::setAlgorithm(const QString &algorithm) | |
| 35 | +{ | |
| 36 | + this->algorithm = algorithm; | |
| 37 | + QStringList datasets; | |
| 38 | + QRegExp re("^" + algorithm + "_(.+).csv$"); | |
| 39 | + foreach (const QString &file, QDir(QString("%1/share/mm/Algorithm_Dataset/").arg(br_sdk_path())).entryList()) | |
| 40 | + if (re.indexIn(file) != -1) | |
| 41 | + datasets.append(re.cap(1)); | |
| 42 | + qSort(datasets.begin(), datasets.end(), compareDatasets); | |
| 43 | + clear(); | |
| 44 | + addItems(datasets); | |
| 45 | + setVisible(!datasets.isEmpty()); | |
| 46 | +} | |
| 47 | + | |
| 48 | +/*** PRIVATE SLOTS ***/ | |
| 49 | +void br::Dataset::datasetChangedTo(const QString &dataset) | |
| 50 | +{ | |
| 51 | + emit newDataset(dataset); | |
| 52 | + emit newDistribution(QString("%1/share/mm/Algorithm_Dataset/%2_%3.csv").arg(br_sdk_path(), algorithm, dataset)); | |
| 53 | +} | |
| 54 | + | |
| 55 | +#include "moc_dataset.cpp" | ... | ... |
app/openbr-gui/dataset.h
0 → 100644
| 1 | +#ifndef __DATASET_H | |
| 2 | +#define __DATASET_H | |
| 3 | + | |
| 4 | +#include <QComboBox> | |
| 5 | +#include <QString> | |
| 6 | +#include <QWidget> | |
| 7 | +#include <openbr_export.h> | |
| 8 | + | |
| 9 | +namespace br | |
| 10 | +{ | |
| 11 | + | |
| 12 | +class BR_EXPORT_GUI Dataset : public QComboBox | |
| 13 | +{ | |
| 14 | + Q_OBJECT | |
| 15 | + QString algorithm; | |
| 16 | + | |
| 17 | +public: | |
| 18 | + explicit Dataset(QWidget *parent = 0); | |
| 19 | + | |
| 20 | +public slots: | |
| 21 | + void setAlgorithm(const QString &algorithm); | |
| 22 | + | |
| 23 | +private slots: | |
| 24 | + void datasetChangedTo(const QString &dataset); | |
| 25 | + | |
| 26 | +signals: | |
| 27 | + void newDataset(QString); | |
| 28 | + void newDistribution(QString); | |
| 29 | +}; | |
| 30 | + | |
| 31 | +} // namespace br | |
| 32 | + | |
| 33 | +#endif // __DATASET_H | ... | ... |
app/openbr-gui/gallerytoolbar.cpp
0 → 100644
| 1 | +#include <QDateTime> | |
| 2 | +#include <QDesktopServices> | |
| 3 | +#include <QDir> | |
| 4 | +#include <QFileDialog> | |
| 5 | +#include <QIcon> | |
| 6 | +#include <QMessageBox> | |
| 7 | +#include <QSharedPointer> | |
| 8 | +#include <QSize> | |
| 9 | +#include <QtConcurrentRun> | |
| 10 | +#include <opencv2/highgui/highgui.hpp> | |
| 11 | +#include <assert.h> | |
| 12 | +#include <openbr_plugin.h> | |
| 13 | + | |
| 14 | +#include "gallerytoolbar.h" | |
| 15 | +#include "utility.h" | |
| 16 | + | |
| 17 | +/**** GALLERY ****/ | |
| 18 | +/*** STATIC ***/ | |
| 19 | +QMutex br::GalleryToolBar::galleryLock; | |
| 20 | + | |
| 21 | +/*** PUBLIC ***/ | |
| 22 | +br::GalleryToolBar::GalleryToolBar(QWidget *parent) | |
| 23 | + : QToolBar("Gallery", parent) | |
| 24 | +{ | |
| 25 | + lGallery.setAlignment(Qt::AlignCenter); | |
| 26 | + lGallery.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); | |
| 27 | + tbOpenFile.setIcon(QIcon(":/glyphicons/png/glyphicons_138_picture@2x.png")); | |
| 28 | + tbOpenFile.setToolTip("Load Photo"); | |
| 29 | + tbOpenFolder.setIcon(QIcon(":/glyphicons/png/glyphicons_144_folder_open@2x.png")); | |
| 30 | + tbOpenFolder.setToolTip("Load Photo Folder"); | |
| 31 | + tbWebcam.setCheckable(true); | |
| 32 | + tbWebcam.setIcon(QIcon(":/glyphicons/png/glyphicons_301_webcam@2x.png")); | |
| 33 | + tbWebcam.setToolTip("Load Webcam"); | |
| 34 | + tbBack.setEnabled(false); | |
| 35 | + tbBack.setIcon(QIcon(":/glyphicons/png/glyphicons_221_unshare@2x.png")); | |
| 36 | + tbBack.setToolTip("Back"); | |
| 37 | + tbMean.setIcon(QIcon(":/glyphicons/png/glyphicons_003_user@2x.png")); | |
| 38 | + tbMean.setToolTip("Mean Image"); | |
| 39 | + | |
| 40 | + addWidget(&tbOpenFile); | |
| 41 | + addWidget(&tbOpenFolder); | |
| 42 | + addWidget(&tbWebcam); | |
| 43 | + addSeparator(); | |
| 44 | + addWidget(&lGallery); | |
| 45 | + addSeparator(); | |
| 46 | + addWidget(&tbBack); | |
| 47 | + addWidget(&tbMean); | |
| 48 | + setIconSize(QSize(20,20)); | |
| 49 | + | |
| 50 | + connect(&tbOpenFile, SIGNAL(clicked()), this, SLOT(openFile())); | |
| 51 | + connect(&tbOpenFolder, SIGNAL(clicked()), this, SLOT(openFolder())); | |
| 52 | + connect(&tbBack, SIGNAL(clicked()), this, SLOT(home())); | |
| 53 | + connect(&tbMean, SIGNAL(clicked()), this, SLOT(mean())); | |
| 54 | + connect(&enrollmentWatcher, SIGNAL(finished()), this, SLOT(enrollmentFinished())); | |
| 55 | + connect(&timer, SIGNAL(timeout()), this, SLOT(checkWebcam())); | |
| 56 | + | |
| 57 | + timer.start(500); | |
| 58 | +} | |
| 59 | + | |
| 60 | +/*** PUBLIC SLOTS ***/ | |
| 61 | +void br::GalleryToolBar::enroll(const br::File &input) | |
| 62 | +{ | |
| 63 | + if (input.isNull()) return; | |
| 64 | + enrollmentWatcher.setFuture(QtConcurrent::run(this, &GalleryToolBar::_enroll, input)); | |
| 65 | +} | |
| 66 | + | |
| 67 | +void br::GalleryToolBar::enroll(const QImage &input) | |
| 68 | +{ | |
| 69 | + QString tempFileName = br::Context::scratchPath() + "/tmp/" + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss:zzz") + ".png"; | |
| 70 | + input.save(tempFileName); | |
| 71 | + enroll(tempFileName); | |
| 72 | +} | |
| 73 | + | |
| 74 | +void br::GalleryToolBar::select(const br::File &file) | |
| 75 | +{ | |
| 76 | + tbBack.setEnabled(true); | |
| 77 | + emit newFiles(br::FileList() << file); | |
| 78 | + emit newGallery(file); | |
| 79 | +} | |
| 80 | + | |
| 81 | +/*** PRIVATE ***/ | |
| 82 | +void br::GalleryToolBar::_enroll(const br::File &input) | |
| 83 | +{ | |
| 84 | + galleryLock.lock(); | |
| 85 | + this->input = input; | |
| 86 | + if (input.suffix() == "gal") gallery = input.name + ".mem"; | |
| 87 | + else gallery = QString("%1/galleries/%2.gal[cache]").arg(br_scratch_path(), qPrintable(input.baseName()+input.hash())); | |
| 88 | + files = br::Enroll(input.flat(), gallery.flat()); | |
| 89 | + galleryLock.unlock(); | |
| 90 | +} | |
| 91 | + | |
| 92 | +void br::GalleryToolBar::_checkWebcam() | |
| 93 | +{ | |
| 94 | + static QSharedPointer<cv::VideoCapture> videoCapture; | |
| 95 | + if (videoCapture.isNull()) { | |
| 96 | + videoCapture = QSharedPointer<cv::VideoCapture>(new cv::VideoCapture(0)); | |
| 97 | + cv::Mat m; | |
| 98 | + while (!m.data) videoCapture->read(m); // First frames can be empty | |
| 99 | + } | |
| 100 | + | |
| 101 | + if (galleryLock.tryLock()) { | |
| 102 | + cv::Mat m; | |
| 103 | + videoCapture->read(m); | |
| 104 | + galleryLock.unlock(); | |
| 105 | + enroll(toQImage(m)); | |
| 106 | + } | |
| 107 | +} | |
| 108 | + | |
| 109 | +/*** PRIVATE SLOTS ***/ | |
| 110 | +void br::GalleryToolBar::checkWebcam() | |
| 111 | +{ | |
| 112 | + // Check webcam | |
| 113 | + if (!tbWebcam.isChecked()) return; | |
| 114 | + QtConcurrent::run(this, &GalleryToolBar::_checkWebcam); | |
| 115 | +} | |
| 116 | + | |
| 117 | +void br::GalleryToolBar::enrollmentFinished() | |
| 118 | +{ | |
| 119 | + if (files.isEmpty()) { | |
| 120 | + if (!input.getBool("forceEnrollment") && !tbWebcam.isChecked()) { | |
| 121 | + QMessageBox msgBox; | |
| 122 | + msgBox.setText("Quality test failed."); | |
| 123 | + msgBox.setInformativeText("Enroll anyway?"); | |
| 124 | + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); | |
| 125 | + msgBox.setDefaultButton(QMessageBox::Ok); | |
| 126 | + int ret = msgBox.exec(); | |
| 127 | + | |
| 128 | + if (ret == QMessageBox::Ok) { | |
| 129 | + br::File file = input; | |
| 130 | + file.setBool("forceEnrollment"); | |
| 131 | + enroll(file); | |
| 132 | + } | |
| 133 | + } | |
| 134 | + return; | |
| 135 | + } | |
| 136 | + | |
| 137 | + lGallery.setText(input.baseName() + (files.size() != 1 ? " (" + QString::number(files.size()) + ")" : "")); | |
| 138 | + tbBack.setEnabled(false); | |
| 139 | + emit newFiles(files); | |
| 140 | + emit newGallery(gallery); | |
| 141 | +} | |
| 142 | + | |
| 143 | +void br::GalleryToolBar::home() | |
| 144 | +{ | |
| 145 | + tbBack.setEnabled(false); | |
| 146 | + emit newGallery(gallery); | |
| 147 | +} | |
| 148 | + | |
| 149 | +void br::GalleryToolBar::mean() | |
| 150 | +{ | |
| 151 | + const QString file = QString("%1/mean/%2.png").arg(br_scratch_path(), input.baseName()+input.hash()); | |
| 152 | + br_set_property("CENTER_TRAIN_B", qPrintable(file)); | |
| 153 | + br::File trainingFile = input; | |
| 154 | + trainingFile.setBool("forceEnrollment"); | |
| 155 | + br_train(qPrintable(trainingFile.flat()), "[algorithm=MedianFace]"); | |
| 156 | + enroll(file); | |
| 157 | +} | |
| 158 | + | |
| 159 | +void br::GalleryToolBar::openFile() | |
| 160 | +{ | |
| 161 | + enroll(QFileDialog::getOpenFileName(this, "Select Photo", QDesktopServices::storageLocation(QDesktopServices::PicturesLocation))); | |
| 162 | +} | |
| 163 | + | |
| 164 | +void br::GalleryToolBar::openFolder() | |
| 165 | +{ | |
| 166 | + enroll(QFileDialog::getExistingDirectory(this, "Select Photo Directory", QDesktopServices::storageLocation(QDesktopServices::HomeLocation))); | |
| 167 | +} | |
| 168 | + | |
| 169 | +#include "moc_gallerytoolbar.cpp" | ... | ... |
app/openbr-gui/gallerytoolbar.h
0 → 100644
| 1 | +#ifndef __GALLERYTOOLBAR_H | |
| 2 | +#define __GALLERYTOOLBAR_H | |
| 3 | + | |
| 4 | +#include <QFutureWatcher> | |
| 5 | +#include <QImage> | |
| 6 | +#include <QLabel> | |
| 7 | +#include <QMutex> | |
| 8 | +#include <QString> | |
| 9 | +#include <QTimer> | |
| 10 | +#include <QToolBar> | |
| 11 | +#include <QToolButton> | |
| 12 | +#include <QWidget> | |
| 13 | +#include <openbr_plugin.h> | |
| 14 | + | |
| 15 | +namespace br | |
| 16 | +{ | |
| 17 | + | |
| 18 | +class BR_EXPORT_GUI GalleryToolBar : public QToolBar | |
| 19 | +{ | |
| 20 | + Q_OBJECT | |
| 21 | + br::File input, gallery; | |
| 22 | + br::FileList files; | |
| 23 | + | |
| 24 | + QLabel lGallery; | |
| 25 | + QToolButton tbOpenFile, tbOpenFolder, tbWebcam, tbBack, tbMean; | |
| 26 | + QFutureWatcher<void> enrollmentWatcher; | |
| 27 | + QTimer timer; | |
| 28 | + | |
| 29 | + static QMutex galleryLock; | |
| 30 | + | |
| 31 | +public: | |
| 32 | + explicit GalleryToolBar(QWidget *parent = 0); | |
| 33 | + | |
| 34 | +public slots: | |
| 35 | + void enroll(const br::File &input); | |
| 36 | + void enroll(const QImage &input); | |
| 37 | + void select(const br::File &file); | |
| 38 | + | |
| 39 | +private: | |
| 40 | + void _enroll(const br::File &input); | |
| 41 | + void _checkWebcam(); | |
| 42 | + | |
| 43 | +private slots: | |
| 44 | + void checkWebcam(); | |
| 45 | + void enrollmentFinished(); | |
| 46 | + void home(); | |
| 47 | + void mean(); | |
| 48 | + void openFile(); | |
| 49 | + void openFolder(); | |
| 50 | + | |
| 51 | +signals: | |
| 52 | + void newGallery(br::File gallery); | |
| 53 | + void newFiles(br::FileList files); | |
| 54 | +}; | |
| 55 | + | |
| 56 | +} // namespace br | |
| 57 | + | |
| 58 | +#endif // GALLERYTOOLBAR_H | ... | ... |
app/openbr-gui/galleryviewer.cpp
0 → 100644
| 1 | +#include "galleryviewer.h" | |
| 2 | + | |
| 3 | +using namespace br; | |
| 4 | + | |
| 5 | +GalleryViewer::GalleryViewer(QWidget *parent) | |
| 6 | + : QMainWindow(parent) | |
| 7 | +{ | |
| 8 | + addToolBar(Qt::TopToolBarArea, &gGallery); | |
| 9 | + addToolBar(Qt::BottomToolBarArea, &tmTemplateMetadata); | |
| 10 | + setCentralWidget(&tvgTemplateViewerGrid); | |
| 11 | + setWindowFlags(Qt::Widget); | |
| 12 | + | |
| 13 | + connect(&tvgTemplateViewerGrid, SIGNAL(newInput(br::File)), &gGallery, SLOT(enroll(br::File))); | |
| 14 | + connect(&tvgTemplateViewerGrid, SIGNAL(newInput(QImage)), &gGallery, SLOT(enroll(QImage))); | |
| 15 | + connect(&tvgTemplateViewerGrid, SIGNAL(selectedInput(br::File)), &gGallery, SLOT(select(br::File))); | |
| 16 | + | |
| 17 | + tmTemplateMetadata.addClassifier("GenderClassification", "MM0"); | |
| 18 | + tmTemplateMetadata.addClassifier("AgeRegression", "MM0"); | |
| 19 | + setFiles(FileList()); | |
| 20 | +} | |
| 21 | + | |
| 22 | +/*** PUBLIC SLOTS ***/ | |
| 23 | +void GalleryViewer::setAlgorithm(const QString &algorithm) | |
| 24 | +{ | |
| 25 | + tmTemplateMetadata.setAlgorithm(algorithm); | |
| 26 | + setFiles(FileList()); | |
| 27 | +} | |
| 28 | + | |
| 29 | +void GalleryViewer::setFiles(FileList files) | |
| 30 | +{ | |
| 31 | + bool same = true; | |
| 32 | + foreach (const File &file, files) { | |
| 33 | + if (file != files.first()) { | |
| 34 | + same = false; | |
| 35 | + break; | |
| 36 | + } | |
| 37 | + } | |
| 38 | + if (same) files = files.mid(0, 1); | |
| 39 | + | |
| 40 | + tvgTemplateViewerGrid.setFiles(files); | |
| 41 | + tmTemplateMetadata.setVisible(files.size() == 1); | |
| 42 | + if (files.size() == 1) | |
| 43 | + tmTemplateMetadata.setFile(files.first()); | |
| 44 | +} | |
| 45 | + | |
| 46 | +#include "moc_galleryviewer.cpp" | ... | ... |
app/openbr-gui/galleryviewer.h
0 → 100644
| 1 | +#ifndef GALLERYVIEWER_H | |
| 2 | +#define GALLERYVIEWER_H | |
| 3 | + | |
| 4 | +#include <QMainWindow> | |
| 5 | +#include <QWidget> | |
| 6 | +#include <openbr_plugin.h> | |
| 7 | + | |
| 8 | +#include "gallerytoolbar.h" | |
| 9 | +#include "templateviewergrid.h" | |
| 10 | +#include "templatemetadata.h" | |
| 11 | + | |
| 12 | +namespace br | |
| 13 | +{ | |
| 14 | + | |
| 15 | +class BR_EXPORT_GUI GalleryViewer : public QMainWindow | |
| 16 | +{ | |
| 17 | + Q_OBJECT | |
| 18 | + | |
| 19 | +public: | |
| 20 | + GalleryToolBar gGallery; | |
| 21 | + TemplateViewerGrid tvgTemplateViewerGrid; | |
| 22 | + TemplateMetadata tmTemplateMetadata; | |
| 23 | + | |
| 24 | + explicit GalleryViewer(QWidget *parent = 0); | |
| 25 | + | |
| 26 | +public slots: | |
| 27 | + void setAlgorithm(const QString &algorithm); | |
| 28 | + void setFiles(br::FileList files); | |
| 29 | +}; | |
| 30 | + | |
| 31 | +} // namespace br | |
| 32 | + | |
| 33 | +#endif // GALLERYVIEWER_H | ... | ... |
app/openbr-gui/help.cpp
0 → 100644
| 1 | +#include <QDesktopServices> | |
| 2 | +#include <QFile> | |
| 3 | +#include <QMessageBox> | |
| 4 | +#include <QString> | |
| 5 | +#include <QUrl> | |
| 6 | +#include <openbr.h> | |
| 7 | + | |
| 8 | +#include "help.h" | |
| 9 | + | |
| 10 | +/**** HELP ****/ | |
| 11 | +/*** PUBLIC ***/ | |
| 12 | +br::Help::Help(QWidget *parent) | |
| 13 | + : QMenu(parent) | |
| 14 | +{ | |
| 15 | + actions.append(QSharedPointer<QAction>(addAction("About"))); | |
| 16 | + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showAbout())); | |
| 17 | + actions.append(QSharedPointer<QAction>(addAction("Documentation"))); | |
| 18 | + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showDocumentation())); | |
| 19 | + actions.append(QSharedPointer<QAction>(addAction("License"))); | |
| 20 | + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showLicense())); | |
| 21 | +} | |
| 22 | + | |
| 23 | +/*** PUBLIC SLOTS ***/ | |
| 24 | +void br::Help::showAbout() | |
| 25 | +{ | |
| 26 | + QMessageBox::about(this, "About", br_about()); | |
| 27 | +} | |
| 28 | + | |
| 29 | +void br::Help::showDocumentation() | |
| 30 | +{ | |
| 31 | + QDesktopServices::openUrl(QUrl(QString("file:///%1/doc/html/index.html").arg(br_sdk_path()))); | |
| 32 | +} | |
| 33 | + | |
| 34 | +void br::Help::showLicense() | |
| 35 | +{ | |
| 36 | + QFile file(QString("%1/LICENSE.txt").arg(br_sdk_path())); | |
| 37 | + file.open(QFile::ReadOnly); | |
| 38 | + QString license = file.readAll(); | |
| 39 | + file.close(); | |
| 40 | + | |
| 41 | + QMessageBox::about(this, "License", license); | |
| 42 | +} | |
| 43 | + | |
| 44 | +#include "moc_help.cpp" | ... | ... |
app/openbr-gui/help.h
0 → 100644
| 1 | +#ifndef HELP_H | |
| 2 | +#define HELP_H | |
| 3 | + | |
| 4 | +#include <QAction> | |
| 5 | +#include <QList> | |
| 6 | +#include <QMenu> | |
| 7 | +#include <QSharedPointer> | |
| 8 | +#include <QWidget> | |
| 9 | +#include <openbr_export.h> | |
| 10 | + | |
| 11 | +namespace br | |
| 12 | +{ | |
| 13 | + | |
| 14 | +class BR_EXPORT_GUI Help : public QMenu | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + QList< QSharedPointer<QAction> > actions; | |
| 18 | + | |
| 19 | +public: | |
| 20 | + explicit Help(QWidget *parent = 0); | |
| 21 | + | |
| 22 | +public slots: | |
| 23 | + void showAbout(); | |
| 24 | + void showDocumentation(); | |
| 25 | + void showLicense(); | |
| 26 | +}; | |
| 27 | + | |
| 28 | +} // namespace br | |
| 29 | + | |
| 30 | +#endif // HELP_H | ... | ... |
app/openbr-gui/icons.qrc
0 → 100644
app/openbr-gui/icons/mitre_logo.png
0 → 100644
57.6 KB
app/openbr-gui/icons/mm.icns
0 → 100644
No preview for this file type
app/openbr-gui/icons/mm.ico
0 → 100644
No preview for this file type
app/openbr-gui/icons/mm.png
0 → 100644
12.2 KB
app/openbr-gui/imageviewer.h
app/openbr-gui/progress.cpp
0 → 100644
| 1 | +#include <openbr.h> | |
| 2 | +#include <QDebug> | |
| 3 | +#include "progress.h" | |
| 4 | + | |
| 5 | +/**** PROGRESS ****/ | |
| 6 | +/*** PUBLIC ***/ | |
| 7 | +br::Progress::Progress(QWidget *parent) | |
| 8 | + : QStatusBar(parent) | |
| 9 | +{ | |
| 10 | + pbProgress.setVisible(false); | |
| 11 | + pbProgress.setMaximum(100); | |
| 12 | + pbProgress.setMinimum(0); | |
| 13 | + pbProgress.setValue(0); | |
| 14 | + pbProgress.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); | |
| 15 | + pbProgress.setTextVisible(false); | |
| 16 | + | |
| 17 | + addWidget(&wSpacer, 1); | |
| 18 | + addPermanentWidget(&pbProgress); | |
| 19 | + addPermanentWidget(&lTimeRemaining); | |
| 20 | + connect(&timer, SIGNAL(timeout()), this, SLOT(checkProgress())); | |
| 21 | + timer.start(1000); | |
| 22 | +} | |
| 23 | + | |
| 24 | +/*** PRIVATE SLOTS ***/ | |
| 25 | +void br::Progress::checkProgress() | |
| 26 | +{ | |
| 27 | + const int progress = 100 * br_progress(); | |
| 28 | + const bool visible = progress >= 0; | |
| 29 | + | |
| 30 | + if (visible) { | |
| 31 | + showMessage(br_most_recent_message()); | |
| 32 | + pbProgress.setValue(progress); | |
| 33 | + if (progress > 100) pbProgress.setMaximum(0); | |
| 34 | + else pbProgress.setMaximum(100); | |
| 35 | + | |
| 36 | + int s = br_time_remaining(); | |
| 37 | + if (s >= 0) { | |
| 38 | + int h = s / (60*60); | |
| 39 | + int m = (s - h*60*60) / 60; | |
| 40 | + s = (s - h*60*60 - m*60); | |
| 41 | + lTimeRemaining.setText(QString("%1:%2:%3").arg(h, 2, 10, QLatin1Char('0')).arg(m, 2, 10, QLatin1Char('0')).arg(s, 2, 10, QLatin1Char('0'))); | |
| 42 | + } else { | |
| 43 | + lTimeRemaining.clear(); | |
| 44 | + } | |
| 45 | + } else { | |
| 46 | + clearMessage(); | |
| 47 | + lTimeRemaining.clear(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + pbProgress.setVisible(visible); | |
| 51 | +} | |
| 52 | + | |
| 53 | +#include "moc_progress.cpp" | ... | ... |
app/openbr-gui/progress.h
0 → 100644
| 1 | +#ifndef __PROGRESS_H | |
| 2 | +#define __PROGRESS_H | |
| 3 | + | |
| 4 | +#include <QLabel> | |
| 5 | +#include <QProgressBar> | |
| 6 | +#include <QStatusBar> | |
| 7 | +#include <QTimer> | |
| 8 | +#include <QWidget> | |
| 9 | +#include <openbr_export.h> | |
| 10 | + | |
| 11 | +namespace br | |
| 12 | +{ | |
| 13 | + | |
| 14 | +class BR_EXPORT_GUI Progress : public QStatusBar | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + QWidget wSpacer; | |
| 18 | + QProgressBar pbProgress; | |
| 19 | + QLabel lTimeRemaining; | |
| 20 | + QTimer timer; | |
| 21 | + | |
| 22 | +public: | |
| 23 | + explicit Progress(QWidget *parent = 0); | |
| 24 | + | |
| 25 | +private slots: | |
| 26 | + void checkProgress(); | |
| 27 | +}; | |
| 28 | + | |
| 29 | +} | |
| 30 | + | |
| 31 | +#endif // __PROGRESS_H | ... | ... |
app/openbr-gui/score.cpp
0 → 100644
| 1 | +#include <QDebug> | |
| 2 | +#include "score.h" | |
| 3 | + | |
| 4 | +/**** SCORE ****/ | |
| 5 | +/*** PUBLIC ***/ | |
| 6 | +br::Score::Score(QWidget *parent) | |
| 7 | + : QLabel(parent) | |
| 8 | +{ | |
| 9 | + setToolTip("Similarity Score"); | |
| 10 | +} | |
| 11 | + | |
| 12 | +/*** PUBLIC SLOTS ***/ | |
| 13 | +void br::Score::setScore(float score) | |
| 14 | +{ | |
| 15 | + setText("<b>Similarity:</b> " + QString::number(score, 'f', 2)); | |
| 16 | +} | |
| 17 | + | |
| 18 | +#include "moc_score.cpp" | ... | ... |
app/openbr-gui/score.h
0 → 100644
| 1 | +#ifndef __SCORE_H | |
| 2 | +#define __SCORE_H | |
| 3 | + | |
| 4 | +#include <QLabel> | |
| 5 | +#include <QWidget> | |
| 6 | +#include <openbr_export.h> | |
| 7 | + | |
| 8 | +namespace br | |
| 9 | +{ | |
| 10 | + | |
| 11 | +class BR_EXPORT_GUI Score : public QLabel | |
| 12 | +{ | |
| 13 | + Q_OBJECT | |
| 14 | + | |
| 15 | +public: | |
| 16 | + explicit Score(QWidget *parent = 0); | |
| 17 | + | |
| 18 | +public slots: | |
| 19 | + void setScore(float score); | |
| 20 | +}; | |
| 21 | + | |
| 22 | +} | |
| 23 | + | |
| 24 | +#endif // __SCORE_H | ... | ... |
app/openbr-gui/splashscreen.cpp
0 → 100644
| 1 | +#include <QPixmap> | |
| 2 | +#include <openbr_plugin.h> | |
| 3 | + | |
| 4 | +#include "splashscreen.h" | |
| 5 | + | |
| 6 | +using namespace br; | |
| 7 | + | |
| 8 | +/**** SPLASH_SCREEN ****/ | |
| 9 | +/*** PUBLIC ***/ | |
| 10 | +SplashScreen::SplashScreen() | |
| 11 | + : QSplashScreen(QPixmap(":/icons/mm.png").scaledToWidth(384, Qt::SmoothTransformation)) | |
| 12 | +{ | |
| 13 | + connect(&timer, SIGNAL(timeout()), this, SLOT(updateMessage())); | |
| 14 | + timer.start(100); | |
| 15 | +} | |
| 16 | + | |
| 17 | +/*** PROTECTED ***/ | |
| 18 | +void SplashScreen::closeEvent(QCloseEvent *event) | |
| 19 | +{ | |
| 20 | + QSplashScreen::closeEvent(event); | |
| 21 | + event->accept(); | |
| 22 | + timer.stop(); | |
| 23 | +} | |
| 24 | + | |
| 25 | +/*** PRIVATE SLOTS ***/ | |
| 26 | +void SplashScreen::updateMessage() | |
| 27 | +{ | |
| 28 | + showMessage("Version " + Context::version() + " " + QChar(169) + " MITRE 2012\n" + Globals->mostRecentMessage, Qt::AlignHCenter | Qt::AlignBottom); | |
| 29 | +} | |
| 30 | + | |
| 31 | +#include "moc_splashscreen.cpp" | ... | ... |
app/openbr-gui/splashscreen.h
0 → 100644
| 1 | +#ifndef __SPLASHSCREEN_H | |
| 2 | +#define __SPLASHSCREEN_H | |
| 3 | + | |
| 4 | +#include <QCloseEvent> | |
| 5 | +#include <QLabel> | |
| 6 | +#include <QSplashScreen> | |
| 7 | +#include <QTimer> | |
| 8 | +#include <QWidget> | |
| 9 | +#include <openbr_export.h> | |
| 10 | + | |
| 11 | +namespace br | |
| 12 | +{ | |
| 13 | + | |
| 14 | +class BR_EXPORT_GUI SplashScreen : public QSplashScreen | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + QTimer timer; | |
| 18 | + | |
| 19 | +public: | |
| 20 | + SplashScreen(); | |
| 21 | + | |
| 22 | +protected: | |
| 23 | + void closeEvent(QCloseEvent *event); | |
| 24 | + | |
| 25 | +private slots: | |
| 26 | + void updateMessage(); | |
| 27 | +}; | |
| 28 | + | |
| 29 | +} // namespace br | |
| 30 | + | |
| 31 | +#endif // __SPLASHSCREEN_H | ... | ... |
app/openbr-gui/templatemetadata.cpp
0 → 100644
| 1 | +#include <QAction> | |
| 2 | +#include <QFileInfo> | |
| 3 | + | |
| 4 | +#include "templatemetadata.h" | |
| 5 | + | |
| 6 | +/*** PUBLIC ***/ | |
| 7 | +br::TemplateMetadata::TemplateMetadata(QWidget *parent) | |
| 8 | + : QToolBar("Template Metadata", parent) | |
| 9 | +{ | |
| 10 | + lFile.setTextInteractionFlags(Qt::TextSelectableByMouse); | |
| 11 | + wSpacer.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); | |
| 12 | + addWidget(&wOffset); | |
| 13 | + addWidget(&lFile); | |
| 14 | + addWidget(&wSpacer); | |
| 15 | + addWidget(&lQuality); | |
| 16 | +} | |
| 17 | + | |
| 18 | +void br::TemplateMetadata::addClassifier(const QString &classifier_, const QString algorithm) | |
| 19 | +{ | |
| 20 | + QSharedPointer<Classifier> classifier(new Classifier()); | |
| 21 | + classifier->setAlgorithm(classifier_); | |
| 22 | + QAction *action = addWidget(classifier.data()); | |
| 23 | + conditionalClassifiers.append(ConditionalClassifier(algorithm, classifier, action)); | |
| 24 | +} | |
| 25 | + | |
| 26 | +/**** PRIVATE SLOTS ****/ | |
| 27 | +void br::TemplateMetadata::setFile(const br::File &file) | |
| 28 | +{ | |
| 29 | + if (file.isNull()) lFile.clear(); | |
| 30 | + else lFile.setText("<b>File:</b> " + file.fileName()); | |
| 31 | + lQuality.setText(QString("<b>Quality:</b> %1").arg(file.getBool("FTE") ? "Low" : "High")); | |
| 32 | + foreach (const ConditionalClassifier &classifier, conditionalClassifiers) | |
| 33 | + if (classifier.action->isVisible()) classifier.classifier->classify(file); | |
| 34 | +} | |
| 35 | + | |
| 36 | +void br::TemplateMetadata::setAlgorithm(const QString &algorithm) | |
| 37 | +{ | |
| 38 | + foreach (const ConditionalClassifier &classifier, conditionalClassifiers) { | |
| 39 | + classifier.classifier->clear(); | |
| 40 | + classifier.action->setVisible(classifier.algorithm.isEmpty() || classifier.algorithm == algorithm); | |
| 41 | + } | |
| 42 | +} | |
| 43 | + | |
| 44 | +#include "moc_templatemetadata.cpp" | ... | ... |
app/openbr-gui/templatemetadata.h
0 → 100644
| 1 | +#ifndef __TEMPLATEMETADATA_H | |
| 2 | +#define __TEMPLATEMETADATA_H | |
| 3 | + | |
| 4 | +#include <QLabel> | |
| 5 | +#include <QList> | |
| 6 | +#include <QPair> | |
| 7 | +#include <QSharedPointer> | |
| 8 | +#include <QToolBar> | |
| 9 | +#include <QWidget> | |
| 10 | +#include <openbr_plugin.h> | |
| 11 | + | |
| 12 | +#include "classifier.h" | |
| 13 | + | |
| 14 | +namespace br | |
| 15 | +{ | |
| 16 | + | |
| 17 | +class BR_EXPORT_GUI TemplateMetadata : public QToolBar | |
| 18 | +{ | |
| 19 | + Q_OBJECT | |
| 20 | + QLabel lFile, lQuality; | |
| 21 | + QWidget wOffset, wSpacer; | |
| 22 | + | |
| 23 | + struct ConditionalClassifier | |
| 24 | + { | |
| 25 | + QString algorithm; | |
| 26 | + QSharedPointer<Classifier> classifier; | |
| 27 | + QAction *action; | |
| 28 | + | |
| 29 | + ConditionalClassifier() : action(NULL) {} | |
| 30 | + ConditionalClassifier(const QString &algorithm_, const QSharedPointer<Classifier> &classifier_, QAction *action_) | |
| 31 | + : algorithm(algorithm_), classifier(classifier_), action(action_) {} | |
| 32 | + }; | |
| 33 | + QList<ConditionalClassifier> conditionalClassifiers; | |
| 34 | + | |
| 35 | +public: | |
| 36 | + explicit TemplateMetadata(QWidget *parent = 0); | |
| 37 | + void addClassifier(const QString &classifier, const QString algorithm = ""); | |
| 38 | + | |
| 39 | +public slots: | |
| 40 | + void setFile(const br::File &file); | |
| 41 | + void setAlgorithm(const QString &algorithm); | |
| 42 | +}; | |
| 43 | + | |
| 44 | +} // namespace br | |
| 45 | + | |
| 46 | +#endif // TEMPLATEMETADATA_H | ... | ... |
app/openbr-gui/templateviewer.cpp
0 → 100644
| 1 | +#include <QColor> | |
| 2 | +#include <QPainter> | |
| 3 | +#include <QPen> | |
| 4 | +#include <QUrl> | |
| 5 | +#include <QtConcurrentRun> | |
| 6 | +#include <openbr.h> | |
| 7 | + | |
| 8 | +#include "templateviewer.h" | |
| 9 | + | |
| 10 | +using namespace br; | |
| 11 | + | |
| 12 | +/*** STATIC ***/ | |
| 13 | +const int NumLandmarks = 2; | |
| 14 | + | |
| 15 | +static bool lessThan(const QPointF &a, const QPointF &b) | |
| 16 | +{ | |
| 17 | + return ((a.x() < b.x()) || | |
| 18 | + ((a.x() == b.x()) && (a.y() < b.y()))); | |
| 19 | +} | |
| 20 | + | |
| 21 | +/*** PUBLIC ***/ | |
| 22 | +TemplateViewer::TemplateViewer(QWidget *parent) | |
| 23 | + : ImageViewer(parent) | |
| 24 | +{ | |
| 25 | + setAcceptDrops(true); | |
| 26 | + setMouseTracking(true); | |
| 27 | + setText("<b>Drag Photo or Folder Here</b>\n"); | |
| 28 | + format = "Registered"; | |
| 29 | + editable = true; | |
| 30 | + setFile(File()); | |
| 31 | + update(); | |
| 32 | +} | |
| 33 | + | |
| 34 | +/*** PUBLIC SLOTS ***/ | |
| 35 | +void TemplateViewer::setFile(const File &file_) | |
| 36 | +{ | |
| 37 | + this->file = file_; | |
| 38 | + | |
| 39 | + // Update landmarks | |
| 40 | + landmarks.clear(); | |
| 41 | + if (file.contains("Affine_0_X") && file.contains("Affine_0_Y")) | |
| 42 | + landmarks.append(QPointF(file.getFloat("Affine_0_X"), file.getFloat("Affine_0_Y"))); | |
| 43 | + if (file.contains("Affine_1_X") && file.contains("Affine_1_Y")) | |
| 44 | + landmarks.append(QPointF(file.getFloat("Affine_1_X"), file.getFloat("Affine_1_Y"))); | |
| 45 | + while (landmarks.size() < NumLandmarks) | |
| 46 | + landmarks.append(QPointF()); | |
| 47 | + nearestLandmark = -1; | |
| 48 | + | |
| 49 | + QtConcurrent::run(this, &TemplateViewer::refreshImage); | |
| 50 | +} | |
| 51 | + | |
| 52 | +void TemplateViewer::setEditable(bool enabled) | |
| 53 | +{ | |
| 54 | + editable = enabled; | |
| 55 | + update(); | |
| 56 | +} | |
| 57 | + | |
| 58 | +void TemplateViewer::setMousePoint(const QPointF &mousePoint) | |
| 59 | +{ | |
| 60 | + this->mousePoint = mousePoint; | |
| 61 | + update(); | |
| 62 | +} | |
| 63 | + | |
| 64 | +void TemplateViewer::setFormat(const QString &format) | |
| 65 | +{ | |
| 66 | + this->format = format; | |
| 67 | + QtConcurrent::run(this, &TemplateViewer::refreshImage); | |
| 68 | +} | |
| 69 | + | |
| 70 | +/*** PRIVATE ***/ | |
| 71 | +void TemplateViewer::refreshImage() | |
| 72 | +{ | |
| 73 | + if (file.isNull() || (format == "Photo")) { | |
| 74 | + setImage(file, true); | |
| 75 | + } else { | |
| 76 | + const QString path = QString(br_scratch_path()) + "/thumbnails"; | |
| 77 | + const QString hash = file.hash()+format; | |
| 78 | + const QString processedFile = path+"/"+file.baseName()+hash+".png"; | |
| 79 | + if (!QFileInfo(processedFile).exists()) { | |
| 80 | + if (format == "Registered") | |
| 81 | + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=RegisterAffine]"); | |
| 82 | + else if (format == "Enhanced") | |
| 83 | + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=ContrastEnhanced]"); | |
| 84 | + else if (format == "Features") | |
| 85 | + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=ColoredLBP]"); | |
| 86 | + } | |
| 87 | + setImage(processedFile, true); | |
| 88 | + } | |
| 89 | +} | |
| 90 | + | |
| 91 | +QPointF TemplateViewer::getImagePoint(const QPointF &sp) const | |
| 92 | +{ | |
| 93 | + if (!pixmap() || isNull()) return QPointF(); | |
| 94 | + QPointF ip = QPointF(imageWidth()*(sp.x() - (width() - pixmap()->width())/2)/pixmap()->width(), | |
| 95 | + imageHeight()*(sp.y() - (height() - pixmap()->height())/2)/pixmap()->height()); | |
| 96 | + if ((ip.x() < 0) || (ip.x() > imageWidth()) || (ip.y() < 0) || (ip.y() > imageHeight())) return QPointF(); | |
| 97 | + return ip; | |
| 98 | +} | |
| 99 | + | |
| 100 | +QPointF TemplateViewer::getScreenPoint(const QPointF &ip) const | |
| 101 | +{ | |
| 102 | + if (!pixmap() || isNull()) return QPointF(); | |
| 103 | + return QPointF(ip.x()*pixmap()->width()/imageWidth() + (width()-pixmap()->width())/2, | |
| 104 | + ip.y()*pixmap()->height()/imageHeight() + (height()-pixmap()->height())/2); | |
| 105 | +} | |
| 106 | + | |
| 107 | +/*** PROTECTED SLOTS ***/ | |
| 108 | +void TemplateViewer::dragEnterEvent(QDragEnterEvent *event) | |
| 109 | +{ | |
| 110 | + ImageViewer::dragEnterEvent(event); | |
| 111 | + event->accept(); | |
| 112 | + | |
| 113 | + if (event->mimeData()->hasUrls() || event->mimeData()->hasImage()) | |
| 114 | + event->acceptProposedAction(); | |
| 115 | +} | |
| 116 | + | |
| 117 | +void TemplateViewer::dropEvent(QDropEvent *event) | |
| 118 | +{ | |
| 119 | + ImageViewer::dropEvent(event); | |
| 120 | + event->accept(); | |
| 121 | + | |
| 122 | + event->acceptProposedAction(); | |
| 123 | + const QMimeData *mimeData = event->mimeData(); | |
| 124 | + if (mimeData->hasImage()) { | |
| 125 | + QImage input = qvariant_cast<QImage>(mimeData->imageData()); | |
| 126 | + emit newInput(input); | |
| 127 | + } else if (event->mimeData()->hasUrls()) { | |
| 128 | + File input; | |
| 129 | + foreach (const QUrl &url, mimeData->urls()) { | |
| 130 | + if (!url.isValid()) continue; | |
| 131 | + if (url.toString().startsWith("http://images.google.com/search")) continue; // Not a true image URL | |
| 132 | + const QString localFile = url.toLocalFile(); | |
| 133 | + if (localFile.isNull()) input.append(url.toString()); | |
| 134 | + else input.append(localFile); | |
| 135 | + } | |
| 136 | + if (input.isNull()) return; | |
| 137 | + emit newInput(input); | |
| 138 | + } | |
| 139 | +} | |
| 140 | + | |
| 141 | +void TemplateViewer::leaveEvent(QEvent *event) | |
| 142 | +{ | |
| 143 | + ImageViewer::leaveEvent(event); | |
| 144 | + event->accept(); | |
| 145 | + clearFocus(); | |
| 146 | + | |
| 147 | + nearestLandmark = -1; | |
| 148 | + mousePoint = QPointF(); | |
| 149 | + emit newMousePoint(mousePoint); | |
| 150 | + unsetCursor(); | |
| 151 | + update(); | |
| 152 | +} | |
| 153 | + | |
| 154 | +void TemplateViewer::mouseMoveEvent(QMouseEvent *event) | |
| 155 | +{ | |
| 156 | + ImageViewer::mouseMoveEvent(event); | |
| 157 | + event->accept(); | |
| 158 | + | |
| 159 | + mousePoint = getImagePoint(event->pos()); | |
| 160 | + nearestLandmark = -1; | |
| 161 | + if (!mousePoint.isNull()) { | |
| 162 | + double nearestDistance = std::numeric_limits<double>::max(); | |
| 163 | + for (int i=0; i<NumLandmarks; i++) { | |
| 164 | + if (landmarks[i].isNull()) { | |
| 165 | + nearestLandmark = -1; | |
| 166 | + break; | |
| 167 | + } | |
| 168 | + | |
| 169 | + double dist = sqrt(pow(mousePoint.x() - landmarks[i].x(), 2.0) + pow(mousePoint.y() - landmarks[i].y(), 2.0)); | |
| 170 | + if (dist < nearestDistance) { | |
| 171 | + nearestDistance = dist; | |
| 172 | + nearestLandmark = i; | |
| 173 | + } | |
| 174 | + } | |
| 175 | + | |
| 176 | + if (format == "Photo") unsetCursor(); | |
| 177 | + else setCursor(Qt::BlankCursor); | |
| 178 | + } else { | |
| 179 | + unsetCursor(); | |
| 180 | + } | |
| 181 | + | |
| 182 | + emit newMousePoint(mousePoint); | |
| 183 | + update(); | |
| 184 | +} | |
| 185 | + | |
| 186 | +void TemplateViewer::mousePressEvent(QMouseEvent *event) | |
| 187 | +{ | |
| 188 | + ImageViewer::mousePressEvent(event); | |
| 189 | + event->accept(); | |
| 190 | + | |
| 191 | + if (isNull() || getImagePoint(event->pos()).isNull()) return; | |
| 192 | + | |
| 193 | + if (!editable || (format != "Photo")) { | |
| 194 | + emit selectedInput(file); | |
| 195 | + return; | |
| 196 | + } | |
| 197 | + | |
| 198 | + int index; | |
| 199 | + for (index=0; index<NumLandmarks; index++) | |
| 200 | + if (landmarks[index].isNull()) break; | |
| 201 | + | |
| 202 | + if ((event->button() == Qt::RightButton) || (index == NumLandmarks)) { | |
| 203 | + // Remove nearest point | |
| 204 | + if (nearestLandmark == -1) return; | |
| 205 | + index = nearestLandmark; | |
| 206 | + landmarks[nearestLandmark] = QPointF(); | |
| 207 | + nearestLandmark = -1; | |
| 208 | + } | |
| 209 | + | |
| 210 | + if (event->button() == Qt::LeftButton) { | |
| 211 | + // Add a point | |
| 212 | + landmarks[index] = getImagePoint(event->pos()); | |
| 213 | + qSort(landmarks.begin(), landmarks.end(), lessThan); | |
| 214 | + if (!landmarks.contains(QPointF())) | |
| 215 | + emit selectedInput(file.name+QString("[Affine_0_X=%1, Affine_0_Y=%2, Affine_1_X=%3, Affine_1_Y=%4, forceEnrollment]").arg(QString::number(landmarks[0].x()), | |
| 216 | + QString::number(landmarks[0].y()), | |
| 217 | + QString::number(landmarks[1].x()), | |
| 218 | + QString::number(landmarks[1].y()))); | |
| 219 | + } | |
| 220 | + | |
| 221 | + update(); | |
| 222 | +} | |
| 223 | + | |
| 224 | +void TemplateViewer::paintEvent(QPaintEvent *event) | |
| 225 | +{ | |
| 226 | + static const QColor nearest(0, 0, 255, 192); | |
| 227 | + static const QColor normal(0, 255, 0, 192); | |
| 228 | + | |
| 229 | + ImageViewer::paintEvent(event); | |
| 230 | + event->accept(); | |
| 231 | + | |
| 232 | + QPainter painter(this); | |
| 233 | + painter.setRenderHint(QPainter::Antialiasing, true); | |
| 234 | + | |
| 235 | + if (format == "Photo") { | |
| 236 | + for (int i=0; i<NumLandmarks; i++) { | |
| 237 | + if (!landmarks[i].isNull()) { | |
| 238 | + if ((i == nearestLandmark) && | |
| 239 | + editable) painter.setBrush(QBrush(nearest)); | |
| 240 | + else painter.setBrush(QBrush(normal)); | |
| 241 | + painter.drawEllipse(getScreenPoint(landmarks[i]), 4, 4); | |
| 242 | + } | |
| 243 | + } | |
| 244 | + } else { | |
| 245 | + if (!mousePoint.isNull() && !isNull()) { | |
| 246 | + painter.setPen(QPen(normal)); | |
| 247 | + painter.drawLine(getScreenPoint(QPointF(mousePoint.x(), 0)), getScreenPoint(QPointF(mousePoint.x(), imageHeight()))); | |
| 248 | + painter.drawLine(getScreenPoint(QPointF(0, mousePoint.y())), getScreenPoint(QPointF(imageWidth(), mousePoint.y()))); | |
| 249 | + } | |
| 250 | + } | |
| 251 | +} | |
| 252 | + | |
| 253 | +#include "moc_templateviewer.cpp" | ... | ... |
app/openbr-gui/templateviewer.h
0 → 100644
| 1 | +#ifndef __TEMPLATEVIEWER_H | |
| 2 | +#define __TEMPLATEVIEWER_H | |
| 3 | + | |
| 4 | +#include <QDragEnterEvent> | |
| 5 | +#include <QDropEvent> | |
| 6 | +#include <QEvent> | |
| 7 | +#include <QImage> | |
| 8 | +#include <QList> | |
| 9 | +#include <QMouseEvent> | |
| 10 | +#include <QPointF> | |
| 11 | +#include <QString> | |
| 12 | +#include <QWidget> | |
| 13 | +#include <openbr_plugin.h> | |
| 14 | + | |
| 15 | +#include "openbr-gui/imageviewer.h" | |
| 16 | + | |
| 17 | +namespace br | |
| 18 | +{ | |
| 19 | + | |
| 20 | +class BR_EXPORT_GUI TemplateViewer : public br::ImageViewer | |
| 21 | +{ | |
| 22 | + Q_OBJECT | |
| 23 | + br::File file; | |
| 24 | + QPointF mousePoint; | |
| 25 | + QString format; | |
| 26 | + | |
| 27 | + bool editable; | |
| 28 | + QList<QPointF> landmarks; | |
| 29 | + int nearestLandmark; | |
| 30 | + | |
| 31 | +public: | |
| 32 | + explicit TemplateViewer(QWidget *parent = 0); | |
| 33 | + | |
| 34 | +public slots: | |
| 35 | + void setFile(const br::File &file); | |
| 36 | + void setEditable(bool enabled); | |
| 37 | + void setMousePoint(const QPointF &mousePoint); | |
| 38 | + void setFormat(const QString &format); | |
| 39 | + | |
| 40 | +private: | |
| 41 | + void refreshImage(); | |
| 42 | + QPointF getImagePoint(const QPointF &sp) const; | |
| 43 | + QPointF getScreenPoint(const QPointF &ip) const; | |
| 44 | + | |
| 45 | +protected slots: | |
| 46 | + void dragEnterEvent(QDragEnterEvent *event); | |
| 47 | + void dropEvent(QDropEvent *event); | |
| 48 | + void leaveEvent(QEvent *event); | |
| 49 | + void mouseMoveEvent(QMouseEvent *event); | |
| 50 | + void mousePressEvent(QMouseEvent *event); | |
| 51 | + void paintEvent(QPaintEvent *event); | |
| 52 | + | |
| 53 | +signals: | |
| 54 | + void newInput(br::File input); | |
| 55 | + void newInput(QImage input); | |
| 56 | + void newMousePoint(QPointF mousePoint); | |
| 57 | + void selectedInput(br::File input); | |
| 58 | +}; | |
| 59 | + | |
| 60 | +} | |
| 61 | + | |
| 62 | +#endif // TEMPLATEVIEWER_H | ... | ... |
app/openbr-gui/templateviewergrid.cpp
0 → 100644
| 1 | +#include "templateviewergrid.h" | |
| 2 | + | |
| 3 | +using namespace br; | |
| 4 | + | |
| 5 | +/**** TEMPLATE_VIEWER_GRID ****/ | |
| 6 | +/*** PUBLIC ***/ | |
| 7 | +TemplateViewerGrid::TemplateViewerGrid(QWidget *parent) | |
| 8 | + : QWidget(parent) | |
| 9 | +{ | |
| 10 | + setLayout(&gridLayout); | |
| 11 | + setFiles(FileList(16)); | |
| 12 | + setFiles(FileList(1)); | |
| 13 | +} | |
| 14 | + | |
| 15 | +/*** PUBLIC SLOTS ***/ | |
| 16 | +void TemplateViewerGrid::setFiles(const FileList &files) | |
| 17 | +{ | |
| 18 | + const int size = std::max(1, (int)ceil(sqrt((float)files.size()))); | |
| 19 | + while (templateViewers.size() < size*size) { | |
| 20 | + templateViewers.append(QSharedPointer<TemplateViewer>(new TemplateViewer())); | |
| 21 | + connect(templateViewers.last().data(), SIGNAL(newInput(br::File)), this, SIGNAL(newInput(br::File))); | |
| 22 | + connect(templateViewers.last().data(), SIGNAL(newInput(QImage)), this, SIGNAL(newInput(QImage))); | |
| 23 | + connect(templateViewers.last().data(), SIGNAL(newMousePoint(QPointF)), this, SIGNAL(newMousePoint(QPointF))); | |
| 24 | + connect(templateViewers.last().data(), SIGNAL(selectedInput(br::File)), this, SIGNAL(selectedInput(br::File))); | |
| 25 | + } | |
| 26 | + | |
| 27 | + { // Clear layout | |
| 28 | + QLayoutItem *child; | |
| 29 | + while ((child = gridLayout.takeAt(0)) != 0) | |
| 30 | + child->widget()->setVisible(false); | |
| 31 | + } | |
| 32 | + | |
| 33 | + for (int i=0; i<templateViewers.size(); i++) { | |
| 34 | + if (i < size*size) { | |
| 35 | + gridLayout.addWidget(templateViewers[i].data(), i/size, i%size, 1, 1); | |
| 36 | + templateViewers[i]->setVisible(true); | |
| 37 | + } | |
| 38 | + | |
| 39 | + if (i < files.size()) { | |
| 40 | + templateViewers[i]->setFile(files[i]); | |
| 41 | + } else { | |
| 42 | + templateViewers[i]->setDefaultText("<b>"+ (size > 1 ? QString() : QString("Drag Photo or Folder Here")) +"</b>"); | |
| 43 | + templateViewers[i]->setFile(QString()); | |
| 44 | + } | |
| 45 | + | |
| 46 | + templateViewers[i]->setEditable(files.size() == 1); | |
| 47 | + } | |
| 48 | +} | |
| 49 | + | |
| 50 | +void TemplateViewerGrid::setFormat(const QString &format) | |
| 51 | +{ | |
| 52 | + foreach (const QSharedPointer<TemplateViewer> &templateViewer, templateViewers) | |
| 53 | + templateViewer->setFormat(format); | |
| 54 | +} | |
| 55 | + | |
| 56 | +void TemplateViewerGrid::setMousePoint(const QPointF &mousePoint) | |
| 57 | +{ | |
| 58 | + foreach (const QSharedPointer<TemplateViewer> &templateViewer, templateViewers) | |
| 59 | + templateViewer->setMousePoint(mousePoint); | |
| 60 | +} | |
| 61 | + | |
| 62 | +#include "moc_templateviewergrid.cpp" | ... | ... |
app/openbr-gui/templateviewergrid.h
0 → 100644
| 1 | +#ifndef __TEMPLATE_VIEWER_GRID_H | |
| 2 | +#define __TEMPLATE_VIEWER_GRID_H | |
| 3 | + | |
| 4 | +#include <QGridLayout> | |
| 5 | +#include <QList> | |
| 6 | +#include <QSharedPointer> | |
| 7 | +#include <openbr_plugin.h> | |
| 8 | + | |
| 9 | +#include "templateviewer.h" | |
| 10 | + | |
| 11 | +namespace br | |
| 12 | +{ | |
| 13 | + | |
| 14 | +class BR_EXPORT_GUI TemplateViewerGrid : public QWidget | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + | |
| 18 | + QGridLayout gridLayout; | |
| 19 | + QList< QSharedPointer<TemplateViewer> > templateViewers; | |
| 20 | + | |
| 21 | +public: | |
| 22 | + explicit TemplateViewerGrid(QWidget *parent = 0); | |
| 23 | + | |
| 24 | +public slots: | |
| 25 | + void setFiles(const br::FileList &file); | |
| 26 | + void setFormat(const QString &format); | |
| 27 | + void setMousePoint(const QPointF &mousePoint); | |
| 28 | + | |
| 29 | +signals: | |
| 30 | + void newInput(br::File input); | |
| 31 | + void newInput(QImage input); | |
| 32 | + void newMousePoint(QPointF mousePoint); | |
| 33 | + void selectedInput(br::File input); | |
| 34 | +}; | |
| 35 | + | |
| 36 | +} // namespace br | |
| 37 | + | |
| 38 | +#endif // __TEMPLATE_VIEWER_GRID_H | ... | ... |
app/openbr-gui/utility.cpp
0 → 100644
| 1 | +#include <QImage> | |
| 2 | +#include <limits> | |
| 3 | +#include <vector> | |
| 4 | +#include <opencv2/imgproc/imgproc.hpp> | |
| 5 | + | |
| 6 | +using namespace cv; | |
| 7 | + | |
| 8 | +/**** STATIC ****/ | |
| 9 | +QImage toQImage(const Mat &mat) | |
| 10 | +{ | |
| 11 | + // Convert to 8U depth | |
| 12 | + Mat mat8u; | |
| 13 | + if (mat.depth() != CV_8U) { | |
| 14 | + double globalMin = std::numeric_limits<double>::max(); | |
| 15 | + double globalMax = -std::numeric_limits<double>::max(); | |
| 16 | + | |
| 17 | + std::vector<Mat> mv; | |
| 18 | + split(mat, mv); | |
| 19 | + for (size_t i=0; i<mv.size(); i++) { | |
| 20 | + double min, max; | |
| 21 | + minMaxLoc(mv[i], &min, &max); | |
| 22 | + globalMin = std::min(globalMin, min); | |
| 23 | + globalMax = std::max(globalMax, max); | |
| 24 | + } | |
| 25 | + assert(globalMax >= globalMin); | |
| 26 | + | |
| 27 | + double range = globalMax - globalMin; | |
| 28 | + if (range != 0) { | |
| 29 | + double scale = 255 / range; | |
| 30 | + convertScaleAbs(mat, mat8u, scale, -(globalMin * scale)); | |
| 31 | + } else { | |
| 32 | + // Monochromatic | |
| 33 | + mat8u = Mat(mat.size(), CV_8UC1, Scalar((globalMin+globalMax)/2)); | |
| 34 | + } | |
| 35 | + } else { | |
| 36 | + mat8u = mat; | |
| 37 | + } | |
| 38 | + | |
| 39 | + // Convert to 3 channels | |
| 40 | + Mat mat8uc3; | |
| 41 | + if (mat8u.channels() == 4) cvtColor(mat8u, mat8uc3, CV_BGRA2RGB); | |
| 42 | + else if (mat8u.channels() == 3) cvtColor(mat8u, mat8uc3, CV_BGR2RGB); | |
| 43 | + else if (mat8u.channels() == 1) cvtColor(mat8u, mat8uc3, CV_GRAY2RGB); | |
| 44 | + | |
| 45 | + return QImage(mat8uc3.data, mat8uc3.cols, mat8uc3.rows, 3*mat8uc3.cols, QImage::Format_RGB888).copy(); | |
| 46 | +} | ... | ... |
app/openbr-gui/utility.h
0 → 100644
app/openbr-gui/view.cpp
0 → 100644
| 1 | +#include "view.h" | |
| 2 | + | |
| 3 | +/**** VIEW ****/ | |
| 4 | +/*** PUBLIC ***/ | |
| 5 | +View::View(QWidget *parent) | |
| 6 | + : QToolBar(parent) | |
| 7 | + , agFormat(parent) | |
| 8 | + , agCount(parent) | |
| 9 | +{ | |
| 10 | + agFormat.addAction("Photo"); | |
| 11 | + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_D); | |
| 12 | + agFormat.addAction("Registered"); | |
| 13 | + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_R); | |
| 14 | + agFormat.addAction("Enhanced"); | |
| 15 | + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_E); | |
| 16 | + agFormat.addAction("Features"); | |
| 17 | + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_F); | |
| 18 | + | |
| 19 | + agCount.addAction("1"); | |
| 20 | + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_1); | |
| 21 | + agCount.addAction("4"); | |
| 22 | + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_2); | |
| 23 | + agCount.addAction("9"); | |
| 24 | + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_3); | |
| 25 | + agCount.addAction("16"); | |
| 26 | + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_4); | |
| 27 | + | |
| 28 | + addActions(agFormat.actions()); | |
| 29 | + addSeparator(); | |
| 30 | + addActions(agCount.actions()); | |
| 31 | + | |
| 32 | + setToolTip("View"); | |
| 33 | + | |
| 34 | + connect(&agFormat, SIGNAL(triggered(QAction*)), this, SLOT(formatChanged(QAction*))); | |
| 35 | + connect(&agCount, SIGNAL(triggered(QAction*)), this, SLOT(countChanged(QAction*))); | |
| 36 | + | |
| 37 | + foreach (QAction *action, agCount.actions()) | |
| 38 | + action->setCheckable(true); | |
| 39 | + agCount.actions()[2]->setChecked(true); | |
| 40 | + | |
| 41 | + foreach (QAction *action, agFormat.actions()) | |
| 42 | + action->setCheckable(true); | |
| 43 | + agFormat.actions()[1]->setChecked(true); | |
| 44 | +} | |
| 45 | + | |
| 46 | +/*** PRIVATE SLOTS ***/ | |
| 47 | +void View::formatChanged(QAction *action) | |
| 48 | +{ | |
| 49 | + emit newFormat(action->text()); | |
| 50 | +} | |
| 51 | + | |
| 52 | +void View::countChanged(QAction *action) | |
| 53 | +{ | |
| 54 | + emit newCount(action->text().toInt()); | |
| 55 | +} | |
| 56 | + | |
| 57 | +#include "moc_view.cpp" | ... | ... |
app/openbr-gui/view.h
0 → 100644
| 1 | +#ifndef VIEW_H | |
| 2 | +#define VIEW_H | |
| 3 | + | |
| 4 | +#include <QAction> | |
| 5 | +#include <QActionGroup> | |
| 6 | +#include <QLabel> | |
| 7 | +#include <QString> | |
| 8 | +#include <QToolBar> | |
| 9 | +#include <QToolButton> | |
| 10 | +#include <QWidget> | |
| 11 | + | |
| 12 | +class View : public QToolBar | |
| 13 | +{ | |
| 14 | + Q_OBJECT | |
| 15 | + QToolButton tbPhoto, tbRegistered, tbEnhanced, tbFeatures; | |
| 16 | + QToolButton tb1, tb4, tb9, tb16; | |
| 17 | + QActionGroup agFormat, agCount; | |
| 18 | + | |
| 19 | +public: | |
| 20 | + explicit View(QWidget *parent = 0); | |
| 21 | + | |
| 22 | +private slots: | |
| 23 | + void formatChanged(QAction *action); | |
| 24 | + void countChanged(QAction *action); | |
| 25 | + | |
| 26 | +signals: | |
| 27 | + void newFormat(QString image); | |
| 28 | + void newCount(int count); | |
| 29 | +}; | |
| 30 | + | |
| 31 | +#endif // VIEW_H | ... | ... |