diff --git a/core/tests/facesengine/face_cluster.cpp b/core/tests/facesengine/face_cluster.cpp index f58edc5122..dfebe112a0 100644 --- a/core/tests/facesengine/face_cluster.cpp +++ b/core/tests/facesengine/face_cluster.cpp @@ -1,413 +1,413 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-08-10 * Description : CLI tool to test and verify clustering for Face Recognition * * Copyright (C) 2019 by Thanh Trung Dinh * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // C++ includes #include #include // Qt includes #include #include #include #include #include #include #include // Local includes #include "dimg.h" #include "facescansettings.h" #include "facedetector.h" #include "recognitiondatabase.h" #include "coredbaccess.h" #include "dbengineparameters.h" using namespace Digikam; // -------------------------------------------------------------------------------------------------- /** * Function to return the * intersection vector of v1 and v2 */ void intersection(const std::vector& v1, const std::vector& v2, std::vector& vout) { // Find the intersection of the two sets std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::inserter(vout, vout.begin())); } /** * Function to return the Jaccard distance of two vectors */ double jaccard_distance(const std::vector& v1, const std::vector& v2) { // Sizes of both the sets double size_v1 = v1.size(); double size_v2 = v2.size(); // Get the intersection set std::vector intersect; intersection(v1, v2, intersect); // Size of the intersection set double size_in = intersect.size(); // Calculate the Jaccard index // using the formula double jaccard_index = size_in / (size_v1 + size_v2 - size_in); // Calculate the Jaccard distance // using the formula double jaccard_dist = 1 - jaccard_index; // Return the Jaccard distance return jaccard_dist; } QStringList toPaths(char** argv, int startIndex, int argc) { QStringList files; for (int i = startIndex ; i < argc ; ++i) { files << QString::fromLatin1(argv[i]); } return files; } QList toImages(const QStringList& paths) { QList images; foreach (const QString& path, paths) { images << QImage(path); } return images; } int prepareForTrain(QString datasetPath, QStringList& images, std::vector& testClusteredIndices) { if (!datasetPath.endsWith(QLatin1String("/"))) { datasetPath.append(QLatin1String("/")); } QDir testSet(datasetPath); QStringList subjects = testSet.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); int nbOfClusters = subjects.size(); qDebug() << "Number of clusters to be defined" << nbOfClusters; for (int i = 1 ; i <= nbOfClusters ; ++i) { QString subjectPath = QString::fromLatin1("%1%2") .arg(datasetPath) .arg(subjects.takeFirst()); QDir subjectDir(subjectPath); QStringList files = subjectDir.entryList(QDir::Files); unsigned int nbOfFacesPerClusters = files.size(); for (unsigned j = 1 ; j <= nbOfFacesPerClusters ; ++j) { QString path = QString::fromLatin1("%1/%2").arg(subjectPath) .arg(files.takeFirst()); testClusteredIndices.push_back(i - 1); images << path; } } qDebug() << "nbOfClusters (prepareForTrain) " << nbOfClusters; return nbOfClusters; } QList processFaceDetection(const QString& imagePath, FaceDetector detector) { QList detectedFaces = detector.detectFaces(imagePath); qDebug() << "(Input CV) Found " << detectedFaces.size() << " faces"; return detectedFaces; } QList retrieveFaces(const QList& images, const QList& rects) { QList faces; unsigned index = 0; foreach (const QRectF& rect, rects) { DImg temp(images.at(index)); faces << temp.copyQImage(rect); ++index; } return faces; } void createClustersFromClusterIndices(const std::vector& clusteredIndices, QList>& clusters) { int nbOfClusters = 0; for (size_t i = 0 ; i < clusteredIndices.size() ; ++i) { int nb = clusteredIndices[i]; if (nb > nbOfClusters) { nbOfClusters = nb; } } nbOfClusters++; for (int i = 0 ; i < nbOfClusters ; ++i) { clusters << std::vector(); } qDebug() << "nbOfClusters " << clusters.size(); for (int i = 0 ; i < (int)clusteredIndices.size() ; ++i) { clusters[clusteredIndices[i]].push_back(i); } } void verifyClusteringResults(const std::vector& clusteredIndices, const std::vector& testClusteredIndices, const QStringList& dataset, QStringList& falsePositiveCases) { QList> clusters, testClusters; createClustersFromClusterIndices(clusteredIndices, clusters); createClustersFromClusterIndices(testClusteredIndices, testClusters); std::set falsePositivePoints; int testClustersSize = testClusters.size(); std::vector visited(testClustersSize, 1.0); std::vector> lastVisit(testClustersSize, std::set{}); for (int i = 0 ; i < testClustersSize ; ++i) { std::vector refSet = testClusters.at(i); double minDist = 1.0; int indice = 0; for (int j = 0 ; j < clusters.size() ; ++j) { double dist = jaccard_distance(refSet, clusters.at(j)); if (dist < minDist) { indice = j; minDist = dist; } } qDebug() << "testCluster " << i << " with group " << indice; std::vector similarSet = clusters.at(indice); if (minDist < visited[indice]) { visited[indice] = minDist; std::set lastVisitSet = lastVisit[indice]; std::set newVisitSet; - std::set_symmetric_difference(refSet.begin(), refSet.end(), similarSet.begin(), similarSet.end(), + std::set_symmetric_difference(refSet.begin(), refSet.end(), similarSet.begin(), similarSet.end(), std::inserter(newVisitSet, newVisitSet.begin())); for (int elm: lastVisitSet) { falsePositivePoints.erase(elm); } lastVisit[indice] = newVisitSet; falsePositivePoints.insert(newVisitSet.begin(), newVisitSet.end()); } else { - std::set_intersection(refSet.begin(), refSet.end(), similarSet.begin(), similarSet.end(), + std::set_intersection(refSet.begin(), refSet.end(), similarSet.begin(), similarSet.end(), std::inserter(falsePositivePoints, falsePositivePoints.begin())); } } for (auto indx: falsePositivePoints) { falsePositiveCases << dataset[indx]; } } // -------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); app.setApplicationName(QString::fromLatin1("digikam")); // for DB init. // Options for commandline parser QCommandLineParser parser; parser.addOption(QCommandLineOption(QLatin1String("db"), QLatin1String("Faces database"), QLatin1String("path to db folder"))); parser.addHelpOption(); parser.process(app); // Parse arguments bool optionErrors = false; if (parser.optionNames().empty()) { qWarning() << "No options!!!"; optionErrors = true; } else if (!parser.isSet(QLatin1String("db"))) { qWarning() << "Missing database for test!!!"; optionErrors = true; } if (optionErrors) { parser.showHelp(); return 1; } QString facedb = parser.value(QLatin1String("db")); // Init config for digiKam DbEngineParameters prm = DbEngineParameters::parametersFromConfig(); CoreDbAccess::setParameters(prm, CoreDbAccess::MainApplication); RecognitionDatabase db; db.activeFaceRecognizer(RecognitionDatabase::RecognizeAlgorithm::DNN); db.setRecognizerThreshold(0.91F); // This is sensitive for the performance of face clustering // Construct test set, data set QStringList dataset; std::vector testClusteredIndices; int nbOfClusters = prepareForTrain(facedb, dataset, testClusteredIndices); // Init FaceDetector used for detecting faces and bounding box // before recognizing FaceDetector detector; // Evaluation metrics unsigned totalClustered = 0; unsigned elapsedClustering = 0; QStringList undetectedFaces; QList detectedFaces; QList bboxes; QList rawImages = toImages(dataset); foreach (const QImage& image, rawImages) { QString imagePath = dataset.takeFirst(); QList detectedBoundingBox = processFaceDetection(imagePath, detector); if (detectedBoundingBox.size()) { detectedFaces << image; bboxes << detectedBoundingBox.first(); dataset << imagePath; ++totalClustered; } else { undetectedFaces << imagePath; } } std::vector clusteredIndices(dataset.size(), -1); QList faces = retrieveFaces(detectedFaces, bboxes); QElapsedTimer timer; timer.start(); db.clusterFaces(faces, clusteredIndices, dataset, nbOfClusters); elapsedClustering += timer.elapsed(); // Verify clustering QStringList falsePositiveCases; verifyClusteringResults(clusteredIndices, testClusteredIndices, dataset, falsePositiveCases); // Display results unsigned nbUndetectedFaces = undetectedFaces.size(); qDebug() << "\n" << nbUndetectedFaces << " / " << dataset.size() + nbUndetectedFaces << " (" << float(nbUndetectedFaces) / (dataset.size() + nbUndetectedFaces) * 100 << "%)" << " faces cannot be detected"; foreach (const QString& path, undetectedFaces) { qDebug() << path; } unsigned nbOfFalsePositiveCases = falsePositiveCases.size(); qDebug() << "\nFalse positive cases"; qDebug() << "\n" << nbOfFalsePositiveCases << " / " << dataset.size() << " (" << float(nbOfFalsePositiveCases*100) / dataset.size()<< "%)" << " faces were wrongly clustered"; foreach (const QString& imagePath, falsePositiveCases) { qDebug() << imagePath; } qDebug() << "\n Time for clustering " << elapsedClustering << " ms"; return 0; } diff --git a/core/tests/facesengine/face_rec.cpp b/core/tests/facesengine/face_rec.cpp index 646a667a14..285298aafc 100644 --- a/core/tests/facesengine/face_rec.cpp +++ b/core/tests/facesengine/face_rec.cpp @@ -1,539 +1,539 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-05-15 * Description : CLI tool to test and verify Face Recognition * NOTE: This tool integrates the whole Face Management * work flow, especially designed to verify and benchmark * Face Recognition algorithm. It is adapted from recognize.cpp * developed by Aditya Bhatt. * * Copyright (C) 2019 by Thanh Trung Dinh * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "dimg.h" #include "facescansettings.h" #include "facedetector.h" #include "recognitiondatabase.h" #include "coredbaccess.h" #include "dbengineparameters.h" using namespace Digikam; // -------------------------------------------------------------------------------------------------- QStringList toPaths(char** argv, int startIndex, int argc) { QStringList files; for (int i = startIndex ; i < argc ; ++i) { files << QString::fromLatin1(argv[i]); } return files; } QList toImages(const QStringList& paths) { QList images; foreach (const QString& path, paths) { images << QImage(path); } return images; } void prepareForTrain(const QString& testSetPath, QMap& testset, QMap& trainingset, double ratio, unsigned int nbOfSamples, unsigned int& nbOfIdentities) { QDir testSet(testSetPath); QStringList subjects = testSet.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); qDebug() << nbOfSamples << ", " << nbOfIdentities; if (nbOfIdentities == 0) { nbOfIdentities = subjects.size(); } for (unsigned i = 1 ; i <= nbOfIdentities ; ++i) { QString subjectPath = QString::fromLatin1("%1%2").arg(testSetPath) .arg(subjects.takeFirst()); QDir subjectDir(subjectPath); QStringList files = subjectDir.entryList(QDir::Files); unsigned int nbOfSamplePerIdentity = (nbOfSamples == 0) ? files.size() : nbOfSamples; for (unsigned j = 1 ; j <= nbOfSamplePerIdentity ; ++j) { QString path = QString::fromLatin1("%1/%2").arg(subjectPath) .arg(files.takeFirst()); if (j <= static_cast(qRound(nbOfSamplePerIdentity * ratio))) { trainingset[i] << path; qDebug() << "training " << path; } else { testset[i] << path; qDebug() << "test " << path; } } } } QImage scaleForDetection(const DImg& image, FaceDetector& detector) { int recommendedSize = detector.recommendedImageSize(image.size()); if (qMax(image.width(), image.height()) > (uint)recommendedSize) { return image.smoothScale(recommendedSize, recommendedSize, Qt::KeepAspectRatio).copyQImage(); } return image.copyQImage(); } QList processFaceDetection(const QImage& image, FaceDetector& detector) { DImg img(image); QImage detectionImage = scaleForDetection(img, detector); QList detectedFaces = detector.detectFaces(detectionImage, img.originalSize()); qDebug() << "Found " << detectedFaces.size() << " faces"; return detectedFaces; } QList processFaceDetection(const QString& imagePath, FaceDetector& detector) { QList detectedFaces = detector.detectFaces(imagePath); qDebug() << "(Input CV) Found " << detectedFaces.size() << " faces"; return detectedFaces; } QImage retrieveFace(const DImg& image, const QList& rects) { if (rects.size() > 1) { qWarning() << "More than 1 face found in image, strange for our test set!!!"; assert(0); } QRectF rect = rects.first(); QImage face = image.copyQImage(rect); return face; } QList retrieveFaces(const QList& images, const QList& rects) { QList faces; unsigned index = 0; foreach (const QRectF& rect, rects) { DImg temp(images.at(index)); faces << temp.copyQImage(rect); ++index; } return faces; } // -------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); app.setApplicationName(QString::fromLatin1("digikam")); // for DB init. // Options for commandline parser QCommandLineParser parser; parser.addOption(QCommandLineOption(QLatin1String("db"), QLatin1String("Faces database"), QLatin1String("path to db folder"))); parser.addOption(QCommandLineOption(QLatin1String("rs"), QLatin1String("Split ratio (test set / whole set)"), QLatin1String("decimal"))); parser.addOption(QCommandLineOption(QLatin1String("ts"), QLatin1String("Test set folder"), QLatin1String("path relative to db folder"))); parser.addOption(QCommandLineOption(QLatin1String("ds"), QLatin1String("Training set (dev set) folder"), QLatin1String("path relative to db folder"))); parser.addOption(QCommandLineOption(QLatin1String("ni"), QLatin1String("Number of total objects"), QLatin1String("nbIdentities"))); parser.addOption(QCommandLineOption(QLatin1String("ns"), QLatin1String("Number of samples per object"), QLatin1String("nbSamples"))); parser.addOption(QCommandLineOption(QLatin1String("as"), QLatin1String("Option to run test on the entire set"))); parser.addHelpOption(); parser.process(app); // Parse arguments bool optionErrors = false; if (parser.optionNames().empty()) { qWarning() << "NO options!!!"; optionErrors = true; } else if (!parser.isSet(QLatin1String("db"))) { qWarning() << "MISSING database for test!!!"; optionErrors = true; } else if (!parser.isSet(QLatin1String("as")) && (!parser.isSet(QLatin1String("ni")) || !parser.isSet(QLatin1String("ns")))) { qWarning() << "UNKNOWN training set / test set separation!!!"; optionErrors = true; } else if (parser.isSet(QLatin1String("ts")) && !parser.isSet(QLatin1String("ds"))) { qWarning() << "UNKNOWN Dev set!!!"; optionErrors = true; } else if (parser.isSet(QLatin1String("ds")) && !parser.isSet(QLatin1String("ts"))) { qWarning() << "UNKNOWN Test set!!!"; optionErrors = true; } if (optionErrors) { parser.showHelp(); return 1; } QString facedb = parser.value(QLatin1String("db")); unsigned int nbOfSamples = 0; unsigned int nbOfIdentities = 0; if (!parser.isSet(QLatin1String("as"))) { nbOfSamples = parser.value(QLatin1String("ns")).toUInt(); nbOfIdentities = parser.value(QLatin1String("ni")).toUInt(); } double ratio = 0; if (parser.isSet(QLatin1String("rs"))) { ratio = parser.value(QLatin1String("rs")).toDouble(); } // Init config for digiKam DbEngineParameters prm = DbEngineParameters::parametersFromConfig(); CoreDbAccess::setParameters(prm, CoreDbAccess::MainApplication); RecognitionDatabase db; db.activeFaceRecognizer(RecognitionDatabase::RecognizeAlgorithm::DNN); db.setRecognizerThreshold(0.5); // Construct training set, test set QMap testset, trainingset; if (ratio > 0) { prepareForTrain(facedb, testset, trainingset, ratio, nbOfSamples, nbOfIdentities); } else { QString testsetFolder = parser.value(QLatin1String("ts")); QString trainingsetFoler = parser.value(QLatin1String("ds")); // TODO: Overload of prepareForTrain() to create training set and test set here } // Create IDs QMap idMap; for (unsigned i = 1 ; i <= nbOfIdentities ; ++i) { QMap attributes; attributes[QLatin1String("name")] = QString::number(i); idMap[i] = db.addIdentityDebug(attributes); } db.createDNNDebug(); // Create OpenCVDNNFaceRecognizer instance without loading recognition database // Init FaceDetector used for detecting faces and bounding box // before recognizing FaceDetector detector; // Evaluation metrics unsigned int correct = 0, notRecognized = 0, falsePositive = 0, totalTrained = 0, totalRecognized = 0; unsigned int elapsedTraining = 0, elapsedTesting = 0; unsigned int detectingTime = 0; /* * // Without using detector for (QMap::const_iterator it = trainingset.constBegin() ; it != trainingset.constEnd() ; ++it) { Identity identity = db.findIdentity(QString::fromLatin1("name"), QString::number(it.key())); if (identity.isNull()) { qDebug() << "Identity management failed for person " << it.key(); } QList images = toImages(it.value()); qDebug() << "Training directory " << it.key(); db.activeFaceRecognizer(RecognitionDatabase::RecognizeAlgorithm::DNN); db.train(identity, images, trainingContext); totalTrained += images.size(); } elapsedTraining = timer.restart(); for (QMap::const_iterator it = testset.constBegin() ; it != testset.constEnd() ; ++it) { Identity identity = idMap.value(it.key()); QList images = toImages(it.value()); QList results = db.recognizeFaces(images); qDebug() << "Result for " << it.value().first() << " is identity " << results.first().id(); foreach (const Identity& foundId, results) { if (foundId.isNull()) { ++notRecognized; } else if (foundId == identity) { ++correct; } else { ++falsePositive; } } totalRecognized += images.size(); } */ QStringList undetectedTrainedFaces; QStringList undetectedTestedFaces; QStringList falsePositiveFaces; QLatin1String trainingContext("Debug"); for (QMap::const_iterator it = trainingset.constBegin() ; it != trainingset.constEnd() ; ++it) { Identity identity = idMap.value(it.key()); QStringList imagePaths = it.value(); QList detectedFaces; QList bboxes; QList rawImages = toImages(imagePaths); qDebug() << "Training directory " << it.key(); foreach (const QImage& image, rawImages) { QString imagePath = imagePaths.takeFirst(); // Start timing for benchmark face detection QElapsedTimer timer; timer.start(); QList detectedBoundingBox = processFaceDetection(imagePath, detector); detectingTime += timer.elapsed(); if (detectedBoundingBox.size()) { detectedFaces << image; bboxes << detectedBoundingBox.first(); ++totalTrained; } else { undetectedTrainedFaces << imagePath; } } QList faces = retrieveFaces(detectedFaces, bboxes); // Start timing for benchmark training QElapsedTimer timer; timer.start(); db.train(identity, faces, trainingContext); elapsedTraining += timer.elapsed(); } for (QMap::const_iterator it = testset.constBegin() ; it != testset.constEnd() ; ++it) { Identity identity = idMap.value(it.key()); QList rawImages = toImages(it.value()); QStringList imagePaths = it.value(); QList detectedFaces; QList bboxes; foreach (const QImage& image, rawImages) { QString imagePath = imagePaths.takeFirst(); // Start timing for benchmark face detection QElapsedTimer timer; timer.start(); QList detectedBoundingBox = processFaceDetection(imagePath, detector); detectingTime += timer.elapsed(); if (detectedBoundingBox.size()) { detectedFaces << image; bboxes << detectedBoundingBox.first(); ++totalRecognized; } else { undetectedTestedFaces << imagePath; } imagePaths << imagePath; } QList faces = retrieveFaces(detectedFaces, bboxes); // Start timing for benchmark testing QElapsedTimer timer; timer.start(); QList results = db.recognizeFaces(faces); elapsedTesting += timer.elapsed(); // qDebug() << "Result for " << it.value().first() << " is identity " << results.first().id(); foreach (const Identity& foundId, results) { QString imagePath = imagePaths.takeFirst(); if (foundId.isNull()) { ++notRecognized; } else if (foundId == identity) { ++correct; } else { ++falsePositive; falsePositiveFaces << QString::fromLatin1("Image at %1 with identity %2") .arg(imagePath) .arg(foundId.id()); } } // totalRecognized += images.size(); } unsigned nbUndetectedTrainedFaces = undetectedTrainedFaces.size(); - qDebug() << "\n" << nbUndetectedTrainedFaces << " / " << totalTrained + nbUndetectedTrainedFaces - << " (" << float(nbUndetectedTrainedFaces) / (totalTrained + nbUndetectedTrainedFaces) * 100 << "%)" + qDebug() << "\n" << nbUndetectedTrainedFaces << " / " << totalTrained + nbUndetectedTrainedFaces + << " (" << float(nbUndetectedTrainedFaces) / (totalTrained + nbUndetectedTrainedFaces) * 100 << "%)" << " faces cannot be detected for training"; foreach (const QString& path, undetectedTrainedFaces) { qDebug() << path; } if (totalTrained) { qDebug() << "Training " << totalTrained << "of " << nbOfIdentities << " different objects took " << elapsedTraining << " ms, " << ((float)elapsedTraining/totalTrained) << " ms per image"; } unsigned nbUndetectedTestedFaces = undetectedTestedFaces.size(); - qDebug() << "\n" << nbUndetectedTestedFaces << " / " << totalRecognized + nbUndetectedTestedFaces - << " (" << float(nbUndetectedTestedFaces) / (totalRecognized + nbUndetectedTestedFaces) * 100 << "%)" + qDebug() << "\n" << nbUndetectedTestedFaces << " / " << totalRecognized + nbUndetectedTestedFaces + << " (" << float(nbUndetectedTestedFaces) / (totalRecognized + nbUndetectedTestedFaces) * 100 << "%)" << " faces cannot be detected for testing"; foreach (const QString& path, undetectedTestedFaces) { qDebug() << path; } if (totalRecognized) { qDebug() << "Recognition test performed on " << totalRecognized << " of " << nbOfIdentities << " different objects took " << elapsedTesting << " ms, " << ((float)elapsedTesting/totalRecognized) << " ms per image"; qDebug() << correct << " / " << totalRecognized << " (" << (float(correct) / totalRecognized*100) << "%) were correctly recognized"; qDebug() << falsePositive << " / " << totalRecognized << " (" << (float(falsePositive) / totalRecognized*100) << "%) were falsely assigned to an identity (false positive)"; qDebug() << notRecognized << " / " << totalRecognized << " (" << (float(notRecognized) / totalRecognized*100) << "%) were not recognized"; } else { qDebug() << "No face recognized"; } qDebug() << "\nFalse positive faces"; foreach (const QString& path, falsePositiveFaces) { qDebug() << path; } qDebug() << "\n Average time of face detection " << detectingTime*1.0 / (totalTrained + nbUndetectedTrainedFaces + totalRecognized + nbUndetectedTestedFaces) << "ms"; return 0; } diff --git a/core/tests/metadataengine/createxmpsidecartest.cpp b/core/tests/metadataengine/createxmpsidecartest.cpp index dea50f8fec..62c58b591d 100644 --- a/core/tests/metadataengine/createxmpsidecartest.cpp +++ b/core/tests/metadataengine/createxmpsidecartest.cpp @@ -1,90 +1,90 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-27 * Description : an unit-test to test XMP sidecar creation with DMetadata * * Copyright (C) 2010 by Jakob Malm * Copyright (C) 2010-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "createxmpsidecartest.h" // Qt includes #include QTEST_MAIN(CreateXmpSidecarTest) void CreateXmpSidecarTest::testCreateXmpSidecar() { MetaEngineSettingsContainer settings; settings.metadataWritingMode = DMetadata::WRITE_TO_SIDECAR_ONLY; createXmpSidecar(m_originalImageFolder + QLatin1String("2015-07-22_00001.JPG"), settings); createXmpSidecar(m_originalImageFolder + QLatin1String("IMG_2520.CR2"), settings); } void CreateXmpSidecarTest::createXmpSidecar(const QString& file, const MetaEngineSettingsContainer& settings) { qDebug() << "File to process: " << file; QString path = m_tempDir.filePath(QFileInfo(file).fileName().trimmed()); QString pathXmp = path + QLatin1String(".xmp"); qDebug() << "Temporary target file: " << path; bool ret = !path.isNull(); QVERIFY(ret); // Copy image file in temporary dir. QFile::remove(path); QFile target(file); ret = target.copy(path); QVERIFY(ret); // Check if no xmp sidecar relevant is present. qDebug() << "Temporary XMP target file to create:" << pathXmp; ret = !pathXmp.isNull(); QVERIFY(ret); QFile::remove(pathXmp); // Export metadata from image to a fresh xmp sidecar. DMetadata meta; meta.setSettings(settings); ret = meta.load(path); QVERIFY(ret); ret = meta.save(path); QVERIFY(ret); QFile sidecar(pathXmp); ret = sidecar.exists(); QVERIFY(ret); - + qDebug() << "Sidecar" << pathXmp << "size :" << sidecar.size(); // Check if xmp sidecar are created and can be loaded DMetadata meta2; ret = meta2.load(pathXmp); QVERIFY(ret); } diff --git a/core/tests/metadataengine/patchpreviewtest.cpp b/core/tests/metadataengine/patchpreviewtest.cpp index a95cbe8904..29c43a391a 100644 --- a/core/tests/metadataengine/patchpreviewtest.cpp +++ b/core/tests/metadataengine/patchpreviewtest.cpp @@ -1,116 +1,116 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-10-25 * Description : An unit-test to extract preview and patch with DMetadata. * This stage is used by Export tools. * * Copyright (C) 2019-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "patchpreviewtest.h" // Qt includes #include #include // Local includes #include "dpluginloader.h" #include "previewloadthread.h" QTEST_MAIN(PatchPreviewTest) void PatchPreviewTest::testExtractPreviewAndFixMetadata() { patchPreview(m_originalImageFolder + QLatin1String("IMG_2520.CR2"), true, 1024, 100); // See bug #400140 } void PatchPreviewTest::patchPreview(const QString& file, bool rescale, int maxDim, int imageQuality) { qDebug() << "File to process:" << file; - QString path = m_tempDir.filePath(QFileInfo(file).fileName().trimmed()) + + QString path = m_tempDir.filePath(QFileInfo(file).fileName().trimmed()) + QLatin1String(".jpg"); qDebug() << "Temporary target file:" << path; bool ret = !path.isNull(); QVERIFY(ret); // Load preview from original image. QImage image = PreviewLoadThread::loadHighQualitySynchronously(file).copyQImage(); if (image.isNull()) { image.load(file); } ret = image.isNull(); QVERIFY(!ret); if (rescale && (image.width() > maxDim || image.height() > maxDim)) { image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } // Save preview in temporary directory. ret = image.save(path, "JPEG", imageQuality); QVERIFY(ret); // Load metadata from original image. DMetadata meta; ret = meta.load(file); QVERIFY(ret); QByteArray exif = meta.getExifEncoded(); QByteArray iptc = meta.getIptc(); QByteArray xmp = meta.getXmp(); // Backport metadata to preview file. meta.load(path); QVERIFY(ret); meta.setExif(exif); meta.setIptc(iptc); meta.setXmp(xmp); meta.setItemDimensions(image.size()); meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); ret = meta.applyChanges(true); QVERIFY(ret); } void PatchPreviewTest::initTestCase() { AbstractUnitTest::initTestCase(); QDir dir(QFINDTESTDATA("../../dplugins/dimg")); qputenv("DK_PLUGIN_PATH", dir.canonicalPath().toUtf8()); DPluginLoader::instance()->init(); } void PatchPreviewTest::cleanupTestCase() { AbstractUnitTest::cleanupTestCase(); DPluginLoader::instance()->cleanUp(); } diff --git a/core/tests/metadataengine/printtagslisttest.h b/core/tests/metadataengine/printtagslisttest.h index 46623df339..7eaff8eea8 100644 --- a/core/tests/metadataengine/printtagslisttest.h +++ b/core/tests/metadataengine/printtagslisttest.h @@ -1,51 +1,51 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-07-12 * Description : An unit-test to print all available metadata tags provided by Exiv2. * * Copyright (C) 2009-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_PRINT_TAGS_LIST_TEST_H #define DIGIKAM_PRINT_TAGS_LIST_TEST_H // Local includes #include "abstractunittest.h" #include "metaenginesettingscontainer.h" #include "dmetadatasettingscontainer.h" using namespace Digikam; class PrintTagsListTest : public AbstractUnitTest { Q_OBJECT private: - + void parseTagsList(const DMetadata::TagsMap& tags); private Q_SLOTS: void testPrintAllAvailableStdExifTags(); void testPrintAllAvailableMakernotesTags(); void testPrintAllAvailableIptcTags(); void testPrintAllAvailableXmpTags(); }; #endif // DIGIKAM_PRINT_TAGS_LIST_TEST_H