Commit da345e9d0139a8693792404f2dfecb6df892b774

Authored by jklontz
2 parents 9e34c2c0 dc645cee

Merge pull request #53 from biometrics/local_remap

Changes to subject/label
app/examples/age_estimation.cpp
... ... @@ -29,7 +29,7 @@
29 29  
30 30 static void printTemplate(const br::Template &t)
31 31 {
32   - printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.label()));
  32 + printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.get<float>("Subject")));
33 33 }
34 34  
35 35 int main(int argc, char *argv[])
... ...
app/examples/gender_estimation.cpp
... ... @@ -29,7 +29,7 @@
29 29  
30 30 static void printTemplate(const br::Template &t)
31 31 {
32   - printf("%s gender: %s\n", qPrintable(t.file.fileName()), t.file.label() == 1 ? "Female" : "Male");
  32 + printf("%s gender: %s\n", qPrintable(t.file.fileName()), qPrintable(t.file.get<QString>("Subject")));
33 33 }
34 34  
35 35 int main(int argc, char *argv[])
... ...
openbr/core/bee.cpp
... ... @@ -96,7 +96,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign
96 96 if ((key == "Index") || (key == "Subject")) continue;
97 97 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\"");
98 98 }
99   - lines.append("\t<biometric-signature name=\"" + file.subject() +"\">");
  99 + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Subject") +"\">");
100 100 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>");
101 101 lines.append("\t</biometric-signature>");
102 102 }
... ... @@ -260,26 +260,28 @@ void BEE::makeMask(const QString &amp;targetInput, const QString &amp;queryInput, const
260 260  
261 261 cv::Mat BEE::makeMask(const br::FileList &targets, const br::FileList &queries, int partition)
262 262 {
263   - QList<float> targetLabels = targets.labels();
264   - QList<float> queryLabels = queries.labels();
  263 + // Would like to use indexProperty for this, but didn't make a version of that for Filelist yet
  264 + // -cao
  265 + QList<QString> targetLabels = targets.get<QString>("Subject", "-1");
  266 + QList<QString> queryLabels = queries.get<QString>("Subject", "-1");
265 267 QList<int> targetPartitions = targets.crossValidationPartitions();
266 268 QList<int> queryPartitions = queries.crossValidationPartitions();
267 269  
268 270 Mat mask(queries.size(), targets.size(), CV_8UC1);
269 271 for (int i=0; i<queries.size(); i++) {
270 272 const QString &fileA = queries[i];
271   - const int labelA = queryLabels[i];
  273 + const QString labelA = queryLabels[i];
272 274 const int partitionA = queryPartitions[i];
273 275  
274 276 for (int j=0; j<targets.size(); j++) {
275 277 const QString &fileB = targets[j];
276   - const int labelB = targetLabels[j];
  278 + const QString labelB = targetLabels[j];
277 279 const int partitionB = targetPartitions[j];
278 280  
279 281 Mask_t val;
280 282 if (fileA == fileB) val = DontCare;
281   - else if (labelA == -1) val = DontCare;
282   - else if (labelB == -1) val = DontCare;
  283 + else if (labelA == "-1") val = DontCare;
  284 + else if (labelB == "-1") val = DontCare;
283 285 else if (partitionA != partition) val = DontCare;
284 286 else if (partitionB == -1) val = NonMatch;
285 287 else if (partitionB != partition) val = DontCare;
... ...
openbr/core/classify.cpp
... ... @@ -45,8 +45,8 @@ void br::EvalClassification(const QString &amp;predictedInput, const QString &amp;truthI
45 45 qFatal("Input order mismatch.");
46 46  
47 47 // Typically these lists will be of length one, but this generalization allows measuring multi-class labeling accuracy.
48   - QString predictedSubject = predicted[i].file.subject();
49   - QString trueSubject = truth[i].file.subject();
  48 + QString predictedSubject = predicted[i].file.get<QString>("Subject");
  49 + QString trueSubject = truth[i].file.get<QString>("Subject");
50 50  
51 51 QStringList predictedSubjects(predictedSubject);
52 52 QStringList trueSubjects(trueSubject);
... ... @@ -66,11 +66,12 @@ void br::EvalClassification(const QString &amp;predictedInput, const QString &amp;truthI
66 66 counters[subject].falsePositive += 1.f / predictedSubjects.size();
67 67 }
68 68  
69   - QSharedPointer<Output> output(Output::make("", FileList() << "Subject" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size())));
  69 + const QStringList keys = counters.keys();
  70 + QSharedPointer<Output> output(Output::make("", FileList() << "Count" << "Precision" << "Recall" << "F-score", FileList(keys)));
70 71  
71 72 int tpc = 0;
72 73 int fnc = 0;
73   - const QStringList keys = counters.keys();
  74 +
74 75 for (int i=0; i<counters.size(); i++) {
75 76 const QString &subject = keys[i];
76 77 const Counter &counter = counters[subject];
... ... @@ -80,11 +81,10 @@ void br::EvalClassification(const QString &amp;predictedInput, const QString &amp;truthI
80 81 const float precision = counter.truePositive / (float)(counter.truePositive + counter.falsePositive);
81 82 const float recall = counter.truePositive / (float)(counter.truePositive + counter.falseNegative);
82 83 const float fscore = 2 * precision * recall / (precision + recall);
83   - output->setRelative(File("", subject).label(), i, 0);
84   - output->setRelative(count, i, 1);
85   - output->setRelative(precision, i, 2);
86   - output->setRelative(recall, i, 3);
87   - output->setRelative(fscore, i, 4);
  84 + output->setRelative(count, i, 0);
  85 + output->setRelative(precision, i, 1);
  86 + output->setRelative(recall, i, 2);
  87 + output->setRelative(fscore, i, 3);
88 88 }
89 89  
90 90 qDebug("Overall Accuracy = %f", (float)tpc / (float)(tpc + fnc));
... ... @@ -103,9 +103,9 @@ void br::EvalRegression(const QString &amp;predictedInput, const QString &amp;truthInput
103 103 for (int i=0; i<predicted.size(); i++) {
104 104 if (predicted[i].file.name != truth[i].file.name)
105 105 qFatal("Input order mismatch.");
106   - rmsError += pow(predicted[i].file.label()-truth[i].file.label(), 2.f);
107   - truthValues.append(QString::number(truth[i].file.label()));
108   - predictedValues.append(QString::number(predicted[i].file.label()));
  106 + rmsError += pow(predicted[i].file.get<float>("Subject")-truth[i].file.get<float>("Subject"), 2.f);
  107 + truthValues.append(QString::number(truth[i].file.get<float>("Subject")));
  108 + predictedValues.append(QString::number(predicted[i].file.get<float>("Subject")));
109 109 }
110 110  
111 111 QStringList rSource;
... ...
openbr/core/cluster.cpp
... ... @@ -278,7 +278,9 @@ void br::EvalClustering(const QString &amp;csv, const QString &amp;input)
278 278 {
279 279 qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input));
280 280  
281   - QList<float> labels = TemplateList::fromGallery(input).files().labels();
  281 + // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are
  282 + // not named).
  283 + QList<int> labels = TemplateList::fromGallery(input).files().get<int>("Subject");
282 284  
283 285 QHash<int, int> labelToIndex;
284 286 int nClusters = 0;
... ...
openbr/core/core.cpp
... ... @@ -78,7 +78,6 @@ struct AlgorithmCore
78 78 const bool hasComparer = !distance.isNull();
79 79 out << hasComparer;
80 80 if (hasComparer) distance->store(out);
81   - out << Globals->subjects;
82 81  
83 82 // Compress and save to file
84 83 QtUtils::writeFile(model, data, -1);
... ... @@ -98,7 +97,6 @@ struct AlgorithmCore
98 97 transform->load(in);
99 98 bool hasDistance; in >> hasDistance;
100 99 if (hasDistance) distance->load(in);
101   - in >> Globals->subjects;
102 100 }
103 101  
104 102 File getMemoryGallery(const File &file) const
... ...
openbr/core/opencvutils.cpp
... ... @@ -119,6 +119,17 @@ Mat OpenCVUtils::toMat(const QList&lt;float&gt; &amp;src, int rows)
119 119 return dst;
120 120 }
121 121  
  122 +Mat OpenCVUtils::toMat(const QList<int> &src, int rows)
  123 +{
  124 + if (rows == -1) rows = src.size();
  125 + int columns = src.isEmpty() ? 0 : src.size() / rows;
  126 + if (rows*columns != src.size()) qFatal("Invalid matrix size.");
  127 + Mat dst(rows, columns, CV_32FC1);
  128 + for (int i=0; i<src.size(); i++)
  129 + dst.at<float>(i/columns,i%columns) = src[i];
  130 + return dst;
  131 +}
  132 +
122 133 Mat OpenCVUtils::toMat(const QList<Mat> &src)
123 134 {
124 135 if (src.isEmpty()) return Mat();
... ...
openbr/core/opencvutils.h
... ... @@ -35,6 +35,8 @@ namespace OpenCVUtils
35 35  
36 36 // To image
37 37 cv::Mat toMat(const QList<float> &src, int rows = -1);
  38 + cv::Mat toMat(const QList<int> &src, int rows = -1);
  39 +
38 40 cv::Mat toMat(const QList<cv::Mat> &src); // Data organized one matrix per row
39 41 cv::Mat toMatByRow(const QList<cv::Mat> &src); // Data organized one row per row
40 42  
... ...
openbr/frvt2012.cpp
... ... @@ -132,7 +132,7 @@ int32_t SdkEstimator::estimate_age(const ONEFACE &amp;input_face, int32_t &amp;age)
132 132 TemplateList templates;
133 133 templates.append(templateFromONEFACE(input_face));
134 134 templates >> *frvt2012_age_transform.data();
135   - age = templates.first().file.label();
  135 + age = templates.first().file.get<float>("Subject");
136 136 return templates.first().file.failed() ? 4 : 0;
137 137 }
138 138  
... ... @@ -141,6 +141,6 @@ int32_t SdkEstimator::estimate_gender(const ONEFACE &amp;input_face, int8_t &amp;gender,
141 141 TemplateList templates;
142 142 templates.append(templateFromONEFACE(input_face));
143 143 templates >> *frvt2012_gender_transform.data();
144   - mf = gender = templates.first().file.label();
  144 + mf = gender = templates.first().file.get<QString>("Subject") == "Male" ? 0 : 1;
145 145 return templates.first().file.failed() ? 4 : 0;
146 146 }
... ...
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.label() == 0 ? "Male" : "Female");
  47 + value = (f.get<QString>("Subject"));
48 48 } else if (algorithm == "AgeRegression") {
49 49 key = "Age";
50   - value = QString::number(int(f.label()+0.5)) + " Years";
  50 + value = QString::number(int(f.get<float>("Subject")+0.5)) + " Years";
51 51 } else {
52 52 key = algorithm;
53   - value = QString::number(f.label());
  53 + value = f.get<QString>("Subject");
54 54 }
55 55 break;
56 56 }
... ...
openbr/openbr_plugin.cpp
... ... @@ -167,34 +167,6 @@ bool File::getBool(const QString &amp;key, bool defaultValue) const
167 167 return variant.value<bool>();
168 168 }
169 169  
170   -QString File::subject() const
171   -{
172   - const QVariant l = m_metadata.value("Label");
173   - if (!l.isNull()) return Globals->subjects.key(l.toFloat(), l.toString());
174   - return m_metadata.value("Subject").toString();
175   -}
176   -
177   -float File::label() const
178   -{
179   - const QVariant l = m_metadata.value("Label");
180   - if (!l.isNull()) return l.toFloat();
181   -
182   - const QVariant s = m_metadata.value("Subject");
183   - if (s.isNull()) return -1;
184   -
185   - const QString subject = s.toString();
186   -
187   - bool is_num = false;
188   - float num = subject.toFloat(&is_num);
189   - if (is_num) return num;
190   -
191   - static QMutex mutex;
192   - QMutexLocker mutexLocker(&mutex);
193   - if (!Globals->subjects.contains(subject))
194   - Globals->subjects.insert(subject, Globals->subjects.size());
195   - return Globals->subjects.value(subject);
196   -}
197   -
198 170 QList<QPointF> File::namedPoints() const
199 171 {
200 172 QList<QPointF> landmarks;
... ... @@ -360,14 +332,6 @@ void FileList::sort(const QString&amp; key)
360 332 *this = sortedList;
361 333 }
362 334  
363   -QList<float> FileList::labels() const
364   -{
365   - QList<float> labels; labels.reserve(size());
366   - foreach (const File &f, *this)
367   - labels.append(f.label());
368   - return labels;
369   -}
370   -
371 335 QList<int> FileList::crossValidationPartitions() const
372 336 {
373 337 QList<int> crossValidationPartitions; crossValidationPartitions.reserve(size());
... ... @@ -449,7 +413,7 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
449 413  
450 414 newTemplates[i].file.set("Partition", -1);
451 415 } else {
452   - const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.subject().toLatin1(), QCryptographicHash::Md5);
  416 + const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Subject").toLatin1(), QCryptographicHash::Md5);
453 417 // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow
454 418 newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate);
455 419 }
... ... @@ -469,11 +433,13 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
469 433 return templates;
470 434 }
471 435  
472   -TemplateList TemplateList::relabel(const TemplateList &tl)
  436 +// indexes some property, assigns an integer id to each unique value of propName
  437 +// stores the index values in "Label" of the output template list
  438 +TemplateList TemplateList::relabel(const TemplateList &tl, const QString & propName)
473 439 {
474   - const QList<int> originalLabels = tl.labels<int>();
475   - QHash<int,int> labelTable;
476   - foreach (int label, originalLabels)
  440 + const QList<QString> originalLabels = tl.get<QString>(propName);
  441 + QHash<QString,int> labelTable;
  442 + foreach (const QString & label, originalLabels)
477 443 if (!labelTable.contains(label))
478 444 labelTable.insert(label, labelTable.size());
479 445  
... ... @@ -483,6 +449,52 @@ TemplateList TemplateList::relabel(const TemplateList &amp;tl)
483 449 return result;
484 450 }
485 451  
  452 +QList<int> TemplateList::indexProperty(const QString & propName, QHash<QString, int> * valueMap,QHash<int, QVariant> * reverseLookup) const
  453 +{
  454 + QHash<QString, int> dummyForwards;
  455 + QHash<int, QVariant> dummyBackwards;
  456 +
  457 + if (!valueMap) valueMap = &dummyForwards;
  458 + if (!reverseLookup) reverseLookup = &dummyBackwards;
  459 +
  460 + return indexProperty(propName, *valueMap, *reverseLookup);
  461 +}
  462 +
  463 +QList<int> TemplateList::indexProperty(const QString & propName, QHash<QString, int> & valueMap, QHash<int, QVariant> & reverseLookup) const
  464 +{
  465 + valueMap.clear();
  466 + reverseLookup.clear();
  467 +
  468 + const QList<QVariant> originalLabels = values(propName);
  469 + foreach (const QVariant & label, originalLabels) {
  470 + QString labelString = label.toString();
  471 + if (!valueMap.contains(labelString)) {
  472 + reverseLookup.insert(valueMap.size(), label);
  473 + valueMap.insert(labelString, valueMap.size());
  474 + }
  475 + }
  476 +
  477 + QList<int> result;
  478 + for (int i=0; i<originalLabels.size(); i++)
  479 + result.append(valueMap[originalLabels[i].toString()]);
  480 +
  481 + return result;
  482 +}
  483 +
  484 +// uses -1 for missing values
  485 +QList<int> TemplateList::applyIndex(const QString & propName, const QHash<QString, int> & valueMap) const
  486 +{
  487 + const QList<QString> originalLabels = get<QString>(propName);
  488 +
  489 + QList<int> result;
  490 + for (int i=0; i<originalLabels.size(); i++) {
  491 + if (!valueMap.contains(originalLabels[i])) result.append(-1);
  492 + else result.append(valueMap[originalLabels[i]]);
  493 + }
  494 +
  495 + return result;
  496 +}
  497 +
486 498 /* Object - public methods */
487 499 QStringList Object::parameters() const
488 500 {
... ... @@ -1021,10 +1033,6 @@ MatrixOutput *MatrixOutput::make(const FileList &amp;targetFiles, const FileList &amp;qu
1021 1033 /* MatrixOutput - protected methods */
1022 1034 QString MatrixOutput::toString(int row, int column) const
1023 1035 {
1024   - if (targetFiles[column] == "Subject") {
1025   - const int label = data.at<float>(row,column);
1026   - return Globals->subjects.key(label, QString::number(label));
1027   - }
1028 1036 return QString::number(data.at<float>(row,column));
1029 1037 }
1030 1038  
... ...
openbr/openbr_plugin.h
... ... @@ -254,8 +254,6 @@ struct BR_EXPORT File
254 254 return variant.value<T>();
255 255 }
256 256  
257   - QString subject() const; /*!< \brief Looks up the subject from the file's label. */
258   - float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */
259 257 inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */
260 258  
261 259 QList<QPointF> namedPoints() const; /*!< \brief Returns points convertible from metadata keys. */
... ... @@ -299,7 +297,24 @@ struct BR_EXPORT FileList : public QList&lt;File&gt;
299 297 QStringList flat() const; /*!< \brief Returns br::File::flat() for each file in the list. */
300 298 QStringList names() const; /*!< \brief Returns #br::File::name for each file in the list. */
301 299 void sort(const QString& key); /*!< \brief Sort the list based on metadata. */
302   - QList<float> labels() const; /*!< \brief Returns br::File::label() for each file in the list. */
  300 + /*!< \brief Returns values associated with the input propName for each file in the list. */
  301 + template<typename T>
  302 + QList<T> get(const QString & propName) const
  303 + {
  304 + QList<T> values; values.reserve(size());
  305 + foreach (const File &f, *this)
  306 + values.append(f.get<T>(propName));
  307 + return values;
  308 + }
  309 + template<typename T>
  310 + QList<T> get(const QString & propName, T defaultValue) const
  311 + {
  312 + QList<T> values; values.reserve(size());
  313 + foreach (const File &f, *this)
  314 + values.append(f.contains(propName) ? f.get<T>(propName) : defaultValue);
  315 + return values;
  316 + }
  317 +
303 318 QList<int> crossValidationPartitions() const; /*!< \brief Returns the cross-validation partition (default=0) for each file in the list. */
304 319 int failures() const; /*!< \brief Returns the number of files with br::File::failed(). */
305 320 };
... ... @@ -383,7 +398,14 @@ struct TemplateList : public QList&lt;Template&gt;
383 398 TemplateList(const QList<Template> &templates) : uniform(false) { append(templates); } /*!< \brief Initialize the template list from another template list. */
384 399 TemplateList(const QList<File> &files) : uniform(false) { foreach (const File &file, files) append(file); } /*!< \brief Initialize the template list from a file list. */
385 400 BR_EXPORT static TemplateList fromGallery(const File &gallery); /*!< \brief Create a template list from a br::Gallery. */
386   - BR_EXPORT static TemplateList relabel(const TemplateList &tl); /*!< \brief Ensure labels are in the range [0,numClasses-1]. */
  401 +
  402 + /*!< \brief Ensure labels are in the range [0,numClasses-1]. */
  403 + BR_EXPORT static TemplateList relabel(const TemplateList & tl, const QString & propName);
  404 +
  405 + QList<int> indexProperty(const QString & propName, QHash<QString, int> * valueMap=NULL,QHash<int, QVariant> * reverseLookup = NULL) const;
  406 + QList<int> indexProperty(const QString & propName, QHash<QString, int> & valueMap, QHash<int, QVariant> & reverseLookup) const;
  407 + QList<int> applyIndex(const QString & propName, const QHash<QString, int> & valueMap) const;
  408 +
387 409  
388 410 /*!
389 411 * \brief Returns the total number of bytes in all the templates.
... ... @@ -457,23 +479,30 @@ struct TemplateList : public QList&lt;Template&gt;
457 479 /*!
458 480 * \brief Returns br::Template::label() for each template in the list.
459 481 */
460   - template <typename T>
461   - QList<T> labels() const
  482 + template<typename T>
  483 + QList<T> get(const QString & propName) const
  484 + {
  485 + QList<T> values; values.reserve(size());
  486 + foreach (const Template &t, *this) values.append(t.file.get<T>(propName));
  487 + return values;
  488 + }
  489 + QList<QVariant> values(const QString & propName) const
462 490 {
463   - QList<T> labels; labels.reserve(size());
464   - foreach (const Template &t, *this) labels.append(t.file.label());
465   - return labels;
  491 + QList<QVariant> values; values.reserve(size());
  492 + foreach (const Template &t, *this) values.append(t.file.value(propName));
  493 + return values;
466 494 }
467 495  
468 496 /*!
469 497 * \brief Returns the number of occurences for each label in the list.
470 498 */
471   - QMap<int,int> labelCounts(bool excludeFailures = false) const
  499 + template<typename T>
  500 + QMap<T,int> countValues(const QString & propName, bool excludeFailures = false) const
472 501 {
473   - QMap<int, int> labelCounts;
  502 + QMap<T, int> labelCounts;
474 503 foreach (const File &file, files())
475 504 if (!excludeFailures || !file.failed())
476   - labelCounts[file.label()]++;
  505 + labelCounts[file.get<T>(propName)]++;
477 506 return labelCounts;
478 507 }
479 508  
... ... @@ -649,7 +678,6 @@ public:
649 678 BR_PROPERTY(int, crossValidate, 0)
650 679  
651 680 QHash<QString,QString> abbreviations; /*!< \brief Used by br::Transform::make() to expand abbreviated algorithms into their complete definitions. */
652   - QHash<QString,int> subjects; /*!< \brief Used by classifiers to associate text class labels with unique integers IDs. */
653 681 QTime startTime; /*!< \brief Used to estimate timeRemaining(). */
654 682  
655 683 /*!
... ...
openbr/plugins/cluster.cpp
... ... @@ -111,13 +111,13 @@ class KNNTransform : public Transform
111 111 QHash<QString, float> votes;
112 112 const int max = (k < 1) ? sortedScores.size() : std::min(k, sortedScores.size());
113 113 for (int j=0; j<max; j++)
114   - votes[gallery[sortedScores[j].second].file.subject()] += (weighted ? sortedScores[j].first : 1);
  114 + votes[gallery[sortedScores[j].second].file.get<QString>("Subject")] += (weighted ? sortedScores[j].first : 1);
115 115 subjects.append(votes.keys()[votes.values().indexOf(Common::Max(votes.values()))]);
116 116  
117 117 // Remove subject from consideration
118 118 if (subjects.size() < numSubjects)
119 119 for (int j=sortedScores.size()-1; j>=0; j--)
120   - if (gallery[sortedScores[j].second].file.subject() == subjects.last())
  120 + if (gallery[sortedScores[j].second].file.get<QString>("Subject") == subjects.last())
121 121 sortedScores.removeAt(j);
122 122 }
123 123  
... ...
openbr/plugins/eigen3.cpp
... ... @@ -329,7 +329,8 @@ class LDATransform : public Transform
329 329  
330 330 void train(const TemplateList &_trainingSet)
331 331 {
332   - TemplateList trainingSet = TemplateList::relabel(_trainingSet);
  332 + // creates "Label"
  333 + TemplateList trainingSet = TemplateList::relabel(_trainingSet, "Subject");
333 334  
334 335 int instances = trainingSet.size();
335 336  
... ... @@ -342,13 +343,13 @@ class LDATransform : public Transform
342 343  
343 344 TemplateList ldaTrainingSet;
344 345 static_cast<Transform*>(&pca)->project(trainingSet, ldaTrainingSet);
345   - ldaTrainingSet = TemplateList::relabel(ldaTrainingSet);
346 346  
347 347 int dimsIn = ldaTrainingSet.first().m().rows * ldaTrainingSet.first().m().cols;
348 348  
349 349 // OpenBR ensures that class values range from 0 to numClasses-1.
350   - QList<int> classes = trainingSet.labels<int>();
351   - QMap<int, int> classCounts = trainingSet.labelCounts();
  350 + // Label exists because we created it earlier with relabel
  351 + QList<int> classes = trainingSet.get<int>("Label");
  352 + QMap<int, int> classCounts = trainingSet.countValues<int>("Label");
352 353 const int numClasses = classCounts.size();
353 354  
354 355 // Map Eigen into OpenCV
... ...
openbr/plugins/gallery.cpp
... ... @@ -65,13 +65,13 @@ class arffGallery : public Gallery
65 65 const int dimensions = t.m().rows * t.m().cols;
66 66 for (int i=0; i<dimensions; i++)
67 67 arffFile.write(qPrintable("@ATTRIBUTE v" + QString::number(i) + " REAL\n"));
68   - arffFile.write(qPrintable("@ATTRIBUTE class {" + QStringList(Globals->subjects.keys()).join(',') + "}\n"));
  68 + arffFile.write(qPrintable("@ATTRIBUTE class string\n"));
69 69  
70 70 arffFile.write("\n@DATA\n");
71 71 }
72 72  
73 73 arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(',')));
74   - arffFile.write(qPrintable(",'" + t.file.subject() + "'\n"));
  74 + arffFile.write(qPrintable(",'" + t.file.get<QString>("Subject") + "'\n"));
75 75 }
76 76 };
77 77  
... ... @@ -517,11 +517,7 @@ class csvGallery : public Gallery
517 517  
518 518 static QString getCSVElement(const QString &key, const QVariant &value, bool header)
519 519 {
520   - if ((key == "Label") && !header) {
521   - QString stringLabel = Globals->subjects.key(value.value<int>());
522   - if (stringLabel.isEmpty()) return value.value<QString>();
523   - else return stringLabel;
524   - } else if (value.canConvert<QString>()) {
  520 + if (value.canConvert<QString>()) {
525 521 if (header) return key;
526 522 else return value.value<QString>();
527 523 } else if (value.canConvert<QPointF>()) {
... ... @@ -878,7 +874,7 @@ class statGallery : public Gallery
878 874  
879 875 void write(const Template &t)
880 876 {
881   - subjects.insert(t.file.subject());
  877 + subjects.insert(t.file.get<QString>("Subject"));
882 878 bytes.append(t.bytes());
883 879 }
884 880 };
... ...
openbr/plugins/independent.cpp
... ... @@ -20,21 +20,23 @@ static TemplateList Downsample(const TemplateList &amp;templates, const Transform *t
20 20 const bool atLeast = transform->instances < 0;
21 21 const int instances = abs(transform->instances);
22 22  
23   - QList<int> allLabels = templates.labels<int>();
24   - QList<int> uniqueLabels = allLabels.toSet().toList();
  23 + QList<QString> allLabels = templates.get<QString>("Subject");
  24 + QList<QString> uniqueLabels = allLabels.toSet().toList();
25 25 qSort(uniqueLabels);
26 26  
27   - QMap<int,int> counts = templates.labelCounts(instances != std::numeric_limits<int>::max());
  27 + QMap<QString,int> counts = templates.countValues<QString>("Subject", instances != std::numeric_limits<int>::max());
  28 +
28 29 if ((instances != std::numeric_limits<int>::max()) && (transform->classes != std::numeric_limits<int>::max()))
29   - foreach (int label, counts.keys())
  30 + foreach (const QString & label, counts.keys())
30 31 if (counts[label] < instances)
31 32 counts.remove(label);
  33 +
32 34 uniqueLabels = counts.keys();
33 35 if ((transform->classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < transform->classes))
34 36 qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size());
35 37  
36 38 Common::seedRNG();
37   - QList<int> selectedLabels = uniqueLabels;
  39 + QList<QString> selectedLabels = uniqueLabels;
38 40 if (transform->classes < uniqueLabels.size()) {
39 41 std::random_shuffle(selectedLabels.begin(), selectedLabels.end());
40 42 selectedLabels = selectedLabels.mid(0, transform->classes);
... ... @@ -42,7 +44,7 @@ static TemplateList Downsample(const TemplateList &amp;templates, const Transform *t
42 44  
43 45 TemplateList downsample;
44 46 for (int i=0; i<selectedLabels.size(); i++) {
45   - const int selectedLabel = selectedLabels[i];
  47 + const QString selectedLabel = selectedLabels[i];
46 48 QList<int> indices;
47 49 for (int j=0; j<allLabels.size(); j++)
48 50 if ((allLabels[j] == selectedLabel) && (!templates.value(j).file.get<bool>("FTE", false)))
... ...
openbr/plugins/meta.cpp
... ... @@ -453,7 +453,6 @@ private:
453 453 const QString &file = src.file;
454 454 if (cache.contains(file)) {
455 455 dst = cache[file];
456   - dst.file.set("Label", src.file.label());
457 456 } else {
458 457 transform->project(src, dst);
459 458 cacheLock.lock();
... ...
openbr/plugins/normalize.cpp
... ... @@ -126,7 +126,8 @@ private:
126 126 {
127 127 Mat m;
128 128 OpenCVUtils::toMat(data.data()).convertTo(m, CV_64F);
129   - const QList<int> labels = data.labels<int>();
  129 +
  130 + const QList<int> labels = data.indexProperty("Subject");
130 131 const int dims = m.cols;
131 132  
132 133 vector<Mat> mv, av, bv;
... ...
openbr/plugins/output.cpp
... ... @@ -145,8 +145,10 @@ class meltOutput : public MatrixOutput
145 145  
146 146 QStringList lines;
147 147 if (file.baseName() != "terminal") lines.append(QString("Query,Target,Mask,Similarity%1").arg(keys));
148   - QList<float> queryLabels = queryFiles.labels();
149   - QList<float> targetLabels = targetFiles.labels();
  148 +
  149 + QList<QString> queryLabels = queryFiles.get<QString>("Subject");
  150 + QList<QString> targetLabels = targetFiles.get<QString>("Subject");
  151 +
150 152 for (int i=0; i<queryFiles.size(); i++) {
151 153 for (int j=(selfSimilar ? i+1 : 0); j<targetFiles.size(); j++) {
152 154 const bool genuine = queryLabels[i] == targetLabels[j];
... ... @@ -296,7 +298,7 @@ class txtOutput : public MatrixOutput
296 298 if (file.isNull() || targetFiles.isEmpty() || queryFiles.isEmpty()) return;
297 299 QStringList lines;
298 300 foreach (const File &file, queryFiles)
299   - lines.append(file.name + " " + file.subject());
  301 + lines.append(file.name + " " + file.get<QString>("Subject"));
300 302 QtUtils::writeFile(file, lines);
301 303 }
302 304 };
... ...
openbr/plugins/pixel.cpp deleted
1   -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2   - * Copyright 2012 The MITRE Corporation *
3   - * *
4   - * Licensed under the Apache License, Version 2.0 (the "License"); *
5   - * you may not use this file except in compliance with the License. *
6   - * You may obtain a copy of the License at *
7   - * *
8   - * http://www.apache.org/licenses/LICENSE-2.0 *
9   - * *
10   - * Unless required by applicable law or agreed to in writing, software *
11   - * distributed under the License is distributed on an "AS IS" BASIS, *
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13   - * See the License for the specific language governing permissions and *
14   - * limitations under the License. *
15   - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16   -
17   -#include "openbr_internal.h"
18   -
19   -using namespace cv;
20   -
21   -namespace br
22   -{
23   -
24   -/*!
25   - * \ingroup transforms
26   - * \brief Treat each pixel as a classification task
27   - * \author E. Taborsky \cite mmtaborsky
28   - */
29   -class PerPixelClassifierTransform : public MetaTransform
30   -{
31   - Q_OBJECT
32   - Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform)
33   - Q_PROPERTY(int pixels READ get_pixels WRITE set_pixels RESET reset_pixels STORED false)
34   - Q_PROPERTY(int orient READ get_orient WRITE set_orient RESET reset_orient STORED false)
35   - BR_PROPERTY(br::Transform*, transform, NULL)
36   - BR_PROPERTY(int, pixels, 10000)
37   - BR_PROPERTY(bool, orient, false)
38   -
39   - /*
40   - Bins:
41   - |4|3|2|
42   - |5| |1|
43   - |6|7|8|
44   - */
45   -
46   - QList<float> shift(int n, QList<float> &src) const
47   - {
48   - for (int i = 0; i < n; i++){ // Equivalent to src.append(src.takeFirst()) ?
49   - src.append(src.at(i));
50   - src.removeFirst();
51   - }
52   - return src;
53   - }
54   -
55   - void rotate(Template &src, Template &dst) const
56   - {
57   - int images = (src.m().cols)/9;
58   - dst = src;
59   - for (int i = 0; i < images; i++){
60   - double a = src.m().at<float>(7+(i*9)); //top
61   - double b = src.m().at<float>(1+(i*9)); //bottom
62   - double c = src.m().at<float>(5+(i*9)); //right
63   - double d = src.m().at<float>(3+(i*9)); //left
64   - double orientation = atan2((a-b),(c-d));
65   - int bin;
66   - if (orientation > 0){
67   - bin = ((orientation/CV_PI)*4.0 +.5);
68   - } else {
69   - bin = 8.0 + ((orientation/CV_PI)*4.0 + .5);
70   - }
71   -
72   - // put things in an order that makes sense to rotate
73   - // blugh
74   - QList<float> orderedList;
75   - QList<float> rotatedList;
76   - orderedList.insert(0, src.m().at<float>(3+(i*9)));
77   - orderedList.insert(1, src.m().at<float>(6+(i*9)));
78   - orderedList.insert(2, src.m().at<float>(7+(i*9)));
79   - orderedList.insert(3, src.m().at<float>(8+(i*9)));
80   - orderedList.insert(4, src.m().at<float>(5+(i*9)));
81   - orderedList.insert(5, src.m().at<float>(2+(i*9)));
82   - orderedList.insert(6, src.m().at<float>(1+(i*9)));
83   - orderedList.insert(7, src.m().at<float>(0+(i*9)));
84   -
85   - rotatedList = shift(bin, orderedList);
86   -
87   - dst.m().at<float>(0+(i*9)) = rotatedList.at(7);
88   - dst.m().at<float>(1+(i*9)) = rotatedList.at(6);
89   - dst.m().at<float>(2+(i*9)) = rotatedList.at(5);
90   - dst.m().at<float>(3+(i*9)) = rotatedList.at(0);
91   - dst.m().at<float>(4+(i*9)) = src.m().at<float>(4+(i*9)); // middle pixel not in orderedList
92   - dst.m().at<float>(5+(i*9)) = rotatedList.at(4);
93   - dst.m().at<float>(6+(i*9)) = rotatedList.at(1);
94   - dst.m().at<float>(7+(i*9)) = rotatedList.at(2);
95   - dst.m().at<float>(8+(i*9)) = rotatedList.at(3);
96   - }
97   - }
98   -
99   - void train(const TemplateList &trainingSet)
100   - {
101   - TemplateList pixelTemplates = TemplateList();
102   - const int length = trainingSet.length();
103   - int pixelsPerImage = pixels/length;
104   -
105   - for (int i=0; i < length; i++){
106   - Template src = trainingSet.at(i);
107   -
108   - const int mats = src.length();
109   - const int rows = src.m().rows;
110   - const int cols = src.m().cols;
111   -
112   - RNG &rng = theRNG();
113   - TemplateList srcPixelTemplates;
114   -
115   - for (int m=0; m < pixelsPerImage; m++){
116   - int index = rng.uniform(0, rows*cols);
117   - Template temp = Template(src.file, cv::Mat(1, mats, CV_32F));
118   - float *ptemp = (float*)temp.m().ptr();
119   - for (int n=0; n < mats; n++){
120   - uchar *psrc = src[n].ptr();
121   - ptemp[n] = psrc[index];
122   - }
123   - cv::Mat labelMat = src.file.value("labels").value<cv::Mat>();
124   - uchar* plabel = labelMat.ptr();
125   - temp.file.set("Label", plabel[index]);
126   -
127   - if (orient){
128   - Template rotated;
129   - rotate(temp, rotated);
130   - srcPixelTemplates.append(rotated);
131   - } else {
132   - srcPixelTemplates.append(temp);
133   - }
134   - }
135   - pixelTemplates.append(srcPixelTemplates);
136   - }
137   - transform->train(pixelTemplates);
138   - }
139   -
140   - void project(const Template &src, Template &dst) const
141   - {
142   - const int mats = src.length();
143   - const int rows = src.m().rows;
144   - const int cols = src.m().cols;
145   -
146   - dst = src; // Do we really want to copy all the src matrices into dst?
147   - dst.merge(Template(src.file, cv::Mat(src.m().rows, src.m().cols, CV_32F)));
148   - float *pdst = (float*) dst.m().ptr();
149   -
150   - for (int r = 0; r < rows; r++){
151   - for (int c = 0; c < cols; c++){
152   - Template temp = Template(src.file, cv::Mat(1, mats, CV_32F));
153   - Template dstTemp = Template(src.file, cv::Mat(1, mats, CV_32F));
154   -
155   - for (int n=0; n < mats; n++){
156   - const uchar *psrc = src[n].ptr();
157   - float *ptemp = (float*)temp[0].ptr();
158   - int index = r*cols + c;
159   - ptemp[n] = psrc[index];
160   - }
161   -
162   - if (orient){
163   - Template rotated = Template(src.file, cv::Mat(1, mats, CV_32F));
164   - rotate(temp, rotated);
165   - temp = rotated;
166   - }
167   -
168   - transform->project(temp,dstTemp);
169   - pdst[r*cols+c] = dstTemp.file.label();
170   - }
171   - }
172   - }
173   -};
174   -
175   -BR_REGISTER(Transform, PerPixelClassifierTransform)
176   -
177   -/*!
178   - * \ingroup transforms
179   - * \brief Construct feature vectors of neighboring pixels
180   - * \author E. Taborsky \cite mmtaborsky
181   - */
182   -class NeighborsTransform : public UntrainableMetaTransform
183   -{
184   - Q_OBJECT
185   -
186   - void project(const Template &src, Template &dst) const
187   - {
188   - int rows = src.m().rows;
189   - int cols = src.m().cols;
190   - int mats = src.length();
191   - dst.file = src.file;
192   -
193   - for (int n = 0; n < mats; n++){ //each matrix, except the last one, will be turned into 9 matrices
194   - const uchar *psrc = src[n].ptr();
195   - for (int i = -1; i < 2; i++){
196   - for (int j = -1; j < 2; j++){ // these nine matrices are shifted versions of the original
197   - cv::Mat mat = cv::Mat(rows, cols, CV_8UC1);
198   - uchar *pmat = (uchar*)mat.ptr();
199   - for (int r = 0; r < rows; r++){
200   - for (int c = 0; c < cols; c++){
201   - int index = r*cols+c;
202   - int newIndex = index + i*cols + j;
203   - if ((newIndex < 0) || (newIndex >= rows*cols)){
204   - pmat[index] = psrc[index];
205   - } else {
206   - pmat[index] = psrc[newIndex];
207   - }
208   - }
209   - }
210   - dst.push_back(mat); //add mat to dst
211   - }
212   - }
213   - }
214   - dst.push_back(src.m()); // add the last matrix
215   - }
216   -};
217   -
218   -BR_REGISTER(Transform, NeighborsTransform)
219   -
220   -/*!
221   - * \ingroup transforms
222   - * \brief To binary vector
223   - * \author E. Taborsky \cite mmtaborsky
224   - */
225   -class ToBinaryVectorTransform : public UntrainableMetaTransform
226   -{
227   - Q_OBJECT
228   - Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED false)
229   - Q_PROPERTY(int length READ get_length WRITE set_length RESET reset_length STORED false)
230   - BR_PROPERTY(br::Transform*, transform, NULL)
231   - BR_PROPERTY(int, length, -1)
232   -
233   - //needs to be updated..
234   - void project(const Template &src, Template &dst) const
235   - {
236   -
237   - dst = src;
238   - int mats = src.length();
239   - for (int i = 0; i < mats; i++){
240   - // Does this actually modify the data?
241   - dst[i]*(1.0/255.0); //scaling the input matrices to make the svm happier
242   - }
243   - for (int i = 0; i < length*(mats); i++){
244   - dst.prepend(Template(src.file, Mat::zeros(src.m().rows, src.m().cols, CV_8U)));
245   - }
246   -
247   - // original pixel values at the end
248   -
249   - Template transformed;
250   - transformed.file = src.file;
251   - transform->project(src, transformed);
252   -
253   - int rows = transformed.m().rows;
254   - int cols = transformed.m().cols;
255   -
256   - for (int i = 0; i < mats; i++){
257   - uchar *ptransformed = transformed[i].ptr();
258   - for (int r = 0; r < rows; r++){
259   - for (int c = 0; c < cols; c++){
260   - uchar index = ptransformed[r*cols+c];
261   - dst[index+(length*i)].at<uchar>(r,c) = 1;
262   - }
263   - }
264   - }
265   - }
266   -};
267   -
268   -BR_REGISTER(Transform, ToBinaryVectorTransform)
269   -
270   -/*!
271   - * \ingroup transforms
272   - * \brief If "labels" is specified, makes the last matrix into metadata
273   - * \author E. Taborsky \cite mmtaborsky
274   - */
275   -
276   -class ToMetadataTransform : public UntrainableMetaTransform
277   -{
278   - Q_OBJECT
279   -
280   - void project(const Template &src, Template &dst) const
281   - {
282   - dst = src;
283   - if (dst.file.contains("labels")){
284   - QVariant last = qVariantFromValue(dst.m());
285   - dst.file.set("labels", last);
286   - dst.pop_back();
287   - }
288   - }
289   -
290   -};
291   -
292   -BR_REGISTER(Transform, ToMetadataTransform)
293   -
294   -} // namespace br
295   -
296   -#include "pixel.moc"
openbr/plugins/quality.cpp
... ... @@ -26,10 +26,10 @@ class ImpostorUniquenessMeasureTransform : public Transform
26 26  
27 27 float calculateIUM(const Template &probe, const TemplateList &gallery) const
28 28 {
29   - const int probeLabel = probe.file.label();
  29 + const QString probeLabel = probe.file.get<QString>("Subject");
30 30 TemplateList subset = gallery;
31 31 for (int j=subset.size()-1; j>=0; j--)
32   - if (subset[j].file.label() == probeLabel)
  32 + if (subset[j].file.get<QString>("Subject") == probeLabel)
33 33 subset.removeAt(j);
34 34  
35 35 QList<float> scores = distance->compare(subset, probe);
... ... @@ -158,8 +158,7 @@ class MatchProbabilityDistance : public Distance
158 158 {
159 159 distance->train(src);
160 160  
161   - const QList<int> labels = src.labels<int>();
162   -
  161 + const QList<int> labels = src.indexProperty("Subject");
163 162 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size())));
164 163 distance->compare(src, src, matrixOutput.data());
165 164  
... ... @@ -229,7 +228,7 @@ class HeatMapDistance : public Distance
229 228 {
230 229 distance->train(src);
231 230  
232   - const QList<int> labels = src.labels<int>();
  231 + const QList<int> labels = src.indexProperty("Subject");
233 232  
234 233 QList<TemplateList> patches;
235 234  
... ... @@ -317,7 +316,7 @@ class UnitDistance : public Distance
317 316 void train(const TemplateList &templates)
318 317 {
319 318 const TemplateList samples = templates.mid(0, 2000);
320   - const QList<float> sampleLabels = samples.labels<float>();
  319 + const QList<int> sampleLabels = samples.indexProperty("Subject");
321 320 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(samples.size()), FileList(samples.size())));
322 321 Distance::compare(samples, samples, matrixOutput.data());
323 322  
... ...
openbr/plugins/quantize.cpp
... ... @@ -150,7 +150,7 @@ class BayesianQuantizationDistance : public Distance
150 150 qFatal("Expected sigle matrix templates of type CV_8UC1!");
151 151  
152 152 const Mat data = OpenCVUtils::toMat(src.data());
153   - const QList<int> templateLabels = src.labels<int>();
  153 + const QList<int> templateLabels = src.indexProperty("Subject");
154 154 loglikelihoods = QVector<float>(data.cols*256, 0);
155 155  
156 156 QFutureSynchronizer<void> futures;
... ... @@ -473,7 +473,8 @@ private:
473 473 {
474 474 Mat data = OpenCVUtils::toMat(src.data());
475 475 const int step = getStep(data.cols);
476   - const QList<int> labels = src.labels<int>();
  476 +
  477 + const QList<int> labels = src.indexProperty("Subject");
477 478  
478 479 Mat &lut = ProductQuantizationLUTs[index];
479 480 lut = Mat(getDims(data.cols), 256*(256+1)/2, CV_32FC1);
... ...
openbr/plugins/quantize2.cpp
... ... @@ -77,7 +77,7 @@ class BayesianQuantizationTransform : public Transform
77 77 void train(const TemplateList &src)
78 78 {
79 79 const Mat data = OpenCVUtils::toMat(src.data());
80   - const QList<int> labels = src.labels<int>();
  80 + const QList<int> labels = src.indexProperty("Subject");
81 81  
82 82 thresholds = QVector<float>(256*data.cols);
83 83  
... ...
openbr/plugins/svm.cpp
... ... @@ -121,28 +121,46 @@ private:
121 121 BR_PROPERTY(float, gamma, -1)
122 122  
123 123 SVM svm;
  124 + QHash<QString, int> labelMap;
  125 + QHash<int, QVariant> reverseLookup;
124 126  
125 127 void train(const TemplateList &_data)
126 128 {
127 129 Mat data = OpenCVUtils::toMat(_data.data());
128   - Mat lab = OpenCVUtils::toMat(_data.labels<float>());
  130 + Mat lab;
  131 + // If we are doing regression, assume subject has float values
  132 + if (type == EPS_SVR || type == NU_SVR) {
  133 + lab = OpenCVUtils::toMat(_data.get<float>("Subject"));
  134 + }
  135 + // If we are doing classification, assume subject has discrete values, map them
  136 + // and store the mapping data
  137 + else {
  138 + QList<int> dataLabels = _data.indexProperty("Subject", labelMap, reverseLookup);
  139 + lab = OpenCVUtils::toMat(dataLabels);
  140 + }
129 141 trainSVM(svm, data, lab, kernel, type, C, gamma);
130 142 }
131 143  
132 144 void project(const Template &src, Template &dst) const
133 145 {
134 146 dst = src;
135   - dst.file.set("Label", svm.predict(src.m().reshape(1, 1)));
  147 + float prediction = svm.predict(src.m().reshape(1, 1));
  148 + if (type == EPS_SVR || type == NU_SVR)
  149 + dst.file.set("Subject", prediction);
  150 + else
  151 + dst.file.set("Subject", reverseLookup[prediction]);
136 152 }
137 153  
138 154 void store(QDataStream &stream) const
139 155 {
140 156 storeSVM(svm, stream);
  157 + stream << labelMap << reverseLookup;
141 158 }
142 159  
143 160 void load(QDataStream &stream)
144 161 {
145 162 loadSVM(svm, stream);
  163 + stream >> labelMap >> reverseLookup;
146 164 }
147 165 };
148 166  
... ... @@ -182,7 +200,7 @@ private:
182 200 void train(const TemplateList &src)
183 201 {
184 202 const Mat data = OpenCVUtils::toMat(src.data());
185   - const QList<int> lab = src.labels<int>();
  203 + const QList<int> lab = src.indexProperty("Subject");
186 204  
187 205 const int instances = data.rows * (data.rows+1) / 2;
188 206 Mat deltaData(instances, data.cols, data.type());
... ...
1   -Subproject commit cddd8fc2231191ab5d62dc29573f73e97d5065c5
  1 +Subproject commit dccddf4dd3a5239911807beeec39308f8890b1e4
... ...