Commit d6ae28496be88d981d27f70a2a326344a61c05e9
1 parent
31008334
Added output of sample landmarks, added index to control which image is used as sample
Showing
6 changed files
with
114 additions
and
12 deletions
app/br/br.cpp
| ... | ... | @@ -163,8 +163,8 @@ public: |
| 163 | 163 | check((parc >= 2) && (parc <= 6), "Incorrect parameter count for 'evalDetection'."); |
| 164 | 164 | br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 0, parc == 6 ? atoi(parv[5]) : 0); |
| 165 | 165 | } else if (!strcmp(fun, "evalLandmarking")) { |
| 166 | - check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalLandmarking'."); | |
| 167 | - br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1); | |
| 166 | + check((parc >= 2) && (parc <= 6), "Incorrect parameter count for 'evalLandmarking'."); | |
| 167 | + br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1, parc >= 6 ? atoi(parv[5]) : 0); | |
| 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] : ""); | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -1032,7 +1032,7 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery |
| 1032 | 1032 | return averageOverlap; |
| 1033 | 1033 | } |
| 1034 | 1034 | |
| 1035 | -float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv, int normalizationIndexA, int normalizationIndexB) | |
| 1035 | +float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv, int normalizationIndexA, int normalizationIndexB, int sampleIndex) | |
| 1036 | 1036 | { |
| 1037 | 1037 | qDebug("Evaluating landmarking of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); |
| 1038 | 1038 | const TemplateList predicted(TemplateList::fromGallery(predictedGallery)); |
| ... | ... | @@ -1042,6 +1042,8 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 1042 | 1042 | |
| 1043 | 1043 | int skipped = 0; |
| 1044 | 1044 | QList< QList<float> > pointErrors; |
| 1045 | + QList<float> normalizedLengths; | |
| 1046 | + | |
| 1045 | 1047 | for (int i=0; i<predicted.size(); i++) { |
| 1046 | 1048 | const QString &predictedName = predictedNames[i]; |
| 1047 | 1049 | const int truthIndex = truthNames.indexOf(predictedName); |
| ... | ... | @@ -1057,20 +1059,49 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 1057 | 1059 | if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range."); |
| 1058 | 1060 | if (normalizationIndexB >= truthPoints.size()) qFatal("Normalization index B is out of range."); |
| 1059 | 1061 | const float normalizedLength = QtUtils::euclideanLength(truthPoints[normalizationIndexB] - truthPoints[normalizationIndexA]); |
| 1060 | - for (int j=0; j<predictedPoints.size(); j++) | |
| 1062 | + normalizedLengths.append(normalizedLength); | |
| 1063 | + for (int j=0; j<predictedPoints.size(); j++) { | |
| 1061 | 1064 | pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength); |
| 1065 | + } | |
| 1062 | 1066 | } |
| 1063 | - qDebug() << "Skipped " << skipped << " files due to point size mismatch."; | |
| 1067 | + | |
| 1068 | + qDebug() << "Skipped" << skipped << "files due to point size mismatch."; | |
| 1064 | 1069 | |
| 1065 | 1070 | QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size()); |
| 1071 | + QList<float> medianPointErrors; medianPointErrors.reserve(pointErrors.size()); | |
| 1072 | + QList<float> stddevPointErrors; stddevPointErrors.reserve(pointErrors.size()); | |
| 1073 | + | |
| 1074 | + float normalizedErrorLimit = 1.5; | |
| 1075 | + | |
| 1076 | + QSet<int> worstExamples; | |
| 1066 | 1077 | for (int i=0; i<pointErrors.size(); i++) { |
| 1078 | + if (skipped == 0) { | |
| 1079 | + QList<QPair<float,int> > exampleIndices = Common::Sort(pointErrors[i],true); | |
| 1080 | + for (int j=0; j<exampleIndices.size() && worstExamples.size()<(5*i+1); j++) | |
| 1081 | + if (exampleIndices[j].first < normalizedErrorLimit) | |
| 1082 | + worstExamples.insert(exampleIndices[j].second); | |
| 1083 | + } | |
| 1067 | 1084 | std::sort(pointErrors[i].begin(), pointErrors[i].end()); |
| 1068 | - averagePointErrors.append(Common::Mean(pointErrors[i])); | |
| 1085 | + double mean, stddev; | |
| 1086 | + Common::MeanStdDev(pointErrors[i],&mean,&stddev); | |
| 1087 | + averagePointErrors.append(mean); | |
| 1088 | + stddevPointErrors.append(stddev); | |
| 1089 | + medianPointErrors.append(Common::Median(pointErrors[i])); | |
| 1069 | 1090 | } |
| 1070 | 1091 | const float averagePointError = Common::Mean(averagePointErrors); |
| 1092 | + const float medianPointError = Common::Mean(medianPointErrors); | |
| 1093 | + const float stddevPointError = Common::Mean(stddevPointErrors); | |
| 1071 | 1094 | |
| 1072 | 1095 | QStringList lines; |
| 1073 | 1096 | lines.append("Plot,X,Y"); |
| 1097 | + | |
| 1098 | + QFile exampleFile("landmarking_examples"); | |
| 1099 | + QtUtils::touchDir(exampleFile); | |
| 1100 | + lines.append("EX,landmarking_examples/"+truth[sampleIndex].file.fileName()+","+QString::number(truth[sampleIndex].file.points().size())); | |
| 1101 | + | |
| 1102 | + // Alternatively, can we just pass this through a predetermined transform and write? | |
| 1103 | + Enroll(truth[sampleIndex],"landmarking_examples"); | |
| 1104 | + | |
| 1074 | 1105 | for (int i=0; i<pointErrors.size(); i++) { |
| 1075 | 1106 | const QList<float> &pointError = pointErrors[i]; |
| 1076 | 1107 | const int keep = qMin(Max_Points, pointError.size()); |
| ... | ... | @@ -1081,7 +1112,18 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 1081 | 1112 | lines.append(QString("AvgError,0,%1").arg(averagePointError)); |
| 1082 | 1113 | |
| 1083 | 1114 | QtUtils::writeFile(csv, lines); |
| 1084 | - qDebug("Average Error: %.3f", averagePointError); | |
| 1115 | + | |
| 1116 | + for (int i=0; i<averagePointErrors.size(); i++) { | |
| 1117 | + qDebug("Average Error for point %d: %.3f", i, averagePointErrors[i]); | |
| 1118 | + qDebug("Median Error for point %d: %.3f", i, medianPointErrors[i]); | |
| 1119 | + qDebug("Standard Deviation of Error for point %d: %.3f", i, stddevPointErrors[i]); | |
| 1120 | + } | |
| 1121 | + | |
| 1122 | + qDebug("Average Error for all Points: %.3f", averagePointError); | |
| 1123 | + qDebug("Average Median Error for all Points: %.3f", medianPointError); | |
| 1124 | + qDebug("Average Standard Deviation of Error for all Points: %.3f", stddevPointError); | |
| 1125 | + qDebug("Average Normalization Length (pixels): %.3f", Common::Mean(normalizedLengths)); | |
| 1126 | + | |
| 1085 | 1127 | return averagePointError; |
| 1086 | 1128 | } |
| 1087 | 1129 | ... | ... |
openbr/core/eval.h
| ... | ... | @@ -31,7 +31,7 @@ namespace br |
| 31 | 31 | |
| 32 | 32 | void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 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 | - float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1); // Return average error | |
| 34 | + float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1, int sampleIndex = 0); // Return average error | |
| 35 | 35 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 36 | 36 | } |
| 37 | 37 | ... | ... |
openbr/core/plot.cpp
| ... | ... | @@ -472,13 +472,72 @@ bool PlotLandmarking(const QStringList &files, const File &destination, bool sho |
| 472 | 472 | p.file.write("# Split data into individual plots\n" |
| 473 | 473 | "plot_index = which(names(data)==\"Plot\")\n" |
| 474 | 474 | "Box <- data[grep(\"Box\",data$Plot),-c(1)]\n" |
| 475 | + "EX <- data[grep(\"EX\",data$Plot),-c(1)]\n" | |
| 476 | + "EX$X <- as.character(EX$X)\n" | |
| 477 | + "EX$Y <- as.character(EX$Y)\n" | |
| 475 | 478 | "rm(data)\n" |
| 476 | 479 | "\n"); |
| 477 | 480 | |
| 478 | - p.file.write(qPrintable(QString("ggplot(Box, aes(Y,%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + | |
| 481 | + // Load in the relevant libraries | |
| 482 | + p.file.write(qPrintable(QString("if (nrow(EX) != 0) { \ | |
| 483 | + \n\tlibrary(jpeg) \ | |
| 484 | + \n\tlibrary(png) \ | |
| 485 | + \n\tlibrary(grid)\n\t") + | |
| 486 | + | |
| 487 | + QString("multiplot <- function(..., plotlist=NULL, cols) {") + | |
| 488 | + QString("\n\t\t require(grid) \ | |
| 489 | + \n\n\t\t# Make a list from the ... arguments and plotlist \ | |
| 490 | + \n\t\t plots <- c(list(...), plotlist)\n") + | |
| 491 | + | |
| 492 | + QString("\t\tnumPlots = length(plots)\n\n\t\t \ | |
| 493 | + # Make the panel \ | |
| 494 | + \n\t\tplotCols = cols \ | |
| 495 | + \n\t\tplotRows = ceiling(numPlots/plotCols) \ | |
| 496 | + \n\n") + | |
| 497 | + | |
| 498 | + QString("\t\t# Set up the page \ | |
| 499 | + \n\t\tgrid.newpage() \ | |
| 500 | + \n\t\tpushViewport(viewport(layout = grid.layout(plotRows, plotCols))) \ | |
| 501 | + \n\t\tvplayout <- function(x, y) \ | |
| 502 | + \n\t\t\tviewport(layout.pos.row = x, layout.pos.col = y)\n\n") + | |
| 503 | + | |
| 504 | + QString("\t\t# Make each plot, in the correct location \ | |
| 505 | + \n\t\tfor (i in 1:numPlots) { \ | |
| 506 | + \n\t\t\tcurRow = ceiling(i/plotCols)\n\t\t\tcurCol = (i-1) %% plotCols + 1 \ | |
| 507 | + \n\t\t\tprint(plots[[i]], vp = vplayout(curRow, curCol))\n\t\t}\n\t}\n\n"))); | |
| 508 | + | |
| 509 | + p.file.write(qPrintable(QString("\n\n\t# Print genuine matches below the EER \ | |
| 510 | + \n\t \ | |
| 511 | + for (i in 1:nrow(EX)) { \ | |
| 512 | + \n\t\t path <- EX[i,1] \ | |
| 513 | + \n\t\t points <- EX[i,2] \ | |
| 514 | + \n\t\t file <- unlist(strsplit(path, \"[.]\"))[1] \ | |
| 515 | + \n\t\t ext <- unlist(strsplit(path, \"[.]\"))[2]") + | |
| 516 | + | |
| 517 | + // These should be made into a function assuming we can return an image variable regardless of extension | |
| 518 | + QString("\n\t\t\tif (ext == \"jpg\" || ext == \"JPEG\" || ext == \"jpeg\" || ext == \"JPG\") { \ | |
| 519 | + \n\t\t\t img <- readJPEG(path)\n\t\t } \ | |
| 520 | + else if (ext == \"PNG\" || ext == \"png\") { \ | |
| 521 | + \n\t\t\t img <- readPNG(path)\n\t\t} \ | |
| 522 | + else if (ext == \"TIFF\" || ext == \"tiff\" || ext == \"TIF\" || ext == \"tif\") { \ | |
| 523 | + \n\t\t\t img <- readTIFF(path)\n\t\t} \ | |
| 524 | + else {\n\t\t\tnext\n\t\t} ") + | |
| 525 | + | |
| 526 | + QString("\n\t\t name <- file \ | |
| 527 | + \n\n\t\t g <- rasterGrob(img, interpolate=TRUE)\n\n\t\t") + | |
| 528 | + | |
| 529 | + QString("print(qplot(1:10, 1:10, geom=\"blank\") \ | |
| 530 | + + annotation_custom(g, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) \ | |
| 531 | + + theme(axis.line=element_blank(), axis.title.y=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), line=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) \ | |
| 532 | + + labs(title=\"Sample Landmarks\") + xlab(sprintf(\"Total Landmarks: %s\",points)))\n\t\t}}\n"))); | |
| 533 | + | |
| 534 | + p.file.write(qPrintable(QString("ggplot(Box, aes(Y,%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), | |
| 535 | + p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + | |
| 479 | 536 | QString(" + annotation_logticks(sides=\"b\") + stat_ecdf() + scale_x_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10)) + scale_y_continuous(\"Cumulative Density\", label=percent) + theme_minimal()\n\n"))); |
| 537 | + | |
| 480 | 538 | p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + |
| 481 | 539 | QString("+ annotation_logticks(sides=\"l\") + geom_boxplot(alpha=0.5) + geom_jitter(size=1, alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.01,0.1,1,10)) + theme_minimal()\n\n"))); |
| 540 | + | |
| 482 | 541 | p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + |
| 483 | 542 | QString("+ annotation_logticks(sides=\"l\") + geom_violin(alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10))\n\n"))); |
| 484 | 543 | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -134,9 +134,9 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery |
| 134 | 134 | return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize); |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | -float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b) | |
| 137 | +float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b, int sample_index) | |
| 138 | 138 | { |
| 139 | - return EvalLandmarking(predicted_gallery, truth_gallery, csv, normalization_index_a, normalization_index_b); | |
| 139 | + return EvalLandmarking(predicted_gallery, truth_gallery, csv, normalization_index_a, normalization_index_b, sample_index); | |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | 142 | void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property, const char *truth_property) | ... | ... |
openbr/openbr.h
| ... | ... | @@ -214,8 +214,9 @@ BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *tru |
| 214 | 214 | * \param csv Optional \c .csv file to contain performance metrics. |
| 215 | 215 | * \param normalization_index_a Optional first index in the list of points to use for normalization. |
| 216 | 216 | * \param normalization_index_b Optional second index in the list of points to use for normalization. |
| 217 | + * \param sample_index Optional index for sample landmark image in ground truth gallery. | |
| 217 | 218 | */ |
| 218 | -BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1); | |
| 219 | +BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1, int sample_index = 0); | |
| 219 | 220 | |
| 220 | 221 | /*! |
| 221 | 222 | * \brief Evaluates regression accuracy to disk. | ... | ... |