1
/* ============================================================
3
* This file is a part of digiKam project
4
* http://www.digikam.org
7
* Description : batch face detection
9
* Copyright (C) 2010 by Aditya Bhatt <adityabhatt1991 at gmail dot com>
10
* Copyright (C) 2010-2012 by Gilles Caulier <caulier dot gilles at gmail dot com>
11
* Copyright (C) 2012 by Andi Clemens <andi dot clemens at googlemail dot com>
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)
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.
24
* ============================================================ */
26
#include "facedetector.moc"
31
#include <QVBoxLayout>
40
#include <kpushbutton.h>
41
#include <kstandarddirs.h>
42
#include <kstandardguiitem.h>
43
#include <ktextedit.h>
44
#include <kapplication.h>
48
#include <libkface/recognitiondatabase.h>
54
#include "albummanager.h"
55
#include "facepipeline.h"
56
#include "facescansettings.h"
57
#include "imageinfo.h"
58
#include "imageinfojob.h"
63
class BenchmarkMessageDisplay : public QWidget
67
BenchmarkMessageDisplay(const QString& richText)
70
setAttribute(Qt::WA_DeleteOnClose);
72
QVBoxLayout* vbox = new QVBoxLayout;
73
KTextEdit* edit = new KTextEdit;
74
vbox->addWidget(edit, 1);
75
KPushButton* okButton = new KPushButton(KStandardGuiItem::ok());
76
vbox->addWidget(okButton, 0, Qt::AlignRight);
80
connect(okButton, SIGNAL(clicked()),
83
edit->setHtml(richText);
84
QApplication::clipboard()->setText(edit->toPlainText());
92
// --------------------------------------------------------------------------
94
class FaceDetector::FaceDetectorPriv
108
AlbumPointerList<> albumTodoList;
109
ImageInfoJob albumListing;
110
FacePipeline pipeline;
113
FaceDetector::FaceDetector(const FaceScanSettings& settings, ProgressItem* parent)
114
: MaintenanceTool("FaceDetector", parent),
115
d(new FaceDetectorPriv)
117
if (settings.task == FaceScanSettings::RetrainAll)
119
KFaceIface::RecognitionDatabase::addDatabase();
120
d->pipeline.plugRetrainingDatabaseFilter();
121
d->pipeline.plugTrainer();
122
d->pipeline.construct();
124
else if (settings.task == FaceScanSettings::Benchmark)
127
d->pipeline.plugDatabaseFilter(FacePipeline::ScanAll);
128
d->pipeline.plugPreviewLoader();
130
if (settings.useFullCpu)
132
d->pipeline.plugParallelFaceDetectors();
136
d->pipeline.plugFaceDetector();
139
d->pipeline.plugBenchmarker();
140
d->pipeline.construct();
144
FacePipeline::FilterMode filterMode;
145
FacePipeline::WriteMode writeMode;
147
if (settings.task == FaceScanSettings::DetectAndRecognize)
149
if (settings.alreadyScannedHandling == FaceScanSettings::Skip)
151
filterMode = FacePipeline::SkipAlreadyScanned;
152
writeMode = FacePipeline::NormalWrite;
154
else if (settings.alreadyScannedHandling == FaceScanSettings::Rescan)
156
filterMode = FacePipeline::ScanAll;
157
writeMode = FacePipeline::OverwriteUnconfirmed;
159
else // if (settings.alreadyScannedHandling == FaceScanSettings::Merge)
161
filterMode = FacePipeline::ScanAll;
162
writeMode = FacePipeline::NormalWrite;
165
else // if (settings.task == FaceScanSettings::RecognizeMarkedFaces)
167
filterMode = FacePipeline::ReadUnconfirmedFaces;
168
writeMode = FacePipeline::NormalWrite;
171
d->pipeline.plugDatabaseFilter(filterMode);
173
if (settings.task == FaceScanSettings::DetectAndRecognize)
175
d->pipeline.plugPreviewLoader();
177
if (settings.useFullCpu)
179
d->pipeline.plugParallelFaceDetectors();
183
d->pipeline.plugFaceDetector();
187
d->pipeline.plugFaceRecognizer();
188
d->pipeline.plugDatabaseWriter(writeMode);
189
d->pipeline.construct();
190
d->pipeline.setDetectionAccuracy(settings.accuracy);
193
connect(&d->albumListing, SIGNAL(signalItemsInfo(ImageInfoList)),
194
this, SLOT(slotItemsInfo(ImageInfoList)));
196
connect(&d->albumListing, SIGNAL(signalCompleted()),
197
this, SLOT(slotContinueAlbumListing()));
199
connect(&d->pipeline, SIGNAL(finished()),
200
this, SLOT(slotContinueAlbumListing()));
202
connect(&d->pipeline, SIGNAL(processed(FacePipelinePackage)),
203
this, SLOT(slotShowOneDetected(FacePipelinePackage)));
205
connect(&d->pipeline, SIGNAL(skipped(QList<ImageInfo>)),
206
this, SLOT(slotImagesSkipped(QList<ImageInfo>)));
208
connect(this, SIGNAL(progressItemCanceled(ProgressItem*)),
209
this, SLOT(slotCancel()));
211
if (settings.albums.isEmpty() || settings.task == FaceScanSettings::RetrainAll)
213
d->albumTodoList = AlbumManager::instance()->allPAlbums();
217
d->albumTodoList = settings.albums;
221
FaceDetector::~FaceDetector()
226
void FaceDetector::slotStart()
228
MaintenanceTool::slotStart();
230
setThumbnail(KIcon("edit-image-face-show").pixmap(22));
231
setUsesBusyIndicator(true);
233
// get total count, cached by AlbumManager
234
QMap<int, int> palbumCounts, talbumCounts;
235
bool hasPAlbums = false;
236
bool hasTAlbums = false;
238
foreach(Album* album, d->albumTodoList)
240
if (album->type() == Album::PHYSICAL)
250
palbumCounts = AlbumManager::instance()->getPAlbumsCount();
251
talbumCounts = AlbumManager::instance()->getTAlbumsCount();
253
if (palbumCounts.isEmpty() && hasPAlbums)
255
QApplication::setOverrideCursor(Qt::WaitCursor);
256
palbumCounts = DatabaseAccess().db()->getNumberOfImagesInAlbums();
257
QApplication::restoreOverrideCursor();
260
if (talbumCounts.isEmpty() && hasTAlbums)
262
QApplication::setOverrideCursor(Qt::WaitCursor);
263
talbumCounts = DatabaseAccess().db()->getNumberOfImagesInTags();
264
QApplication::restoreOverrideCursor();
269
foreach(Album* album, d->albumTodoList)
271
if (album->type() == Album::PHYSICAL)
273
d->total += palbumCounts.value(album->id());
276
// this is possibly broken of course because we do not know if images have multiple tags,
277
// but there's no better solution without expensive operation
279
d->total += talbumCounts.value(album->id());
283
kDebug() << "Total is" << d->total;
285
d->total = qMax(1, d->total);
287
setUsesBusyIndicator(false);
288
setLabel(i18n("Updating faces database."));
289
setTotalItems(d->total);
291
slotContinueAlbumListing();
294
void FaceDetector::slotContinueAlbumListing()
296
kDebug() << d->albumListing.isRunning() << !d->pipeline.hasFinished();
298
// we get here by the finished signal from both, and want both to have finished to continue
299
if (d->albumListing.isRunning() || !d->pipeline.hasFinished())
304
// list can have null pointer if album was deleted recently
309
if (d->albumTodoList.isEmpty())
314
album = d->albumTodoList.takeFirst();
318
d->albumListing.allItemsFromAlbum(album);
321
void FaceDetector::slotItemsInfo(const ImageInfoList& items)
323
d->pipeline.process(items);
326
void FaceDetector::slotDone()
330
new BenchmarkMessageDisplay(d->pipeline.benchmarkResult());
333
// Switch on scanned for faces flag on digiKam config file.
334
KGlobal::config()->group("General Settings").writeEntry("Face Scanner First Run", true);
336
MaintenanceTool::slotDone();
339
void FaceDetector::slotCancel()
341
d->pipeline.cancel();
342
MaintenanceTool::slotCancel();
345
void FaceDetector::slotImagesSkipped(const QList<ImageInfo>& infos)
347
advance(infos.size());
350
void FaceDetector::slotShowOneDetected(const FacePipelinePackage& package)
354
if (!package.image.isNull())
356
pix = package.image.smoothScale(22, 22, Qt::KeepAspectRatio).convertToPixmap();
358
else if (!package.faces.isEmpty())
360
pix = QPixmap::fromImage(package.faces.first().image().toQImage().scaled(22, 22, Qt::KeepAspectRatio));
367
} // namespace Digikam