diff --git a/app/openbr-gui/algorithm.cpp b/app/openbr-gui/algorithm.cpp new file mode 100644 index 0000000..4166b5a --- /dev/null +++ b/app/openbr-gui/algorithm.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "algorithm.h" + +/**** ALGORITHM ****/ +/*** PUBLIC ***/ +br::Algorithm::Algorithm(QWidget *parent) + : QComboBox(parent) +{ + setToolTip("Algorithm"); + connect(this, SIGNAL(currentIndexChanged(QString)), this, SLOT(setAlgorithm(QString))); +} + +/*** PUBLIC SLOTS ***/ +bool br::Algorithm::addAlgorithm(const QString &algorithm, const QString &displayName) +{ + static QStringList availableAlgorithms; + if (availableAlgorithms.isEmpty()) + availableAlgorithms = QString(br_objects("Abbreviation", ".*", false)).split("\n"); + + if (!availableAlgorithms.contains(algorithm)) + return false; + + if (displayName.isEmpty()) { + addItem(algorithm); + } else { + displayNames.insert(displayName, algorithm); + addItem(displayName); + } + return true; +} + +/*** PRIVATE SLOTS ***/ +void br::Algorithm::setAlgorithm(QString algorithm) +{ + if (displayNames.contains(algorithm)) + algorithm = displayNames[algorithm]; + + br_set_property("algorithm", qPrintable(algorithm)); + emit newAlgorithm(algorithm); +} + +#include "moc_algorithm.cpp" diff --git a/app/openbr-gui/algorithm.h b/app/openbr-gui/algorithm.h new file mode 100644 index 0000000..2aeb71f --- /dev/null +++ b/app/openbr-gui/algorithm.h @@ -0,0 +1,32 @@ +#ifndef __ALGORITHM_H +#define __ALGORITHM_H + +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Algorithm : public QComboBox +{ + Q_OBJECT + QHash displayNames; + +public: + explicit Algorithm(QWidget *parent = 0); + +public slots: + bool addAlgorithm(const QString &algorithm, const QString &displayName = ""); + +private slots: + void setAlgorithm(QString algorithm); + +signals: + void newAlgorithm(QString algorithm); +}; + +} + +#endif // __ALGORITHM_H diff --git a/app/openbr-gui/classifier.cpp b/app/openbr-gui/classifier.cpp new file mode 100644 index 0000000..a5c6ca3 --- /dev/null +++ b/app/openbr-gui/classifier.cpp @@ -0,0 +1,64 @@ +#include +#include + +#include "classifier.h" + +using namespace br; + +/**** CLASSIFIER ****/ +/*** PUBLIC ***/ +Classifier::Classifier(QWidget *parent) + : QLabel(parent) +{ + setAlignment(Qt::AlignCenter); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + connect(this, SIGNAL(newClassification(QString,QString)), this, SLOT(setClassification(QString,QString))); +} + +void Classifier::setAlgorithm(const QString &algorithm) +{ + this->algorithm = algorithm; + _classify(File()); // Trigger algorithm initialization +} + +/*** PUBLIC SLOTS ***/ +void Classifier::classify(const File &file) +{ + QtConcurrent::run(this, &Classifier::_classify, file); +} + +/*** PRIVATE SLOTS ***/ +void Classifier::setClassification(const QString &key, const QString &value) +{ + if (key.isEmpty()) clear(); + else setText(QString("%1: %2").arg(key, value)); +} + +/*** PRIVATE ***/ +void Classifier::_classify(File file) +{ + file.setBool("forceEnrollment"); + + QString key, value; + foreach (const File &f, Enroll(file.flat(), File("[algorithm=" + algorithm + "]"))) { + qDebug() << f.flat(); + if (!f.contains("Label")) + continue; + + if (algorithm == "GenderClassification") { + key = "Gender"; + value = (f.getInt("Label", 0) == 0 ? "Male" : "Female"); + } else if (algorithm == "AgeRegression") { + key = "Age"; + value = QString::number(int(f.getFloat("Label", 0)+0.5)) + " Years"; + } else { + key = algorithm; + value = f.getString("Label"); + } + break; + } + + emit newClassification(key, value); +} + +#include "moc_classifier.cpp" diff --git a/app/openbr-gui/classifier.h b/app/openbr-gui/classifier.h new file mode 100644 index 0000000..c4ed004 --- /dev/null +++ b/app/openbr-gui/classifier.h @@ -0,0 +1,36 @@ +#ifndef __CLASSIFIER_H +#define __CLASSIFIER_H + +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Classifier : public QLabel +{ + Q_OBJECT + QString algorithm; + +public: + explicit Classifier(QWidget *parent = 0); + void setAlgorithm(const QString &algorithm); + +public slots: + void classify(const br::File &file); + +private slots: + void setClassification(const QString &key, const QString &value); + +private: + void _classify(br::File file); + +signals: + void newClassification(QString key, QString value); +}; + +} // namespace br + +#endif // __CLASSIFIER_H diff --git a/app/openbr-gui/dataset.cpp b/app/openbr-gui/dataset.cpp new file mode 100644 index 0000000..7e1ee78 --- /dev/null +++ b/app/openbr-gui/dataset.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include "dataset.h" + +/**** DATASET ****/ +/*** PUBLIC ***/ +br::Dataset::Dataset(QWidget *parent) + : QComboBox(parent) +{ + setToolTip("Dataset"); + connect(this, SIGNAL(currentIndexChanged(QString)), this, SLOT(datasetChangedTo(QString))); +} + +/*** PUBLIC SLOTS ***/ +static bool compareDatasets(const QString &a, const QString &b) +{ + static QHash knownDatasets; + if (knownDatasets.isEmpty()) { + knownDatasets["MEDS"] = 0; + knownDatasets["PCSO"] = 1; + knownDatasets["Good"] = 2; + knownDatasets["Bad"] = 3; + knownDatasets["Ugly"] = 4; + } + + if (!knownDatasets.contains(b)) return knownDatasets.contains(a); + if (!knownDatasets.contains(a)) return false; + return knownDatasets[a] < knownDatasets[b]; +} + +void br::Dataset::setAlgorithm(const QString &algorithm) +{ + this->algorithm = algorithm; + QStringList datasets; + QRegExp re("^" + algorithm + "_(.+).csv$"); + foreach (const QString &file, QDir(QString("%1/share/mm/Algorithm_Dataset/").arg(br_sdk_path())).entryList()) + if (re.indexIn(file) != -1) + datasets.append(re.cap(1)); + qSort(datasets.begin(), datasets.end(), compareDatasets); + clear(); + addItems(datasets); + setVisible(!datasets.isEmpty()); +} + +/*** PRIVATE SLOTS ***/ +void br::Dataset::datasetChangedTo(const QString &dataset) +{ + emit newDataset(dataset); + emit newDistribution(QString("%1/share/mm/Algorithm_Dataset/%2_%3.csv").arg(br_sdk_path(), algorithm, dataset)); +} + +#include "moc_dataset.cpp" diff --git a/app/openbr-gui/dataset.h b/app/openbr-gui/dataset.h new file mode 100644 index 0000000..e52981d --- /dev/null +++ b/app/openbr-gui/dataset.h @@ -0,0 +1,33 @@ +#ifndef __DATASET_H +#define __DATASET_H + +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Dataset : public QComboBox +{ + Q_OBJECT + QString algorithm; + +public: + explicit Dataset(QWidget *parent = 0); + +public slots: + void setAlgorithm(const QString &algorithm); + +private slots: + void datasetChangedTo(const QString &dataset); + +signals: + void newDataset(QString); + void newDistribution(QString); +}; + +} // namespace br + +#endif // __DATASET_H diff --git a/app/openbr-gui/gallerytoolbar.cpp b/app/openbr-gui/gallerytoolbar.cpp new file mode 100644 index 0000000..8c27f74 --- /dev/null +++ b/app/openbr-gui/gallerytoolbar.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gallerytoolbar.h" +#include "utility.h" + +/**** GALLERY ****/ +/*** STATIC ***/ +QMutex br::GalleryToolBar::galleryLock; + +/*** PUBLIC ***/ +br::GalleryToolBar::GalleryToolBar(QWidget *parent) + : QToolBar("Gallery", parent) +{ + lGallery.setAlignment(Qt::AlignCenter); + lGallery.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tbOpenFile.setIcon(QIcon(":/glyphicons/png/glyphicons_138_picture@2x.png")); + tbOpenFile.setToolTip("Load Photo"); + tbOpenFolder.setIcon(QIcon(":/glyphicons/png/glyphicons_144_folder_open@2x.png")); + tbOpenFolder.setToolTip("Load Photo Folder"); + tbWebcam.setCheckable(true); + tbWebcam.setIcon(QIcon(":/glyphicons/png/glyphicons_301_webcam@2x.png")); + tbWebcam.setToolTip("Load Webcam"); + tbBack.setEnabled(false); + tbBack.setIcon(QIcon(":/glyphicons/png/glyphicons_221_unshare@2x.png")); + tbBack.setToolTip("Back"); + tbMean.setIcon(QIcon(":/glyphicons/png/glyphicons_003_user@2x.png")); + tbMean.setToolTip("Mean Image"); + + addWidget(&tbOpenFile); + addWidget(&tbOpenFolder); + addWidget(&tbWebcam); + addSeparator(); + addWidget(&lGallery); + addSeparator(); + addWidget(&tbBack); + addWidget(&tbMean); + setIconSize(QSize(20,20)); + + connect(&tbOpenFile, SIGNAL(clicked()), this, SLOT(openFile())); + connect(&tbOpenFolder, SIGNAL(clicked()), this, SLOT(openFolder())); + connect(&tbBack, SIGNAL(clicked()), this, SLOT(home())); + connect(&tbMean, SIGNAL(clicked()), this, SLOT(mean())); + connect(&enrollmentWatcher, SIGNAL(finished()), this, SLOT(enrollmentFinished())); + connect(&timer, SIGNAL(timeout()), this, SLOT(checkWebcam())); + + timer.start(500); +} + +/*** PUBLIC SLOTS ***/ +void br::GalleryToolBar::enroll(const br::File &input) +{ + if (input.isNull()) return; + enrollmentWatcher.setFuture(QtConcurrent::run(this, &GalleryToolBar::_enroll, input)); +} + +void br::GalleryToolBar::enroll(const QImage &input) +{ + QString tempFileName = br::Context::scratchPath() + "/tmp/" + QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss:zzz") + ".png"; + input.save(tempFileName); + enroll(tempFileName); +} + +void br::GalleryToolBar::select(const br::File &file) +{ + tbBack.setEnabled(true); + emit newFiles(br::FileList() << file); + emit newGallery(file); +} + +/*** PRIVATE ***/ +void br::GalleryToolBar::_enroll(const br::File &input) +{ + galleryLock.lock(); + this->input = input; + if (input.suffix() == "gal") gallery = input.name + ".mem"; + else gallery = QString("%1/galleries/%2.gal[cache]").arg(br_scratch_path(), qPrintable(input.baseName()+input.hash())); + files = br::Enroll(input.flat(), gallery.flat()); + galleryLock.unlock(); +} + +void br::GalleryToolBar::_checkWebcam() +{ + static QSharedPointer videoCapture; + if (videoCapture.isNull()) { + videoCapture = QSharedPointer(new cv::VideoCapture(0)); + cv::Mat m; + while (!m.data) videoCapture->read(m); // First frames can be empty + } + + if (galleryLock.tryLock()) { + cv::Mat m; + videoCapture->read(m); + galleryLock.unlock(); + enroll(toQImage(m)); + } +} + +/*** PRIVATE SLOTS ***/ +void br::GalleryToolBar::checkWebcam() +{ + // Check webcam + if (!tbWebcam.isChecked()) return; + QtConcurrent::run(this, &GalleryToolBar::_checkWebcam); +} + +void br::GalleryToolBar::enrollmentFinished() +{ + if (files.isEmpty()) { + if (!input.getBool("forceEnrollment") && !tbWebcam.isChecked()) { + QMessageBox msgBox; + msgBox.setText("Quality test failed."); + msgBox.setInformativeText("Enroll anyway?"); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + int ret = msgBox.exec(); + + if (ret == QMessageBox::Ok) { + br::File file = input; + file.setBool("forceEnrollment"); + enroll(file); + } + } + return; + } + + lGallery.setText(input.baseName() + (files.size() != 1 ? " (" + QString::number(files.size()) + ")" : "")); + tbBack.setEnabled(false); + emit newFiles(files); + emit newGallery(gallery); +} + +void br::GalleryToolBar::home() +{ + tbBack.setEnabled(false); + emit newGallery(gallery); +} + +void br::GalleryToolBar::mean() +{ + const QString file = QString("%1/mean/%2.png").arg(br_scratch_path(), input.baseName()+input.hash()); + br_set_property("CENTER_TRAIN_B", qPrintable(file)); + br::File trainingFile = input; + trainingFile.setBool("forceEnrollment"); + br_train(qPrintable(trainingFile.flat()), "[algorithm=MedianFace]"); + enroll(file); +} + +void br::GalleryToolBar::openFile() +{ + enroll(QFileDialog::getOpenFileName(this, "Select Photo", QDesktopServices::storageLocation(QDesktopServices::PicturesLocation))); +} + +void br::GalleryToolBar::openFolder() +{ + enroll(QFileDialog::getExistingDirectory(this, "Select Photo Directory", QDesktopServices::storageLocation(QDesktopServices::HomeLocation))); +} + +#include "moc_gallerytoolbar.cpp" diff --git a/app/openbr-gui/gallerytoolbar.h b/app/openbr-gui/gallerytoolbar.h new file mode 100644 index 0000000..fc2634f --- /dev/null +++ b/app/openbr-gui/gallerytoolbar.h @@ -0,0 +1,58 @@ +#ifndef __GALLERYTOOLBAR_H +#define __GALLERYTOOLBAR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI GalleryToolBar : public QToolBar +{ + Q_OBJECT + br::File input, gallery; + br::FileList files; + + QLabel lGallery; + QToolButton tbOpenFile, tbOpenFolder, tbWebcam, tbBack, tbMean; + QFutureWatcher enrollmentWatcher; + QTimer timer; + + static QMutex galleryLock; + +public: + explicit GalleryToolBar(QWidget *parent = 0); + +public slots: + void enroll(const br::File &input); + void enroll(const QImage &input); + void select(const br::File &file); + +private: + void _enroll(const br::File &input); + void _checkWebcam(); + +private slots: + void checkWebcam(); + void enrollmentFinished(); + void home(); + void mean(); + void openFile(); + void openFolder(); + +signals: + void newGallery(br::File gallery); + void newFiles(br::FileList files); +}; + +} // namespace br + +#endif // GALLERYTOOLBAR_H diff --git a/app/openbr-gui/galleryviewer.cpp b/app/openbr-gui/galleryviewer.cpp new file mode 100644 index 0000000..adb20c7 --- /dev/null +++ b/app/openbr-gui/galleryviewer.cpp @@ -0,0 +1,46 @@ +#include "galleryviewer.h" + +using namespace br; + +GalleryViewer::GalleryViewer(QWidget *parent) + : QMainWindow(parent) +{ + addToolBar(Qt::TopToolBarArea, &gGallery); + addToolBar(Qt::BottomToolBarArea, &tmTemplateMetadata); + setCentralWidget(&tvgTemplateViewerGrid); + setWindowFlags(Qt::Widget); + + connect(&tvgTemplateViewerGrid, SIGNAL(newInput(br::File)), &gGallery, SLOT(enroll(br::File))); + connect(&tvgTemplateViewerGrid, SIGNAL(newInput(QImage)), &gGallery, SLOT(enroll(QImage))); + connect(&tvgTemplateViewerGrid, SIGNAL(selectedInput(br::File)), &gGallery, SLOT(select(br::File))); + + tmTemplateMetadata.addClassifier("GenderClassification", "MM0"); + tmTemplateMetadata.addClassifier("AgeRegression", "MM0"); + setFiles(FileList()); +} + +/*** PUBLIC SLOTS ***/ +void GalleryViewer::setAlgorithm(const QString &algorithm) +{ + tmTemplateMetadata.setAlgorithm(algorithm); + setFiles(FileList()); +} + +void GalleryViewer::setFiles(FileList files) +{ + bool same = true; + foreach (const File &file, files) { + if (file != files.first()) { + same = false; + break; + } + } + if (same) files = files.mid(0, 1); + + tvgTemplateViewerGrid.setFiles(files); + tmTemplateMetadata.setVisible(files.size() == 1); + if (files.size() == 1) + tmTemplateMetadata.setFile(files.first()); +} + +#include "moc_galleryviewer.cpp" diff --git a/app/openbr-gui/galleryviewer.h b/app/openbr-gui/galleryviewer.h new file mode 100644 index 0000000..2c80da3 --- /dev/null +++ b/app/openbr-gui/galleryviewer.h @@ -0,0 +1,33 @@ +#ifndef GALLERYVIEWER_H +#define GALLERYVIEWER_H + +#include +#include +#include + +#include "gallerytoolbar.h" +#include "templateviewergrid.h" +#include "templatemetadata.h" + +namespace br +{ + +class BR_EXPORT_GUI GalleryViewer : public QMainWindow +{ + Q_OBJECT + +public: + GalleryToolBar gGallery; + TemplateViewerGrid tvgTemplateViewerGrid; + TemplateMetadata tmTemplateMetadata; + + explicit GalleryViewer(QWidget *parent = 0); + +public slots: + void setAlgorithm(const QString &algorithm); + void setFiles(br::FileList files); +}; + +} // namespace br + +#endif // GALLERYVIEWER_H diff --git a/app/openbr-gui/help.cpp b/app/openbr-gui/help.cpp new file mode 100644 index 0000000..a8db781 --- /dev/null +++ b/app/openbr-gui/help.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +#include "help.h" + +/**** HELP ****/ +/*** PUBLIC ***/ +br::Help::Help(QWidget *parent) + : QMenu(parent) +{ + actions.append(QSharedPointer(addAction("About"))); + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showAbout())); + actions.append(QSharedPointer(addAction("Documentation"))); + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showDocumentation())); + actions.append(QSharedPointer(addAction("License"))); + connect(actions.last().data(), SIGNAL(triggered()), this, SLOT(showLicense())); +} + +/*** PUBLIC SLOTS ***/ +void br::Help::showAbout() +{ + QMessageBox::about(this, "About", br_about()); +} + +void br::Help::showDocumentation() +{ + QDesktopServices::openUrl(QUrl(QString("file:///%1/doc/html/index.html").arg(br_sdk_path()))); +} + +void br::Help::showLicense() +{ + QFile file(QString("%1/LICENSE.txt").arg(br_sdk_path())); + file.open(QFile::ReadOnly); + QString license = file.readAll(); + file.close(); + + QMessageBox::about(this, "License", license); +} + +#include "moc_help.cpp" diff --git a/app/openbr-gui/help.h b/app/openbr-gui/help.h new file mode 100644 index 0000000..180ce96 --- /dev/null +++ b/app/openbr-gui/help.h @@ -0,0 +1,30 @@ +#ifndef HELP_H +#define HELP_H + +#include +#include +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Help : public QMenu +{ + Q_OBJECT + QList< QSharedPointer > actions; + +public: + explicit Help(QWidget *parent = 0); + +public slots: + void showAbout(); + void showDocumentation(); + void showLicense(); +}; + +} // namespace br + +#endif // HELP_H diff --git a/app/openbr-gui/icons.qrc b/app/openbr-gui/icons.qrc new file mode 100644 index 0000000..08bec70 --- /dev/null +++ b/app/openbr-gui/icons.qrc @@ -0,0 +1,6 @@ + + + icons/mitre_logo.png + icons/mm.png + + diff --git a/app/openbr-gui/icons/mitre_logo.png b/app/openbr-gui/icons/mitre_logo.png new file mode 100644 index 0000000..3806090 --- /dev/null +++ b/app/openbr-gui/icons/mitre_logo.png diff --git a/app/openbr-gui/icons/mm.icns b/app/openbr-gui/icons/mm.icns new file mode 100644 index 0000000..df9b60d --- /dev/null +++ b/app/openbr-gui/icons/mm.icns diff --git a/app/openbr-gui/icons/mm.ico b/app/openbr-gui/icons/mm.ico new file mode 100644 index 0000000..16d0121 --- /dev/null +++ b/app/openbr-gui/icons/mm.ico diff --git a/app/openbr-gui/icons/mm.png b/app/openbr-gui/icons/mm.png new file mode 100644 index 0000000..ea6ac53 --- /dev/null +++ b/app/openbr-gui/icons/mm.png diff --git a/app/openbr-gui/imageviewer.h b/app/openbr-gui/imageviewer.h index 89756a1..f0a117b 100644 --- a/app/openbr-gui/imageviewer.h +++ b/app/openbr-gui/imageviewer.h @@ -25,7 +25,6 @@ #include #include #include - #include namespace br diff --git a/app/openbr-gui/progress.cpp b/app/openbr-gui/progress.cpp new file mode 100644 index 0000000..4ae0903 --- /dev/null +++ b/app/openbr-gui/progress.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "progress.h" + +/**** PROGRESS ****/ +/*** PUBLIC ***/ +br::Progress::Progress(QWidget *parent) + : QStatusBar(parent) +{ + pbProgress.setVisible(false); + pbProgress.setMaximum(100); + pbProgress.setMinimum(0); + pbProgress.setValue(0); + pbProgress.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + pbProgress.setTextVisible(false); + + addWidget(&wSpacer, 1); + addPermanentWidget(&pbProgress); + addPermanentWidget(&lTimeRemaining); + connect(&timer, SIGNAL(timeout()), this, SLOT(checkProgress())); + timer.start(1000); +} + +/*** PRIVATE SLOTS ***/ +void br::Progress::checkProgress() +{ + const int progress = 100 * br_progress(); + const bool visible = progress >= 0; + + if (visible) { + showMessage(br_most_recent_message()); + pbProgress.setValue(progress); + if (progress > 100) pbProgress.setMaximum(0); + else pbProgress.setMaximum(100); + + int s = br_time_remaining(); + if (s >= 0) { + int h = s / (60*60); + int m = (s - h*60*60) / 60; + s = (s - h*60*60 - m*60); + lTimeRemaining.setText(QString("%1:%2:%3").arg(h, 2, 10, QLatin1Char('0')).arg(m, 2, 10, QLatin1Char('0')).arg(s, 2, 10, QLatin1Char('0'))); + } else { + lTimeRemaining.clear(); + } + } else { + clearMessage(); + lTimeRemaining.clear(); + } + + pbProgress.setVisible(visible); +} + +#include "moc_progress.cpp" diff --git a/app/openbr-gui/progress.h b/app/openbr-gui/progress.h new file mode 100644 index 0000000..db9dab8 --- /dev/null +++ b/app/openbr-gui/progress.h @@ -0,0 +1,31 @@ +#ifndef __PROGRESS_H +#define __PROGRESS_H + +#include +#include +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Progress : public QStatusBar +{ + Q_OBJECT + QWidget wSpacer; + QProgressBar pbProgress; + QLabel lTimeRemaining; + QTimer timer; + +public: + explicit Progress(QWidget *parent = 0); + +private slots: + void checkProgress(); +}; + +} + +#endif // __PROGRESS_H diff --git a/app/openbr-gui/score.cpp b/app/openbr-gui/score.cpp new file mode 100644 index 0000000..69af111 --- /dev/null +++ b/app/openbr-gui/score.cpp @@ -0,0 +1,18 @@ +#include +#include "score.h" + +/**** SCORE ****/ +/*** PUBLIC ***/ +br::Score::Score(QWidget *parent) + : QLabel(parent) +{ + setToolTip("Similarity Score"); +} + +/*** PUBLIC SLOTS ***/ +void br::Score::setScore(float score) +{ + setText("Similarity: " + QString::number(score, 'f', 2)); +} + +#include "moc_score.cpp" diff --git a/app/openbr-gui/score.h b/app/openbr-gui/score.h new file mode 100644 index 0000000..b61f3a7 --- /dev/null +++ b/app/openbr-gui/score.h @@ -0,0 +1,24 @@ +#ifndef __SCORE_H +#define __SCORE_H + +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI Score : public QLabel +{ + Q_OBJECT + +public: + explicit Score(QWidget *parent = 0); + +public slots: + void setScore(float score); +}; + +} + +#endif // __SCORE_H diff --git a/app/openbr-gui/splashscreen.cpp b/app/openbr-gui/splashscreen.cpp new file mode 100644 index 0000000..578d73b --- /dev/null +++ b/app/openbr-gui/splashscreen.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "splashscreen.h" + +using namespace br; + +/**** SPLASH_SCREEN ****/ +/*** PUBLIC ***/ +SplashScreen::SplashScreen() + : QSplashScreen(QPixmap(":/icons/mm.png").scaledToWidth(384, Qt::SmoothTransformation)) +{ + connect(&timer, SIGNAL(timeout()), this, SLOT(updateMessage())); + timer.start(100); +} + +/*** PROTECTED ***/ +void SplashScreen::closeEvent(QCloseEvent *event) +{ + QSplashScreen::closeEvent(event); + event->accept(); + timer.stop(); +} + +/*** PRIVATE SLOTS ***/ +void SplashScreen::updateMessage() +{ + showMessage("Version " + Context::version() + " " + QChar(169) + " MITRE 2012\n" + Globals->mostRecentMessage, Qt::AlignHCenter | Qt::AlignBottom); +} + +#include "moc_splashscreen.cpp" diff --git a/app/openbr-gui/splashscreen.h b/app/openbr-gui/splashscreen.h new file mode 100644 index 0000000..273e2f0 --- /dev/null +++ b/app/openbr-gui/splashscreen.h @@ -0,0 +1,31 @@ +#ifndef __SPLASHSCREEN_H +#define __SPLASHSCREEN_H + +#include +#include +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI SplashScreen : public QSplashScreen +{ + Q_OBJECT + QTimer timer; + +public: + SplashScreen(); + +protected: + void closeEvent(QCloseEvent *event); + +private slots: + void updateMessage(); +}; + +} // namespace br + +#endif // __SPLASHSCREEN_H diff --git a/app/openbr-gui/templatemetadata.cpp b/app/openbr-gui/templatemetadata.cpp new file mode 100644 index 0000000..ee5fb9d --- /dev/null +++ b/app/openbr-gui/templatemetadata.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "templatemetadata.h" + +/*** PUBLIC ***/ +br::TemplateMetadata::TemplateMetadata(QWidget *parent) + : QToolBar("Template Metadata", parent) +{ + lFile.setTextInteractionFlags(Qt::TextSelectableByMouse); + wSpacer.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + addWidget(&wOffset); + addWidget(&lFile); + addWidget(&wSpacer); + addWidget(&lQuality); +} + +void br::TemplateMetadata::addClassifier(const QString &classifier_, const QString algorithm) +{ + QSharedPointer classifier(new Classifier()); + classifier->setAlgorithm(classifier_); + QAction *action = addWidget(classifier.data()); + conditionalClassifiers.append(ConditionalClassifier(algorithm, classifier, action)); +} + +/**** PRIVATE SLOTS ****/ +void br::TemplateMetadata::setFile(const br::File &file) +{ + if (file.isNull()) lFile.clear(); + else lFile.setText("File: " + file.fileName()); + lQuality.setText(QString("Quality: %1").arg(file.getBool("FTE") ? "Low" : "High")); + foreach (const ConditionalClassifier &classifier, conditionalClassifiers) + if (classifier.action->isVisible()) classifier.classifier->classify(file); +} + +void br::TemplateMetadata::setAlgorithm(const QString &algorithm) +{ + foreach (const ConditionalClassifier &classifier, conditionalClassifiers) { + classifier.classifier->clear(); + classifier.action->setVisible(classifier.algorithm.isEmpty() || classifier.algorithm == algorithm); + } +} + +#include "moc_templatemetadata.cpp" diff --git a/app/openbr-gui/templatemetadata.h b/app/openbr-gui/templatemetadata.h new file mode 100644 index 0000000..8c2a38f --- /dev/null +++ b/app/openbr-gui/templatemetadata.h @@ -0,0 +1,46 @@ +#ifndef __TEMPLATEMETADATA_H +#define __TEMPLATEMETADATA_H + +#include +#include +#include +#include +#include +#include +#include + +#include "classifier.h" + +namespace br +{ + +class BR_EXPORT_GUI TemplateMetadata : public QToolBar +{ + Q_OBJECT + QLabel lFile, lQuality; + QWidget wOffset, wSpacer; + + struct ConditionalClassifier + { + QString algorithm; + QSharedPointer classifier; + QAction *action; + + ConditionalClassifier() : action(NULL) {} + ConditionalClassifier(const QString &algorithm_, const QSharedPointer &classifier_, QAction *action_) + : algorithm(algorithm_), classifier(classifier_), action(action_) {} + }; + QList conditionalClassifiers; + +public: + explicit TemplateMetadata(QWidget *parent = 0); + void addClassifier(const QString &classifier, const QString algorithm = ""); + +public slots: + void setFile(const br::File &file); + void setAlgorithm(const QString &algorithm); +}; + +} // namespace br + +#endif // TEMPLATEMETADATA_H diff --git a/app/openbr-gui/templateviewer.cpp b/app/openbr-gui/templateviewer.cpp new file mode 100644 index 0000000..eaafd55 --- /dev/null +++ b/app/openbr-gui/templateviewer.cpp @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#include + +#include "templateviewer.h" + +using namespace br; + +/*** STATIC ***/ +const int NumLandmarks = 2; + +static bool lessThan(const QPointF &a, const QPointF &b) +{ + return ((a.x() < b.x()) || + ((a.x() == b.x()) && (a.y() < b.y()))); +} + +/*** PUBLIC ***/ +TemplateViewer::TemplateViewer(QWidget *parent) + : ImageViewer(parent) +{ + setAcceptDrops(true); + setMouseTracking(true); + setText("Drag Photo or Folder Here\n"); + format = "Registered"; + editable = true; + setFile(File()); + update(); +} + +/*** PUBLIC SLOTS ***/ +void TemplateViewer::setFile(const File &file_) +{ + this->file = file_; + + // Update landmarks + landmarks.clear(); + if (file.contains("Affine_0_X") && file.contains("Affine_0_Y")) + landmarks.append(QPointF(file.getFloat("Affine_0_X"), file.getFloat("Affine_0_Y"))); + if (file.contains("Affine_1_X") && file.contains("Affine_1_Y")) + landmarks.append(QPointF(file.getFloat("Affine_1_X"), file.getFloat("Affine_1_Y"))); + while (landmarks.size() < NumLandmarks) + landmarks.append(QPointF()); + nearestLandmark = -1; + + QtConcurrent::run(this, &TemplateViewer::refreshImage); +} + +void TemplateViewer::setEditable(bool enabled) +{ + editable = enabled; + update(); +} + +void TemplateViewer::setMousePoint(const QPointF &mousePoint) +{ + this->mousePoint = mousePoint; + update(); +} + +void TemplateViewer::setFormat(const QString &format) +{ + this->format = format; + QtConcurrent::run(this, &TemplateViewer::refreshImage); +} + +/*** PRIVATE ***/ +void TemplateViewer::refreshImage() +{ + if (file.isNull() || (format == "Photo")) { + setImage(file, true); + } else { + const QString path = QString(br_scratch_path()) + "/thumbnails"; + const QString hash = file.hash()+format; + const QString processedFile = path+"/"+file.baseName()+hash+".png"; + if (!QFileInfo(processedFile).exists()) { + if (format == "Registered") + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=RegisterAffine]"); + else if (format == "Enhanced") + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=ContrastEnhanced]"); + else if (format == "Features") + Enroll(file.flat(), path+"[postfix="+hash+",cache,algorithm=ColoredLBP]"); + } + setImage(processedFile, true); + } +} + +QPointF TemplateViewer::getImagePoint(const QPointF &sp) const +{ + if (!pixmap() || isNull()) return QPointF(); + QPointF ip = QPointF(imageWidth()*(sp.x() - (width() - pixmap()->width())/2)/pixmap()->width(), + imageHeight()*(sp.y() - (height() - pixmap()->height())/2)/pixmap()->height()); + if ((ip.x() < 0) || (ip.x() > imageWidth()) || (ip.y() < 0) || (ip.y() > imageHeight())) return QPointF(); + return ip; +} + +QPointF TemplateViewer::getScreenPoint(const QPointF &ip) const +{ + if (!pixmap() || isNull()) return QPointF(); + return QPointF(ip.x()*pixmap()->width()/imageWidth() + (width()-pixmap()->width())/2, + ip.y()*pixmap()->height()/imageHeight() + (height()-pixmap()->height())/2); +} + +/*** PROTECTED SLOTS ***/ +void TemplateViewer::dragEnterEvent(QDragEnterEvent *event) +{ + ImageViewer::dragEnterEvent(event); + event->accept(); + + if (event->mimeData()->hasUrls() || event->mimeData()->hasImage()) + event->acceptProposedAction(); +} + +void TemplateViewer::dropEvent(QDropEvent *event) +{ + ImageViewer::dropEvent(event); + event->accept(); + + event->acceptProposedAction(); + const QMimeData *mimeData = event->mimeData(); + if (mimeData->hasImage()) { + QImage input = qvariant_cast(mimeData->imageData()); + emit newInput(input); + } else if (event->mimeData()->hasUrls()) { + File input; + foreach (const QUrl &url, mimeData->urls()) { + if (!url.isValid()) continue; + if (url.toString().startsWith("http://images.google.com/search")) continue; // Not a true image URL + const QString localFile = url.toLocalFile(); + if (localFile.isNull()) input.append(url.toString()); + else input.append(localFile); + } + if (input.isNull()) return; + emit newInput(input); + } +} + +void TemplateViewer::leaveEvent(QEvent *event) +{ + ImageViewer::leaveEvent(event); + event->accept(); + clearFocus(); + + nearestLandmark = -1; + mousePoint = QPointF(); + emit newMousePoint(mousePoint); + unsetCursor(); + update(); +} + +void TemplateViewer::mouseMoveEvent(QMouseEvent *event) +{ + ImageViewer::mouseMoveEvent(event); + event->accept(); + + mousePoint = getImagePoint(event->pos()); + nearestLandmark = -1; + if (!mousePoint.isNull()) { + double nearestDistance = std::numeric_limits::max(); + for (int i=0; iaccept(); + + if (isNull() || getImagePoint(event->pos()).isNull()) return; + + if (!editable || (format != "Photo")) { + emit selectedInput(file); + return; + } + + int index; + for (index=0; indexbutton() == Qt::RightButton) || (index == NumLandmarks)) { + // Remove nearest point + if (nearestLandmark == -1) return; + index = nearestLandmark; + landmarks[nearestLandmark] = QPointF(); + nearestLandmark = -1; + } + + if (event->button() == Qt::LeftButton) { + // Add a point + landmarks[index] = getImagePoint(event->pos()); + qSort(landmarks.begin(), landmarks.end(), lessThan); + if (!landmarks.contains(QPointF())) + 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()), + QString::number(landmarks[0].y()), + QString::number(landmarks[1].x()), + QString::number(landmarks[1].y()))); + } + + update(); +} + +void TemplateViewer::paintEvent(QPaintEvent *event) +{ + static const QColor nearest(0, 0, 255, 192); + static const QColor normal(0, 255, 0, 192); + + ImageViewer::paintEvent(event); + event->accept(); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + if (format == "Photo") { + for (int i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openbr-gui/imageviewer.h" + +namespace br +{ + +class BR_EXPORT_GUI TemplateViewer : public br::ImageViewer +{ + Q_OBJECT + br::File file; + QPointF mousePoint; + QString format; + + bool editable; + QList landmarks; + int nearestLandmark; + +public: + explicit TemplateViewer(QWidget *parent = 0); + +public slots: + void setFile(const br::File &file); + void setEditable(bool enabled); + void setMousePoint(const QPointF &mousePoint); + void setFormat(const QString &format); + +private: + void refreshImage(); + QPointF getImagePoint(const QPointF &sp) const; + QPointF getScreenPoint(const QPointF &ip) const; + +protected slots: + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + void leaveEvent(QEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void paintEvent(QPaintEvent *event); + +signals: + void newInput(br::File input); + void newInput(QImage input); + void newMousePoint(QPointF mousePoint); + void selectedInput(br::File input); +}; + +} + +#endif // TEMPLATEVIEWER_H diff --git a/app/openbr-gui/templateviewergrid.cpp b/app/openbr-gui/templateviewergrid.cpp new file mode 100644 index 0000000..d166995 --- /dev/null +++ b/app/openbr-gui/templateviewergrid.cpp @@ -0,0 +1,62 @@ +#include "templateviewergrid.h" + +using namespace br; + +/**** TEMPLATE_VIEWER_GRID ****/ +/*** PUBLIC ***/ +TemplateViewerGrid::TemplateViewerGrid(QWidget *parent) + : QWidget(parent) +{ + setLayout(&gridLayout); + setFiles(FileList(16)); + setFiles(FileList(1)); +} + +/*** PUBLIC SLOTS ***/ +void TemplateViewerGrid::setFiles(const FileList &files) +{ + const int size = std::max(1, (int)ceil(sqrt((float)files.size()))); + while (templateViewers.size() < size*size) { + templateViewers.append(QSharedPointer(new TemplateViewer())); + connect(templateViewers.last().data(), SIGNAL(newInput(br::File)), this, SIGNAL(newInput(br::File))); + connect(templateViewers.last().data(), SIGNAL(newInput(QImage)), this, SIGNAL(newInput(QImage))); + connect(templateViewers.last().data(), SIGNAL(newMousePoint(QPointF)), this, SIGNAL(newMousePoint(QPointF))); + connect(templateViewers.last().data(), SIGNAL(selectedInput(br::File)), this, SIGNAL(selectedInput(br::File))); + } + + { // Clear layout + QLayoutItem *child; + while ((child = gridLayout.takeAt(0)) != 0) + child->widget()->setVisible(false); + } + + for (int i=0; isetVisible(true); + } + + if (i < files.size()) { + templateViewers[i]->setFile(files[i]); + } else { + templateViewers[i]->setDefaultText(""+ (size > 1 ? QString() : QString("Drag Photo or Folder Here")) +""); + templateViewers[i]->setFile(QString()); + } + + templateViewers[i]->setEditable(files.size() == 1); + } +} + +void TemplateViewerGrid::setFormat(const QString &format) +{ + foreach (const QSharedPointer &templateViewer, templateViewers) + templateViewer->setFormat(format); +} + +void TemplateViewerGrid::setMousePoint(const QPointF &mousePoint) +{ + foreach (const QSharedPointer &templateViewer, templateViewers) + templateViewer->setMousePoint(mousePoint); +} + +#include "moc_templateviewergrid.cpp" diff --git a/app/openbr-gui/templateviewergrid.h b/app/openbr-gui/templateviewergrid.h new file mode 100644 index 0000000..204d540 --- /dev/null +++ b/app/openbr-gui/templateviewergrid.h @@ -0,0 +1,38 @@ +#ifndef __TEMPLATE_VIEWER_GRID_H +#define __TEMPLATE_VIEWER_GRID_H + +#include +#include +#include +#include + +#include "templateviewer.h" + +namespace br +{ + +class BR_EXPORT_GUI TemplateViewerGrid : public QWidget +{ + Q_OBJECT + + QGridLayout gridLayout; + QList< QSharedPointer > templateViewers; + +public: + explicit TemplateViewerGrid(QWidget *parent = 0); + +public slots: + void setFiles(const br::FileList &file); + void setFormat(const QString &format); + void setMousePoint(const QPointF &mousePoint); + +signals: + void newInput(br::File input); + void newInput(QImage input); + void newMousePoint(QPointF mousePoint); + void selectedInput(br::File input); +}; + +} // namespace br + +#endif // __TEMPLATE_VIEWER_GRID_H diff --git a/app/openbr-gui/utility.cpp b/app/openbr-gui/utility.cpp new file mode 100644 index 0000000..ff30e33 --- /dev/null +++ b/app/openbr-gui/utility.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +using namespace cv; + +/**** STATIC ****/ +QImage toQImage(const Mat &mat) +{ + // Convert to 8U depth + Mat mat8u; + if (mat.depth() != CV_8U) { + double globalMin = std::numeric_limits::max(); + double globalMax = -std::numeric_limits::max(); + + std::vector mv; + split(mat, mv); + for (size_t i=0; i= globalMin); + + double range = globalMax - globalMin; + if (range != 0) { + double scale = 255 / range; + convertScaleAbs(mat, mat8u, scale, -(globalMin * scale)); + } else { + // Monochromatic + mat8u = Mat(mat.size(), CV_8UC1, Scalar((globalMin+globalMax)/2)); + } + } else { + mat8u = mat; + } + + // Convert to 3 channels + Mat mat8uc3; + if (mat8u.channels() == 4) cvtColor(mat8u, mat8uc3, CV_BGRA2RGB); + else if (mat8u.channels() == 3) cvtColor(mat8u, mat8uc3, CV_BGR2RGB); + else if (mat8u.channels() == 1) cvtColor(mat8u, mat8uc3, CV_GRAY2RGB); + + return QImage(mat8uc3.data, mat8uc3.cols, mat8uc3.rows, 3*mat8uc3.cols, QImage::Format_RGB888).copy(); +} diff --git a/app/openbr-gui/utility.h b/app/openbr-gui/utility.h new file mode 100644 index 0000000..2a6a40a --- /dev/null +++ b/app/openbr-gui/utility.h @@ -0,0 +1,9 @@ +#ifndef UTILITY_H +#define UTILITY_H + +#include +#include + +QImage toQImage(const cv::Mat &mat); + +#endif // UTILITY_H diff --git a/app/openbr-gui/view.cpp b/app/openbr-gui/view.cpp new file mode 100644 index 0000000..8311c12 --- /dev/null +++ b/app/openbr-gui/view.cpp @@ -0,0 +1,57 @@ +#include "view.h" + +/**** VIEW ****/ +/*** PUBLIC ***/ +View::View(QWidget *parent) + : QToolBar(parent) + , agFormat(parent) + , agCount(parent) +{ + agFormat.addAction("Photo"); + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_D); + agFormat.addAction("Registered"); + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_R); + agFormat.addAction("Enhanced"); + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_E); + agFormat.addAction("Features"); + agFormat.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_F); + + agCount.addAction("1"); + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_1); + agCount.addAction("4"); + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_2); + agCount.addAction("9"); + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_3); + agCount.addAction("16"); + agCount.actions().last()->setShortcut(Qt::ControlModifier + Qt::Key_4); + + addActions(agFormat.actions()); + addSeparator(); + addActions(agCount.actions()); + + setToolTip("View"); + + connect(&agFormat, SIGNAL(triggered(QAction*)), this, SLOT(formatChanged(QAction*))); + connect(&agCount, SIGNAL(triggered(QAction*)), this, SLOT(countChanged(QAction*))); + + foreach (QAction *action, agCount.actions()) + action->setCheckable(true); + agCount.actions()[2]->setChecked(true); + + foreach (QAction *action, agFormat.actions()) + action->setCheckable(true); + agFormat.actions()[1]->setChecked(true); +} + +/*** PRIVATE SLOTS ***/ +void View::formatChanged(QAction *action) +{ + emit newFormat(action->text()); +} + +void View::countChanged(QAction *action) +{ + emit newCount(action->text().toInt()); +} + +#include "moc_view.cpp" diff --git a/app/openbr-gui/view.h b/app/openbr-gui/view.h new file mode 100644 index 0000000..84e213b --- /dev/null +++ b/app/openbr-gui/view.h @@ -0,0 +1,31 @@ +#ifndef VIEW_H +#define VIEW_H + +#include +#include +#include +#include +#include +#include +#include + +class View : public QToolBar +{ + Q_OBJECT + QToolButton tbPhoto, tbRegistered, tbEnhanced, tbFeatures; + QToolButton tb1, tb4, tb9, tb16; + QActionGroup agFormat, agCount; + +public: + explicit View(QWidget *parent = 0); + +private slots: + void formatChanged(QAction *action); + void countChanged(QAction *action); + +signals: + void newFormat(QString image); + void newCount(int count); +}; + +#endif // VIEW_H