Commit 3808a6fe13088d7caf3584a0517f4f9dc4cb5f75
Merge branch 'master' into attributes
Showing
21 changed files
with
348 additions
and
54 deletions
LICENSE.txt
app/br/br.cpp
| ... | ... | @@ -138,8 +138,8 @@ public: |
| 138 | 138 | check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'."); |
| 139 | 139 | br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); |
| 140 | 140 | } else if (!strcmp(fun, "evalClustering")) { |
| 141 | - check(parc == 2, "Incorrect parameter count for 'evalClustering'."); | |
| 142 | - br_eval_clustering(parv[0], parv[1]); | |
| 141 | + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalClustering'."); | |
| 142 | + br_eval_clustering(parv[0], parv[1], parc == 3 ? parv[2] : ""); | |
| 143 | 143 | } else if (!strcmp(fun, "evalDetection")) { |
| 144 | 144 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'."); |
| 145 | 145 | br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); | ... | ... |
openbr/core/cluster.cpp
| ... | ... | @@ -100,7 +100,9 @@ Neighborhood getNeighborhood(const QStringList &simmats) |
| 100 | 100 | int currentRows = -1; |
| 101 | 101 | int columnOffset = 0; |
| 102 | 102 | for (int j=0; j<numGalleries; j++) { |
| 103 | - cv::Mat m = BEE::readMat(simmats[i*numGalleries+j]); | |
| 103 | + QScopedPointer<br::Format> format(br::Factory<br::Format>::make(simmats[i*numGalleries+j])); | |
| 104 | + br::Template t = format->read(); | |
| 105 | + cv::Mat m = t.m(); | |
| 104 | 106 | if (j==0) { |
| 105 | 107 | currentRows = m.rows; |
| 106 | 108 | allNeighbors.resize(currentRows); |
| ... | ... | @@ -115,8 +117,9 @@ Neighborhood getNeighborhood(const QStringList &simmats) |
| 115 | 117 | float val = m.at<float>(k,l); |
| 116 | 118 | if ((i==j) && (k==l)) continue; // Skips self-similarity scores |
| 117 | 119 | |
| 118 | - if ((val != -std::numeric_limits<float>::infinity()) && | |
| 119 | - (val != std::numeric_limits<float>::infinity())) { | |
| 120 | + if (val != -std::numeric_limits<float>::max() | |
| 121 | + && val != -std::numeric_limits<float>::infinity() | |
| 122 | + && val != std::numeric_limits<float>::infinity()) { | |
| 120 | 123 | globalMax = std::max(globalMax, val); |
| 121 | 124 | globalMin = std::min(globalMin, val); |
| 122 | 125 | } |
| ... | ... | @@ -157,7 +160,7 @@ Neighborhood getNeighborhood(const QStringList &simmats) |
| 157 | 160 | // Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 |
| 158 | 161 | br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv) |
| 159 | 162 | { |
| 160 | - qDebug("Clustering %d simmat(s)", simmats.size()); | |
| 163 | + qDebug("Clustering %d simmat(s), aggressiveness %f", simmats.size(), aggressiveness); | |
| 161 | 164 | |
| 162 | 165 | // Read in gallery parts, keeping top neighbors of each template |
| 163 | 166 | Neighborhood neighborhood = getNeighborhood(simmats); |
| ... | ... | @@ -275,13 +278,14 @@ float jaccardIndex(const QVector<int> &indicesA, const QVector<int> &indicesB) |
| 275 | 278 | |
| 276 | 279 | // Evaluates clustering algorithms based on metrics described in |
| 277 | 280 | // Santo Fortunato "Community detection in graphs", Physics Reports 486 (2010) |
| 278 | -void br::EvalClustering(const QString &csv, const QString &input) | |
| 281 | +void br::EvalClustering(const QString &csv, const QString &input, QString truth_property) | |
| 279 | 282 | { |
| 283 | + if (truth_property.isEmpty()) | |
| 284 | + truth_property = "Label"; | |
| 280 | 285 | qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input)); |
| 281 | 286 | |
| 282 | - // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are | |
| 283 | - // not named). Direct use of ClusterID is not general -cao | |
| 284 | - QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "ClusterID"); | |
| 287 | + TemplateList tList = TemplateList::fromGallery(input); | |
| 288 | + QList<int> labels = tList.indexProperty(truth_property); | |
| 285 | 289 | |
| 286 | 290 | QHash<int, int> labelToIndex; |
| 287 | 291 | int nClusters = 0; | ... | ... |
openbr/core/cluster.h
| ... | ... | @@ -28,7 +28,7 @@ namespace br |
| 28 | 28 | typedef QVector<Cluster> Clusters; |
| 29 | 29 | |
| 30 | 30 | Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv); |
| 31 | - void EvalClustering(const QString &csv, const QString &input); | |
| 31 | + void EvalClustering(const QString &csv, const QString &input, QString truth_property); | |
| 32 | 32 | |
| 33 | 33 | Clusters ReadClusters(const QString &csv); |
| 34 | 34 | void WriteClusters(const Clusters &clusters, const QString &csv); | ... | ... |
openbr/core/common.cpp
| ... | ... | @@ -76,3 +76,11 @@ QList<float> Common::linspace(float start, float stop, int n) { |
| 76 | 76 | spaced.append(stop); |
| 77 | 77 | return spaced; |
| 78 | 78 | } |
| 79 | + | |
| 80 | +QList<int> Common::ind2sub(int dims, int nPerDim, int idx) { | |
| 81 | + QList<int> subIndices; | |
| 82 | + for (int j = 0; j < dims; j++) { | |
| 83 | + subIndices.append(((int)floor( idx / pow((float)nPerDim, j))) % nPerDim); | |
| 84 | + } | |
| 85 | + return subIndices; | |
| 86 | +} | ... | ... |
openbr/core/common.h
openbr/core/eigenutils.cpp
| ... | ... | @@ -67,3 +67,12 @@ void printEigen(Eigen::MatrixXf X) { |
| 67 | 67 | void printSize(Eigen::MatrixXf X) { |
| 68 | 68 | qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols(); |
| 69 | 69 | } |
| 70 | + | |
| 71 | +float eigMean(const Eigen::MatrixXf& x) { | |
| 72 | + return x.array().sum() / (x.rows() * x.cols()); | |
| 73 | +} | |
| 74 | + | |
| 75 | +float eigStd(const Eigen::MatrixXf& x) { | |
| 76 | + float mean = eigMean(x); | |
| 77 | + return sqrt((x.array() - mean).pow(2).sum() / (x.cols() * x.rows())); | |
| 78 | +} | ... | ... |
openbr/core/eigenutils.h
| ... | ... | @@ -67,4 +67,55 @@ inline QDataStream &operator>>(QDataStream &stream, Eigen::Matrix< _Scalar, _Row |
| 67 | 67 | return stream; |
| 68 | 68 | } |
| 69 | 69 | |
| 70 | +/*Compute the mean of the each column (dim == 1) or row (dim == 2) | |
| 71 | + of the matrix*/ | |
| 72 | +template<typename T> | |
| 73 | +Eigen::MatrixBase<T> eigMean(const Eigen::MatrixBase<T>& x,int dim) | |
| 74 | +{ | |
| 75 | + if (dim == 1) { | |
| 76 | + Eigen::MatrixBase<T> y(1,x.cols()); | |
| 77 | + for (int i = 0; i < x.cols(); i++) | |
| 78 | + y(i) = x.col(i).sum() / x.rows(); | |
| 79 | + return y; | |
| 80 | + } else if (dim == 2) { | |
| 81 | + Eigen::MatrixBase<T> y(x.rows(),1); | |
| 82 | + for (int i = 0; i < x.rows(); i++) | |
| 83 | + y(i) = x.row(i).sum() / x.cols(); | |
| 84 | + return y; | |
| 85 | + } | |
| 86 | + qFatal("A matrix can only have two dimensions"); | |
| 87 | +} | |
| 88 | + | |
| 89 | +/*Compute the element-wise mean*/ | |
| 90 | +float eigMean(const Eigen::MatrixXf& x); | |
| 91 | +/*Compute the element-wise mean*/ | |
| 92 | +float eigStd(const Eigen::MatrixXf& x); | |
| 93 | + | |
| 94 | +/*Compute the std dev of the each column (dim == 1) or row (dim == 2) | |
| 95 | + of the matrix*/ | |
| 96 | +template<typename T> | |
| 97 | +Eigen::MatrixBase<T> eigStd(const Eigen::MatrixBase<T>& x,int dim) | |
| 98 | +{ | |
| 99 | + Eigen::MatrixBase<T> mean = eigMean(x, dim); | |
| 100 | + if (dim == 1) { | |
| 101 | + Eigen::MatrixBase<T> y(1,x.cols()); | |
| 102 | + for (int i = 0; i < x.cols(); i++) { | |
| 103 | + T value = 0; | |
| 104 | + for (int j = 0; j < x.rows(); j++) | |
| 105 | + value += pow(y(j, i) - mean(i), 2); | |
| 106 | + y(i) = sqrt(value / (x.rows() - 1)); | |
| 107 | + } | |
| 108 | + return y; | |
| 109 | + } else if (dim == 2) { | |
| 110 | + Eigen::MatrixBase<T> y(x.rows(),1); | |
| 111 | + for (int i = 0; i < x.rows(); i++) { | |
| 112 | + T value = 0; | |
| 113 | + for (int j = 0; j < x.cols(); j++) | |
| 114 | + value += pow(y(i, j) - mean(j), 2); | |
| 115 | + y(i) = sqrt(value / (x.cols() - 1)); | |
| 116 | + } | |
| 117 | + return y; | |
| 118 | + } | |
| 119 | + qFatal("A matrix can only have two dimensions"); | |
| 120 | +} | |
| 70 | 121 | #endif // EIGENUTILS_H | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -572,6 +572,7 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 572 | 572 | const QStringList predictedNames = File::get<QString>(predicted, "name"); |
| 573 | 573 | const QStringList truthNames = File::get<QString>(truth, "name"); |
| 574 | 574 | |
| 575 | + int skipped = 0; | |
| 575 | 576 | QList< QList<float> > pointErrors; |
| 576 | 577 | for (int i=0; i<predicted.size(); i++) { |
| 577 | 578 | const QString &predictedName = predictedNames[i]; |
| ... | ... | @@ -579,7 +580,10 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 579 | 580 | if (truthIndex == -1) qFatal("Could not identify ground truth for file: %s", qPrintable(predictedName)); |
| 580 | 581 | const QList<QPointF> predictedPoints = predicted[i].file.points(); |
| 581 | 582 | const QList<QPointF> truthPoints = truth[truthIndex].file.points(); |
| 582 | - if (predictedPoints.size() != truthPoints.size()) qFatal("Points size mismatch for file: %s", qPrintable(predictedName)); | |
| 583 | + if (predictedPoints.size() != truthPoints.size()) { | |
| 584 | + skipped++; | |
| 585 | + continue; | |
| 586 | + } | |
| 583 | 587 | while (pointErrors.size() < predictedPoints.size()) |
| 584 | 588 | pointErrors.append(QList<float>()); |
| 585 | 589 | if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range."); |
| ... | ... | @@ -588,6 +592,7 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 588 | 592 | for (int j=0; j<predictedPoints.size(); j++) |
| 589 | 593 | pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength); |
| 590 | 594 | } |
| 595 | + qDebug() << "Skipped " << skipped << " files do to point size mismatch."; | |
| 591 | 596 | |
| 592 | 597 | QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size()); |
| 593 | 598 | for (int i=0; i<pointErrors.size(); i++) { |
| ... | ... | @@ -605,6 +610,8 @@ float EvalLandmarking(const QString &predictedGallery, const QString &truthGalle |
| 605 | 610 | lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)]))); |
| 606 | 611 | } |
| 607 | 612 | |
| 613 | + lines.append(QString("AvgError,0,%1").arg(averagePointError)); | |
| 614 | + | |
| 608 | 615 | QtUtils::writeFile(csv, lines); |
| 609 | 616 | qDebug("Average Error: %.3f", averagePointError); |
| 610 | 617 | return averagePointError; | ... | ... |
openbr/core/qtutils.cpp
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | #include <QProcess> |
| 25 | 25 | #include <QProcessEnvironment> |
| 26 | 26 | #include <QRegExp> |
| 27 | +#include <QRegularExpression> | |
| 27 | 28 | #include <QStack> |
| 28 | 29 | #include <QUrl> |
| 29 | 30 | #include <QMap> |
| ... | ... | @@ -81,7 +82,7 @@ void readFile(const QString &file, QStringList &lines) |
| 81 | 82 | { |
| 82 | 83 | QByteArray data; |
| 83 | 84 | readFile(file, data); |
| 84 | - lines = QString(data).split('\n', QString::SkipEmptyParts); | |
| 85 | + lines = QString(data).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts); | |
| 85 | 86 | for (int i=0; i<lines.size(); i++) |
| 86 | 87 | lines[i] = lines[i].simplified(); |
| 87 | 88 | } | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -109,9 +109,9 @@ void br_eval_classification(const char *predicted_gallery, const char *truth_gal |
| 109 | 109 | EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property); |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | -void br_eval_clustering(const char *csv, const char *gallery) | |
| 112 | +void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property) | |
| 113 | 113 | { |
| 114 | - EvalClustering(csv, gallery); | |
| 114 | + EvalClustering(csv, gallery, truth_property); | |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv) |
| ... | ... | @@ -435,7 +435,14 @@ br_template_list br_load_from_gallery(br_gallery gallery) |
| 435 | 435 | return (br_template_list)tl; |
| 436 | 436 | } |
| 437 | 437 | |
| 438 | -void br_add_to_gallery(br_gallery gallery, br_template_list tl) | |
| 438 | +void br_add_template_to_gallery(br_gallery gallery, br_template tmpl) | |
| 439 | +{ | |
| 440 | + Gallery *gal = reinterpret_cast<Gallery*>(gallery); | |
| 441 | + Template *t = reinterpret_cast<Template*>(tmpl); | |
| 442 | + gal->write(*t); | |
| 443 | +} | |
| 444 | + | |
| 445 | +void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl) | |
| 439 | 446 | { |
| 440 | 447 | Gallery *gal = reinterpret_cast<Gallery*>(gallery); |
| 441 | 448 | TemplateList *realTL = reinterpret_cast<TemplateList*>(tl); | ... | ... |
openbr/openbr.h
| ... | ... | @@ -167,9 +167,10 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char |
| 167 | 167 | * \brief Evaluates and prints clustering accuracy to the terminal. |
| 168 | 168 | * \param csv The cluster results file. |
| 169 | 169 | * \param gallery The br::Gallery used to generate the \ref simmat that was clustered. |
| 170 | + * \param truth_property (Optional) which metadata key to use from <i>gallery</i/>, defaults to Label | |
| 170 | 171 | * \see br_cluster |
| 171 | 172 | */ |
| 172 | -BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery); | |
| 173 | +BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property); | |
| 173 | 174 | |
| 174 | 175 | /*! |
| 175 | 176 | * \brief Evaluates and prints detection accuracy to terminal. |
| ... | ... | @@ -562,9 +563,13 @@ BR_EXPORT br_gallery br_make_gallery(const char *gallery); |
| 562 | 563 | */ |
| 563 | 564 | BR_EXPORT br_template_list br_load_from_gallery(br_gallery gallery); |
| 564 | 565 | /*! |
| 566 | + * \brief Write a br::Template to the br::Gallery on disk. | |
| 567 | + */ | |
| 568 | +BR_EXPORT void br_add_template_to_gallery(br_gallery gallery, br_template tmpl); | |
| 569 | +/*! | |
| 565 | 570 | * \brief Write a br::TemplateList to the br::Gallery on disk. |
| 566 | 571 | */ |
| 567 | -BR_EXPORT void br_add_to_gallery(br_gallery gallery, br_template_list tl); | |
| 572 | +BR_EXPORT void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl); | |
| 568 | 573 | /*! |
| 569 | 574 | * \brief Close the br::Gallery. |
| 570 | 575 | */ | ... | ... |
openbr/openbr_plugin.cpp
| ... | ... | @@ -137,6 +137,8 @@ QVariant File::parse(const QString &value) |
| 137 | 137 | if (ok) return point; |
| 138 | 138 | const QRectF rect = QtUtils::toRect(value, &ok); |
| 139 | 139 | if (ok) return rect; |
| 140 | + const int i = value.toInt(&ok); | |
| 141 | + if (ok) return i; | |
| 140 | 142 | const float f = value.toFloat(&ok); |
| 141 | 143 | if (ok) return f; |
| 142 | 144 | return value; | ... | ... |
openbr/plugins/algorithms.cpp
| ... | ... | @@ -44,7 +44,7 @@ class AlgorithmsInitializer : public Initializer |
| 44 | 44 | Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); |
| 45 | 45 | Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+Bin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+Bin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1"); |
| 46 | 46 | Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)"); |
| 47 | - Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)"); | |
| 47 | + Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Cat+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)"); | |
| 48 | 48 | |
| 49 | 49 | // Video |
| 50 | 50 | Globals->abbreviations.insert("DisplayVideo", "Stream(FPSLimit(30)+Show(false,[FrameNumber])+Discard)"); |
| ... | ... | @@ -64,6 +64,7 @@ class AlgorithmsInitializer : public Initializer |
| 64 | 64 | Globals->abbreviations.insert("SmallSIFT", "Open+LimitSize(512)+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); |
| 65 | 65 | Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)"); |
| 66 | 66 | Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2"); |
| 67 | + Globals->abbreviations.insert("ImageSimilarity", "Open+EnsureChannels(3)+Resize(256,256)+SplitChannels+RectRegions(64,64,64,64)+Hist(256,0,8)+Cat:NegativeLogPlusOne(L2)"); | |
| 67 | 68 | Globals->abbreviations.insert("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+Bin(0,360,9,true)+Merge+Integral+RecursiveIntegralSampler(4,2,8,Singleton(KMeans(256)))+Cat+CvtFloat+Hist(256)+KNN(5,Dist(L1),false,5)+Rename(KNN,Subject)"); |
| 68 | 69 | Globals->abbreviations.insert("TanTriggs", "Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)"); |
| 69 | 70 | ... | ... |
openbr/plugins/eigen3.cpp
| ... | ... | @@ -15,15 +15,35 @@ |
| 15 | 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 16 | 16 | |
| 17 | 17 | #include <Eigen/Dense> |
| 18 | + | |
| 18 | 19 | #include "openbr_internal.h" |
| 19 | 20 | |
| 20 | 21 | #include "openbr/core/common.h" |
| 21 | 22 | #include "openbr/core/eigenutils.h" |
| 23 | +#include "openbr/core/opencvutils.h" | |
| 22 | 24 | |
| 23 | 25 | namespace br |
| 24 | 26 | { |
| 25 | 27 | |
| 26 | 28 | /*! |
| 29 | + * \ingroup initializers | |
| 30 | + * \brief Initialize Eigen | |
| 31 | + * http://eigen.tuxfamily.org/dox/TopicMultiThreading.html | |
| 32 | + * \author Scott Klum \cite sklum | |
| 33 | + */ | |
| 34 | +class EigenInitializer : public Initializer | |
| 35 | +{ | |
| 36 | + Q_OBJECT | |
| 37 | + | |
| 38 | + void initialize() const | |
| 39 | + { | |
| 40 | + Eigen::initParallel(); | |
| 41 | + } | |
| 42 | +}; | |
| 43 | + | |
| 44 | +BR_REGISTER(Initializer, EigenInitializer) | |
| 45 | + | |
| 46 | +/*! | |
| 27 | 47 | * \ingroup transforms |
| 28 | 48 | * \brief Projects input into learned Principal Component Analysis subspace. |
| 29 | 49 | * \author Brendan Klare \cite bklare |
| ... | ... | @@ -296,6 +316,8 @@ BR_REGISTER(Transform, DFFSTransform) |
| 296 | 316 | */ |
| 297 | 317 | class LDATransform : public Transform |
| 298 | 318 | { |
| 319 | + friend class SparseLDATransform; | |
| 320 | + | |
| 299 | 321 | Q_OBJECT |
| 300 | 322 | Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false) |
| 301 | 323 | Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false) |
| ... | ... | @@ -499,21 +521,30 @@ class LDATransform : public Transform |
| 499 | 521 | |
| 500 | 522 | // Do projection |
| 501 | 523 | outMap = projection.transpose() * (inMap - mean); |
| 502 | - | |
| 503 | 524 | if (normalize && isBinary) |
| 504 | 525 | dst.m().at<float>(0,0) = dst.m().at<float>(0,0) / stdDev; |
| 505 | 526 | } |
| 506 | 527 | |
| 507 | 528 | void store(QDataStream &stream) const |
| 508 | 529 | { |
| 509 | - stream << pcaKeep << directLDA << directDrop << dimsOut << mean << projection; | |
| 530 | + stream << pcaKeep; | |
| 531 | + stream << directLDA; | |
| 532 | + stream << directDrop; | |
| 533 | + stream << dimsOut; | |
| 534 | + stream << mean; | |
| 535 | + stream << projection; | |
| 510 | 536 | if (normalize && isBinary) |
| 511 | 537 | stream << stdDev; |
| 512 | 538 | } |
| 513 | 539 | |
| 514 | 540 | void load(QDataStream &stream) |
| 515 | 541 | { |
| 516 | - stream >> pcaKeep >> directLDA >> directDrop >> dimsOut >> mean >> projection; | |
| 542 | + stream >> pcaKeep; | |
| 543 | + stream >> directLDA; | |
| 544 | + stream >> directDrop; | |
| 545 | + stream >> dimsOut; | |
| 546 | + stream >> mean; | |
| 547 | + stream >> projection; | |
| 517 | 548 | if (normalize && isBinary) |
| 518 | 549 | stream >> stdDev; |
| 519 | 550 | } |
| ... | ... | @@ -522,6 +553,104 @@ class LDATransform : public Transform |
| 522 | 553 | BR_REGISTER(Transform, LDATransform) |
| 523 | 554 | |
| 524 | 555 | /*! |
| 556 | + * \ingroup transforms | |
| 557 | + * \brief Projects input into learned Linear Discriminant Analysis subspace | |
| 558 | + * learned on a sparse subset of features with the highest weight | |
| 559 | + * in the original LDA algorithm. | |
| 560 | + * \author Brendan Klare \cite bklare | |
| 561 | + */ | |
| 562 | +class SparseLDATransform : public Transform | |
| 563 | +{ | |
| 564 | + Q_OBJECT | |
| 565 | + Q_PROPERTY(float varThreshold READ get_varThreshold WRITE set_varThreshold RESET reset_varThreshold STORED false) | |
| 566 | + Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false) | |
| 567 | + Q_PROPERTY(bool normalize READ get_normalize WRITE set_normalize RESET reset_normalize STORED false) | |
| 568 | + BR_PROPERTY(float, varThreshold, 1.5) | |
| 569 | + BR_PROPERTY(float, pcaKeep, 0.98) | |
| 570 | + BR_PROPERTY(bool, normalize, true) | |
| 571 | + | |
| 572 | + LDATransform ldaSparse; | |
| 573 | + int dimsOut; | |
| 574 | + QList<int> selections; | |
| 575 | + | |
| 576 | + Eigen::VectorXf mean; | |
| 577 | + | |
| 578 | + void init() | |
| 579 | + { | |
| 580 | + ldaSparse.init(); | |
| 581 | + ldaSparse.pcaKeep = pcaKeep; | |
| 582 | + ldaSparse.inputVariable = "Label"; | |
| 583 | + ldaSparse.isBinary = true; | |
| 584 | + ldaSparse.normalize = true; | |
| 585 | + } | |
| 586 | + | |
| 587 | + void train(const TemplateList &_trainingSet) | |
| 588 | + { | |
| 589 | + | |
| 590 | + LDATransform ldaOrig; | |
| 591 | + ldaOrig.init(); | |
| 592 | + ldaOrig.inputVariable = "Label"; | |
| 593 | + ldaOrig.pcaKeep = pcaKeep; | |
| 594 | + ldaOrig.isBinary = true; | |
| 595 | + ldaOrig.normalize = true; | |
| 596 | + | |
| 597 | + ldaOrig.train(_trainingSet); | |
| 598 | + | |
| 599 | + //Only works on binary class problems for now | |
| 600 | + assert(ldaOrig.projection.cols() == 1); | |
| 601 | + float ldaStd = eigStd(ldaOrig.projection); | |
| 602 | + for (int i = 0; i < ldaOrig.projection.rows(); i++) | |
| 603 | + if (abs(ldaOrig.projection(i)) > varThreshold * ldaStd) | |
| 604 | + selections.append(i); | |
| 605 | + | |
| 606 | + TemplateList newSet; | |
| 607 | + for (int i = 0; i < _trainingSet.size(); i++) { | |
| 608 | + cv::Mat x(_trainingSet[i]); | |
| 609 | + cv::Mat y = cv::Mat(selections.size(), 1, CV_32FC1); | |
| 610 | + int idx = 0; | |
| 611 | + int cnt = 0; | |
| 612 | + for (int j = 0; j < x.rows; j++) | |
| 613 | + for (int k = 0; k < x.cols; k++, cnt++) | |
| 614 | + if (selections.contains(cnt)) | |
| 615 | + y.at<float>(idx++,0) = x.at<float>(j, k); | |
| 616 | + newSet.append(Template(_trainingSet[i].file, y)); | |
| 617 | + } | |
| 618 | + ldaSparse.train(newSet); | |
| 619 | + dimsOut = ldaSparse.dimsOut; | |
| 620 | + } | |
| 621 | + | |
| 622 | + void project(const Template &src, Template &dst) const | |
| 623 | + { | |
| 624 | + Eigen::Map<Eigen::MatrixXf> inMap((float*)src.m().ptr<float>(), src.m().rows*src.m().cols, 1); | |
| 625 | + Eigen::Map<Eigen::MatrixXf> outMap(dst.m().ptr<float>(), dimsOut, 1); | |
| 626 | + | |
| 627 | + int d = selections.size(); | |
| 628 | + cv::Mat inSelect(d,1,CV_32F); | |
| 629 | + for (int i = 0; i < d; i++) | |
| 630 | + inSelect.at<float>(i) = src.m().at<float>(selections[i]); | |
| 631 | + ldaSparse.project(Template(src.file, inSelect), dst); | |
| 632 | + } | |
| 633 | + | |
| 634 | + void store(QDataStream &stream) const | |
| 635 | + { | |
| 636 | + stream << pcaKeep; | |
| 637 | + stream << ldaSparse; | |
| 638 | + stream << dimsOut; | |
| 639 | + stream << selections; | |
| 640 | + } | |
| 641 | + | |
| 642 | + void load(QDataStream &stream) | |
| 643 | + { | |
| 644 | + stream >> pcaKeep; | |
| 645 | + stream >> ldaSparse; | |
| 646 | + stream >> dimsOut; | |
| 647 | + stream >> selections; | |
| 648 | + } | |
| 649 | +}; | |
| 650 | + | |
| 651 | +BR_REGISTER(Transform, SparseLDATransform) | |
| 652 | + | |
| 653 | +/*! | |
| 525 | 654 | * \ingroup distances |
| 526 | 655 | * \brief L1 distance computed using eigen. |
| 527 | 656 | * \author Josh Klontz \cite jklontz | ... | ... |
openbr/plugins/format.cpp
| ... | ... | @@ -151,7 +151,7 @@ class csvFormat : public Format |
| 151 | 151 | { |
| 152 | 152 | QFile f(file.name); |
| 153 | 153 | f.open(QFile::ReadOnly); |
| 154 | - QStringList lines(QString(f.readAll()).split('\n')); | |
| 154 | + QStringList lines(QString(f.readAll()).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts)); | |
| 155 | 155 | f.close(); |
| 156 | 156 | |
| 157 | 157 | bool isUChar = true; | ... | ... |
openbr/plugins/gallery.cpp
| ... | ... | @@ -506,29 +506,78 @@ BR_REGISTER(Gallery, csvGallery) |
| 506 | 506 | * \brief Treats each line as a file. |
| 507 | 507 | * \author Josh Klontz \cite jklontz |
| 508 | 508 | * |
| 509 | - * The entire line is treated as the file path. | |
| 509 | + * The entire line is treated as the file path. An optional label may be specified using a space ' ' separator: | |
| 510 | 510 | * |
| 511 | +\verbatim | |
| 512 | +<FILE> | |
| 513 | +<FILE> | |
| 514 | +... | |
| 515 | +<FILE> | |
| 516 | +\endverbatim | |
| 517 | + * or | |
| 518 | +\verbatim | |
| 519 | +<FILE> <LABEL> | |
| 520 | +<FILE> <LABEL> | |
| 521 | +... | |
| 522 | +<FILE> <LABEL> | |
| 523 | +\endverbatim | |
| 511 | 524 | * \see csvGallery |
| 512 | 525 | */ |
| 513 | 526 | class txtGallery : public Gallery |
| 514 | 527 | { |
| 515 | 528 | Q_OBJECT |
| 516 | - Q_PROPERTY(QString metadataKey READ get_metadataKey WRITE set_metadataKey RESET reset_metadataKey STORED false) | |
| 517 | - BR_PROPERTY(QString, metadataKey, "") | |
| 529 | + Q_PROPERTY(QString label READ get_label WRITE set_label RESET reset_label STORED false) | |
| 530 | + BR_PROPERTY(QString, label, "") | |
| 518 | 531 | |
| 519 | 532 | QStringList lines; |
| 520 | 533 | |
| 521 | 534 | ~txtGallery() |
| 522 | 535 | { |
| 523 | - if (!lines.isEmpty()) QtUtils::writeFile(file.name, lines); | |
| 536 | + if (!lines.isEmpty()) | |
| 537 | + QtUtils::writeFile(file.name, lines); | |
| 524 | 538 | } |
| 525 | 539 | |
| 526 | 540 | TemplateList readBlock(bool *done) |
| 527 | 541 | { |
| 528 | - *done = true; | |
| 529 | 542 | TemplateList templates; |
| 530 | - if (!file.exists()) return templates; | |
| 543 | + foreach (const QString &line, QtUtils::readLines(file)) { | |
| 544 | + int splitIndex = line.lastIndexOf(' '); | |
| 545 | + if (splitIndex == -1) templates.append(File(line)); | |
| 546 | + else templates.append(File(line.mid(0, splitIndex), line.mid(splitIndex+1))); | |
| 547 | + } | |
| 548 | + *done = true; | |
| 549 | + return templates; | |
| 550 | + } | |
| 551 | + | |
| 552 | + void write(const Template &t) | |
| 553 | + { | |
| 554 | + QString line = t.file.name; | |
| 555 | + if (!label.isEmpty()) | |
| 556 | + line += " " + t.file.get<QString>(label); | |
| 557 | + lines.append(line); | |
| 558 | + } | |
| 559 | +}; | |
| 531 | 560 | |
| 561 | +BR_REGISTER(Gallery, txtGallery) | |
| 562 | +/*! | |
| 563 | + * \ingroup galleries | |
| 564 | + * \brief Treats each line as a call to File::flat() | |
| 565 | + * \author Josh Klontz \cite jklontz | |
| 566 | + */ | |
| 567 | +class flatGallery : public Gallery | |
| 568 | +{ | |
| 569 | + Q_OBJECT | |
| 570 | + QStringList lines; | |
| 571 | + | |
| 572 | + ~flatGallery() | |
| 573 | + { | |
| 574 | + if (!lines.isEmpty()) | |
| 575 | + QtUtils::writeFile(file.name, lines); | |
| 576 | + } | |
| 577 | + | |
| 578 | + TemplateList readBlock(bool *done) | |
| 579 | + { | |
| 580 | + TemplateList templates; | |
| 532 | 581 | foreach (const QString &line, QtUtils::readLines(file)) |
| 533 | 582 | templates.append(File(line)); |
| 534 | 583 | *done = true; |
| ... | ... | @@ -537,11 +586,11 @@ class txtGallery : public Gallery |
| 537 | 586 | |
| 538 | 587 | void write(const Template &t) |
| 539 | 588 | { |
| 540 | - lines.append(metadataKey.isEmpty() ? t.file.flat() : t.file.get<QString>(metadataKey)); | |
| 589 | + lines.append(t.file.flat()); | |
| 541 | 590 | } |
| 542 | 591 | }; |
| 543 | 592 | |
| 544 | -BR_REGISTER(Gallery, txtGallery) | |
| 593 | +BR_REGISTER(Gallery, flatGallery) | |
| 545 | 594 | |
| 546 | 595 | /*! |
| 547 | 596 | * \ingroup galleries |
| ... | ... | @@ -687,7 +736,9 @@ class dbGallery : public Gallery |
| 687 | 736 | if (!subset.isEmpty()) { |
| 688 | 737 | const QStringList &words = subset.split(":"); |
| 689 | 738 | QtUtils::checkArgsSize("Input", words, 2, 4); |
| 690 | - seed = QtUtils::toInt(words[0]); | |
| 739 | + if (words[0] == "train") seed = 0; | |
| 740 | + else if (words[0] == "test" ) seed = 1; | |
| 741 | + else seed = QtUtils::toInt(words[0]); | |
| 691 | 742 | if (words[1].startsWith('{') && words[1].endsWith('}')) { |
| 692 | 743 | foreach (const QString ®exp, words[1].mid(1, words[1].size()-2).split(",")) |
| 693 | 744 | metadataFields.append(QRegExp(regexp)); |
| ... | ... | @@ -813,16 +864,10 @@ class googleGallery : public Gallery |
| 813 | 864 | return templates; |
| 814 | 865 | } |
| 815 | 866 | |
| 816 | - void write(const Template &t) | |
| 867 | + void write(const Template &) | |
| 817 | 868 | { |
| 818 | - (void) t; | |
| 819 | 869 | qFatal("Not supported."); |
| 820 | 870 | } |
| 821 | - | |
| 822 | - void init() | |
| 823 | - { | |
| 824 | - // | |
| 825 | - } | |
| 826 | 871 | }; |
| 827 | 872 | |
| 828 | 873 | BR_REGISTER(Gallery, googleGallery) | ... | ... |
openbr/plugins/gui.cpp
| ... | ... | @@ -413,13 +413,13 @@ private: |
| 413 | 413 | |
| 414 | 414 | }; |
| 415 | 415 | |
| 416 | -class DisplayGUI : public QMainWindow | |
| 416 | +class GUIWindow : public QMainWindow | |
| 417 | 417 | { |
| 418 | 418 | Q_OBJECT |
| 419 | 419 | |
| 420 | 420 | public: |
| 421 | 421 | |
| 422 | - DisplayGUI(QWidget * parent = NULL) : QMainWindow(parent) | |
| 422 | + GUIWindow(QWidget * parent = NULL) : QMainWindow(parent) | |
| 423 | 423 | { |
| 424 | 424 | centralWidget = new QWidget(); |
| 425 | 425 | layout = new QHBoxLayout(); |
| ... | ... | @@ -592,8 +592,10 @@ public: |
| 592 | 592 | template<typename WindowType> |
| 593 | 593 | void initActual() |
| 594 | 594 | { |
| 595 | - if (!Globals->useGui) | |
| 595 | + if (!Globals->useGui) { | |
| 596 | + qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className()); | |
| 596 | 597 | return; |
| 598 | + } | |
| 597 | 599 | |
| 598 | 600 | if (displayBuffer) |
| 599 | 601 | delete displayBuffer; |
| ... | ... | @@ -795,7 +797,7 @@ class ElicitTransform : public ShowTransform |
| 795 | 797 | |
| 796 | 798 | Q_OBJECT |
| 797 | 799 | |
| 798 | - DisplayGUI *gui; | |
| 800 | + GUIWindow *gui; | |
| 799 | 801 | |
| 800 | 802 | public: |
| 801 | 803 | ElicitTransform() : ShowTransform() |
| ... | ... | @@ -835,14 +837,16 @@ public: |
| 835 | 837 | |
| 836 | 838 | void init() |
| 837 | 839 | { |
| 838 | - initActual<DisplayGUI>(); | |
| 840 | + initActual<GUIWindow>(); | |
| 839 | 841 | } |
| 840 | 842 | |
| 841 | 843 | template<typename GUIType> |
| 842 | 844 | void initActual() |
| 843 | 845 | { |
| 844 | - if (!Globals->useGui) | |
| 846 | + if (!Globals->useGui) { | |
| 847 | + qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className()); | |
| 845 | 848 | return; |
| 849 | + } | |
| 846 | 850 | |
| 847 | 851 | TimeVaryingTransform::init(); |
| 848 | 852 | ... | ... |
openbr/plugins/independent.cpp
| ... | ... | @@ -9,13 +9,14 @@ using namespace cv; |
| 9 | 9 | namespace br |
| 10 | 10 | { |
| 11 | 11 | |
| 12 | -static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable, const QStringList &gallery) | |
| 12 | +static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable, const QStringList &gallery, const QStringList &subjects) | |
| 13 | 13 | { |
| 14 | 14 | // Return early when no downsampling is required |
| 15 | 15 | if ((classes == std::numeric_limits<int>::max()) && |
| 16 | 16 | (instances == std::numeric_limits<int>::max()) && |
| 17 | 17 | (fraction >= 1) && |
| 18 | - (gallery.isEmpty())) | |
| 18 | + (gallery.isEmpty()) && | |
| 19 | + (subjects.isEmpty())) | |
| 19 | 20 | return templates; |
| 20 | 21 | |
| 21 | 22 | const bool atLeast = instances < 0; |
| ... | ... | @@ -67,6 +68,11 @@ static TemplateList Downsample(const TemplateList &templates, int classes, int i |
| 67 | 68 | if (!gallery.contains(downsample[i].file.get<QString>("Gallery"))) |
| 68 | 69 | downsample.removeAt(i); |
| 69 | 70 | |
| 71 | + if (!subjects.isEmpty()) | |
| 72 | + for (int i=downsample.size()-1; i>=0; i--) | |
| 73 | + if (subjects.contains(downsample[i].file.get<QString>(inputVariable))) | |
| 74 | + downsample.removeAt(i); | |
| 75 | + | |
| 70 | 76 | return downsample; |
| 71 | 77 | } |
| 72 | 78 | |
| ... | ... | @@ -79,12 +85,15 @@ class DownsampleTrainingTransform : public Transform |
| 79 | 85 | Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false) |
| 80 | 86 | Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) |
| 81 | 87 | Q_PROPERTY(QStringList gallery READ get_gallery WRITE set_gallery RESET reset_gallery STORED false) |
| 88 | + Q_PROPERTY(QStringList subjects READ get_subjects WRITE set_subjects RESET reset_subjects STORED false) | |
| 82 | 89 | BR_PROPERTY(br::Transform*, transform, NULL) |
| 83 | 90 | BR_PROPERTY(int, classes, std::numeric_limits<int>::max()) |
| 84 | 91 | BR_PROPERTY(int, instances, std::numeric_limits<int>::max()) |
| 85 | 92 | BR_PROPERTY(float, fraction, 1) |
| 86 | 93 | BR_PROPERTY(QString, inputVariable, "Label") |
| 87 | 94 | BR_PROPERTY(QStringList, gallery, QStringList()) |
| 95 | + BR_PROPERTY(QStringList, subjects, QStringList()) | |
| 96 | + | |
| 88 | 97 | |
| 89 | 98 | void project(const Template & src, Template & dst) const |
| 90 | 99 | { |
| ... | ... | @@ -97,7 +106,7 @@ class DownsampleTrainingTransform : public Transform |
| 97 | 106 | if (!transform || !transform->trainable) |
| 98 | 107 | return; |
| 99 | 108 | |
| 100 | - TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable, gallery); | |
| 109 | + TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable, gallery, subjects); | |
| 101 | 110 | |
| 102 | 111 | transform->train(downsampled); |
| 103 | 112 | } | ... | ... |
openbr/plugins/stream.cpp
| ... | ... | @@ -1285,11 +1285,18 @@ public: |
| 1285 | 1285 | qWarning("Attempted to train untrainable transform, nothing will happen."); |
| 1286 | 1286 | return; |
| 1287 | 1287 | } |
| 1288 | + QList<TemplateList> separated; | |
| 1289 | + foreach (const TemplateList & list, data) { | |
| 1290 | + foreach(const Template & t, list) { | |
| 1291 | + separated.append(TemplateList()); | |
| 1292 | + separated.last().append(t); | |
| 1293 | + } | |
| 1294 | + } | |
| 1288 | 1295 | |
| 1289 | 1296 | for (int i=0; i < transforms.size(); i++) { |
| 1290 | 1297 | // OK we have a trainable transform, we need to get input data for it. |
| 1291 | 1298 | if (transforms[i]->trainable) { |
| 1292 | - QList<TemplateList> copy = data; | |
| 1299 | + QList<TemplateList> copy = separated; | |
| 1293 | 1300 | // Project from the start to the trainable stage. |
| 1294 | 1301 | subProject(copy,i); |
| 1295 | 1302 | |
| ... | ... | @@ -1308,7 +1315,7 @@ public: |
| 1308 | 1315 | in.append(src); |
| 1309 | 1316 | TemplateList out; |
| 1310 | 1317 | CompositeTransform::project(in,out); |
| 1311 | - dst = out.first(); | |
| 1318 | + if (!out.isEmpty()) dst = out.first(); | |
| 1312 | 1319 | if (out.size() > 1) |
| 1313 | 1320 | qDebug("Returning first output template only"); |
| 1314 | 1321 | } |
| ... | ... | @@ -1319,7 +1326,7 @@ public: |
| 1319 | 1326 | in.append(src); |
| 1320 | 1327 | TemplateList out; |
| 1321 | 1328 | projectUpdate(in,out); |
| 1322 | - dst = out.first(); | |
| 1329 | + if (!out.isEmpty()) dst = out.first(); | |
| 1323 | 1330 | if (out.size() > 1) |
| 1324 | 1331 | qDebug("Returning first output template only"); |
| 1325 | 1332 | } | ... | ... |
scripts/brpy/__init__.py
| ... | ... | @@ -58,7 +58,7 @@ def init_brpy(br_loc='/usr/local/lib'): |
| 58 | 58 | br.br_eval.argtypes = _string_args(3) |
| 59 | 59 | br.br_eval.restype = c_float |
| 60 | 60 | br.br_eval_classification.argtypes = _string_args(4) |
| 61 | - br.br_eval_clustering.argtypes = _string_args(2) | |
| 61 | + br.br_eval_clustering.argtypes = _string_args(3) | |
| 62 | 62 | br.br_eval_detection.argtypes = _string_args(3) |
| 63 | 63 | br.br_eval_detection.restype = c_float |
| 64 | 64 | br.br_eval_landmarking.argtypes = _string_args(3) + [c_int, c_int] |
| ... | ... | @@ -143,7 +143,8 @@ def init_brpy(br_loc='/usr/local/lib'): |
| 143 | 143 | br.br_make_gallery.restype = c_void_p |
| 144 | 144 | br.br_load_from_gallery.argtypes = [c_void_p] |
| 145 | 145 | br.br_load_from_gallery.restype = c_void_p |
| 146 | - br.br_add_to_gallery.argtypes = [c_void_p, c_void_p] | |
| 146 | + br.br_add_template_to_gallery.argtypes = [c_void_p, c_void_p] | |
| 147 | + br.br_add_template_list_to_gallery.argtypes = [c_void_p, c_void_p] | |
| 147 | 148 | br.br_close_gallery.argtypes = [c_void_p] |
| 148 | 149 | |
| 149 | 150 | return br | ... | ... |