File indexing completed on 2025-01-19 03:57:56

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-09-03
0007  * Description : Integrated, multithread face detection / recognition
0008  *
0009  * SPDX-FileCopyrightText: 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "facepipeline_p.h"
0017 
0018 // Local includes
0019 
0020 #include "digikam_debug.h"
0021 #include "databasewriter.h"
0022 #include "detectionbenchmarker.h"
0023 #include "detectionworker.h"
0024 #include "recognitionbenchmarker.h"
0025 #include "recognitionworker.h"
0026 #include "trainerworker.h"
0027 #include "facepreviewloader.h"
0028 #include "faceitemretriever.h"
0029 #include "parallelpipes.h"
0030 #include "scanstatefilter.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 FacePipeline::FacePipeline()
0036     : d(new Private(this))
0037 {
0038     qRegisterMetaType<FacePipelineExtendedPackage::Ptr>("FacePipelineExtendedPackage::Ptr");
0039 }
0040 
0041 FacePipeline::~FacePipeline()
0042 {
0043     shutDown();
0044 
0045     delete d->databaseFilter;
0046     delete d->previewThread;
0047     delete d->detectionWorker;
0048     delete d->parallelDetectors;
0049     delete d->recognitionWorker;
0050     delete d->databaseWriter;
0051     delete d->trainerWorker;
0052     qDeleteAll(d->thumbnailLoadThreads);
0053     delete d->detectionBenchmarker;
0054     delete d->recognitionBenchmarker;
0055     delete d;
0056 }
0057 
0058 void FacePipeline::shutDown()
0059 {
0060     cancel();
0061     d->wait();
0062 }
0063 
0064 bool FacePipeline::hasFinished() const
0065 {
0066     return d->hasFinished();
0067 }
0068 
0069 QString FacePipeline::benchmarkResult() const
0070 {
0071     if (d->detectionBenchmarker)
0072     {
0073         return d->detectionBenchmarker->result();
0074     }
0075 
0076     if (d->recognitionBenchmarker)
0077     {
0078         return d->recognitionBenchmarker->result();
0079     }
0080 
0081     return QString();
0082 }
0083 
0084 void FacePipeline::plugDatabaseFilter(FilterMode mode)
0085 {
0086     d->databaseFilter = new ScanStateFilter(mode, d);
0087 }
0088 
0089 void FacePipeline::plugRerecognizingDatabaseFilter()
0090 {
0091     plugDatabaseFilter(ReadUnconfirmedFaces);
0092     d->databaseFilter->tasks = FacePipelineFaceTagsIface::ForRecognition;
0093 }
0094 
0095 void FacePipeline::plugRetrainingDatabaseFilter()
0096 {
0097     plugDatabaseFilter(ReadConfirmedFaces);
0098     d->databaseFilter->tasks = FacePipelineFaceTagsIface::ForTraining;
0099 }
0100 
0101 void FacePipeline::plugFacePreviewLoader()
0102 {
0103     d->previewThread = new FacePreviewLoader(d);
0104 }
0105 
0106 void FacePipeline::plugFaceDetector()
0107 {
0108     d->detectionWorker = new DetectionWorker(d);
0109 
0110     connect(d, SIGNAL(accuracyAndModel(double,bool)),
0111             d->detectionWorker, SLOT(setAccuracyAndModel(double,bool)),
0112             Qt::QueuedConnection);
0113 }
0114 
0115 void FacePipeline::plugParallelFaceDetectors()
0116 {
0117     if (QThread::idealThreadCount() <= 1)
0118     {
0119         plugFaceDetector();
0120         return;
0121     }
0122 
0123     // limit number of parallel detectors to 3, because of memory cost (cascades)
0124 
0125     const int n          = qMin(3, QThread::idealThreadCount());
0126     d->parallelDetectors = new ParallelPipes;
0127 
0128     for (int i = 0 ; i < n ; ++i)
0129     {
0130         DetectionWorker* const worker = new DetectionWorker(d);
0131 
0132         connect(d, SIGNAL(accuracyAndModel(double,bool)),
0133                 worker, SLOT(setAccuracyAndModel(double,bool)),
0134                 Qt::QueuedConnection);
0135 
0136         d->parallelDetectors->add(worker);
0137     }
0138 }
0139 
0140 void FacePipeline::plugFaceRecognizer()
0141 {
0142     d->recognitionWorker = new RecognitionWorker(d);
0143 
0144     connect(d, SIGNAL(accuracyAndModel(double,bool)),
0145             d->recognitionWorker, SLOT(setThreshold(double,bool)),
0146             Qt::QueuedConnection);
0147 }
0148 
0149 void FacePipeline::plugDatabaseWriter(WriteMode mode)
0150 {
0151     d->databaseWriter = new DatabaseWriter(mode, d);
0152 }
0153 
0154 void FacePipeline::plugTrainer()
0155 {
0156     d->trainerWorker = new TrainerWorker(d);
0157 }
0158 
0159 void FacePipeline::plugDetectionBenchmarker()
0160 {
0161     d->detectionBenchmarker = new DetectionBenchmarker(d);
0162 }
0163 
0164 void FacePipeline::plugRecognitionBenchmarker()
0165 {
0166     d->recognitionBenchmarker = new RecognitionBenchmarker(d);
0167 }
0168 
0169 void FacePipeline::plugDatabaseEditor()
0170 {
0171     plugDatabaseWriter(NormalWrite);
0172 }
0173 
0174 void FacePipeline::construct()
0175 {
0176     if (d->previewThread)
0177     {
0178         d->pipeline << d->previewThread;
0179         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add preview thread";
0180     }
0181 
0182     if      (d->parallelDetectors)
0183     {
0184         d->pipeline << d->parallelDetectors;
0185         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add parallel thread detectors";
0186     }
0187     else if (d->detectionWorker)
0188     {
0189         d->pipeline << d->detectionWorker;
0190         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add single thread detector";
0191     }
0192 
0193     if (d->recognitionWorker)
0194     {
0195         d->pipeline << d->recognitionWorker;
0196         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add recognition worker";
0197     }
0198 
0199     if (d->detectionBenchmarker)
0200     {
0201         d->pipeline << d->detectionBenchmarker;
0202         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add detection benchmaker";
0203     }
0204 
0205     if (d->recognitionBenchmarker)
0206     {
0207         d->pipeline << d->recognitionBenchmarker;
0208         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add recognition benchmaker";
0209     }
0210 
0211     if (d->databaseWriter)
0212     {
0213         d->pipeline << d->databaseWriter;
0214         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add database writer";
0215     }
0216 
0217     if (d->trainerWorker)
0218     {
0219         d->pipeline << d->trainerWorker;
0220         qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add faces trainer";
0221     }
0222 
0223     if (d->pipeline.isEmpty())
0224     {
0225         qCWarning(DIGIKAM_GENERAL_LOG) << "Nothing plugged in. It is a noop.";
0226         return;
0227     }
0228 
0229     connect(d, SIGNAL(startProcess(FacePipelineExtendedPackage::Ptr)),
0230             d->pipeline.first(), SLOT(process(FacePipelineExtendedPackage::Ptr)),
0231             Qt::QueuedConnection);
0232 
0233     for (int i = 0 ; i < (d->pipeline.size() - 1) ; ++i)
0234     {
0235         connect(d->pipeline.at(i), SIGNAL(processed(FacePipelineExtendedPackage::Ptr)),
0236                 d->pipeline.at(i + 1), SLOT(process(FacePipelineExtendedPackage::Ptr)),
0237                 Qt::QueuedConnection);
0238     }
0239 
0240     connect(d->pipeline.last(), SIGNAL(processed(FacePipelineExtendedPackage::Ptr)),
0241             d, SLOT(finishProcess(FacePipelineExtendedPackage::Ptr)),
0242             Qt::QueuedConnection);
0243 
0244     d->applyPriority();
0245 }
0246 
0247 void FacePipeline::setPriority(QThread::Priority priority)
0248 {
0249     if (d->priority == priority)
0250     {
0251         return;
0252     }
0253 
0254     d->priority = priority;
0255     d->applyPriority();
0256 }
0257 
0258 QThread::Priority FacePipeline::priority() const
0259 {
0260     return d->priority;
0261 }
0262 
0263 void FacePipeline::cancel()
0264 {
0265     d->stop();
0266 }
0267 
0268 bool FacePipeline::process(const ItemInfo& info)
0269 {
0270     QString filePath = info.filePath();
0271 
0272     if (filePath.isNull())
0273     {
0274         qCWarning(DIGIKAM_GENERAL_LOG) << "ItemInfo has no valid file path. Skipping.";
0275         return false;
0276     }
0277 
0278     FacePipelineExtendedPackage::Ptr package = d->filterOrBuildPackage(info);
0279 
0280     if (!package)
0281     {
0282         return false;
0283     }
0284 
0285     d->send(package);
0286 
0287     return true;
0288 }
0289 
0290 bool FacePipeline::process(const ItemInfo& info,
0291                            const DImg& image)
0292 {
0293     FacePipelineExtendedPackage::Ptr package = d->filterOrBuildPackage(info);
0294 
0295     if (!package)
0296     {
0297         return false;
0298     }
0299 
0300     package->image = image;
0301     d->send(package);
0302 
0303     return true;
0304 }
0305 
0306 /*
0307 bool FacePipeline::add(const ItemInfo& info,
0308                        const QRect& rect,
0309                        const DImg& image)
0310 {
0311     FacePipelineExtendedPackage::Ptr package = d->buildPackage(info);
0312     package->image                           = image;
0313     package->detectionImage                  = image;
0314     package->faces << Face(rect);
0315     d->send(package);
0316 }
0317 */
0318 
0319 void FacePipeline::train(const ItemInfo& info,
0320                          const QList<FaceTagsIface>& databaseFaces)
0321 {
0322     train(info, databaseFaces, DImg());
0323 }
0324 
0325 void FacePipeline::train(const ItemInfo& info,
0326                          const QList<FaceTagsIface>& databaseFaces,
0327                          const DImg& image)
0328 {
0329     FacePipelineExtendedPackage::Ptr package = d->buildPackage(info,
0330                                                                FacePipelineFaceTagsIfaceList(databaseFaces),
0331                                                                image);
0332     package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForTraining);
0333     d->send(package);
0334 }
0335 
0336 FaceTagsIface FacePipeline::confirm(const ItemInfo& info,
0337                                     const FaceTagsIface& databaseFace,
0338                                     int assignedTagId,
0339                                     const TagRegion& assignedRegion)
0340 {
0341     return confirm(info, databaseFace, DImg(), assignedTagId, assignedRegion);
0342 }
0343 
0344 FaceTagsIface FacePipeline::confirm(const ItemInfo& info,
0345                                     const FaceTagsIface& databaseFace,
0346                                     const DImg& image,
0347                                     int assignedTagId,
0348                                     const TagRegion& assignedRegion)
0349 {
0350     FacePipelineFaceTagsIface face            = FacePipelineFaceTagsIface(databaseFace);
0351     face.assignedTagId                        = assignedTagId;
0352     face.assignedRegion                       = assignedRegion;
0353     face.roles                               |= FacePipelineFaceTagsIface::ForConfirmation;
0354     FacePipelineExtendedPackage::Ptr package  = d->buildPackage(info, face, image);
0355 
0356     d->send(package);
0357 
0358     return FaceTagsEditor::confirmedEntry(face, assignedTagId, assignedRegion);
0359 }
0360 
0361 FaceTagsIface FacePipeline::addManually(const ItemInfo& info,
0362                                         const DImg& image,
0363                                         const TagRegion& assignedRegion)
0364 {
0365     FacePipelineFaceTagsIface face;     // giving a null face => no existing face yet, add it
0366     face.assignedTagId                        = -1;
0367     face.assignedRegion                       = assignedRegion;
0368     face.roles                               |= FacePipelineFaceTagsIface::ForEditing;
0369     FacePipelineExtendedPackage::Ptr package  = d->buildPackage(info, face, image);
0370 
0371     package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing);
0372     d->send(package);
0373 
0374     return FaceTagsEditor::unconfirmedEntry(info.id(), face.assignedTagId, face.assignedRegion);
0375 }
0376 
0377 FaceTagsIface FacePipeline::editRegion(const ItemInfo& info,
0378                                        const DImg& image,
0379                                        const FaceTagsIface& databaseFace,
0380                                        const TagRegion& newRegion)
0381 {
0382     FacePipelineFaceTagsIface face            = FacePipelineFaceTagsIface(databaseFace);
0383     face.assignedTagId                        = -1;
0384     face.assignedRegion                       = newRegion;
0385     face.roles                               |= FacePipelineFaceTagsIface::ForEditing;
0386     FacePipelineExtendedPackage::Ptr package  = d->buildPackage(info, face, image);
0387 
0388     package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing);
0389     d->send(package);
0390 
0391     face.setRegion(newRegion);
0392 
0393     return face;
0394 }
0395 
0396 FaceTagsIface FacePipeline::editTag(const ItemInfo& info,
0397                                     const FaceTagsIface& databaseFace,
0398                                     int newTagId)
0399 {
0400     FacePipelineFaceTagsIface face           = FacePipelineFaceTagsIface(databaseFace);
0401     face.assignedTagId                       = newTagId;
0402     face.assignedRegion                      = TagRegion();
0403     face.roles                              |= FacePipelineFaceTagsIface::ForEditing;
0404 
0405     FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, face, DImg());
0406 
0407     package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing);
0408     d->send(package);
0409 
0410     return face;
0411 }
0412 
0413 void FacePipeline::remove(const ItemInfo& info,
0414                           const FaceTagsIface& databaseFace)
0415 {
0416     FacePipelineExtendedPackage::Ptr package = d->buildPackage(info,
0417                                                                FacePipelineFaceTagsIface(databaseFace),
0418                                                                DImg());
0419     package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing);
0420     d->send(package);
0421 }
0422 
0423 void FacePipeline::process(const QList<ItemInfo>& infos)
0424 {
0425     d->processBatch(infos);
0426 }
0427 
0428 void FacePipeline::setAccuracyAndModel(double value, bool yolo)
0429 {
0430     Q_EMIT d->accuracyAndModel(value, yolo);
0431 }
0432 
0433 } // namespace Digikam
0434 
0435 #include "moc_facepipeline.cpp"