diff --git a/app/examples/age_estimation.cpp b/app/examples/age_estimation.cpp index 95b17eb..592241b 100644 --- a/app/examples/age_estimation.cpp +++ b/app/examples/age_estimation.cpp @@ -29,9 +29,7 @@ static void printTemplate(const br::Template &t) { - printf("%s age: %d\n", - qPrintable(t.file.fileName()), - t.file.get("Label")); + printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.label())); } int main(int argc, char *argv[]) diff --git a/app/examples/face_recognition_evaluation.cpp b/app/examples/face_recognition_evaluation.cpp index 58f2337..ebf6462 100644 --- a/app/examples/face_recognition_evaluation.cpp +++ b/app/examples/face_recognition_evaluation.cpp @@ -55,14 +55,12 @@ int main(int argc, char *argv[]) // Evaluate the performance of OpenBR's FaceRecognition and a COTS face recognition system. br_eval("FaceRecognition_MEDS.mtx", "MEDS.mask", "Algorithm_Dataset/FaceRecognition_MEDS.csv"); - br_eval("../data/MEDS/simmat/COTS_MEDS.mtx", "MEDS.mask", "Algorithm_Dataset/COTS_MEDS.csv"); // The '_' character has special significance and is used to populate plot legends. // Requires R installation, see documentation of br_plot for details. - const char *files[2]; + const char *files[1]; files[0] = "Algorithm_Dataset/FaceRecognition_MEDS.csv"; - files[1] = "Algorithm_Dataset/COTS_MEDS.csv"; - br_plot(2, files, "MEDS", true); + br_plot(1, files, "MEDS", true); br_finalize(); return 0; diff --git a/app/examples/gender_estimation.cpp b/app/examples/gender_estimation.cpp index f24ec3a..6013c3a 100644 --- a/app/examples/gender_estimation.cpp +++ b/app/examples/gender_estimation.cpp @@ -29,9 +29,7 @@ static void printTemplate(const br::Template &t) { - printf("%s gender: %s\n", - qPrintable(t.file.fileName()), - t.file.get("Label") == 1 ? "Female" : "Male"); + printf("%s gender: %s\n", qPrintable(t.file.fileName()), t.file.label() == 1 ? "Female" : "Male"); } int main(int argc, char *argv[]) diff --git a/openbr/core/bee.cpp b/openbr/core/bee.cpp index 4a34c1b..f24a6a2 100644 --- a/openbr/core/bee.cpp +++ b/openbr/core/bee.cpp @@ -58,24 +58,18 @@ FileList BEE::readSigset(const QString &sigset, bool ignoreMetadata) QString name = d.attribute("name"); while (!fileNode.isNull()) { // Looping through files - File file; + File file("", name); QDomElement e = fileNode.toElement(); QDomNamedNodeMap attributes = e.attributes(); for (int i=0; i"); diff --git a/openbr/core/classify.cpp b/openbr/core/classify.cpp index 6155f8a..fd4cfae 100644 --- a/openbr/core/classify.cpp +++ b/openbr/core/classify.cpp @@ -56,7 +56,7 @@ void br::EvalClassification(const QString &predictedInput, const QString &truthI } } - QSharedPointer output(Output::make("", FileList() << "Label" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); + QSharedPointer output(Output::make("", FileList() << "Subject" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); int tpc = 0; int fnc = 0; diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index bf1b959..e9715a5 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -77,7 +77,7 @@ struct AlgorithmCore const bool hasComparer = !distance.isNull(); out << hasComparer; if (hasComparer) distance->store(out); - out << Globals->classes; + out << Globals->subjects; // Compress and save to file QtUtils::writeFile(model, data, -1); @@ -97,7 +97,7 @@ struct AlgorithmCore transform->load(in); bool hasDistance; in >> hasDistance; if (hasDistance) distance->load(in); - in >> Globals->classes; + in >> Globals->subjects; } File getMemoryGallery(const File &file) const diff --git a/openbr/gui/classifier.cpp b/openbr/gui/classifier.cpp index 517f04b..d93b818 100644 --- a/openbr/gui/classifier.cpp +++ b/openbr/gui/classifier.cpp @@ -44,13 +44,13 @@ void Classifier::_classify(File file) if (algorithm == "GenderClassification") { key = "Gender"; - value = (f.get("Label", 0) == 0 ? "Male" : "Female"); + value = (f.label() == 0 ? "Male" : "Female"); } else if (algorithm == "AgeRegression") { key = "Age"; - value = QString::number(int(f.get("Label", 0)+0.5)) + " Years"; + value = QString::number(int(f.label()+0.5)) + " Years"; } else { key = algorithm; - value = f.get("Label"); + value = QString::number(f.label()); } break; } diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index e9076ba..b7f0058 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -132,33 +132,11 @@ QVariant File::parse(const QString &value) if (ok) return point; const QRectF rect = QtUtils::toRect(value, &ok); if (ok) return rect; - - /* We assume that if the value starts with '0' - then it was probably intended to be a string UID - and that it's numerical value is not relevant. */ - if (!value.startsWith('0') || (value == "0")) { - const float f = value.toFloat(&ok); - if (ok) return f; - } - + const float f = value.toFloat(&ok); + if (ok) return f; return value; } -void File::set(const QString &key, const QVariant &value) -{ - if (key == "Label") { - const QString valueString = value.toString(); - if (!Globals->classes.contains(valueString)) { - static QMutex mutex; - QMutexLocker mutexLocker(&mutex); - if (!Globals->classes.contains(valueString)) - Globals->classes.insert(valueString, Globals->classes.size()); - } - } - - m_metadata.insert(key, value); -} - void File::set(const QString &key, const QString &value) { if (value.startsWith('[') && value.endsWith(']')) { @@ -179,22 +157,27 @@ bool File::getBool(const QString &key, bool defaultValue) const return variant.value(); } -QString File::subject(int label) +QString File::subject() const { - return Globals->classes.key(label, QString::number(label)); + const QVariant l = m_metadata.value("Label"); + if (!l.isNull()) return Globals->subjects.key(l.toFloat(), l.toString()); + return m_metadata.value("Subject").toString(); } float File::label() const { - const QVariant variant = value("Label"); - if (variant.isNull()) return -1; + const QVariant l = m_metadata.value("Label"); + if (!l.isNull()) return l.toFloat(); - if (Globals->classes.contains(variant.toString())) - return Globals->classes.value(variant.toString()); + const QVariant s = m_metadata.value("Subject"); + if (s.isNull()) return -1; - bool ok; - const float val = variant.toFloat(&ok); - return ok ? val : -1; + const QString subject = s.toString(); + static QMutex mutex; + QMutexLocker mutexLocker(&mutex); + if (!Globals->subjects.contains(subject)) + Globals->subjects.insert(subject, Globals->subjects.size()); + return Globals->subjects.value(subject); } QList File::namedPoints() const @@ -310,8 +293,6 @@ QDataStream &br::operator<<(QDataStream &stream, const File &file) QDataStream &br::operator>>(QDataStream &stream, File &file) { return stream >> file.name >> file.m_metadata; - const QVariant label = file.m_metadata.value("Label"); - if (!label.isNull()) file.set("Label", label); // Trigger population of Globals->classes } /* FileList - public methods */ @@ -464,14 +445,15 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) TemplateList TemplateList::relabel(const TemplateList &tl) { - QHash labels; - foreach (int label, tl.labels()) - if (!labels.contains(label)) - labels.insert(label, labels.size()); + const QList originalLabels = tl.labels(); + QHash labelTable; + foreach (int label, originalLabels) + if (!labelTable.contains(label)) + labelTable.insert(label, labelTable.size()); TemplateList result = tl; for (int i=0; iclassName(), qPrintable(name), qPrintable(value), qPrintable(type)); + setProperty(name, variant, !type.isEmpty()); +} + +void Object::setProperty(const QString &name, const QVariant &value, bool failOnError) +{ + if (!QObject::setProperty(qPrintable(name), value) && failOnError) + qFatal("Failed to set %s::%s to: %s", + metaObject()->className(), qPrintable(name), qPrintable(value.toString())); } -QStringList br::Object::parse(const QString &string, char split) +QStringList Object::parse(const QString &string, char split) { return QtUtils::parse(string, split); } @@ -738,19 +725,21 @@ void Object::init(const File &file_) } foreach (QString key, file.localKeys()) { - const QString value = file.value(key).toString(); + const QVariant value = file.value(key); + const QString valueString = value.toString(); if (key.startsWith(("_Arg"))) { - int argument_number = key.mid(4).toInt(); - int target_idx = argument_number + firstAvailablePropertyIdx; - - if (target_idx >= metaObject()->propertyCount()) { - qWarning("too many arguments for transform, ignoring %s\n", qPrintable(value)); + int argumentNumber = key.mid(4).toInt(); + int targetIdx = argumentNumber + firstAvailablePropertyIdx; + if (targetIdx >= metaObject()->propertyCount()) { + qWarning("too many arguments for transform %s, ignoring %s", qPrintable(objectName()), qPrintable(valueString)); continue; } - key = metaObject()->property(target_idx).name(); + key = metaObject()->property(targetIdx).name(); } - setProperty(key, value); + + if (valueString.isEmpty()) setProperty(key, value); // Set the property directly + else setProperty(key, valueString); // Parse the value first } init(); @@ -1038,8 +1027,10 @@ MatrixOutput *MatrixOutput::make(const FileList &targetFiles, const FileList &qu /* MatrixOutput - protected methods */ QString MatrixOutput::toString(int row, int column) const { - if (targetFiles[column] == "Label") - return File::subject(data.at(row,column)); + if (targetFiles[column] == "Label") { + const int label = data.at(row,column); + return Globals->subjects.key(label, QString::number(label)); + } return QString::number(data.at(row,column)); } @@ -1085,153 +1076,6 @@ Gallery *Gallery::make(const File &file) return gallery; } -static TemplateList Downsample(const TemplateList &templates, const Transform *transform) -{ - // Return early when no downsampling is required - if ((transform->classes == std::numeric_limits::max()) && - (transform->instances == std::numeric_limits::max()) && - (transform->fraction >= 1)) - return templates; - - const bool atLeast = transform->instances < 0; - const int instances = abs(transform->instances); - - QList allLabels = templates.labels(); - QList uniqueLabels = allLabels.toSet().toList(); - qSort(uniqueLabels); - - QMap counts = templates.labelCounts(instances != std::numeric_limits::max()); - if ((instances != std::numeric_limits::max()) && (transform->classes != std::numeric_limits::max())) - foreach (int label, counts.keys()) - if (counts[label] < instances) - counts.remove(label); - uniqueLabels = counts.keys(); - if ((transform->classes != std::numeric_limits::max()) && (uniqueLabels.size() < transform->classes)) - qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); - - Common::seedRNG(); - QList selectedLabels = uniqueLabels; - if (transform->classes < uniqueLabels.size()) { - std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); - selectedLabels = selectedLabels.mid(0, transform->classes); - } - - TemplateList downsample; - for (int i=0; i indices; - for (int j=0; j("FTE", false))) - indices.append(j); - - std::random_shuffle(indices.begin(), indices.end()); - const int max = atLeast ? indices.size() : std::min(indices.size(), instances); - for (int j=0; jfraction < 1) { - std::random_shuffle(downsample.begin(), downsample.end()); - downsample = downsample.mid(0, downsample.size()*transform->fraction); - } - - return downsample; -} - -/*! - * \ingroup transforms - * \brief Clones the transform so that it can be applied independently. - * - * \em Independent transforms expect single-matrix templates. - */ -class Independent : public MetaTransform -{ - Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms STORED false) - BR_PROPERTY(QList, transforms, QList()) - - public: - /*! - * \brief Independent - * \param transform - */ - Independent(Transform *transform) - { - transform->setParent(this); - transforms.append(transform); - file = transform->file; - trainable = transform->trainable; - setObjectName(transforms.first()->objectName()); - } - -private: - Transform *clone() const - { - return new Independent(transforms.first()->clone()); - } - - static void _train(Transform *transform, const TemplateList *data) - { - transform->train(*data); - } - - void train(const TemplateList &data) - { - // Don't bother if the transform is untrainable - if (!trainable) return; - - QList templatesList; - foreach (const Template &t, data) { - if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) - qWarning("Independent::train template %s of size %d differs from expected size %d.", qPrintable(t.file.name), t.size(), templatesList.size()); - while (templatesList.size() < t.size()) - templatesList.append(TemplateList()); - for (int i=0; iclone()); - - for (int i=0; i futures; - for (int i=0; i mats; - for (int i=0; iproject(Template(src.file, src[i]), dst); - mats.append(dst); - dst.clear(); - } - dst.append(mats); - } - - void store(QDataStream &stream) const - { - const int size = transforms.size(); - stream << size; - for (int i=0; istore(stream); - } - - void load(QDataStream &stream) - { - int size; - stream >> size; - while (transforms.size() < size) - transforms.append(transforms.first()->clone()); - for (int i=0; iload(stream); - } -}; - /* Transform - public methods */ Transform::Transform(bool _independent, bool _trainable) { @@ -1279,8 +1123,17 @@ Transform *Transform::make(QString str, QObject *parent) File f = "." + str; Transform *transform = Factory::make(f); - if (transform->independent) - transform = new Independent(transform); + if (transform->independent) { +// Transform *independentTransform = Factory::make(".Independent"); +// static_cast(independentTransform)->setProperty("transform", qVariantFromValue(transform)); +// independentTransform->init(); +// transform = independentTransform; + + File independent(".Independent"); + independent.set("transform", qVariantFromValue(transform)); + transform = Factory::make(independent); + } + transform->setParent(parent); return transform; } diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 2775aee..a7e1a7f 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -107,11 +107,11 @@ void reset_##NAME() { NAME = DEFAULT; } * * The br::File is one of the workhorse classes in OpenBR. * It is typically used to store the path to a file on disk with associated metadata. - * The ability to associate a hashtable of metadata with the file helps keep the API simple and stable while providing customizable behavior when appropriate. + * The ability to associate a metadata map with the file helps keep the API simple and stable while providing customizable behavior when appropriate. * - * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata table. - * If the key does not exist in the local hashtable then it will be resolved using the properities in the global br::Context. - * This has the desirable effect that file metadata may optionally set globally using br::Context::set to operate on all files. + * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata. + * If the key does not exist in the local map then it will be resolved using the properities in the global br::Context. + * This has the desirable effect that file metadata may optionally be set globally using br::Context::set to operate on all files. * * Files have a simple grammar that allow them to be converted to and from strings. * If a string ends with a \c ] or \c ) then the text within the final \c [] or \c () are parsed as comma sperated metadata fields. @@ -119,6 +119,13 @@ void reset_##NAME() { NAME = DEFAULT; } * Fields within \c () are expected to have the format (value1, value2, ..., valueN) with the keys determined from the order of \c Q_PROPERTY. * The rest of the string is assigned to #name. * + * The metadata keys \c Subject and \c Label have special significance in the system. + * \c Subject is a string specifying a unique identifier used to determine ground truth match/non-match. + * \c Label is a floating point value used for supervised learning. + * When the system needs labels for training, but only subjects are provided in the file metadata, the rule for generating labels is as follows. + * If the subject value can be converted to a float then do so and consider that the label. + * Otherwise, generate a unique integer ID for the string starting from zero and incrementing by one everytime another ID is needed. + * * Metadata keys fall into one of two categories: * - \c camelCaseKeys are inputs that specify how to process the file. * - \c Capitalized_Underscored_Keys are outputs computed from processing the file. @@ -129,7 +136,8 @@ void reset_##NAME() { NAME = DEFAULT; } * --- | ---- | ----------- * separator | QString | Seperate #name into multiple files * Index | int | Index of a template in a template list - * Label | float | Classification/Regression class + * Subject | QString | Class name + * Label | float | Class value * Confidence | float | Classification/Regression quality * FTE | bool | Failure to enroll * FTO | bool | Failure to open @@ -153,7 +161,7 @@ struct BR_EXPORT File File() {} File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ - File(const QString &file, const QVariant &label) { init(file); set("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ + File(const QString &file, const QVariant &subject) { init(file); set("Subject", subject); } /*!< \brief Construct a file from a string and assign a label. */ File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ inline operator QString() const { return name; } /*!< \brief Returns #name. */ QString flat() const; /*!< \brief A stringified version of the file with metadata. */ @@ -195,7 +203,7 @@ struct BR_EXPORT File bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */ QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */ static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ - void set(const QString &key, const QVariant &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ + inline void set(const QString &key, const QVariant &value) { m_metadata.insert(key, value); } /*!< \brief Insert or overwrite the metadata key with the specified value. */ void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ @@ -235,10 +243,8 @@ struct BR_EXPORT File return variant.value(); } - static QString subject(int label); /*!< \brief Looks up the subject for the provided label. */ - inline QString subject() const { return subject(label()); } /*!< \brief Looks up the subject from the file's label. */ + QString subject() const; /*!< \brief Looks up the subject from the file's label. */ float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */ - inline void setLabel(float label) { set("Label", label); } /*!< \brief Convenience function for setting the file's \c Label. */ inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ QList namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */ @@ -497,6 +503,7 @@ public: QString argument(int index) const; /*!< \brief A string value for the argument at the specified index. */ QString description() const; /*!< \brief Returns a string description of the object. */ void setProperty(const QString &name, const QString &value); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ + void setProperty(const QString &name, const QVariant &value, bool failOnError = false); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ static QStringList parse(const QString &string, char split = ','); /*!< \brief Splits the string while respecting lexical scoping of (), [], \<\>, and {}. */ private: @@ -633,7 +640,7 @@ public: BR_PROPERTY(int, crossValidate, 0) QHash abbreviations; /*!< \brief Used by br::Transform::make() to expand abbreviated algorithms into their complete definitions. */ - QHash classes; /*!< \brief Used by classifiers to associate text class labels with unique integers IDs. */ + QHash subjects; /*!< \brief Used by classifiers to associate text class labels with unique integers IDs. */ QTime startTime; /*!< \brief Used to estimate timeRemaining(). */ /*! diff --git a/openbr/plugins/gallery.cpp b/openbr/plugins/gallery.cpp index 35b6aad..f0bb614 100644 --- a/openbr/plugins/gallery.cpp +++ b/openbr/plugins/gallery.cpp @@ -37,6 +37,7 @@ namespace br * \ingroup galleries * \brief Weka ARFF file format. * \author Josh Klontz \cite jklontz + * http://weka.wikispaces.com/ARFF+%28stable+version%29 */ class arffGallery : public Gallery { @@ -59,19 +60,16 @@ class arffGallery : public Gallery "@RELATION OpenBR\n" "\n"); - arffFile.write("@ATTRIBUTE filename STRING\n"); - arffFile.write(qPrintable("@ATTRIBUTE class {" + QStringList(Globals->classes.keys()).join(',') + "}\n")); - const int dimensions = t.m().rows * t.m().cols; for (int i=0; isubjects.keys()).join(',') + "}\n")); arffFile.write("\n@DATA\n"); } - arffFile.write(qPrintable("'" + t.file.name + "',")); - arffFile.write(qPrintable("'" + t.file.subject() + "',")); - arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(',')+"\n")); + arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(','))); + arffFile.write(qPrintable(",'" + t.file.subject() + "'\n")); } }; @@ -117,6 +115,8 @@ class galGallery : public Gallery void write(const Template &t) { + if (t.isEmpty() && t.file.isNull()) + return; stream << t; } }; @@ -464,7 +464,7 @@ class csvGallery : public Gallery static QString getCSVElement(const QString &key, const QVariant &value, bool header) { if ((key == "Label") && !header) { - QString stringLabel = Globals->classes.key(value.value()); + QString stringLabel = Globals->subjects.key(value.value()); if (stringLabel.isEmpty()) return value.value(); else return stringLabel; } else if (value.canConvert()) { diff --git a/openbr/plugins/independent.cpp b/openbr/plugins/independent.cpp new file mode 100644 index 0000000..b0153fe --- /dev/null +++ b/openbr/plugins/independent.cpp @@ -0,0 +1,167 @@ +#include +#include + +#include "openbr_internal.h" +#include "openbr/core/common.h" + +using namespace cv; + +namespace br +{ + +static TemplateList Downsample(const TemplateList &templates, const Transform *transform) +{ + // Return early when no downsampling is required + if ((transform->classes == std::numeric_limits::max()) && + (transform->instances == std::numeric_limits::max()) && + (transform->fraction >= 1)) + return templates; + + const bool atLeast = transform->instances < 0; + const int instances = abs(transform->instances); + + QList allLabels = templates.labels(); + QList uniqueLabels = allLabels.toSet().toList(); + qSort(uniqueLabels); + + QMap counts = templates.labelCounts(instances != std::numeric_limits::max()); + if ((instances != std::numeric_limits::max()) && (transform->classes != std::numeric_limits::max())) + foreach (int label, counts.keys()) + if (counts[label] < instances) + counts.remove(label); + uniqueLabels = counts.keys(); + if ((transform->classes != std::numeric_limits::max()) && (uniqueLabels.size() < transform->classes)) + qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); + + Common::seedRNG(); + QList selectedLabels = uniqueLabels; + if (transform->classes < uniqueLabels.size()) { + std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); + selectedLabels = selectedLabels.mid(0, transform->classes); + } + + TemplateList downsample; + for (int i=0; i indices; + for (int j=0; j("FTE", false))) + indices.append(j); + + std::random_shuffle(indices.begin(), indices.end()); + const int max = atLeast ? indices.size() : std::min(indices.size(), instances); + for (int j=0; jfraction < 1) { + std::random_shuffle(downsample.begin(), downsample.end()); + downsample = downsample.mid(0, downsample.size()*transform->fraction); + } + + return downsample; +} + +/*! + * \ingroup transforms + * \brief Clones the transform so that it can be applied independently. + * \author Josh Klontz \cite jklontz + * \em Independent transforms expect single-matrix templates. + */ +class IndependentTransform : public MetaTransform +{ + Q_OBJECT + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform STORED false) + BR_PROPERTY(br::Transform*, transform, NULL) + + QList transforms; + + void init() + { + transforms.clear(); + if (transform == NULL) + return; + + transform->setParent(this); + transforms.append(transform); + file = transform->file; + trainable = transform->trainable; + setObjectName(transform->objectName()); + } + + Transform *clone() const + { + IndependentTransform *independentTransform = new IndependentTransform(); + independentTransform->transform = transform->clone(); + independentTransform->init(); + return independentTransform; + } + + static void _train(Transform *transform, const TemplateList *data) + { + transform->train(*data); + } + + void train(const TemplateList &data) + { + // Don't bother if the transform is untrainable + if (!trainable) return; + + QList templatesList; + foreach (const Template &t, data) { + if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) + qWarning("Independent::train template %s of size %d differs from expected size %d.", qPrintable(t.file.name), t.size(), templatesList.size()); + while (templatesList.size() < t.size()) + templatesList.append(TemplateList()); + for (int i=0; iclone()); + + for (int i=0; i futures; + for (int i=0; i mats; + for (int i=0; iproject(Template(src.file, src[i]), dst); + mats.append(dst); + dst.clear(); + } + dst.append(mats); + } + + void store(QDataStream &stream) const + { + const int size = transforms.size(); + stream << size; + for (int i=0; istore(stream); + } + + void load(QDataStream &stream) + { + int size; + stream >> size; + while (transforms.size() < size) + transforms.append(transform->clone()); + for (int i=0; iload(stream); + } +}; + +BR_REGISTER(Transform, IndependentTransform) + +} // namespace br + +#include "independent.moc" diff --git a/openbr/plugins/mask.cpp b/openbr/plugins/mask.cpp index f9979e8..8b86221 100644 --- a/openbr/plugins/mask.cpp +++ b/openbr/plugins/mask.cpp @@ -171,7 +171,7 @@ class LargestConvexAreaTransform : public UntrainableTransform if (area / hullArea > 0.98) maxArea = std::max(maxArea, area); } - dst.file.setLabel(maxArea); + dst.file.set("Label", maxArea); } }; diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index a018244..94ae184 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -15,6 +15,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include +#include #include #include "openbr_internal.h" #include "openbr/core/common.h" @@ -440,7 +441,7 @@ private: const QString &file = src.file; if (cache.contains(file)) { dst = cache[file]; - dst.file.setLabel(src.file.label()); + dst.file.set("Label", src.file.label()); } else { transform->project(src, dst); cacheLock.lock(); diff --git a/openbr/plugins/misc.cpp b/openbr/plugins/misc.cpp index f84e7ea..7b9225f 100644 --- a/openbr/plugins/misc.cpp +++ b/openbr/plugins/misc.cpp @@ -423,6 +423,51 @@ class RelabelTransform : public UntrainableMetaTransform BR_REGISTER(Transform, RelabelTransform) +/*! + * \ingroup transforms + * \brief Remove templates with the specified file extension. + * \author Josh Klontz \cite jklontz + */ +class RemoveTemplatesTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) + BR_PROPERTY(QString, regexp, "") + + void project(const Template &src, Template &dst) const + { + const QRegularExpression re(regexp); + const QRegularExpressionMatch match = re.match(src.file.suffix()); + if (match.hasMatch()) dst = Template(); + else dst = src; + } +}; + +BR_REGISTER(Transform, RemoveTemplatesTransform) + +/*! + * \ingroup transforms + * \brief Remove template metadata with the specified key(s). + * \author Josh Klontz \cite jklontz + */ +class RemoveMetadataTransform : public UntrainableMetaTransform +{ + Q_OBJECT + Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) + BR_PROPERTY(QString, regexp, "") + + void project(const Template &src, Template &dst) const + { + dst = src; + const QRegularExpression re(regexp); + foreach (const QString &key, dst.file.localKeys()) + if (re.match(key).hasMatch()) + dst.file.remove(key); + } +}; + +BR_REGISTER(Transform, RemoveMetadataTransform) + } #include "misc.moc" diff --git a/openbr/plugins/output.cpp b/openbr/plugins/output.cpp index 33c1f75..e07a78b 100644 --- a/openbr/plugins/output.cpp +++ b/openbr/plugins/output.cpp @@ -298,7 +298,7 @@ class rankOutput : public MatrixOutput typedef QPair Pair; int rank = 1; foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector(data.row(i)), true)) { - if(targetFiles[pair.second].get("Label") == queryFiles[i].get("Label")) { + if (targetFiles[pair.second].get("Subject") == queryFiles[i].get("Subject")) { ranks.append(rank); positions.append(pair.second); scores.append(pair.first); diff --git a/openbr/plugins/pixel.cpp b/openbr/plugins/pixel.cpp index a4795f4..6d5c140 100644 --- a/openbr/plugins/pixel.cpp +++ b/openbr/plugins/pixel.cpp @@ -122,7 +122,7 @@ class PerPixelClassifierTransform : public MetaTransform } cv::Mat labelMat = src.file.value("labels").value(); uchar* plabel = labelMat.ptr(); - temp.file.setLabel(plabel[index]); + temp.file.set("Label", plabel[index]); if (orient){ Template rotated; diff --git a/openbr/plugins/svm.cpp b/openbr/plugins/svm.cpp index 470201f..614273f 100644 --- a/openbr/plugins/svm.cpp +++ b/openbr/plugins/svm.cpp @@ -114,7 +114,7 @@ private: void project(const Template &src, Template &dst) const { dst = src; - dst.file.setLabel((svm.predict(src.m().reshape(0, 1)) - b)/a); + dst.file.set("Label", ((svm.predict(src.m().reshape(0, 1)) - b)/a)); } void store(QDataStream &stream) const diff --git a/scripts/evalFaceRecognition-MEDS.sh b/scripts/evalFaceRecognition-MEDS.sh index bfb2156..544aed7 100755 --- a/scripts/evalFaceRecognition-MEDS.sh +++ b/scripts/evalFaceRecognition-MEDS.sh @@ -18,14 +18,5 @@ fi # Run Algorithm on MEDS br -algorithm ${ALGORITHM} -path ../data/MEDS/img -compare ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml ${ALGORITHM}_MEDS.mtx -eval ${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv -# Evaluate other algorithms -for ALGORITHM in COTS -do - if [ ! -e Algorithm_Dataset/${ALGORITHM}_MEDS.csv ]; then - br -eval ../data/MEDS/simmat/${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv & - fi -done -wait - # Plot results br -plot Algorithm_Dataset/*_MEDS.csv MEDS diff --git a/share/openbr/cmake/FindAlphanum.cmake b/share/openbr/cmake/FindAlphanum.cmake index 8c58ae1..64b2efa 100644 --- a/share/openbr/cmake/FindAlphanum.cmake +++ b/share/openbr/cmake/FindAlphanum.cmake @@ -1,2 +1,3 @@ find_path(ALPHANUM_DIR alphanum.hpp ${CMAKE_SOURCE_DIR}/3rdparty/*) +mark_as_advanced(ALPHANUM_DIR) include_directories(${ALPHANUM_DIR})