Commit e443d2244ccddf56b2e60f0ea898bfa70db5575f

Authored by Scott Klum
2 parents 90961a27 30c65225

Merge branch 'master' of https://github.com/biometrics/openbr

app/examples/age_estimation.cpp
... ... @@ -29,9 +29,7 @@
29 29  
30 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 35 int main(int argc, char *argv[])
... ...
app/examples/face_recognition_evaluation.cpp
... ... @@ -55,14 +55,12 @@ int main(int argc, char *argv[])
55 55  
56 56 // Evaluate the performance of OpenBR's FaceRecognition and a COTS face recognition system.
57 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 59 // The '_' character has special significance and is used to populate plot legends.
61 60 // Requires R installation, see documentation of br_plot for details.
62   - const char *files[2];
  61 + const char *files[1];
63 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 65 br_finalize();
68 66 return 0;
... ...
app/examples/gender_estimation.cpp
... ... @@ -29,9 +29,7 @@
29 29  
30 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 35 int main(int argc, char *argv[])
... ...
openbr/core/bee.cpp
... ... @@ -58,24 +58,18 @@ FileList BEE::readSigset(const QString &amp;sigset, bool ignoreMetadata)
58 58 QString name = d.attribute("name");
59 59 while (!fileNode.isNull()) {
60 60 // Looping through files
61   - File file;
  61 + File file("", name);
62 62  
63 63 QDomElement e = fileNode.toElement();
64 64 QDomNamedNodeMap attributes = e.attributes();
65 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 73 fileList.append(file);
80 74  
81 75 fileNode = fileNode.nextSibling();
... ... @@ -99,7 +93,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign
99 93 QStringList metadata;
100 94 if (!ignoreMetadata)
101 95 foreach (const QString &key, file.localKeys()) {
102   - if ((key == "Index") || (key == "Label")) continue;
  96 + if ((key == "Index") || (key == "Subject")) continue;
103 97 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\"");
104 98 }
105 99 lines.append("\t<biometric-signature name=\"" + file.subject() +"\">");
... ...
openbr/core/classify.cpp
... ... @@ -56,7 +56,7 @@ void br::EvalClassification(const QString &amp;predictedInput, const QString &amp;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 61 int tpc = 0;
62 62 int fnc = 0;
... ...
openbr/core/core.cpp
... ... @@ -77,7 +77,7 @@ struct AlgorithmCore
77 77 const bool hasComparer = !distance.isNull();
78 78 out << hasComparer;
79 79 if (hasComparer) distance->store(out);
80   - out << Globals->classes;
  80 + out << Globals->subjects;
81 81  
82 82 // Compress and save to file
83 83 QtUtils::writeFile(model, data, -1);
... ... @@ -97,7 +97,7 @@ struct AlgorithmCore
97 97 transform->load(in);
98 98 bool hasDistance; in >> hasDistance;
99 99 if (hasDistance) distance->load(in);
100   - in >> Globals->classes;
  100 + in >> Globals->subjects;
101 101 }
102 102  
103 103 File getMemoryGallery(const File &file) const
... ...
openbr/gui/classifier.cpp
... ... @@ -44,13 +44,13 @@ void Classifier::_classify(File file)
44 44  
45 45 if (algorithm == "GenderClassification") {
46 46 key = "Gender";
47   - value = (f.get<int>("Label", 0) == 0 ? "Male" : "Female");
  47 + value = (f.label() == 0 ? "Male" : "Female");
48 48 } else if (algorithm == "AgeRegression") {
49 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 51 } else {
52 52 key = algorithm;
53   - value = f.get<QString>("Label");
  53 + value = QString::number(f.label());
54 54 }
55 55 break;
56 56 }
... ...
openbr/openbr_plugin.cpp
... ... @@ -132,33 +132,11 @@ QVariant File::parse(const QString &amp;value)
132 132 if (ok) return point;
133 133 const QRectF rect = QtUtils::toRect(value, &ok);
134 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 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 140 void File::set(const QString &key, const QString &value)
163 141 {
164 142 if (value.startsWith('[') && value.endsWith(']')) {
... ... @@ -179,22 +157,27 @@ bool File::getBool(const QString &amp;key, bool defaultValue) const
179 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 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 183 QList<QPointF> File::namedPoints() const
... ... @@ -310,8 +293,6 @@ QDataStream &amp;br::operator&lt;&lt;(QDataStream &amp;stream, const File &amp;file)
310 293 QDataStream &br::operator>>(QDataStream &stream, File &file)
311 294 {
312 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 298 /* FileList - public methods */
... ... @@ -464,14 +445,15 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
464 445  
465 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 454 TemplateList result = tl;
473 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 457 return result;
476 458 }
477 459  
... ... @@ -681,12 +663,17 @@ void Object::setProperty(const QString &amp;name, const QString &amp;value)
681 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 678 return QtUtils::parse(string, split);
692 679 }
... ... @@ -738,19 +725,21 @@ void Object::init(const File &amp;file_)
738 725 }
739 726  
740 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 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 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 745 init();
... ... @@ -1038,8 +1027,10 @@ MatrixOutput *MatrixOutput::make(const FileList &amp;targetFiles, const FileList &amp;qu
1038 1027 /* MatrixOutput - protected methods */
1039 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 1034 return QString::number(data.at<float>(row,column));
1044 1035 }
1045 1036  
... ... @@ -1085,153 +1076,6 @@ Gallery *Gallery::make(const File &amp;file)
1085 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 1079 /* Transform - public methods */
1236 1080 Transform::Transform(bool _independent, bool _trainable)
1237 1081 {
... ... @@ -1279,8 +1123,17 @@ Transform *Transform::make(QString str, QObject *parent)
1279 1123 File f = "." + str;
1280 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 1137 transform->setParent(parent);
1285 1138 return transform;
1286 1139 }
... ...
openbr/openbr_plugin.h
... ... @@ -107,11 +107,11 @@ void reset_##NAME() { NAME = DEFAULT; }
107 107 *
108 108 * The br::File is one of the workhorse classes in OpenBR.
109 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 116 * Files have a simple grammar that allow them to be converted to and from strings.
117 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 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 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 129 * Metadata keys fall into one of two categories:
123 130 * - \c camelCaseKeys are inputs that specify how to process the file.
124 131 * - \c Capitalized_Underscored_Keys are outputs computed from processing the file.
... ... @@ -129,7 +136,8 @@ void reset_##NAME() { NAME = DEFAULT; }
129 136 * --- | ---- | -----------
130 137 * separator | QString | Seperate #name into multiple files
131 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 141 * Confidence | float | Classification/Regression quality
134 142 * FTE | bool | Failure to enroll
135 143 * FTO | bool | Failure to open
... ... @@ -153,7 +161,7 @@ struct BR_EXPORT File
153 161  
154 162 File() {}
155 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 165 File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */
158 166 inline operator QString() const { return name; } /*!< \brief Returns #name. */
159 167 QString flat() const; /*!< \brief A stringified version of the file with metadata. */
... ... @@ -195,7 +203,7 @@ struct BR_EXPORT File
195 203 bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */
196 204 QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */
197 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 207 void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */
200 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 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 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 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 250 QList<QPointF> namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */
... ... @@ -497,6 +503,7 @@ public:
497 503 QString argument(int index) const; /*!< \brief A string value for the argument at the specified index. */
498 504 QString description() const; /*!< \brief Returns a string description of the object. */
499 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 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 509 private:
... ... @@ -633,7 +640,7 @@ public:
633 640 BR_PROPERTY(int, crossValidate, 0)
634 641  
635 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 644 QTime startTime; /*!< \brief Used to estimate timeRemaining(). */
638 645  
639 646 /*!
... ...
openbr/plugins/gallery.cpp
... ... @@ -37,6 +37,7 @@ namespace br
37 37 * \ingroup galleries
38 38 * \brief Weka ARFF file format.
39 39 * \author Josh Klontz \cite jklontz
  40 + * http://weka.wikispaces.com/ARFF+%28stable+version%29
40 41 */
41 42 class arffGallery : public Gallery
42 43 {
... ... @@ -59,19 +60,16 @@ class arffGallery : public Gallery
59 60 "@RELATION OpenBR\n"
60 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 63 const int dimensions = t.m().rows * t.m().cols;
66 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 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 115  
118 116 void write(const Template &t)
119 117 {
  118 + if (t.isEmpty() && t.file.isNull())
  119 + return;
120 120 stream << t;
121 121 }
122 122 };
... ... @@ -464,7 +464,7 @@ class csvGallery : public Gallery
464 464 static QString getCSVElement(const QString &key, const QVariant &value, bool header)
465 465 {
466 466 if ((key == "Label") && !header) {
467   - QString stringLabel = Globals->classes.key(value.value<int>());
  467 + QString stringLabel = Globals->subjects.key(value.value<int>());
468 468 if (stringLabel.isEmpty()) return value.value<QString>();
469 469 else return stringLabel;
470 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 171 if (area / hullArea > 0.98)
172 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 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17 17 #include <QFutureSynchronizer>
  18 +#include <QRegularExpression>
18 19 #include <QtConcurrentRun>
19 20 #include "openbr_internal.h"
20 21 #include "openbr/core/common.h"
... ... @@ -440,7 +441,7 @@ private:
440 441 const QString &file = src.file;
441 442 if (cache.contains(file)) {
442 443 dst = cache[file];
443   - dst.file.setLabel(src.file.label());
  444 + dst.file.set("Label", src.file.label());
444 445 } else {
445 446 transform->project(src, dst);
446 447 cacheLock.lock();
... ...
openbr/plugins/misc.cpp
... ... @@ -423,6 +423,51 @@ class RelabelTransform : public UntrainableMetaTransform
423 423  
424 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 473 #include "misc.moc"
... ...
openbr/plugins/output.cpp
... ... @@ -298,7 +298,7 @@ class rankOutput : public MatrixOutput
298 298 typedef QPair<float,int> Pair;
299 299 int rank = 1;
300 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 302 ranks.append(rank);
303 303 positions.append(pair.second);
304 304 scores.append(pair.first);
... ...
openbr/plugins/pixel.cpp
... ... @@ -122,7 +122,7 @@ class PerPixelClassifierTransform : public MetaTransform
122 122 }
123 123 cv::Mat labelMat = src.file.value("labels").value<cv::Mat>();
124 124 uchar* plabel = labelMat.ptr();
125   - temp.file.setLabel(plabel[index]);
  125 + temp.file.set("Label", plabel[index]);
126 126  
127 127 if (orient){
128 128 Template rotated;
... ...
openbr/plugins/svm.cpp
... ... @@ -114,7 +114,7 @@ private:
114 114 void project(const Template &src, Template &dst) const
115 115 {
116 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 120 void store(QDataStream &stream) const
... ...
scripts/evalFaceRecognition-MEDS.sh
... ... @@ -18,14 +18,5 @@ fi
18 18 # Run Algorithm on MEDS
19 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 21 # Plot results
31 22 br -plot Algorithm_Dataset/*_MEDS.csv MEDS
... ...
share/openbr/cmake/FindAlphanum.cmake
1 1 find_path(ALPHANUM_DIR alphanum.hpp ${CMAKE_SOURCE_DIR}/3rdparty/*)
  2 +mark_as_advanced(ALPHANUM_DIR)
2 3 include_directories(${ALPHANUM_DIR})
... ...