Commit 3e6d1cff5d31d5549def0bd27da140540cdb1542
1 parent
70a3f508
add gtGallery, knnOutput and evalKNN
Showing
7 changed files
with
254 additions
and
0 deletions
app/br/br.cpp
| ... | ... | @@ -168,6 +168,9 @@ public: |
| 168 | 168 | } else if (!strcmp(fun, "evalRegression")) { |
| 169 | 169 | check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); |
| 170 | 170 | br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); |
| 171 | + } else if (!strcmp(fun, "evalKNN")) { | |
| 172 | + check(parc >=2 && parc < 4, "Incorrect parameter count for 'evalKNN'."); | |
| 173 | + br_eval_knn(parv[0], parv[1], parc >= 3 ? parv[2] : ""); | |
| 171 | 174 | } else if (!strcmp(fun, "pairwiseCompare")) { |
| 172 | 175 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'pairwiseCompare'."); |
| 173 | 176 | br_pairwise_compare(parv[0], parv[1], parc == 3 ? parv[2] : ""); |
| ... | ... | @@ -276,6 +279,7 @@ private: |
| 276 | 279 | "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}] [{maxSize}]\n" |
| 277 | 280 | "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv} [<normalization_index_a> <normalization_index_b>] [sample_index] [total_examples]]\n" |
| 278 | 281 | "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n" |
| 282 | + "-evalKNN <knn_graph> <knn_truth> [{iet_file}]\n" | |
| 279 | 283 | "-pairwiseCompare <target_gallery> <query_gallery> [{output}]\n" |
| 280 | 284 | "-inplaceEval <simmat> <target> <query> [{csv}]\n" |
| 281 | 285 | "-assertEval <simmat> <mask> <accuracy>\n" | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -1300,4 +1300,132 @@ void EvalRegression(const QString &predictedGallery, const QString &truthGallery |
| 1300 | 1300 | qDebug("MAE = %f", maeError/predicted.size()); |
| 1301 | 1301 | } |
| 1302 | 1302 | |
| 1303 | +void readKNN(size_t &templateCount, size_t &k, QVector<Candidate> &neighbors, const QString &fileName) | |
| 1304 | +{ | |
| 1305 | + QFile file(fileName); | |
| 1306 | + if (!file.open(QFile::ReadOnly)) | |
| 1307 | + qFatal("Failed to open k-NN file for reading!"); | |
| 1308 | + file.read((char*) &templateCount, sizeof(size_t)); | |
| 1309 | + file.read((char*) &k, sizeof(size_t)); | |
| 1310 | + neighbors.resize(templateCount * k); | |
| 1311 | + | |
| 1312 | + file.read((char*) neighbors.data(), templateCount * k * sizeof(Candidate)); | |
| 1313 | +} | |
| 1314 | + | |
| 1315 | +void readKNNTruth(size_t templateCount, QVector< QList<size_t> > &groundTruth, const QString &fileName) | |
| 1316 | +{ | |
| 1317 | + groundTruth.reserve(templateCount); | |
| 1318 | + QFile truthFile(fileName); | |
| 1319 | + if (!truthFile.open(QFile::ReadOnly | QFile::Text)) | |
| 1320 | + qFatal("Failed to open k-NN ground truth file for reading!"); | |
| 1321 | + size_t i=0; | |
| 1322 | + while (!truthFile.atEnd()) { | |
| 1323 | + const QString line = truthFile.readLine().trimmed(); | |
| 1324 | + if (!line.isEmpty()) | |
| 1325 | + foreach (const QString &index, line.split('\t')) { | |
| 1326 | + bool ok; | |
| 1327 | + groundTruth[i].append(index.toLong(&ok)); | |
| 1328 | + if (!ok) | |
| 1329 | + qFatal("Failed to parse long in k-NN ground truth!"); | |
| 1330 | + } | |
| 1331 | + i++; | |
| 1332 | + } | |
| 1333 | + if (i != templateCount) | |
| 1334 | + qFatal("Invalid ground truth file!"); | |
| 1335 | +} | |
| 1336 | + | |
| 1337 | +void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &iet) | |
| 1338 | +{ | |
| 1339 | + qDebug("Evaluating k-NN of %s against %s", qPrintable(knnGraph), qPrintable(knnTruth)); | |
| 1340 | + | |
| 1341 | + size_t templateCount; | |
| 1342 | + size_t k; | |
| 1343 | + QVector<Candidate> neighbors; | |
| 1344 | + readKNN(templateCount, k, neighbors, knnGraph); | |
| 1345 | + | |
| 1346 | + /* | |
| 1347 | + * Read the ground truth from disk. | |
| 1348 | + * Line i contains the template indicies of the mates for probe i. | |
| 1349 | + * See the `gtGallery` implementation for details. | |
| 1350 | + */ | |
| 1351 | + QVector< QList<size_t> > truth(templateCount); | |
| 1352 | + readKNNTruth(templateCount, truth, knnTruth); | |
| 1353 | + | |
| 1354 | + /* | |
| 1355 | + * For each probe, record the similarity of the highest mate (if one exists) and the highest non-mate. | |
| 1356 | + */ | |
| 1357 | + QList<float> matedSimilarities, unmatedSimilarities; | |
| 1358 | + size_t numMatedSearches = 0; | |
| 1359 | + for (size_t i=0; i<templateCount; i++) { | |
| 1360 | + const QList<size_t> &mates = truth[i]; | |
| 1361 | + if (!mates.empty()) | |
| 1362 | + numMatedSearches++; | |
| 1363 | + | |
| 1364 | + bool recordedHighestMatedSimilarity = false; | |
| 1365 | + bool recordedHighestUnmatedSimilarity = false; | |
| 1366 | + for (size_t j=0; j<k; j++) { | |
| 1367 | + const Candidate &neighbor = neighbors[i*k+j]; | |
| 1368 | + | |
| 1369 | + if (mates.contains(neighbor.index)) { | |
| 1370 | + // Found a mate | |
| 1371 | + if (!recordedHighestMatedSimilarity) { | |
| 1372 | + matedSimilarities.append(neighbor.similarity); | |
| 1373 | + recordedHighestMatedSimilarity = true; | |
| 1374 | + } | |
| 1375 | + } else { | |
| 1376 | + // Found a non-mate | |
| 1377 | + if (!recordedHighestUnmatedSimilarity) { | |
| 1378 | + unmatedSimilarities.append(neighbor.similarity); | |
| 1379 | + recordedHighestUnmatedSimilarity = true; | |
| 1380 | + } | |
| 1381 | + } | |
| 1382 | + | |
| 1383 | + if (recordedHighestMatedSimilarity && recordedHighestUnmatedSimilarity) | |
| 1384 | + break; // we can stop scanning the candidate list for this probe | |
| 1385 | + } | |
| 1386 | + } | |
| 1387 | + | |
| 1388 | + // Sort the similarity scores lowest-to-highest | |
| 1389 | + std::sort(matedSimilarities.begin(), matedSimilarities.end()); | |
| 1390 | + std::sort(unmatedSimilarities.begin(), unmatedSimilarities.end()); | |
| 1391 | + const size_t numMatedSimilarities = matedSimilarities.size(); | |
| 1392 | + const size_t numUnmatedSimilarities = unmatedSimilarities.size(); | |
| 1393 | + | |
| 1394 | + if (numMatedSimilarities == 0) | |
| 1395 | + qFatal("No mated searches!"); | |
| 1396 | + | |
| 1397 | + if (numUnmatedSimilarities == 0) | |
| 1398 | + qFatal("No unmated searches!"); | |
| 1399 | + | |
| 1400 | + printf("Rank-%zu Return Rate: %g\n", k, double(numMatedSimilarities) / double(numMatedSearches)); | |
| 1401 | + | |
| 1402 | + // Open the output file | |
| 1403 | + QFile ietFile(iet); | |
| 1404 | + if (!ietFile.open(QFile::WriteOnly | QFile::Text)) | |
| 1405 | + qFatal("Failed to open IET file for writing!"); | |
| 1406 | + ietFile.write("Threshold,FPIR,FNIR\n"); | |
| 1407 | + | |
| 1408 | + /* | |
| 1409 | + * Iterate through the similarity scores highest-to-lowest, | |
| 1410 | + * for each threshold count the number mated and unmated searches, | |
| 1411 | + * record the corresponding FPIR and FNIR values for the threshold. | |
| 1412 | + */ | |
| 1413 | + size_t matedCount = 0; | |
| 1414 | + size_t unmatedCount = 0; | |
| 1415 | + while (!matedSimilarities.empty()) { | |
| 1416 | + const float threshold = matedSimilarities.back(); | |
| 1417 | + while (!matedSimilarities.empty() && (matedSimilarities.back() >= threshold)) { | |
| 1418 | + matedSimilarities.removeLast(); | |
| 1419 | + matedCount++; | |
| 1420 | + } | |
| 1421 | + while (!unmatedSimilarities.empty() && (unmatedSimilarities.back() >= threshold)) { | |
| 1422 | + unmatedSimilarities.removeLast(); | |
| 1423 | + unmatedCount++; | |
| 1424 | + } | |
| 1425 | + ietFile.write(qPrintable(QString::number(threshold) + "," + | |
| 1426 | + QString::number(double(unmatedCount) / double(numUnmatedSimilarities)) + "," + | |
| 1427 | + QString::number(1.0 - double(matedCount) / double(numMatedSimilarities)) + "\n")); | |
| 1428 | + } | |
| 1429 | +} | |
| 1430 | + | |
| 1303 | 1431 | } // namespace br | ... | ... |
openbr/core/eval.h
| ... | ... | @@ -33,6 +33,17 @@ namespace br |
| 33 | 33 | float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0); // Return average overlap |
| 34 | 34 | float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1, int sampleIndex = 0, int totalExamples = 5); // Return average error |
| 35 | 35 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 36 | + void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &iet = ""); | |
| 37 | + | |
| 38 | + struct Candidate | |
| 39 | + { | |
| 40 | + size_t index; | |
| 41 | + float similarity; | |
| 42 | + | |
| 43 | + Candidate() {} | |
| 44 | + Candidate(size_t index_, float similarity_) | |
| 45 | + : index(index_), similarity(similarity_) {} | |
| 46 | + }; | |
| 36 | 47 | } |
| 37 | 48 | |
| 38 | 49 | #endif // BR_EVAL_H | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -145,6 +145,11 @@ void br_eval_regression(const char *predicted_gallery, const char *truth_gallery |
| 145 | 145 | EvalRegression(predicted_gallery, truth_gallery, predicted_property, truth_property); |
| 146 | 146 | } |
| 147 | 147 | |
| 148 | +void br_eval_knn(const char *knnGraph, const char *knnTruth, const char *iet) | |
| 149 | +{ | |
| 150 | + EvalKNN(knnGraph, knnTruth, iet); | |
| 151 | +} | |
| 152 | + | |
| 148 | 153 | void br_finalize() |
| 149 | 154 | { |
| 150 | 155 | Context::finalize(); | ... | ... |
openbr/openbr.h
| ... | ... | @@ -64,6 +64,8 @@ BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *t |
| 64 | 64 | |
| 65 | 65 | BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property = "", const char *truth_property = ""); |
| 66 | 66 | |
| 67 | +BR_EXPORT void br_eval_knn(const char *knnGraph, const char *knnTruth, const char *iet = ""); | |
| 68 | + | |
| 67 | 69 | BR_EXPORT void br_finalize(); |
| 68 | 70 | |
| 69 | 71 | BR_EXPORT void br_fuse(int num_input_simmats, const char *input_simmats[], | ... | ... |
openbr/plugins/gallery/gt.cpp
0 → 100644
| 1 | +#include "openbr/plugins/openbr_internal.h" | |
| 2 | +#include "openbr/core/qtutils.h" | |
| 3 | + | |
| 4 | +using namespace br; | |
| 5 | + | |
| 6 | +/*! | |
| 7 | + * \ingroup galleries | |
| 8 | + * \brief Ground truth format for evaluating kNN | |
| 9 | + * \author Josh Klontz \cite jklontz | |
| 10 | + */ | |
| 11 | +class gtGallery : public Gallery | |
| 12 | +{ | |
| 13 | + Q_OBJECT | |
| 14 | + | |
| 15 | + TemplateList templates; | |
| 16 | + | |
| 17 | + ~gtGallery() | |
| 18 | + { | |
| 19 | + const QList<int> labels = File::get<int>(TemplateList::relabel(templates, "Label", false), "Label"); | |
| 20 | + | |
| 21 | + QStringList lines; | |
| 22 | + for (int i=0; i<labels.size(); i++) { | |
| 23 | + QStringList words; | |
| 24 | + for (int j=0; j<labels.size(); j++) { | |
| 25 | + if ((labels[i] == labels[j]) && (i != j)) | |
| 26 | + words.append(QString::number(j)); | |
| 27 | + } | |
| 28 | + lines.append(words.join("\t")); | |
| 29 | + } | |
| 30 | + | |
| 31 | + QtUtils::writeFile(file.name, lines); | |
| 32 | + } | |
| 33 | + | |
| 34 | + TemplateList readBlock(bool *done) | |
| 35 | + { | |
| 36 | + *done = true; | |
| 37 | + qFatal("Not supported!"); | |
| 38 | + return TemplateList(); | |
| 39 | + } | |
| 40 | + | |
| 41 | + void write(const Template &t) | |
| 42 | + { | |
| 43 | + templates.append(t); | |
| 44 | + } | |
| 45 | +}; | |
| 46 | + | |
| 47 | +BR_REGISTER(Gallery, gtGallery) | |
| 48 | + | |
| 49 | +#include "gt.moc" | ... | ... |
openbr/plugins/output/knn.cpp
0 → 100644
| 1 | +#include <openbr/plugins/openbr_internal.h> | |
| 2 | +#include <openbr/core/common.h> | |
| 3 | +#include <openbr/core/opencvutils.h> | |
| 4 | +#include <openbr/core/eval.h> | |
| 5 | + | |
| 6 | +namespace br | |
| 7 | +{ | |
| 8 | + | |
| 9 | +/*! | |
| 10 | + * \ingroup outputs | |
| 11 | + * \brief Outputs the k-Nearest Neighbors from the gallery for each probe. | |
| 12 | + * \author Ben Klein \cite bhklein | |
| 13 | + */ | |
| 14 | +class knnOutput : public MatrixOutput | |
| 15 | +{ | |
| 16 | + Q_OBJECT | |
| 17 | + | |
| 18 | + ~knnOutput() | |
| 19 | + { | |
| 20 | + size_t num_probes = (size_t)queryFiles.size(); | |
| 21 | + if (targetFiles.isEmpty() || queryFiles.isEmpty()) return; | |
| 22 | + size_t k = file.get<size_t>("k", 20); | |
| 23 | + | |
| 24 | + if ((size_t)targetFiles.size() < k) | |
| 25 | + qFatal("Gallery size %s is smaller than k = %s.", qPrintable(QString::number(targetFiles.size())), qPrintable(QString::number(k))); | |
| 26 | + | |
| 27 | + QFile f(file); | |
| 28 | + if (!f.open(QFile::WriteOnly)) | |
| 29 | + qFatal("Unable to open %s for writing.", qPrintable(file)); | |
| 30 | + f.write((const char*) &num_probes, sizeof(size_t)); | |
| 31 | + f.write((const char*) &k, sizeof(size_t)); | |
| 32 | + | |
| 33 | + QVector<Candidate> neighbors; neighbors.reserve(num_probes*k); | |
| 34 | + | |
| 35 | + for (size_t i=0; i<num_probes; i++) { | |
| 36 | + typedef QPair<float,int> Pair; | |
| 37 | + size_t rank = 0; | |
| 38 | + foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { | |
| 39 | + if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) { | |
| 40 | + Candidate candidate((size_t)pair.second, pair.first); | |
| 41 | + neighbors.push_back(candidate); | |
| 42 | + if (++rank >= k) break; | |
| 43 | + } | |
| 44 | + } | |
| 45 | + } | |
| 46 | + f.write((const char*) neighbors.data(), num_probes * k * sizeof(Candidate)); | |
| 47 | + f.close(); | |
| 48 | + } | |
| 49 | +}; | |
| 50 | + | |
| 51 | +BR_REGISTER(Output, knnOutput) | |
| 52 | + | |
| 53 | +} // namespace br | |
| 54 | + | |
| 55 | +#include "output/knn.moc" | ... | ... |