"Fossies" - the Fresh Open Source Software Archive

Member "digikam-6.3.0/core/utilities/maintenance/facesdetector.cpp" (4 Sep 2019, 11123 Bytes) of package /linux/misc/digikam-6.3.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "facesdetector.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.2.0_vs_6.3.0.

    1 /* ============================================================
    2  *
    3  * This file is a part of digiKam project
    4  * https://www.digikam.org
    5  *
    6  * Date        : 2010-07-18
    7  * Description : batch face detection
    8  *
    9  * Copyright (C) 2010      by Aditya Bhatt <adityabhatt1991 at gmail dot com>
   10  * Copyright (C) 2010-2019 by Gilles Caulier <caulier dot gilles at gmail dot com>
   11  * Copyright (C) 2012      by Andi Clemens <andi dot clemens at gmail dot com>
   12  *
   13  * This program is free software; you can redistribute it
   14  * and/or modify it under the terms of the GNU General
   15  * Public License as published by the Free Software Foundation;
   16  * either version 2, or (at your option)
   17  * any later version.
   18  *
   19  * This program is distributed in the hope that it will be useful,
   20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22  * GNU General Public License for more details.
   23  *
   24  * ============================================================ */
   25 
   26 #include "facesdetector.h"
   27 
   28 // Qt includes
   29 
   30 #include <QClipboard>
   31 #include <QVBoxLayout>
   32 #include <QTimer>
   33 #include <QIcon>
   34 #include <QPushButton>
   35 #include <QApplication>
   36 #include <QTextEdit>
   37 
   38 // KDE includes
   39 
   40 #include <kconfiggroup.h>
   41 #include <klocalizedstring.h>
   42 #include <ksharedconfig.h>
   43 
   44 // Local includes
   45 
   46 #include "recognitiondatabase.h"
   47 #include "digikam_debug.h"
   48 #include "coredb.h"
   49 #include "album.h"
   50 #include "albummanager.h"
   51 #include "albumpointer.h"
   52 #include "facepipeline.h"
   53 #include "facescansettings.h"
   54 #include "iteminfo.h"
   55 #include "iteminfojob.h"
   56 
   57 namespace Digikam
   58 {
   59 
   60 class Q_DECL_HIDDEN BenchmarkMessageDisplay : public QWidget
   61 {
   62 public:
   63 
   64     explicit BenchmarkMessageDisplay(const QString& richText)
   65         : QWidget(nullptr)
   66     {
   67         setAttribute(Qt::WA_DeleteOnClose);
   68 
   69         QVBoxLayout* const vbox     = new QVBoxLayout;
   70         QTextEdit* const edit       = new QTextEdit;
   71         vbox->addWidget(edit, 1);
   72         QPushButton* const okButton = new QPushButton(i18n("OK"));
   73         vbox->addWidget(okButton, 0, Qt::AlignRight);
   74 
   75         setLayout(vbox);
   76 
   77         connect(okButton, SIGNAL(clicked()),
   78                 this, SLOT(close()));
   79 
   80         edit->setHtml(richText);
   81         QApplication::clipboard()->setText(edit->toPlainText());
   82 
   83         resize(500, 400);
   84         show();
   85         raise();
   86     }
   87 };
   88 
   89 // --------------------------------------------------------------------------
   90 
   91 class Q_DECL_HIDDEN FacesDetector::Private
   92 {
   93 public:
   94 
   95     explicit Private() :
   96         benchmark(false),
   97         useItemInfos(false)
   98     {
   99     }
  100 
  101     bool                 benchmark;
  102     bool                 useItemInfos;
  103 
  104     AlbumPointerList<>   albumTodoList;
  105     ItemInfoList         infoTodoList;
  106     ItemInfoJob          albumListing;
  107     FacePipeline         pipeline;
  108 };
  109 
  110 FacesDetector::FacesDetector(const FaceScanSettings& settings, ProgressItem* const parent)
  111     : MaintenanceTool(QLatin1String("FacesDetector"), parent),
  112       d(new Private)
  113 {
  114     setLabel(i18n("Updating faces database."));
  115     ProgressManager::addProgressItem(this);
  116 
  117     if (settings.task == FaceScanSettings::RetrainAll)
  118     {
  119         // clear all training data in the database
  120         RecognitionDatabase().clearAllTraining(QLatin1String("digikam"));
  121         d->pipeline.plugRetrainingDatabaseFilter();
  122         d->pipeline.plugTrainer();
  123         d->pipeline.construct();
  124     }
  125     else if (settings.task == FaceScanSettings::BenchmarkDetection)
  126     {
  127         d->benchmark = true;
  128         d->pipeline.plugDatabaseFilter(FacePipeline::ScanAll);
  129         d->pipeline.plugFacePreviewLoader();
  130 
  131         if (settings.useFullCpu)
  132         {
  133             d->pipeline.plugParallelFaceDetectors();
  134         }
  135         else
  136         {
  137             d->pipeline.plugFaceDetector();
  138         }
  139 
  140         d->pipeline.plugDetectionBenchmarker();
  141         d->pipeline.construct();
  142     }
  143     else if (settings.task == FaceScanSettings::BenchmarkRecognition)
  144     {
  145         d->benchmark = true;
  146         d->pipeline.plugRetrainingDatabaseFilter();
  147         d->pipeline.plugFaceRecognizer();
  148         d->pipeline.plugRecognitionBenchmarker();
  149         d->pipeline.construct();
  150     }
  151     else if ((settings.task == FaceScanSettings::DetectAndRecognize) ||
  152              (settings.task == FaceScanSettings::Detect))
  153     {
  154         FacePipeline::FilterMode filterMode;
  155         FacePipeline::WriteMode  writeMode;
  156 
  157         if (settings.alreadyScannedHandling == FaceScanSettings::Skip)
  158         {
  159             filterMode = FacePipeline::SkipAlreadyScanned;
  160             writeMode  = FacePipeline::NormalWrite;
  161         }
  162         else if (settings.alreadyScannedHandling == FaceScanSettings::Rescan)
  163         {
  164             filterMode = FacePipeline::ScanAll;
  165             writeMode  = FacePipeline::OverwriteUnconfirmed;
  166         }
  167         else // FaceScanSettings::Merge
  168         {
  169             filterMode = FacePipeline::ScanAll;
  170             writeMode  = FacePipeline::NormalWrite;
  171         }
  172 
  173         d->pipeline.plugDatabaseFilter(filterMode);
  174         d->pipeline.plugFacePreviewLoader();
  175 
  176         if (settings.useFullCpu)
  177         {
  178             d->pipeline.plugParallelFaceDetectors();
  179         }
  180         else
  181         {
  182             d->pipeline.plugFaceDetector();
  183         }
  184 
  185         if (settings.task == FaceScanSettings::DetectAndRecognize)
  186         {
  187             //d->pipeline.plugRerecognizingDatabaseFilter();
  188             qCDebug(DIGIKAM_GENERAL_LOG) << "recognize algorithm: " << (int)settings.recognizeAlgorithm;
  189             d->pipeline.plugFaceRecognizer();
  190             d->pipeline.activeFaceRecognizer(settings.recognizeAlgorithm);
  191         }
  192 
  193         d->pipeline.plugDatabaseWriter(writeMode);
  194         d->pipeline.setDetectionAccuracy(settings.accuracy);
  195         d->pipeline.construct();
  196     }
  197     else // FaceScanSettings::RecognizeMarkedFaces
  198     {
  199         d->pipeline.plugRerecognizingDatabaseFilter();
  200         d->pipeline.plugFaceRecognizer();
  201         d->pipeline.activeFaceRecognizer(settings.recognizeAlgorithm);
  202         d->pipeline.plugDatabaseWriter(FacePipeline::NormalWrite);
  203         d->pipeline.setDetectionAccuracy(settings.accuracy);
  204         d->pipeline.construct();
  205     }
  206 
  207     connect(&d->albumListing, SIGNAL(signalItemsInfo(ItemInfoList)),
  208             this, SLOT(slotItemsInfo(ItemInfoList)));
  209 
  210     connect(&d->albumListing, SIGNAL(signalCompleted()),
  211             this, SLOT(slotContinueAlbumListing()));
  212 
  213     connect(&d->pipeline, SIGNAL(finished()),
  214             this, SLOT(slotContinueAlbumListing()));
  215 
  216     connect(&d->pipeline, SIGNAL(processed(FacePipelinePackage)),
  217             this, SLOT(slotShowOneDetected(FacePipelinePackage)));
  218 
  219     connect(&d->pipeline, SIGNAL(skipped(QList<ItemInfo>)),
  220             this, SLOT(slotImagesSkipped(QList<ItemInfo>)));
  221 
  222     connect(this, SIGNAL(progressItemCanceled(ProgressItem*)),
  223             this, SLOT(slotCancel()));
  224 
  225     if ((settings.albums.isEmpty() && settings.infos.isEmpty()) ||
  226          settings.task == FaceScanSettings::RetrainAll)
  227     {
  228         d->albumTodoList = AlbumManager::instance()->allPAlbums();
  229     }
  230     else if (!settings.albums.isEmpty())
  231     {
  232         d->albumTodoList = settings.albums;
  233     }
  234     else
  235     {
  236         d->infoTodoList = settings.infos;
  237         d->useItemInfos = true;
  238     }
  239 }
  240 
  241 FacesDetector::~FacesDetector()
  242 {
  243     delete d;
  244 }
  245 
  246 void FacesDetector::slotStart()
  247 {
  248     MaintenanceTool::slotStart();
  249 
  250     setThumbnail(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(22));
  251 
  252     if (d->useItemInfos)
  253     {
  254         int total = d->infoTodoList.count();
  255         qCDebug(DIGIKAM_GENERAL_LOG) << "Total is" << total;
  256 
  257         setTotalItems(total);
  258 
  259         return slotItemsInfo(d->infoTodoList);
  260     }
  261 
  262     setUsesBusyIndicator(true);
  263 
  264     // get total count, cached by AlbumManager
  265     QMap<int, int> palbumCounts;
  266     QMap<int, int> talbumCounts;
  267     bool hasPAlbums = false;
  268     bool hasTAlbums = false;
  269 
  270     foreach (Album* const album, d->albumTodoList)
  271     {
  272         if (album->type() == Album::PHYSICAL)
  273         {
  274             hasPAlbums = true;
  275         }
  276         else
  277         {
  278             hasTAlbums = true;
  279         }
  280     }
  281 
  282     palbumCounts = AlbumManager::instance()->getPAlbumsCount();
  283     talbumCounts = AlbumManager::instance()->getTAlbumsCount();
  284 
  285     if (palbumCounts.isEmpty() && hasPAlbums)
  286     {
  287         QApplication::setOverrideCursor(Qt::WaitCursor);
  288         palbumCounts = CoreDbAccess().db()->getNumberOfImagesInAlbums();
  289         QApplication::restoreOverrideCursor();
  290     }
  291 
  292     if (talbumCounts.isEmpty() && hasTAlbums)
  293     {
  294         QApplication::setOverrideCursor(Qt::WaitCursor);
  295         talbumCounts = CoreDbAccess().db()->getNumberOfImagesInTags();
  296         QApplication::restoreOverrideCursor();
  297     }
  298 
  299     // first, we use the progressValueMap map to store absolute counts
  300 
  301     QMap<Album*, int> progressValueMap;
  302 
  303     foreach (Album* const album, d->albumTodoList)
  304     {
  305         if (album->type() == Album::PHYSICAL)
  306         {
  307             progressValueMap[album] = palbumCounts.value(album->id());
  308         }
  309         else
  310         {
  311             // this is possibly broken of course because we do not know if images have multiple tags,
  312             // but there's no better solution without expensive operation
  313             progressValueMap[album] = talbumCounts.value(album->id());
  314         }
  315     }
  316 
  317     // second, calculate (approximate) overall sum
  318 
  319     int total = 0;
  320 
  321     foreach (int count, progressValueMap)
  322     {
  323         total += count;
  324     }
  325 
  326     total = qMax(1, total);
  327     qCDebug(DIGIKAM_GENERAL_LOG) << "Total is" << total;
  328 
  329     setUsesBusyIndicator(false);
  330     setTotalItems(total);
  331 
  332     slotContinueAlbumListing();
  333 }
  334 
  335 void FacesDetector::slotContinueAlbumListing()
  336 {
  337     if (d->useItemInfos)
  338     {
  339         return slotDone();
  340     }
  341 
  342     qCDebug(DIGIKAM_GENERAL_LOG) << d->albumListing.isRunning() << !d->pipeline.hasFinished();
  343 
  344     // we get here by the finished signal from both, and want both to have finished to continue
  345     if (d->albumListing.isRunning() || !d->pipeline.hasFinished())
  346     {
  347         return;
  348     }
  349 
  350     // list can have null pointer if album was deleted recently
  351     Album* album = nullptr;
  352 
  353     do
  354     {
  355         if (d->albumTodoList.isEmpty())
  356         {
  357             return slotDone();
  358         }
  359 
  360         album = d->albumTodoList.takeFirst();
  361     }
  362     while (!album);
  363 
  364     d->albumListing.allItemsFromAlbum(album);
  365 }
  366 
  367 void FacesDetector::slotItemsInfo(const ItemInfoList& items)
  368 {
  369     d->pipeline.process(items);
  370 }
  371 
  372 void FacesDetector::slotDone()
  373 {
  374     if (d->benchmark)
  375     {
  376         new BenchmarkMessageDisplay(d->pipeline.benchmarkResult());
  377     }
  378 
  379     // Switch on scanned for faces flag on digiKam config file.
  380     KSharedConfig::openConfig()->group("General Settings").writeEntry("Face Scanner First Run", true);
  381 
  382     MaintenanceTool::slotDone();
  383 }
  384 
  385 void FacesDetector::slotCancel()
  386 {
  387     d->pipeline.shutDown();
  388     MaintenanceTool::slotCancel();
  389 }
  390 
  391 void FacesDetector::slotImagesSkipped(const QList<ItemInfo>& infos)
  392 {
  393     advance(infos.size());
  394 }
  395 
  396 void FacesDetector::slotShowOneDetected(const FacePipelinePackage& /*package*/)
  397 {
  398     advance(1);
  399 }
  400 
  401 } // namespace Digikam