Commit e443d2244ccddf56b2e60f0ea898bfa70db5575f
Merge branch 'master' of https://github.com/biometrics/openbr
Showing
19 changed files
with
318 additions
and
265 deletions
app/examples/age_estimation.cpp
| @@ -29,9 +29,7 @@ | @@ -29,9 +29,7 @@ | ||
| 29 | 29 | ||
| 30 | static void printTemplate(const br::Template &t) | 30 | static void printTemplate(const br::Template &t) |
| 31 | { | 31 | { |
| 32 | - printf("%s age: %d\n", | ||
| 33 | - qPrintable(t.file.fileName()), | ||
| 34 | - t.file.get<int>("Label")); | 32 | + printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.label())); |
| 35 | } | 33 | } |
| 36 | 34 | ||
| 37 | int main(int argc, char *argv[]) | 35 | int main(int argc, char *argv[]) |
app/examples/face_recognition_evaluation.cpp
| @@ -55,14 +55,12 @@ int main(int argc, char *argv[]) | @@ -55,14 +55,12 @@ int main(int argc, char *argv[]) | ||
| 55 | 55 | ||
| 56 | // Evaluate the performance of OpenBR's FaceRecognition and a COTS face recognition system. | 56 | // Evaluate the performance of OpenBR's FaceRecognition and a COTS face recognition system. |
| 57 | br_eval("FaceRecognition_MEDS.mtx", "MEDS.mask", "Algorithm_Dataset/FaceRecognition_MEDS.csv"); | 57 | br_eval("FaceRecognition_MEDS.mtx", "MEDS.mask", "Algorithm_Dataset/FaceRecognition_MEDS.csv"); |
| 58 | - br_eval("../data/MEDS/simmat/COTS_MEDS.mtx", "MEDS.mask", "Algorithm_Dataset/COTS_MEDS.csv"); | ||
| 59 | 58 | ||
| 60 | // The '_' character has special significance and is used to populate plot legends. | 59 | // The '_' character has special significance and is used to populate plot legends. |
| 61 | // Requires R installation, see documentation of br_plot for details. | 60 | // Requires R installation, see documentation of br_plot for details. |
| 62 | - const char *files[2]; | 61 | + const char *files[1]; |
| 63 | files[0] = "Algorithm_Dataset/FaceRecognition_MEDS.csv"; | 62 | files[0] = "Algorithm_Dataset/FaceRecognition_MEDS.csv"; |
| 64 | - files[1] = "Algorithm_Dataset/COTS_MEDS.csv"; | ||
| 65 | - br_plot(2, files, "MEDS", true); | 63 | + br_plot(1, files, "MEDS", true); |
| 66 | 64 | ||
| 67 | br_finalize(); | 65 | br_finalize(); |
| 68 | return 0; | 66 | return 0; |
app/examples/gender_estimation.cpp
| @@ -29,9 +29,7 @@ | @@ -29,9 +29,7 @@ | ||
| 29 | 29 | ||
| 30 | static void printTemplate(const br::Template &t) | 30 | static void printTemplate(const br::Template &t) |
| 31 | { | 31 | { |
| 32 | - printf("%s gender: %s\n", | ||
| 33 | - qPrintable(t.file.fileName()), | ||
| 34 | - t.file.get<int>("Label") == 1 ? "Female" : "Male"); | 32 | + printf("%s gender: %s\n", qPrintable(t.file.fileName()), t.file.label() == 1 ? "Female" : "Male"); |
| 35 | } | 33 | } |
| 36 | 34 | ||
| 37 | int main(int argc, char *argv[]) | 35 | int main(int argc, char *argv[]) |
openbr/core/bee.cpp
| @@ -58,24 +58,18 @@ FileList BEE::readSigset(const QString &sigset, bool ignoreMetadata) | @@ -58,24 +58,18 @@ FileList BEE::readSigset(const QString &sigset, bool ignoreMetadata) | ||
| 58 | QString name = d.attribute("name"); | 58 | QString name = d.attribute("name"); |
| 59 | while (!fileNode.isNull()) { | 59 | while (!fileNode.isNull()) { |
| 60 | // Looping through files | 60 | // Looping through files |
| 61 | - File file; | 61 | + File file("", name); |
| 62 | 62 | ||
| 63 | QDomElement e = fileNode.toElement(); | 63 | QDomElement e = fileNode.toElement(); |
| 64 | QDomNamedNodeMap attributes = e.attributes(); | 64 | QDomNamedNodeMap attributes = e.attributes(); |
| 65 | for (int i=0; i<attributes.length(); i++) { | 65 | for (int i=0; i<attributes.length(); i++) { |
| 66 | - QString key = attributes.item(i).nodeName(); | ||
| 67 | - QString value = attributes.item(i).nodeValue(); | ||
| 68 | - | ||
| 69 | - if (key == "file-name") { | ||
| 70 | - File newFile(value, name); | ||
| 71 | - newFile.append(file); | ||
| 72 | - file = newFile; | ||
| 73 | - } else if (!ignoreMetadata) { | ||
| 74 | - file.set(key, value); | ||
| 75 | - } | 66 | + const QString key = attributes.item(i).nodeName(); |
| 67 | + const QString value = attributes.item(i).nodeValue(); | ||
| 68 | + if (key == "file-name") file.name = value; | ||
| 69 | + else if (!ignoreMetadata) file.set(key, value); | ||
| 76 | } | 70 | } |
| 77 | 71 | ||
| 78 | - if (file.isNull()) qFatal("Empty file-name in %s.", qPrintable(sigset)); | 72 | + if (file.name.isEmpty()) qFatal("Missing file-name in %s.", qPrintable(sigset)); |
| 79 | fileList.append(file); | 73 | fileList.append(file); |
| 80 | 74 | ||
| 81 | fileNode = fileNode.nextSibling(); | 75 | fileNode = fileNode.nextSibling(); |
| @@ -99,7 +93,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign | @@ -99,7 +93,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign | ||
| 99 | QStringList metadata; | 93 | QStringList metadata; |
| 100 | if (!ignoreMetadata) | 94 | if (!ignoreMetadata) |
| 101 | foreach (const QString &key, file.localKeys()) { | 95 | foreach (const QString &key, file.localKeys()) { |
| 102 | - if ((key == "Index") || (key == "Label")) continue; | 96 | + if ((key == "Index") || (key == "Subject")) continue; |
| 103 | metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); | 97 | metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); |
| 104 | } | 98 | } |
| 105 | lines.append("\t<biometric-signature name=\"" + file.subject() +"\">"); | 99 | lines.append("\t<biometric-signature name=\"" + file.subject() +"\">"); |
openbr/core/classify.cpp
| @@ -56,7 +56,7 @@ void br::EvalClassification(const QString &predictedInput, const QString &truthI | @@ -56,7 +56,7 @@ void br::EvalClassification(const QString &predictedInput, const QString &truthI | ||
| 56 | } | 56 | } |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | - QSharedPointer<Output> output(Output::make("", FileList() << "Label" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); | 59 | + QSharedPointer<Output> output(Output::make("", FileList() << "Subject" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); |
| 60 | 60 | ||
| 61 | int tpc = 0; | 61 | int tpc = 0; |
| 62 | int fnc = 0; | 62 | int fnc = 0; |
openbr/core/core.cpp
| @@ -77,7 +77,7 @@ struct AlgorithmCore | @@ -77,7 +77,7 @@ struct AlgorithmCore | ||
| 77 | const bool hasComparer = !distance.isNull(); | 77 | const bool hasComparer = !distance.isNull(); |
| 78 | out << hasComparer; | 78 | out << hasComparer; |
| 79 | if (hasComparer) distance->store(out); | 79 | if (hasComparer) distance->store(out); |
| 80 | - out << Globals->classes; | 80 | + out << Globals->subjects; |
| 81 | 81 | ||
| 82 | // Compress and save to file | 82 | // Compress and save to file |
| 83 | QtUtils::writeFile(model, data, -1); | 83 | QtUtils::writeFile(model, data, -1); |
| @@ -97,7 +97,7 @@ struct AlgorithmCore | @@ -97,7 +97,7 @@ struct AlgorithmCore | ||
| 97 | transform->load(in); | 97 | transform->load(in); |
| 98 | bool hasDistance; in >> hasDistance; | 98 | bool hasDistance; in >> hasDistance; |
| 99 | if (hasDistance) distance->load(in); | 99 | if (hasDistance) distance->load(in); |
| 100 | - in >> Globals->classes; | 100 | + in >> Globals->subjects; |
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | File getMemoryGallery(const File &file) const | 103 | File getMemoryGallery(const File &file) const |
openbr/gui/classifier.cpp
| @@ -44,13 +44,13 @@ void Classifier::_classify(File file) | @@ -44,13 +44,13 @@ void Classifier::_classify(File file) | ||
| 44 | 44 | ||
| 45 | if (algorithm == "GenderClassification") { | 45 | if (algorithm == "GenderClassification") { |
| 46 | key = "Gender"; | 46 | key = "Gender"; |
| 47 | - value = (f.get<int>("Label", 0) == 0 ? "Male" : "Female"); | 47 | + value = (f.label() == 0 ? "Male" : "Female"); |
| 48 | } else if (algorithm == "AgeRegression") { | 48 | } else if (algorithm == "AgeRegression") { |
| 49 | key = "Age"; | 49 | key = "Age"; |
| 50 | - value = QString::number(int(f.get<float>("Label", 0)+0.5)) + " Years"; | 50 | + value = QString::number(int(f.label()+0.5)) + " Years"; |
| 51 | } else { | 51 | } else { |
| 52 | key = algorithm; | 52 | key = algorithm; |
| 53 | - value = f.get<QString>("Label"); | 53 | + value = QString::number(f.label()); |
| 54 | } | 54 | } |
| 55 | break; | 55 | break; |
| 56 | } | 56 | } |
openbr/openbr_plugin.cpp
| @@ -132,33 +132,11 @@ QVariant File::parse(const QString &value) | @@ -132,33 +132,11 @@ QVariant File::parse(const QString &value) | ||
| 132 | if (ok) return point; | 132 | if (ok) return point; |
| 133 | const QRectF rect = QtUtils::toRect(value, &ok); | 133 | const QRectF rect = QtUtils::toRect(value, &ok); |
| 134 | if (ok) return rect; | 134 | if (ok) return rect; |
| 135 | - | ||
| 136 | - /* We assume that if the value starts with '0' | ||
| 137 | - then it was probably intended to be a string UID | ||
| 138 | - and that it's numerical value is not relevant. */ | ||
| 139 | - if (!value.startsWith('0') || (value == "0")) { | ||
| 140 | - const float f = value.toFloat(&ok); | ||
| 141 | - if (ok) return f; | ||
| 142 | - } | ||
| 143 | - | 135 | + const float f = value.toFloat(&ok); |
| 136 | + if (ok) return f; | ||
| 144 | return value; | 137 | return value; |
| 145 | } | 138 | } |
| 146 | 139 | ||
| 147 | -void File::set(const QString &key, const QVariant &value) | ||
| 148 | -{ | ||
| 149 | - if (key == "Label") { | ||
| 150 | - const QString valueString = value.toString(); | ||
| 151 | - if (!Globals->classes.contains(valueString)) { | ||
| 152 | - static QMutex mutex; | ||
| 153 | - QMutexLocker mutexLocker(&mutex); | ||
| 154 | - if (!Globals->classes.contains(valueString)) | ||
| 155 | - Globals->classes.insert(valueString, Globals->classes.size()); | ||
| 156 | - } | ||
| 157 | - } | ||
| 158 | - | ||
| 159 | - m_metadata.insert(key, value); | ||
| 160 | -} | ||
| 161 | - | ||
| 162 | void File::set(const QString &key, const QString &value) | 140 | void File::set(const QString &key, const QString &value) |
| 163 | { | 141 | { |
| 164 | if (value.startsWith('[') && value.endsWith(']')) { | 142 | if (value.startsWith('[') && value.endsWith(']')) { |
| @@ -179,22 +157,27 @@ bool File::getBool(const QString &key, bool defaultValue) const | @@ -179,22 +157,27 @@ bool File::getBool(const QString &key, bool defaultValue) const | ||
| 179 | return variant.value<bool>(); | 157 | return variant.value<bool>(); |
| 180 | } | 158 | } |
| 181 | 159 | ||
| 182 | -QString File::subject(int label) | 160 | +QString File::subject() const |
| 183 | { | 161 | { |
| 184 | - return Globals->classes.key(label, QString::number(label)); | 162 | + const QVariant l = m_metadata.value("Label"); |
| 163 | + if (!l.isNull()) return Globals->subjects.key(l.toFloat(), l.toString()); | ||
| 164 | + return m_metadata.value("Subject").toString(); | ||
| 185 | } | 165 | } |
| 186 | 166 | ||
| 187 | float File::label() const | 167 | float File::label() const |
| 188 | { | 168 | { |
| 189 | - const QVariant variant = value("Label"); | ||
| 190 | - if (variant.isNull()) return -1; | 169 | + const QVariant l = m_metadata.value("Label"); |
| 170 | + if (!l.isNull()) return l.toFloat(); | ||
| 191 | 171 | ||
| 192 | - if (Globals->classes.contains(variant.toString())) | ||
| 193 | - return Globals->classes.value(variant.toString()); | 172 | + const QVariant s = m_metadata.value("Subject"); |
| 173 | + if (s.isNull()) return -1; | ||
| 194 | 174 | ||
| 195 | - bool ok; | ||
| 196 | - const float val = variant.toFloat(&ok); | ||
| 197 | - return ok ? val : -1; | 175 | + const QString subject = s.toString(); |
| 176 | + static QMutex mutex; | ||
| 177 | + QMutexLocker mutexLocker(&mutex); | ||
| 178 | + if (!Globals->subjects.contains(subject)) | ||
| 179 | + Globals->subjects.insert(subject, Globals->subjects.size()); | ||
| 180 | + return Globals->subjects.value(subject); | ||
| 198 | } | 181 | } |
| 199 | 182 | ||
| 200 | QList<QPointF> File::namedPoints() const | 183 | QList<QPointF> File::namedPoints() const |
| @@ -310,8 +293,6 @@ QDataStream &br::operator<<(QDataStream &stream, const File &file) | @@ -310,8 +293,6 @@ QDataStream &br::operator<<(QDataStream &stream, const File &file) | ||
| 310 | QDataStream &br::operator>>(QDataStream &stream, File &file) | 293 | QDataStream &br::operator>>(QDataStream &stream, File &file) |
| 311 | { | 294 | { |
| 312 | return stream >> file.name >> file.m_metadata; | 295 | return stream >> file.name >> file.m_metadata; |
| 313 | - const QVariant label = file.m_metadata.value("Label"); | ||
| 314 | - if (!label.isNull()) file.set("Label", label); // Trigger population of Globals->classes | ||
| 315 | } | 296 | } |
| 316 | 297 | ||
| 317 | /* FileList - public methods */ | 298 | /* FileList - public methods */ |
| @@ -464,14 +445,15 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) | @@ -464,14 +445,15 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) | ||
| 464 | 445 | ||
| 465 | TemplateList TemplateList::relabel(const TemplateList &tl) | 446 | TemplateList TemplateList::relabel(const TemplateList &tl) |
| 466 | { | 447 | { |
| 467 | - QHash<int,int> labels; | ||
| 468 | - foreach (int label, tl.labels<int>()) | ||
| 469 | - if (!labels.contains(label)) | ||
| 470 | - labels.insert(label, labels.size()); | 448 | + const QList<int> originalLabels = tl.labels<int>(); |
| 449 | + QHash<int,int> labelTable; | ||
| 450 | + foreach (int label, originalLabels) | ||
| 451 | + if (!labelTable.contains(label)) | ||
| 452 | + labelTable.insert(label, labelTable.size()); | ||
| 471 | 453 | ||
| 472 | TemplateList result = tl; | 454 | TemplateList result = tl; |
| 473 | for (int i=0; i<result.size(); i++) | 455 | for (int i=0; i<result.size(); i++) |
| 474 | - result[i].file.setLabel(labels[result[i].file.label()]); | 456 | + result[i].file.set("Label", labelTable[originalLabels[i]]); |
| 475 | return result; | 457 | return result; |
| 476 | } | 458 | } |
| 477 | 459 | ||
| @@ -681,12 +663,17 @@ void Object::setProperty(const QString &name, const QString &value) | @@ -681,12 +663,17 @@ void Object::setProperty(const QString &name, const QString &value) | ||
| 681 | variant = value; | 663 | variant = value; |
| 682 | } | 664 | } |
| 683 | 665 | ||
| 684 | - if (!QObject::setProperty(qPrintable(name), variant) && !type.isEmpty()) | ||
| 685 | - qFatal("Failed to set %s::%s to: %s %s", | ||
| 686 | - metaObject()->className(), qPrintable(name), qPrintable(value), qPrintable(type)); | 666 | + setProperty(name, variant, !type.isEmpty()); |
| 667 | +} | ||
| 668 | + | ||
| 669 | +void Object::setProperty(const QString &name, const QVariant &value, bool failOnError) | ||
| 670 | +{ | ||
| 671 | + if (!QObject::setProperty(qPrintable(name), value) && failOnError) | ||
| 672 | + qFatal("Failed to set %s::%s to: %s", | ||
| 673 | + metaObject()->className(), qPrintable(name), qPrintable(value.toString())); | ||
| 687 | } | 674 | } |
| 688 | 675 | ||
| 689 | -QStringList br::Object::parse(const QString &string, char split) | 676 | +QStringList Object::parse(const QString &string, char split) |
| 690 | { | 677 | { |
| 691 | return QtUtils::parse(string, split); | 678 | return QtUtils::parse(string, split); |
| 692 | } | 679 | } |
| @@ -738,19 +725,21 @@ void Object::init(const File &file_) | @@ -738,19 +725,21 @@ void Object::init(const File &file_) | ||
| 738 | } | 725 | } |
| 739 | 726 | ||
| 740 | foreach (QString key, file.localKeys()) { | 727 | foreach (QString key, file.localKeys()) { |
| 741 | - const QString value = file.value(key).toString(); | 728 | + const QVariant value = file.value(key); |
| 729 | + const QString valueString = value.toString(); | ||
| 742 | 730 | ||
| 743 | if (key.startsWith(("_Arg"))) { | 731 | if (key.startsWith(("_Arg"))) { |
| 744 | - int argument_number = key.mid(4).toInt(); | ||
| 745 | - int target_idx = argument_number + firstAvailablePropertyIdx; | ||
| 746 | - | ||
| 747 | - if (target_idx >= metaObject()->propertyCount()) { | ||
| 748 | - qWarning("too many arguments for transform, ignoring %s\n", qPrintable(value)); | 732 | + int argumentNumber = key.mid(4).toInt(); |
| 733 | + int targetIdx = argumentNumber + firstAvailablePropertyIdx; | ||
| 734 | + if (targetIdx >= metaObject()->propertyCount()) { | ||
| 735 | + qWarning("too many arguments for transform %s, ignoring %s", qPrintable(objectName()), qPrintable(valueString)); | ||
| 749 | continue; | 736 | continue; |
| 750 | } | 737 | } |
| 751 | - key = metaObject()->property(target_idx).name(); | 738 | + key = metaObject()->property(targetIdx).name(); |
| 752 | } | 739 | } |
| 753 | - setProperty(key, value); | 740 | + |
| 741 | + if (valueString.isEmpty()) setProperty(key, value); // Set the property directly | ||
| 742 | + else setProperty(key, valueString); // Parse the value first | ||
| 754 | } | 743 | } |
| 755 | 744 | ||
| 756 | init(); | 745 | init(); |
| @@ -1038,8 +1027,10 @@ MatrixOutput *MatrixOutput::make(const FileList &targetFiles, const FileList &qu | @@ -1038,8 +1027,10 @@ MatrixOutput *MatrixOutput::make(const FileList &targetFiles, const FileList &qu | ||
| 1038 | /* MatrixOutput - protected methods */ | 1027 | /* MatrixOutput - protected methods */ |
| 1039 | QString MatrixOutput::toString(int row, int column) const | 1028 | QString MatrixOutput::toString(int row, int column) const |
| 1040 | { | 1029 | { |
| 1041 | - if (targetFiles[column] == "Label") | ||
| 1042 | - return File::subject(data.at<float>(row,column)); | 1030 | + if (targetFiles[column] == "Label") { |
| 1031 | + const int label = data.at<float>(row,column); | ||
| 1032 | + return Globals->subjects.key(label, QString::number(label)); | ||
| 1033 | + } | ||
| 1043 | return QString::number(data.at<float>(row,column)); | 1034 | return QString::number(data.at<float>(row,column)); |
| 1044 | } | 1035 | } |
| 1045 | 1036 | ||
| @@ -1085,153 +1076,6 @@ Gallery *Gallery::make(const File &file) | @@ -1085,153 +1076,6 @@ Gallery *Gallery::make(const File &file) | ||
| 1085 | return gallery; | 1076 | return gallery; |
| 1086 | } | 1077 | } |
| 1087 | 1078 | ||
| 1088 | -static TemplateList Downsample(const TemplateList &templates, const Transform *transform) | ||
| 1089 | -{ | ||
| 1090 | - // Return early when no downsampling is required | ||
| 1091 | - if ((transform->classes == std::numeric_limits<int>::max()) && | ||
| 1092 | - (transform->instances == std::numeric_limits<int>::max()) && | ||
| 1093 | - (transform->fraction >= 1)) | ||
| 1094 | - return templates; | ||
| 1095 | - | ||
| 1096 | - const bool atLeast = transform->instances < 0; | ||
| 1097 | - const int instances = abs(transform->instances); | ||
| 1098 | - | ||
| 1099 | - QList<int> allLabels = templates.labels<int>(); | ||
| 1100 | - QList<int> uniqueLabels = allLabels.toSet().toList(); | ||
| 1101 | - qSort(uniqueLabels); | ||
| 1102 | - | ||
| 1103 | - QMap<int,int> counts = templates.labelCounts(instances != std::numeric_limits<int>::max()); | ||
| 1104 | - if ((instances != std::numeric_limits<int>::max()) && (transform->classes != std::numeric_limits<int>::max())) | ||
| 1105 | - foreach (int label, counts.keys()) | ||
| 1106 | - if (counts[label] < instances) | ||
| 1107 | - counts.remove(label); | ||
| 1108 | - uniqueLabels = counts.keys(); | ||
| 1109 | - if ((transform->classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < transform->classes)) | ||
| 1110 | - qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); | ||
| 1111 | - | ||
| 1112 | - Common::seedRNG(); | ||
| 1113 | - QList<int> selectedLabels = uniqueLabels; | ||
| 1114 | - if (transform->classes < uniqueLabels.size()) { | ||
| 1115 | - std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); | ||
| 1116 | - selectedLabels = selectedLabels.mid(0, transform->classes); | ||
| 1117 | - } | ||
| 1118 | - | ||
| 1119 | - TemplateList downsample; | ||
| 1120 | - for (int i=0; i<selectedLabels.size(); i++) { | ||
| 1121 | - const int selectedLabel = selectedLabels[i]; | ||
| 1122 | - QList<int> indices; | ||
| 1123 | - for (int j=0; j<allLabels.size(); j++) | ||
| 1124 | - if ((allLabels[j] == selectedLabel) && (!templates.value(j).file.get<bool>("FTE", false))) | ||
| 1125 | - indices.append(j); | ||
| 1126 | - | ||
| 1127 | - std::random_shuffle(indices.begin(), indices.end()); | ||
| 1128 | - const int max = atLeast ? indices.size() : std::min(indices.size(), instances); | ||
| 1129 | - for (int j=0; j<max; j++) | ||
| 1130 | - downsample.append(templates.value(indices[j])); | ||
| 1131 | - } | ||
| 1132 | - | ||
| 1133 | - if (transform->fraction < 1) { | ||
| 1134 | - std::random_shuffle(downsample.begin(), downsample.end()); | ||
| 1135 | - downsample = downsample.mid(0, downsample.size()*transform->fraction); | ||
| 1136 | - } | ||
| 1137 | - | ||
| 1138 | - return downsample; | ||
| 1139 | -} | ||
| 1140 | - | ||
| 1141 | -/*! | ||
| 1142 | - * \ingroup transforms | ||
| 1143 | - * \brief Clones the transform so that it can be applied independently. | ||
| 1144 | - * | ||
| 1145 | - * \em Independent transforms expect single-matrix templates. | ||
| 1146 | - */ | ||
| 1147 | -class Independent : public MetaTransform | ||
| 1148 | -{ | ||
| 1149 | - Q_PROPERTY(QList<Transform*> transforms READ get_transforms WRITE set_transforms STORED false) | ||
| 1150 | - BR_PROPERTY(QList<Transform*>, transforms, QList<Transform*>()) | ||
| 1151 | - | ||
| 1152 | - public: | ||
| 1153 | - /*! | ||
| 1154 | - * \brief Independent | ||
| 1155 | - * \param transform | ||
| 1156 | - */ | ||
| 1157 | - Independent(Transform *transform) | ||
| 1158 | - { | ||
| 1159 | - transform->setParent(this); | ||
| 1160 | - transforms.append(transform); | ||
| 1161 | - file = transform->file; | ||
| 1162 | - trainable = transform->trainable; | ||
| 1163 | - setObjectName(transforms.first()->objectName()); | ||
| 1164 | - } | ||
| 1165 | - | ||
| 1166 | -private: | ||
| 1167 | - Transform *clone() const | ||
| 1168 | - { | ||
| 1169 | - return new Independent(transforms.first()->clone()); | ||
| 1170 | - } | ||
| 1171 | - | ||
| 1172 | - static void _train(Transform *transform, const TemplateList *data) | ||
| 1173 | - { | ||
| 1174 | - transform->train(*data); | ||
| 1175 | - } | ||
| 1176 | - | ||
| 1177 | - void train(const TemplateList &data) | ||
| 1178 | - { | ||
| 1179 | - // Don't bother if the transform is untrainable | ||
| 1180 | - if (!trainable) return; | ||
| 1181 | - | ||
| 1182 | - QList<TemplateList> templatesList; | ||
| 1183 | - foreach (const Template &t, data) { | ||
| 1184 | - if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) | ||
| 1185 | - qWarning("Independent::train template %s of size %d differs from expected size %d.", qPrintable(t.file.name), t.size(), templatesList.size()); | ||
| 1186 | - while (templatesList.size() < t.size()) | ||
| 1187 | - templatesList.append(TemplateList()); | ||
| 1188 | - for (int i=0; i<t.size(); i++) | ||
| 1189 | - templatesList[i].append(Template(t.file, t[i])); | ||
| 1190 | - } | ||
| 1191 | - | ||
| 1192 | - while (transforms.size() < templatesList.size()) | ||
| 1193 | - transforms.append(transforms.first()->clone()); | ||
| 1194 | - | ||
| 1195 | - for (int i=0; i<templatesList.size(); i++) | ||
| 1196 | - templatesList[i] = Downsample(templatesList[i], transforms[i]); | ||
| 1197 | - | ||
| 1198 | - QFutureSynchronizer<void> futures; | ||
| 1199 | - for (int i=0; i<templatesList.size(); i++) | ||
| 1200 | - futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i])); | ||
| 1201 | - futures.waitForFinished(); | ||
| 1202 | - } | ||
| 1203 | - | ||
| 1204 | - void project(const Template &src, Template &dst) const | ||
| 1205 | - { | ||
| 1206 | - dst.file = src.file; | ||
| 1207 | - QList<Mat> mats; | ||
| 1208 | - for (int i=0; i<src.size(); i++) { | ||
| 1209 | - transforms[i%transforms.size()]->project(Template(src.file, src[i]), dst); | ||
| 1210 | - mats.append(dst); | ||
| 1211 | - dst.clear(); | ||
| 1212 | - } | ||
| 1213 | - dst.append(mats); | ||
| 1214 | - } | ||
| 1215 | - | ||
| 1216 | - void store(QDataStream &stream) const | ||
| 1217 | - { | ||
| 1218 | - const int size = transforms.size(); | ||
| 1219 | - stream << size; | ||
| 1220 | - for (int i=0; i<size; i++) | ||
| 1221 | - transforms[i]->store(stream); | ||
| 1222 | - } | ||
| 1223 | - | ||
| 1224 | - void load(QDataStream &stream) | ||
| 1225 | - { | ||
| 1226 | - int size; | ||
| 1227 | - stream >> size; | ||
| 1228 | - while (transforms.size() < size) | ||
| 1229 | - transforms.append(transforms.first()->clone()); | ||
| 1230 | - for (int i=0; i<size; i++) | ||
| 1231 | - transforms[i]->load(stream); | ||
| 1232 | - } | ||
| 1233 | -}; | ||
| 1234 | - | ||
| 1235 | /* Transform - public methods */ | 1079 | /* Transform - public methods */ |
| 1236 | Transform::Transform(bool _independent, bool _trainable) | 1080 | Transform::Transform(bool _independent, bool _trainable) |
| 1237 | { | 1081 | { |
| @@ -1279,8 +1123,17 @@ Transform *Transform::make(QString str, QObject *parent) | @@ -1279,8 +1123,17 @@ Transform *Transform::make(QString str, QObject *parent) | ||
| 1279 | File f = "." + str; | 1123 | File f = "." + str; |
| 1280 | Transform *transform = Factory<Transform>::make(f); | 1124 | Transform *transform = Factory<Transform>::make(f); |
| 1281 | 1125 | ||
| 1282 | - if (transform->independent) | ||
| 1283 | - transform = new Independent(transform); | 1126 | + if (transform->independent) { |
| 1127 | +// Transform *independentTransform = Factory<Transform>::make(".Independent"); | ||
| 1128 | +// static_cast<QObject*>(independentTransform)->setProperty("transform", qVariantFromValue<void*>(transform)); | ||
| 1129 | +// independentTransform->init(); | ||
| 1130 | +// transform = independentTransform; | ||
| 1131 | + | ||
| 1132 | + File independent(".Independent"); | ||
| 1133 | + independent.set("transform", qVariantFromValue<void*>(transform)); | ||
| 1134 | + transform = Factory<Transform>::make(independent); | ||
| 1135 | + } | ||
| 1136 | + | ||
| 1284 | transform->setParent(parent); | 1137 | transform->setParent(parent); |
| 1285 | return transform; | 1138 | return transform; |
| 1286 | } | 1139 | } |
openbr/openbr_plugin.h
| @@ -107,11 +107,11 @@ void reset_##NAME() { NAME = DEFAULT; } | @@ -107,11 +107,11 @@ void reset_##NAME() { NAME = DEFAULT; } | ||
| 107 | * | 107 | * |
| 108 | * The br::File is one of the workhorse classes in OpenBR. | 108 | * The br::File is one of the workhorse classes in OpenBR. |
| 109 | * It is typically used to store the path to a file on disk with associated metadata. | 109 | * It is typically used to store the path to a file on disk with associated metadata. |
| 110 | - * The ability to associate a hashtable of metadata with the file helps keep the API simple and stable while providing customizable behavior when appropriate. | 110 | + * The ability to associate a metadata map with the file helps keep the API simple and stable while providing customizable behavior when appropriate. |
| 111 | * | 111 | * |
| 112 | - * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata table. | ||
| 113 | - * If the key does not exist in the local hashtable then it will be resolved using the properities in the global br::Context. | ||
| 114 | - * This has the desirable effect that file metadata may optionally set globally using br::Context::set to operate on all files. | 112 | + * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata. |
| 113 | + * If the key does not exist in the local map then it will be resolved using the properities in the global br::Context. | ||
| 114 | + * This has the desirable effect that file metadata may optionally be set globally using br::Context::set to operate on all files. | ||
| 115 | * | 115 | * |
| 116 | * Files have a simple grammar that allow them to be converted to and from strings. | 116 | * Files have a simple grammar that allow them to be converted to and from strings. |
| 117 | * 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. | 117 | * 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; } | @@ -119,6 +119,13 @@ void reset_##NAME() { NAME = DEFAULT; } | ||
| 119 | * Fields within \c () are expected to have the format <tt>(value1, value2, ..., valueN)</tt> with the keys determined from the order of \c Q_PROPERTY. | 119 | * Fields within \c () are expected to have the format <tt>(value1, value2, ..., valueN)</tt> with the keys determined from the order of \c Q_PROPERTY. |
| 120 | * The rest of the string is assigned to #name. | 120 | * The rest of the string is assigned to #name. |
| 121 | * | 121 | * |
| 122 | + * The metadata keys \c Subject and \c Label have special significance in the system. | ||
| 123 | + * \c Subject is a string specifying a unique identifier used to determine ground truth match/non-match. | ||
| 124 | + * \c Label is a floating point value used for supervised learning. | ||
| 125 | + * When the system needs labels for training, but only subjects are provided in the file metadata, the rule for generating labels is as follows. | ||
| 126 | + * If the subject value can be converted to a float then do so and consider that the label. | ||
| 127 | + * Otherwise, generate a unique integer ID for the string starting from zero and incrementing by one everytime another ID is needed. | ||
| 128 | + * | ||
| 122 | * Metadata keys fall into one of two categories: | 129 | * Metadata keys fall into one of two categories: |
| 123 | * - \c camelCaseKeys are inputs that specify how to process the file. | 130 | * - \c camelCaseKeys are inputs that specify how to process the file. |
| 124 | * - \c Capitalized_Underscored_Keys are outputs computed from processing the file. | 131 | * - \c Capitalized_Underscored_Keys are outputs computed from processing the file. |
| @@ -129,7 +136,8 @@ void reset_##NAME() { NAME = DEFAULT; } | @@ -129,7 +136,8 @@ void reset_##NAME() { NAME = DEFAULT; } | ||
| 129 | * --- | ---- | ----------- | 136 | * --- | ---- | ----------- |
| 130 | * separator | QString | Seperate #name into multiple files | 137 | * separator | QString | Seperate #name into multiple files |
| 131 | * Index | int | Index of a template in a template list | 138 | * Index | int | Index of a template in a template list |
| 132 | - * Label | float | Classification/Regression class | 139 | + * Subject | QString | Class name |
| 140 | + * Label | float | Class value | ||
| 133 | * Confidence | float | Classification/Regression quality | 141 | * Confidence | float | Classification/Regression quality |
| 134 | * FTE | bool | Failure to enroll | 142 | * FTE | bool | Failure to enroll |
| 135 | * FTO | bool | Failure to open | 143 | * FTO | bool | Failure to open |
| @@ -153,7 +161,7 @@ struct BR_EXPORT File | @@ -153,7 +161,7 @@ struct BR_EXPORT File | ||
| 153 | 161 | ||
| 154 | File() {} | 162 | File() {} |
| 155 | File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ | 163 | File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ |
| 156 | - File(const QString &file, const QVariant &label) { init(file); set("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ | 164 | + File(const QString &file, const QVariant &subject) { init(file); set("Subject", subject); } /*!< \brief Construct a file from a string and assign a label. */ |
| 157 | File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ | 165 | File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ |
| 158 | inline operator QString() const { return name; } /*!< \brief Returns #name. */ | 166 | inline operator QString() const { return name; } /*!< \brief Returns #name. */ |
| 159 | QString flat() const; /*!< \brief A stringified version of the file with metadata. */ | 167 | QString flat() const; /*!< \brief A stringified version of the file with metadata. */ |
| @@ -195,7 +203,7 @@ struct BR_EXPORT File | @@ -195,7 +203,7 @@ struct BR_EXPORT File | ||
| 195 | bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */ | 203 | bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */ |
| 196 | QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */ | 204 | QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */ |
| 197 | static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ | 205 | static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ |
| 198 | - void set(const QString &key, const QVariant &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ | 206 | + 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. */ |
| 199 | void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ | 207 | void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ |
| 200 | inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ | 208 | inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ |
| 201 | 209 | ||
| @@ -235,10 +243,8 @@ struct BR_EXPORT File | @@ -235,10 +243,8 @@ struct BR_EXPORT File | ||
| 235 | return variant.value<T>(); | 243 | return variant.value<T>(); |
| 236 | } | 244 | } |
| 237 | 245 | ||
| 238 | - static QString subject(int label); /*!< \brief Looks up the subject for the provided label. */ | ||
| 239 | - inline QString subject() const { return subject(label()); } /*!< \brief Looks up the subject from the file's label. */ | 246 | + QString subject() const; /*!< \brief Looks up the subject from the file's label. */ |
| 240 | float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */ | 247 | float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */ |
| 241 | - inline void setLabel(float label) { set("Label", label); } /*!< \brief Convenience function for setting the file's \c Label. */ | ||
| 242 | inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ | 248 | inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ |
| 243 | 249 | ||
| 244 | QList<QPointF> namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */ | 250 | QList<QPointF> namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */ |
| @@ -497,6 +503,7 @@ public: | @@ -497,6 +503,7 @@ public: | ||
| 497 | QString argument(int index) const; /*!< \brief A string value for the argument at the specified index. */ | 503 | QString argument(int index) const; /*!< \brief A string value for the argument at the specified index. */ |
| 498 | QString description() const; /*!< \brief Returns a string description of the object. */ | 504 | QString description() const; /*!< \brief Returns a string description of the object. */ |
| 499 | void setProperty(const QString &name, const QString &value); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ | 505 | void setProperty(const QString &name, const QString &value); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ |
| 506 | + void setProperty(const QString &name, const QVariant &value, bool failOnError = false); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ | ||
| 500 | static QStringList parse(const QString &string, char split = ','); /*!< \brief Splits the string while respecting lexical scoping of <tt>()</tt>, <tt>[]</tt>, <tt>\<\></tt>, and <tt>{}</tt>. */ | 507 | static QStringList parse(const QString &string, char split = ','); /*!< \brief Splits the string while respecting lexical scoping of <tt>()</tt>, <tt>[]</tt>, <tt>\<\></tt>, and <tt>{}</tt>. */ |
| 501 | 508 | ||
| 502 | private: | 509 | private: |
| @@ -633,7 +640,7 @@ public: | @@ -633,7 +640,7 @@ public: | ||
| 633 | BR_PROPERTY(int, crossValidate, 0) | 640 | BR_PROPERTY(int, crossValidate, 0) |
| 634 | 641 | ||
| 635 | QHash<QString,QString> abbreviations; /*!< \brief Used by br::Transform::make() to expand abbreviated algorithms into their complete definitions. */ | 642 | QHash<QString,QString> abbreviations; /*!< \brief Used by br::Transform::make() to expand abbreviated algorithms into their complete definitions. */ |
| 636 | - QHash<QString,int> classes; /*!< \brief Used by classifiers to associate text class labels with unique integers IDs. */ | 643 | + QHash<QString,int> subjects; /*!< \brief Used by classifiers to associate text class labels with unique integers IDs. */ |
| 637 | QTime startTime; /*!< \brief Used to estimate timeRemaining(). */ | 644 | QTime startTime; /*!< \brief Used to estimate timeRemaining(). */ |
| 638 | 645 | ||
| 639 | /*! | 646 | /*! |
openbr/plugins/gallery.cpp
| @@ -37,6 +37,7 @@ namespace br | @@ -37,6 +37,7 @@ namespace br | ||
| 37 | * \ingroup galleries | 37 | * \ingroup galleries |
| 38 | * \brief Weka ARFF file format. | 38 | * \brief Weka ARFF file format. |
| 39 | * \author Josh Klontz \cite jklontz | 39 | * \author Josh Klontz \cite jklontz |
| 40 | + * http://weka.wikispaces.com/ARFF+%28stable+version%29 | ||
| 40 | */ | 41 | */ |
| 41 | class arffGallery : public Gallery | 42 | class arffGallery : public Gallery |
| 42 | { | 43 | { |
| @@ -59,19 +60,16 @@ class arffGallery : public Gallery | @@ -59,19 +60,16 @@ class arffGallery : public Gallery | ||
| 59 | "@RELATION OpenBR\n" | 60 | "@RELATION OpenBR\n" |
| 60 | "\n"); | 61 | "\n"); |
| 61 | 62 | ||
| 62 | - arffFile.write("@ATTRIBUTE filename STRING\n"); | ||
| 63 | - arffFile.write(qPrintable("@ATTRIBUTE class {" + QStringList(Globals->classes.keys()).join(',') + "}\n")); | ||
| 64 | - | ||
| 65 | const int dimensions = t.m().rows * t.m().cols; | 63 | const int dimensions = t.m().rows * t.m().cols; |
| 66 | for (int i=0; i<dimensions; i++) | 64 | for (int i=0; i<dimensions; i++) |
| 67 | - arffFile.write(qPrintable("@ATTRIBUTE v" + QString::number(i) + " NUMERIC\n")); | 65 | + arffFile.write(qPrintable("@ATTRIBUTE v" + QString::number(i) + " REAL\n")); |
| 66 | + arffFile.write(qPrintable("@ATTRIBUTE class {" + QStringList(Globals->subjects.keys()).join(',') + "}\n")); | ||
| 68 | 67 | ||
| 69 | arffFile.write("\n@DATA\n"); | 68 | arffFile.write("\n@DATA\n"); |
| 70 | } | 69 | } |
| 71 | 70 | ||
| 72 | - arffFile.write(qPrintable("'" + t.file.name + "',")); | ||
| 73 | - arffFile.write(qPrintable("'" + t.file.subject() + "',")); | ||
| 74 | - arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(',')+"\n")); | 71 | + arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(','))); |
| 72 | + arffFile.write(qPrintable(",'" + t.file.subject() + "'\n")); | ||
| 75 | } | 73 | } |
| 76 | }; | 74 | }; |
| 77 | 75 | ||
| @@ -117,6 +115,8 @@ class galGallery : public Gallery | @@ -117,6 +115,8 @@ class galGallery : public Gallery | ||
| 117 | 115 | ||
| 118 | void write(const Template &t) | 116 | void write(const Template &t) |
| 119 | { | 117 | { |
| 118 | + if (t.isEmpty() && t.file.isNull()) | ||
| 119 | + return; | ||
| 120 | stream << t; | 120 | stream << t; |
| 121 | } | 121 | } |
| 122 | }; | 122 | }; |
| @@ -464,7 +464,7 @@ class csvGallery : public Gallery | @@ -464,7 +464,7 @@ class csvGallery : public Gallery | ||
| 464 | static QString getCSVElement(const QString &key, const QVariant &value, bool header) | 464 | static QString getCSVElement(const QString &key, const QVariant &value, bool header) |
| 465 | { | 465 | { |
| 466 | if ((key == "Label") && !header) { | 466 | if ((key == "Label") && !header) { |
| 467 | - QString stringLabel = Globals->classes.key(value.value<int>()); | 467 | + QString stringLabel = Globals->subjects.key(value.value<int>()); |
| 468 | if (stringLabel.isEmpty()) return value.value<QString>(); | 468 | if (stringLabel.isEmpty()) return value.value<QString>(); |
| 469 | else return stringLabel; | 469 | else return stringLabel; |
| 470 | } else if (value.canConvert<QString>()) { | 470 | } else if (value.canConvert<QString>()) { |
openbr/plugins/independent.cpp
0 โ 100644
| 1 | +#include <QFutureSynchronizer> | ||
| 2 | +#include <QtConcurrentRun> | ||
| 3 | + | ||
| 4 | +#include "openbr_internal.h" | ||
| 5 | +#include "openbr/core/common.h" | ||
| 6 | + | ||
| 7 | +using namespace cv; | ||
| 8 | + | ||
| 9 | +namespace br | ||
| 10 | +{ | ||
| 11 | + | ||
| 12 | +static TemplateList Downsample(const TemplateList &templates, const Transform *transform) | ||
| 13 | +{ | ||
| 14 | + // Return early when no downsampling is required | ||
| 15 | + if ((transform->classes == std::numeric_limits<int>::max()) && | ||
| 16 | + (transform->instances == std::numeric_limits<int>::max()) && | ||
| 17 | + (transform->fraction >= 1)) | ||
| 18 | + return templates; | ||
| 19 | + | ||
| 20 | + const bool atLeast = transform->instances < 0; | ||
| 21 | + const int instances = abs(transform->instances); | ||
| 22 | + | ||
| 23 | + QList<int> allLabels = templates.labels<int>(); | ||
| 24 | + QList<int> uniqueLabels = allLabels.toSet().toList(); | ||
| 25 | + qSort(uniqueLabels); | ||
| 26 | + | ||
| 27 | + QMap<int,int> counts = templates.labelCounts(instances != std::numeric_limits<int>::max()); | ||
| 28 | + if ((instances != std::numeric_limits<int>::max()) && (transform->classes != std::numeric_limits<int>::max())) | ||
| 29 | + foreach (int label, counts.keys()) | ||
| 30 | + if (counts[label] < instances) | ||
| 31 | + counts.remove(label); | ||
| 32 | + uniqueLabels = counts.keys(); | ||
| 33 | + if ((transform->classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < transform->classes)) | ||
| 34 | + qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); | ||
| 35 | + | ||
| 36 | + Common::seedRNG(); | ||
| 37 | + QList<int> selectedLabels = uniqueLabels; | ||
| 38 | + if (transform->classes < uniqueLabels.size()) { | ||
| 39 | + std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); | ||
| 40 | + selectedLabels = selectedLabels.mid(0, transform->classes); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + TemplateList downsample; | ||
| 44 | + for (int i=0; i<selectedLabels.size(); i++) { | ||
| 45 | + const int selectedLabel = selectedLabels[i]; | ||
| 46 | + QList<int> indices; | ||
| 47 | + for (int j=0; j<allLabels.size(); j++) | ||
| 48 | + if ((allLabels[j] == selectedLabel) && (!templates.value(j).file.get<bool>("FTE", false))) | ||
| 49 | + indices.append(j); | ||
| 50 | + | ||
| 51 | + std::random_shuffle(indices.begin(), indices.end()); | ||
| 52 | + const int max = atLeast ? indices.size() : std::min(indices.size(), instances); | ||
| 53 | + for (int j=0; j<max; j++) | ||
| 54 | + downsample.append(templates.value(indices[j])); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + if (transform->fraction < 1) { | ||
| 58 | + std::random_shuffle(downsample.begin(), downsample.end()); | ||
| 59 | + downsample = downsample.mid(0, downsample.size()*transform->fraction); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + return downsample; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +/*! | ||
| 66 | + * \ingroup transforms | ||
| 67 | + * \brief Clones the transform so that it can be applied independently. | ||
| 68 | + * \author Josh Klontz \cite jklontz | ||
| 69 | + * \em Independent transforms expect single-matrix templates. | ||
| 70 | + */ | ||
| 71 | +class IndependentTransform : public MetaTransform | ||
| 72 | +{ | ||
| 73 | + Q_OBJECT | ||
| 74 | + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform STORED false) | ||
| 75 | + BR_PROPERTY(br::Transform*, transform, NULL) | ||
| 76 | + | ||
| 77 | + QList<Transform*> transforms; | ||
| 78 | + | ||
| 79 | + void init() | ||
| 80 | + { | ||
| 81 | + transforms.clear(); | ||
| 82 | + if (transform == NULL) | ||
| 83 | + return; | ||
| 84 | + | ||
| 85 | + transform->setParent(this); | ||
| 86 | + transforms.append(transform); | ||
| 87 | + file = transform->file; | ||
| 88 | + trainable = transform->trainable; | ||
| 89 | + setObjectName(transform->objectName()); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + Transform *clone() const | ||
| 93 | + { | ||
| 94 | + IndependentTransform *independentTransform = new IndependentTransform(); | ||
| 95 | + independentTransform->transform = transform->clone(); | ||
| 96 | + independentTransform->init(); | ||
| 97 | + return independentTransform; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + static void _train(Transform *transform, const TemplateList *data) | ||
| 101 | + { | ||
| 102 | + transform->train(*data); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + void train(const TemplateList &data) | ||
| 106 | + { | ||
| 107 | + // Don't bother if the transform is untrainable | ||
| 108 | + if (!trainable) return; | ||
| 109 | + | ||
| 110 | + QList<TemplateList> templatesList; | ||
| 111 | + foreach (const Template &t, data) { | ||
| 112 | + if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) | ||
| 113 | + qWarning("Independent::train template %s of size %d differs from expected size %d.", qPrintable(t.file.name), t.size(), templatesList.size()); | ||
| 114 | + while (templatesList.size() < t.size()) | ||
| 115 | + templatesList.append(TemplateList()); | ||
| 116 | + for (int i=0; i<t.size(); i++) | ||
| 117 | + templatesList[i].append(Template(t.file, t[i])); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + while (transforms.size() < templatesList.size()) | ||
| 121 | + transforms.append(transform->clone()); | ||
| 122 | + | ||
| 123 | + for (int i=0; i<templatesList.size(); i++) | ||
| 124 | + templatesList[i] = Downsample(templatesList[i], transforms[i]); | ||
| 125 | + | ||
| 126 | + QFutureSynchronizer<void> futures; | ||
| 127 | + for (int i=0; i<templatesList.size(); i++) | ||
| 128 | + futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i])); | ||
| 129 | + futures.waitForFinished(); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + void project(const Template &src, Template &dst) const | ||
| 133 | + { | ||
| 134 | + dst.file = src.file; | ||
| 135 | + QList<Mat> mats; | ||
| 136 | + for (int i=0; i<src.size(); i++) { | ||
| 137 | + transforms[i%transforms.size()]->project(Template(src.file, src[i]), dst); | ||
| 138 | + mats.append(dst); | ||
| 139 | + dst.clear(); | ||
| 140 | + } | ||
| 141 | + dst.append(mats); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + void store(QDataStream &stream) const | ||
| 145 | + { | ||
| 146 | + const int size = transforms.size(); | ||
| 147 | + stream << size; | ||
| 148 | + for (int i=0; i<size; i++) | ||
| 149 | + transforms[i]->store(stream); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + void load(QDataStream &stream) | ||
| 153 | + { | ||
| 154 | + int size; | ||
| 155 | + stream >> size; | ||
| 156 | + while (transforms.size() < size) | ||
| 157 | + transforms.append(transform->clone()); | ||
| 158 | + for (int i=0; i<size; i++) | ||
| 159 | + transforms[i]->load(stream); | ||
| 160 | + } | ||
| 161 | +}; | ||
| 162 | + | ||
| 163 | +BR_REGISTER(Transform, IndependentTransform) | ||
| 164 | + | ||
| 165 | +} // namespace br | ||
| 166 | + | ||
| 167 | +#include "independent.moc" |
openbr/plugins/mask.cpp
| @@ -171,7 +171,7 @@ class LargestConvexAreaTransform : public UntrainableTransform | @@ -171,7 +171,7 @@ class LargestConvexAreaTransform : public UntrainableTransform | ||
| 171 | if (area / hullArea > 0.98) | 171 | if (area / hullArea > 0.98) |
| 172 | maxArea = std::max(maxArea, area); | 172 | maxArea = std::max(maxArea, area); |
| 173 | } | 173 | } |
| 174 | - dst.file.setLabel(maxArea); | 174 | + dst.file.set("Label", maxArea); |
| 175 | } | 175 | } |
| 176 | }; | 176 | }; |
| 177 | 177 |
openbr/plugins/meta.cpp
| @@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
| 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 16 | 16 | ||
| 17 | #include <QFutureSynchronizer> | 17 | #include <QFutureSynchronizer> |
| 18 | +#include <QRegularExpression> | ||
| 18 | #include <QtConcurrentRun> | 19 | #include <QtConcurrentRun> |
| 19 | #include "openbr_internal.h" | 20 | #include "openbr_internal.h" |
| 20 | #include "openbr/core/common.h" | 21 | #include "openbr/core/common.h" |
| @@ -440,7 +441,7 @@ private: | @@ -440,7 +441,7 @@ private: | ||
| 440 | const QString &file = src.file; | 441 | const QString &file = src.file; |
| 441 | if (cache.contains(file)) { | 442 | if (cache.contains(file)) { |
| 442 | dst = cache[file]; | 443 | dst = cache[file]; |
| 443 | - dst.file.setLabel(src.file.label()); | 444 | + dst.file.set("Label", src.file.label()); |
| 444 | } else { | 445 | } else { |
| 445 | transform->project(src, dst); | 446 | transform->project(src, dst); |
| 446 | cacheLock.lock(); | 447 | cacheLock.lock(); |
openbr/plugins/misc.cpp
| @@ -423,6 +423,51 @@ class RelabelTransform : public UntrainableMetaTransform | @@ -423,6 +423,51 @@ class RelabelTransform : public UntrainableMetaTransform | ||
| 423 | 423 | ||
| 424 | BR_REGISTER(Transform, RelabelTransform) | 424 | BR_REGISTER(Transform, RelabelTransform) |
| 425 | 425 | ||
| 426 | +/*! | ||
| 427 | + * \ingroup transforms | ||
| 428 | + * \brief Remove templates with the specified file extension. | ||
| 429 | + * \author Josh Klontz \cite jklontz | ||
| 430 | + */ | ||
| 431 | +class RemoveTemplatesTransform : public UntrainableMetaTransform | ||
| 432 | +{ | ||
| 433 | + Q_OBJECT | ||
| 434 | + Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) | ||
| 435 | + BR_PROPERTY(QString, regexp, "") | ||
| 436 | + | ||
| 437 | + void project(const Template &src, Template &dst) const | ||
| 438 | + { | ||
| 439 | + const QRegularExpression re(regexp); | ||
| 440 | + const QRegularExpressionMatch match = re.match(src.file.suffix()); | ||
| 441 | + if (match.hasMatch()) dst = Template(); | ||
| 442 | + else dst = src; | ||
| 443 | + } | ||
| 444 | +}; | ||
| 445 | + | ||
| 446 | +BR_REGISTER(Transform, RemoveTemplatesTransform) | ||
| 447 | + | ||
| 448 | +/*! | ||
| 449 | + * \ingroup transforms | ||
| 450 | + * \brief Remove template metadata with the specified key(s). | ||
| 451 | + * \author Josh Klontz \cite jklontz | ||
| 452 | + */ | ||
| 453 | +class RemoveMetadataTransform : public UntrainableMetaTransform | ||
| 454 | +{ | ||
| 455 | + Q_OBJECT | ||
| 456 | + Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) | ||
| 457 | + BR_PROPERTY(QString, regexp, "") | ||
| 458 | + | ||
| 459 | + void project(const Template &src, Template &dst) const | ||
| 460 | + { | ||
| 461 | + dst = src; | ||
| 462 | + const QRegularExpression re(regexp); | ||
| 463 | + foreach (const QString &key, dst.file.localKeys()) | ||
| 464 | + if (re.match(key).hasMatch()) | ||
| 465 | + dst.file.remove(key); | ||
| 466 | + } | ||
| 467 | +}; | ||
| 468 | + | ||
| 469 | +BR_REGISTER(Transform, RemoveMetadataTransform) | ||
| 470 | + | ||
| 426 | } | 471 | } |
| 427 | 472 | ||
| 428 | #include "misc.moc" | 473 | #include "misc.moc" |
openbr/plugins/output.cpp
| @@ -298,7 +298,7 @@ class rankOutput : public MatrixOutput | @@ -298,7 +298,7 @@ class rankOutput : public MatrixOutput | ||
| 298 | typedef QPair<float,int> Pair; | 298 | typedef QPair<float,int> Pair; |
| 299 | int rank = 1; | 299 | int rank = 1; |
| 300 | foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { | 300 | foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { |
| 301 | - if(targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) { | 301 | + if (targetFiles[pair.second].get<QString>("Subject") == queryFiles[i].get<QString>("Subject")) { |
| 302 | ranks.append(rank); | 302 | ranks.append(rank); |
| 303 | positions.append(pair.second); | 303 | positions.append(pair.second); |
| 304 | scores.append(pair.first); | 304 | scores.append(pair.first); |
openbr/plugins/pixel.cpp
| @@ -122,7 +122,7 @@ class PerPixelClassifierTransform : public MetaTransform | @@ -122,7 +122,7 @@ class PerPixelClassifierTransform : public MetaTransform | ||
| 122 | } | 122 | } |
| 123 | cv::Mat labelMat = src.file.value("labels").value<cv::Mat>(); | 123 | cv::Mat labelMat = src.file.value("labels").value<cv::Mat>(); |
| 124 | uchar* plabel = labelMat.ptr(); | 124 | uchar* plabel = labelMat.ptr(); |
| 125 | - temp.file.setLabel(plabel[index]); | 125 | + temp.file.set("Label", plabel[index]); |
| 126 | 126 | ||
| 127 | if (orient){ | 127 | if (orient){ |
| 128 | Template rotated; | 128 | Template rotated; |
openbr/plugins/svm.cpp
| @@ -114,7 +114,7 @@ private: | @@ -114,7 +114,7 @@ private: | ||
| 114 | void project(const Template &src, Template &dst) const | 114 | void project(const Template &src, Template &dst) const |
| 115 | { | 115 | { |
| 116 | dst = src; | 116 | dst = src; |
| 117 | - dst.file.setLabel((svm.predict(src.m().reshape(0, 1)) - b)/a); | 117 | + dst.file.set("Label", ((svm.predict(src.m().reshape(0, 1)) - b)/a)); |
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | void store(QDataStream &stream) const | 120 | void store(QDataStream &stream) const |
scripts/evalFaceRecognition-MEDS.sh
| @@ -18,14 +18,5 @@ fi | @@ -18,14 +18,5 @@ fi | ||
| 18 | # Run Algorithm on MEDS | 18 | # Run Algorithm on MEDS |
| 19 | 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 | 19 | 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 |
| 20 | 20 | ||
| 21 | -# Evaluate other algorithms | ||
| 22 | -for ALGORITHM in COTS | ||
| 23 | -do | ||
| 24 | - if [ ! -e Algorithm_Dataset/${ALGORITHM}_MEDS.csv ]; then | ||
| 25 | - br -eval ../data/MEDS/simmat/${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv & | ||
| 26 | - fi | ||
| 27 | -done | ||
| 28 | -wait | ||
| 29 | - | ||
| 30 | # Plot results | 21 | # Plot results |
| 31 | br -plot Algorithm_Dataset/*_MEDS.csv MEDS | 22 | br -plot Algorithm_Dataset/*_MEDS.csv MEDS |
share/openbr/cmake/FindAlphanum.cmake