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-10-09
0007  * Description : Widget to choose options for face scanning
0008  *
0009  * SPDX-FileCopyrightText: 2010-2012 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 // NOTE: Uncomment this line to enable detect and recognize option
0017 // Currently this option is hidden, since it's not handled properly and provides confusing functionality => Fix it later
0018 //#define ENABLE_DETECT_AND_RECOGNIZE
0019 
0020 #include "facescanwidget_p.h"
0021 
0022 namespace Digikam
0023 {
0024 
0025 FaceScanWidget::FaceScanWidget(QWidget* const parent)
0026     : QTabWidget       (parent),
0027       StateSavingObject(this),
0028       d                (new Private)
0029 {
0030     setObjectName(d->configName);
0031     setupUi();
0032     setupConnections();
0033 }
0034 
0035 FaceScanWidget::~FaceScanWidget()
0036 {
0037     delete d;
0038 }
0039 
0040 void FaceScanWidget::doLoadState()
0041 {
0042     KConfigGroup group = getConfigGroup();
0043     QString mainTask   = group.readEntry(entryName(d->configMainTask),
0044                                          d->configValueDetect);
0045 
0046     if      (mainTask == d->configValueRecognizedMarkedFaces)
0047     {
0048         d->reRecognizeButton->setChecked(true);
0049     }
0050     else if (mainTask == d->configValueDetectAndRecognize)
0051     {
0052 
0053 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0054 
0055         d->detectAndRecognizeButton->setChecked(true);
0056 
0057 #else
0058 
0059         d->detectButton->setChecked(true);
0060 
0061 #endif
0062 
0063     }
0064     else
0065     {
0066         d->detectButton->setChecked(true);
0067     }
0068 
0069     FaceScanSettings::AlreadyScannedHandling handling;
0070     handling = (FaceScanSettings::AlreadyScannedHandling)group.readEntry(entryName(d->configAlreadyScannedHandling),
0071                                                                          (int)FaceScanSettings::Skip);
0072 
0073     d->alreadyScannedBox->setCurrentIndex(d->alreadyScannedBox->findData(handling));
0074 
0075     d->accuracyInput->setValue(ApplicationSettings::instance()->getFaceDetectionAccuracy() * 100);
0076 
0077     d->albumSelectors->loadState();
0078 
0079     d->useYoloV3Button->setChecked(ApplicationSettings::instance()->getFaceDetectionYoloV3());
0080 
0081     d->useFullCpuButton->setChecked(group.readEntry(entryName(d->configUseFullCpu), false));
0082 }
0083 
0084 void FaceScanWidget::doSaveState()
0085 {
0086     KConfigGroup group = getConfigGroup();
0087 
0088     QString mainTask;
0089 
0090     if (d->detectButton->isChecked())
0091     {
0092         mainTask = d->configValueDetect;
0093     }
0094 
0095 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0096 
0097     else if (d->detectAndRecognizeButton->isChecked())
0098     {
0099         mainTask = d->configValueDetectAndRecognize;
0100     }
0101 
0102 #endif
0103 
0104     else // d->reRecognizeButton
0105     {
0106         mainTask = d->configValueRecognizedMarkedFaces;
0107     }
0108 
0109     group.writeEntry(entryName(d->configMainTask), mainTask);
0110     group.writeEntry(entryName(d->configAlreadyScannedHandling),
0111                                d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt());
0112 
0113     ApplicationSettings::instance()->setFaceDetectionAccuracy(double(d->accuracyInput->value()) / 100);
0114     d->albumSelectors->saveState();
0115 
0116     ApplicationSettings::instance()->setFaceDetectionYoloV3(d->useYoloV3Button->isChecked());
0117 
0118     group.writeEntry(entryName(d->configUseFullCpu), d->useFullCpuButton->isChecked());
0119 }
0120 
0121 void FaceScanWidget::setupUi()
0122 {
0123     // ---- Workflow tab --------
0124 
0125     d->workflowWidget                   = new QWidget(this);
0126     d->workflowWidget->setToolTip(i18nc("@info:tooltip",
0127                                         "digiKam can search for faces in your photos.\n"
0128                                         "When you have identified your friends on a number of photos,\n"
0129                                         "it can also recognize the people shown on your photos."));
0130 
0131     QVBoxLayout* const optionLayout     = new QVBoxLayout;
0132 
0133     QHBoxLayout* const scanOptionLayout = new QHBoxLayout;
0134 
0135     d->alreadyScannedBox                = new SqueezedComboBox;
0136     d->alreadyScannedBox->addSqueezedItem(i18nc("@label:listbox", "Skip images already scanned"),           FaceScanSettings::Skip);
0137     d->alreadyScannedBox->addSqueezedItem(i18nc("@label:listbox", "Scan again and merge results"),          FaceScanSettings::Merge);
0138     d->alreadyScannedBox->addSqueezedItem(i18nc("@label:listbox", "Clear unconfirmed results and rescan"),  FaceScanSettings::Rescan);
0139     d->alreadyScannedBox->addSqueezedItem(i18nc("@label:listbox", "Clear all previous results and rescan"), FaceScanSettings::ClearAll);
0140 
0141     QString buttonText;
0142     d->helpButton = new QPushButton(QIcon::fromTheme(QLatin1String("help-browser")), buttonText);
0143     d->helpButton->setToolTip(i18nc("@info", "Help"));
0144 
0145     connect(d->helpButton, &QPushButton::clicked,
0146             this, []()
0147         {
0148             openOnlineDocumentation(QLatin1String("main_window"), QLatin1String("people_view"));
0149         }
0150     );
0151 
0152     scanOptionLayout->addWidget(d->alreadyScannedBox, 9);
0153     scanOptionLayout->addWidget(d->helpButton,        1);
0154 
0155     optionLayout->addLayout(scanOptionLayout);
0156 
0157     d->alreadyScannedBox->setCurrentIndex(FaceScanSettings::Skip);
0158 
0159     d->detectButton                   = new QRadioButton(i18nc("@option:radio", "Detect faces"));
0160     d->detectButton->setToolTip(i18nc("@info", "Find all faces in your photos"));
0161 
0162 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0163 
0164     d->detectAndRecognizeButton       = new QRadioButton(i18nc("@option:radio", "Detect and recognize faces"));
0165     d->detectAndRecognizeButton->setToolTip(i18nc("@info", "Find all faces in your photos and\n"
0166                                                            "try to recognize which person is depicted"));
0167 #endif
0168 
0169     d->reRecognizeButton              = new QRadioButton(i18nc("@option:radio", "Recognize faces"));
0170     d->reRecognizeButton->setToolTip(i18nc("@info", "Try again to recognize the people depicted\n"
0171                                                     "on marked but yet unconfirmed faces."));
0172 
0173     optionLayout->addWidget(d->detectButton);
0174 
0175 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0176 
0177     optionLayout->addWidget(d->detectAndRecognizeButton);
0178 
0179 #endif
0180 
0181     optionLayout->addWidget(d->reRecognizeButton);
0182 
0183 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0184 
0185     QStyleOptionButton buttonOption;
0186     buttonOption.initFrom(d->detectAndRecognizeButton);
0187     int indent = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &buttonOption, d->detectAndRecognizeButton).width();
0188     optionLayout->setColumnMinimumWidth(0, indent);
0189 
0190 #endif
0191 
0192     d->workflowWidget->setLayout(optionLayout);
0193     addTab(d->workflowWidget, i18nc("@title:tab", "Workflow"));
0194 
0195     // ---- Album tab ---------
0196 
0197     d->albumSelectors                 = new AlbumSelectors(QString(), d->configName,
0198                                                            this, AlbumSelectors::AlbumType::All, true);
0199     addTab(d->albumSelectors, i18nc("@title:tab", "Search in"));
0200 
0201     // ---- Settings tab ------
0202 
0203     QWidget* const settingsTab        = new QWidget(this);
0204     QVBoxLayout* const settingsLayout = new QVBoxLayout(settingsTab);
0205 
0206     QGroupBox* const accuracyBox      = new QGroupBox(i18nc("@label", "Face Accuracy"), settingsTab);
0207     QGridLayout* const accuracyGrid   = new QGridLayout(accuracyBox);
0208 
0209     QLabel* const sensitivityLabel    = new QLabel(i18nc("@label left extremities of a scale", "Sensitivity"), settingsTab);
0210     sensitivityLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
0211 
0212     QLabel* const specificityLabel    = new QLabel(i18nc("@label right extremities of a scale", "Specificity"), settingsTab);
0213     specificityLabel->setAlignment(Qt::AlignTop | Qt::AlignRight);
0214 
0215     d->accuracyInput                  = new DIntNumInput(settingsTab);
0216     d->accuracyInput->setDefaultValue(70);
0217     d->accuracyInput->setRange(0, 100, 10);
0218     d->accuracyInput->setToolTip(i18nc("@info:tooltip",
0219                                        "Adjust sensitivity versus specificity: the higher the value, the more accurately faces will\n"
0220                                        "be recognized, but less faces will be recognized\n"
0221                                        "(only faces that are very similar to pre-tagged faces are recognized)."));
0222 
0223     accuracyGrid->addWidget(d->accuracyInput, 0, 0, 1, 3);
0224     accuracyGrid->addWidget(sensitivityLabel, 1, 0, 1, 1);
0225     accuracyGrid->addWidget(specificityLabel, 1, 2, 1, 1);
0226     accuracyGrid->setColumnStretch(1, 10);
0227 
0228     d->useYoloV3Button                = new QCheckBox(settingsTab);
0229     d->useYoloV3Button->setText(i18nc("@option:check", "Use YOLO v3 detection model"));
0230     d->useYoloV3Button->setToolTip(i18nc("@info:tooltip",
0231                                          "Face detection with YOLO v3 data model. Better results but slower."));
0232 
0233     d->useFullCpuButton               = new QCheckBox(settingsTab);
0234     d->useFullCpuButton->setText(i18nc("@option:check", "Work on all processor cores"));
0235     d->useFullCpuButton->setToolTip(i18nc("@info:tooltip",
0236                                           "Face detection and recognition are time-consuming tasks.\n"
0237                                           "You can choose if you wish to employ all processor cores\n"
0238                                           "on your system, or work in the background only on one core."));
0239 
0240     settingsLayout->addWidget(accuracyBox);
0241     settingsLayout->addWidget(d->useYoloV3Button);
0242     settingsLayout->addWidget(d->useFullCpuButton);
0243 
0244     settingsLayout->addStretch(10);
0245 
0246     addTab(settingsTab, i18nc("@title:tab", "Settings"));
0247 }
0248 
0249 void FaceScanWidget::setupConnections()
0250 {
0251 /*
0252      connect(d->detectButton, SIGNAL(toggled(bool)),
0253             d->alreadyScannedBox, SLOT(setEnabled(bool)));
0254 */
0255 
0256 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0257 
0258     connect(d->detectAndRecognizeButton, SIGNAL(toggled(bool)),
0259             d->alreadyScannedBox, SLOT(setEnabled(bool)));
0260 
0261 #endif
0262 
0263     connect(d->detectButton, SIGNAL(toggled(bool)),
0264             this, SLOT(slotPrepareForDetect(bool)));
0265 
0266     connect(d->reRecognizeButton, SIGNAL(toggled(bool)),
0267             this, SLOT(slotPrepareForRecognize(bool)));
0268 
0269     connect(d->accuracyInput, &DIntNumInput::valueChanged,
0270             this, [=](int value)
0271         {
0272             ApplicationSettings::instance()->setFaceDetectionAccuracy(double(value) / 100);
0273         }
0274     );
0275 
0276     connect(d->useYoloV3Button, &QCheckBox::toggled,
0277             this, [=](bool yolo)
0278         {
0279             ApplicationSettings::instance()->setFaceDetectionYoloV3(yolo);
0280         }
0281     );
0282 }
0283 
0284 void FaceScanWidget::slotPrepareForDetect(bool status)
0285 {
0286     d->alreadyScannedBox->setEnabled(status);
0287 }
0288 
0289 void FaceScanWidget::slotPrepareForRecognize(bool /*status*/)
0290 {
0291     d->alreadyScannedBox->setEnabled(false);
0292 }
0293 
0294 bool FaceScanWidget::settingsConflicted() const
0295 {
0296     return d->settingsConflicted;
0297 }
0298 
0299 FaceScanSettings FaceScanWidget::settings() const
0300 {
0301     FaceScanSettings settings;
0302 
0303     d->settingsConflicted = false;
0304 
0305     if (d->detectButton->isChecked())
0306     {
0307         settings.task = FaceScanSettings::Detect;
0308     }
0309     else
0310     {
0311 
0312 #ifdef ENABLE_DETECT_AND_RECOGNIZE
0313 
0314         if (d->detectAndRecognizeButton->isChecked())
0315         {
0316             settings.task = FaceScanSettings::DetectAndRecognize;
0317         }
0318         else // recognize only
0319 
0320 #endif
0321 
0322         {
0323             settings.task = FaceScanSettings::RecognizeMarkedFaces;
0324 
0325             // preset settingsConflicted as True, since by default there are no tags to recognize
0326 
0327             d->settingsConflicted = true;
0328         }
0329     }
0330 
0331     settings.alreadyScannedHandling = (FaceScanSettings::AlreadyScannedHandling)
0332                                       d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt();
0333 
0334     settings.albums                 = d->albumSelectors->selectedAlbumsAndTags();
0335     settings.accuracy               = double(d->accuracyInput->value()) / 100;
0336     settings.wholeAlbums            = d->albumSelectors->wholeAlbumsChecked();
0337 
0338     if (d->settingsConflicted)
0339     {
0340         int numberOfIdentities      = FaceDbAccess().db()->getNumberOfIdentities();
0341         d->settingsConflicted       = (numberOfIdentities == 0);
0342     }
0343 
0344     settings.useYoloV3              = d->useYoloV3Button->isChecked();
0345     settings.useFullCpu             = d->useFullCpuButton->isChecked();
0346 
0347     return settings;
0348 }
0349 
0350 } // namespace Digikam
0351 
0352 #include "moc_facescanwidget.cpp"