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

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 "databasewriter.h"
0017 
0018 // Local includes
0019 
0020 #include "digikam_debug.h"
0021 
0022 namespace Digikam
0023 {
0024 
0025 DatabaseWriter::DatabaseWriter(FacePipeline::WriteMode mode, FacePipeline::Private* const dd)
0026     : mode               (mode),
0027       thumbnailLoadThread(dd->createThumbnailLoadThread()),
0028       d                  (dd)
0029 {
0030 }
0031 
0032 DatabaseWriter::~DatabaseWriter()
0033 {
0034 }
0035 
0036 void DatabaseWriter::process(const FacePipelineExtendedPackage::Ptr& package)
0037 {
0038     if      (package->databaseFaces.isEmpty())
0039     {
0040         // Detection / Recognition
0041 
0042         FaceUtils utils;
0043 
0044         // OverwriteUnconfirmed means that a new scan discarded unconfirmed results of previous scans
0045         // (assuming at least equal or new, better methodology is in use compared to the previous scan)
0046 
0047         if      ((mode == FacePipeline::OverwriteUnconfirmed) &&
0048                  (package->processFlags & FacePipelinePackage::ProcessedByDetector))
0049         {
0050             QList<FaceTagsIface> oldEntries = utils.unconfirmedFaceTagsIfaces(package->info.id());
0051             qCDebug(DIGIKAM_GENERAL_LOG) << "Removing old entries" << oldEntries;
0052             utils.removeFaces(oldEntries);
0053         }
0054         else if ((mode == FacePipeline::OverwriteAllFaces) &&
0055                  (package->processFlags & FacePipelinePackage::ProcessedByDetector))
0056         {
0057             utils.removeAllFaces(package->info.id());
0058         }
0059 
0060         // mark the whole image as scanned-for-faces
0061 
0062         utils.markAsScanned(package->info);
0063 
0064         if (!package->info.isNull() && !package->detectedFaces.isEmpty())
0065         {
0066             package->databaseFaces = utils.writeUnconfirmedResults(package->info.id(),
0067                                                                    package->detectedFaces,
0068                                                                    package->recognitionResults,
0069                                                                    package->image.originalSize());
0070             package->databaseFaces.setRole(FacePipelineFaceTagsIface::DetectedFromImage);
0071 
0072             if (!package->image.isNull())
0073             {
0074                 utils.storeThumbnails(thumbnailLoadThread, package->filePath,
0075                                       package->databaseFaces.toFaceTagsIfaceList(), package->image);
0076             }
0077         }
0078     }
0079     else if (package->processFlags & FacePipelinePackage::ProcessedByRecognizer)
0080     {
0081         FaceUtils utils;
0082 
0083         for (int i = 0 ; i < package->databaseFaces.size() ; ++i)
0084         {
0085             if (package->databaseFaces[i].roles & FacePipelineFaceTagsIface::ForRecognition)
0086             {
0087                 // Allow to overwrite existing recognition with new, possibly valid, "not recognized" status
0088 
0089                 int tagId = FaceTags::unknownPersonTagId();
0090 
0091                 // NOTE: See bug #338485 : check if index is not outside of container size.
0092 
0093                 if ((i < package->recognitionResults.size()) &&
0094                     !package->recognitionResults[i].isNull())
0095                 {
0096                     // Only perform this call if recognition as results, to prevent crash in QMap. See bug #335624
0097 
0098                     tagId = FaceTags::getOrCreateTagForIdentity(package->recognitionResults[i].attributesMap());
0099                 }
0100 
0101                 package->databaseFaces[i]        = FacePipelineFaceTagsIface(utils.changeSuggestedName(package->databaseFaces[i], tagId));
0102                 package->databaseFaces[i].roles &= ~FacePipelineFaceTagsIface::ForRecognition;
0103            }
0104         }
0105     }
0106     else
0107     {
0108         // Editing database entries
0109 
0110         FaceUtils utils;
0111         FacePipelineFaceTagsIfaceList add;
0112         FacePipelineFaceTagsIfaceList::iterator it;
0113 
0114         for (it = package->databaseFaces.begin() ; it != package->databaseFaces.end() ; ++it)
0115         {
0116             if      (it->roles & FacePipelineFaceTagsIface::ForConfirmation)
0117             {
0118                 FacePipelineFaceTagsIface confirmed = FacePipelineFaceTagsIface(utils.confirmName(*it, it->assignedTagId, it->assignedRegion));
0119                 confirmed.roles                    |= FacePipelineFaceTagsIface::Confirmed | FacePipelineFaceTagsIface::ForTraining;
0120                 add << confirmed;
0121             }
0122             else if (it->roles & FacePipelineFaceTagsIface::ForEditing)
0123             {
0124                 if      (it->isNull())
0125                 {
0126                     // add Manually
0127 
0128                     FaceTagsIface newFace = utils.unconfirmedEntry(package->info.id(), it->assignedTagId, it->assignedRegion);
0129                     utils.addManually(newFace);
0130                     add << FacePipelineFaceTagsIface(newFace);
0131                 }
0132                 else if (it->assignedRegion.isValid())
0133                 {
0134                     add << FacePipelineFaceTagsIface(utils.changeRegion(*it, it->assignedRegion));
0135                 }
0136                 else if (FaceTags::isPerson(it->assignedTagId))
0137                 {
0138                     // Change Tag operation.
0139 
0140                     add << FacePipelineFaceTagsIface(utils.changeTag(*it, it->assignedTagId));
0141                 }
0142                 else
0143                 {
0144                     utils.removeFace(*it);
0145                 }
0146 
0147                 it->roles &= ~FacePipelineFaceTagsIface::ForEditing;
0148                 it->roles |= FacePipelineFaceTagsIface::Edited;
0149             }
0150 
0151             // Training is done by trainerWorker
0152         }
0153 
0154         if (!package->image.isNull())
0155         {
0156             utils.storeThumbnails(thumbnailLoadThread, package->filePath, add.toFaceTagsIfaceList(), package->image);
0157         }
0158 
0159         package->databaseFaces << add;
0160     }
0161 
0162     package->processFlags |= FacePipelinePackage::WrittenToDatabase;
0163 
0164     Q_EMIT processed(package);
0165 }
0166 
0167 } // namespace Digikam
0168 
0169 #include "moc_databasewriter.cpp"