From 06030367394963a3985998fa1f33043347f325dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 22 Dec 2022 21:43:16 +0100 Subject: [PATCH 001/151] Prepare flag for photos face analysis state --- src/database/general_flags.hpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/database/general_flags.hpp b/src/database/general_flags.hpp index 8f39952b9d..55416c8fa5 100644 --- a/src/database/general_flags.hpp +++ b/src/database/general_flags.hpp @@ -4,6 +4,9 @@ #include +// Various flags for photos. +// Use 0 as most common/basic state, +// so all photos without flag are considered 'normal/basic/unchecked'. namespace Database::CommonGeneralFlags { const QString State("state"); // photo state. @@ -23,6 +26,20 @@ namespace Database::CommonGeneralFlags Normal = 0, // 0 (or nonexistent entry) - photo is in fine state Incompatible = 1, // 1 - could not generate phash. Not an image file. }; + + const QString FacesAnalysisState("faces_analysis_state"); + enum class FacesAnalysisType + { + // We could have use 3 states here - 0 for not analyzed, 1 for analyzed and 2 for no faces + // However that would cause many entries in db as each photo would eventualy went to state 1 or 2. + // So a bit tricky solution is used here: + // 0 (or nonexistent entry) means photo was not scaned for faces, + // or faces were found (and database will return faces for this photo in such case) + // 1 - no faces found. + // that should allow to distinguish between all 3 states with less db usage. + NotAnalysedOrAnalysedAndFound = 0, + AnalysedAndNotFound = 1, + }; } -#endif // GENERAL_FLAGS_HPP_INCLUDED +#endif From 131e0b9a739d80afe846d5b3caa91687aa13641b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 22 Dec 2022 22:03:55 +0100 Subject: [PATCH 002/151] Mark photos with no faces --- src/gui/desktop/utils/people_manipulator.cpp | 34 ++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_manipulator.cpp index 7857ce30b9..30d79cc3e9 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_manipulator.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -100,19 +101,32 @@ void PeopleManipulator::setName(std::size_t n, const QString& name) void PeopleManipulator::store() { - store_people_names(); - store_fingerprints(); + if (m_faces.empty()) + { + m_db.exec([ph_id = m_pid](Database::IBackend& backend) + { + backend.set( + ph_id, + Database::CommonGeneralFlags::FacesAnalysisState, + Database::CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound); + }); + } + else + { + store_people_names(); + store_fingerprints(); - // update names assigned to face locations - for (auto& face: m_faces) - face.face.p_id = face.person.id(); + // update names assigned to face locations + for (auto& face: m_faces) + face.face.p_id = face.person.id(); - // update fingerprints assigned to face locations - for (auto& face: m_faces) - if (face.face.f_id.valid() == false) - face.face.f_id = face.fingerprint.id(); + // update fingerprints assigned to face locations + for (auto& face: m_faces) + if (face.face.f_id.valid() == false) + face.face.f_id = face.fingerprint.id(); - store_people_information(); + store_people_information(); + } } From faeeec07e83e48b533cdad0238773464ad5cf641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 23 Dec 2022 19:50:51 +0100 Subject: [PATCH 003/151] Make it clear what's going on --- src/gui/desktop/utils/people_manipulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_manipulator.cpp index 30d79cc3e9..2c2963b4d5 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_manipulator.cpp @@ -172,6 +172,7 @@ void PeopleManipulator::findFaces_thrd() std::copy(list_of_faces.cbegin(), list_of_faces.cend(), std::back_inserter(result)); } + // sort faces so they appear from left to right std::sort(result.begin(), result.end(), [](const QRect& lhs, const QRect& rhs) { if (lhs.right() < rhs.left()) // lhs if left to rhs? - in order return true; From 957af3f899ddebc87debe249bc8a55afb1ac610f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 23 Dec 2022 21:55:24 +0100 Subject: [PATCH 004/151] Do not load faces when we already know there are none --- src/gui/desktop/utils/people_manipulator.cpp | 41 +++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_manipulator.cpp index 2c2963b4d5..66fcbdbc42 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_manipulator.cpp @@ -29,6 +29,8 @@ #include +using namespace Database::CommonGeneralFlags; + namespace { Person::Fingerprint average_fingerprint(const std::vector& faces) @@ -107,8 +109,8 @@ void PeopleManipulator::store() { backend.set( ph_id, - Database::CommonGeneralFlags::FacesAnalysisState, - Database::CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound); + FacesAnalysisState, + FacesAnalysisType::AnalysedAndNotFound); }); } else @@ -150,26 +152,37 @@ void PeopleManipulator::findFaces_thrd() { QVector result; + // check if we already know there are no faces on this photo + const bool facesNotFound = evaluate(m_db, [this](Database::IBackend& backend) + { + const auto analysisState = backend.get(m_pid, FacesAnalysisState); + + return analysisState? *analysisState == static_cast(FacesAnalysisType::AnalysedAndNotFound): false; + }); + const QString path = pathFor(m_pid); const QFileInfo pathInfo(path); const QString full_path = pathInfo.absoluteFilePath(); m_image = OrientedImage(m_core.getExifReaderFactory().get(), full_path); - const std::vector list_of_faces = fetchFacesFromDb(); - - if (list_of_faces.empty()) + if (facesNotFound == false) { - FaceRecognition face_recognition(&m_core); - const auto faces = face_recognition.fetchFaces(full_path); + const std::vector list_of_faces = fetchFacesFromDb(); - for(const QRect& face: faces) - result.append(face); - } - else - { - result.reserve(static_cast(list_of_faces.size())); + if (list_of_faces.empty()) + { + FaceRecognition face_recognition(&m_core); + const auto faces = face_recognition.fetchFaces(full_path); + + for(const QRect& face: faces) + result.append(face); + } + else + { + result.reserve(static_cast(list_of_faces.size())); - std::copy(list_of_faces.cbegin(), list_of_faces.cend(), std::back_inserter(result)); + std::copy(list_of_faces.cbegin(), list_of_faces.cend(), std::back_inserter(result)); + } } // sort faces so they appear from left to right From 3093857b118302c559c6a5c2cf0e446a5c2f26c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 23 Dec 2022 21:55:48 +0100 Subject: [PATCH 005/151] Add base for batch face detection tool --- .../quick_items/Views/BatchFaceDetection.qml | 6 ++++++ src/gui/desktop/quick_items/Views/MainWindow.qml | 11 +++++++++++ tr/photo_broom_en.ts | 13 +++++++++---- tr/photo_broom_pl.ts | 11 ++++++++--- 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/gui/desktop/quick_items/Views/BatchFaceDetection.qml diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml new file mode 100644 index 0000000000..2eefc8447d --- /dev/null +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -0,0 +1,6 @@ + +import QtQuick 2.15 + +Item { + +} diff --git a/src/gui/desktop/quick_items/Views/MainWindow.qml b/src/gui/desktop/quick_items/Views/MainWindow.qml index 424cc0b9de..bba0313fc1 100644 --- a/src/gui/desktop/quick_items/Views/MainWindow.qml +++ b/src/gui/desktop/quick_items/Views/MainWindow.qml @@ -90,6 +90,7 @@ ApplicationWindow { Action { text: qsTr("S&eries detector..."); onTriggered: { toolsStackView.currentIndex = 1; mainView.currentIndex = 1; } } Action { text: qsTr("Ph&oto data completion..."); onTriggered: { toolsStackView.currentIndex = 2; mainView.currentIndex = 1; } } Action { text: qsTr("Look for &duplicates"); onTriggered: { toolsStackView.currentIndex = 3; mainView.currentIndex = 1; } } + Action { text: qsTr("&Face detection..."); onTriggered: { toolsStackView.currentIndex = 4; mainView.currentIndex = 1; } } } Menu { id: settingsMenu @@ -202,6 +203,16 @@ ApplicationWindow { sourceComponent: PhotoBroomProject.projectOpen? duplicates_view : undefined } + Loader { + active: PhotoBroomProject.projectOpen && toolsStackView.currentIndex == StackLayout.index + + Component { + id: batch_face_detection + BatchFaceDetection { } + } + + sourceComponent: PhotoBroomProject.projectOpen? batch_face_detection : undefined + } } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index a8f7c49b1a..a6fe53abdf 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -350,17 +350,22 @@ Check paths in configuration window. - + + &Face detection... + + + + &Settings - + Tasks - + Back to photos @@ -385,7 +390,7 @@ Check paths in configuration window. &Refresh collection... - + &Configuration diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index b94ae18cab..f86b982001 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -353,6 +353,11 @@ Sprawdź poprawność ścieżek w konfiguracji. Look for &duplicates Wyszukaj &duplikaty + + + &Face detection... + + &Settings @@ -369,7 +374,7 @@ Sprawdź poprawność ścieżek w konfiguracji. Powrót - + P&hotos &Zdjęcia @@ -394,12 +399,12 @@ Sprawdź poprawność ścieżek w konfiguracji. &Odśwież kolekcję... - + &Configuration &Ustawienia programu - + T&asks &Operacje From 4c62a4f8223a54a5559f2e687c7a93fc61aaccdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 25 Dec 2022 23:01:30 +0100 Subject: [PATCH 006/151] Simplify FaceRecognition interface --- src/face_recognition/face_recognition.cpp | 32 +++++--------------- src/face_recognition/face_recognition.hpp | 9 +++--- src/gui/desktop/utils/people_manipulator.cpp | 9 +++--- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/face_recognition/face_recognition.cpp b/src/face_recognition/face_recognition.cpp index 1cad16133d..24f74174c5 100644 --- a/src/face_recognition/face_recognition.cpp +++ b/src/face_recognition/face_recognition.cpp @@ -63,22 +63,9 @@ namespace } -struct FaceRecognition::Data -{ - explicit Data(ICoreFactoryAccessor* coreAccessor) - : m_logger(coreAccessor->getLoggerFactory().get("FaceRecognition")) - , m_exif(coreAccessor->getExifReaderFactory().get()) - { - - } - std::unique_ptr m_logger; - IExifReader& m_exif; -}; - - -FaceRecognition::FaceRecognition(ICoreFactoryAccessor* coreAccessor): - m_data(std::make_unique(coreAccessor)) +FaceRecognition::FaceRecognition(const std::unique_ptr& logger) + : m_logger(logger->subLogger("FaceRecognition")) { } @@ -96,15 +83,12 @@ bool FaceRecognition::checkSystem() } -QVector FaceRecognition::fetchFaces(const QString& path) const +std::vector FaceRecognition::fetchFaces(const OrientedImage& orientedPhoto) const { - OrientedImage orientedPhoto(m_data->m_exif, path); - const int pixels = orientedPhoto->width() * orientedPhoto->height(); const double mpixels = pixels / 1e6; - m_data->m_logger->debug(QString("Looking for faces in photo %1. Size: %2Mpx") - .arg(path) + m_logger->debug(QString("Looking for faces in photo of size: %2Mpx") .arg(mpixels, 0, 'f', 1) ); @@ -114,12 +98,12 @@ QVector FaceRecognition::fetchFaces(const QString& path) const const auto faces = fetchFaces(orientedPhoto, 1); const auto elapsed = timer.elapsed(); - m_data->m_logger->info(QString("Found %1 faces in time: %2ms") + m_logger->info(QString("Found %1 faces in time: %2ms") .arg(faces.size()) .arg(elapsed) ); - return faces; + return std::vector(faces.begin(), faces.end()); } @@ -127,7 +111,7 @@ Person::Fingerprint FaceRecognition::getFingerprint(const OrientedImage& image, { const QImage face = face_rect.isEmpty()? image.get(): image.get().copy(face_rect); - dlib_api::FaceEncoder faceEndoder(m_data->m_logger.get()); + dlib_api::FaceEncoder faceEndoder(m_logger.get()); const dlib_api::FaceEncodings face_encodings = faceEndoder.face_encodings(face); return face_encodings; @@ -151,7 +135,7 @@ QVector FaceRecognition::fetchFaces(const OrientedImage& orientedPhoto, d const QSize scaledSize = orientedPhoto.get().size() * scale; const QImage photo = orientedPhoto.get().scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - result = dlib_api::FaceLocator(m_data->m_logger.get()).face_locations(photo, 0); + result = dlib_api::FaceLocator(m_logger.get()).face_locations(photo, 0); std::transform(result.begin(), result.end(), result.begin(), [scale](const QRect& face){ return QRectF(face.topLeft().x() / scale, face.topLeft().y() / scale, diff --git a/src/face_recognition/face_recognition.hpp b/src/face_recognition/face_recognition.hpp index 6201f00616..5de292b91d 100644 --- a/src/face_recognition/face_recognition.hpp +++ b/src/face_recognition/face_recognition.hpp @@ -43,7 +43,7 @@ namespace Database class FACE_RECOGNITION_EXPORT FaceRecognition final { public: - FaceRecognition(ICoreFactoryAccessor *); + FaceRecognition(const std::unique_ptr& logger); FaceRecognition(const FaceRecognition &) = delete; ~FaceRecognition(); @@ -54,17 +54,16 @@ class FACE_RECOGNITION_EXPORT FaceRecognition final static bool checkSystem(); // Locate faces on given photo. - QVector fetchFaces(const QString &) const; + std::vector fetchFaces(const OrientedImage &) const; Person::Fingerprint getFingerprint(const OrientedImage& image, const QRect& face = QRect()); int recognize(const Person::Fingerprint& unknown, const std::vector& known); private: - struct Data; - std::unique_ptr m_data; + std::unique_ptr m_logger; QVector fetchFaces(const OrientedImage &, double scale) const; }; -#endif // FACERECOGNITION_HPP +#endif diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_manipulator.cpp index 66fcbdbc42..670d52bdf1 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_manipulator.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -171,8 +172,8 @@ void PeopleManipulator::findFaces_thrd() if (list_of_faces.empty()) { - FaceRecognition face_recognition(&m_core); - const auto faces = face_recognition.fetchFaces(full_path); + FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); + const auto faces = face_recognition.fetchFaces(m_image); for(const QRect& face: faces) result.append(face); @@ -264,7 +265,7 @@ void PeopleManipulator::recognizeFaces_thrd_calculate_missing_fingerprints() for (FaceInfo& faceInfo: m_faces) if (faceInfo.fingerprint.id().valid() == false) { - FaceRecognition face_recognition(&m_core); + FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); const auto fingerprint = face_recognition.getFingerprint(m_image, faceInfo.face.rect); @@ -275,7 +276,7 @@ void PeopleManipulator::recognizeFaces_thrd_calculate_missing_fingerprints() void PeopleManipulator::recognizeFaces_thrd_recognize_people() { - FaceRecognition face_recognition(&m_core); + FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); const auto people_fingerprints = fetchPeopleAndFingerprints(); const std::vector& known_fingerprints = std::get<0>(people_fingerprints); From fff7e4306ca590d1def9bd37af3eee4f537ee9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 27 Dec 2022 21:10:48 +0100 Subject: [PATCH 007/151] Reorganize PeopleManipulator --- src/gui/desktop/models/faces_model.cpp | 58 +- src/gui/desktop/models/faces_model.hpp | 8 +- src/gui/desktop/utils/people_manipulator.cpp | 606 +++++++++---------- src/gui/desktop/utils/people_manipulator.hpp | 77 +-- 4 files changed, 333 insertions(+), 416 deletions(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index 0265ba678b..0592bba2a6 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -22,10 +23,7 @@ ENUM_ROLES_SETUP(FacesModel::Roles); FacesModel::FacesModel(QObject *parent): - QAbstractListModel(parent), - m_id(), - m_peopleManipulator(), - m_faces() + QAbstractListModel(parent) { QMetaObject::invokeMethod(this, &FacesModel::initialSetup, Qt::QueuedConnection); } @@ -33,7 +31,8 @@ FacesModel::FacesModel(QObject *parent): FacesModel::~FacesModel() { - apply(); + if (m_database) + apply(); } @@ -61,25 +60,26 @@ int FacesModel::state() const QList FacesModel::facesMask() const { - if (m_photoSize.isValid()) + if (m_faces.empty()) + return {}; + else { - QRegion reg(0, 0, m_photoSize.width(), m_photoSize.height()); + const QSize photoSize = m_faces.front()->image()->size(); + QRegion reg(0, 0, photoSize.width(), photoSize.height()); - for (const QRect& rect: m_faces) - reg-=QRegion(rect); + for (const auto& face: m_faces) + reg -= QRegion(face->rect()); const QList qmlListOfRects(reg.begin(), reg.end()); return qmlListOfRects; } - else - return {}; } int FacesModel::rowCount(const QModelIndex& parent) const { - return parent.isValid() == false? static_cast(m_facesCount): 0; + return parent.isValid() == false? static_cast(m_faces.size()): 0; } @@ -87,12 +87,12 @@ QVariant FacesModel::data(const QModelIndex& index, int role) const { const std::size_t row = static_cast(index.row()); - if (index.column() == 0 && row < m_peopleManipulator->facesCount()) + if (index.column() == 0 && row < m_faces.size()) { if (role == Qt::DisplayRole) - return m_peopleManipulator->name(row); + return m_faces[row]->name(); else if (role == Roles::FaceRectRole) - return m_peopleManipulator->position(row); + return m_faces[row]->rect(); } return {}; @@ -107,32 +107,29 @@ QHash FacesModel::roleNames() const bool FacesModel::setData(const QModelIndex& index, const QVariant& data, int role) { - if (role == Qt::EditRole && index.column() == 0 && index.row() < m_facesCount) - m_peopleManipulator->setName(index.row(), data.toString()); + const std::size_t r = static_cast(index.row()); + if (role == Qt::EditRole && index.column() == 0 && r < m_faces.size()) + m_faces[r]->setName(data.toString()); return true; } -void FacesModel::updateFaceInformation() +void FacesModel::updateFaceInformation(std::shared_ptr>> faces) { - const auto faces_count = m_peopleManipulator->facesCount(); + const int faces_count = static_cast(faces->size()); updateDetectionState(faces_count == 0? 2: 1); m_faces.clear(); - for(std::size_t i = 0; i < faces_count; i++) - m_faces.push_back(m_peopleManipulator->position(i)); if (faces_count > 0) { beginInsertRows(QModelIndex(), 0, static_cast(faces_count - 1)); - m_facesCount = static_cast(m_peopleManipulator->facesCount()); + m_faces = std::move(*faces); endInsertRows(); } - m_photoSize = m_peopleManipulator->photoSize(); - emit facesMaskChanged(facesMask()); } @@ -142,11 +139,12 @@ void FacesModel::initialSetup() assert(m_id.valid()); assert(m_database); assert(m_core); - assert(m_peopleManipulator.get() == nullptr); - m_peopleManipulator = std::make_unique(m_id, *m_database, *m_core); - connect(m_peopleManipulator.get(), &PeopleManipulator::facesAnalyzed, - this, &FacesModel::updateFaceInformation); + runOn(m_core->getTaskExecutor(), [this]() + { + const auto faces = std::make_shared>>(FaceEditor(*m_database, *m_core).getFacesFor(m_id)); + invokeMethod(this, &FacesModel::updateFaceInformation, faces); + }); updateDetectionState(0); } @@ -160,6 +158,6 @@ void FacesModel::updateDetectionState(int state) void FacesModel::apply() { - if (m_database) - m_peopleManipulator->store(); + for(auto& face: m_faces) + face->store(); } diff --git a/src/gui/desktop/models/faces_model.hpp b/src/gui/desktop/models/faces_model.hpp index f84202b9f8..361681c7f1 100644 --- a/src/gui/desktop/models/faces_model.hpp +++ b/src/gui/desktop/models/faces_model.hpp @@ -12,7 +12,6 @@ #include "utils/people_manipulator.hpp" - class FacesModel: public QAbstractListModel { Q_OBJECT @@ -51,14 +50,11 @@ class FacesModel: public QAbstractListModel Photo::Id m_id; Database::IDatabase* m_database = nullptr; ICoreFactoryAccessor* m_core = nullptr; - std::unique_ptr m_peopleManipulator; - QVector m_faces; - QString m_photoPath; + std::vector> m_faces; QSize m_photoSize; int m_state = 0; - int m_facesCount = 0; - void updateFaceInformation(); + void updateFaceInformation(std::shared_ptr>>); void initialSetup(); void updateDetectionState(int); diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_manipulator.cpp index 670d52bdf1..76d6b6d929 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_manipulator.cpp @@ -34,6 +34,25 @@ using namespace Database::CommonGeneralFlags; namespace { + struct FaceInfo + { + PersonInfo face; + PersonName person; + PersonFingerprint fingerprint; + + FaceInfo(const Photo::Id& id, const QRect& r) + { + face.ph_id = id; + face.rect = r; + } + + FaceInfo(const PersonInfo& pi) + : face(pi) + { + + } + }; + Person::Fingerprint average_fingerprint(const std::vector& faces) { if (faces.empty()) @@ -48,440 +67,396 @@ namespace return avg_face; } -} - - -PeopleManipulator::PeopleManipulator(const Photo::Id& pid, Database::IDatabase& db, ICoreFactoryAccessor& core) - : m_pid(pid) - , m_core(core) - , m_db(db) -{ - findFaces(); -} - - -PeopleManipulator::~PeopleManipulator() -{ - m_callback_ctrl.invalidate(); -} - - -std::size_t PeopleManipulator::facesCount() const -{ - return m_faces.size(); -} - -const QString& PeopleManipulator::name(std::size_t n) const -{ - return m_faces[n].person.name(); -} - - -const QRect& PeopleManipulator::position(std::size_t n) const -{ - return m_faces[n].face.rect; -} - - -QSize PeopleManipulator::photoSize() const -{ - return m_image->size(); -} - - -void PeopleManipulator::setName(std::size_t n, const QString& name) -{ - const QString trimmed_name = name.trimmed(); - if (n < m_faces.size() && m_faces[n].person.name() != trimmed_name) + bool wasPhotoAnalyzedAndHasNoFaces(Database::IDatabase& db, const Photo::Id& ph_id) { - PersonName new_name(trimmed_name); - m_faces[n].person = new_name; - } -} + return evaluate(db, [ph_id](Database::IBackend& backend) + { + const auto analysisState = backend.get(ph_id, FacesAnalysisState); + return analysisState? *analysisState == static_cast(FacesAnalysisType::AnalysedAndNotFound): false; + }); + } -void PeopleManipulator::store() -{ - if (m_faces.empty()) + QString pathFor(Database::IDatabase& db, const Photo::Id& id) { - m_db.exec([ph_id = m_pid](Database::IBackend& backend) + return evaluate(db, [id](Database::IBackend& backend) { - backend.set( - ph_id, - FacesAnalysisState, - FacesAnalysisType::AnalysedAndNotFound); + auto photo = backend.getPhotoDelta(id, {Photo::Field::Path}); + + return photo.get(); }); } - else - { - store_people_names(); - store_fingerprints(); - // update names assigned to face locations - for (auto& face: m_faces) - face.face.p_id = face.person.id(); + std::vector fetchFacesFromDb(Database::IDatabase& db, const Photo::Id& ph_id) + { + return evaluate(Database::IBackend &)>(db, [ph_id](Database::IBackend& backend) + { + std::vector faces; - // update fingerprints assigned to face locations - for (auto& face: m_faces) - if (face.face.f_id.valid() == false) - face.face.f_id = face.fingerprint.id(); + const auto people = backend.peopleInformationAccessor().listPeople(ph_id); + std::ranges::copy_if(people, std::back_inserter(faces), [](const PersonInfo& pi) { return pi.rect.isValid(); }); - store_people_information(); + return faces; + }); } -} - -void PeopleManipulator::runOnThread(void (PeopleManipulator::*method)()) -{ - auto task = std::bind(method, this); - auto safe_task = m_callback_ctrl.make_safe_callback<>(task); - auto& executor = m_core.getTaskExecutor(); - - runOn(executor, safe_task, "PeopleManipulator"); -} - - -void PeopleManipulator::findFaces() -{ - runOnThread(&PeopleManipulator::findFaces_thrd); -} - - -void PeopleManipulator::findFaces_thrd() -{ - QVector result; - - // check if we already know there are no faces on this photo - const bool facesNotFound = evaluate(m_db, [this](Database::IBackend& backend) + PersonName personName(Database::IDatabase& db, const Person::Id& id) { - const auto analysisState = backend.get(m_pid, FacesAnalysisState); + const PersonName person = evaluate + (db, [id](Database::IBackend& backend) + { + const auto people = backend.peopleInformationAccessor().person(id); - return analysisState? *analysisState == static_cast(FacesAnalysisType::AnalysedAndNotFound): false; - }); + return people; + }); - const QString path = pathFor(m_pid); - const QFileInfo pathInfo(path); - const QString full_path = pathInfo.absoluteFilePath(); - m_image = OrientedImage(m_core.getExifReaderFactory().get(), full_path); + return person; + } - if (facesNotFound == false) + std::tuple, std::vector> fetchPeopleAndFingerprints(Database::IDatabase& db) { - const std::vector list_of_faces = fetchFacesFromDb(); + typedef std::tuple, std::vector> Result; - if (list_of_faces.empty()) + return evaluate(db, [](Database::IBackend& backend) { - FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); - const auto faces = face_recognition.fetchFaces(m_image); + std::vector people_fingerprints; + std::vector people; - for(const QRect& face: faces) - result.append(face); - } - else - { - result.reserve(static_cast(list_of_faces.size())); + const auto all_people = backend.peopleInformationAccessor().listPeople(); + for(const auto& person: all_people) + { + const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(person.id()); - std::copy(list_of_faces.cbegin(), list_of_faces.cend(), std::back_inserter(result)); - } + if (fingerprints.empty() == false) + { + people_fingerprints.push_back(average_fingerprint(fingerprints)); + people.push_back(person.id()); + } + } + + return std::tuple(people_fingerprints, people); + }); } - // sort faces so they appear from left to right - std::sort(result.begin(), result.end(), [](const QRect& lhs, const QRect& rhs) { - if (lhs.right() < rhs.left()) // lhs if left to rhs? - in order - return true; - else if (lhs.left() > rhs.right()) // lhs is right to rhs? - not in order - return false; - else - return lhs.top() < rhs.top(); // when in line - lhs needs to be above - }); + std::vector detectFaces(const OrientedImage& image, const std::unique_ptr& logger) + { + return FaceRecognition(logger).fetchFaces(image); + } - invokeMethod(this, &PeopleManipulator::findFaces_result, result); -} + void calculateMissingFingerprints(std::vector& faces, const OrientedImage& image, const std::unique_ptr& logger) + { + FaceRecognition face_recognition(logger); + for (FaceInfo& faceInfo: faces) + if (faceInfo.fingerprint.id().valid() == false) + { + const auto fingerprint = face_recognition.getFingerprint(image, faceInfo.face.rect); + faceInfo.fingerprint = fingerprint; + } + } -void PeopleManipulator::findFaces_result(const QVector& faces) -{ - m_faces.reserve(faces.size()); + void recognizePeople(std::vector& faces, Database::IDatabase& db, const std::unique_ptr& logger) + { + FaceRecognition face_recognition(logger); + const auto people_fingerprints = fetchPeopleAndFingerprints(db); + const std::vector& known_fingerprints = std::get<0>(people_fingerprints); - std::copy(faces.cbegin(), faces.cend(), std::back_inserter(m_faces)); + for (FaceInfo& faceInfo: faces) + if (faceInfo.person.name().isEmpty()) + { + const int pos = face_recognition.recognize(faceInfo.fingerprint.fingerprint(), known_fingerprints); - for (auto& face: m_faces) - face.face.ph_id = m_pid; + if (pos >=0) + { + const std::vector& known_people = std::get<1>(people_fingerprints); + const Person::Id found_person = known_people[pos]; + faceInfo.person = personName(db, found_person); + } + } + } - recognizeFaces(); -} + class FacesSaver + { + public: + FacesSaver(Database::IDatabase &, ICoreFactoryAccessor &); + ~FacesSaver(); + void store(FaceInfo &); -void PeopleManipulator::recognizeFaces() -{ - runOnThread(&PeopleManipulator::recognizeFaces_thrd); -} + private: + ICoreFactoryAccessor& m_core; + Database::IDatabase& m_db; + std::vector m_people; + void store_person_name(FaceInfo& face); + void store_fingerprint(FaceInfo& face); + void store_person_information(const FaceInfo& face); -void PeopleManipulator::recognizeFaces_thrd_fetch_from_db() -{ - const std::vector peopleData = fetchPeopleFromDb(); + std::vector fetchPeople() const; + PersonName storeNewPerson(const QString& name) const; + }; - for (FaceInfo& faceInfo: m_faces) + struct Face: public IFace { - // check if we have data for given face rect in db - auto person_it = std::find_if(peopleData.cbegin(), peopleData.cend(), [faceInfo](const PersonInfo& pi) - { - return pi.rect == faceInfo.face.rect; - }); + Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) + : m_faceInfo(fi) + , m_image(image) + , m_saver(saver) + {} - if (person_it != peopleData.cend()) // rect matches + const QRect& rect() const override { - if (person_it->p_id.valid()) - faceInfo.person = personData(person_it->p_id); // fill name - - faceInfo.face = *person_it; + return m_faceInfo.face.rect; } - } - - // collect faces and try to access theirs fingerprints - std::vector faces_ids; - for (FaceInfo& faceInfo: m_faces) - if (faceInfo.face.id.valid()) - faces_ids.push_back(faceInfo.face.id); - - const auto fingerprints = fetchFingerprints(faces_ids); - - for (FaceInfo& faceInfo: m_faces) - if (faceInfo.face.id.valid()) + const QString& name() const override { - auto it = fingerprints.find(faceInfo.face.id); + return m_faceInfo.person.name(); + } - if (it != fingerprints.end()) - faceInfo.fingerprint = it->second; + const OrientedImage& image() const override + { + return *m_image; } -} + void setName(const QString& name) override + { + m_faceInfo.person = PersonName(name); + } -void PeopleManipulator::recognizeFaces_thrd_calculate_missing_fingerprints() -{ - for (FaceInfo& faceInfo: m_faces) - if (faceInfo.fingerprint.id().valid() == false) + void store() override { - FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); + m_saver->store(m_faceInfo); + } - const auto fingerprint = face_recognition.getFingerprint(m_image, faceInfo.face.rect); + FaceInfo m_faceInfo; + std::shared_ptr m_image; + std::shared_ptr m_saver; + }; - faceInfo.fingerprint = fingerprint; - } -} + void sortFaces(std::vector& faces) + { + // sort faces so they appear from left to right + std::sort(faces.begin(), faces.end(), [](const FaceInfo& lhs, const FaceInfo& rhs) { + const auto lhs_face = lhs.face.rect; + const auto rhs_face = rhs.face.rect; + + if (lhs_face.left() < rhs_face.left()) // lhs if left to rhs? - in order + return true; + else if (lhs_face.left() > rhs_face.right()) // lhs is right to rhs? - not in order + return false; + else + return lhs_face.top() < rhs_face.top(); // when in line - lhs needs to be above + }); + } + std::vector findFaces(Database::IDatabase& db, + const OrientedImage& image, + const std::unique_ptr& logger, + const Photo::Id& id) + { + std::vector result; -void PeopleManipulator::recognizeFaces_thrd_recognize_people() -{ - FaceRecognition face_recognition(m_core.getLoggerFactory().get("PeopleManipulator")); - const auto people_fingerprints = fetchPeopleAndFingerprints(); - const std::vector& known_fingerprints = std::get<0>(people_fingerprints); + const bool facesNotFound = wasPhotoAnalyzedAndHasNoFaces(db, id); - for (FaceInfo& faceInfo: m_faces) - if (faceInfo.person.name().isEmpty()) + // photo not analyzed yet (no records in db) or analyzed and we have data in db + if (facesNotFound == false) { - const int pos = face_recognition.recognize(faceInfo.fingerprint.fingerprint(), known_fingerprints); + const std::vector list_of_faces = fetchFacesFromDb(db, id); - if (pos >=0) + // no data in db + if (list_of_faces.empty()) { - const std::vector& known_people = std::get<1>(people_fingerprints); - const Person::Id found_person = known_people[pos]; - faceInfo.person = personData(found_person); - } - } -} + // analyze photo - look for faces + const auto detected_faces = detectFaces(image, logger); + std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) + { + return FaceInfo(id, rect);; + }); + //calculate fingerprints + calculateMissingFingerprints(result, image, logger); -void PeopleManipulator::recognizeFaces_thrd() -{ - recognizeFaces_thrd_fetch_from_db(); + //recognize people + recognizePeople(result, db, logger); - const bool missing_fingerprints = - std::any_of(m_faces.cbegin(), - m_faces.cend(), - [](const auto& face) + if (result.empty()) + { + db.exec([id](Database::IBackend& backend) + { + backend.set( + id, + FacesAnalysisState, + FacesAnalysisType::AnalysedAndNotFound); + }); + } + } + else // data in db just use it + { + evaluate(db, [&result, &list_of_faces](Database::IBackend& backend) + { + std::vector pi_ids; + std::ranges::transform(list_of_faces, std::back_inserter(pi_ids), [](const PersonInfo& pi) { - return face.fingerprint.id().valid() == false; + return pi.id; }); - if (missing_fingerprints) - { - recognizeFaces_thrd_calculate_missing_fingerprints(); - recognizeFaces_thrd_recognize_people(); - } + const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(pi_ids); + assert(fingerprints.size() == list_of_faces.size()); + + for (const PersonInfo& pi: list_of_faces) + { + FaceInfo fi(pi); + fi.fingerprint = fingerprints.find(pi.id)->second; + + if (pi.p_id.valid()) + fi.person = backend.peopleInformationAccessor().person(pi.p_id); + + result.push_back(fi); + }; + }); + } + } + + sortFaces(result); - invokeMethod(this, &PeopleManipulator::recognizeFaces_result); + return result; + } } -void PeopleManipulator::recognizeFaces_result() +FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core) + : m_db(db) + , m_core(core) { - emit facesAnalyzed(); + } -void PeopleManipulator::store_people_names() +std::vector> FaceEditor::getFacesFor(const Photo::Id& id) { - auto nameChanged = [](const PeopleManipulator::FaceInfo& faceInfo) { - // no person id and name is not empty? - return faceInfo.person.id().valid() == false && faceInfo.person.name().isEmpty() == false; - }; + const QString path = pathFor(m_db, id); + const QFileInfo pathInfo(path); + const QString full_path = pathInfo.absoluteFilePath(); + auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); - const bool anyNameChanged = std::ranges::any_of(m_faces, nameChanged); + const auto faces = findFaces( + m_db, + *image, + m_core.getLoggerFactory().get("FaceEditor"), + id); - if (anyNameChanged) - { - const std::vector people = fetchPeople(); + auto storage = std::make_shared(m_db, m_core); - // make sure each name is known (exists in db) - for (auto& face: m_faces) - if (nameChanged(face)) - { - const QString& name = face.person.name(); + std::vector> result; - auto it = std::find_if(people.cbegin(), people.cend(), [name](const PersonName& d) - { - return d.name() == name; - }); + std::ranges::transform(faces, std::back_inserter(result), [&image, &storage](const FaceInfo& fi) + { + return std::make_unique(fi, image, storage); + }); - if (it == people.cend()) // new name, store it in db - face.person = storeNewPerson(name); - else - face.person = *it; - } - } + return result; } -void PeopleManipulator::store_fingerprints() +FacesSaver::FacesSaver(Database::IDatabase& db, ICoreFactoryAccessor& core) + : m_core(core) + , m_db(db) { - for (auto& face: m_faces) - if (face.fingerprint.id().valid() == false) - { - const PersonFingerprint::Id fid = - evaluate(m_db, [fingerprint = face.fingerprint](Database::IBackend& backend) - { - return backend.peopleInformationAccessor().store(fingerprint); - }); - - const PersonFingerprint fingerprint(fid, face.fingerprint.fingerprint()); - face.fingerprint = fingerprint; - } + m_people = fetchPeople(); } -void PeopleManipulator::store_people_information() +FacesSaver::~FacesSaver() { - for (const auto& face: m_faces) - { - const PersonInfo& faceInfo = face.face; - const PersonFingerprint& fingerprint = face.fingerprint; - m_db.exec([faceInfo, fingerprint](Database::IBackend& backend) - { - backend.peopleInformationAccessor().store(faceInfo); - }); - } } -std::vector PeopleManipulator::fetchFacesFromDb() const +void FacesSaver::store(FaceInfo& face) { - return evaluate(Database::IBackend &)> - (m_db, [id = m_pid](Database::IBackend& backend) - { - std::vector faces; - - const auto people = backend.peopleInformationAccessor().listPeople(id); - for(const auto& person: people) - if (person.rect.isValid()) - faces.push_back(person.rect); + store_person_name(face); + store_fingerprint(face); - return faces; - }); -} + // update names assigned to face + face.face.p_id = face.person.id(); + // update fingerprints assigned to face + if (face.face.f_id.valid() == false) + face.face.f_id = face.fingerprint.id(); -std::vector PeopleManipulator::fetchPeopleFromDb() const -{ - return evaluate(Database::IBackend &)> - (m_db, [id = m_pid](Database::IBackend& backend) - { - auto people = backend.peopleInformationAccessor().listPeople(id); - - return people; - }); + store_person_information(face); } -std::tuple, std::vector> PeopleManipulator::fetchPeopleAndFingerprints() const +void FacesSaver::store_person_name(FaceInfo& face) { - typedef std::tuple, std::vector> Result; + const bool nameChanged = + face.person.id().valid() == false && face.person.name().isEmpty() == false; - return evaluate(m_db, [](Database::IBackend& backend) + if (nameChanged) { - std::vector people_fingerprints; - std::vector people; + // introduce name associated with face to db (if needed) + const QString& name = face.person.name(); - const auto all_people = backend.peopleInformationAccessor().listPeople(); - for(const auto& person: all_people) + auto it = std::find_if(m_people.cbegin(), m_people.cend(), [name](const PersonName& d) { - const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(person.id()); + return d.name() == name; + }); - if (fingerprints.empty() == false) - { - people_fingerprints.push_back(average_fingerprint(fingerprints)); - people.push_back(person.id()); - } + if (it == m_people.cend()) // new name, store it in db + { + face.person = storeNewPerson(name); + m_people.push_back(face.person); } - - return std::tuple(people_fingerprints, people); - }); + else + face.person = *it; + } } -std::map PeopleManipulator::fetchFingerprints(const std::vector& ids) const +void FacesSaver::store_fingerprint(FaceInfo& face) { - typedef std::map Result; - - return evaluate(m_db, [ids](Database::IBackend& backend) + if (face.fingerprint.id().valid() == false) { - const Result result = backend.peopleInformationAccessor().fingerprintsFor(ids); + const PersonFingerprint::Id fid = + evaluate(m_db, [fingerprint = face.fingerprint](Database::IBackend& backend) + { + return backend.peopleInformationAccessor().store(fingerprint); + }); - return result; - }); + const PersonFingerprint fingerprint(fid, face.fingerprint.fingerprint()); + face.fingerprint = fingerprint; + } } -std::vector PeopleManipulator::fetchPeople() const +void FacesSaver::store_person_information(const FaceInfo& face) { - return evaluate(Database::IBackend &)>(m_db, [](Database::IBackend& backend) - { - auto people = backend.peopleInformationAccessor().listPeople(); + const PersonInfo& faceInfo = face.face; + const PersonFingerprint& fingerprint = face.fingerprint; - return people; + m_db.exec([faceInfo, fingerprint](Database::IBackend& backend) + { + backend.peopleInformationAccessor().store(faceInfo); }); } -PersonName PeopleManipulator::personData(const Person::Id& id) const +std::vector FacesSaver::fetchPeople() const { - const PersonName person = evaluate - (m_db, [id](Database::IBackend& backend) + return evaluate(Database::IBackend &)>(m_db, [](Database::IBackend& backend) { - const auto people = backend.peopleInformationAccessor().person(id); + auto people = backend.peopleInformationAccessor().listPeople(); return people; }); - - return person; } -PersonName PeopleManipulator::storeNewPerson(const QString& name) const +PersonName FacesSaver::storeNewPerson(const QString& name) const { const PersonName person = evaluate (m_db, [name](Database::IBackend& backend) @@ -493,14 +468,3 @@ PersonName PeopleManipulator::storeNewPerson(const QString& name) const return person; } - - -QString PeopleManipulator::pathFor(const Photo::Id& id) const -{ - return evaluate(m_db, [id](Database::IBackend& backend) - { - auto photo = backend.getPhotoDelta(id, {Photo::Field::Path}); - - return photo.get(); - }); -} diff --git a/src/gui/desktop/utils/people_manipulator.hpp b/src/gui/desktop/utils/people_manipulator.hpp index 3e59970495..e577577ed5 100644 --- a/src/gui/desktop/utils/people_manipulator.hpp +++ b/src/gui/desktop/utils/people_manipulator.hpp @@ -26,71 +26,30 @@ struct ICoreFactoryAccessor; - -class PeopleManipulator: public QObject +class IFace { - Q_OBJECT - - public: - PeopleManipulator(const Photo::Id &, Database::IDatabase &, ICoreFactoryAccessor &); - ~PeopleManipulator(); - - std::size_t facesCount() const; - const QString& name(std::size_t) const; - const QRect& position(std::size_t) const; - QSize photoSize() const; - - void setName(std::size_t, const QString &); - void store(); - - signals: - void facesAnalyzed() const; +public: + virtual ~IFace() = default; - private: - struct FaceInfo - { - PersonInfo face; - PersonName person; - PersonFingerprint fingerprint; + virtual const QRect& rect() const = 0; + virtual const QString& name() const = 0; + virtual const OrientedImage& image() const = 0; - FaceInfo(const QRect& r) - { - face.rect = r; - } - }; - - safe_callback_ctrl m_callback_ctrl; - std::vector m_faces; - OrientedImage m_image; - Photo::Id m_pid; - ICoreFactoryAccessor& m_core; - Database::IDatabase& m_db; - - void runOnThread(void (PeopleManipulator::*)()); + virtual void setName(const QString &) = 0; + virtual void store() = 0; +}; - void findFaces(); - void findFaces_thrd(); - void findFaces_result(const QVector &); - void recognizeFaces(); - void recognizeFaces_thrd_fetch_from_db(); - void recognizeFaces_thrd_calculate_missing_fingerprints(); - void recognizeFaces_thrd_recognize_people(); - void recognizeFaces_thrd(); - void recognizeFaces_result(); +class FaceEditor +{ +public: + FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &); - void store_people_names(); - void store_fingerprints(); - void store_people_information(); + std::vector> getFacesFor(const Photo::Id &); - std::vector fetchFacesFromDb() const; - std::vector fetchPeopleFromDb() const; - std::tuple, std::vector> fetchPeopleAndFingerprints() const; - std::map fetchFingerprints(const std::vector& ids) const; - std::vector fetchPeople() const; - PersonName personData(const Person::Id& id) const; - PersonName storeNewPerson(const QString& name) const; - QString pathFor(const Photo::Id& id) const; +private: + Database::IDatabase& m_db; + ICoreFactoryAccessor& m_core; }; -#endif // PEOPLEMANIPULATOR_HPP +#endif From c9c93cddd01147d8090477d3212dbc6eb7c2d8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 27 Dec 2022 21:20:41 +0100 Subject: [PATCH 008/151] Rename files --- src/gui/desktop/models/faces_model.hpp | 2 +- src/gui/desktop/utils/CMakeLists.txt | 4 ++-- .../utils/{people_manipulator.cpp => people_editor.cpp} | 2 +- .../utils/{people_manipulator.hpp => people_editor.hpp} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename src/gui/desktop/utils/{people_manipulator.cpp => people_editor.cpp} (99%) rename src/gui/desktop/utils/{people_manipulator.hpp => people_editor.hpp} (100%) diff --git a/src/gui/desktop/models/faces_model.hpp b/src/gui/desktop/models/faces_model.hpp index 361681c7f1..52864cb693 100644 --- a/src/gui/desktop/models/faces_model.hpp +++ b/src/gui/desktop/models/faces_model.hpp @@ -10,7 +10,7 @@ #include #include -#include "utils/people_manipulator.hpp" +#include "utils/people_editor.hpp" class FacesModel: public QAbstractListModel { diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index d0ac8c15a4..249a70fa44 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -29,10 +29,10 @@ set(UTILS_SOURCES model_index_utils.hpp painter_helpers.cpp painter_helpers.hpp + people_editor.cpp + people_editor.hpp people_list_model.cpp people_list_model.hpp - people_manipulator.cpp - people_manipulator.hpp photos_collector.cpp photos_collector.hpp qml_setup.cpp diff --git a/src/gui/desktop/utils/people_manipulator.cpp b/src/gui/desktop/utils/people_editor.cpp similarity index 99% rename from src/gui/desktop/utils/people_manipulator.cpp rename to src/gui/desktop/utils/people_editor.cpp index 76d6b6d929..b7d1b8ad9e 100644 --- a/src/gui/desktop/utils/people_manipulator.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "people_manipulator.hpp" +#include "people_editor.hpp" #include diff --git a/src/gui/desktop/utils/people_manipulator.hpp b/src/gui/desktop/utils/people_editor.hpp similarity index 100% rename from src/gui/desktop/utils/people_manipulator.hpp rename to src/gui/desktop/utils/people_editor.hpp From 5bf9206d0eb4ecc4025c333feebc15caf7c900ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 27 Dec 2022 21:32:30 +0100 Subject: [PATCH 009/151] Mark constructor explicit --- src/gui/desktop/utils/people_editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index b7d1b8ad9e..2ceeec951c 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -46,7 +46,7 @@ namespace face.rect = r; } - FaceInfo(const PersonInfo& pi) + explicit FaceInfo(const PersonInfo& pi) : face(pi) { From 8cdd6db758d4be388be57c6b95dca34475391a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 28 Dec 2022 15:04:50 +0100 Subject: [PATCH 010/151] Improve height of people list --- src/gui/desktop/quick_items/Views/FacesDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/quick_items/Views/FacesDialog.qml b/src/gui/desktop/quick_items/Views/FacesDialog.qml index 58729d60b2..f24c46595a 100644 --- a/src/gui/desktop/quick_items/Views/FacesDialog.qml +++ b/src/gui/desktop/quick_items/Views/FacesDialog.qml @@ -147,8 +147,8 @@ Item { anchors.top: parent.top anchors.right: parent.right + anchors.bottom: toolsArea.top implicitWidth: contentWidth - height: contentHeight model: facesModel From e456991a97053286efd24bdcd9cc3f18a811f493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 28 Dec 2022 21:10:42 +0100 Subject: [PATCH 011/151] Base for batch face recognition --- src/gui/desktop/models/CMakeLists.txt | 2 + .../models/batch_face_recognition_model.cpp | 55 +++++++++++++++++++ .../models/batch_face_recognition_model.hpp | 34 ++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/gui/desktop/models/batch_face_recognition_model.cpp create mode 100644 src/gui/desktop/models/batch_face_recognition_model.hpp diff --git a/src/gui/desktop/models/CMakeLists.txt b/src/gui/desktop/models/CMakeLists.txt index d0d761b55f..adfd937115 100644 --- a/src/gui/desktop/models/CMakeLists.txt +++ b/src/gui/desktop/models/CMakeLists.txt @@ -5,6 +5,8 @@ set(SRC aheavy_list_model.hpp # needed for moc aphoto_data_model.cpp aphoto_data_model.hpp + batch_face_recognition_model.cpp + batch_face_recognition_model.hpp duplicates_model.cpp duplicates_model.hpp faces_model.cpp diff --git a/src/gui/desktop/models/batch_face_recognition_model.cpp b/src/gui/desktop/models/batch_face_recognition_model.cpp new file mode 100644 index 0000000000..0cab713cc7 --- /dev/null +++ b/src/gui/desktop/models/batch_face_recognition_model.cpp @@ -0,0 +1,55 @@ + +#include +#include + +#include "batch_face_recognition_model.hpp" + + +BatchFaceRecognitionModel::BatchFaceRecognitionModel() +{ + // perform initialization when all required properties are set + QMetaObject::invokeMethod(this, [this] + { + assert(m_db != nullptr); + assert(m_core != nullptr); + + beginAnalysis(); + }, + Qt::QueuedConnection); +} + + +int BatchFaceRecognitionModel::rowCount(const QModelIndex& parent) const +{ + return 0; +} + + +QVariant BatchFaceRecognitionModel::data(const QModelIndex& index, int role) const +{ + QVariant result; + + return result; +} + + +void BatchFaceRecognitionModel::setDatabase(Database::IDatabase* db) +{ + m_db = db; +} + + +Database::IDatabase* BatchFaceRecognitionModel::database() +{ + return m_db; +} + + +void BatchFaceRecognitionModel::beginAnalysis() +{ + m_db->exec([](Database::IBackend& backend) + { + auto baseFitler = Database::getValidPhotosFilter(); + backend.photoOperator().getPhotos(); + }); +} diff --git a/src/gui/desktop/models/batch_face_recognition_model.hpp b/src/gui/desktop/models/batch_face_recognition_model.hpp new file mode 100644 index 0000000000..1f0bf2f47e --- /dev/null +++ b/src/gui/desktop/models/batch_face_recognition_model.hpp @@ -0,0 +1,34 @@ + +#ifndef BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED +#define BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED + +#include + +#include +#include + + +class BatchFaceRecognitionModel: public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(Database::IDatabase* database WRITE setDatabase READ database REQUIRED) + Q_PROPERTY(ICoreFactoryAccessor* core MEMBER m_core REQUIRED) + +public: + BatchFaceRecognitionModel(); + + int rowCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + void setDatabase(Database::IDatabase *); + Database::IDatabase* database(); + +private: + Database::IDatabase* m_db; + ICoreFactoryAccessor* m_core; + + void beginAnalysis(); +}; + +#endif From a32e766b9e63fc20f494180e67ee8a47178539e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 22 Jan 2023 10:52:20 +0100 Subject: [PATCH 012/151] Format code --- src/database/photo_data.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/photo_data.hpp b/src/database/photo_data.hpp index 3f8c99340e..f93ece43df 100644 --- a/src/database/photo_data.hpp +++ b/src/database/photo_data.hpp @@ -47,7 +47,7 @@ namespace Photo QString path; QSize geometry; GroupInfo groupInfo; - Photo::PHashT phash; + Photo::PHashT phash; Data() = default; Data(const Data &) = default; @@ -66,7 +66,7 @@ namespace Photo /** - * @brief Structure containing chosen of photo details + * @brief Structure containing chosen details of photo */ class DATABASE_EXPORT DataDelta { From c34a9e969ab5c79273d8f5caa74d8a3f520b55a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 3 Feb 2023 23:25:29 +0100 Subject: [PATCH 013/151] Define operator<=> for QString and QRect --- src/core/qt_operators.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/core/qt_operators.hpp diff --git a/src/core/qt_operators.hpp b/src/core/qt_operators.hpp new file mode 100644 index 0000000000..40121fef46 --- /dev/null +++ b/src/core/qt_operators.hpp @@ -0,0 +1,24 @@ + +#ifndef QT_OPERATORS_HPP_INCLUDED +#define QT_OPERATORS_HPP_INCLUDED + +#include +#include +#include + +inline auto operator<=>(const QRect& lhs, const QRect& rhs) +{ + return std::make_tuple(lhs.x(), lhs.y(), lhs.width(), lhs.height()) <=> std::make_tuple(rhs.x(), rhs.y(), rhs.width(), rhs.height()); +} + +inline auto operator<=>(const QString& lhs, const QString& rhs) +{ + if (lhs < rhs) + return std::strong_ordering::less; + else if (lhs > rhs) + return std::strong_ordering::greater; + else + return std::strong_ordering::equal; +} + +#endif From 1ebf9a8a99555410096ce919b10f8e8d18b28192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 3 Feb 2023 23:34:39 +0100 Subject: [PATCH 014/151] Prepare Photo::DataDelta for storing full people --- src/database/person_data.hpp | 32 +++++++++++++++++------------- src/database/photo_data.hpp | 3 ++- src/database/photo_data_fields.hpp | 16 +++++++++------ 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/database/person_data.hpp b/src/database/person_data.hpp index 51f40c9322..c5a2f2414f 100644 --- a/src/database/person_data.hpp +++ b/src/database/person_data.hpp @@ -24,8 +24,9 @@ #include #include +#include -#include "photo_data.hpp" +#include "photo_types.hpp" #include "database_export.h" @@ -46,10 +47,7 @@ class DATABASE_EXPORT PersonName final ~PersonName() = default; PersonName& operator=(const PersonName &) = default; - bool operator<(const PersonName& other) const - { - return std::tie(m_id, m_name) < std::tie(other.m_id, other.m_name); - } + auto operator<=>(const PersonName &) const = default; const Person::Id& id() const; const QString& name() const; @@ -69,6 +67,8 @@ class DATABASE_EXPORT PersonFingerprint PersonFingerprint(const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint) {} PersonFingerprint(const Id& id, const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint), m_id(id) {} + auto operator<=>(const PersonFingerprint &) const = default; + const Id& id() const { return m_id; } const Person::Fingerprint& fingerprint() const { return m_fingerprint; } @@ -110,17 +110,21 @@ class DATABASE_EXPORT PersonInfo PersonInfo(const PersonInfo &) = default; PersonInfo& operator=(const PersonInfo &) = default; + auto operator<=>(const PersonInfo &) const = default; +}; - bool operator==(const PersonInfo& other) const - { - return id == other.id && - p_id == other.p_id && - ph_id == other.ph_id && - f_id == other.f_id && - rect == other.rect; - } + +class PersonFullInfo +{ +public: + QRect position; + PersonFingerprint fingerprint; + PersonName name; + + auto operator<=>(const PersonFullInfo &) const = default; }; + Q_DECLARE_METATYPE( PersonName ) -#endif // PERSONDATA_HPP +#endif diff --git a/src/database/photo_data.hpp b/src/database/photo_data.hpp index f93ece43df..1a71411505 100644 --- a/src/database/photo_data.hpp +++ b/src/database/photo_data.hpp @@ -128,7 +128,8 @@ namespace Photo DeltaTypes::Storage, DeltaTypes::Storage, DeltaTypes::Storage, - DeltaTypes::Storage + DeltaTypes::Storage, + DeltaTypes::Storage > Storage; Photo::Id m_id; diff --git a/src/database/photo_data_fields.hpp b/src/database/photo_data_fields.hpp index 1bf08c2d33..00dfae4e4d 100644 --- a/src/database/photo_data_fields.hpp +++ b/src/database/photo_data_fields.hpp @@ -3,6 +3,8 @@ #define PHOTO_DATA_FIELDS_HPP_INCLUDED #include "photo_types.hpp" +#include "person_data.hpp" + namespace Photo { @@ -14,15 +16,17 @@ namespace Photo Geometry, GroupInfo, PHash, + People, }; template struct DeltaTypes {}; - template<> struct DeltaTypes { using Storage = Tag::TagsList; }; - template<> struct DeltaTypes { using Storage = Photo::FlagValues; }; - template<> struct DeltaTypes { using Storage = QString; }; - template<> struct DeltaTypes { using Storage = QSize; }; - template<> struct DeltaTypes { using Storage = GroupInfo; }; - template<> struct DeltaTypes { using Storage = Photo::PHashT; }; + template<> struct DeltaTypes { using Storage = Tag::TagsList; }; + template<> struct DeltaTypes { using Storage = Photo::FlagValues; }; + template<> struct DeltaTypes { using Storage = QString; }; + template<> struct DeltaTypes { using Storage = QSize; }; + template<> struct DeltaTypes { using Storage = GroupInfo; }; + template<> struct DeltaTypes { using Storage = Photo::PHashT; }; + template<> struct DeltaTypes { using Storage = std::vector; }; } #endif From 98d77389edd698900f645a000223c075357e7ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 4 Feb 2023 10:24:39 +0100 Subject: [PATCH 015/151] Add ranges converter --- src/core/containers_utils.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/containers_utils.hpp b/src/core/containers_utils.hpp index d05cfa7165..b29db440b9 100644 --- a/src/core/containers_utils.hpp +++ b/src/core/containers_utils.hpp @@ -191,4 +191,11 @@ ForwardIt remove_unique(ForwardIt first, ForwardIt last) return remove_unique(first, last, std::equal_to{}); } + +template +CT range_to(R&& range) +{ + return CT(range.begin(), range.end()); +} + #endif From 61222326f861fd06295975c65abeddcb86cd5a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Feb 2023 20:08:17 +0100 Subject: [PATCH 016/151] Build fix --- src/gui/desktop/models/batch_face_recognition_model.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/models/batch_face_recognition_model.cpp b/src/gui/desktop/models/batch_face_recognition_model.cpp index 0cab713cc7..af4241fe35 100644 --- a/src/gui/desktop/models/batch_face_recognition_model.cpp +++ b/src/gui/desktop/models/batch_face_recognition_model.cpp @@ -49,7 +49,8 @@ void BatchFaceRecognitionModel::beginAnalysis() { m_db->exec([](Database::IBackend& backend) { - auto baseFitler = Database::getValidPhotosFilter(); - backend.photoOperator().getPhotos(); + auto baseFilter = Database::getValidPhotosFilter(); + auto statusFilter = Database::FilterFaceAnalysisStatus(); + backend.photoOperator().getPhotos(Database::GroupFilter({baseFilter, statusFilter})); }); } From 4043505097ece57fb499e4d6d468a6c24a47083a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Feb 2023 21:07:08 +0100 Subject: [PATCH 017/151] Introduce new filter --- src/database/filter.hpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/database/filter.hpp b/src/database/filter.hpp index 08efc7ae48..5cba9a885b 100644 --- a/src/database/filter.hpp +++ b/src/database/filter.hpp @@ -50,6 +50,7 @@ namespace Database struct FilterPhotosWithGeneralFlag; struct FilterPhotosWithPHash; struct FilterSimilarPhotos; + struct FilterFaceAnalysisStatus; typedef std::variant Filter; enum class ComparisonOp @@ -190,12 +192,21 @@ namespace Database ol::data_ptr filter; }; + struct DATABASE_EXPORT FilterFaceAnalysisStatus + { + enum Status + { + NotPerformed, + Performed, + } status; + }; + // helpers /** * @brief return filter which will filter out broken photos (missing, broken, deleted etc) */ - Filter getValidPhotosFilter(); + Filter DATABASE_EXPORT getValidPhotosFilter(); } #endif // FILTER_HPP From ec65d3797b86708260af5db96f0070ce498fb193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 7 Feb 2023 22:54:05 +0100 Subject: [PATCH 018/151] Store people data --- .../backends/sql_backends/sql_backend.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/database/backends/sql_backends/sql_backend.cpp b/src/database/backends/sql_backends/sql_backend.cpp index 203ea70c38..709a54b357 100644 --- a/src/database/backends/sql_backends/sql_backend.cpp +++ b/src/database/backends/sql_backends/sql_backend.cpp @@ -970,6 +970,21 @@ namespace Database if (status && data.has(Photo::Field::PHash)) photoOperator().setPHash(data.getId(), data.get()); + if (status && data.has(Photo::Field::People)) + { + const auto& people = data.get(); + auto& accessor = peopleInformationAccessor(); + + for(const auto& person: people) + { + const auto p_id = accessor.store(person.name); + const auto f_id = accessor.store(person.fingerprint); + + const PersonInfo pf(p_id, data.getId(), f_id, person.position); + accessor.store(pf); + } + } + photoChangeLogOperator().storeDifference(currentStateOfPhoto, data); return status; From e6e5efbe9c0dcf44eff40601079fd5e9e01f32ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 7 Feb 2023 22:54:11 +0100 Subject: [PATCH 019/151] typo fix --- src/database/ipeople_information_accessor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/ipeople_information_accessor.hpp b/src/database/ipeople_information_accessor.hpp index 8d905cfa62..25ec5e172a 100644 --- a/src/database/ipeople_information_accessor.hpp +++ b/src/database/ipeople_information_accessor.hpp @@ -46,7 +46,7 @@ namespace Database * \arg pi Details about person. It needs to refer to a valid photo id. \n * Also at least one of \a rect, \a person or \a id need to be valid * - * If \a pi has valid id and rect is invalid and person is is not valid, \n + * If \a pi has valid id and rect is invalid and person id is not valid, \n * then information about person is removed. \n * if \a pi has invalid id then database will be searched for exisiting \n * rect or person matching information in \a pi. If found, id will be \n From c94073112d6ec6ef21ce5af7865840b1b9a03330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 11 Feb 2023 19:06:31 +0100 Subject: [PATCH 020/151] Limit scope to the actual usage --- src/database/aphoto_change_log_operator.hpp | 2 +- src/database/implementation/aphoto_change_log_operator.cpp | 2 +- src/database/iphoto_change_log_operator.hpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/database/aphoto_change_log_operator.hpp b/src/database/aphoto_change_log_operator.hpp index 20bbe0ad3a..2632a18a92 100644 --- a/src/database/aphoto_change_log_operator.hpp +++ b/src/database/aphoto_change_log_operator.hpp @@ -10,7 +10,7 @@ namespace Database class DATABASE_EXPORT APhotoChangeLogOperator: public IPhotoChangeLogOperator { public: - void storeDifference(const Photo::FullDelta &, const Photo::DataDelta &) override; + void storeDifference(const DiffDelta &, const Photo::DataDelta &) override; void groupCreated(const Group::Id &, const Group::Type &, const Photo::Id& representative) override; void groupDeleted(const Group::Id &, const Photo::Id& representative, const std::vector& members) override; diff --git a/src/database/implementation/aphoto_change_log_operator.cpp b/src/database/implementation/aphoto_change_log_operator.cpp index c8549edc6c..e22ff20355 100644 --- a/src/database/implementation/aphoto_change_log_operator.cpp +++ b/src/database/implementation/aphoto_change_log_operator.cpp @@ -77,7 +77,7 @@ namespace namespace Database { - void APhotoChangeLogOperator::storeDifference(const Photo::FullDelta& currentContent, const Photo::DataDelta& newContent) + void APhotoChangeLogOperator::storeDifference(const DiffDelta& currentContent, const Photo::DataDelta& newContent) { assert(currentContent.getId() == newContent.getId()); const Photo::Id& id = currentContent.getId(); diff --git a/src/database/iphoto_change_log_operator.hpp b/src/database/iphoto_change_log_operator.hpp index c4580c4ce1..d56ceb854e 100644 --- a/src/database/iphoto_change_log_operator.hpp +++ b/src/database/iphoto_change_log_operator.hpp @@ -13,9 +13,11 @@ namespace Database { struct IPhotoChangeLogOperator { + using DiffDelta = Photo::ExplicitDelta; + virtual ~IPhotoChangeLogOperator() = default; - virtual void storeDifference(const Photo::FullDelta &, const Photo::DataDelta &) = 0; + virtual void storeDifference(const DiffDelta &, const Photo::DataDelta &) = 0; virtual void groupCreated(const Group::Id &, const Group::Type &, const Photo::Id& representative) = 0; virtual void groupDeleted(const Group::Id &, const Photo::Id& representative, const std::vector& members) = 0; From b61aee707b723ffbf0ec808fbee1c2644bbfe917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 11 Feb 2023 19:07:08 +0100 Subject: [PATCH 021/151] Do not store people data people are stored separately --- .../memory_backend/memory_backend.cpp | 165 +++++++++--------- .../memory_backend/memory_backend.hpp | 11 +- 2 files changed, 96 insertions(+), 80 deletions(-) diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index 87aad5542e..295d14a02b 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -11,11 +11,12 @@ namespace Database { + struct MemoryBackend::DB { std::map m_flags; std::map m_groups; - std::set> m_photos; + std::set> m_photos; std::set> m_peopleNames; std::set> m_peopleInfo; std::vector m_logEntries; @@ -26,92 +27,98 @@ namespace Database int m_nextGroup = 0; int m_nextPersonInfo = 0; }; -} - -namespace -{ - template - bool compare(const T& lhs, const T& rhs, Qt::SortOrder order) - { - return order == Qt::AscendingOrder? lhs < rhs: rhs < lhs; - } - int tristate_compare(const Photo::Data& lhs, const Photo::Data& rhs, const Tag::Types& tagType, Qt::SortOrder order) + namespace { - const auto l_it = lhs.tags.find(tagType); - const auto r_it = rhs.tags.find(tagType); - const auto l_tag = l_it == lhs.tags.end()? TagValue(): l_it->second; - const auto r_tag = r_it == rhs.tags.end()? TagValue(): r_it->second; + template + bool compare(const T& lhs, const T& rhs, Qt::SortOrder order) + { + return order == Qt::AscendingOrder? lhs < rhs: rhs < lhs; + } - const bool is_less = compare(l_tag, r_tag, order); - const bool is_greater = compare(r_tag, l_tag, order); + int tristate_compare(const Photo::Data& lhs, const Photo::Data& rhs, const Tag::Types& tagType, Qt::SortOrder order) + { + const auto l_it = lhs.tags.find(tagType); + const auto r_it = rhs.tags.find(tagType); + const auto l_tag = l_it == lhs.tags.end()? TagValue(): l_it->second; + const auto r_tag = r_it == rhs.tags.end()? TagValue(): r_it->second; - return (is_less? -1: 0) + (is_greater? 1: 0); - } + const bool is_less = compare(l_tag, r_tag, order); + const bool is_greater = compare(r_tag, l_tag, order); - std::vector filterPhotos(const std::vector& photos, - const Database::MemoryBackend::DB& db, - const Database::Filter& dbFilter) - { - std::vector result = photos; + return (is_less? -1: 0) + (is_greater? 1: 0); + } - std::visit([&result, &db](auto&& filter) + std::vector filterPhotos(const std::vector& photos, + const Database::MemoryBackend::DB& db, + const Database::Filter& dbFilter) { - using T = std::decay_t; - if constexpr (std::is_same_v) - { - result.erase(std::remove_if(result.begin(), result.end(), [](const Photo::FullDelta& photo) { - return !photo.get().valid(); - }), result.end()); - - std::sort(result.begin(), result.end(), [](const Photo::FullDelta& lhs, const Photo::FullDelta& rhs) { - return lhs.get() < rhs.get(); - }); + std::vector result = photos; - result.erase(remove_unique(result.begin(), result.end(), [](const Photo::FullDelta& lhs, const Photo::FullDelta& rhs) { - return lhs.get() == rhs.get(); - }), result.end()); - } - else if constexpr (std::is_same_v) - { - result.erase(std::remove_if(result.begin(), result.end(), [](const Photo::FullDelta& photo) { - return !photo.get().valid(); - }), result.end()); - } - else if constexpr (std::is_same_v) + std::visit([&result, &db](auto&& filter) { - result.erase(std::remove_if(result.begin(), result.end(), [&filter, &db](const Photo::FullDelta& photo) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + result.erase(std::remove_if(result.begin(), result.end(), [](const MemoryBackend::StoregeDelta& photo) { + return !photo.get().valid(); + }), result.end()); - int value = 0; - auto it = db.m_flags.find(photo.getId()); + std::sort(result.begin(), result.end(), [](const MemoryBackend::StoregeDelta& lhs, const MemoryBackend::StoregeDelta& rhs) { + return lhs.get() < rhs.get(); + }); - if (it != db.m_flags.end()) // if no flags for given photo, continue with value == 0 - { - const auto& flagsMap = it->second; - auto itm = flagsMap.find(filter.name); + result.erase(remove_unique(result.begin(), result.end(), [](const MemoryBackend::StoregeDelta& lhs, const MemoryBackend::StoregeDelta& rhs) { + return lhs.get() == rhs.get(); + }), result.end()); + } + else if constexpr (std::is_same_v) + { + result.erase(std::remove_if(result.begin(), result.end(), [](const MemoryBackend::StoregeDelta& photo) { + return !photo.get().valid(); + }), result.end()); + } + else if constexpr (std::is_same_v) + { + result.erase(std::remove_if(result.begin(), result.end(), [&filter, &db](const MemoryBackend::StoregeDelta& photo) { - value = itm == flagsMap.end()? 0: itm->second; // if no flag value for given flag, continue with value == 0 - } + int value = 0; + auto it = db.m_flags.find(photo.getId()); - return filter.mode == Database::FilterPhotosWithGeneralFlag::Mode::Exact? - value != filter.value: - (value & filter.value) != filter.value; + if (it != db.m_flags.end()) // if no flags for given photo, continue with value == 0 + { + const auto& flagsMap = it->second; + auto itm = flagsMap.find(filter.name); - }), result.end()); - } + value = itm == flagsMap.end()? 0: itm->second; // if no flag value for given flag, continue with value == 0 + } - }, dbFilter); + return filter.mode == Database::FilterPhotosWithGeneralFlag::Mode::Exact? + value != filter.value: + (value & filter.value) != filter.value; - return result; - } -} + }), result.end()); + } + else if constexpr (std::is_same_v) + { + result.erase(std::remove_if(result.begin(), result.end(), [&filter, &db](const MemoryBackend::StoregeDelta& photo) { + if (filter.status == Database::FilterFaceAnalysisStatus::Performed) + { + //return db.m_peopleInfo. + } + else + { + } + return true; + }), result.end()); + } + }, dbFilter); + + return result; + } -namespace Database -{ - namespace - { class Transaction { public: @@ -179,7 +186,7 @@ namespace Database const Photo::Id id(m_db->m_nextPhotoId); delta.setId(id); - auto [it, i] = m_db->m_photos.insert(Photo::FullDelta(delta)); + auto [it, i] = m_db->m_photos.insert(StoregeDelta(delta)); assert(i == true); ids.push_back(id); @@ -201,7 +208,7 @@ namespace Database for (const auto& delta: deltas) { auto it = m_db->m_photos.find(delta.getId()); - Photo::FullDelta currentDelta(*it); + StoregeDelta currentDelta(*it); photoChangeLogOperator().storeDifference(currentDelta, delta); @@ -582,7 +589,7 @@ namespace Database std::vector photosToClear; - for (const Photo::FullDelta& delta: m_db->m_photos) + for (const StoregeDelta& delta: m_db->m_photos) { const auto& groupInfo = delta.get(); @@ -626,15 +633,15 @@ namespace Database std::vector MemoryBackend::membersOf(const Group::Id& id) const { - std::vector members; - std::copy_if(m_db->m_photos.begin(), m_db->m_photos.end(), std::back_inserter(members), [id](const Photo::FullDelta& data) + std::vector members; + std::copy_if(m_db->m_photos.begin(), m_db->m_photos.end(), std::back_inserter(members), [id](const StoregeDelta& data) { const auto& groupInfo = data.get(); return groupInfo.group_id == id && groupInfo.role == GroupInfo::Role::Member; }); std::vector ids; - std::transform(members.begin(), members.end(), std::back_inserter(ids), [](const Photo::FullDelta& d){ return d.getId(); }); + std::transform(members.begin(), members.end(), std::back_inserter(ids), [](const StoregeDelta& d){ return d.getId(); }); return ids; } @@ -642,14 +649,14 @@ namespace Database std::vector MemoryBackend::listGroups() const { - std::vector representatives; - std::copy_if(m_db->m_photos.begin(), m_db->m_photos.end(), std::back_inserter(representatives), [](const Photo::FullDelta& data) + std::vector representatives; + std::copy_if(m_db->m_photos.begin(), m_db->m_photos.end(), std::back_inserter(representatives), [](const StoregeDelta& data) { return data.get().role == GroupInfo::Role::Representative; }); std::vector infos; - std::transform(representatives.begin(), representatives.end(), std::back_inserter(infos), [](const Photo::FullDelta& d){ return d.get(); }); + std::transform(representatives.begin(), representatives.end(), std::back_inserter(infos), [](const StoregeDelta& d){ return d.get(); }); std::vector ids; std::transform(infos.begin(), infos.end(), std::back_inserter(ids), &extract); @@ -691,7 +698,7 @@ namespace Database std::vector MemoryBackend::getPhotos(const Filter& filter) { - std::vector data(m_db->m_photos.begin(), m_db->m_photos.end()); + std::vector data(m_db->m_photos.begin(), m_db->m_photos.end()); data = filterPhotos(data, *m_db, filter); std::vector ids; @@ -737,7 +744,7 @@ namespace Database } - Photo::Id MemoryBackend::getIdFor(const Photo::FullDelta& d) + Photo::Id MemoryBackend::getIdFor(const StoregeDelta& d) { return d.getId(); } diff --git a/src/database/backends/memory_backend/memory_backend.hpp b/src/database/backends/memory_backend/memory_backend.hpp index 469612e3f7..2219a93446 100644 --- a/src/database/backends/memory_backend/memory_backend.hpp +++ b/src/database/backends/memory_backend/memory_backend.hpp @@ -26,6 +26,15 @@ namespace Database IPhotoOperator { public: + using StoregeDelta = Photo::ExplicitDelta< + Photo::Field::Tags, + Photo::Field::Flags, + Photo::Field::Path, + Photo::Field::Geometry, + Photo::Field::GroupInfo, + Photo::Field::PHash + >; + MemoryBackend(); ~MemoryBackend(); @@ -86,7 +95,7 @@ namespace Database bool hasPHash(const Photo::Id &) override; // - static Photo::Id getIdFor(const Photo::FullDelta& d); + static Photo::Id getIdFor(const StoregeDelta& d); static Person::Id getIdFor(const PersonName& pn); static PersonInfo::Id getIdFor(const PersonInfo& pn); From b601d09db524af2dd90b2cda5defe1051fedc1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 11 Feb 2023 19:07:22 +0100 Subject: [PATCH 022/151] Relax rules and format --- src/database/explicit_photo_delta.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/database/explicit_photo_delta.hpp b/src/database/explicit_photo_delta.hpp index 28fc2c784c..0467c5b6d8 100644 --- a/src/database/explicit_photo_delta.hpp +++ b/src/database/explicit_photo_delta.hpp @@ -42,7 +42,7 @@ namespace Photo } template - explicit ExplicitDelta(const ExplicitDelta& other) noexcept + ExplicitDelta(const ExplicitDelta& other) noexcept { static_assert( (... && ExplicitDelta::template has()), "Other object needs to be superset of this"); @@ -160,14 +160,14 @@ namespace Photo // based on: https://stackoverflow.com/questions/60434033/how-do-i-expand-a-compile-time-stdarray-into-a-parameter-pack namespace details { - template ())> struct Generator; + template())> struct Generator; - template + template struct Generator> { using type = ExplicitDelta; }; - template + template using Generator_t = typename Generator::type; } From 26cc97c079fdf7cacd17f333c69b79f995b1995a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 11 Feb 2023 22:43:18 +0100 Subject: [PATCH 023/151] Handle 'people' in json --- .../implementation/json_to_backend.cpp | 15 ++++++++++ src/unit_tests_utils/CMakeLists.txt | 2 ++ .../sample_dbs/photos_with_people.json | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 src/unit_tests_utils/sample_dbs/photos_with_people.json diff --git a/src/database/database_tools/implementation/json_to_backend.cpp b/src/database/database_tools/implementation/json_to_backend.cpp index 548f205126..6a54895248 100644 --- a/src/database/database_tools/implementation/json_to_backend.cpp +++ b/src/database/database_tools/implementation/json_to_backend.cpp @@ -1,4 +1,5 @@ +#include #include #include #include @@ -6,6 +7,7 @@ #include #include +#include #include "../json_to_backend.hpp" #include "database/ibackend.hpp" #include "database/igroup_operator.hpp" @@ -102,6 +104,19 @@ namespace Database const Photo::PHashT phash(it.value().toString().toULongLong(&ok, 16)); delta.insert(phash); } + else if (it.key() == "people") + { + const auto people = + std::ranges::views::transform(it.value().toArray(), + [](const QJsonValue& value) + { + const auto person = value.toObject(); + + return PersonFullInfo{.name = person["name"].toString()}; + }); + + delta.insert(range_to>(people)); + } else if (it.key() == "id") id = it.value().toString(); else diff --git a/src/unit_tests_utils/CMakeLists.txt b/src/unit_tests_utils/CMakeLists.txt index bb7f89cec9..2fb5a8bf82 100644 --- a/src/unit_tests_utils/CMakeLists.txt +++ b/src/unit_tests_utils/CMakeLists.txt @@ -3,6 +3,7 @@ include(${CMAKE_SOURCE_DIR}/cmake/functions.cmake) stringify_file(${CMAKE_CURRENT_BINARY_DIR}/db_for_series_detection.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/db_for_series_detection.json "const char* db" "SeriesDB") stringify_file(${CMAKE_CURRENT_BINARY_DIR}/phash_db.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/phash_db.json "const char* db" "PHashDB") +stringify_file(${CMAKE_CURRENT_BINARY_DIR}/photos_with_people.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/photos_with_people.json "const char* db" "PeopleDB") stringify_file(${CMAKE_CURRENT_BINARY_DIR}/rich_db.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/rich_db.json "const char* db1" "RichDB") stringify_file(${CMAKE_CURRENT_BINARY_DIR}/sample_db.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/sample_db.json "const char* db1" "SampleDB") stringify_file(${CMAKE_CURRENT_BINARY_DIR}/sample_db2.json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sample_dbs/sample_db2.json "const char* db2" "SampleDB") @@ -14,6 +15,7 @@ target_sources(sample_dbs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/db_for_series_detection.json.hpp ${CMAKE_CURRENT_BINARY_DIR}/phash_db.json.hpp + ${CMAKE_CURRENT_BINARY_DIR}/photos_with_people.json.hpp ${CMAKE_CURRENT_BINARY_DIR}/rich_db.json.hpp ${CMAKE_CURRENT_BINARY_DIR}/sample_db.json.hpp ${CMAKE_CURRENT_BINARY_DIR}/sample_db2.json.hpp diff --git a/src/unit_tests_utils/sample_dbs/photos_with_people.json b/src/unit_tests_utils/sample_dbs/photos_with_people.json new file mode 100644 index 0000000000..235c973da6 --- /dev/null +++ b/src/unit_tests_utils/sample_dbs/photos_with_people.json @@ -0,0 +1,28 @@ + +{ + "photos": [ + { + "path": "/some/path1.jpeg", + "tags": { "date": "2001.01.01", "time": "10:00", "event": "Some event", "place": "Internet" }, + "people": [ { "name": "person 1" }, { "name": "person 2" } ] + }, + { + "path": "/some/path2.jpeg", + "tags": { "date": "2001.01.01", "time": "10:00", "event": "", "place": "Internet" }, + "people": [ { "name": "person 2" }, { "name": "person 3" } ] + }, + { + "path": "/some/path3.jpeg", + "tags": { "date": "2001.01.01", "time": "11:00", "event": "Another event", "place": "" } + }, + { + "path": "/some/path4.jpeg", + "tags": { "date": "2001.01.01", "time": "11:00", "event": "Another event", "place": "" }, + "people": [ { "name": "person 4" }, { "name": "person 5" } ] + }, + { + "path": "/some/path5.jpeg", + "tags": { "date": "2001.01.01", "time": "11:00", "event": "Another event", "place": "" } + } + ] +} From 70cd1c67b6dbcc42add9bd083edd0a6745636e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 11 Feb 2023 22:43:27 +0100 Subject: [PATCH 024/151] Filter by analyzed photos --- .../backends/memory_backend/CMakeLists.txt | 22 ++++--- .../memory_backend/memory_backend.cpp | 61 ++++++++++++++++--- .../sql_filter_query_generator.cpp | 36 +++++++++++ .../sql_filter_query_generator.hpp | 1 + .../unit_tests_for_backends/filters_tests.cpp | 16 +++++ 5 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/database/backends/memory_backend/CMakeLists.txt b/src/database/backends/memory_backend/CMakeLists.txt index d137da3995..d2e07391ad 100644 --- a/src/database/backends/memory_backend/CMakeLists.txt +++ b/src/database/backends/memory_backend/CMakeLists.txt @@ -1,25 +1,27 @@ find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Boost REQUIRED) add_library(database_memory_backend - memory_backend.cpp - memory_backend.hpp + memory_backend.cpp + memory_backend.hpp ) target_include_directories(database_memory_backend - PUBLIC - ${PROJECT_SOURCE_DIR}/src - ${CMAKE_CURRENT_BINARY_DIR} + PUBLIC + ${PROJECT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(database_memory_backend - PUBLIC - Qt::Core + PUBLIC + Qt::Core + Boost::boost - PRIVATE - core - database + PRIVATE + core + database ) generate_export_header(database_memory_backend) diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index 295d14a02b..b4475c899b 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -1,24 +1,43 @@ #include +#include +#include +#include #include #include #include "memory_backend.hpp" -#include "database/transaction_wrapper.hpp" +#include "database/general_flags.hpp" #include "database/notifications_accumulator.hpp" #include "database/project_info.hpp" #include "database/photo_utils.hpp" +#include "database/transaction_wrapper.hpp" + + +using boost::multi_index_container; +using namespace boost::multi_index; namespace Database { + struct pi_id_tag {}; + struct pi_ph_id_tag {}; + + using PeopleContainer = multi_index_container< + PersonInfo, + indexed_by< + ordered_unique, BOOST_MULTI_INDEX_MEMBER(PersonInfo, PersonInfo::Id, id)>, + ordered_non_unique, BOOST_MULTI_INDEX_MEMBER(PersonInfo, Photo::Id, ph_id)> + > + >; + struct MemoryBackend::DB { std::map m_flags; std::map m_groups; std::set> m_photos; std::set> m_peopleNames; - std::set> m_peopleInfo; + PeopleContainer m_peopleInfo; std::vector m_logEntries; std::map, QByteArray> m_blobs; @@ -102,14 +121,25 @@ namespace Database else if constexpr (std::is_same_v) { result.erase(std::remove_if(result.begin(), result.end(), [&filter, &db](const MemoryBackend::StoregeDelta& photo) { - if (filter.status == Database::FilterFaceAnalysisStatus::Performed) - { - //return db.m_peopleInfo. - } - else + bool performed = false; + + const auto ph_id = photo.getId(); + const auto it = db.m_flags.find(ph_id); + + // 'analyzed' flag set? + if (it != db.m_flags.end()) { + const auto f_it = it->second.find(CommonGeneralFlags::FacesAnalysisState); + + if (f_it != it->second.end() && static_cast(f_it->second) == CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound) + performed = true; } - return true; + + // any people? + performed = get(db.m_peopleInfo).contains(ph_id); + + return (filter.status == Database::FilterFaceAnalysisStatus::Performed && !performed) || + (filter.status == Database::FilterFaceAnalysisStatus::NotPerformed && performed); }), result.end()); } @@ -189,6 +219,21 @@ namespace Database auto [it, i] = m_db->m_photos.insert(StoregeDelta(delta)); assert(i == true); + if (delta.has(Photo::Field::People)) + { + const auto& people = delta.get(); + auto& accessor = peopleInformationAccessor(); + + for(const auto& person: people) + { + const auto p_id = accessor.store(person.name); + const auto f_id = accessor.store(person.fingerprint); + + const PersonInfo pf(p_id, delta.getId(), f_id, person.position); + accessor.store(pf); + } + } + ids.push_back(id); m_db->m_nextPhotoId++; diff --git a/src/database/backends/sql_backends/sql_filter_query_generator.cpp b/src/database/backends/sql_backends/sql_filter_query_generator.cpp index 9aac0cca29..1e025f0be4 100644 --- a/src/database/backends/sql_backends/sql_filter_query_generator.cpp +++ b/src/database/backends/sql_backends/sql_filter_query_generator.cpp @@ -21,6 +21,7 @@ #include +#include #include "tables.hpp" @@ -381,4 +382,39 @@ namespace Database return finalQuery; } + + QString SqlFilterQueryGenerator::visit(const FilterFaceAnalysisStatus& analysisStatus) const + { + QString query; + switch (analysisStatus.status) + { + // find all photos which were analysed and no faces were found + all photos with found faces. + // See comment for FilterFaceAnalysisStatus flag + case FilterFaceAnalysisStatus::Performed: + query = QString("SELECT DISTINCT %1.id FROM %1 " + "LEFT JOIN %2 ON (%2.photo_id = %1.id AND %2.name = \"%4\") " + "LEFT JOIN %3 ON %3.photo_id = %1.id " + "WHERE COALESCE(%2.value, 0) = %5 OR %3.id IS NOT NULL") + .arg(TAB_PHOTOS) + .arg(TAB_GENERAL_FLAGS) + .arg(TAB_PEOPLE) + .arg(CommonGeneralFlags::FacesAnalysisState) + .arg(static_cast(CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound)); + break; + + case FilterFaceAnalysisStatus::NotPerformed: + query = QString("SELECT photo_id FROM %1 " + "LEFT JOIN %2 ON (%2.photo_id = %1.id AND %2.name = \"%4\") " + "LEFT JOIN %3 ON %3.photo_id = %1.id " + "WHERE COALESCE(%2.value, 0) = %5 AND %3.id IS NULL") + .arg(TAB_PHOTOS) + .arg(TAB_GENERAL_FLAGS) + .arg(TAB_PEOPLE) + .arg(CommonGeneralFlags::FacesAnalysisState) + .arg(static_cast(CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound)); + break; + } + + return query; + } } diff --git a/src/database/backends/sql_backends/sql_filter_query_generator.hpp b/src/database/backends/sql_backends/sql_filter_query_generator.hpp index 1608666945..e2d9be6a20 100644 --- a/src/database/backends/sql_backends/sql_filter_query_generator.hpp +++ b/src/database/backends/sql_backends/sql_filter_query_generator.hpp @@ -55,6 +55,7 @@ namespace Database QString visit(const FilterPhotosWithGeneralFlag& genericFlagsFilter) const; QString visit(const FilterPhotosWithPHash& withPhashFilter) const; QString visit(const FilterSimilarPhotos& similarPhotosFilter) const; + QString visit(const FilterFaceAnalysisStatus &) const; }; } diff --git a/src/database/unit_tests_for_backends/filters_tests.cpp b/src/database/unit_tests_for_backends/filters_tests.cpp index 2e624c45e5..c23ca09588 100644 --- a/src/database/unit_tests_for_backends/filters_tests.cpp +++ b/src/database/unit_tests_for_backends/filters_tests.cpp @@ -1,5 +1,7 @@ #include "common.hpp" +#include "database_tools/json_to_backend.hpp" +#include "unit_tests_utils/photos_with_people.json.hpp" using testing::UnorderedElementsAreArray; using testing::ElementsAre; @@ -51,3 +53,17 @@ TYPED_TEST(FiltersTest, generalFlagsFilterTests) EXPECT_THAT(value55Photos, ElementsAre(ids.back())); } } + + +TYPED_TEST(FiltersTest, faceAnalysisStatus) +{ + Database::JsonToBackend converter(*this->m_backend); + converter.append(PeopleDB::db); + + auto peopleFilter = Database::FilterFaceAnalysisStatus(); + peopleFilter.status = Database::FilterFaceAnalysisStatus::Performed; + + auto analyzedPhotos = this->m_backend->photoOperator().getPhotos({peopleFilter}); + + EXPECT_EQ(analyzedPhotos.size(), 3); +} From c19466ed202edfa571b943a0ffde2fefb17758ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 22 Jul 2023 10:35:52 +0200 Subject: [PATCH 025/151] Add boost dependency --- vcpkg.json | 1 + 1 file changed, 1 insertion(+) diff --git a/vcpkg.json b/vcpkg.json index f1ce2e7331..d63db0adac 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -16,6 +16,7 @@ "default-features": false, "features": [ "contrib" ] }, + "boost-multi-index", "exiv2", "jsoncpp", "libguarded", From 76eb39b6a1748239f4b225f52f7915a0c6321680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 14 Feb 2023 21:52:51 +0100 Subject: [PATCH 026/151] add boost dependency --- .github/workflows/linux-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 93917a2928..5431d78ff5 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -47,6 +47,7 @@ jobs: qt6-multimedia-dev \ libqt6svg6-dev \ qt6-l10n-tools \ + libboost-dev \ libopencv-dev \ libdlib-dev \ libexiv2-dev \ From 32ad3cc63a2b8164a29b2aee8302bcfd82f10cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 16 Feb 2023 21:37:06 +0100 Subject: [PATCH 027/151] Fix build on ubuntu --- .../memory_backend/memory_backend.cpp | 6 ++++-- .../implementation/json_to_backend.cpp | 20 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index b4475c899b..d481be98e1 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -135,8 +135,10 @@ namespace Database performed = true; } - // any people? - performed = get(db.m_peopleInfo).contains(ph_id); + // any people? TODO: use contains() when boost 1.78 is available on github actions + const auto& c = get(db.m_peopleInfo); + const auto c_it = c.find(ph_id); + performed = c_it != c.end(); return (filter.status == Database::FilterFaceAnalysisStatus::Performed && !performed) || (filter.status == Database::FilterFaceAnalysisStatus::NotPerformed && performed); diff --git a/src/database/database_tools/implementation/json_to_backend.cpp b/src/database/database_tools/implementation/json_to_backend.cpp index 6a54895248..fa9c42e32a 100644 --- a/src/database/database_tools/implementation/json_to_backend.cpp +++ b/src/database/database_tools/implementation/json_to_backend.cpp @@ -106,16 +106,20 @@ namespace Database } else if (it.key() == "people") { - const auto people = - std::ranges::views::transform(it.value().toArray(), - [](const QJsonValue& value) - { - const auto person = value.toObject(); + std::vector people; + const auto array = it.value().toArray(); - return PersonFullInfo{.name = person["name"].toString()}; - }); + std::transform(array.cbegin(), + array.cend(), + std::back_inserter(people), + [](const QJsonValue& value) + { + const auto person = value.toObject(); + + return PersonFullInfo{.name = person["name"].toString()}; + }); - delta.insert(range_to>(people)); + delta.insert(people); } else if (it.key() == "id") id = it.value().toString(); From af825b212e7054f8fca379c200b8763cb9ea4161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 17 Feb 2023 22:35:34 +0100 Subject: [PATCH 028/151] Reuse FlatModel instead of making new one --- src/database/filter.hpp | 2 + .../unit_tests_for_backends/filters_tests.cpp | 4 +- src/gui/desktop/models/CMakeLists.txt | 3 +- .../models/batch_face_recognition_model.cpp | 56 ------------------- .../models/batch_face_recognition_model.hpp | 34 ----------- src/gui/desktop/models/qml_flat_model.cpp | 34 +++++++++++ src/gui/desktop/models/qml_flat_model.hpp | 25 +++++++++ .../quick_items/Views/BatchFaceDetection.qml | 6 ++ src/gui/desktop/utils/qml_setup.cpp | 2 + 9 files changed, 71 insertions(+), 95 deletions(-) delete mode 100644 src/gui/desktop/models/batch_face_recognition_model.cpp delete mode 100644 src/gui/desktop/models/batch_face_recognition_model.hpp create mode 100644 src/gui/desktop/models/qml_flat_model.cpp create mode 100644 src/gui/desktop/models/qml_flat_model.hpp diff --git a/src/database/filter.hpp b/src/database/filter.hpp index 5cba9a885b..64f17354cf 100644 --- a/src/database/filter.hpp +++ b/src/database/filter.hpp @@ -199,6 +199,8 @@ namespace Database NotPerformed, Performed, } status; + + FilterFaceAnalysisStatus(Status s): status(s) {}; }; // helpers diff --git a/src/database/unit_tests_for_backends/filters_tests.cpp b/src/database/unit_tests_for_backends/filters_tests.cpp index c23ca09588..b33907f240 100644 --- a/src/database/unit_tests_for_backends/filters_tests.cpp +++ b/src/database/unit_tests_for_backends/filters_tests.cpp @@ -60,9 +60,7 @@ TYPED_TEST(FiltersTest, faceAnalysisStatus) Database::JsonToBackend converter(*this->m_backend); converter.append(PeopleDB::db); - auto peopleFilter = Database::FilterFaceAnalysisStatus(); - peopleFilter.status = Database::FilterFaceAnalysisStatus::Performed; - + auto peopleFilter = Database::FilterFaceAnalysisStatus(Database::FilterFaceAnalysisStatus::Performed); auto analyzedPhotos = this->m_backend->photoOperator().getPhotos({peopleFilter}); EXPECT_EQ(analyzedPhotos.size(), 3); diff --git a/src/gui/desktop/models/CMakeLists.txt b/src/gui/desktop/models/CMakeLists.txt index adfd937115..74fbb99dd3 100644 --- a/src/gui/desktop/models/CMakeLists.txt +++ b/src/gui/desktop/models/CMakeLists.txt @@ -5,8 +5,6 @@ set(SRC aheavy_list_model.hpp # needed for moc aphoto_data_model.cpp aphoto_data_model.hpp - batch_face_recognition_model.cpp - batch_face_recognition_model.hpp duplicates_model.cpp duplicates_model.hpp faces_model.cpp @@ -19,6 +17,7 @@ set(SRC photo_properties_model.cpp photo_properties_model.hpp photos_data_guesser.cpp + qml_flat_model.cpp roles_expansion.hpp series_model.cpp series_model.hpp diff --git a/src/gui/desktop/models/batch_face_recognition_model.cpp b/src/gui/desktop/models/batch_face_recognition_model.cpp deleted file mode 100644 index af4241fe35..0000000000 --- a/src/gui/desktop/models/batch_face_recognition_model.cpp +++ /dev/null @@ -1,56 +0,0 @@ - -#include -#include - -#include "batch_face_recognition_model.hpp" - - -BatchFaceRecognitionModel::BatchFaceRecognitionModel() -{ - // perform initialization when all required properties are set - QMetaObject::invokeMethod(this, [this] - { - assert(m_db != nullptr); - assert(m_core != nullptr); - - beginAnalysis(); - }, - Qt::QueuedConnection); -} - - -int BatchFaceRecognitionModel::rowCount(const QModelIndex& parent) const -{ - return 0; -} - - -QVariant BatchFaceRecognitionModel::data(const QModelIndex& index, int role) const -{ - QVariant result; - - return result; -} - - -void BatchFaceRecognitionModel::setDatabase(Database::IDatabase* db) -{ - m_db = db; -} - - -Database::IDatabase* BatchFaceRecognitionModel::database() -{ - return m_db; -} - - -void BatchFaceRecognitionModel::beginAnalysis() -{ - m_db->exec([](Database::IBackend& backend) - { - auto baseFilter = Database::getValidPhotosFilter(); - auto statusFilter = Database::FilterFaceAnalysisStatus(); - backend.photoOperator().getPhotos(Database::GroupFilter({baseFilter, statusFilter})); - }); -} diff --git a/src/gui/desktop/models/batch_face_recognition_model.hpp b/src/gui/desktop/models/batch_face_recognition_model.hpp deleted file mode 100644 index 1f0bf2f47e..0000000000 --- a/src/gui/desktop/models/batch_face_recognition_model.hpp +++ /dev/null @@ -1,34 +0,0 @@ - -#ifndef BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED -#define BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED - -#include - -#include -#include - - -class BatchFaceRecognitionModel: public QAbstractListModel -{ - Q_OBJECT - - Q_PROPERTY(Database::IDatabase* database WRITE setDatabase READ database REQUIRED) - Q_PROPERTY(ICoreFactoryAccessor* core MEMBER m_core REQUIRED) - -public: - BatchFaceRecognitionModel(); - - int rowCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - - void setDatabase(Database::IDatabase *); - Database::IDatabase* database(); - -private: - Database::IDatabase* m_db; - ICoreFactoryAccessor* m_core; - - void beginAnalysis(); -}; - -#endif diff --git a/src/gui/desktop/models/qml_flat_model.cpp b/src/gui/desktop/models/qml_flat_model.cpp new file mode 100644 index 0000000000..9345da4691 --- /dev/null +++ b/src/gui/desktop/models/qml_flat_model.cpp @@ -0,0 +1,34 @@ + +#include "qml_flat_model.hpp" + +using namespace Database; + +QMLFlatModel::QMLFlatModel() +{ + +} + + +void QMLFlatModel::setTextFilters(const QStringList& filters) +{ + m_filters = filters; + std::vector dbFilter; + + for (const auto& filter: filters) + { + if (filter == "faces not analysed") + dbFilter.push_back(FilterFaceAnalysisStatus(FilterFaceAnalysisStatus::NotPerformed)); + else + { + // log warning + } + } + + FlatModel::setFilter(dbFilter); +} + + +const QStringList& QMLFlatModel::textFilters() const +{ + return m_filters; +} diff --git a/src/gui/desktop/models/qml_flat_model.hpp b/src/gui/desktop/models/qml_flat_model.hpp new file mode 100644 index 0000000000..c858c9e439 --- /dev/null +++ b/src/gui/desktop/models/qml_flat_model.hpp @@ -0,0 +1,25 @@ + +#ifndef BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED +#define BATCH_FACE_RECOGNITION_MODEL_HPP_INCLUDED + +#include "flat_model.hpp" + + +class QMLFlatModel: public FlatModel +{ + Q_OBJECT + + Q_PROPERTY(Database::IDatabase* database WRITE setDatabase READ database REQUIRED) + Q_PROPERTY(QStringList text_filters WRITE setTextFilters READ textFilters) + +public: + QMLFlatModel(); + + void setTextFilters(const QStringList& filters); + const QStringList& textFilters() const; + +private: + QStringList m_filters; +}; + +#endif diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 2eefc8447d..082f2d4d4b 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -1,6 +1,12 @@ import QtQuick 2.15 +import photo_broom.models + Item { + QMLFlatModel { + id: data_model + text_filters: ["faces not analysed"] + } } diff --git a/src/gui/desktop/utils/qml_setup.cpp b/src/gui/desktop/utils/qml_setup.cpp index 810886d612..3c980bfc03 100644 --- a/src/gui/desktop/utils/qml_setup.cpp +++ b/src/gui/desktop/utils/qml_setup.cpp @@ -8,6 +8,7 @@ #include "models/duplicates_model.hpp" #include "models/faces_model.hpp" #include "models/photos_data_guesser.hpp" +#include "models/qml_flat_model.hpp" #include "models/series_model.hpp" #include "utils/variant_display.hpp" #include "inotifications.hpp" @@ -21,6 +22,7 @@ void register_qml_types() qmlRegisterType("photo_broom.models", 1, 0, "PhotoPropertiesModel"); qmlRegisterType("photo_broom.models", 1, 0, "DuplicatesModel"); qmlRegisterType("photo_broom.models", 1, 0, "FacesModel"); + qmlRegisterType("photo_broom.models", 1, 0, "QMLFlatModel"); qmlRegisterType("photo_broom.models", 1, 0, "SeriesModel"); qmlRegisterType("photo_broom.models", 1, 0, "TagsModel"); qmlRegisterType("photo_broom.utils", 1, 0, "Variant"); From 854b0cc3e66d04635a5a18ca09e2a1f12c8bb880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 17 Feb 2023 22:49:28 +0100 Subject: [PATCH 029/151] Set database --- src/gui/desktop/quick_items/Views/BatchFaceDetection.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 082f2d4d4b..d44d1ad62e 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -1,12 +1,14 @@ import QtQuick 2.15 import photo_broom.models +import photo_broom.singletons Item { QMLFlatModel { id: data_model + database: PhotoBroomProject.database text_filters: ["faces not analysed"] } } From fbceedcbc3203e46859c9b056c44f272ce3c0800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 11:17:58 +0100 Subject: [PATCH 030/151] Fix bug in query --- .../backends/sql_backends/sql_filter_query_generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/backends/sql_backends/sql_filter_query_generator.cpp b/src/database/backends/sql_backends/sql_filter_query_generator.cpp index 1e025f0be4..41445c7076 100644 --- a/src/database/backends/sql_backends/sql_filter_query_generator.cpp +++ b/src/database/backends/sql_backends/sql_filter_query_generator.cpp @@ -403,7 +403,7 @@ namespace Database break; case FilterFaceAnalysisStatus::NotPerformed: - query = QString("SELECT photo_id FROM %1 " + query = QString("SELECT DISTINCT %1.id FROM %1 " "LEFT JOIN %2 ON (%2.photo_id = %1.id AND %2.name = \"%4\") " "LEFT JOIN %3 ON %3.photo_id = %1.id " "WHERE COALESCE(%2.value, 0) = %5 AND %3.id IS NULL") @@ -411,7 +411,7 @@ namespace Database .arg(TAB_GENERAL_FLAGS) .arg(TAB_PEOPLE) .arg(CommonGeneralFlags::FacesAnalysisState) - .arg(static_cast(CommonGeneralFlags::FacesAnalysisType::AnalysedAndNotFound)); + .arg(static_cast(CommonGeneralFlags::FacesAnalysisType::NotAnalysedOrAnalysedAndFound)); break; } From 9987dfef12885a1d5a70e2962fba3caa60fccf45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 15:17:42 +0100 Subject: [PATCH 031/151] Use constants for filters --- src/gui/desktop/models/qml_flat_model.cpp | 16 +++++++++++++++- src/gui/desktop/models/qml_flat_model.hpp | 7 +++++++ .../quick_items/Views/BatchFaceDetection.qml | 8 +++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/models/qml_flat_model.cpp b/src/gui/desktop/models/qml_flat_model.cpp index 9345da4691..0d527d59e9 100644 --- a/src/gui/desktop/models/qml_flat_model.cpp +++ b/src/gui/desktop/models/qml_flat_model.cpp @@ -16,8 +16,10 @@ void QMLFlatModel::setTextFilters(const QStringList& filters) for (const auto& filter: filters) { - if (filter == "faces not analysed") + if (filter == facesNotAnalysedFilter()) dbFilter.push_back(FilterFaceAnalysisStatus(FilterFaceAnalysisStatus::NotPerformed)); + else if (filter == validMediaFilter()) + dbFilter.push_back(getValidPhotosFilter()); else { // log warning @@ -32,3 +34,15 @@ const QStringList& QMLFlatModel::textFilters() const { return m_filters; } + + +QString QMLFlatModel::facesNotAnalysedFilter() const +{ + return QStringLiteral("faces not analysed"); +} + + +QString QMLFlatModel::validMediaFilter() const +{ + return QStringLiteral("valid entries"); +} diff --git a/src/gui/desktop/models/qml_flat_model.hpp b/src/gui/desktop/models/qml_flat_model.hpp index c858c9e439..54ac00ca77 100644 --- a/src/gui/desktop/models/qml_flat_model.hpp +++ b/src/gui/desktop/models/qml_flat_model.hpp @@ -12,12 +12,19 @@ class QMLFlatModel: public FlatModel Q_PROPERTY(Database::IDatabase* database WRITE setDatabase READ database REQUIRED) Q_PROPERTY(QStringList text_filters WRITE setTextFilters READ textFilters) + // filters + Q_PROPERTY(QString facesNotAnalysed READ facesNotAnalysedFilter CONSTANT) + Q_PROPERTY(QString validMedia READ validMediaFilter CONSTANT) + public: QMLFlatModel(); void setTextFilters(const QStringList& filters); const QStringList& textFilters() const; + QString facesNotAnalysedFilter() const; + QString validMediaFilter() const; + private: QStringList m_filters; }; diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index d44d1ad62e..beda81efd6 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -2,6 +2,7 @@ import QtQuick 2.15 import photo_broom.models import photo_broom.singletons +import "ViewsComponents" as Internals Item { @@ -9,6 +10,11 @@ Item { QMLFlatModel { id: data_model database: PhotoBroomProject.database - text_filters: ["faces not analysed"] + text_filters: [data_model.facesNotAnalysed, data_model.validMedia] + } + + Internals.PhotosGridView { + anchors.fill: parent + model: data_model } } From 054c97bddbe8849689abd8d6cd5b232fbcc3be39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 16:28:14 +0100 Subject: [PATCH 032/151] Use shorter names --- src/gui/desktop/models/qml_flat_model.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/models/qml_flat_model.cpp b/src/gui/desktop/models/qml_flat_model.cpp index 0d527d59e9..c9a4f8d691 100644 --- a/src/gui/desktop/models/qml_flat_model.cpp +++ b/src/gui/desktop/models/qml_flat_model.cpp @@ -38,11 +38,11 @@ const QStringList& QMLFlatModel::textFilters() const QString QMLFlatModel::facesNotAnalysedFilter() const { - return QStringLiteral("faces not analysed"); + return QStringLiteral("FNA"); // faces not analysed } QString QMLFlatModel::validMediaFilter() const { - return QStringLiteral("valid entries"); + return QStringLiteral("VMF"); // valid media files } From 66b0c38f881ea9c9d623e2b11f87a4df189fbdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 21:09:50 +0100 Subject: [PATCH 033/151] Move flickDeceleration closer to the root --- src/gui/desktop/quick_items/Views/PhotosView.qml | 1 - .../quick_items/Views/ViewsComponents/PhotosGridView.qml | 4 ++-- tr/photo_broom_en.ts | 6 +++--- tr/photo_broom_pl.ts | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/PhotosView.qml b/src/gui/desktop/quick_items/Views/PhotosView.qml index 287577a429..3cd83751f5 100644 --- a/src/gui/desktop/quick_items/Views/PhotosView.qml +++ b/src/gui/desktop/quick_items/Views/PhotosView.qml @@ -96,7 +96,6 @@ FocusScope { activeFocusOnTab: true keyNavigationEnabled: true - flickDeceleration: 10000 model: photosModelControllerId.photos thumbnailSize: thumbnailSliderId.size diff --git a/src/gui/desktop/quick_items/Views/ViewsComponents/PhotosGridView.qml b/src/gui/desktop/quick_items/Views/ViewsComponents/PhotosGridView.qml index c2204dbcdc..f536e5c5dc 100644 --- a/src/gui/desktop/quick_items/Views/ViewsComponents/PhotosGridView.qml +++ b/src/gui/desktop/quick_items/Views/ViewsComponents/PhotosGridView.qml @@ -16,8 +16,10 @@ Components.MultiselectGridView { signal itemDoubleClicked(int index) + flickDeceleration: 10000 cellWidth: thumbnailSize + thumbnailMargin * 2 cellHeight: thumbnailSize + thumbnailMargin * 2 + currentIndex: -1 delegate: PhotoDelegate { id: delegateId @@ -52,8 +54,6 @@ Components.MultiselectGridView { Component.onCompleted: selected = grid.isIndexSelected(index); } - currentIndex: -1 - ScrollBar.vertical: ScrollBar { } // TODO: without it parent (PhotosView) doesn't get focus and Esc key does not work. diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index a6fe53abdf..9f98db2fb9 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -747,17 +747,17 @@ Error code: %1 PhotosView - + <b>Properties</b> - + <b>Media information</b> - + <b>Debug window</b> diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index f86b982001..bc270b693f 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -779,7 +779,7 @@ Kod błędu: %1 PhotosView - + <b>Properties</b> <b>Właściwości</b> From ad6485bd1c80189bccb353984b7a934926dc5328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 21:19:18 +0100 Subject: [PATCH 034/151] Name untouched photos --- .../quick_items/Views/BatchFaceDetection.qml | 13 ++++++++++--- tr/photo_broom_en.ts | 8 ++++++++ tr/photo_broom_pl.ts | 8 ++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index beda81efd6..89397579c5 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -1,5 +1,7 @@ -import QtQuick 2.15 +import QtQuick +import QtQuick.Controls + import photo_broom.models import photo_broom.singletons import "ViewsComponents" as Internals @@ -13,8 +15,13 @@ Item { text_filters: [data_model.facesNotAnalysed, data_model.validMedia] } - Internals.PhotosGridView { + GroupBox { anchors.fill: parent - model: data_model + title: qsTr("Photos to be analyzed") + + Internals.PhotosGridView { + anchors.fill: parent + model: data_model + } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 9f98db2fb9..caaad93ce3 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -57,6 +57,14 @@ + + BatchFaceDetection + + + Photos to be analyzed + + + CollectionScanner diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index bc270b693f..da00548634 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -58,6 +58,14 @@ Wydarzenie + + BatchFaceDetection + + + Photos to be analyzed + + + CollectionScanner From 962eca4f3e67895640628ebbd785a0ad8780f489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 21:56:38 +0100 Subject: [PATCH 035/151] Introduce BatchFaceDetector --- src/gui/desktop/utils/CMakeLists.txt | 2 ++ src/gui/desktop/utils/batch_face_detector.cpp | 14 +++++++++++ src/gui/desktop/utils/batch_face_detector.hpp | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/gui/desktop/utils/batch_face_detector.cpp create mode 100644 src/gui/desktop/utils/batch_face_detector.hpp diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 249a70fa44..3b3bbcdcb0 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -13,6 +13,8 @@ set(UTILS_SOURCES grouppers/generator_utils.hpp grouppers/hdr_generator.cpp grouppers/hdr_generator.hpp + batch_face_detector.cpp + batch_face_detector.hpp collection_scanner.cpp collection_scanner.hpp config_tools.cpp diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp new file mode 100644 index 0000000000..f11fb268c1 --- /dev/null +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -0,0 +1,14 @@ + +#include "batch_face_detector.hpp" + + +void BatchFaceDetector::setPhotosModel(APhotoDataModel* model) +{ + m_photosModel = model; +} + + +APhotoDataModel *BatchFaceDetector::photosModel() const +{ + return m_photosModel; +} diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp new file mode 100644 index 0000000000..4d7d08a524 --- /dev/null +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -0,0 +1,23 @@ + +#ifndef BATCH_FACE_DETECTOR_HPP_INCLUDED +#define BATCH_FACE_DETECTOR_HPP_INCLUDED + +#include + +#include "models/aphoto_data_model.hpp" + + +class BatchFaceDetector: public QObject +{ + Q_OBJECT + Q_PROPERTY(APhotoDataModel* photos_model WRITE setPhotosModel READ photosModel) + +public: + void setPhotosModel(APhotoDataModel *); + APhotoDataModel* photosModel() const; + +private: + APhotoDataModel* m_photosModel; +}; + +#endif From 4003bab65297015b333831054194b91c5085fd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Feb 2023 22:20:15 +0100 Subject: [PATCH 036/151] drop unused code --- src/core/implementation/task_executor.cpp | 36 ------------------- .../implementation/task_executor_utils.cpp | 6 ---- src/core/itask_executor.hpp | 1 - src/core/task_executor.hpp | 4 --- src/core/task_executor_utils.hpp | 1 - src/unit_tests_utils/fake_task_executor.hpp | 5 --- 6 files changed, 53 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 51336d6f88..7e6a700a9b 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -34,7 +34,6 @@ TaskExecutor::TaskExecutor(ILogger& logger, int threadsToUse): m_taskEater(), m_logger(logger), m_threads(threadsToUse), - m_lightTasks(0), m_working(true) { m_logger.info(QString("Using %1 threads.").arg(m_threads)); @@ -59,33 +58,6 @@ void TaskExecutor::add(std::unique_ptr&& task) } -void TaskExecutor::addLight(std::unique_ptr&& task) -{ - assert(m_working); - - // start a task and increase count of them - std::lock_guard guard(m_lightTasksMutex); - ++m_lightTasks; - - auto light_task = std::thread( [this, lt = std::move(task)] - { - set_thread_name("TE::LightTask"); - - // do job - lt->perform(); - - // notify about finished task - std::unique_lock lock(m_lightTasksMutex); - assert(m_lightTasks > 0); - --m_lightTasks; - - std::notify_all_at_thread_exit(m_lightTaskFinished, std::move(lock)); - }); - - light_task.detach(); -} - - int TaskExecutor::heavyWorkers() const { return m_threads; @@ -102,14 +74,6 @@ void TaskExecutor::stop() m_tasks.stop(); assert(m_taskEater.joinable()); m_taskEater.join(); - - // wait for light tasks - std::unique_lock lock(m_lightTasksMutex); - - m_lightTaskFinished.wait(lock, [this] - { - return m_lightTasks == 0; - }); } } diff --git a/src/core/implementation/task_executor_utils.cpp b/src/core/implementation/task_executor_utils.cpp index 50066c1725..6c1bcc15c0 100644 --- a/src/core/implementation/task_executor_utils.cpp +++ b/src/core/implementation/task_executor_utils.cpp @@ -86,12 +86,6 @@ void TasksQueue::add(std::unique_ptr&& task) } -void TasksQueue::addLight(std::unique_ptr&& task) -{ - m_executor.addLight(std::move(task)); -} - - int TasksQueue::heavyWorkers() const { return m_executor.heavyWorkers(); diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index a9d8363079..bdfe0e0677 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -39,7 +39,6 @@ struct CORE_EXPORT ITaskExecutor virtual ~ITaskExecutor() = default; virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) - virtual void addLight(std::unique_ptr &&) = 0; // add long but light task (awaiting results from other threads etc) virtual int heavyWorkers() const = 0; // return number of heavy task workers }; diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 99a20195d2..2447d0d76b 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -38,7 +38,6 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor TaskExecutor& operator=(const TaskExecutor &) = delete; void add(std::unique_ptr &&) override; - void addLight(std::unique_ptr &&) override; int heavyWorkers() const override; @@ -48,11 +47,8 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor typedef ol::TS_Queue> QueueT; QueueT m_tasks; std::thread m_taskEater; - std::mutex m_lightTasksMutex; - std::condition_variable m_lightTaskFinished; ILogger& m_logger; unsigned int m_threads; - int m_lightTasks; bool m_working; void eat(); diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index f94767cad0..27402555c6 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -301,7 +301,6 @@ class CORE_EXPORT TasksQueue: public ITaskExecutor, public Queue &&) override; - void addLight(std::unique_ptr &&) override; int heavyWorkers() const override; private: diff --git a/src/unit_tests_utils/fake_task_executor.hpp b/src/unit_tests_utils/fake_task_executor.hpp index 92d9a8fa1c..0227b8b859 100644 --- a/src/unit_tests_utils/fake_task_executor.hpp +++ b/src/unit_tests_utils/fake_task_executor.hpp @@ -9,11 +9,6 @@ class FakeTaskExecutor: public ITaskExecutor task->perform(); } - void addLight(std::unique_ptr&& task) override - { - task->perform(); - } - int heavyWorkers() const override { return 1; From 04df4bc42d3f157342190ddc468dc240e000c3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 23 Feb 2023 19:57:47 +0100 Subject: [PATCH 037/151] Fix name --- src/gui/desktop/models/aphoto_data_model.hpp | 27 +++----------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/gui/desktop/models/aphoto_data_model.hpp b/src/gui/desktop/models/aphoto_data_model.hpp index 816e9ceaac..d8d9a966f1 100644 --- a/src/gui/desktop/models/aphoto_data_model.hpp +++ b/src/gui/desktop/models/aphoto_data_model.hpp @@ -1,24 +1,6 @@ -/* - * Photo Broom - photos management tool. - * Copyright (C) 2015 Michał Walenciak - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef ASCALABLEIMAGESMODEL_HPP -#define ASCALABLEIMAGESMODEL_HPP + +#ifndef APHOTO_DATA_MODEL_HPP_INCLUDED +#define APHOTO_DATA_MODEL_HPP_INCLUDED #include #include @@ -50,5 +32,4 @@ class APhotoDataModel: public QAbstractItemModel void registerRole(int, const QByteArray &); }; - -#endif // ASCALABLEIMAGESMODEL_HPP +#endif From 2c64a72a3ee8ac011d4cb2c8d33498cc74830872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 25 Feb 2023 19:25:03 +0100 Subject: [PATCH 038/151] Introduce base for batch photos detector --- src/gui/desktop/utils/batch_face_detector.cpp | 72 ++++++++++++++++++- src/gui/desktop/utils/batch_face_detector.hpp | 12 +++- src/gui/desktop/utils/qml_setup.cpp | 2 + 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index f11fb268c1..e1c0d6acae 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -1,14 +1,84 @@ +#include +#include + #include "batch_face_detector.hpp" +#include +#include +#include +namespace +{ + template + requires std::is_base_of_v + auto invoke_and_wait(QPointer object, const F& function, Args&&... args) + { + QPromise promise; + QFuture future = promise.future(); + + call_from_object_thread(object, [&promise, &function, &args...]() + { + promise.start(); + promise.addResult(function(args...)); + promise.finish(); + }); + + future.waitForFinished(); + + return future.result(); + } +} + + void BatchFaceDetector::setPhotosModel(APhotoDataModel* model) { m_photosModel = model; + + if (m_photosModel != nullptr) + { + assert(m_core != nullptr); + + auto task = std::bind(&BatchFaceDetector::processPhotos, this); + runOn(m_core->getTaskExecutor(), task, "BatchFaceDetector::processPhotos"); + } +} + + +void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) +{ + m_core = core; } -APhotoDataModel *BatchFaceDetector::photosModel() const +APhotoDataModel* BatchFaceDetector::photosModel() const { return m_photosModel; } + + +ICoreFactoryAccessor* BatchFaceDetector::core() const +{ + return m_core; +} + + +void BatchFaceDetector::processPhotos() +{ + QPointer modelPtr(m_photosModel); + + for(;;) + { + const std::optional data = invoke_and_wait>(modelPtr, [modelPtr] + { + const auto rows = modelPtr->rowCount(); + + std::optional d = rows > 0? modelPtr->getPhotoData(modelPtr->index(0, 0)) : std::optional{}; + + return d; + }); + + if (data.has_value() == false) + break; + } +} diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 4d7d08a524..de47924fcc 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -4,20 +4,30 @@ #include +#include #include "models/aphoto_data_model.hpp" + class BatchFaceDetector: public QObject { Q_OBJECT + Q_PROPERTY(ICoreFactoryAccessor* core WRITE setCore READ core REQUIRED) Q_PROPERTY(APhotoDataModel* photos_model WRITE setPhotosModel READ photosModel) public: void setPhotosModel(APhotoDataModel *); + void setCore(ICoreFactoryAccessor *); + APhotoDataModel* photosModel() const; + ICoreFactoryAccessor* core() const; private: - APhotoDataModel* m_photosModel; + APhotoDataModel* m_photosModel = nullptr; + ICoreFactoryAccessor* m_core = nullptr; + + // thread eating photos from model + void processPhotos(); }; #endif diff --git a/src/gui/desktop/utils/qml_setup.cpp b/src/gui/desktop/utils/qml_setup.cpp index 3c980bfc03..913617fa15 100644 --- a/src/gui/desktop/utils/qml_setup.cpp +++ b/src/gui/desktop/utils/qml_setup.cpp @@ -12,6 +12,7 @@ #include "models/series_model.hpp" #include "utils/variant_display.hpp" #include "inotifications.hpp" +#include "batch_face_detector.hpp" void register_qml_types() @@ -26,6 +27,7 @@ void register_qml_types() qmlRegisterType("photo_broom.models", 1, 0, "SeriesModel"); qmlRegisterType("photo_broom.models", 1, 0, "TagsModel"); qmlRegisterType("photo_broom.utils", 1, 0, "Variant"); + qmlRegisterType("photo_broom.utils", 1, 0, "BatchFaceDetector"); qRegisterMetaType("QAbstractItemModel*"); qmlRegisterUncreatableMetaObject(Photo::staticMetaObject, "photo_broom.enums", 1, 0, "PhotoEnums", "Error: only enums"); qmlRegisterUncreatableMetaObject(Tag::staticMetaObject, "photo_broom.enums", 1, 0, "TagEnums", "Error: only enums"); From 6855620b199d0ca1bf7cfce219a9dd08d9b4cfba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 25 Feb 2023 19:25:20 +0100 Subject: [PATCH 039/151] Instantiate BatchFaceDetector --- src/gui/desktop/quick_items/Views/BatchFaceDetection.qml | 7 +++++++ tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 89397579c5..ea48b1fa1c 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -4,6 +4,7 @@ import QtQuick.Controls import photo_broom.models import photo_broom.singletons +import photo_broom.utils import "ViewsComponents" as Internals @@ -15,6 +16,12 @@ Item { text_filters: [data_model.facesNotAnalysed, data_model.validMedia] } + BatchFaceDetector { + id: detector + core: PhotoBroomProject.coreFactory + photos_model: data_model + } + GroupBox { anchors.fill: parent title: qsTr("Photos to be analyzed") diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index caaad93ce3..a4d6edd0a0 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -60,7 +60,7 @@ BatchFaceDetection - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index da00548634..18504cce54 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -61,7 +61,7 @@ BatchFaceDetection - + Photos to be analyzed From 808f2c9e2da7ab424cd3ee4e59604d0d8c603997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 26 Feb 2023 09:53:58 +0100 Subject: [PATCH 040/151] Make FaceEditor require logger Just to have parent's namespace included --- src/gui/desktop/models/faces_model.cpp | 6 +++++- src/gui/desktop/utils/people_editor.cpp | 5 +++-- src/gui/desktop/utils/people_editor.hpp | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index 0592bba2a6..ead81a557e 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -142,7 +143,10 @@ void FacesModel::initialSetup() runOn(m_core->getTaskExecutor(), [this]() { - const auto faces = std::make_shared>>(FaceEditor(*m_database, *m_core).getFacesFor(m_id)); + const auto faces = std::make_shared>>(FaceEditor( + *m_database, + *m_core, + m_core->getLoggerFactory().get("FacesModel")).getFacesFor(m_id)); invokeMethod(this, &FacesModel::updateFaceInformation, faces); }); diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 2ceeec951c..50280add81 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -327,9 +327,10 @@ namespace } -FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core) +FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const std::unique_ptr& logger) : m_db(db) , m_core(core) + , m_logger(logger->subLogger("FaceEditor")) { } @@ -345,7 +346,7 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) const auto faces = findFaces( m_db, *image, - m_core.getLoggerFactory().get("FaceEditor"), + m_logger, id); auto storage = std::make_shared(m_db, m_core); diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index e577577ed5..656864cfa5 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -19,6 +19,7 @@ #define PEOPLEMANIPULATOR_HPP #include +#include #include #include #include @@ -43,11 +44,12 @@ class IFace class FaceEditor { public: - FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &); + FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &, const std::unique_ptr &); std::vector> getFacesFor(const Photo::Id &); private: + std::unique_ptr m_logger; Database::IDatabase& m_db; ICoreFactoryAccessor& m_core; }; From 5da6e7f2b69bf7e40efb918b9143ad47ee3bb553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 18:15:24 +0100 Subject: [PATCH 041/151] Fix language --- .../quick_items/photos_model_controller_component.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/desktop/quick_items/photos_model_controller_component.cpp b/src/gui/desktop/quick_items/photos_model_controller_component.cpp index 71963f731d..77cef560c8 100644 --- a/src/gui/desktop/quick_items/photos_model_controller_component.cpp +++ b/src/gui/desktop/quick_items/photos_model_controller_component.cpp @@ -342,17 +342,17 @@ QStringList PhotosModelControllerComponent::rawCategories() const void PhotosModelControllerComponent::getTimeRangeForFilters(Database::IBackend& backend) { - auto dates = backend.listTagValues(Tag::Types::Date, {}); + auto date = backend.listTagValues(Tag::Types::Date, {}); const Database::FilterPhotosWithTag with_date_filter(Tag::Types::Date); const Database::FilterNotMatchingFilter without_date_filter(with_date_filter); const auto photos_without_date_tag = backend.photoOperator().getPhotos(without_date_filter); if (photos_without_date_tag.empty() == false) - dates.push_back(QDate()); + date.push_back(QDate()); - std::sort(dates.begin(), dates.end()); - invokeMethod(this, &PhotosModelControllerComponent::setAvailableDates, dates); + std::sort(date.begin(), date.end()); + invokeMethod(this, &PhotosModelControllerComponent::setAvailableDates, date); } From dbca96e159e98f4ca314edbbf97e2874d8399755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 18:15:34 +0100 Subject: [PATCH 042/151] Format code --- src/database/general_flags.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/general_flags.hpp b/src/database/general_flags.hpp index 55416c8fa5..570ec88d8b 100644 --- a/src/database/general_flags.hpp +++ b/src/database/general_flags.hpp @@ -38,7 +38,7 @@ namespace Database::CommonGeneralFlags // 1 - no faces found. // that should allow to distinguish between all 3 states with less db usage. NotAnalysedOrAnalysedAndFound = 0, - AnalysedAndNotFound = 1, + AnalysedAndNotFound = 1, }; } From b4fe22cc7f227cf1e70ce5d0272eb128d0d7dfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 18:15:46 +0100 Subject: [PATCH 043/151] Be more movable --- src/core/function_wrappers.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/function_wrappers.hpp b/src/core/function_wrappers.hpp index 0bd7567425..89e5b9c67e 100644 --- a/src/core/function_wrappers.hpp +++ b/src/core/function_wrappers.hpp @@ -129,9 +129,9 @@ class safe_callback_ctrl final template void invokeMethod(Obj* object, const F& method, Args&&... args) requires std::is_base_of::value { - QMetaObject::invokeMethod(object, [object, method, args...]() + QMetaObject::invokeMethod(object, [object, method, ...args = std::forward(args)]() mutable { - (object->*method)(args...); + (object->*method)(std::forward(args)...); }); } From b23c4cb6cae85ac2c800f929464e15a4f7572d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 21:15:14 +0100 Subject: [PATCH 044/151] Read faces from photos --- src/gui/desktop/utils/batch_face_detector.cpp | 116 ++++++++++++++++-- src/gui/desktop/utils/batch_face_detector.hpp | 39 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index e1c0d6acae..3e4870dacc 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -1,5 +1,8 @@ +#include #include +#include +#include #include #include "batch_face_detector.hpp" @@ -33,21 +36,44 @@ namespace void BatchFaceDetector::setPhotosModel(APhotoDataModel* model) { + if (m_photosModel != nullptr) + { + disconnect(m_photosModel, &QAbstractItemModel::rowsInserted, this, &BatchFaceDetector::newPhotos); + } + m_photosModel = model; if (m_photosModel != nullptr) { assert(m_core != nullptr); - auto task = std::bind(&BatchFaceDetector::processPhotos, this); - runOn(m_core->getTaskExecutor(), task, "BatchFaceDetector::processPhotos"); + connect(m_photosModel, &QAbstractItemModel::rowsInserted, this, &BatchFaceDetector::newPhotos); + + QMetaObject::invokeMethod(this, [this] + { + const auto rows = m_photosModel->rowCount(); + m_faces.clear(); + m_ids.clear(); + + if (rows > 0) + newPhotos({}, 0, rows - 1); + }, + Qt::QueuedConnection); } } void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) { + assert(m_core == nullptr); m_core = core; + m_logger = m_core->getLoggerFactory().get("BatchFaceDetector"); +} + + +void BatchFaceDetector::setDB(Database::IDatabase* db) +{ + m_db = db; } @@ -63,22 +89,92 @@ ICoreFactoryAccessor* BatchFaceDetector::core() const } +Database::IDatabase* BatchFaceDetector::db() const +{ + return m_db; +} + + +int BatchFaceDetector::rowCount(const QModelIndex& parent) const +{ + return parent.isValid()? 0: static_cast(m_faces.size()); +} + + +QVariant BatchFaceDetector::data(const QModelIndex& idx, int role) const +{ + assert(idx.row() < static_cast(m_faces.size())); + assert(idx.column() == 0); + + const size_t row = static_cast(idx.row()); + + if (role == Qt::DisplayRole) + return m_faces[row].faceData->name(); + else if (role == Qt::DecorationRole) + return m_faces[row].faceImg; + else + return {}; +} + + +void BatchFaceDetector::kickProcessing() +{ + if (m_ids.empty() == false) + QMetaObject::invokeMethod(this, &BatchFaceDetector::processPhotos, Qt::QueuedConnection); +} + + void BatchFaceDetector::processPhotos() { + assert(m_ids.empty() == false); QPointer modelPtr(m_photosModel); - for(;;) + FaceEditor fe(*m_db, *m_core, m_logger); + + const auto id = m_ids.front(); + m_ids.pop_front(); + + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this] mutable { - const std::optional data = invoke_and_wait>(modelPtr, [modelPtr] + auto faces = fe.getFacesFor(id); + std::vector facesDetails; + + for (auto& face: faces) { - const auto rows = modelPtr->rowCount(); + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } - std::optional d = rows > 0? modelPtr->getPhotoData(modelPtr->index(0, 0)) : std::optional{}; + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); + }); +} - return d; - }); - if (data.has_value() == false) - break; +void BatchFaceDetector::appendFaces(std::vector&& faces) +{ + if (faces.empty() == false) + { + const int newFaces = static_cast(faces.size()); + const int existingFaces = static_cast(m_faces.size()); + beginInsertRows({}, existingFaces, existingFaces + newFaces - 1); + m_faces.insert(m_faces.end(), std::make_move_iterator(faces.begin()), std::make_move_iterator(faces.end())); + endInsertRows(); } + + kickProcessing(); +} + + +void BatchFaceDetector::newPhotos(const QModelIndex &, int first, int last) +{ + const bool needKicking = m_ids.empty(); + + for(int row = first; row <= last; row++) + { + const Photo::Id id(m_photosModel->data(m_photosModel->index(row, 0), APhotoDataModel::PhotoIdRole)); + m_ids.push_back(id); + } + + if (needKicking) + kickProcessing(); } diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index de47924fcc..ad3c53598d 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -2,32 +2,65 @@ #ifndef BATCH_FACE_DETECTOR_HPP_INCLUDED #define BATCH_FACE_DETECTOR_HPP_INCLUDED +#include #include +#include #include -#include "models/aphoto_data_model.hpp" +#include +#include "models/aphoto_data_model.hpp" +#include "people_editor.hpp" -class BatchFaceDetector: public QObject +class BatchFaceDetector: public QAbstractListModel { Q_OBJECT Q_PROPERTY(ICoreFactoryAccessor* core WRITE setCore READ core REQUIRED) + Q_PROPERTY(Database::IDatabase* db WRITE setDB READ db REQUIRED) Q_PROPERTY(APhotoDataModel* photos_model WRITE setPhotosModel READ photosModel) public: void setPhotosModel(APhotoDataModel *); void setCore(ICoreFactoryAccessor *); + void setDB(Database::IDatabase *); APhotoDataModel* photosModel() const; ICoreFactoryAccessor* core() const; + Database::IDatabase* db() const; + + int rowCount(const QModelIndex &) const override; + QVariant data(const QModelIndex &, int) const override; private: + struct Face + { + Face(std::unique_ptr f, const QImage& img) + : faceData(std::move(f)) + , faceImg(img) + {} + + Face(const Face &) = delete; + Face(Face &&) = default; + + Face& operator=(const Face &) = delete; + Face& operator=(Face &&) = default; + + std::unique_ptr faceData; + QImage faceImg; + }; + + std::deque m_ids; + std::vector m_faces; + std::unique_ptr m_logger; APhotoDataModel* m_photosModel = nullptr; ICoreFactoryAccessor* m_core = nullptr; + Database::IDatabase* m_db = nullptr; - // thread eating photos from model + void kickProcessing(); void processPhotos(); + void appendFaces(std::vector &&); + void newPhotos(const QModelIndex &, int, int); }; #endif From d935e9fcafaf6dd15f4e5bf1612ff6c81f874a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 21:23:13 +0100 Subject: [PATCH 045/151] Provide base for View with detected faces --- .../quick_items/Views/BatchFaceDetection.qml | 38 ++++++++++++++++--- tr/photo_broom_en.ts | 7 +++- tr/photo_broom_pl.ts | 7 +++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index ea48b1fa1c..b033a2b4f1 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -5,6 +5,7 @@ import QtQuick.Controls import photo_broom.models import photo_broom.singletons import photo_broom.utils +import QmlItems import "ViewsComponents" as Internals @@ -12,23 +13,48 @@ Item { QMLFlatModel { id: data_model - database: PhotoBroomProject.database text_filters: [data_model.facesNotAnalysed, data_model.validMedia] + database: PhotoBroomProject.database } BatchFaceDetector { id: detector core: PhotoBroomProject.coreFactory + db: PhotoBroomProject.database photos_model: data_model } - GroupBox { + SplitView { anchors.fill: parent - title: qsTr("Photos to be analyzed") + orientation: Qt.Vertical + + GroupBox { + title: qsTr("Discovered faces") + + GridView { + anchors.fill: parent + model: detector + + cellWidth: 170 + cellHeight: 170 + + delegate: Picture { + required property var decoration + + height: 150 + width: 150 + source: decoration + } + } + } + + GroupBox { + title: qsTr("Photos to be analyzed") - Internals.PhotosGridView { - anchors.fill: parent - model: data_model + Internals.PhotosGridView { + anchors.fill: parent + model: data_model + } } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index a4d6edd0a0..11521b54d0 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -60,7 +60,12 @@ BatchFaceDetection - + + Discovered faces + + + + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 18504cce54..9a4242ea5f 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -61,7 +61,12 @@ BatchFaceDetection - + + Discovered faces + + + + Photos to be analyzed From f05ce2654a26828339fd4073ee992a003d49dfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 21:55:58 +0100 Subject: [PATCH 046/151] Use actual size --- src/gui/desktop/QmlItems/picture_item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/QmlItems/picture_item.cpp b/src/gui/desktop/QmlItems/picture_item.cpp index 59f2bc30ad..3724500434 100644 --- a/src/gui/desktop/QmlItems/picture_item.cpp +++ b/src/gui/desktop/QmlItems/picture_item.cpp @@ -34,7 +34,7 @@ const QImage& PictureItem::source() const void PictureItem::paint(QPainter* painter) { - const QRectF rect(QPointF(0, 0), QSizeF(implicitWidth(), implicitHeight())); + const QRectF rect(QPointF(0, 0), QSizeF(width(), height())); painter->drawImage(rect, m_source); } From d8b4654b75e95865e64ffb5c99921cb6ab4bb1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Feb 2023 21:56:50 +0100 Subject: [PATCH 047/151] Move invoke_and_wait where it belongs --- src/core/function_wrappers.hpp | 23 ++++++++++++++++ src/gui/desktop/utils/batch_face_detector.cpp | 26 ------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/core/function_wrappers.hpp b/src/core/function_wrappers.hpp index 89e5b9c67e..3c1ae04c76 100644 --- a/src/core/function_wrappers.hpp +++ b/src/core/function_wrappers.hpp @@ -8,8 +8,10 @@ #include #include +#include #include + // Internal struct with data shared between safe_callback and safe_callback_ctrl struct safe_callback_data { @@ -136,6 +138,27 @@ void invokeMethod(Obj* object, const F& method, Args&&... args) requires std::is } +// Works as extended invokeMethod but waits for results +template +requires std::is_base_of_v +auto invoke_and_wait(QPointer object, const F& function, Args&&... args) +{ + QPromise promise; + QFuture future = promise.future(); + + call_from_object_thread(object, [&promise, &function, &args...]() + { + promise.start(); + promise.addResult(function(args...)); + promise.finish(); + }); + + future.waitForFinished(); + + return future.result(); +} + + // call_from_object_thread uses Qt mechanisms to invoke function in another thread // (thread of 'object' object) template diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 3e4870dacc..d42d3f0508 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -8,32 +8,6 @@ #include "batch_face_detector.hpp" -#include -#include -#include -namespace -{ - template - requires std::is_base_of_v - auto invoke_and_wait(QPointer object, const F& function, Args&&... args) - { - QPromise promise; - QFuture future = promise.future(); - - call_from_object_thread(object, [&promise, &function, &args...]() - { - promise.start(); - promise.addResult(function(args...)); - promise.finish(); - }); - - future.waitForFinished(); - - return future.result(); - } -} - - void BatchFaceDetector::setPhotosModel(APhotoDataModel* model) { if (m_photosModel != nullptr) From e7549d961e7d496ea41f05cc5a1c4f3152c4539c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 1 Mar 2023 09:14:03 +0100 Subject: [PATCH 048/151] Fallback to c++20 Visual studio does not handle simples lambdas yet --- src/gui/desktop/utils/batch_face_detector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index d42d3f0508..10f9c88a90 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -108,7 +108,7 @@ void BatchFaceDetector::processPhotos() const auto id = m_ids.front(); m_ids.pop_front(); - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this] mutable + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this]() mutable { auto faces = fe.getFacesFor(id); std::vector facesDetails; From 0cbbfcce8249d16770a40f9885a5a07d023050d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 1 Mar 2023 16:15:56 +0100 Subject: [PATCH 049/151] Show name --- .../quick_items/Views/BatchFaceDetection.qml | 18 ++++++++++++++---- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index b033a2b4f1..adf747b775 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -30,6 +30,7 @@ Item { GroupBox { title: qsTr("Discovered faces") + clip: true GridView { anchors.fill: parent @@ -38,18 +39,27 @@ Item { cellWidth: 170 cellHeight: 170 - delegate: Picture { + delegate: Column { required property var decoration + required property var display - height: 150 - width: 150 - source: decoration + Picture { + height: 150 + width: 150 + source: decoration + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: display + } } } } GroupBox { title: qsTr("Photos to be analyzed") + clip: true Internals.PhotosGridView { anchors.fill: parent diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 11521b54d0..b4773baaa3 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 9a4242ea5f..b0bd48d932 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From 10df80a81ebe428266c536b8244c9795d76df99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 2 Mar 2023 14:37:48 +0100 Subject: [PATCH 050/151] drop unused includes --- src/gui/desktop/ui/mainwindow.cpp | 3 --- tr/photo_broom_en.ts | 36 +++++++++++++++---------------- tr/photo_broom_pl.ts | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/gui/desktop/ui/mainwindow.cpp b/src/gui/desktop/ui/mainwindow.cpp index 7676383e24..5c54cb95f1 100644 --- a/src/gui/desktop/ui/mainwindow.cpp +++ b/src/gui/desktop/ui/mainwindow.cpp @@ -5,10 +5,7 @@ #include #include -#include -#include #include -#include #include #include diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index b4773baaa3..086b518715 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -413,54 +413,54 @@ Check paths in configuration window. - + New version - + New version of PhotoBroom is available <a href="%1">here</a>. - + Internet connection problem - + Could not check if there is new version of PhotoBroom. Please check your internet connection. - + Open collection - + Photo Broom files (*.bpj) - + About Photo Broom - + About Qt - - + + Unsupported photo collection version - + Photo collection you are trying to open uses database in version which is not supported. It means your application is too old to open it. @@ -468,7 +468,7 @@ Please upgrade application to open this collection. - + Photo collection you are trying to open uses database in version which is not supported. It means your database is too old to open it. @@ -476,12 +476,12 @@ It means your database is too old to open it. - + Could not open collection - + Photo collection could not be opened. It usually means that collection files are broken or you don't have rights to access them. @@ -491,23 +491,23 @@ Please check collection files: - + Collection locked - + Photo collection could not be opened. It is already opened by another Photo Broom instance. - + Unexpected error - + An unexpected error occured while opening photo collection. Please report a bug. Error code: %1 diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index b0bd48d932..3e72627d6d 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -422,7 +422,7 @@ Sprawdź poprawność ścieżek w konfiguracji. &Operacje - + New version Nowa wersja From 4213465d32b4d227fcedfa574bdf78597efce133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 12 Mar 2023 19:01:26 +0100 Subject: [PATCH 051/151] Add experiment with coroutine based task manager --- src/core/implementation/task_executor.cpp | 54 ++++++++++++++++++- .../implementation/task_executor_utils.cpp | 7 +++ src/core/itask_executor.hpp | 44 +++++++++++++-- src/core/task_executor.hpp | 11 +++- src/core/task_executor_utils.hpp | 1 + src/gui/desktop/utils/batch_face_detector.cpp | 24 +++++++++ src/gui/desktop/utils/batch_face_detector.hpp | 2 + src/unit_tests_utils/fake_task_executor.hpp | 6 +++ 8 files changed, 143 insertions(+), 6 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 7e6a700a9b..d31da21eda 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -38,10 +38,15 @@ TaskExecutor::TaskExecutor(ILogger& logger, int threadsToUse): { m_logger.info(QString("Using %1 threads.").arg(m_threads)); - m_taskEater = std::thread( [&] + m_taskEater = std::thread([&] { this->eat(); }); + + m_processRunner = std::thread([&] + { + this->runProcesses(); + }); } @@ -58,6 +63,13 @@ void TaskExecutor::add(std::unique_ptr&& task) } +void TaskExecutor::add(std::shared_ptr&& task) +{ + m_processes[task] = IProcess::Process(); + m_processesIdleCV.notify_one(); +} + + int TaskExecutor::heavyWorkers() const { return m_threads; @@ -70,9 +82,16 @@ void TaskExecutor::stop() { m_working = false; - // wait for heavy tasks + // stop processes + m_processesIdleCV.notify_one(); + assert(m_processRunner.joinable()); + + // stop heavy tasks m_tasks.stop(); assert(m_taskEater.joinable()); + + // wait for threads + m_processRunner.join(); m_taskEater.join(); } } @@ -169,3 +188,34 @@ void TaskExecutor::execute(const std::shared_ptr& task) const { task->perform(); } + + +void TaskExecutor::runProcesses() +{ + while(m_working) + { + bool has_running = false; + + for(auto& process: m_processes) + { + if (process.first->state == IProcess::State::NotStarted) + { + has_running = true; + process.second = process.first->init(); + process.first->state = IProcess::State::Running; + } + + if (process.first->state == IProcess::State::Running) + { + has_running = true; + process.second.h_(); + } + } + + if (has_running == false) + { + std::unique_lock lock(m_processesIdleMutex); + m_processesIdleCV.wait(lock); + } + }; +} diff --git a/src/core/implementation/task_executor_utils.cpp b/src/core/implementation/task_executor_utils.cpp index 6c1bcc15c0..ab46bc493b 100644 --- a/src/core/implementation/task_executor_utils.cpp +++ b/src/core/implementation/task_executor_utils.cpp @@ -86,6 +86,13 @@ void TasksQueue::add(std::unique_ptr&& task) } +void TasksQueue::add(std::shared_ptr&& task) +{ + /// TODO: implement + assert(!"Not implemenbted yet"); +} + + int TasksQueue::heavyWorkers() const { return m_executor.heavyWorkers(); diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index bdfe0e0677..6aec22195f 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -20,6 +20,7 @@ #ifndef ITASKEXECUTOR_H #define ITASKEXECUTOR_H +#include #include #include @@ -36,11 +37,48 @@ struct CORE_EXPORT ITaskExecutor virtual void perform() = 0; ///< @brief perform job }; + struct CORE_EXPORT IProcess + { + enum class State + { + NotStarted, + Suspended, + Running, + }; + + State state = State::NotStarted; + + struct promise; + + struct Process + { + struct promise_type; + using handle_type = std::coroutine_handle; + + struct promise_type + { + Process get_return_object() + { + return {.h_ = handle_type::from_promise(*this)}; + } + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; + + handle_type h_; + }; + + virtual Process init() = 0; + }; + virtual ~ITaskExecutor() = default; - virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) + virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) + virtual void add(std::shared_ptr &&) = 0; // add task to be run in a ring with other IProcesses - virtual int heavyWorkers() const = 0; // return number of heavy task workers + virtual int heavyWorkers() const = 0; // return number of heavy task workers }; -#endif // TASKEXECUTOR_H +#endif diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 2447d0d76b..721f88b4d9 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -20,6 +20,8 @@ #ifndef TASKEXECUTOR_HPP #define TASKEXECUTOR_HPP +#include +#include #include #include "core_export.h" @@ -38,6 +40,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor TaskExecutor& operator=(const TaskExecutor &) = delete; void add(std::unique_ptr &&) override; + void add(std::shared_ptr &&) override; int heavyWorkers() const override; @@ -45,15 +48,21 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor private: typedef ol::TS_Queue> QueueT; + typedef std::map, IProcess::Process> ProcessesT; QueueT m_tasks; + ProcessesT m_processes; std::thread m_taskEater; + std::thread m_processRunner; + std::mutex m_processesIdleMutex; + std::condition_variable m_processesIdleCV; ILogger& m_logger; unsigned int m_threads; bool m_working; void eat(); void execute(const std::shared_ptr& task) const; + void runProcesses(); }; -#endif // TASKEXECUTOR_HPP +#endif diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index 27402555c6..dd2861003f 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -301,6 +301,7 @@ class CORE_EXPORT TasksQueue: public ITaskExecutor, public Queue &&) override; + void add(std::shared_ptr &&) override; int heavyWorkers() const override; private: diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 10f9c88a90..c61237c636 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -3,10 +3,32 @@ #include #include #include +#include #include #include "batch_face_detector.hpp" +#include +namespace +{ + class Process: public ITaskExecutor::IProcess + { + ITaskExecutor::IProcess::Process init() override + { + co_await std::suspend_always{}; + + std::cout << "5\n"; + } + }; +} + + + +BatchFaceDetector::~BatchFaceDetector() +{ + +} + void BatchFaceDetector::setPhotosModel(APhotoDataModel* model) { @@ -42,6 +64,8 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) assert(m_core == nullptr); m_core = core; m_logger = m_core->getLoggerFactory().get("BatchFaceDetector"); + + m_core->getTaskExecutor().add(std::make_shared()); } diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index ad3c53598d..5b3f41cec5 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -21,6 +21,8 @@ class BatchFaceDetector: public QAbstractListModel Q_PROPERTY(APhotoDataModel* photos_model WRITE setPhotosModel READ photosModel) public: + ~BatchFaceDetector(); + void setPhotosModel(APhotoDataModel *); void setCore(ICoreFactoryAccessor *); void setDB(Database::IDatabase *); diff --git a/src/unit_tests_utils/fake_task_executor.hpp b/src/unit_tests_utils/fake_task_executor.hpp index 0227b8b859..d8376aeaca 100644 --- a/src/unit_tests_utils/fake_task_executor.hpp +++ b/src/unit_tests_utils/fake_task_executor.hpp @@ -9,6 +9,12 @@ class FakeTaskExecutor: public ITaskExecutor task->perform(); } + void add(std::shared_ptr&& task) override + { + /// TODO: implement + assert(!"Not implemented yet"); + } + int heavyWorkers() const override { return 1; From bd5eadef1f6b06d251b6b2707090474420300a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 12 Mar 2023 23:01:13 +0100 Subject: [PATCH 052/151] Put as many filters as possible before Filter definition that Fixes clang compialtion --- src/database/filter.hpp | 94 ++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/src/database/filter.hpp b/src/database/filter.hpp index 64f17354cf..cdd1ff69cb 100644 --- a/src/database/filter.hpp +++ b/src/database/filter.hpp @@ -35,40 +35,6 @@ namespace Database { - //filters - - struct EmptyFilter; - struct GroupFilter; - struct FilterPhotosWithTag; - struct FilterPhotosWithFlags; - struct FilterNotMatchingFilter; - struct FilterPhotosWithId; - struct FilterPhotosMatchingExpression; - struct FilterPhotosWithPath; - struct FilterPhotosWithRole; - struct FilterPhotosWithPerson; - struct FilterPhotosWithGeneralFlag; - struct FilterPhotosWithPHash; - struct FilterSimilarPhotos; - struct FilterFaceAnalysisStatus; - - - typedef std::variant Filter; - enum class ComparisonOp { Equal, @@ -84,18 +50,14 @@ namespace Database Or, }; - struct DATABASE_EXPORT EmptyFilter - { + //filters - }; + struct GroupFilter; + struct FilterNotMatchingFilter; - struct DATABASE_EXPORT GroupFilter + struct DATABASE_EXPORT EmptyFilter { - GroupFilter(const std::vector &); - GroupFilter(const std::initializer_list &); - std::vector filters; - LogicalOp mode = LogicalOp::And; }; struct DATABASE_EXPORT FilterPhotosWithTag @@ -181,6 +143,44 @@ namespace Database struct DATABASE_EXPORT FilterSimilarPhotos { }; + struct DATABASE_EXPORT FilterFaceAnalysisStatus + { + enum Status + { + NotPerformed, + Performed, + } status; + + FilterFaceAnalysisStatus(Status s): status(s) {}; + }; + + + typedef std::variant Filter; + + + struct DATABASE_EXPORT GroupFilter + { + GroupFilter(const std::vector &); + GroupFilter(const std::initializer_list &); + + std::vector filters; + LogicalOp mode = LogicalOp::And; + }; + struct DATABASE_EXPORT FilterNotMatchingFilter { template @@ -192,16 +192,6 @@ namespace Database ol::data_ptr filter; }; - struct DATABASE_EXPORT FilterFaceAnalysisStatus - { - enum Status - { - NotPerformed, - Performed, - } status; - - FilterFaceAnalysisStatus(Status s): status(s) {}; - }; // helpers From 61d232db6ff4ef4a0c1cd6f17a9aa07d9f8590a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 15 Mar 2023 20:47:32 +0100 Subject: [PATCH 053/151] Simplify usage Instead of interface just std::function --- src/core/implementation/task_executor.cpp | 15 ++---- .../implementation/task_executor_utils.cpp | 2 +- src/core/itask_executor.hpp | 50 ++++++++----------- src/core/task_executor.hpp | 17 +++++-- src/core/task_executor_utils.hpp | 2 +- .../memory_backend/memory_backend.cpp | 3 +- src/gui/desktop/utils/batch_face_detector.cpp | 24 ++++----- src/unit_tests_utils/fake_task_executor.hpp | 7 ++- 8 files changed, 57 insertions(+), 63 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index d31da21eda..20b9240cd7 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -63,9 +63,9 @@ void TaskExecutor::add(std::unique_ptr&& task) } -void TaskExecutor::add(std::shared_ptr&& task) +void TaskExecutor::add(Process&& task) { - m_processes[task] = IProcess::Process(); + m_processes.emplace_back(ProcessInfo::State::Running, task()); m_processesIdleCV.notify_one(); } @@ -198,17 +198,10 @@ void TaskExecutor::runProcesses() for(auto& process: m_processes) { - if (process.first->state == IProcess::State::NotStarted) + if (process.state == ProcessInfo::State::Running) { has_running = true; - process.second = process.first->init(); - process.first->state = IProcess::State::Running; - } - - if (process.first->state == IProcess::State::Running) - { - has_running = true; - process.second.h_(); + process.coru.h(); } } diff --git a/src/core/implementation/task_executor_utils.cpp b/src/core/implementation/task_executor_utils.cpp index ab46bc493b..8c0b698c1c 100644 --- a/src/core/implementation/task_executor_utils.cpp +++ b/src/core/implementation/task_executor_utils.cpp @@ -86,7 +86,7 @@ void TasksQueue::add(std::unique_ptr&& task) } -void TasksQueue::add(std::shared_ptr&& task) +void TasksQueue::add(Process &&) { /// TODO: implement assert(!"Not implemenbted yet"); diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 6aec22195f..e942a17d68 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -21,6 +21,7 @@ #define ITASKEXECUTOR_H #include +#include #include #include @@ -37,46 +38,37 @@ struct CORE_EXPORT ITaskExecutor virtual void perform() = 0; ///< @brief perform job }; - struct CORE_EXPORT IProcess + struct ProcessCoroutine { - enum class State - { - NotStarted, - Suspended, - Running, - }; - - State state = State::NotStarted; - - struct promise; + struct promise_type; + using handle_type = std::coroutine_handle; - struct Process + struct promise_type { - struct promise_type; - using handle_type = std::coroutine_handle; - - struct promise_type + ProcessCoroutine get_return_object() { - Process get_return_object() - { - return {.h_ = handle_type::from_promise(*this)}; - } - std::suspend_always initial_suspend() noexcept { return {}; } - std::suspend_always final_suspend() noexcept { return {}; } - void return_void() {} - void unhandled_exception() {} - }; - - handle_type h_; + return ProcessCoroutine(handle_type::from_promise(*this)); + } + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} }; - virtual Process init() = 0; + ProcessCoroutine() = default; + ProcessCoroutine(handle_type h_): h(h_) {} + + handle_type h = nullptr; + operator std::coroutine_handle() const { return h; } + operator std::coroutine_handle<>() const { return h; } }; + using Process = std::function; + virtual ~ITaskExecutor() = default; virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) - virtual void add(std::shared_ptr &&) = 0; // add task to be run in a ring with other IProcesses + virtual void add(Process &&) = 0; // add task to be run in a ring with other Processes virtual int heavyWorkers() const = 0; // return number of heavy task workers }; diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 721f88b4d9..c4dce86e45 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -40,17 +40,28 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor TaskExecutor& operator=(const TaskExecutor &) = delete; void add(std::unique_ptr &&) override; - void add(std::shared_ptr &&) override; + void add(Process &&) override; int heavyWorkers() const override; void stop(); private: + struct ProcessInfo + { + enum class State + { + Suspended, + Running, + }; + + State state = State::Suspended; + ProcessCoroutine coru; + }; + typedef ol::TS_Queue> QueueT; - typedef std::map, IProcess::Process> ProcessesT; QueueT m_tasks; - ProcessesT m_processes; + std::vector m_processes; std::thread m_taskEater; std::thread m_processRunner; std::mutex m_processesIdleMutex; diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index dd2861003f..9d0515ac69 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -301,7 +301,7 @@ class CORE_EXPORT TasksQueue: public ITaskExecutor, public Queue &&) override; - void add(std::shared_ptr &&) override; + void add(Process &&) override; int heavyWorkers() const override; private: diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index d481be98e1..7bf1414e3c 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -14,7 +14,6 @@ #include "database/transaction_wrapper.hpp" -using boost::multi_index_container; using namespace boost::multi_index; namespace Database @@ -23,7 +22,7 @@ namespace Database struct pi_id_tag {}; struct pi_ph_id_tag {}; - using PeopleContainer = multi_index_container< + using PeopleContainer = boost::multi_index_container< PersonInfo, indexed_by< ordered_unique, BOOST_MULTI_INDEX_MEMBER(PersonInfo, PersonInfo::Id, id)>, diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index c61237c636..3d0bb79eeb 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -9,20 +9,6 @@ #include "batch_face_detector.hpp" #include -namespace -{ - class Process: public ITaskExecutor::IProcess - { - ITaskExecutor::IProcess::Process init() override - { - co_await std::suspend_always{}; - - std::cout << "5\n"; - } - }; -} - - BatchFaceDetector::~BatchFaceDetector() { @@ -65,7 +51,15 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) m_core = core; m_logger = m_core->getLoggerFactory().get("BatchFaceDetector"); - m_core->getTaskExecutor().add(std::make_shared()); + m_core->getTaskExecutor().add([]() -> ITaskExecutor::ProcessCoroutine + { + while(true) + { + co_await std::suspend_always{}; + + std::cout << "5\n"; + } + }); } diff --git a/src/unit_tests_utils/fake_task_executor.hpp b/src/unit_tests_utils/fake_task_executor.hpp index d8376aeaca..b7321d1a61 100644 --- a/src/unit_tests_utils/fake_task_executor.hpp +++ b/src/unit_tests_utils/fake_task_executor.hpp @@ -1,4 +1,7 @@ +#ifndef FAKE_TASK_EXECUTOR_HPP_INCLUDED +#define FAKE_TASK_EXECUTOR_HPP_INCLUDED + #include class FakeTaskExecutor: public ITaskExecutor @@ -9,7 +12,7 @@ class FakeTaskExecutor: public ITaskExecutor task->perform(); } - void add(std::shared_ptr&& task) override + void add(Process &&) override { /// TODO: implement assert(!"Not implemented yet"); @@ -20,3 +23,5 @@ class FakeTaskExecutor: public ITaskExecutor return 1; } }; + +#endif From b2b9c16f34afc02794c9a0ceccfacc5d7545d135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 16 Mar 2023 21:45:48 +0100 Subject: [PATCH 054/151] Simplify --- src/core/implementation/task_executor.cpp | 2 +- src/core/itask_executor.hpp | 1 - src/core/task_executor.hpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 20b9240cd7..b893d007ad 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -201,7 +201,7 @@ void TaskExecutor::runProcesses() if (process.state == ProcessInfo::State::Running) { has_running = true; - process.coru.h(); + process.co_h(); } } diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index e942a17d68..663bd9364c 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -55,7 +55,6 @@ struct CORE_EXPORT ITaskExecutor void unhandled_exception() {} }; - ProcessCoroutine() = default; ProcessCoroutine(handle_type h_): h(h_) {} handle_type h = nullptr; diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index c4dce86e45..d445966029 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -56,7 +56,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor }; State state = State::Suspended; - ProcessCoroutine coru; + ProcessCoroutine::handle_type co_h; }; typedef ol::TS_Queue> QueueT; From 9ddcd287e4f798227e7f325c60a21ccbd9f53d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 16 Mar 2023 21:45:55 +0100 Subject: [PATCH 055/151] Reorder initialization --- src/gui/desktop/utils/people_editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 50280add81..63b550e46e 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -328,9 +328,9 @@ namespace FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const std::unique_ptr& logger) - : m_db(db) + : m_logger(logger->subLogger("FaceEditor")) + , m_db(db) , m_core(core) - , m_logger(logger->subLogger("FaceEditor")) { } From 03987a92c96e33d7efdb6128714bae7b0a090da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 16 Mar 2023 21:52:47 +0100 Subject: [PATCH 056/151] Return interface for process control --- src/core/implementation/task_executor.cpp | 8 +++++--- src/core/implementation/task_executor_utils.cpp | 4 +++- src/core/itask_executor.hpp | 15 ++++++++++++++- src/core/task_executor.hpp | 10 ++-------- src/core/task_executor_utils.hpp | 2 +- src/unit_tests_utils/fake_task_executor.hpp | 4 +++- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index b893d007ad..2200b8dba7 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -63,10 +63,12 @@ void TaskExecutor::add(std::unique_ptr&& task) } -void TaskExecutor::add(Process&& task) +ITaskExecutor::IProcessControl* TaskExecutor::add(Process&& task) { - m_processes.emplace_back(ProcessInfo::State::Running, task()); + m_processes.emplace_back(ProcessState::Running, task()); m_processesIdleCV.notify_one(); + + return nullptr; } @@ -198,7 +200,7 @@ void TaskExecutor::runProcesses() for(auto& process: m_processes) { - if (process.state == ProcessInfo::State::Running) + if (process.state == ProcessState::Running) { has_running = true; process.co_h(); diff --git a/src/core/implementation/task_executor_utils.cpp b/src/core/implementation/task_executor_utils.cpp index 8c0b698c1c..abb19b98ea 100644 --- a/src/core/implementation/task_executor_utils.cpp +++ b/src/core/implementation/task_executor_utils.cpp @@ -86,10 +86,12 @@ void TasksQueue::add(std::unique_ptr&& task) } -void TasksQueue::add(Process &&) +ITaskExecutor::IProcessControl* TasksQueue::add(Process &&) { /// TODO: implement assert(!"Not implemenbted yet"); + + return nullptr; } diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 663bd9364c..8b22e835af 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -64,10 +64,23 @@ struct CORE_EXPORT ITaskExecutor using Process = std::function; + enum class ProcessState + { + Suspended, + Running, + }; + + struct IProcessControl + { + virtual void terminate() = 0; + virtual void resume() = 0; + virtual ProcessState state() = 0; + }; + virtual ~ITaskExecutor() = default; virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) - virtual void add(Process &&) = 0; // add task to be run in a ring with other Processes + virtual IProcessControl* add(Process &&) = 0; // add task to be run in a ring with other Processes virtual int heavyWorkers() const = 0; // return number of heavy task workers }; diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index d445966029..122133da45 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -40,7 +40,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor TaskExecutor& operator=(const TaskExecutor &) = delete; void add(std::unique_ptr &&) override; - void add(Process &&) override; + IProcessControl* add(Process &&) override; int heavyWorkers() const override; @@ -49,13 +49,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor private: struct ProcessInfo { - enum class State - { - Suspended, - Running, - }; - - State state = State::Suspended; + ProcessState state = ProcessState::Suspended; ProcessCoroutine::handle_type co_h; }; diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index 9d0515ac69..0f3f2438d6 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -301,7 +301,7 @@ class CORE_EXPORT TasksQueue: public ITaskExecutor, public Queue &&) override; - void add(Process &&) override; + IProcessControl* add(Process &&) override; int heavyWorkers() const override; private: diff --git a/src/unit_tests_utils/fake_task_executor.hpp b/src/unit_tests_utils/fake_task_executor.hpp index b7321d1a61..a1e0a9409b 100644 --- a/src/unit_tests_utils/fake_task_executor.hpp +++ b/src/unit_tests_utils/fake_task_executor.hpp @@ -12,10 +12,12 @@ class FakeTaskExecutor: public ITaskExecutor task->perform(); } - void add(Process &&) override + IProcessControl* add(Process &&) override { /// TODO: implement assert(!"Not implemented yet"); + + return nullptr; } int heavyWorkers() const override From b02c4de6b222c5a6dd6f4f72a4750f4ba9f75b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 17 Mar 2023 22:24:30 +0100 Subject: [PATCH 057/151] Allow to suspend process --- src/core/implementation/task_executor.cpp | 80 +++++++++++++++++-- src/core/itask_executor.hpp | 24 ++++-- src/core/task_executor.hpp | 49 +++++++++++- src/core/task_executor_utils.hpp | 1 - src/gui/desktop/utils/batch_face_detector.cpp | 7 +- 5 files changed, 140 insertions(+), 21 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 2200b8dba7..51057261c0 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -29,6 +30,27 @@ #include "thread_utils.hpp" +void TaskExecutor::ProcessInfo::terminate() +{ + m_executor.terminate(this); +} + + +void TaskExecutor::ProcessInfo::resume() +{ + +} + + +ITaskExecutor::ProcessState TaskExecutor::ProcessInfo::state() +{ + return m_state; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + TaskExecutor::TaskExecutor(ILogger& logger, int threadsToUse): m_tasks(), m_taskEater(), @@ -65,8 +87,8 @@ void TaskExecutor::add(std::unique_ptr&& task) ITaskExecutor::IProcessControl* TaskExecutor::add(Process&& task) { - m_processes.emplace_back(ProcessState::Running, task()); - m_processesIdleCV.notify_one(); + m_processes.push_back(std::make_unique(*this, ProcessState::Running, task())); + wakeUpScheduler(); return nullptr; } @@ -85,7 +107,7 @@ void TaskExecutor::stop() m_working = false; // stop processes - m_processesIdleCV.notify_one(); + wakeUpScheduler(); assert(m_processRunner.joinable()); // stop heavy tasks @@ -198,15 +220,48 @@ void TaskExecutor::runProcesses() { bool has_running = false; + std::set toTerminate; + for(auto& process: m_processes) { - if (process.state == ProcessState::Running) + const auto state = process->state(); + + switch(state) { - has_running = true; - process.co_h(); + case ProcessState::Suspended: + break; + + case ProcessState::Running: + { + const auto stateRequest = process->run(); + + switch(stateRequest) + { + case ProcessStateRequest::Run: + has_running = true; + break; + + case ProcessStateRequest::Suspend: + process->setState(ProcessState::Suspended); + break; + + case ProcessStateRequest::Terminate: + toTerminate.insert(process.get()); + break; + } + + break; + } } } + if (toTerminate.empty() == false) + m_processes.erase(std::remove_if( + m_processes.begin(), + m_processes.end(), + [&toTerminate](const auto& process){ return toTerminate.contains(process.get()); }), + m_processes.end()); + if (has_running == false) { std::unique_lock lock(m_processesIdleMutex); @@ -214,3 +269,16 @@ void TaskExecutor::runProcesses() } }; } + + +void TaskExecutor::terminate(TaskExecutor::ProcessInfo* process) +{ + std::lock_guard _(m_processAlternationMutex); + // process->setState(ProcessState::Terminating); + wakeUpScheduler(); +} + +void TaskExecutor::wakeUpScheduler() +{ + m_processesIdleCV.notify_one(); +} diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 8b22e835af..9cc004364e 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -38,6 +38,19 @@ struct CORE_EXPORT ITaskExecutor virtual void perform() = 0; ///< @brief perform job }; + enum class ProcessStateRequest + { + Suspend, + Run, + Terminate, + }; + + enum class ProcessState + { + Suspended, + Running, + }; + struct ProcessCoroutine { struct promise_type; @@ -45,14 +58,17 @@ struct CORE_EXPORT ITaskExecutor struct promise_type { + ProcessStateRequest stateRequest = ProcessStateRequest::Run; + ProcessCoroutine get_return_object() { return ProcessCoroutine(handle_type::from_promise(*this)); } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } - void return_void() {} + void return_void() { stateRequest = ProcessStateRequest::Terminate; } void unhandled_exception() {} + std::suspend_always yield_value(ProcessStateRequest sr) { stateRequest = sr; return {}; } }; ProcessCoroutine(handle_type h_): h(h_) {} @@ -64,12 +80,6 @@ struct CORE_EXPORT ITaskExecutor using Process = std::function; - enum class ProcessState - { - Suspended, - Running, - }; - struct IProcessControl { virtual void terminate() = 0; diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 122133da45..d5b8f9f35f 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -47,18 +47,57 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor void stop(); private: - struct ProcessInfo + class ProcessInfo: IProcessControl { - ProcessState state = ProcessState::Suspended; - ProcessCoroutine::handle_type co_h; + public: + ProcessInfo(TaskExecutor& executor, ProcessState s, const ProcessCoroutine& h) + : m_state(s) + , m_co_h(h) + , m_executor(executor) + {} + + ~ProcessInfo() + { + m_co_h.destroy(); + } + + void terminate() override; + void resume() override; + ProcessState state() override; + + void setState(ProcessState s) + { + m_state = s; + } + + ProcessStateRequest run() const + { + m_co_h(); + + return stateRequest(); + } + + ProcessStateRequest stateRequest() const + { + const auto &promise = m_co_h.promise(); + return promise.stateRequest; + } + + private: + ProcessState m_state = ProcessState::Suspended; + ProcessCoroutine::handle_type m_co_h; + TaskExecutor& m_executor; }; + friend class ProcessInfo; + typedef ol::TS_Queue> QueueT; QueueT m_tasks; - std::vector m_processes; + std::vector> m_processes; std::thread m_taskEater; std::thread m_processRunner; std::mutex m_processesIdleMutex; + std::mutex m_processAlternationMutex; std::condition_variable m_processesIdleCV; ILogger& m_logger; unsigned int m_threads; @@ -67,6 +106,8 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor void eat(); void execute(const std::shared_ptr& task) const; void runProcesses(); + void terminate(ProcessInfo *); + void wakeUpScheduler(); }; diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index 0f3f2438d6..86b90cd548 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -289,7 +289,6 @@ class Queue }; - /** * @brief A subqueue for ITaskExecutor. */ diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 3d0bb79eeb..62ca096fb3 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -53,12 +53,13 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) m_core->getTaskExecutor().add([]() -> ITaskExecutor::ProcessCoroutine { - while(true) + //while(true) { - co_await std::suspend_always{}; - std::cout << "5\n"; + co_yield ITaskExecutor::ProcessStateRequest::Run; } + + std::cout << "3\n"; }); } From 4f34c2df03a913c94c1d3649c55a16393dedaa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 18 Mar 2023 13:54:21 +0100 Subject: [PATCH 058/151] Use ITaskExecutor::Process in BatchFaceDetector --- src/core/implementation/task_executor.cpp | 34 ++++++--- src/core/itask_executor.hpp | 7 +- src/core/task_executor.hpp | 15 ++-- src/gui/desktop/utils/batch_face_detector.cpp | 73 +++++++++---------- src/gui/desktop/utils/batch_face_detector.hpp | 6 +- 5 files changed, 76 insertions(+), 59 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 51057261c0..ba025ae2a3 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -32,13 +32,15 @@ void TaskExecutor::ProcessInfo::terminate() { - m_executor.terminate(this); + m_work = false; + m_executor.wakeUpScheduler(); } void TaskExecutor::ProcessInfo::resume() { - + setState(ITaskExecutor::ProcessState::Running); + m_executor.wakeUpScheduler(); } @@ -48,10 +50,16 @@ ITaskExecutor::ProcessState TaskExecutor::ProcessInfo::state() } +bool TaskExecutor::ProcessInfo::keepWorking() +{ + return m_work; +} + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TaskExecutor::TaskExecutor(ILogger& logger, int threadsToUse): +TaskExecutor::TaskExecutor(ILogger& logger, unsigned int threadsToUse): m_tasks(), m_taskEater(), m_logger(logger), @@ -87,10 +95,15 @@ void TaskExecutor::add(std::unique_ptr&& task) ITaskExecutor::IProcessControl* TaskExecutor::add(Process&& task) { - m_processes.push_back(std::make_unique(*this, ProcessState::Running, task())); + auto process = std::make_unique(*this, ProcessState::Running); + ITaskExecutor::IProcessControl* control = process.get(); + ITaskExecutor::IProcessSupervisor* supervisor = process.get(); + process->setCoroutine(task(supervisor)); + + m_processes.push_back(std::move(process)); wakeUpScheduler(); - return nullptr; + return control; } @@ -233,8 +246,12 @@ void TaskExecutor::runProcesses() case ProcessState::Running: { + const bool toBeStopped = !process->keepWorking(); const auto stateRequest = process->run(); + // if toBeStopped == true then process should exit with Terminate request + assert(stateRequest == ProcessStateRequest::Terminate || toBeStopped == false); + switch(stateRequest) { case ProcessStateRequest::Run: @@ -271,13 +288,6 @@ void TaskExecutor::runProcesses() } -void TaskExecutor::terminate(TaskExecutor::ProcessInfo* process) -{ - std::lock_guard _(m_processAlternationMutex); - // process->setState(ProcessState::Terminating); - wakeUpScheduler(); -} - void TaskExecutor::wakeUpScheduler() { m_processesIdleCV.notify_one(); diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 9cc004364e..051dd7c254 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -51,6 +51,11 @@ struct CORE_EXPORT ITaskExecutor Running, }; + struct IProcessSupervisor + { + virtual bool keepWorking() = 0; + }; + struct ProcessCoroutine { struct promise_type; @@ -78,7 +83,7 @@ struct CORE_EXPORT ITaskExecutor operator std::coroutine_handle<>() const { return h; } }; - using Process = std::function; + using Process = std::function; struct IProcessControl { diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index d5b8f9f35f..c19f0d3dd6 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -33,7 +33,7 @@ struct ILogger; struct CORE_EXPORT TaskExecutor: public ITaskExecutor { - explicit TaskExecutor(ILogger &, int threadsToUse); + explicit TaskExecutor(ILogger &, unsigned int threadsToUse); TaskExecutor(const TaskExecutor &) = delete; virtual ~TaskExecutor(); @@ -47,12 +47,11 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor void stop(); private: - class ProcessInfo: IProcessControl + class ProcessInfo: public IProcessControl, public IProcessSupervisor { public: - ProcessInfo(TaskExecutor& executor, ProcessState s, const ProcessCoroutine& h) + ProcessInfo(TaskExecutor& executor, ProcessState s) : m_state(s) - , m_co_h(h) , m_executor(executor) {} @@ -61,9 +60,15 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor m_co_h.destroy(); } + void setCoroutine(const ProcessCoroutine& h) + { + m_co_h = h; + } + void terminate() override; void resume() override; ProcessState state() override; + bool keepWorking() override; void setState(ProcessState s) { @@ -87,6 +92,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor ProcessState m_state = ProcessState::Suspended; ProcessCoroutine::handle_type m_co_h; TaskExecutor& m_executor; + bool m_work = true; }; friend class ProcessInfo; @@ -106,7 +112,6 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor void eat(); void execute(const std::shared_ptr& task) const; void runProcesses(); - void terminate(ProcessInfo *); void wakeUpScheduler(); }; diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 62ca096fb3..6930e07f64 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "batch_face_detector.hpp" @@ -12,7 +11,7 @@ BatchFaceDetector::~BatchFaceDetector() { - + m_photosProcessingProcess->terminate(); } @@ -51,16 +50,8 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) m_core = core; m_logger = m_core->getLoggerFactory().get("BatchFaceDetector"); - m_core->getTaskExecutor().add([]() -> ITaskExecutor::ProcessCoroutine - { - //while(true) - { - std::cout << "5\n"; - co_yield ITaskExecutor::ProcessStateRequest::Run; - } - - std::cout << "3\n"; - }); + auto process = std::bind(&BatchFaceDetector::processPhotos, this, std::placeholders::_1); + m_photosProcessingProcess = m_core->getTaskExecutor().add(process); } @@ -110,36 +101,42 @@ QVariant BatchFaceDetector::data(const QModelIndex& idx, int role) const } -void BatchFaceDetector::kickProcessing() +ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor::IProcessSupervisor* supervisor) { - if (m_ids.empty() == false) - QMetaObject::invokeMethod(this, &BatchFaceDetector::processPhotos, Qt::QueuedConnection); -} + while(supervisor->keepWorking()) + { + std::unique_lock _(m_idsMtx); + // sleep if there is nothing to do + if (m_ids.empty()) + { + _.unlock(); + co_yield ITaskExecutor::ProcessStateRequest::Suspend; + continue; + } -void BatchFaceDetector::processPhotos() -{ - assert(m_ids.empty() == false); - QPointer modelPtr(m_photosModel); + FaceEditor fe(*m_db, *m_core, m_logger); - FaceEditor fe(*m_db, *m_core, m_logger); + const auto id = m_ids.front(); + m_ids.pop_front(); + _.unlock(); - const auto id = m_ids.front(); - m_ids.pop_front(); + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this]() mutable + { + auto faces = fe.getFacesFor(id); + std::vector facesDetails; - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this]() mutable - { - auto faces = fe.getFacesFor(id); - std::vector facesDetails; + for (auto& face: faces) + { + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } - for (auto& face: faces) - { - const auto faceImg = face->image()->copy(face->rect()); - facesDetails.emplace_back(std::move(face), faceImg); - } + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); + }); - invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); - }); + co_yield ITaskExecutor::ProcessStateRequest::Run; + } } @@ -153,14 +150,12 @@ void BatchFaceDetector::appendFaces(std::vector&& faces) m_faces.insert(m_faces.end(), std::make_move_iterator(faces.begin()), std::make_move_iterator(faces.end())); endInsertRows(); } - - kickProcessing(); } void BatchFaceDetector::newPhotos(const QModelIndex &, int first, int last) { - const bool needKicking = m_ids.empty(); + std::lock_guard _(m_idsMtx); for(int row = first; row <= last; row++) { @@ -168,6 +163,6 @@ void BatchFaceDetector::newPhotos(const QModelIndex &, int first, int last) m_ids.push_back(id); } - if (needKicking) - kickProcessing(); + // make sure processing process is not suspended + m_photosProcessingProcess->resume(); } diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 5b3f41cec5..cc45bbd4ea 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "models/aphoto_data_model.hpp" #include "people_editor.hpp" @@ -53,14 +54,15 @@ class BatchFaceDetector: public QAbstractListModel }; std::deque m_ids; + std::mutex m_idsMtx; std::vector m_faces; std::unique_ptr m_logger; APhotoDataModel* m_photosModel = nullptr; ICoreFactoryAccessor* m_core = nullptr; Database::IDatabase* m_db = nullptr; + ITaskExecutor::IProcessControl* m_photosProcessingProcess = nullptr; - void kickProcessing(); - void processPhotos(); + ITaskExecutor::ProcessCoroutine processPhotos(ITaskExecutor::IProcessSupervisor *); void appendFaces(std::vector &&); void newPhotos(const QModelIndex &, int, int); }; From ee7271d2173483df6ea23ec05c2d6775367742c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Mar 2023 09:37:06 +0100 Subject: [PATCH 059/151] Fix typo --- src/face_recognition/dlib_wrapper/dlib_face_recognition_api.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.hpp b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.hpp index 774b360153..58a97b4044 100644 --- a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.hpp +++ b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.hpp @@ -75,7 +75,7 @@ namespace dlib_api * @brief check if we have proper system setup to perform face recognition * @return true if face recognition will work. False if it would crash app. * - * if dlib was compiled with CUDA support yey no cuda is available, then + * if dlib was compiled with CUDA support yet no cuda is available, then * we cannot work - dlib will crash/throw on CUDA usage */ DLIB_WRAPPER_EXPORT bool check_system_prerequisites(); From 6f2eb5b65ea622094ebb6324a72079be83c7b774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Mar 2023 16:13:14 +0100 Subject: [PATCH 060/151] Fix typo --- src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp index 12f79a97fb..74fc644ab9 100644 --- a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp +++ b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp @@ -349,7 +349,7 @@ namespace dlib_api FaceEncodings FaceEncoder::face_encodings(const QImage& qimage, int num_jitters, EncodingsModel model) { - // here we assume, that given image is a face extraceted from image with help of face_locations() + // here we assume, that given image is a face extracted from image with help of face_locations() const QSize size = qimage.size(); m_data->logger->debug( From d5746b782d03620a45a6c6a09efb6ae2e42de7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Mar 2023 16:14:02 +0100 Subject: [PATCH 061/151] Process only one photo at a time Analyzing many faces at once may consume too much GPU memory --- src/core/itask_executor.hpp | 1 + src/gui/desktop/utils/batch_face_detector.cpp | 45 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 051dd7c254..ec86a8c902 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -54,6 +54,7 @@ struct CORE_EXPORT ITaskExecutor struct IProcessSupervisor { virtual bool keepWorking() = 0; + virtual void resume() = 0; }; struct ProcessCoroutine diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 6930e07f64..a0cd6cb79e 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -105,37 +105,34 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: { while(supervisor->keepWorking()) { - std::unique_lock _(m_idsMtx); + std::unique_lock lk(m_idsMtx); - // sleep if there is nothing to do - if (m_ids.empty()) + if (m_ids.empty() == false) { - _.unlock(); - co_yield ITaskExecutor::ProcessStateRequest::Suspend; - continue; - } - - FaceEditor fe(*m_db, *m_core, m_logger); + FaceEditor fe(*m_db, *m_core, m_logger); - const auto id = m_ids.front(); - m_ids.pop_front(); - _.unlock(); + const auto id = m_ids.front(); + m_ids.pop_front(); - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this]() mutable - { - auto faces = fe.getFacesFor(id); - std::vector facesDetails; - - for (auto& face: faces) + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this, supervisor]() mutable { - const auto faceImg = face->image()->copy(face->rect()); - facesDetails.emplace_back(std::move(face), faceImg); - } + auto faces = fe.getFacesFor(id); + std::vector facesDetails; + + for (auto& face: faces) + { + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } + + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); + supervisor->resume(); + }); + } - invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); - }); + lk.unlock(); - co_yield ITaskExecutor::ProcessStateRequest::Run; + co_yield ITaskExecutor::ProcessStateRequest::Suspend; } } From 9f6f2072d80cda717f54a9ad3df43e8b44c5c179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 20 Mar 2023 20:08:18 +0100 Subject: [PATCH 062/151] hide implementation --- src/core/implementation/task_executor.cpp | 26 +++++++++++++++++++++++ src/core/task_executor.hpp | 26 ++++------------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index ba025ae2a3..431377884a 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -30,6 +30,11 @@ #include "thread_utils.hpp" +void TaskExecutor::ProcessInfo::setCoroutine(const ProcessCoroutine& h) +{ + m_co_h = h; +} + void TaskExecutor::ProcessInfo::terminate() { m_work = false; @@ -56,6 +61,27 @@ bool TaskExecutor::ProcessInfo::keepWorking() } +void TaskExecutor::ProcessInfo::setState(ProcessState s) +{ + m_state = s; +} + + +ITaskExecutor::ProcessStateRequest TaskExecutor::ProcessInfo::run() const +{ + m_co_h(); + + return stateRequest(); +} + + +ITaskExecutor::ProcessStateRequest TaskExecutor::ProcessInfo::stateRequest() const +{ + const auto &promise = m_co_h.promise(); + return promise.stateRequest; +} + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index c19f0d3dd6..ac96b8c695 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -60,33 +60,15 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor m_co_h.destroy(); } - void setCoroutine(const ProcessCoroutine& h) - { - m_co_h = h; - } - + void setCoroutine(const ProcessCoroutine& h); void terminate() override; void resume() override; ProcessState state() override; bool keepWorking() override; - void setState(ProcessState s) - { - m_state = s; - } - - ProcessStateRequest run() const - { - m_co_h(); - - return stateRequest(); - } - - ProcessStateRequest stateRequest() const - { - const auto &promise = m_co_h.promise(); - return promise.stateRequest; - } + void setState(ProcessState s); + ProcessStateRequest run() const; + ProcessStateRequest stateRequest() const; private: ProcessState m_state = ProcessState::Suspended; From dbed5a34402fb0647ce0ec24a95855aa7cc223a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 20 Mar 2023 21:04:09 +0100 Subject: [PATCH 063/151] Simplify and lock Process durin state change --- src/core/implementation/task_executor.cpp | 44 +++++++++---------- src/core/itask_executor.hpp | 14 ++---- src/core/task_executor.hpp | 5 ++- src/gui/desktop/utils/batch_face_detector.cpp | 2 +- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 431377884a..8899a52fd1 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -17,16 +17,15 @@ * */ -#include "task_executor.hpp" #include #include #include #include -#include #include +#include "task_executor.hpp" #include "thread_utils.hpp" @@ -63,22 +62,22 @@ bool TaskExecutor::ProcessInfo::keepWorking() void TaskExecutor::ProcessInfo::setState(ProcessState s) { + std::lock_guard _(m_stateMtx); m_state = s; } -ITaskExecutor::ProcessStateRequest TaskExecutor::ProcessInfo::run() const +void TaskExecutor::ProcessInfo::run() { - m_co_h(); + // lock state and raturn lock to caller, so state is locked until re + std::lock_guard lk(m_stateMtx); - return stateRequest(); -} + m_co_h(); + m_state = m_co_h.promise().nextState; -ITaskExecutor::ProcessStateRequest TaskExecutor::ProcessInfo::stateRequest() const -{ - const auto &promise = m_co_h.promise(); - return promise.stateRequest; + if (m_state == ProcessState::Finished) + m_co_h.destroy(); } @@ -259,7 +258,7 @@ void TaskExecutor::runProcesses() { bool has_running = false; - std::set toTerminate; + std::set toRemove; for(auto& process: m_processes) { @@ -273,23 +272,24 @@ void TaskExecutor::runProcesses() case ProcessState::Running: { const bool toBeStopped = !process->keepWorking(); - const auto stateRequest = process->run(); + process->run(); + + const auto newState = process->state(); - // if toBeStopped == true then process should exit with Terminate request - assert(stateRequest == ProcessStateRequest::Terminate || toBeStopped == false); + // if toBeStopped == true then process should have Finished + assert(newState == ProcessState::Finished || toBeStopped == false); - switch(stateRequest) + switch(newState) { - case ProcessStateRequest::Run: + case ProcessState::Running: has_running = true; break; - case ProcessStateRequest::Suspend: - process->setState(ProcessState::Suspended); + case ProcessState::Suspended: break; - case ProcessStateRequest::Terminate: - toTerminate.insert(process.get()); + case ProcessState::Finished: + toRemove.insert(process.get()); break; } @@ -298,11 +298,11 @@ void TaskExecutor::runProcesses() } } - if (toTerminate.empty() == false) + if (toRemove.empty() == false) m_processes.erase(std::remove_if( m_processes.begin(), m_processes.end(), - [&toTerminate](const auto& process){ return toTerminate.contains(process.get()); }), + [&toRemove](const auto& process){ return toRemove.contains(process.get()); }), m_processes.end()); if (has_running == false) diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index ec86a8c902..8f773d48cd 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -38,17 +38,11 @@ struct CORE_EXPORT ITaskExecutor virtual void perform() = 0; ///< @brief perform job }; - enum class ProcessStateRequest - { - Suspend, - Run, - Terminate, - }; - enum class ProcessState { Suspended, Running, + Finished, }; struct IProcessSupervisor @@ -64,7 +58,7 @@ struct CORE_EXPORT ITaskExecutor struct promise_type { - ProcessStateRequest stateRequest = ProcessStateRequest::Run; + ProcessState nextState = ProcessState::Running; ProcessCoroutine get_return_object() { @@ -72,9 +66,9 @@ struct CORE_EXPORT ITaskExecutor } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } - void return_void() { stateRequest = ProcessStateRequest::Terminate; } + void return_void() { nextState = ProcessState::Finished; } void unhandled_exception() {} - std::suspend_always yield_value(ProcessStateRequest sr) { stateRequest = sr; return {}; } + std::suspend_always yield_value(ProcessState sr) { nextState = sr; return {}; } }; ProcessCoroutine(handle_type h_): h(h_) {} diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index ac96b8c695..218b6ddb54 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "core_export.h" @@ -67,10 +68,10 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor bool keepWorking() override; void setState(ProcessState s); - ProcessStateRequest run() const; - ProcessStateRequest stateRequest() const; + void run(); private: + std::mutex m_stateMtx; ProcessState m_state = ProcessState::Suspended; ProcessCoroutine::handle_type m_co_h; TaskExecutor& m_executor; diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index a0cd6cb79e..b1b71b4512 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -132,7 +132,7 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: lk.unlock(); - co_yield ITaskExecutor::ProcessStateRequest::Suspend; + co_yield ITaskExecutor::ProcessState::Suspended; } } From 220f4f210eb3d8d4360455a94d9497c3c5d87f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 21 Mar 2023 21:04:12 +0100 Subject: [PATCH 064/151] Make sure client won't end up with invalid pointer When process is removed, client should have a valid pointer to IProcessControl --- src/core/implementation/task_executor.cpp | 14 ++++++++----- .../implementation/task_executor_utils.cpp | 2 +- src/core/itask_executor.hpp | 20 +++++++++---------- src/core/task_executor.hpp | 4 ++-- src/core/task_executor_utils.hpp | 2 +- src/gui/desktop/utils/batch_face_detector.hpp | 2 +- src/unit_tests_utils/fake_task_executor.hpp | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 8899a52fd1..835c5f03cd 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -63,6 +63,7 @@ bool TaskExecutor::ProcessInfo::keepWorking() void TaskExecutor::ProcessInfo::setState(ProcessState s) { std::lock_guard _(m_stateMtx); + assert(m_state != ProcessState::Finished || s == ProcessState::Finished); // no sense of changing state of finished process m_state = s; } @@ -118,17 +119,16 @@ void TaskExecutor::add(std::unique_ptr&& task) } -ITaskExecutor::IProcessControl* TaskExecutor::add(Process&& task) +std::shared_ptr TaskExecutor::add(Process&& task) { - auto process = std::make_unique(*this, ProcessState::Running); - ITaskExecutor::IProcessControl* control = process.get(); + auto process = std::make_shared(*this, ProcessState::Running); ITaskExecutor::IProcessSupervisor* supervisor = process.get(); process->setCoroutine(task(supervisor)); - m_processes.push_back(std::move(process)); + m_processes.push_back(process); wakeUpScheduler(); - return control; + return process; } @@ -295,6 +295,10 @@ void TaskExecutor::runProcesses() break; } + + case ProcessState::Finished: + assert(!"Should not happend"); + break; } } diff --git a/src/core/implementation/task_executor_utils.cpp b/src/core/implementation/task_executor_utils.cpp index abb19b98ea..7f9244715b 100644 --- a/src/core/implementation/task_executor_utils.cpp +++ b/src/core/implementation/task_executor_utils.cpp @@ -86,7 +86,7 @@ void TasksQueue::add(std::unique_ptr&& task) } -ITaskExecutor::IProcessControl* TasksQueue::add(Process &&) +std::shared_ptr TasksQueue::add(Process &&) { /// TODO: implement assert(!"Not implemenbted yet"); diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 8f773d48cd..8dfd93bf95 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -45,12 +45,6 @@ struct CORE_EXPORT ITaskExecutor Finished, }; - struct IProcessSupervisor - { - virtual bool keepWorking() = 0; - virtual void resume() = 0; - }; - struct ProcessCoroutine { struct promise_type; @@ -78,7 +72,11 @@ struct CORE_EXPORT ITaskExecutor operator std::coroutine_handle<>() const { return h; } }; - using Process = std::function; + struct IProcessSupervisor + { + virtual bool keepWorking() = 0; + virtual void resume() = 0; + }; struct IProcessControl { @@ -87,12 +85,14 @@ struct CORE_EXPORT ITaskExecutor virtual ProcessState state() = 0; }; + using Process = std::function; + virtual ~ITaskExecutor() = default; - virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) - virtual IProcessControl* add(Process &&) = 0; // add task to be run in a ring with other Processes + virtual void add(std::unique_ptr &&) = 0; // add short but heavy task (calculations) + virtual std::shared_ptr add(Process &&) = 0; // add task to be run in a ring with other Processes - virtual int heavyWorkers() const = 0; // return number of heavy task workers + virtual int heavyWorkers() const = 0; // return number of heavy task workers }; #endif diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 218b6ddb54..66e642f67e 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -41,7 +41,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor TaskExecutor& operator=(const TaskExecutor &) = delete; void add(std::unique_ptr &&) override; - IProcessControl* add(Process &&) override; + std::shared_ptr add(Process &&) override; int heavyWorkers() const override; @@ -82,7 +82,7 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor typedef ol::TS_Queue> QueueT; QueueT m_tasks; - std::vector> m_processes; + std::vector> m_processes; std::thread m_taskEater; std::thread m_processRunner; std::mutex m_processesIdleMutex; diff --git a/src/core/task_executor_utils.hpp b/src/core/task_executor_utils.hpp index 86b90cd548..a52c848abb 100644 --- a/src/core/task_executor_utils.hpp +++ b/src/core/task_executor_utils.hpp @@ -300,7 +300,7 @@ class CORE_EXPORT TasksQueue: public ITaskExecutor, public Queue &&) override; - IProcessControl* add(Process &&) override; + std::shared_ptr add(Process &&) override; int heavyWorkers() const override; private: diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index cc45bbd4ea..3f09de7617 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -60,7 +60,7 @@ class BatchFaceDetector: public QAbstractListModel APhotoDataModel* m_photosModel = nullptr; ICoreFactoryAccessor* m_core = nullptr; Database::IDatabase* m_db = nullptr; - ITaskExecutor::IProcessControl* m_photosProcessingProcess = nullptr; + std::shared_ptr m_photosProcessingProcess; ITaskExecutor::ProcessCoroutine processPhotos(ITaskExecutor::IProcessSupervisor *); void appendFaces(std::vector &&); diff --git a/src/unit_tests_utils/fake_task_executor.hpp b/src/unit_tests_utils/fake_task_executor.hpp index a1e0a9409b..a037b9b446 100644 --- a/src/unit_tests_utils/fake_task_executor.hpp +++ b/src/unit_tests_utils/fake_task_executor.hpp @@ -12,7 +12,7 @@ class FakeTaskExecutor: public ITaskExecutor task->perform(); } - IProcessControl* add(Process &&) override + std::shared_ptr add(Process &&) override { /// TODO: implement assert(!"Not implemented yet"); From c865c2dc216ce434196c3ce2f29807b782bdcf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 23 Mar 2023 21:36:27 +0100 Subject: [PATCH 065/151] Introduce base for Database::Client --- src/database/CMakeLists.txt | 1 + src/database/database_client.hpp | 19 +++++++++++++++++++ .../implementation/database_client.cpp | 9 +++++++++ 3 files changed, 29 insertions(+) create mode 100644 src/database/database_client.hpp create mode 100644 src/database/implementation/database_client.cpp diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt index 64674ab931..69711fb828 100644 --- a/src/database/CMakeLists.txt +++ b/src/database/CMakeLists.txt @@ -45,6 +45,7 @@ set(DATABASE_SOURCES implementation/async_database.cpp implementation/async_database.hpp implementation/database_builder.cpp + implementation/database_client.cpp implementation/database_queue.cpp implementation/filter.cpp implementation/notifications_accumulator.cpp diff --git a/src/database/database_client.hpp b/src/database/database_client.hpp new file mode 100644 index 0000000000..c6d26fb61a --- /dev/null +++ b/src/database/database_client.hpp @@ -0,0 +1,19 @@ + +#ifndef DATABASE_CLIENT_HPP_INCLUDED +#define DATABASE_CLIENT_HPP_INCLUDED + +#include "database_export.h" + +#include "idatabase.hpp" + + +namespace Database +{ + class DATABASE_EXPORT Client + { + public: + explicit Client(IDatabase *); + }; +} + +#endif diff --git a/src/database/implementation/database_client.cpp b/src/database/implementation/database_client.cpp new file mode 100644 index 0000000000..81108e3971 --- /dev/null +++ b/src/database/implementation/database_client.cpp @@ -0,0 +1,9 @@ + +#include "database_client.hpp" + +namespace Database +{ + Client::Client(IDatabase* db) + { + } +} From 518263388a99b1b9db5685ba0a9e9aaf8f602839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 25 Mar 2023 16:22:50 +0100 Subject: [PATCH 066/151] Make database wait for all clients to disconnect --- src/database/CMakeLists.txt | 1 - src/database/database_client.hpp | 19 ------------------- .../implementation/database_client.cpp | 9 --------- src/gui/desktop/utils/batch_face_detector.cpp | 3 ++- tr/photo_broom_en.ts | 8 ++++++++ tr/photo_broom_pl.ts | 8 ++++++++ 6 files changed, 18 insertions(+), 30 deletions(-) delete mode 100644 src/database/database_client.hpp delete mode 100644 src/database/implementation/database_client.cpp diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt index 69711fb828..64674ab931 100644 --- a/src/database/CMakeLists.txt +++ b/src/database/CMakeLists.txt @@ -45,7 +45,6 @@ set(DATABASE_SOURCES implementation/async_database.cpp implementation/async_database.hpp implementation/database_builder.cpp - implementation/database_client.cpp implementation/database_queue.cpp implementation/filter.cpp implementation/notifications_accumulator.cpp diff --git a/src/database/database_client.hpp b/src/database/database_client.hpp deleted file mode 100644 index c6d26fb61a..0000000000 --- a/src/database/database_client.hpp +++ /dev/null @@ -1,19 +0,0 @@ - -#ifndef DATABASE_CLIENT_HPP_INCLUDED -#define DATABASE_CLIENT_HPP_INCLUDED - -#include "database_export.h" - -#include "idatabase.hpp" - - -namespace Database -{ - class DATABASE_EXPORT Client - { - public: - explicit Client(IDatabase *); - }; -} - -#endif diff --git a/src/database/implementation/database_client.cpp b/src/database/implementation/database_client.cpp deleted file mode 100644 index 81108e3971..0000000000 --- a/src/database/implementation/database_client.cpp +++ /dev/null @@ -1,9 +0,0 @@ - -#include "database_client.hpp" - -namespace Database -{ - Client::Client(IDatabase* db) - { - } -} diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index b1b71b4512..caf63094b5 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -109,12 +109,13 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: if (m_ids.empty() == false) { + auto observer = m_db->attach(tr("Batch face recognition")); // prevent db from closing while we use it FaceEditor fe(*m_db, *m_core, m_logger); const auto id = m_ids.front(); m_ids.pop_front(); - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this, supervisor]() mutable + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), o = std::move(observer), id, this, supervisor]() mutable { auto faces = fe.getFacesFor(id); std::vector facesDetails; diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 086b518715..2e5b09e129 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -70,6 +70,14 @@ + + BatchFaceDetector + + + Batch face recognition + + + CollectionScanner diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 3e72627d6d..5a348983de 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -71,6 +71,14 @@ + + BatchFaceDetector + + + Batch face recognition + + + CollectionScanner From 1957426769c04ec10674c9b466c82e3aeda4700c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 26 Mar 2023 17:12:18 +0200 Subject: [PATCH 067/151] Notify Database clients aboud database close --- src/gui/desktop/utils/batch_face_detector.cpp | 20 +++++++++++++------ src/gui/desktop/utils/batch_face_detector.hpp | 2 +- tr/photo_broom_en.ts | 4 ++-- tr/photo_broom_pl.ts | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index caf63094b5..91e9b85316 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -11,7 +11,8 @@ BatchFaceDetector::~BatchFaceDetector() { - m_photosProcessingProcess->terminate(); + // db client should be destroyed by now + assert(m_dbClient.get() == nullptr); } @@ -57,7 +58,12 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) void BatchFaceDetector::setDB(Database::IDatabase* db) { - m_db = db; + m_dbClient = db->attach(tr("Batch face detector")); + if (m_dbClient) + m_dbClient->onClose([this]() + { + m_photosProcessingProcess->terminate(); + }); } @@ -75,7 +81,7 @@ ICoreFactoryAccessor* BatchFaceDetector::core() const Database::IDatabase* BatchFaceDetector::db() const { - return m_db; + return m_dbClient? &m_dbClient->db(): nullptr; } @@ -109,13 +115,12 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: if (m_ids.empty() == false) { - auto observer = m_db->attach(tr("Batch face recognition")); // prevent db from closing while we use it - FaceEditor fe(*m_db, *m_core, m_logger); + FaceEditor fe(m_dbClient->db(), *m_core, m_logger); const auto id = m_ids.front(); m_ids.pop_front(); - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), o = std::move(observer), id, this, supervisor]() mutable + runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this, supervisor]() mutable { auto faces = fe.getFacesFor(id); std::vector facesDetails; @@ -135,6 +140,9 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: co_yield ITaskExecutor::ProcessState::Suspended; } + + // face scanning is done, db won't be needed anymore, release it + m_dbClient.reset(); } diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 3f09de7617..7e61a4e278 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -57,9 +57,9 @@ class BatchFaceDetector: public QAbstractListModel std::mutex m_idsMtx; std::vector m_faces; std::unique_ptr m_logger; + std::unique_ptr m_dbClient; APhotoDataModel* m_photosModel = nullptr; ICoreFactoryAccessor* m_core = nullptr; - Database::IDatabase* m_db = nullptr; std::shared_ptr m_photosProcessingProcess; ITaskExecutor::ProcessCoroutine processPhotos(ITaskExecutor::IProcessSupervisor *); diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 2e5b09e129..1add334717 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,8 +73,8 @@ BatchFaceDetector - - Batch face recognition + + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 5a348983de..145496bf66 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,8 +74,8 @@ BatchFaceDetector - - Batch face recognition + + Batch face detector From 1bbbd88eb3b86aa4f74c8256884a7ac0149c9a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 26 Mar 2023 20:32:13 +0200 Subject: [PATCH 068/151] Destroy coroutine in destructor only --- src/core/implementation/task_executor.cpp | 18 ++++++++++++++---- src/core/task_executor.hpp | 11 ++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/core/implementation/task_executor.cpp b/src/core/implementation/task_executor.cpp index 835c5f03cd..7306a44f0c 100644 --- a/src/core/implementation/task_executor.cpp +++ b/src/core/implementation/task_executor.cpp @@ -29,6 +29,19 @@ #include "thread_utils.hpp" +TaskExecutor::ProcessInfo::ProcessInfo(TaskExecutor& executor, ProcessState s) + : m_state(s) + , m_executor(executor) +{} + + +TaskExecutor::ProcessInfo::~ProcessInfo() +{ + if (m_co_h.done() == false) + m_co_h.destroy(); +} + + void TaskExecutor::ProcessInfo::setCoroutine(const ProcessCoroutine& h) { m_co_h = h; @@ -76,9 +89,6 @@ void TaskExecutor::ProcessInfo::run() m_co_h(); m_state = m_co_h.promise().nextState; - - if (m_state == ProcessState::Finished) - m_co_h.destroy(); } @@ -276,7 +286,7 @@ void TaskExecutor::runProcesses() const auto newState = process->state(); - // if toBeStopped == true then process should have Finished + // if toBeStopped == true then process should have finished assert(newState == ProcessState::Finished || toBeStopped == false); switch(newState) diff --git a/src/core/task_executor.hpp b/src/core/task_executor.hpp index 66e642f67e..15bf82414e 100644 --- a/src/core/task_executor.hpp +++ b/src/core/task_executor.hpp @@ -51,15 +51,8 @@ struct CORE_EXPORT TaskExecutor: public ITaskExecutor class ProcessInfo: public IProcessControl, public IProcessSupervisor { public: - ProcessInfo(TaskExecutor& executor, ProcessState s) - : m_state(s) - , m_executor(executor) - {} - - ~ProcessInfo() - { - m_co_h.destroy(); - } + ProcessInfo(TaskExecutor &, ProcessState); + ~ProcessInfo(); void setCoroutine(const ProcessCoroutine& h); void terminate() override; From 8ea6e96e5c39eb363bb66ee5080c9d4f62fcfeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 26 Mar 2023 20:49:09 +0200 Subject: [PATCH 069/151] Run batch processing on new db --- src/gui/desktop/utils/batch_face_detector.cpp | 9 ++++++--- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 91e9b85316..cc9e38ac88 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -50,9 +50,6 @@ void BatchFaceDetector::setCore(ICoreFactoryAccessor* core) assert(m_core == nullptr); m_core = core; m_logger = m_core->getLoggerFactory().get("BatchFaceDetector"); - - auto process = std::bind(&BatchFaceDetector::processPhotos, this, std::placeholders::_1); - m_photosProcessingProcess = m_core->getTaskExecutor().add(process); } @@ -60,10 +57,16 @@ void BatchFaceDetector::setDB(Database::IDatabase* db) { m_dbClient = db->attach(tr("Batch face detector")); if (m_dbClient) + { m_dbClient->onClose([this]() { m_photosProcessingProcess->terminate(); }); + + // begin photo analysis + auto process = std::bind(&BatchFaceDetector::processPhotos, this, std::placeholders::_1); + m_photosProcessingProcess = m_core->getTaskExecutor().add(process); + } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 1add334717..92024b80f9 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 145496bf66..363c00959c 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 9a4581517f3a968960f55bab3cf71e6aaf1c359d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 26 Mar 2023 21:12:27 +0200 Subject: [PATCH 070/151] Be explicit --- src/core/itask_executor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/itask_executor.hpp b/src/core/itask_executor.hpp index 8dfd93bf95..b6683f6a1e 100644 --- a/src/core/itask_executor.hpp +++ b/src/core/itask_executor.hpp @@ -65,7 +65,7 @@ struct CORE_EXPORT ITaskExecutor std::suspend_always yield_value(ProcessState sr) { nextState = sr; return {}; } }; - ProcessCoroutine(handle_type h_): h(h_) {} + explicit ProcessCoroutine(handle_type h_): h(h_) {} handle_type h = nullptr; operator std::coroutine_handle() const { return h; } From becfe6bcf8cb93deb4423c6ac520ac8bd87a2d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 27 Mar 2023 21:16:23 +0200 Subject: [PATCH 071/151] Be consistent --- src/database/implementation/async_database.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/implementation/async_database.hpp b/src/database/implementation/async_database.hpp index ce82ebd2bb..f81eb23e7f 100644 --- a/src/database/implementation/async_database.hpp +++ b/src/database/implementation/async_database.hpp @@ -72,7 +72,7 @@ namespace Database AsyncDatabase& m_db; }; - friend struct Client; + friend class Client; std::set m_clients; std::mutex m_clientsMutex; From 959320a02e9582e5bd73192a3060710984ad3538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 27 Mar 2023 21:59:42 +0200 Subject: [PATCH 072/151] Set minimal heights for elements --- src/gui/desktop/quick_items/Views/BatchFaceDetection.qml | 4 ++++ tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index adf747b775..50237852ed 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -32,6 +32,8 @@ Item { title: qsTr("Discovered faces") clip: true + SplitView.minimumHeight: 150 + GridView { anchors.fill: parent model: detector @@ -61,6 +63,8 @@ Item { title: qsTr("Photos to be analyzed") clip: true + SplitView.minimumHeight: 150 + Internals.PhotosGridView { anchors.fill: parent model: data_model diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 92024b80f9..00172e0184 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 363c00959c..d10ea8d0ac 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From 6e419b0c622349245e326b0a2c1278f8f66a2126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 27 Mar 2023 21:59:50 +0200 Subject: [PATCH 073/151] Drop comment --- src/database/implementation/async_database.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/implementation/async_database.hpp b/src/database/implementation/async_database.hpp index f81eb23e7f..6a3ffb44c9 100644 --- a/src/database/implementation/async_database.hpp +++ b/src/database/implementation/async_database.hpp @@ -93,4 +93,4 @@ namespace Database } -#endif // DATABASETHREAD_HPP +#endif From e7cdf8f2a51d47ab2dbba970a4b86768b079c073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 28 Mar 2023 21:21:39 +0200 Subject: [PATCH 074/151] Show highlight item with base for 2 action buttons --- .../quick_items/Views/BatchFaceDetection.qml | 62 ++++++++++++++++--- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 50237852ed..7cc293a68d 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -35,25 +35,69 @@ Item { SplitView.minimumHeight: 150 GridView { + id: gridView + anchors.fill: parent model: detector cellWidth: 170 - cellHeight: 170 + cellHeight: 200 - delegate: Column { + delegate: Item { required property var decoration required property var display + required property var index + + width: 170 + height: 200 + + Column { + anchors.fill: parent + + Picture { + anchors.horizontalCenter: parent.horizontalCenter + height: 150 + width: 150 + source: decoration + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: display + } + } - Picture { - height: 150 - width: 150 - source: decoration + MouseArea { + anchors.fill: parent + onClicked: { + gridView.currentIndex = index + } } + } + + highlight: Item { + height: 150 + width: 150 + z: 2 + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + + width: 20 + height: 20 + radius: 10 + color: "green" + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left - Text { - anchors.horizontalCenter: parent.horizontalCenter - text: display + width: 20 + height: 20 + radius: 10 + color: "red" } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 00172e0184..8c502a59dc 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index d10ea8d0ac..7b7cc00f77 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From 16ea2b056a0e293cdfd0e5e231ca69f9ef2d6d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 29 Mar 2023 22:25:53 +0200 Subject: [PATCH 075/151] Introduce LineEdit --- src/gui/desktop/QmlItems/LineEdit.qml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/gui/desktop/QmlItems/LineEdit.qml diff --git a/src/gui/desktop/QmlItems/LineEdit.qml b/src/gui/desktop/QmlItems/LineEdit.qml new file mode 100644 index 0000000000..5c32e46bdc --- /dev/null +++ b/src/gui/desktop/QmlItems/LineEdit.qml @@ -0,0 +1,22 @@ + +import QtQuick 2.15 + +Rectangle { + + property alias text: input.text + + implicitHeight: input.implicitHeight + 8 + implicitWidth: input.implicitWidth + + border.color: "gray" + border.width: 1 + radius: 2 + + TextInput { + id: input + anchors.fill: parent + anchors.margins: 4 + + selectByMouse: true + } +} From 0d799583cf00e55ad179976d82260250fad2f305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 29 Mar 2023 22:26:11 +0200 Subject: [PATCH 076/151] Reorganize buttons display --- .../quick_items/Views/BatchFaceDetection.qml | 81 ++++++++++--------- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 7cc293a68d..e6c22c7409 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -35,8 +35,6 @@ Item { SplitView.minimumHeight: 150 GridView { - id: gridView - anchors.fill: parent model: detector @@ -54,51 +52,58 @@ Item { Column { anchors.fill: parent - Picture { + Item { anchors.horizontalCenter: parent.horizontalCenter height: 150 width: 150 - source: decoration + + Picture { + anchors.fill: parent + source: decoration + } + + Item { + id: actionButtons + anchors.fill: parent + + opacity: 0 + Behavior on opacity { PropertyAnimation{} } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + + width: 20 + height: 20 + radius: 10 + color: "green" + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + + width: 20 + height: 20 + radius: 10 + color: "red" + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: actionButtons.opacity = 1 + onExited: actionButtons.opacity = 0 + } } - Text { + LineEdit { anchors.horizontalCenter: parent.horizontalCenter text: display + width: parent.width - 40 } } - - MouseArea { - anchors.fill: parent - onClicked: { - gridView.currentIndex = index - } - } - } - - highlight: Item { - height: 150 - width: 150 - z: 2 - - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - - width: 20 - height: 20 - radius: 10 - color: "green" - } - - Rectangle { - anchors.top: parent.top - anchors.left: parent.left - - width: 20 - height: 20 - radius: 10 - color: "red" - } } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 8c502a59dc..3ad4b4d7ad 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 7b7cc00f77..6297befbdb 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From 27e21a10b9ff1627f2ce20c94535ac5546e777b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 31 Mar 2023 21:30:32 +0200 Subject: [PATCH 077/151] Add OK and Trash images --- src/gui/images/images.qrc | 2 ++ src/gui/images/ok.svg | 64 ++++++++++++++++++++++++++++++++++++ src/gui/images/trash.svg | 69 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/gui/images/ok.svg create mode 100644 src/gui/images/trash.svg diff --git a/src/gui/images/images.qrc b/src/gui/images/images.qrc index 6596a1b398..a9beeacb42 100644 --- a/src/gui/images/images.qrc +++ b/src/gui/images/images.qrc @@ -9,8 +9,10 @@ half_star.svg missing.svg new.svg + ok.svg paper.svg star.svg + trash.svg video.svg diff --git a/src/gui/images/ok.svg b/src/gui/images/ok.svg new file mode 100644 index 0000000000..6c427ff57e --- /dev/null +++ b/src/gui/images/ok.svg @@ -0,0 +1,64 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/gui/images/trash.svg b/src/gui/images/trash.svg new file mode 100644 index 0000000000..0266b74032 --- /dev/null +++ b/src/gui/images/trash.svg @@ -0,0 +1,69 @@ + +image/svg+xml + + + + + From 40742d1b06ec5209412d89998dc823471d50aba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 6 Apr 2023 15:41:00 +0200 Subject: [PATCH 078/151] Move buttons into MouseArea to make buttons work as expected --- .../quick_items/Views/BatchFaceDetection.qml | 48 +++++++++---------- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index e6c22c7409..254f0586cc 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -62,39 +62,35 @@ Item { source: decoration } - Item { - id: actionButtons + MouseArea { + id: mouseArea anchors.fill: parent + hoverEnabled: true - opacity: 0 - Behavior on opacity { PropertyAnimation{} } + Item { + anchors.fill: parent - Rectangle { - anchors.top: parent.top - anchors.right: parent.right + opacity: mouseArea.containsMouse? 1.0: 0.0 + Behavior on opacity { PropertyAnimation{} } - width: 20 - height: 20 - radius: 10 - color: "green" - } + ImageButton { + anchors.top: parent.top + anchors.right: parent.right - Rectangle { - anchors.top: parent.top - anchors.left: parent.left + width: 20 + height: 20 + source: "qrc:/gui/ok.svg" + } - width: 20 - height: 20 - radius: 10 - color: "red" - } - } + ImageButton { + anchors.top: parent.top + anchors.left: parent.left - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: actionButtons.opacity = 1 - onExited: actionButtons.opacity = 0 + width: 20 + height: 20 + source: "qrc:/gui/trash.svg" + } + } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 3ad4b4d7ad..83dd6c074c 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 6297befbdb..c141979e35 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From d99e238d22f5736c07203fe5408e92d703d0eaeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 6 Apr 2023 19:23:01 +0200 Subject: [PATCH 079/151] Add ImageButton Scale style --- src/gui/desktop/QmlItems/ImageButton.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/desktop/QmlItems/ImageButton.qml b/src/gui/desktop/QmlItems/ImageButton.qml index ec781715ad..231cca45b4 100644 --- a/src/gui/desktop/QmlItems/ImageButton.qml +++ b/src/gui/desktop/QmlItems/ImageButton.qml @@ -4,15 +4,24 @@ import QtQuick 2.15 Item { id: root + enum Style { + BackLight, + Scale + } + required property string source + property int style: Style.BackLight + signal clicked() + Rectangle { anchors.fill: parent id: background color: "black" + visible: root.style === root.BackLight opacity: 0.0 + mouseArea.containsMouse * 0.2 + mouseArea.pressed * 0.2 } @@ -20,6 +29,9 @@ Item { anchors.fill: parent source: root.source + scale: root.style === root.Scale? (mouseArea.containsMouse? 1.0: 0.7): 1.0 + + Behavior on scale { PropertyAnimation{ duration: 100 } } } MouseArea { From 24e5ea25bd03db9fce9278f26372e2afa07df2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 6 Apr 2023 19:23:12 +0200 Subject: [PATCH 080/151] Use scale style --- src/gui/desktop/quick_items/Views/BatchFaceDetection.qml | 4 ++++ tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 254f0586cc..32f57df536 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -80,6 +80,8 @@ Item { width: 20 height: 20 source: "qrc:/gui/ok.svg" + + style: ImageButton.Scale } ImageButton { @@ -89,6 +91,8 @@ Item { width: 20 height: 20 source: "qrc:/gui/trash.svg" + + style: ImageButton.Scale } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 83dd6c074c..3ad4b4d7ad 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index c141979e35..6297befbdb 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From f23fb8a202645b0459e10055b81925af321b3e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 6 Apr 2023 19:29:00 +0200 Subject: [PATCH 081/151] Trigger action --- .../desktop/quick_items/Views/BatchFaceDetection.qml | 6 ++++++ src/gui/desktop/utils/batch_face_detector.cpp | 12 ++++++++++++ src/gui/desktop/utils/batch_face_detector.hpp | 3 +++ tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 32f57df536..9371054503 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -42,6 +42,8 @@ Item { cellHeight: 200 delegate: Item { + id: delegateItem + required property var decoration required property var display required property var index @@ -82,6 +84,8 @@ Item { source: "qrc:/gui/ok.svg" style: ImageButton.Scale + + onClicked: detector.accept(delegateItem.index) } ImageButton { @@ -93,6 +97,8 @@ Item { source: "qrc:/gui/trash.svg" style: ImageButton.Scale + + onClicked: detector.drop(delegateItem.index) } } } diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index cc9e38ac88..d99a1cbf92 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -110,6 +110,18 @@ QVariant BatchFaceDetector::data(const QModelIndex& idx, int role) const } +void BatchFaceDetector::accept(int idx) +{ + +} + + +void BatchFaceDetector::drop(int idx) +{ + +} + + ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor::IProcessSupervisor* supervisor) { while(supervisor->keepWorking()) diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 7e61a4e278..ddba1b073e 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -35,6 +35,9 @@ class BatchFaceDetector: public QAbstractListModel int rowCount(const QModelIndex &) const override; QVariant data(const QModelIndex &, int) const override; + Q_INVOKABLE void accept(int); + Q_INVOKABLE void drop(int); + private: struct Face { diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 3ad4b4d7ad..6e3fb9f02d 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 6297befbdb..12aa8dbe75 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From e56a1c75b74b880f862b0c05f32e679e8e49742e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 6 Aug 2023 20:10:28 +0200 Subject: [PATCH 082/151] Fix warnings --- .../unit_tests_for_backends/photo_operator_tests.cpp | 4 ++-- src/gui/desktop/utils/collection_scanner.cpp | 4 ++-- src/gui/desktop/utils/groups_manager.cpp | 9 +++++---- src/gui/desktop/utils/thumbnail_manager.cpp | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/database/unit_tests_for_backends/photo_operator_tests.cpp b/src/database/unit_tests_for_backends/photo_operator_tests.cpp index 4744e42913..f2fa92c283 100644 --- a/src/database/unit_tests_for_backends/photo_operator_tests.cpp +++ b/src/database/unit_tests_for_backends/photo_operator_tests.cpp @@ -123,7 +123,7 @@ TYPED_TEST(PhotoOperatorTest, sortingByPHashReversed) std::vector photos; for (const auto& id: ids) - photos.push_back(this->m_backend->getPhotoDelta(id, {Photo::Field::PHash})); + photos.push_back(this->m_backend->template getPhotoDelta(id)); std::vector phashes; std::transform(photos.begin(), photos.end(), std::back_inserter(phashes), [](const Photo::DataDelta& data) { return data.get().value(); }); @@ -180,7 +180,7 @@ TYPED_TEST(PhotoOperatorTest, removal) // Some may ask Photo::DataDelta for it while photo is being deleted. // It is not convenient to protect them all against null result. // Instead db should mark such photos and delete them later (possibly on db close). - const Photo::DataDelta readData = this->m_backend->getPhotoDelta(id, {Photo::Field::Path}); // TODO: for some reason Photo::DataDelta cannot be replaced with auto. gcc 12.1.1 bug? + const Photo::DataDelta readData = this->m_backend->template getPhotoDelta(id); // TODO: for some reason Photo::DataDelta cannot be replaced with auto. gcc 12.1.1 bug? const auto& readDataPath = readData.get(); EXPECT_EQ(readDataPath, path); } diff --git a/src/gui/desktop/utils/collection_scanner.cpp b/src/gui/desktop/utils/collection_scanner.cpp index f7a616e671..476fb6ebca 100644 --- a/src/gui/desktop/utils/collection_scanner.cpp +++ b/src/gui/desktop/utils/collection_scanner.cpp @@ -86,13 +86,13 @@ void CollectionScanner::scan() photoDeltas.reserve(photos.size()); for(const Photo::Id& id: photos) - photoDeltas.push_back(backend.getPhotoDelta(id, {Photo::Field::Path})); + photoDeltas.push_back(backend.getPhotoDelta(id)); std::vector missingPhotoDeltas; missingPhotoDeltas.reserve(missingPhotos.size()); for(const Photo::Id& id: missingPhotos) - missingPhotoDeltas.push_back(backend.getPhotoDelta(id, {Photo::Field::Path})); + missingPhotoDeltas.push_back(backend.getPhotoDelta(id)); db_callback(photoDeltas, missingPhotoDeltas); }); diff --git a/src/gui/desktop/utils/groups_manager.cpp b/src/gui/desktop/utils/groups_manager.cpp index 6cfcd97b14..602205ad80 100644 --- a/src/gui/desktop/utils/groups_manager.cpp +++ b/src/gui/desktop/utils/groups_manager.cpp @@ -89,14 +89,15 @@ void GroupsManager::group(Database::IDatabase& database, QPromise&& promis const Group::Type& type = group.type; // copy details of first member to representative - const Photo::Data firstPhoto = backend.getPhoto(photos[0]); + const auto firstPhoto = backend.getPhotoDelta(photos[0]); - auto it = firstPhoto.flags.find(Photo::FlagsE::StagingArea); - const Photo::FlagValues flags = { {Photo::FlagsE::StagingArea, it == firstPhoto.flags.end()? 0: it->second} }; + const auto firstPhotoFlags = firstPhoto.get(); + auto it = firstPhotoFlags.find(Photo::FlagsE::StagingArea); + const Photo::FlagValues flags = { {Photo::FlagsE::StagingArea, it == firstPhotoFlags.end()? 0: it->second} }; Photo::DataDelta data; data.insert(representativePath); - data.insert(firstPhoto.tags); + data.insert(firstPhoto.get()); data.insert(flags); // store representative photo diff --git a/src/gui/desktop/utils/thumbnail_manager.cpp b/src/gui/desktop/utils/thumbnail_manager.cpp index 84ea2b4e18..6e0116621d 100644 --- a/src/gui/desktop/utils/thumbnail_manager.cpp +++ b/src/gui/desktop/utils/thumbnail_manager.cpp @@ -76,7 +76,7 @@ void ThumbnailManager::fetch(const Photo::Id& id, const QSize& desired_size, con // load path to photo const Photo::DataDelta photoData = evaluate(*m_db, [id](Database::IBackend& backend) { - return backend.getPhotoDelta(id, {Photo::Field::Path}); + return backend.getPhotoDelta(id); }); // generate base thumbnail From 2e277a9c2a86abeba543aa52a154c6505ae5b635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 9 Sep 2023 14:32:16 +0200 Subject: [PATCH 083/151] Format code --- src/database/ibackend.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/ibackend.hpp b/src/database/ibackend.hpp index cb5fde23e5..07c34cd224 100644 --- a/src/database/ibackend.hpp +++ b/src/database/ibackend.hpp @@ -122,8 +122,8 @@ namespace Database const Filter &) = 0; /// get particular photo - [[deprecated("Use getPhotoDelta template ")]] virtual Photo::Data getPhoto(const Photo::Id &) = 0; - [[deprecated("Use getPhotoDelta template ")]] virtual Photo::DataDelta getPhotoDelta(const Photo::Id &, const std::set & = {}) = 0; + [[deprecated("Use getPhotoDelta template ")]] virtual Photo::Data getPhoto(const Photo::Id &) = 0; + [[deprecated("Use getPhotoDelta template ")]] virtual Photo::DataDelta getPhotoDelta(const Photo::Id &, const std::set & = {}) = 0; template Photo::ExplicitDelta getPhotoDelta(const Photo::Id& id) From 6ddb73109a1f812664a56da06e29bf24c6fd2926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 9 Sep 2023 18:56:37 +0200 Subject: [PATCH 084/151] Add new BlobType for batch face fetcher --- src/database/ibackend.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/ibackend.hpp b/src/database/ibackend.hpp index 07c34cd224..c2e210aa41 100644 --- a/src/database/ibackend.hpp +++ b/src/database/ibackend.hpp @@ -93,6 +93,7 @@ namespace Database enum class BlobType { Thumbnail = 0, + BatchFaceFetcher = 1, }; virtual ~IBackend() = default; From ac2bf300057f57877c435426f8da4961ba693908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 9 Sep 2023 18:57:08 +0200 Subject: [PATCH 085/151] Store BatchFaceDetector partial results in db --- src/gui/desktop/utils/batch_face_detector.cpp | 100 ++++++++++++++---- src/gui/desktop/utils/batch_face_detector.hpp | 1 + tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 4 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index d99a1cbf92..9802a909c7 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -1,9 +1,14 @@ +#include +#include +#include + #include #include #include #include #include +#include #include "batch_face_detector.hpp" @@ -126,34 +131,75 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: { while(supervisor->keepWorking()) { - std::unique_lock lk(m_idsMtx); + const auto id_opt = getNextId(); - if (m_ids.empty() == false) + if (id_opt) { - FaceEditor fe(m_dbClient->db(), *m_core, m_logger); - - const auto id = m_ids.front(); - m_ids.pop_front(); + const auto id = *id_opt; - runOn(m_core->getTaskExecutor(), [fe = std::move(fe), id, this, supervisor]() mutable + // check if data already in db + const QByteArray blob = evaluate(m_dbClient->db(), [id](Database::IBackend& backend) { - auto faces = fe.getFacesFor(id); - std::vector facesDetails; + return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); + }); - for (auto& face: faces) + if (blob.isEmpty()) + { + // no data in db, generate + runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable { - const auto faceImg = face->image()->copy(face->rect()); - facesDetails.emplace_back(std::move(face), faceImg); - } + FaceEditor fe(m_dbClient->db(), *m_core, m_logger); - invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); - supervisor->resume(); - }); - } + auto faces = fe.getFacesFor(id); + std::vector facesDetails; + + // store data in db + QJsonArray facesJson; + for (auto& face: faces) + { + QJsonObject rectJson; + rectJson["x"] = face->rect().x(); + rectJson["y"] = face->rect().y(); + rectJson["w"] = face->rect().width(); + rectJson["h"] = face->rect().height(); + + QJsonObject faceJson; + faceJson["face"] = rectJson; + + facesJson.append(faceJson); + } + + const QJsonDocument json(facesJson); + + execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) + { + return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + }); + + // prepare details for model + for (auto& face: faces) + { + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } + + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); - lk.unlock(); + supervisor->resume(); + }); - co_yield ITaskExecutor::ProcessState::Suspended; + co_yield ITaskExecutor::ProcessState::Suspended; + } + else + { + // use data stored in blob + + + co_yield ITaskExecutor::ProcessState::Running; + } + } + else + co_yield ITaskExecutor::ProcessState::Suspended; } // face scanning is done, db won't be needed anymore, release it @@ -187,3 +233,19 @@ void BatchFaceDetector::newPhotos(const QModelIndex &, int first, int last) // make sure processing process is not suspended m_photosProcessingProcess->resume(); } + + +std::optional BatchFaceDetector::getNextId() +{ + std::lock_guard lk(m_idsMtx); + + std::optional result; + + if (m_ids.empty() == false) + { + result = m_ids.front(); + m_ids.pop_front(); + } + + return result; +} diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index ddba1b073e..00674840ca 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -68,6 +68,7 @@ class BatchFaceDetector: public QAbstractListModel ITaskExecutor::ProcessCoroutine processPhotos(ITaskExecutor::IProcessSupervisor *); void appendFaces(std::vector &&); void newPhotos(const QModelIndex &, int, int); + std::optional getNextId(); }; #endif diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 6e3fb9f02d..d101598474 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 12aa8dbe75..210d70e7d7 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 74aba1455bab877d7a7d0d4567fab92a60578f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 9 Sep 2023 19:04:50 +0200 Subject: [PATCH 086/151] Extract photo loading --- src/gui/desktop/utils/batch_face_detector.cpp | 94 ++++++++++--------- src/gui/desktop/utils/batch_face_detector.hpp | 1 + 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 9802a909c7..df7952d23b 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -146,60 +146,20 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: if (blob.isEmpty()) { // no data in db, generate - runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable - { - FaceEditor fe(m_dbClient->db(), *m_core, m_logger); + loadPhotoData(id, supervisor); - auto faces = fe.getFacesFor(id); - std::vector facesDetails; - - // store data in db - QJsonArray facesJson; - for (auto& face: faces) - { - QJsonObject rectJson; - rectJson["x"] = face->rect().x(); - rectJson["y"] = face->rect().y(); - rectJson["w"] = face->rect().width(); - rectJson["h"] = face->rect().height(); - - QJsonObject faceJson; - faceJson["face"] = rectJson; - - facesJson.append(faceJson); - } - - const QJsonDocument json(facesJson); - - execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) - { - return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); - }); - - // prepare details for model - for (auto& face: faces) - { - const auto faceImg = face->image()->copy(face->rect()); - facesDetails.emplace_back(std::move(face), faceImg); - } - - invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); - - supervisor->resume(); - }); - - co_yield ITaskExecutor::ProcessState::Suspended; + co_yield ITaskExecutor::ProcessState::Suspended; // waiting for photo to be loaded, go to sleep } else { // use data stored in blob - co_yield ITaskExecutor::ProcessState::Running; + co_yield ITaskExecutor::ProcessState::Running; // data processed immediately, ask for more cpu time } } else - co_yield ITaskExecutor::ProcessState::Suspended; + co_yield ITaskExecutor::ProcessState::Suspended; // no data, go to sleep } // face scanning is done, db won't be needed anymore, release it @@ -249,3 +209,49 @@ std::optional BatchFaceDetector::getNextId() return result; } + + +void BatchFaceDetector::loadPhotoData(const Photo::Id& id, ITaskExecutor::IProcessSupervisor* supervisor) +{ + runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable + { + FaceEditor fe(m_dbClient->db(), *m_core, m_logger); + + auto faces = fe.getFacesFor(id); + std::vector facesDetails; + + // store data in db + QJsonArray facesJson; + for (auto& face: faces) + { + QJsonObject rectJson; + rectJson["x"] = face->rect().x(); + rectJson["y"] = face->rect().y(); + rectJson["w"] = face->rect().width(); + rectJson["h"] = face->rect().height(); + + QJsonObject faceJson; + faceJson["face"] = rectJson; + + facesJson.append(faceJson); + } + + const QJsonDocument json(facesJson); + + execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) + { + return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + }); + + // prepare details for model + for (auto& face: faces) + { + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } + + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); + + supervisor->resume(); + }); +} diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 00674840ca..0284e4f8bd 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -69,6 +69,7 @@ class BatchFaceDetector: public QAbstractListModel void appendFaces(std::vector &&); void newPhotos(const QModelIndex &, int, int); std::optional getNextId(); + void loadPhotoData(const Photo::Id &, ITaskExecutor::IProcessSupervisor *); }; #endif From 396a9eff16f6ec0015d121d8968cdfe585fce077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 10 Sep 2023 14:41:18 +0200 Subject: [PATCH 087/151] Cleanup code --- src/gui/desktop/utils/batch_face_detector.cpp | 69 +++++++++---------- src/gui/desktop/utils/batch_face_detector.hpp | 2 +- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index df7952d23b..c6f18dfb74 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -146,7 +146,11 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: if (blob.isEmpty()) { // no data in db, generate - loadPhotoData(id, supervisor); + runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable + { + loadPhotoData(id); + supervisor->resume(); // restore this task after data were loaded from photo + }); co_yield ITaskExecutor::ProcessState::Suspended; // waiting for photo to be loaded, go to sleep } @@ -211,47 +215,42 @@ std::optional BatchFaceDetector::getNextId() } -void BatchFaceDetector::loadPhotoData(const Photo::Id& id, ITaskExecutor::IProcessSupervisor* supervisor) +void BatchFaceDetector::loadPhotoData(const Photo::Id& id) { - runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable - { - FaceEditor fe(m_dbClient->db(), *m_core, m_logger); - - auto faces = fe.getFacesFor(id); - std::vector facesDetails; + FaceEditor fe(m_dbClient->db(), *m_core, m_logger); - // store data in db - QJsonArray facesJson; - for (auto& face: faces) - { - QJsonObject rectJson; - rectJson["x"] = face->rect().x(); - rectJson["y"] = face->rect().y(); - rectJson["w"] = face->rect().width(); - rectJson["h"] = face->rect().height(); + auto faces = fe.getFacesFor(id); + std::vector facesDetails; - QJsonObject faceJson; - faceJson["face"] = rectJson; + // store data in db + QJsonArray facesJson; + for (auto& face: faces) + { + QJsonObject rectJson; + rectJson["x"] = face->rect().x(); + rectJson["y"] = face->rect().y(); + rectJson["w"] = face->rect().width(); + rectJson["h"] = face->rect().height(); - facesJson.append(faceJson); - } + QJsonObject faceJson; + faceJson["face"] = rectJson; - const QJsonDocument json(facesJson); + facesJson.append(faceJson); + } - execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) - { - return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); - }); + const QJsonDocument json(facesJson); - // prepare details for model - for (auto& face: faces) - { - const auto faceImg = face->image()->copy(face->rect()); - facesDetails.emplace_back(std::move(face), faceImg); - } + execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) + { + return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + }); - invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); + // prepare details for model + for (auto& face: faces) + { + const auto faceImg = face->image()->copy(face->rect()); + facesDetails.emplace_back(std::move(face), faceImg); + } - supervisor->resume(); - }); + invokeMethod(this, &BatchFaceDetector::appendFaces, std::move(facesDetails)); } diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 0284e4f8bd..3f270f0465 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -69,7 +69,7 @@ class BatchFaceDetector: public QAbstractListModel void appendFaces(std::vector &&); void newPhotos(const QModelIndex &, int, int); std::optional getNextId(); - void loadPhotoData(const Photo::Id &, ITaskExecutor::IProcessSupervisor *); + void loadPhotoData(const Photo::Id &); }; #endif From 162a5c58eafd2955ba19a2d386056f6a23ee172a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 10 Sep 2023 14:44:29 +0200 Subject: [PATCH 088/151] Rename method --- src/gui/desktop/utils/batch_face_detector.cpp | 6 +++--- src/gui/desktop/utils/batch_face_detector.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index c6f18dfb74..1a40af17b8 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -148,8 +148,8 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: // no data in db, generate runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable { - loadPhotoData(id); - supervisor->resume(); // restore this task after data were loaded from photo + loadFacesFromPhoto(id); + supervisor->resume(); // restore this task after faces were loaded from photo }); co_yield ITaskExecutor::ProcessState::Suspended; // waiting for photo to be loaded, go to sleep @@ -215,7 +215,7 @@ std::optional BatchFaceDetector::getNextId() } -void BatchFaceDetector::loadPhotoData(const Photo::Id& id) +void BatchFaceDetector::loadFacesFromPhoto(const Photo::Id& id) { FaceEditor fe(m_dbClient->db(), *m_core, m_logger); diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 3f270f0465..4d09edbf17 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -69,7 +69,7 @@ class BatchFaceDetector: public QAbstractListModel void appendFaces(std::vector &&); void newPhotos(const QModelIndex &, int, int); std::optional getNextId(); - void loadPhotoData(const Photo::Id &); + void loadFacesFromPhoto(const Photo::Id &); }; #endif From 8aebb3c45d19e841887152429c2ed34b339d8044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 10 Sep 2023 21:18:07 +0200 Subject: [PATCH 089/151] Cache detected faces as blob in database --- src/gui/desktop/utils/people_editor.cpp | 150 +++++++++++++++++++++--- 1 file changed, 135 insertions(+), 15 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 63b550e46e..24bb28b7f9 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -18,6 +18,9 @@ #include "people_editor.hpp" #include +#include +#include +#include #include #include @@ -68,6 +71,97 @@ namespace return avg_face; } + QJsonObject toJson(const QRect& rect) + { + QJsonObject rectJson; + rectJson["x"] = rect.x(); + rectJson["y"] = rect.y(); + rectJson["w"] = rect.width(); + rectJson["h"] = rect.height(); + + return rectJson; + } + + QJsonObject toJson(const PersonInfo& pi) + { + assert(pi.id.valid() == false); + assert(pi.p_id.valid() == false); + assert(pi.ph_id.valid()); + assert(pi.f_id.valid() == false); + + QJsonObject piJson; + piJson["ph_id"] = pi.ph_id.value(); + piJson["rect"] = toJson(pi.rect); + + return piJson; + } + + QJsonArray toJson(const Person::Fingerprint& fp) + { + QJsonArray fpJson; + + for(const auto v: fp) + fpJson.append(v); + + return fpJson; + } + + QJsonObject toJson(const PersonFingerprint& fp) + { + assert(fp.id().valid() == false); + + QJsonObject fpJson; + fpJson["fingerprint"] = toJson(fp.fingerprint()); + + return fpJson; + } + + QJsonObject toJson(const FaceInfo& face) + { + QJsonObject faceJson; + faceJson["face"] = toJson(face.face); + faceJson["fingerprint"] = toJson(face.fingerprint); + + return faceJson; + } + + QJsonArray toJson(const std::vector& faces) + { + QJsonArray facesJson; + for (auto& face: faces) + facesJson.append(toJson(face)); + + return facesJson; + } + + std::vector fromJson(const QJsonDocument& doc) + { + std::vector result; + + const QJsonArray faceInfos = doc.array(); + for (const QJsonValue faceValue: faceInfos) + { + const QJsonObject faceInfoObject = faceValue.toObject(); + const QJsonObject faceObject = faceInfoObject["face"].toObject(); + const QJsonObject fingerprintObject = faceInfoObject["fingerprint"].toObject(); + const QJsonObject faceRect = faceObject["rect"].toObject(); + const QJsonArray personFingerprintObject = fingerprintObject["fingerprint"].toArray(); + + const QRect rect(faceRect["x"].toInt(), faceRect["y"].toInt(), faceRect["w"].toInt(), faceRect["h"].toInt()); + Person::Fingerprint fingerprint; + std::ranges::transform(personFingerprintObject, std::back_inserter(fingerprint), [](const auto v){return v.toDouble();}); + const Photo::Id ph_id(faceObject["ph_id"]); + + FaceInfo fi(ph_id, rect); + fi.fingerprint = PersonFingerprint({}, fingerprint); + fi.person = PersonName(); + + result.push_back(fi); + } + + return result; + } + bool wasPhotoAnalyzedAndHasNoFaces(Database::IDatabase& db, const Photo::Id& ph_id) { @@ -269,28 +363,54 @@ namespace // no data in db if (list_of_faces.empty()) { - // analyze photo - look for faces - const auto detected_faces = detectFaces(image, logger); - std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) + // check in cache + const QByteArray blob = evaluate(db, [id](Database::IBackend& backend) { - return FaceInfo(id, rect);; + return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); }); - //calculate fingerprints - calculateMissingFingerprints(result, image, logger); - - //recognize people - recognizePeople(result, db, logger); + if (blob.isEmpty() == false) + { + const QJsonDocument json = QJsonDocument::fromJson(blob); - if (result.empty()) + result = fromJson(json); + } + else { - db.exec([id](Database::IBackend& backend) + // analyze photo - look for faces + const auto detected_faces = detectFaces(image, logger); + std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) { - backend.set( - id, - FacesAnalysisState, - FacesAnalysisType::AnalysedAndNotFound); + return FaceInfo(id, rect);; }); + + //calculate fingerprints + calculateMissingFingerprints(result, image, logger); + + //recognize people + recognizePeople(result, db, logger); + + if (result.empty()) + { + // mark photo as one without faces + db.exec([id](Database::IBackend& backend) + { + backend.set( + id, + FacesAnalysisState, + FacesAnalysisType::AnalysedAndNotFound); + }); + } + else + { + // store detected, but not confirmed by user faces as blob in database for cache + const QJsonDocument json(toJson(result)); + + execute(db, [id, blob = json.toJson()](Database::IBackend& backend) + { + return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + }); + } } } else // data in db just use it From 2791ac31511acf76c31f0011e0a92f0a450aed7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 10 Sep 2023 21:18:36 +0200 Subject: [PATCH 090/151] Do not perform face caching in BatchFaceDetector --- src/gui/desktop/utils/batch_face_detector.cpp | 57 ++----------------- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 1a40af17b8..24f3e8dac9 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -1,8 +1,4 @@ -#include -#include -#include - #include #include #include @@ -137,33 +133,15 @@ ITaskExecutor::ProcessCoroutine BatchFaceDetector::processPhotos(ITaskExecutor:: { const auto id = *id_opt; - // check if data already in db - const QByteArray blob = evaluate(m_dbClient->db(), [id](Database::IBackend& backend) + // no data in db, generate + runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable { - return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); + loadFacesFromPhoto(id); + supervisor->resume(); // restore this task after faces were loaded from photo }); - - if (blob.isEmpty()) - { - // no data in db, generate - runOn(m_core->getTaskExecutor(), [id, this, supervisor]() mutable - { - loadFacesFromPhoto(id); - supervisor->resume(); // restore this task after faces were loaded from photo - }); - - co_yield ITaskExecutor::ProcessState::Suspended; // waiting for photo to be loaded, go to sleep - } - else - { - // use data stored in blob - - - co_yield ITaskExecutor::ProcessState::Running; // data processed immediately, ask for more cpu time - } } - else - co_yield ITaskExecutor::ProcessState::Suspended; // no data, go to sleep + + co_yield ITaskExecutor::ProcessState::Suspended; } // face scanning is done, db won't be needed anymore, release it @@ -222,29 +200,6 @@ void BatchFaceDetector::loadFacesFromPhoto(const Photo::Id& id) auto faces = fe.getFacesFor(id); std::vector facesDetails; - // store data in db - QJsonArray facesJson; - for (auto& face: faces) - { - QJsonObject rectJson; - rectJson["x"] = face->rect().x(); - rectJson["y"] = face->rect().y(); - rectJson["w"] = face->rect().width(); - rectJson["h"] = face->rect().height(); - - QJsonObject faceJson; - faceJson["face"] = rectJson; - - facesJson.append(faceJson); - } - - const QJsonDocument json(facesJson); - - execute(m_dbClient->db(), [id, blob = json.toJson()](Database::IBackend& backend) - { - return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); - }); - // prepare details for model for (auto& face: faces) { diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index d101598474..bacc33d368 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 210d70e7d7..8eeb56640e 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 7ad45254b3a766dff6e724c109b233d1da716176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 10 Sep 2023 21:20:05 +0200 Subject: [PATCH 091/151] Cleanup --- src/gui/desktop/utils/batch_face_detector.cpp | 1 - tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 24f3e8dac9..dcaa73224c 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "batch_face_detector.hpp" diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index bacc33d368..6e3fb9f02d 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 8eeb56640e..12aa8dbe75 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 3db8d50342ef4401f3863bd3d0fa341f51870deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 14 Sep 2023 20:49:42 +0200 Subject: [PATCH 092/151] Fix typo --- tools/{update_licence.py => update_license.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{update_licence.py => update_license.py} (100%) diff --git a/tools/update_licence.py b/tools/update_license.py similarity index 100% rename from tools/update_licence.py rename to tools/update_license.py From 327a1a6f3ba17183524f7316667ef4d6bc353d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 15 Sep 2023 21:41:48 +0200 Subject: [PATCH 093/151] Drop dead code --- src/photos_crawler/CMakeLists.txt | 2 -- src/plugins/CMakeLists.txt | 1 - 2 files changed, 3 deletions(-) diff --git a/src/photos_crawler/CMakeLists.txt b/src/photos_crawler/CMakeLists.txt index 1093175249..1e97f0d653 100644 --- a/src/photos_crawler/CMakeLists.txt +++ b/src/photos_crawler/CMakeLists.txt @@ -4,8 +4,6 @@ find_package(Threads) include(GenerateExportHeader) -include_directories(${CMAKE_BINARY_DIR}/exports) - set(ANALYZER_SOURCES default_analyzers/file_analyzer.cpp default_filesystem_scanners/filesystemscanner.cpp diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 4ef9c31a21..3542797da2 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -18,7 +18,6 @@ set_target_properties(plugins PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories(plugins PRIVATE - ${CMAKE_BINARY_DIR}/exports ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE From 5526be480ef4a26df86cad9087e23296946a914c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 14 Sep 2023 20:53:48 +0200 Subject: [PATCH 094/151] Include reflect++ tool --- .github/workflows/linux-build.yml | 7 +++---- .github/workflows/macos-build.yml.bak | 2 +- .github/workflows/windows-build.yml | 1 + .gitmodules | 3 +++ CMakeLists.txt | 3 +++ tools/CMakeLists.txt | 2 ++ tools/reflect++ | 1 + vcpkg.json | 12 ++++++------ 8 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 tools/CMakeLists.txt create mode 160000 tools/reflect++ diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 5431d78ff5..912e71d4ad 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -25,17 +25,14 @@ jobs: submodules: true - name: Load llvm keys - if: matrix.compiler.compiler == 'LLVM' run: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - - name: Update clang - if: matrix.compiler.compiler == 'LLVM' uses: myci-actions/add-deb-repo@10 with: repo: deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main repo-name: llvm-toolchain-focal-16 update: false - install: clang-16 - name: Install system dependencies run: | @@ -47,7 +44,9 @@ jobs: qt6-multimedia-dev \ libqt6svg6-dev \ qt6-l10n-tools \ + clang-16 \ libboost-dev \ + libclang-16-dev \ libopencv-dev \ libdlib-dev \ libexiv2-dev \ @@ -82,4 +81,4 @@ jobs: build-type: Release cc: ${{ matrix.compiler.CC }} cxx: ${{ matrix.compiler.CXX }} - configure-options: -DBUILD_SHARED_LIBS=${{ matrix.shared }} -DLUPDATE=/usr/lib/qt6/bin/lupdate -DLRELEASE:FILEPATH=/usr/lib/qt6/bin/lrelease -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + configure-options: -DBUILD_SHARED_LIBS=${{ matrix.shared }} -DLUPDATE=/usr/lib/qt6/bin/lupdate -DLRELEASE:FILEPATH=/usr/lib/qt6/bin/lrelease -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DClang_DIR=/usr/lib/llvm-16/lib/cmake/clang diff --git a/.github/workflows/macos-build.yml.bak b/.github/workflows/macos-build.yml.bak index 8a7881dfd8..4af2471f78 100644 --- a/.github/workflows/macos-build.yml.bak +++ b/.github/workflows/macos-build.yml.bak @@ -47,7 +47,7 @@ jobs: with: build-dir: ${{ runner.workspace }}/build build-type: Release - configure-options: -DBUILD_TESTING=OFF + configure-options: -DBUILD_TESTING=OFF -DClang_DIR=/usr/local/opt/llvm/lib/cmake/clang cc: /usr/local/opt/llvm/bin/clang cxx: /usr/local/opt/llvm/bin/clang++ env: diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 207436f570..53941f2888 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -24,6 +24,7 @@ jobs: - name: Prepare build dir run: | New-Item -ItemType directory -Path "c:\" -Name "build" + compact.exe /C "c:\build\" New-Item -ItemType Junction -Path "out" -Target "c:\build" Move-Item -Path vcpkg -Destination c:\build New-Item -ItemType Junction -Path "vcpkg" -Target "c:\build\vcpkg" diff --git a/.gitmodules b/.gitmodules index 1ea2dd3c5d..66301d09af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "src/gui/desktop/utils/animated_webp"] path = src/gui/desktop/utils/animated_webp url = https://github.com/Kicer86/AnimatedWebP.git +[submodule "tools/reflex++"] + path = tools/reflect++ + url = https://github.com/Kicer86/reflexpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c131277161..c537999c26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) + #check if git modules are setup if(NOT EXISTS ${PROJECT_SOURCE_DIR}/cmake_modules/FindEasyExif.cmake) message(FATAL_ERROR "Git submodules were not updated. See docs/build.txt for instructions.") @@ -126,6 +128,7 @@ endif(UNIX OR CYGWIN) #subdirs add_subdirectory(src) +add_subdirectory(tools) add_subdirectory(tr) #documentation diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000000..340ebb1329 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_subdirectory(reflect++) diff --git a/tools/reflect++ b/tools/reflect++ new file mode 160000 index 0000000000..36bf3a1fc5 --- /dev/null +++ b/tools/reflect++ @@ -0,0 +1 @@ +Subproject commit 36bf3a1fc5409e52c442a6c948f813eb40f265e1 diff --git a/vcpkg.json b/vcpkg.json index d63db0adac..03764ffcc4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,14 +7,14 @@ "description": "Photo Broom is a tool for managing your photos.", "dependencies": [ { - "name": "dlib", - "default-features": false, - "features": [ "fftw3" ] + "name": "dlib", + "default-features": false, + "features": [ "fftw3" ] }, { - "name": "opencv4", - "default-features": false, - "features": [ "contrib" ] + "name": "opencv4", + "default-features": false, + "features": [ "contrib" ] }, "boost-multi-index", "exiv2", From 556ee2898e1e172c8d6fb388ae6a0e4ddfa10f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 22 Sep 2023 21:13:39 +0200 Subject: [PATCH 095/151] Be explicit --- .../implementation/json_to_backend.cpp | 2 +- src/database/implementation/person_data.cpp | 8 -------- src/database/person_data.hpp | 12 ++++++------ 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/database/database_tools/implementation/json_to_backend.cpp b/src/database/database_tools/implementation/json_to_backend.cpp index fa9c42e32a..3dcd08dfe9 100644 --- a/src/database/database_tools/implementation/json_to_backend.cpp +++ b/src/database/database_tools/implementation/json_to_backend.cpp @@ -116,7 +116,7 @@ namespace Database { const auto person = value.toObject(); - return PersonFullInfo{.name = person["name"].toString()}; + return PersonFullInfo{.name = PersonName(person["name"].toString())}; }); delta.insert(people); diff --git a/src/database/implementation/person_data.cpp b/src/database/implementation/person_data.cpp index 25516119e4..256214dc1a 100644 --- a/src/database/implementation/person_data.cpp +++ b/src/database/implementation/person_data.cpp @@ -41,14 +41,6 @@ PersonName::PersonName(const QString& name): } -PersonName::PersonName (const PersonName& other): - m_id(other.m_id), - m_name(other.m_name) -{ - -} - - const Person::Id& PersonName::id() const { return m_id; diff --git a/src/database/person_data.hpp b/src/database/person_data.hpp index c5a2f2414f..91dd7fe439 100644 --- a/src/database/person_data.hpp +++ b/src/database/person_data.hpp @@ -41,9 +41,9 @@ class DATABASE_EXPORT PersonName final { public: PersonName(); - PersonName(const Person::Id &, const QString &); - PersonName(const QString &); - PersonName(const PersonName &); + explicit PersonName(const Person::Id &, const QString &); + explicit PersonName(const QString &); + PersonName(const PersonName &) = default; ~PersonName() = default; PersonName& operator=(const PersonName &) = default; @@ -64,8 +64,8 @@ class DATABASE_EXPORT PersonFingerprint using Id = ::Id; PersonFingerprint() {} - PersonFingerprint(const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint) {} - PersonFingerprint(const Id& id, const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint), m_id(id) {} + explicit PersonFingerprint(const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint) {} + explicit PersonFingerprint(const Id& id, const Person::Fingerprint& fingerprint): m_fingerprint(fingerprint), m_id(id) {} auto operator<=>(const PersonFingerprint &) const = default; @@ -125,6 +125,6 @@ class PersonFullInfo }; -Q_DECLARE_METATYPE( PersonName ) +Q_DECLARE_METATYPE(PersonName) #endif From b86dadeb5f67f0f044a6d584274c0ea736fb8df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 28 Sep 2023 20:45:31 +0200 Subject: [PATCH 096/151] Introduce json serialization --- src/core/core_test.cmake | 11 ++ src/core/json_serializer.hpp | 181 ++++++++++++++++++ src/core/unit_tests/json_serializer_tests.cpp | 86 +++++++++ src/core/unit_tests/json_serializer_tests.hpp | 37 ++++ tools/reflect++ | 2 +- 5 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 src/core/json_serializer.hpp create mode 100644 src/core/unit_tests/json_serializer_tests.cpp create mode 100644 src/core/unit_tests/json_serializer_tests.hpp diff --git a/src/core/core_test.cmake b/src/core/core_test.cmake index b33c7302cd..29973ad406 100644 --- a/src/core/core_test.cmake +++ b/src/core/core_test.cmake @@ -5,6 +5,14 @@ find_package(GTest REQUIRED CONFIG) find_package(Qt6 REQUIRED COMPONENTS Core Gui) find_package(Qt6Test REQUIRED) +include(${PROJECT_SOURCE_DIR}/tools/reflect++/Reflect++.cmake) + +ReflectFiles( + core + ReflectionFiles + + unit_tests/json_serializer_tests.hpp +) addTestTarget(core SOURCES @@ -23,6 +31,7 @@ addTestTarget(core unit_tests/data_from_path_extractor_tests.cpp unit_tests/exiftool_video_details_reader_tests.cpp unit_tests/function_wrappers_tests.cpp + unit_tests/json_serializer_tests.cpp unit_tests/lazy_ptr_tests.cpp unit_tests/model_compositor_tests.cpp #unit_tests/oriented_image_tests.cpp @@ -31,6 +40,8 @@ addTestTarget(core unit_tests/status_tests.cpp unit_tests/tag_value_tests.cpp + ${ReflectionFiles} + LIBRARIES GTest::gtest GTest::gmock diff --git a/src/core/json_serializer.hpp b/src/core/json_serializer.hpp new file mode 100644 index 0000000000..187fb7c53b --- /dev/null +++ b/src/core/json_serializer.hpp @@ -0,0 +1,181 @@ + +#ifndef JSON_SERIALIZER_HPP_INCLUDED +#define JSON_SERIALIZER_HPP_INCLUDED + +#include +#include +#include +#include + +#include "generic_concepts.hpp" + + + +namespace JSon +{ + template + struct CustomType; + + namespace impl + { + template + concept hasAssignmentOperator = requires(T a, const R b) + { + { a = b }; + }; + + template + concept isQtStreamable = requires(QDataStream s, T a) + { + { s << a }; + { s >> a }; + }; + + template + concept hasCustomSerialization = requires(T a) + { + { JSon::CustomType::serialize(a) }; + { JSon::CustomType::deserialize({}) } -> std::same_as; + }; + + + template + QJsonObject serialize(const T &); + + template + auto getSerialized(const T &); + + template + requires Container + QJsonArray serialize(const T& arr) + { + QJsonArray jsonArr; + + for(const auto& i: arr) + jsonArr.append(getSerialized(i)); + + return jsonArr; + } + + template + QJsonObject serialize(const T& obj) + { + QJsonObject jsonObj; + + if constexpr (hasCustomSerialization) + jsonObj = JSon::CustomType::serialize(obj); + else + reflectpp::get_object_members(obj, [&jsonObj](auto member_info, const auto& member) + { + const QString name = QString::fromStdString(std::string(member_info.name)); + jsonObj[name] = getSerialized(member); + }); + + return jsonObj; + } + + template + auto getSerialized(const T& obj) + { + if constexpr (hasAssignmentOperator) + return obj; + else if constexpr (isQtStreamable) + { + QByteArray d; + QDataStream s(&d, QIODeviceBase::WriteOnly); + s << obj; + return d.toBase64().data(); + } + else + return serialize(obj); + } + + template + T deserialize(const JT &); + + template + T getDeserialized(const QJsonValueRef &); + + template + requires Container + T deserialize(const JT& json) + { + assert(json.isArray()); + T r; + + QJsonArray array; + if constexpr (std::is_same_v) + array = json.array(); + else + array = json.toArray(); + + using VT = typename T::value_type; + + for(const auto& e: array) + r.push_back(getDeserialized(e)); + + return r; + } + + template + T deserialize(const JT& json) + { + assert(json.isObject()); + T r; + + QJsonObject object; + if constexpr (std::is_same_v) + object = json.object(); + else + object = json.toObject(); + + if constexpr (hasCustomSerialization) + return JSon::CustomType::deserialize(object); + else + reflectpp::set_object_members(r, [&object](const auto member_info) + { + using VT = decltype(member_info)::type; + const QString name = QString::fromStdString(std::string(member_info.name)); + const auto value = object[name]; + + return getDeserialized(value); + }); + + return r; + } + + template + T getDeserialized(const QJsonValueRef& value) + { + if constexpr (hasAssignmentOperator) + return value.toVariant().value(); + else if constexpr (isQtStreamable) + { + QByteArray d = QByteArray::fromBase64(value.toString().toUtf8()); + QDataStream s(&d, QIODeviceBase::ReadOnly); + T obj; + s >> obj; + return obj; + } + else + return deserialize(value); + } + } + + template + QJsonDocument serialize(const T& obj) + { + const auto jsonObj = impl::serialize(obj); + const QJsonDocument doc(jsonObj); + + return doc; + } + + template + T deserialize(const QJsonDocument& doc) + { + return impl::deserialize(doc); + } +} + +#endif diff --git a/src/core/unit_tests/json_serializer_tests.cpp b/src/core/unit_tests/json_serializer_tests.cpp new file mode 100644 index 0000000000..11ef3065e8 --- /dev/null +++ b/src/core/unit_tests/json_serializer_tests.cpp @@ -0,0 +1,86 @@ + +#include + +#include "json_serializer_tests_r++.hpp" +#include "json_serializer.hpp" + + +namespace +{ + struct XYZ + { + int a = 7; + int b = 8; + + auto operator<=>(const XYZ &) const = default; + }; +} + +namespace JSon +{ + template<> + struct CustomType + { + static QJsonObject serialize(const XYZ& xyz) + { + QJsonObject json; + json["a"] = xyz.a; + json["b"] = xyz.b; + + return json; + } + + static XYZ deserialize(const QJsonObject& json) + { + XYZ xyz; + + xyz.a = json["a"].toInt(); + xyz.b = json["b"].toInt(); + + return xyz; + } + }; +} + + +TEST(JsonSerializerTest, SerializationDeserialization) +{ + DEF def; + def.abc_vec.emplace_back(11, 15.f, -8., "World", QRect(3, 6, 9, 11)); + def.abc_list.emplace_back(-6, -765.f, 89., "World", QRect(-1, -50, 9, 11)); + + const QJsonDocument json = JSon::serialize(def); + const DEF def2 = JSon::deserialize(json); + + EXPECT_EQ(def, def2); +} + + +TEST(JsonSerializerTest, CustomSerializationDeserialization) +{ + static_assert(JSon::impl::hasCustomSerialization); + + XYZ xyz; + xyz.a = 77; + xyz.b = -156; + + const QJsonDocument json = JSon::serialize(xyz); + const XYZ xyz2 = JSon::deserialize(json); + + EXPECT_EQ(xyz, xyz2); +} + + +TEST(JsonSerializerTest, CustomSerializationDeserializationToArray) +{ + static_assert(JSon::impl::hasCustomSerialization); + + std::vector xyz; + for(int i = 0; i < 5; i++) + xyz.emplace_back(rand(), rand()); + + const QJsonDocument json = JSon::serialize(xyz); + const auto xyz2 = JSon::deserialize>(json); + + EXPECT_EQ(xyz, xyz2); +} diff --git a/src/core/unit_tests/json_serializer_tests.hpp b/src/core/unit_tests/json_serializer_tests.hpp new file mode 100644 index 0000000000..bea74be7b6 --- /dev/null +++ b/src/core/unit_tests/json_serializer_tests.hpp @@ -0,0 +1,37 @@ + +#pragma once + +#include +#include +#include +#include + + +namespace +{ + struct ABC + { + int a = 5; + float b = 45.f; + double c = 123.; + std::string d = "Hello"; + QRect e = QRect(1, 2, 3, 4); + std::vector f = {}; + + bool operator==(const ABC& other) const + { + return std::tie(a, b, c, d, e, f) == std::tie(other.a, other.b, other.c, other.d, other.e, other.f); + } + }; + + struct DEF + { + std::vector abc_vec; + std::list abc_list; + + bool operator==(const DEF& other) const + { + return std::tie(abc_vec, abc_list) == std::tie(other.abc_vec, other.abc_list); + } + }; +} diff --git a/tools/reflect++ b/tools/reflect++ index 36bf3a1fc5..4d03b23549 160000 --- a/tools/reflect++ +++ b/tools/reflect++ @@ -1 +1 @@ -Subproject commit 36bf3a1fc5409e52c442a6c948f813eb40f265e1 +Subproject commit 4d03b2354918ffe3c1c86cbd7401197032c557de From 76968a71fec2de59272dc462bfbd717db80e5d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 28 Sep 2023 21:00:20 +0200 Subject: [PATCH 097/151] Cleanups --- src/core/core_test.cmake | 7 ++++--- tools/reflect++ | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/core_test.cmake b/src/core/core_test.cmake index 29973ad406..57c9b450f8 100644 --- a/src/core/core_test.cmake +++ b/src/core/core_test.cmake @@ -8,10 +8,11 @@ find_package(Qt6Test REQUIRED) include(${PROJECT_SOURCE_DIR}/tools/reflect++/Reflect++.cmake) ReflectFiles( - core ReflectionFiles - - unit_tests/json_serializer_tests.hpp + TARGET + core + SOURCES + unit_tests/json_serializer_tests.hpp ) addTestTarget(core diff --git a/tools/reflect++ b/tools/reflect++ index 4d03b23549..5bc90a473f 160000 --- a/tools/reflect++ +++ b/tools/reflect++ @@ -1 +1 @@ -Subproject commit 4d03b2354918ffe3c1c86cbd7401197032c557de +Subproject commit 5bc90a473f0fdd2319a6cefbf0f2e9be75e5947e From 7ce989f036f88a6404f725076db11231eac1bbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 28 Sep 2023 21:04:17 +0200 Subject: [PATCH 098/151] Cleanup --- src/core/id.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/id.hpp b/src/core/id.hpp index 67170b6eb5..bbfe895164 100644 --- a/src/core/id.hpp +++ b/src/core/id.hpp @@ -121,4 +121,4 @@ class Id bool m_valid = false; }; -#endif // ID_HPP +#endif From 6b461db33b58cd1ee9c29a1f3a117f2d87487483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 29 Sep 2023 18:45:04 +0200 Subject: [PATCH 099/151] Make it possible for custom types to be QJsonArrays --- src/core/json_serializer.hpp | 115 ++++++++++-------- src/core/unit_tests/json_serializer_tests.cpp | 114 ++++++++++++++++- 2 files changed, 172 insertions(+), 57 deletions(-) diff --git a/src/core/json_serializer.hpp b/src/core/json_serializer.hpp index 187fb7c53b..9dc96c1379 100644 --- a/src/core/json_serializer.hpp +++ b/src/core/json_serializer.hpp @@ -34,44 +34,71 @@ namespace JSon template concept hasCustomSerialization = requires(T a) { - { JSon::CustomType::serialize(a) }; + { JSon::CustomType::serialize(a) } -> std::same_as::type>; { JSon::CustomType::deserialize({}) } -> std::same_as; }; + template + JT convertTo(const T& valueRef) + { + if constexpr (std::is_same_v) + { + assert(valueRef.isArray()); - template - QJsonObject serialize(const T &); - - template - auto getSerialized(const T &); + QJsonArray array; + if constexpr (std::is_same_v) + array = valueRef.array(); + else + array = valueRef.toArray(); - template - requires Container - QJsonArray serialize(const T& arr) - { - QJsonArray jsonArr; + return array; + } + else if constexpr (std::is_same_v) + { + assert(valueRef.isObject()); - for(const auto& i: arr) - jsonArr.append(getSerialized(i)); + QJsonObject object; + if constexpr (std::is_same_v) + object = valueRef.object(); + else + object = valueRef.toObject(); - return jsonArr; + return object; + } + else + static_assert(false, "Unexpected type"); } + template - QJsonObject serialize(const T& obj) - { - QJsonObject jsonObj; + auto getSerialized(const T &); + template + auto serialize(const T& obj) + { if constexpr (hasCustomSerialization) - jsonObj = JSon::CustomType::serialize(obj); + return JSon::CustomType::serialize(obj); + else if constexpr (Container) + { + QJsonArray jsonArr; + + for(const auto& i: obj) + jsonArr.append(getSerialized(i)); + + return jsonArr; + } else + { + QJsonObject jsonObj; + reflectpp::get_object_members(obj, [&jsonObj](auto member_info, const auto& member) { const QString name = QString::fromStdString(std::string(member_info.name)); jsonObj[name] = getSerialized(member); }); - return jsonObj; + return jsonObj; + } } template @@ -91,47 +118,28 @@ namespace JSon } template - T deserialize(const JT &); - - template - T getDeserialized(const QJsonValueRef &); + T getDeserialized(const JT &); template - requires Container T deserialize(const JT& json) { - assert(json.isArray()); T r; - QJsonArray array; - if constexpr (std::is_same_v) - array = json.array(); - else - array = json.toArray(); - - using VT = typename T::value_type; - - for(const auto& e: array) - r.push_back(getDeserialized(e)); - - return r; - } + if constexpr (hasCustomSerialization) + return JSon::CustomType::deserialize(convertTo::type>(json)); + else if constexpr (Container) + { + const QJsonArray array = convertTo(json); - template - T deserialize(const JT& json) - { - assert(json.isObject()); - T r; + using VT = typename T::value_type; - QJsonObject object; - if constexpr (std::is_same_v) - object = json.object(); + for(const auto& e: array) + r.push_back(getDeserialized(e)); + } else - object = json.toObject(); + { + const QJsonObject object = convertTo(json); - if constexpr (hasCustomSerialization) - return JSon::CustomType::deserialize(object); - else reflectpp::set_object_members(r, [&object](const auto member_info) { using VT = decltype(member_info)::type; @@ -140,15 +148,16 @@ namespace JSon return getDeserialized(value); }); + } return r; } - template - T getDeserialized(const QJsonValueRef& value) + template + T getDeserialized(const JT& value) { if constexpr (hasAssignmentOperator) - return value.toVariant().value(); + return value.toVariant().template value(); else if constexpr (isQtStreamable) { QByteArray d = QByteArray::fromBase64(value.toString().toUtf8()); diff --git a/src/core/unit_tests/json_serializer_tests.cpp b/src/core/unit_tests/json_serializer_tests.cpp index 11ef3065e8..93ab13bf48 100644 --- a/src/core/unit_tests/json_serializer_tests.cpp +++ b/src/core/unit_tests/json_serializer_tests.cpp @@ -14,6 +14,13 @@ namespace auto operator<=>(const XYZ &) const = default; }; + + struct IJK + { + std::vector a; + + auto operator<=>(const IJK &) const = default; + }; } namespace JSon @@ -21,6 +28,8 @@ namespace JSon template<> struct CustomType { + using type = QJsonObject; + static QJsonObject serialize(const XYZ& xyz) { QJsonObject json; @@ -40,9 +49,35 @@ namespace JSon return xyz; } }; + + template<> + struct CustomType + { + using type = QJsonArray; + + static QJsonArray serialize(const IJK& ijk) + { + QJsonArray array; + std::ranges::copy(ijk.a, std::back_inserter(array)); + + return array; + } + + static IJK deserialize(const QJsonArray& json) + { + IJK ijk; + + std::transform(json.begin(), json.end(), std::back_inserter(ijk.a), [](const QJsonValueConstRef& value) {return value.toInt();}); + + return ijk; + } + }; } +static_assert(JSon::impl::hasCustomSerialization); + + TEST(JsonSerializerTest, SerializationDeserialization) { DEF def; @@ -58,7 +93,6 @@ TEST(JsonSerializerTest, SerializationDeserialization) TEST(JsonSerializerTest, CustomSerializationDeserialization) { - static_assert(JSon::impl::hasCustomSerialization); XYZ xyz; xyz.a = 77; @@ -71,10 +105,8 @@ TEST(JsonSerializerTest, CustomSerializationDeserialization) } -TEST(JsonSerializerTest, CustomSerializationDeserializationToArray) +TEST(JsonSerializerTest, CustomSerializationDeserializationOfArray) { - static_assert(JSon::impl::hasCustomSerialization); - std::vector xyz; for(int i = 0; i < 5; i++) xyz.emplace_back(rand(), rand()); @@ -84,3 +116,77 @@ TEST(JsonSerializerTest, CustomSerializationDeserializationToArray) EXPECT_EQ(xyz, xyz2); } + + +TEST(JsonSerializerTest, CustomArraySerializationDeserialization) +{ + IJK ijk; + ijk.a.push_back(1); + ijk.a.push_back(2); + ijk.a.push_back(555); + + const QJsonDocument json = JSon::serialize(ijk); + const auto ijk2 = JSon::deserialize(json); + + EXPECT_EQ(ijk, ijk2); +} + + +TEST(JsonSerializerTest, CustomTypeSerialization) +{ + const XYZ xyz; + const auto json = JSon::serialize(xyz); + + const QJsonDocument expectedJson = QJsonDocument::fromJson( + R"( + { + "a": 7, + "b": 8 + } + )" + ); + + EXPECT_EQ(json, expectedJson); +} + + +TEST(JsonSerializerTest, ArrayOfCustomTypeSerialization) +{ + const std::vector xyz({ {1, 2}, {-5, -9}, {-100, 800} }); + const auto json = JSon::serialize(xyz); + + const QJsonDocument expectedJson = QJsonDocument::fromJson( + R"( + [ + { + "a": 1, "b": 2 + }, + { + "a": -5, "b": -9 + }, + { + "a": -100, "b": 800 + } + ] + )" + ); + + EXPECT_EQ(json, expectedJson); +} + + +TEST(JsonSerializerTest, ArrayOfSimpleTypeSerialization) +{ + const std::vector xyz({-10, -5, 1, 9, -500, 9}); + const auto json = JSon::serialize(xyz); + + const QJsonDocument expectedJson = QJsonDocument::fromJson( + R"( + [ + -10, -5, 1, 9, -500, 9 + ] + )" + ); + + EXPECT_EQ(json, expectedJson); +} From 9a5cd191e506c1e3b08bbd84801dd889eff990a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 29 Sep 2023 18:58:31 +0200 Subject: [PATCH 100/151] Use JSon serializer for cache handling --- src/database/person_data.hpp | 2 +- src/gui/desktop/utils/CMakeLists.txt | 14 ++- src/gui/desktop/utils/people_editor.cpp | 140 ++++++++---------------- src/gui/desktop/utils/people_editor.hpp | 9 ++ 4 files changed, 66 insertions(+), 99 deletions(-) diff --git a/src/database/person_data.hpp b/src/database/person_data.hpp index 91dd7fe439..7fa862b67f 100644 --- a/src/database/person_data.hpp +++ b/src/database/person_data.hpp @@ -33,7 +33,7 @@ namespace Person { using Id = Id; - typedef std::vector Fingerprint; + using Fingerprint = std::vector; } diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 3b3bbcdcb0..5888f25e0f 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -4,6 +4,8 @@ add_subdirectory(animated_webp) find_package(Qt6 REQUIRED COMPONENTS Svg Quick QuickWidgets) find_package(OpenCV REQUIRED) +include(${PROJECT_SOURCE_DIR}/tools/reflect++/Reflect++.cmake) + set(UTILS_SOURCES grouppers/animation_generator.cpp grouppers/animation_generator.hpp @@ -55,7 +57,7 @@ set(UTILS_SOURCES variant_display.hpp ) -add_library(gui_utils OBJECT ${UTILS_SOURCES}) +add_library(gui_utils OBJECT ${UTILS_SOURCES} ${ReflectionFiles}) set_target_properties(gui_utils PROPERTIES AUTOMOC TRUE) target_link_libraries(gui_utils PRIVATE @@ -77,5 +79,15 @@ target_include_directories(gui_utils PRIVATE ${CMAKE_SOURCE_DIR}/src/gui/ ${CMAKE_SOURCE_DIR}/src/gui/desktop + ${CMAKE_CURRENT_BINARY_DIR} +) + +ReflectFiles( + ReflectionFiles + TARGET + gui_utils + SOURCES + people_editor.hpp ) +target_sources(gui_utils PRIVATE ${ReflectionFiles}) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 24bb28b7f9..87affc626e 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -16,6 +16,7 @@ */ #include "people_editor.hpp" +#include "people_editor_r++.hpp" #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +37,29 @@ using namespace Database::CommonGeneralFlags; +namespace JSon +{ + template + struct CustomType> + { + using type = QJsonObject; + + static QJsonObject serialize(const Id& id) + { + QJsonObject json; + json["id"] = id.value(); + + return json; + } + + static Id deserialize(const QJsonObject& json) + { + return Id(json["id"].toVariant().value()); + } + }; +} + + namespace { struct FaceInfo @@ -71,98 +96,6 @@ namespace return avg_face; } - QJsonObject toJson(const QRect& rect) - { - QJsonObject rectJson; - rectJson["x"] = rect.x(); - rectJson["y"] = rect.y(); - rectJson["w"] = rect.width(); - rectJson["h"] = rect.height(); - - return rectJson; - } - - QJsonObject toJson(const PersonInfo& pi) - { - assert(pi.id.valid() == false); - assert(pi.p_id.valid() == false); - assert(pi.ph_id.valid()); - assert(pi.f_id.valid() == false); - - QJsonObject piJson; - piJson["ph_id"] = pi.ph_id.value(); - piJson["rect"] = toJson(pi.rect); - - return piJson; - } - - QJsonArray toJson(const Person::Fingerprint& fp) - { - QJsonArray fpJson; - - for(const auto v: fp) - fpJson.append(v); - - return fpJson; - } - - QJsonObject toJson(const PersonFingerprint& fp) - { - assert(fp.id().valid() == false); - - QJsonObject fpJson; - fpJson["fingerprint"] = toJson(fp.fingerprint()); - - return fpJson; - } - - QJsonObject toJson(const FaceInfo& face) - { - QJsonObject faceJson; - faceJson["face"] = toJson(face.face); - faceJson["fingerprint"] = toJson(face.fingerprint); - - return faceJson; - } - - QJsonArray toJson(const std::vector& faces) - { - QJsonArray facesJson; - for (auto& face: faces) - facesJson.append(toJson(face)); - - return facesJson; - } - - std::vector fromJson(const QJsonDocument& doc) - { - std::vector result; - - const QJsonArray faceInfos = doc.array(); - for (const QJsonValue faceValue: faceInfos) - { - const QJsonObject faceInfoObject = faceValue.toObject(); - const QJsonObject faceObject = faceInfoObject["face"].toObject(); - const QJsonObject fingerprintObject = faceInfoObject["fingerprint"].toObject(); - const QJsonObject faceRect = faceObject["rect"].toObject(); - const QJsonArray personFingerprintObject = fingerprintObject["fingerprint"].toArray(); - - const QRect rect(faceRect["x"].toInt(), faceRect["y"].toInt(), faceRect["w"].toInt(), faceRect["h"].toInt()); - Person::Fingerprint fingerprint; - std::ranges::transform(personFingerprintObject, std::back_inserter(fingerprint), [](const auto v){return v.toDouble();}); - const Photo::Id ph_id(faceObject["ph_id"]); - - FaceInfo fi(ph_id, rect); - fi.fingerprint = PersonFingerprint({}, fingerprint); - fi.person = PersonName(); - - result.push_back(fi); - } - - return result; - } - - bool wasPhotoAnalyzedAndHasNoFaces(Database::IDatabase& db, const Photo::Id& ph_id) { return evaluate(db, [ph_id](Database::IBackend& backend) @@ -247,7 +180,7 @@ namespace { const auto fingerprint = face_recognition.getFingerprint(image, faceInfo.face.rect); - faceInfo.fingerprint = fingerprint; + faceInfo.fingerprint = PersonFingerprint(fingerprint); } } @@ -372,8 +305,16 @@ namespace if (blob.isEmpty() == false) { const QJsonDocument json = QJsonDocument::fromJson(blob); + const std::vector storage = JSon::deserialize>(json); + + for (const auto& faceData: storage) + { + FaceInfo fi(faceData.ph_id, faceData.position); + fi.fingerprint = PersonFingerprint(faceData.fingerprint); + fi.person = PersonName(faceData.name); - result = fromJson(json); + result.push_back(fi); + } } else { @@ -381,7 +322,7 @@ namespace const auto detected_faces = detectFaces(image, logger); std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) { - return FaceInfo(id, rect);; + return FaceInfo(id, rect); }); //calculate fingerprints @@ -403,8 +344,13 @@ namespace } else { - // store detected, but not confirmed by user faces as blob in database for cache - const QJsonDocument json(toJson(result)); + std::vector storage; + + for (const auto& faceInfo: result) + storage.emplace_back(faceInfo.face.rect, faceInfo.fingerprint.fingerprint(), faceInfo.person.name(), faceInfo.face.ph_id); + + // store detected, but not confirmed by user, faces as blob in database for cache + const QJsonDocument json = JSon::serialize(storage); execute(db, [id, blob = json.toJson()](Database::IBackend& backend) { diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 656864cfa5..4c3d224257 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -48,6 +48,15 @@ class FaceEditor std::vector> getFacesFor(const Photo::Id &); + // internal usage + struct CalculatedData + { + QRect position; + Person::Fingerprint fingerprint; + QString name; + Photo::Id ph_id; + }; + private: std::unique_ptr m_logger; Database::IDatabase& m_db; From a6aa98cc6c81a90866eb149875340fe0650d60df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 29 Sep 2023 21:50:53 +0200 Subject: [PATCH 101/151] Remove static_assert as it does not work on clang (always fails) --- src/core/json_serializer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/json_serializer.hpp b/src/core/json_serializer.hpp index 9dc96c1379..f460bbfe50 100644 --- a/src/core/json_serializer.hpp +++ b/src/core/json_serializer.hpp @@ -66,7 +66,7 @@ namespace JSon return object; } else - static_assert(false, "Unexpected type"); + assert(!"Unexpected type"); } From 3277e921712e4a5d65468b88a30aaaa4be9a17f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 29 Sep 2023 22:09:46 +0200 Subject: [PATCH 102/151] QJsonValueConstRef is unknown with older Qts --- src/core/unit_tests/json_serializer_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/unit_tests/json_serializer_tests.cpp b/src/core/unit_tests/json_serializer_tests.cpp index 93ab13bf48..5fc25605c0 100644 --- a/src/core/unit_tests/json_serializer_tests.cpp +++ b/src/core/unit_tests/json_serializer_tests.cpp @@ -67,7 +67,7 @@ namespace JSon { IJK ijk; - std::transform(json.begin(), json.end(), std::back_inserter(ijk.a), [](const QJsonValueConstRef& value) {return value.toInt();}); + std::transform(json.begin(), json.end(), std::back_inserter(ijk.a), [](const auto& value) {return value.toInt();}); return ijk; } From 4a41b5803167c176eed4668e5606b1c3f95be9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 30 Sep 2023 16:23:29 +0200 Subject: [PATCH 103/151] Drop unused member --- src/gui/desktop/utils/people_editor.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 87affc626e..b6e47a70ad 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -207,13 +207,12 @@ namespace class FacesSaver { public: - FacesSaver(Database::IDatabase &, ICoreFactoryAccessor &); + FacesSaver(Database::IDatabase &); ~FacesSaver(); void store(FaceInfo &); private: - ICoreFactoryAccessor& m_core; Database::IDatabase& m_db; std::vector m_people; @@ -415,7 +414,7 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) m_logger, id); - auto storage = std::make_shared(m_db, m_core); + auto storage = std::make_shared(m_db); std::vector> result; @@ -428,9 +427,8 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) } -FacesSaver::FacesSaver(Database::IDatabase& db, ICoreFactoryAccessor& core) - : m_core(core) - , m_db(db) +FacesSaver::FacesSaver(Database::IDatabase& db) + : m_db(db) { m_people = fetchPeople(); } From 87c97c11372c5fe9ee95795fac57c014144b5bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 30 Sep 2023 16:31:32 +0200 Subject: [PATCH 104/151] Extract caching to functions --- src/gui/desktop/utils/people_editor.cpp | 79 ++++++++++++++----------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index b6e47a70ad..fd5d1bcb9b 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -278,6 +278,49 @@ namespace }); } + std::vector loadCachedFacesFor(Database::IDatabase& db, const Photo::Id& id) + { + std::vector result; + + const QByteArray blob = evaluate(db, [id](Database::IBackend& backend) + { + return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); + }); + + if (blob.isEmpty() == false) + { + const QJsonDocument json = QJsonDocument::fromJson(blob); + const std::vector storage = JSon::deserialize>(json); + + for (const auto& faceData: storage) + { + FaceInfo fi(faceData.ph_id, faceData.position); + fi.fingerprint = PersonFingerprint(faceData.fingerprint); + fi.person = PersonName(faceData.name); + + result.push_back(fi); + } + } + + return result; + } + + void cacheFacesFor(Database::IDatabase& db, const Photo::Id& id, const std::vector& result) + { + std::vector storage; + + for (const auto& faceInfo: result) + storage.emplace_back(faceInfo.face.rect, faceInfo.fingerprint.fingerprint(), faceInfo.person.name(), faceInfo.face.ph_id); + + // store detected, but not confirmed by user, faces as blob in database for cache + const QJsonDocument json = JSon::serialize(storage); + + execute(db, [id, blob = json.toJson()](Database::IBackend& backend) + { + backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + }); + } + std::vector findFaces(Database::IDatabase& db, const OrientedImage& image, const std::unique_ptr& logger, @@ -296,26 +339,9 @@ namespace if (list_of_faces.empty()) { // check in cache - const QByteArray blob = evaluate(db, [id](Database::IBackend& backend) - { - return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); - }); - - if (blob.isEmpty() == false) - { - const QJsonDocument json = QJsonDocument::fromJson(blob); - const std::vector storage = JSon::deserialize>(json); - - for (const auto& faceData: storage) - { - FaceInfo fi(faceData.ph_id, faceData.position); - fi.fingerprint = PersonFingerprint(faceData.fingerprint); - fi.person = PersonName(faceData.name); + result = loadCachedFacesFor(db, id); - result.push_back(fi); - } - } - else + if (result.empty()) { // analyze photo - look for faces const auto detected_faces = detectFaces(image, logger); @@ -342,20 +368,7 @@ namespace }); } else - { - std::vector storage; - - for (const auto& faceInfo: result) - storage.emplace_back(faceInfo.face.rect, faceInfo.fingerprint.fingerprint(), faceInfo.person.name(), faceInfo.face.ph_id); - - // store detected, but not confirmed by user, faces as blob in database for cache - const QJsonDocument json = JSon::serialize(storage); - - execute(db, [id, blob = json.toJson()](Database::IBackend& backend) - { - return backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); - }); - } + cacheFacesFor(db, id, result); } } else // data in db just use it From f271d750b5a747497660ce372e7d488784617536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 30 Sep 2023 21:56:56 +0200 Subject: [PATCH 105/151] Use name for blob types instead of a number --- .../backends/memory_backend/memory_backend.cpp | 6 +++--- .../backends/memory_backend/memory_backend.hpp | 4 ++-- .../backends/sql_backends/sql_backend.cpp | 14 +++++++------- .../backends/sql_backends/sql_backend.hpp | 4 ++-- src/database/ibackend.hpp | 10 ++-------- .../unit_tests_for_backends/thumbnails_tests.cpp | 16 ++++++++++------ src/gui/desktop/utils/people_editor.cpp | 6 ++++-- src/gui/desktop/utils/thumbnail_manager.cpp | 8 ++++++-- .../utils/thumbnails_manager_tests.cpp | 9 +++++++-- src/unit_tests_utils/mock_backend.hpp | 4 ++-- 10 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index 7bf1414e3c..3bfb81040f 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -38,7 +38,7 @@ namespace Database std::set> m_peopleNames; PeopleContainer m_peopleInfo; std::vector m_logEntries; - std::map, QByteArray> m_blobs; + std::map, QByteArray> m_blobs; int m_nextPhotoId = 0; int m_nextPersonName = 0; @@ -398,13 +398,13 @@ namespace Database } - void MemoryBackend::writeBlob(const Photo::Id& id, BlobType bt, const QByteArray& blob) + void MemoryBackend::writeBlob(const Photo::Id& id, const QString& bt, const QByteArray& blob) { m_db->m_blobs[{id, bt}] = blob; } - QByteArray MemoryBackend::readBlob(const Photo::Id& id, BlobType bt) + QByteArray MemoryBackend::readBlob(const Photo::Id& id, const QString& bt) { return m_db->m_blobs[{id, bt}]; } diff --git a/src/database/backends/memory_backend/memory_backend.hpp b/src/database/backends/memory_backend/memory_backend.hpp index 2219a93446..bbd76424e4 100644 --- a/src/database/backends/memory_backend/memory_backend.hpp +++ b/src/database/backends/memory_backend/memory_backend.hpp @@ -49,8 +49,8 @@ namespace Database std::optional get(const Photo::Id& id, const QString& name) override; void setBits(const Photo::Id& id, const QString& name, int bits) override final; void clearBits(const Photo::Id& id, const QString& name, int bits) override final; - void writeBlob(const Photo::Id &, BlobType, const QByteArray &) override; - QByteArray readBlob(const Photo::Id &, BlobType) override; + void writeBlob(const Photo::Id &, const QString& bt, const QByteArray &) override; + QByteArray readBlob(const Photo::Id &, const QString& bt) override; std::vector markStagedAsReviewed() override; BackendStatus init(const ProjectInfo &) override; void closeConnections() override; diff --git a/src/database/backends/sql_backends/sql_backend.cpp b/src/database/backends/sql_backends/sql_backend.cpp index 709a54b357..859c5df4a2 100644 --- a/src/database/backends/sql_backends/sql_backend.cpp +++ b/src/database/backends/sql_backends/sql_backend.cpp @@ -512,24 +512,24 @@ namespace Database } - void ASqlBackend::writeBlob(const Photo::Id& id, BlobType bt, const QByteArray& blob) + void ASqlBackend::writeBlob(const Photo::Id& id, const QString& bt, const QByteArray& blob) { UpdateQueryData data(TAB_BLOBS); data.addCondition("photo_id", QString::number(id.value())); - data.addCondition("type", QString::number(static_cast(bt))); + data.addCondition("type", bt); data.setColumns("photo_id", "type", "data"); - data.setValues(QString::number(id.value()), QString::number(static_cast(bt)), blob); + data.setValues(QString::number(id.value()), bt, blob); updateOrInsert(data); } - QByteArray ASqlBackend::readBlob(const Photo::Id& id, BlobType bt) + QByteArray ASqlBackend::readBlob(const Photo::Id& id, const QString& bt) { - const QString blobQuery = QString("SELECT data FROM %1 WHERE photo_id=%2 AND type=%3") + const QString blobQuery = QString("SELECT data FROM %1 WHERE photo_id=%2 AND type='%3'") .arg(TAB_BLOBS) .arg(QString::number(id.value())) - .arg(QString::number(static_cast(bt))); + .arg(bt); QSqlDatabase db = QSqlDatabase::database(m_connectionName); QSqlQuery query(db); @@ -743,7 +743,7 @@ namespace Database // move thumbnails to blob table const QString copy_thumbnails = QString("INSERT INTO blobs(photo_id, type, data) SELECT photo_id, %1 AS type, data FROM thumbnails") - .arg(static_cast(IBackend::BlobType::Thumbnail)); + .arg("thumbnail"); status = m_executor.exec(copy_thumbnails, &query); if (status == false) diff --git a/src/database/backends/sql_backends/sql_backend.hpp b/src/database/backends/sql_backends/sql_backend.hpp index 74a2b1e31f..79ea5277b4 100644 --- a/src/database/backends/sql_backends/sql_backend.hpp +++ b/src/database/backends/sql_backends/sql_backend.hpp @@ -154,8 +154,8 @@ namespace Database void setBits(const Photo::Id& id, const QString& name, int bits) override final; void clearBits(const Photo::Id& id, const QString& name, int bits) override final; - void writeBlob(const Photo::Id &, BlobType, const QByteArray& blob) override; - QByteArray readBlob(const Photo::Id &, BlobType) override; + void writeBlob(const Photo::Id &, const QString& bt, const QByteArray& blob) override; + QByteArray readBlob(const Photo::Id &, const QString& bt) override; std::vector markStagedAsReviewed() override final; // diff --git a/src/database/ibackend.hpp b/src/database/ibackend.hpp index c2e210aa41..eed6442349 100644 --- a/src/database/ibackend.hpp +++ b/src/database/ibackend.hpp @@ -90,12 +90,6 @@ namespace Database */ struct DATABASE_EXPORT IBackend: public QObject { - enum class BlobType - { - Thumbnail = 0, - BatchFaceFetcher = 1, - }; - virtual ~IBackend() = default; /** \brief Add photos to database @@ -193,7 +187,7 @@ namespace Database * @arg bt blob type * @arg blob raw data */ - virtual void writeBlob(const Photo::Id& id, BlobType bt, const QByteArray& blob) = 0; + virtual void writeBlob(const Photo::Id& id, const QString& bt, const QByteArray& blob) = 0; /** * @brief Read blob of type @p bt associated with photo @p id @@ -201,7 +195,7 @@ namespace Database * @arg bt blob type * @return raw data */ - virtual QByteArray readBlob(const Photo::Id& id, BlobType bt) = 0; + virtual QByteArray readBlob(const Photo::Id& id, const QString& bt) = 0; /** * \brief mark all staged photos as reviewed. diff --git a/src/database/unit_tests_for_backends/thumbnails_tests.cpp b/src/database/unit_tests_for_backends/thumbnails_tests.cpp index e5990fd24e..124abd0dc0 100644 --- a/src/database/unit_tests_for_backends/thumbnails_tests.cpp +++ b/src/database/unit_tests_for_backends/thumbnails_tests.cpp @@ -1,6 +1,10 @@ #include "common.hpp" +namespace +{ + const QString ThumbnailBlob = "thumbnail"; +} template struct ThumbnailsTest: DatabaseTest @@ -20,8 +24,8 @@ TYPED_TEST(ThumbnailsTest, storesThumbnail) this->m_backend->addPhotos(photos); const auto id = photos.front().getId(); - this->m_backend->writeBlob(id, Database::IBackend::BlobType::Thumbnail, QByteArray("thumbnail")); - const QByteArray thumbnail = this->m_backend->readBlob(id, Database::IBackend::BlobType::Thumbnail); + this->m_backend->writeBlob(id, ThumbnailBlob, QByteArray("thumbnail")); + const QByteArray thumbnail = this->m_backend->readBlob(id, ThumbnailBlob); EXPECT_EQ(thumbnail, "thumbnail"); } @@ -36,9 +40,9 @@ TYPED_TEST(ThumbnailsTest, thumbnailOverride) this->m_backend->addPhotos(photos); const auto id = photos.front().getId(); - this->m_backend->writeBlob(id, Database::IBackend::BlobType::Thumbnail, QByteArray("thumbnail")); - this->m_backend->writeBlob(id, Database::IBackend::BlobType::Thumbnail, QByteArray("thumbnail2")); - const QByteArray thumbnail = this->m_backend->readBlob(id, Database::IBackend::BlobType::Thumbnail); + this->m_backend->writeBlob(id, ThumbnailBlob, QByteArray("thumbnail")); + this->m_backend->writeBlob(id, ThumbnailBlob, QByteArray("thumbnail2")); + const QByteArray thumbnail = this->m_backend->readBlob(id, ThumbnailBlob); EXPECT_EQ(thumbnail, "thumbnail2"); } @@ -52,7 +56,7 @@ TYPED_TEST(ThumbnailsTest, emptyResultWhenMissing) this->m_backend->addPhotos(photos); const auto id = photos.front().getId(); - const QByteArray thumbnail = this->m_backend->readBlob(id, Database::IBackend::BlobType::Thumbnail); + const QByteArray thumbnail = this->m_backend->readBlob(id, ThumbnailBlob); EXPECT_TRUE(thumbnail.isEmpty()); } diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index fd5d1bcb9b..c1373b77ec 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -62,6 +62,8 @@ namespace JSon namespace { + constexpr auto CacheBlob = "people_editor_cache"; + struct FaceInfo { PersonInfo face; @@ -284,7 +286,7 @@ namespace const QByteArray blob = evaluate(db, [id](Database::IBackend& backend) { - return backend.readBlob(id, Database::IBackend::BlobType::BatchFaceFetcher); + return backend.readBlob(id, CacheBlob); }); if (blob.isEmpty() == false) @@ -317,7 +319,7 @@ namespace execute(db, [id, blob = json.toJson()](Database::IBackend& backend) { - backend.writeBlob(id, Database::IBackend::BlobType::BatchFaceFetcher, blob); + backend.writeBlob(id, CacheBlob, blob); }); } diff --git a/src/gui/desktop/utils/thumbnail_manager.cpp b/src/gui/desktop/utils/thumbnail_manager.cpp index 6e0116621d..0b28c223b1 100644 --- a/src/gui/desktop/utils/thumbnail_manager.cpp +++ b/src/gui/desktop/utils/thumbnail_manager.cpp @@ -25,6 +25,10 @@ #include #include "ithumbnails_cache.hpp" +namespace +{ + constexpr auto BlobType = "thumbnail"; +} using namespace std::placeholders; @@ -65,7 +69,7 @@ void ThumbnailManager::fetch(const Photo::Id& id, const QSize& desired_size, con if (m_db) // load thumbnail from db dbThumb = evaluate(*m_db, [id](Database::IBackend& backend) { - return backend.readBlob(id, Database::IBackend::BlobType::Thumbnail); + return backend.readBlob(id, BlobType); }); QImage baseThumbnail; @@ -92,7 +96,7 @@ void ThumbnailManager::fetch(const Photo::Id& id, const QSize& desired_size, con execute(*m_db, [id, dbThumb](Database::IBackend& backend) { - backend.writeBlob(id, Database::IBackend::BlobType::Thumbnail, dbThumb); + backend.writeBlob(id, BlobType, dbThumb); }); } } diff --git a/src/gui/unit_tests/utils/thumbnails_manager_tests.cpp b/src/gui/unit_tests/utils/thumbnails_manager_tests.cpp index c0dc7cecc0..bea53bbb31 100644 --- a/src/gui/unit_tests/utils/thumbnails_manager_tests.cpp +++ b/src/gui/unit_tests/utils/thumbnails_manager_tests.cpp @@ -21,6 +21,11 @@ using testing::NiceMock; using testing::Return; using testing::ReturnRef; +namespace +{ + const QString ThumbnailBlob = "thumbnail"; +} + struct NullCache: IThumbnailsCache { std::optional find(const Photo::Id &, const IThumbnailsCache::ThumbnailParameters &) override @@ -141,7 +146,7 @@ TEST_F(ThumbnailManagerTest, generatedThumbnailsIsBeingCached) .WillRepeatedly(Return(img)); // database behavior (thumbnail storage) - EXPECT_CALL(backend, writeBlob(id, Database::IBackend::BlobType::Thumbnail, _)).Times(1); + EXPECT_CALL(backend, writeBlob(id, ThumbnailBlob, _)).Times(1); ThumbnailManager tm(executor, generator, cache, EmptyLogger{}); tm.setDatabaseCache(&db); @@ -161,7 +166,7 @@ TEST_F(ThumbnailManagerTest, doNotGenerateThumbnailFoundInCache) NiceMock cache; EXPECT_CALL(cache, find(id, IThumbnailsCache::ThumbnailParameters(QSize(height, height)))).Times(1).WillOnce(Return(img)); - EXPECT_CALL(backend, writeBlob(id, Database::IBackend::BlobType::Thumbnail, _)).Times(0); + EXPECT_CALL(backend, writeBlob(id, ThumbnailBlob, _)).Times(0); MockThumbnailsGenerator generator; diff --git a/src/unit_tests_utils/mock_backend.hpp b/src/unit_tests_utils/mock_backend.hpp index 9bf2698e86..4ab9ff3f0c 100644 --- a/src/unit_tests_utils/mock_backend.hpp +++ b/src/unit_tests_utils/mock_backend.hpp @@ -27,8 +27,8 @@ struct MockBackend: public Database::IBackend MOCK_METHOD(std::optional, get, (const Photo::Id &, const QString &), (override)); MOCK_METHOD(void, setBits, (const Photo::Id& id, const QString& name, int bits), (override)); MOCK_METHOD(void, clearBits, (const Photo::Id& id, const QString& name, int bits), (override)); - MOCK_METHOD(void, writeBlob, (const Photo::Id &, BlobType, const QByteArray &), (override)); - MOCK_METHOD(QByteArray, readBlob, (const Photo::Id &, BlobType), (override)); + MOCK_METHOD(void, writeBlob, (const Photo::Id &, const QString& bt, const QByteArray &), (override)); + MOCK_METHOD(QByteArray, readBlob, (const Photo::Id &, const QString& bt), (override)); MOCK_METHOD(std::vector, markStagedAsReviewed, (), (override)); MOCK_METHOD(Database::BackendStatus, init, (const Database::ProjectInfo &), (override)); MOCK_METHOD(void, closeConnections, (), (override)); From 9a03b94403a5d0d25e87446bbcb034e67bdbcc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 4 Oct 2023 21:48:50 +0200 Subject: [PATCH 106/151] Notify on editing finish --- src/gui/desktop/QmlItems/LineEdit.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/desktop/QmlItems/LineEdit.qml b/src/gui/desktop/QmlItems/LineEdit.qml index 5c32e46bdc..d7014735ee 100644 --- a/src/gui/desktop/QmlItems/LineEdit.qml +++ b/src/gui/desktop/QmlItems/LineEdit.qml @@ -12,11 +12,15 @@ Rectangle { border.width: 1 radius: 2 + signal editingFinished() + TextInput { id: input anchors.fill: parent anchors.margins: 4 selectByMouse: true + + onEditingFinished: parent.editingFinished() } } From c33582aa7ed812bafd6ef896fbbda2ac00edb76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 4 Oct 2023 21:49:13 +0200 Subject: [PATCH 107/151] Store name --- .../quick_items/Views/BatchFaceDetection.qml | 3 +++ src/gui/desktop/utils/batch_face_detector.cpp | 20 +++++++++++++++++++ src/gui/desktop/utils/batch_face_detector.hpp | 1 + tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 9371054503..3456f898e5 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -47,6 +47,7 @@ Item { required property var decoration required property var display required property var index + required property var model width: 170 height: 200 @@ -108,6 +109,8 @@ Item { anchors.horizontalCenter: parent.horizontalCenter text: display width: parent.width - 40 + + onEditingFinished: model.edit = text } } } diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index dcaa73224c..b25c82e3c1 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -110,6 +110,26 @@ QVariant BatchFaceDetector::data(const QModelIndex& idx, int role) const } +bool BatchFaceDetector::setData(const QModelIndex& idx, const QVariant& value, int role) +{ + assert(idx.row() < static_cast(m_faces.size())); + assert(idx.column() == 0); + const QString name = value.toString(); + const size_t row = static_cast(idx.row()); + + auto& face = m_faces[row].faceData; + + if (role == Qt::EditRole && name != face->name()) + { + face->setName(name); + + return true; + } + else + return false; +} + + void BatchFaceDetector::accept(int idx) { diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 4d09edbf17..8b085d6728 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -34,6 +34,7 @@ class BatchFaceDetector: public QAbstractListModel int rowCount(const QModelIndex &) const override; QVariant data(const QModelIndex &, int) const override; + bool setData(const QModelIndex &, const QVariant &, int) override; Q_INVOKABLE void accept(int); Q_INVOKABLE void drop(int); diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 6e3fb9f02d..87d44e361e 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 12aa8dbe75..6151ff660c 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From 1a3d34b0f73f07c04eacae4a52b13dd8771356d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 7 Oct 2023 22:05:41 +0200 Subject: [PATCH 108/151] Drop dead includes --- src/database/group.hpp | 2 -- src/gui/desktop/models/flat_model.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/database/group.hpp b/src/database/group.hpp index 53498197ac..8be832d204 100644 --- a/src/database/group.hpp +++ b/src/database/group.hpp @@ -2,8 +2,6 @@ #ifndef GROUP_HPP #define GROUP_HPP -#include - #include #include "database_export.h" diff --git a/src/gui/desktop/models/flat_model.cpp b/src/gui/desktop/models/flat_model.cpp index cd6106bac4..0d6906f572 100644 --- a/src/gui/desktop/models/flat_model.cpp +++ b/src/gui/desktop/models/flat_model.cpp @@ -17,8 +17,6 @@ #include "flat_model.hpp" -#include - #include #include #include From fe222b97c59fc1a9dd9338d3cd18757ee8da4758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 7 Oct 2023 22:08:27 +0200 Subject: [PATCH 109/151] Simplify lazy_ptr and extend it by new, qt property based, maker --- src/core/lazy_ptr.hpp | 89 ++++++++++++------- src/core/unit_tests/lazy_ptr_tests.cpp | 19 +++- .../backends/sql_backends/sql_backend.hpp | 2 +- .../dlib_face_recognition_api.cpp | 10 ++- 4 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/core/lazy_ptr.hpp b/src/core/lazy_ptr.hpp index 72dd152d6e..53fb18ad1e 100644 --- a/src/core/lazy_ptr.hpp +++ b/src/core/lazy_ptr.hpp @@ -3,8 +3,12 @@ #define LAZY_PTR_HPP_INCLUDED #include +#include +#include +#include -template + +template class lazy_ptr { public: @@ -14,10 +18,20 @@ class lazy_ptr } + template explicit lazy_ptr(const C& constructor) - : m_constructor(constructor) + : m_constructor() { - + if constexpr (std::is_pointer_v) + m_constructor = [constructor]() -> std::unique_ptr + { + return std::unique_ptr(constructor()); + }; + else + m_constructor = [constructor]() -> std::unique_ptr + { + return std::make_unique(constructor()); + }; } T* operator->() @@ -31,46 +45,61 @@ class lazy_ptr } private: + using C = std::function()>; std::unique_ptr m_object; C m_constructor; - // used when C::operator() returns raw pointer - struct PtrCreator - { - std::unique_ptr operator()(C& constructor) const - { - return std::unique_ptr(constructor()); - } - }; - - // used when C::operator() returns value - struct CopyCreator - { - std::unique_ptr operator()(C& constructor) const - { - return std::make_unique(constructor()); - } - }; - T* get() { if (m_object.get() == nullptr) - { - typedef typename std::conditional::value, PtrCreator, CopyCreator>::type Creator; - const Creator creator; - - m_object = creator(m_constructor); - } + m_object = m_constructor(); return m_object.get(); } }; +/** + * @brief Construct lazy_ptr which will be initialized with result of C() called on first use. + */ template -lazy_ptr make_lazy_ptr(const C& c) +requires std::invocable +lazy_ptr make_lazy_ptr(const C& c) { - return lazy_ptr(c); + return lazy_ptr(c); +} + +namespace lazy_ptr_impl +{ + template + T getQtPropertyValue(QObject* obj, const char* name) + { + const QVariant valueVariant = obj->property(name); + + return valueVariant.value(); + } +} + +/** + * @brief Construct lazy_ptr which will be initialized (on first use) with values of Qt's properties passed under Args... which are expected to be const char * + * + * Use case for this function is a class which's member depends on values of properties marked as REQUIRED. + * As such properties are not set at construction time, but soon after, some members may need to wait for + * them to be set, and therfore need to me constructed later. + */ +template +requires (std::is_same_v && ...) +auto make_lazy_ptr(QObject* obj, Args... args) +{ + assert(obj != nullptr); + static_assert(sizeof...(Types) == sizeof...(Args), "Number of arguments and their types needs to be equal"); + + auto c = [obj, args...]() -> T + { + return T(lazy_ptr_impl::getQtPropertyValue(obj, args)...); + }; + + return make_lazy_ptr(c); } -#endif // LAZY_PTR_HPP_INCLUDED +#endif diff --git a/src/core/unit_tests/lazy_ptr_tests.cpp b/src/core/unit_tests/lazy_ptr_tests.cpp index f0b4491422..f22ab170b5 100644 --- a/src/core/unit_tests/lazy_ptr_tests.cpp +++ b/src/core/unit_tests/lazy_ptr_tests.cpp @@ -24,7 +24,7 @@ TEST(LazyPtrTest, noConstructionWhenNotNeeded) ConstructorMock constructor; EXPECT_CALL(constructor, build).Times(0); - make_lazy_ptr(std::ref(constructor)); + make_lazy_ptr([&constructor]() {return constructor();} ); } @@ -33,7 +33,7 @@ TEST(LazyPtrTest, onlyOneConstructionWhenNeeded) ConstructorMock constructor; EXPECT_CALL(constructor, build).Times(1).WillOnce(Return(new int)); - lazy_ptr ptr = make_lazy_ptr(std::ref(constructor)); + lazy_ptr ptr = make_lazy_ptr([&constructor]() {return constructor();} ); *ptr = 0; *ptr = 5; @@ -45,8 +45,21 @@ TEST(LazyPtrTest, onlyOneConstructionWhenNeededForComplexType) ConstructorMock> constructor; EXPECT_CALL(constructor, build).Times(1).WillOnce(Return(new std::pair)); - lazy_ptr ptr = make_lazy_ptr>(std::ref(constructor)); + lazy_ptr ptr = make_lazy_ptr>([&constructor]() {return constructor();} ); ptr->first = 0; ptr->second = 5.0; } + + +TEST(LazyPtrTest, qtPropertiesConstructor) +{ + QObject obj; + + using ObjectTakingQStringInContructor = QString; + + auto lazy_ptr = make_lazy_ptr(&obj, "objectName"); + obj.setObjectName("hello"); + + EXPECT_EQ(*lazy_ptr, "hello"); +} diff --git a/src/database/backends/sql_backends/sql_backend.hpp b/src/database/backends/sql_backends/sql_backend.hpp index 79ea5277b4..75a4b34b2f 100644 --- a/src/database/backends/sql_backends/sql_backend.hpp +++ b/src/database/backends/sql_backends/sql_backend.hpp @@ -130,7 +130,7 @@ namespace Database std::unique_ptr m_groupOperator; std::unique_ptr m_photoOperator; std::unique_ptr m_photoChangeLogOperator; - lazy_ptr> m_peopleInfoAccessor; + lazy_ptr m_peopleInfoAccessor; NotificationsAccumulator m_notificationsAccumulator; mutable TransactionManager m_tr_db; QString m_connectionName; diff --git a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp index 74fc644ab9..9c000af28b 100644 --- a/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp +++ b/src/face_recognition/dlib_wrapper/dlib_face_recognition_api.cpp @@ -135,8 +135,8 @@ namespace dlib_api struct FaceLocator::Data { - lazy_ptr cnn_face_detector; - lazy_ptr hog_face_detector; + lazy_ptr cnn_face_detector; + lazy_ptr hog_face_detector; std::unique_ptr logger; const bool cuda_available; @@ -322,14 +322,16 @@ namespace dlib_api { Data(ILogger* log) : face_encoder( modelPath().toStdString() ) + , predictor_5_point(ObjectDeserializer()) + , predictor_68_point(ObjectDeserializer()) , logger(log) { } face_recognition_model_v1 face_encoder; - lazy_ptr> predictor_5_point; - lazy_ptr> predictor_68_point; + lazy_ptr predictor_5_point; + lazy_ptr predictor_68_point; ILogger* logger; }; From 43a53ac751b7f6af029aee1e254b3ea1fbb0ab97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 8 Oct 2023 12:32:23 +0200 Subject: [PATCH 110/151] Handle unique_ptrs --- src/core/lazy_ptr.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/lazy_ptr.hpp b/src/core/lazy_ptr.hpp index 53fb18ad1e..7b59a3b4f4 100644 --- a/src/core/lazy_ptr.hpp +++ b/src/core/lazy_ptr.hpp @@ -13,16 +13,19 @@ class lazy_ptr { public: lazy_ptr() - : m_constructor() { } template explicit lazy_ptr(const C& constructor) - : m_constructor() { - if constexpr (std::is_pointer_v) + if constexpr (std::is_same_v>) + m_constructor = [constructor]() -> std::unique_ptr + { + return constructor(); + }; + else if constexpr (std::is_pointer_v) m_constructor = [constructor]() -> std::unique_ptr { return std::unique_ptr(constructor()); From e3eb208c38ee42264d3c81dd8dd74e2a016d8f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 8 Oct 2023 12:34:15 +0200 Subject: [PATCH 111/151] Build one FaceEditor --- src/gui/desktop/utils/batch_face_detector.cpp | 12 +++++++++--- src/gui/desktop/utils/batch_face_detector.hpp | 3 +++ tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index b25c82e3c1..2a73a38fe2 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -9,6 +9,14 @@ #include + +BatchFaceDetector::BatchFaceDetector() + : m_faceEditor(make_lazy_ptr([this]{ return std::make_unique(*this->db(), *this->core(), this->m_logger); })) +{ + +} + + BatchFaceDetector::~BatchFaceDetector() { // db client should be destroyed by now @@ -214,9 +222,7 @@ std::optional BatchFaceDetector::getNextId() void BatchFaceDetector::loadFacesFromPhoto(const Photo::Id& id) { - FaceEditor fe(m_dbClient->db(), *m_core, m_logger); - - auto faces = fe.getFacesFor(id); + auto faces = m_faceEditor->getFacesFor(id); std::vector facesDetails; // prepare details for model diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index 8b085d6728..de3112d1a9 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "models/aphoto_data_model.hpp" #include "people_editor.hpp" @@ -22,6 +23,7 @@ class BatchFaceDetector: public QAbstractListModel Q_PROPERTY(APhotoDataModel* photos_model WRITE setPhotosModel READ photosModel) public: + BatchFaceDetector(); ~BatchFaceDetector(); void setPhotosModel(APhotoDataModel *); @@ -62,6 +64,7 @@ class BatchFaceDetector: public QAbstractListModel std::vector m_faces; std::unique_ptr m_logger; std::unique_ptr m_dbClient; + lazy_ptr m_faceEditor; APhotoDataModel* m_photosModel = nullptr; ICoreFactoryAccessor* m_core = nullptr; std::shared_ptr m_photosProcessingProcess; diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 87d44e361e..2ea9fdb841 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 6151ff660c..8e14eb7f9e 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 309307a275e8aa25598cd887aada6654e2d1d14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 8 Oct 2023 19:35:40 +0200 Subject: [PATCH 112/151] extract private classes and struct to separate file --- src/gui/desktop/utils/CMakeLists.txt | 2 +- .../implementation/people_editor_impl.hpp | 44 +++++++++++++++++++ src/gui/desktop/utils/people_editor.cpp | 42 +++++++----------- src/gui/desktop/utils/people_editor.hpp | 15 ++----- 4 files changed, 64 insertions(+), 39 deletions(-) create mode 100644 src/gui/desktop/utils/implementation/people_editor_impl.hpp diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 5888f25e0f..95778feba3 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -87,7 +87,7 @@ ReflectFiles( TARGET gui_utils SOURCES - people_editor.hpp + implementation/people_editor_impl.hpp ) target_sources(gui_utils PRIVATE ${ReflectionFiles}) diff --git a/src/gui/desktop/utils/implementation/people_editor_impl.hpp b/src/gui/desktop/utils/implementation/people_editor_impl.hpp new file mode 100644 index 0000000000..2fd367fdc8 --- /dev/null +++ b/src/gui/desktop/utils/implementation/people_editor_impl.hpp @@ -0,0 +1,44 @@ + +#ifndef PEOPLE_EDITOR_IMPL_HPP_INCLUDED +#define PEOPLE_EDITOR_IMPL_HPP_INCLUDED + +#include +#include + +#include + + +struct CalculatedData +{ + QRect position; + Person::Fingerprint fingerprint; + QString name; + Photo::Id ph_id; +}; + +struct FaceInfo +{ + PersonInfo face; + PersonName person; + PersonFingerprint fingerprint; + + FaceInfo(const Photo::Id& id, const QRect& r) + { + face.ph_id = id; + face.rect = r; + } + + explicit FaceInfo(const PersonInfo& pi) + : face(pi) + { + + } +}; + +struct IFacesSaver +{ + virtual ~IFacesSaver() = default; + virtual void store(FaceInfo &) = 0; +}; + +#endif diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index c1373b77ec..b650a3ff1d 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -16,7 +16,7 @@ */ #include "people_editor.hpp" -#include "people_editor_r++.hpp" +#include "people_editor_impl_r++.hpp" #include #include @@ -34,6 +34,8 @@ #include #include +#include "implementation/people_editor_impl.hpp" + using namespace Database::CommonGeneralFlags; @@ -64,25 +66,6 @@ namespace { constexpr auto CacheBlob = "people_editor_cache"; - struct FaceInfo - { - PersonInfo face; - PersonName person; - PersonFingerprint fingerprint; - - FaceInfo(const Photo::Id& id, const QRect& r) - { - face.ph_id = id; - face.rect = r; - } - - explicit FaceInfo(const PersonInfo& pi) - : face(pi) - { - - } - }; - Person::Fingerprint average_fingerprint(const std::vector& faces) { if (faces.empty()) @@ -206,13 +189,13 @@ namespace } } - class FacesSaver + class FacesSaver: public IFacesSaver { public: FacesSaver(Database::IDatabase &); ~FacesSaver(); - void store(FaceInfo &); + void store(FaceInfo &) override; private: Database::IDatabase& m_db; @@ -228,7 +211,7 @@ namespace struct Face: public IFace { - Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) + Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) : m_faceInfo(fi) , m_image(image) , m_saver(saver) @@ -261,7 +244,7 @@ namespace FaceInfo m_faceInfo; std::shared_ptr m_image; - std::shared_ptr m_saver; + std::shared_ptr m_saver; }; void sortFaces(std::vector& faces) @@ -292,7 +275,7 @@ namespace if (blob.isEmpty() == false) { const QJsonDocument json = QJsonDocument::fromJson(blob); - const std::vector storage = JSon::deserialize>(json); + const std::vector storage = JSon::deserialize>(json); for (const auto& faceData: storage) { @@ -309,7 +292,7 @@ namespace void cacheFacesFor(Database::IDatabase& db, const Photo::Id& id, const std::vector& result) { - std::vector storage; + std::vector storage; for (const auto& faceInfo: result) storage.emplace_back(faceInfo.face.rect, faceInfo.fingerprint.fingerprint(), faceInfo.person.name(), faceInfo.face.ph_id); @@ -429,7 +412,12 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) m_logger, id); - auto storage = std::make_shared(m_db); + auto storage = m_facesSaver.lock(); + if (storage.get() == nullptr) + { + storage = std::make_shared(m_db); + m_facesSaver = storage; + } std::vector> result; diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 4c3d224257..738d8ff3be 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -18,6 +18,7 @@ #ifndef PEOPLEMANIPULATOR_HPP #define PEOPLEMANIPULATOR_HPP +#include #include #include #include @@ -25,8 +26,6 @@ #include -struct ICoreFactoryAccessor; - class IFace { public: @@ -41,6 +40,8 @@ class IFace }; +struct IFacesSaver; + class FaceEditor { public: @@ -48,16 +49,8 @@ class FaceEditor std::vector> getFacesFor(const Photo::Id &); - // internal usage - struct CalculatedData - { - QRect position; - Person::Fingerprint fingerprint; - QString name; - Photo::Id ph_id; - }; - private: + std::weak_ptr m_facesSaver; std::unique_ptr m_logger; Database::IDatabase& m_db; ICoreFactoryAccessor& m_core; From 47631dd66caa503f1c380401af5342a84b6eb8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 8 Oct 2023 19:35:49 +0200 Subject: [PATCH 113/151] Simplify --- src/gui/desktop/utils/batch_face_detector.cpp | 6 +++--- src/gui/desktop/utils/batch_face_detector.hpp | 17 +---------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 2a73a38fe2..0b7c1d3ebf 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -110,9 +110,9 @@ QVariant BatchFaceDetector::data(const QModelIndex& idx, int role) const const size_t row = static_cast(idx.row()); if (role == Qt::DisplayRole) - return m_faces[row].faceData->name(); + return m_faces[row].first->name(); else if (role == Qt::DecorationRole) - return m_faces[row].faceImg; + return m_faces[row].second; else return {}; } @@ -125,7 +125,7 @@ bool BatchFaceDetector::setData(const QModelIndex& idx, const QVariant& value, i const QString name = value.toString(); const size_t row = static_cast(idx.row()); - auto& face = m_faces[row].faceData; + auto& face = m_faces[row].first; if (role == Qt::EditRole && name != face->name()) { diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index de3112d1a9..e9458a0858 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -42,22 +42,7 @@ class BatchFaceDetector: public QAbstractListModel Q_INVOKABLE void drop(int); private: - struct Face - { - Face(std::unique_ptr f, const QImage& img) - : faceData(std::move(f)) - , faceImg(img) - {} - - Face(const Face &) = delete; - Face(Face &&) = default; - - Face& operator=(const Face &) = delete; - Face& operator=(Face &&) = default; - - std::unique_ptr faceData; - QImage faceImg; - }; + using Face = std::pair, QImage>; std::deque m_ids; std::mutex m_idsMtx; From ed82c98e16b87df8d9058df1d669cda59dd1c165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 8 Oct 2023 22:05:19 +0200 Subject: [PATCH 114/151] Be explicit --- src/gui/desktop/utils/people_editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index b650a3ff1d..fe93f3e50d 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -192,7 +192,7 @@ namespace class FacesSaver: public IFacesSaver { public: - FacesSaver(Database::IDatabase &); + explicit FacesSaver(Database::IDatabase &); ~FacesSaver(); void store(FaceInfo &) override; From 95f62d1b1030124db228da2d46378352a099d1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 9 Oct 2023 21:33:14 +0200 Subject: [PATCH 115/151] Extract function --- src/gui/desktop/utils/people_editor.cpp | 33 ++++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index fe93f3e50d..90c9b7ea74 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -189,6 +189,26 @@ namespace } } + std::vector findAndRecognizePeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const std::unique_ptr& logger) + { + std::vector result; + + // analyze photo - look for faces + const auto detected_faces = detectFaces(image, logger); + std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) + { + return FaceInfo(id, rect); + }); + + //calculate fingerprints + calculateMissingFingerprints(result, image, logger); + + //recognize people + recognizePeople(result, db, logger); + + return result; + } + class FacesSaver: public IFacesSaver { public: @@ -328,18 +348,7 @@ namespace if (result.empty()) { - // analyze photo - look for faces - const auto detected_faces = detectFaces(image, logger); - std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) - { - return FaceInfo(id, rect); - }); - - //calculate fingerprints - calculateMissingFingerprints(result, image, logger); - - //recognize people - recognizePeople(result, db, logger); + result = findAndRecognizePeople(image, db, id, logger); if (result.empty()) { From 5d6a26d956e100c04f44feaf771c4b515d6f7ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 9 Oct 2023 21:34:19 +0200 Subject: [PATCH 116/151] Store face on accept --- src/gui/desktop/utils/batch_face_detector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 0b7c1d3ebf..8d904abdcf 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -140,7 +140,7 @@ bool BatchFaceDetector::setData(const QModelIndex& idx, const QVariant& value, i void BatchFaceDetector::accept(int idx) { - + m_faces[idx].first->store(); } From 6054f71dd26007c628e6d8c1ce4557a6705fd7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 9 Oct 2023 22:08:52 +0200 Subject: [PATCH 117/151] Refactor: Make cleanups with loggers --- src/face_recognition/face_recognition.cpp | 4 ++-- src/face_recognition/face_recognition.hpp | 2 +- src/gui/desktop/models/faces_model.cpp | 3 ++- src/gui/desktop/utils/batch_face_detector.cpp | 2 +- src/gui/desktop/utils/people_editor.cpp | 16 ++++++++-------- src/gui/desktop/utils/people_editor.hpp | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/face_recognition/face_recognition.cpp b/src/face_recognition/face_recognition.cpp index 24f74174c5..c6c56f3b80 100644 --- a/src/face_recognition/face_recognition.cpp +++ b/src/face_recognition/face_recognition.cpp @@ -64,8 +64,8 @@ namespace -FaceRecognition::FaceRecognition(const std::unique_ptr& logger) - : m_logger(logger->subLogger("FaceRecognition")) +FaceRecognition::FaceRecognition(const ILogger& logger) + : m_logger(logger.subLogger("FaceRecognition")) { } diff --git a/src/face_recognition/face_recognition.hpp b/src/face_recognition/face_recognition.hpp index 5de292b91d..f430faebd6 100644 --- a/src/face_recognition/face_recognition.hpp +++ b/src/face_recognition/face_recognition.hpp @@ -43,7 +43,7 @@ namespace Database class FACE_RECOGNITION_EXPORT FaceRecognition final { public: - FaceRecognition(const std::unique_ptr& logger); + FaceRecognition(const ILogger& logger); FaceRecognition(const FaceRecognition &) = delete; ~FaceRecognition(); diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index ead81a557e..a934bbacec 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -143,10 +143,11 @@ void FacesModel::initialSetup() runOn(m_core->getTaskExecutor(), [this]() { + const auto logger = m_core->getLoggerFactory().get("FacesModel"); const auto faces = std::make_shared>>(FaceEditor( *m_database, *m_core, - m_core->getLoggerFactory().get("FacesModel")).getFacesFor(m_id)); + *logger).getFacesFor(m_id)); invokeMethod(this, &FacesModel::updateFaceInformation, faces); }); diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index 8d904abdcf..a0dffc7ab1 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -11,7 +11,7 @@ BatchFaceDetector::BatchFaceDetector() - : m_faceEditor(make_lazy_ptr([this]{ return std::make_unique(*this->db(), *this->core(), this->m_logger); })) + : m_faceEditor(make_lazy_ptr([this]{ return std::make_unique(*this->db(), *this->core(), *this->m_logger); })) { } diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 90c9b7ea74..6129eb09be 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -152,12 +152,12 @@ namespace }); } - std::vector detectFaces(const OrientedImage& image, const std::unique_ptr& logger) + std::vector detectFaces(const OrientedImage& image, const ILogger& logger) { return FaceRecognition(logger).fetchFaces(image); } - void calculateMissingFingerprints(std::vector& faces, const OrientedImage& image, const std::unique_ptr& logger) + void calculateMissingFingerprints(std::vector& faces, const OrientedImage& image, const ILogger& logger) { FaceRecognition face_recognition(logger); for (FaceInfo& faceInfo: faces) @@ -169,7 +169,7 @@ namespace } } - void recognizePeople(std::vector& faces, Database::IDatabase& db, const std::unique_ptr& logger) + void recognizePeople(std::vector& faces, Database::IDatabase& db, const ILogger& logger) { FaceRecognition face_recognition(logger); const auto people_fingerprints = fetchPeopleAndFingerprints(db); @@ -189,7 +189,7 @@ namespace } } - std::vector findAndRecognizePeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const std::unique_ptr& logger) + std::vector findAndRecognizePeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const ILogger& logger) { std::vector result; @@ -328,7 +328,7 @@ namespace std::vector findFaces(Database::IDatabase& db, const OrientedImage& image, - const std::unique_ptr& logger, + const ILogger& logger, const Photo::Id& id) { std::vector result; @@ -399,8 +399,8 @@ namespace } -FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const std::unique_ptr& logger) - : m_logger(logger->subLogger("FaceEditor")) +FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const ILogger& logger) + : m_logger(logger.subLogger("FaceEditor")) , m_db(db) , m_core(core) { @@ -418,7 +418,7 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) const auto faces = findFaces( m_db, *image, - m_logger, + *m_logger, id); auto storage = m_facesSaver.lock(); diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 738d8ff3be..8014ebba91 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -45,7 +45,7 @@ struct IFacesSaver; class FaceEditor { public: - FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &, const std::unique_ptr &); + FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &, const ILogger &); std::vector> getFacesFor(const Photo::Id &); From 11daae150eb397fbec5d835d2b217fae9bbfea6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 11 Oct 2023 21:11:22 +0200 Subject: [PATCH 118/151] Extract classes from people editor files --- src/gui/desktop/utils/CMakeLists.txt | 1 + .../utils/implementation/faces_saver.cpp | 113 ++++++++++++ .../utils/implementation/faces_saver.hpp | 28 +++ .../implementation/people_editor_impl.hpp | 42 +++++ src/gui/desktop/utils/people_editor.cpp | 167 +----------------- 5 files changed, 185 insertions(+), 166 deletions(-) create mode 100644 src/gui/desktop/utils/implementation/faces_saver.cpp create mode 100644 src/gui/desktop/utils/implementation/faces_saver.hpp diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 95778feba3..60b4a322e9 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -15,6 +15,7 @@ set(UTILS_SOURCES grouppers/generator_utils.hpp grouppers/hdr_generator.cpp grouppers/hdr_generator.hpp + implementation/faces_saver.cpp batch_face_detector.cpp batch_face_detector.hpp collection_scanner.cpp diff --git a/src/gui/desktop/utils/implementation/faces_saver.cpp b/src/gui/desktop/utils/implementation/faces_saver.cpp new file mode 100644 index 0000000000..f655e4a89f --- /dev/null +++ b/src/gui/desktop/utils/implementation/faces_saver.cpp @@ -0,0 +1,113 @@ + +#include +#include + +#include "faces_saver.hpp" + + +FacesSaver::FacesSaver(Database::IDatabase& db) + : m_db(db) +{ + m_people = fetchPeople(); +} + + +FacesSaver::~FacesSaver() +{ + +} + + +void FacesSaver::store(FaceInfo& face) +{ + store_person_name(face); + store_fingerprint(face); + + // update names assigned to face + face.face.p_id = face.person.id(); + + // update fingerprints assigned to face + if (face.face.f_id.valid() == false) + face.face.f_id = face.fingerprint.id(); + + store_person_information(face); +} + + +void FacesSaver::store_person_name(FaceInfo& face) +{ + const bool nameChanged = + face.person.id().valid() == false && face.person.name().isEmpty() == false; + + if (nameChanged) + { + // introduce name associated with face to db (if needed) + const QString& name = face.person.name(); + + auto it = std::find_if(m_people.cbegin(), m_people.cend(), [name](const PersonName& d) + { + return d.name() == name; + }); + + if (it == m_people.cend()) // new name, store it in db + { + face.person = storeNewPerson(name); + m_people.push_back(face.person); + } + else + face.person = *it; + } +} + + +void FacesSaver::store_fingerprint(FaceInfo& face) +{ + if (face.fingerprint.id().valid() == false) + { + const PersonFingerprint::Id fid = + evaluate(m_db, [fingerprint = face.fingerprint](Database::IBackend& backend) + { + return backend.peopleInformationAccessor().store(fingerprint); + }); + + const PersonFingerprint fingerprint(fid, face.fingerprint.fingerprint()); + face.fingerprint = fingerprint; + } +} + + +void FacesSaver::store_person_information(const FaceInfo& face) +{ + const PersonInfo& faceInfo = face.face; + const PersonFingerprint& fingerprint = face.fingerprint; + + m_db.exec([faceInfo, fingerprint](Database::IBackend& backend) + { + backend.peopleInformationAccessor().store(faceInfo); + }); +} + + +std::vector FacesSaver::fetchPeople() const +{ + return evaluate(Database::IBackend &)>(m_db, [](Database::IBackend& backend) + { + auto people = backend.peopleInformationAccessor().listPeople(); + + return people; + }); +} + + +PersonName FacesSaver::storeNewPerson(const QString& name) const +{ + const PersonName person = evaluate + (m_db, [name](Database::IBackend& backend) + { + const PersonName d(Person::Id(), name); + const auto id = backend.peopleInformationAccessor().store(d); + return PersonName(id, name); + }); + + return person; +} diff --git a/src/gui/desktop/utils/implementation/faces_saver.hpp b/src/gui/desktop/utils/implementation/faces_saver.hpp new file mode 100644 index 0000000000..b3b2ccbd4e --- /dev/null +++ b/src/gui/desktop/utils/implementation/faces_saver.hpp @@ -0,0 +1,28 @@ + +#ifndef FACES_SAVER_HPP_INCLUDED +#define FACES_SAVER_HPP_INCLUDED + +#include "people_editor_impl.hpp" + + +class FacesSaver: public IFacesSaver +{ + public: + explicit FacesSaver(Database::IDatabase &); + ~FacesSaver(); + + void store(FaceInfo &) override; + + private: + Database::IDatabase& m_db; + std::vector m_people; + + void store_person_name(FaceInfo& face); + void store_fingerprint(FaceInfo& face); + void store_person_information(const FaceInfo& face); + + std::vector fetchPeople() const; + PersonName storeNewPerson(const QString& name) const; +}; + +#endif diff --git a/src/gui/desktop/utils/implementation/people_editor_impl.hpp b/src/gui/desktop/utils/implementation/people_editor_impl.hpp index 2fd367fdc8..6b995c1ce5 100644 --- a/src/gui/desktop/utils/implementation/people_editor_impl.hpp +++ b/src/gui/desktop/utils/implementation/people_editor_impl.hpp @@ -6,6 +6,9 @@ #include #include +#include + +#include "../people_editor.hpp" struct CalculatedData @@ -41,4 +44,43 @@ struct IFacesSaver virtual void store(FaceInfo &) = 0; }; + +struct Face: public IFace +{ + Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) + : m_faceInfo(fi) + , m_image(image) + , m_saver(saver) + {} + + const QRect& rect() const override + { + return m_faceInfo.face.rect; + } + + const QString& name() const override + { + return m_faceInfo.person.name(); + } + + const OrientedImage& image() const override + { + return *m_image; + } + + void setName(const QString& name) override + { + m_faceInfo.person = PersonName(name); + } + + void store() override + { + m_saver->store(m_faceInfo); + } + + FaceInfo m_faceInfo; + std::shared_ptr m_image; + std::shared_ptr m_saver; +}; + #endif diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 6129eb09be..ab58f35210 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -34,6 +34,7 @@ #include #include +#include "implementation/faces_saver.hpp" #include "implementation/people_editor_impl.hpp" @@ -209,64 +210,6 @@ namespace return result; } - class FacesSaver: public IFacesSaver - { - public: - explicit FacesSaver(Database::IDatabase &); - ~FacesSaver(); - - void store(FaceInfo &) override; - - private: - Database::IDatabase& m_db; - std::vector m_people; - - void store_person_name(FaceInfo& face); - void store_fingerprint(FaceInfo& face); - void store_person_information(const FaceInfo& face); - - std::vector fetchPeople() const; - PersonName storeNewPerson(const QString& name) const; - }; - - struct Face: public IFace - { - Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) - : m_faceInfo(fi) - , m_image(image) - , m_saver(saver) - {} - - const QRect& rect() const override - { - return m_faceInfo.face.rect; - } - - const QString& name() const override - { - return m_faceInfo.person.name(); - } - - const OrientedImage& image() const override - { - return *m_image; - } - - void setName(const QString& name) override - { - m_faceInfo.person = PersonName(name); - } - - void store() override - { - m_saver->store(m_faceInfo); - } - - FaceInfo m_faceInfo; - std::shared_ptr m_image; - std::shared_ptr m_saver; - }; - void sortFaces(std::vector& faces) { // sort faces so they appear from left to right @@ -437,111 +380,3 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) return result; } - - -FacesSaver::FacesSaver(Database::IDatabase& db) - : m_db(db) -{ - m_people = fetchPeople(); -} - - -FacesSaver::~FacesSaver() -{ - -} - - -void FacesSaver::store(FaceInfo& face) -{ - store_person_name(face); - store_fingerprint(face); - - // update names assigned to face - face.face.p_id = face.person.id(); - - // update fingerprints assigned to face - if (face.face.f_id.valid() == false) - face.face.f_id = face.fingerprint.id(); - - store_person_information(face); -} - - -void FacesSaver::store_person_name(FaceInfo& face) -{ - const bool nameChanged = - face.person.id().valid() == false && face.person.name().isEmpty() == false; - - if (nameChanged) - { - // introduce name associated with face to db (if needed) - const QString& name = face.person.name(); - - auto it = std::find_if(m_people.cbegin(), m_people.cend(), [name](const PersonName& d) - { - return d.name() == name; - }); - - if (it == m_people.cend()) // new name, store it in db - { - face.person = storeNewPerson(name); - m_people.push_back(face.person); - } - else - face.person = *it; - } -} - - -void FacesSaver::store_fingerprint(FaceInfo& face) -{ - if (face.fingerprint.id().valid() == false) - { - const PersonFingerprint::Id fid = - evaluate(m_db, [fingerprint = face.fingerprint](Database::IBackend& backend) - { - return backend.peopleInformationAccessor().store(fingerprint); - }); - - const PersonFingerprint fingerprint(fid, face.fingerprint.fingerprint()); - face.fingerprint = fingerprint; - } -} - - -void FacesSaver::store_person_information(const FaceInfo& face) -{ - const PersonInfo& faceInfo = face.face; - const PersonFingerprint& fingerprint = face.fingerprint; - - m_db.exec([faceInfo, fingerprint](Database::IBackend& backend) - { - backend.peopleInformationAccessor().store(faceInfo); - }); -} - - -std::vector FacesSaver::fetchPeople() const -{ - return evaluate(Database::IBackend &)>(m_db, [](Database::IBackend& backend) - { - auto people = backend.peopleInformationAccessor().listPeople(); - - return people; - }); -} - - -PersonName FacesSaver::storeNewPerson(const QString& name) const -{ - const PersonName person = evaluate - (m_db, [name](Database::IBackend& backend) - { - const PersonName d(Person::Id(), name); - const auto id = backend.peopleInformationAccessor().store(d); - return PersonName(id, name); - }); - - return person; -} From 1657fb14be3137903475da3dbb126fb61a6573f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 11 Oct 2023 21:26:05 +0200 Subject: [PATCH 119/151] Drop dead include --- src/database/implementation/async_database.cpp | 1 - src/gui/desktop/models/faces_model.cpp | 1 - src/gui/desktop/ui/photos_grouping_dialog.cpp | 1 - tr/photo_broom_en.ts | 16 ++++++++-------- tr/photo_broom_pl.ts | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/database/implementation/async_database.cpp b/src/database/implementation/async_database.cpp index 8364aeccfb..93bbedd99d 100644 --- a/src/database/implementation/async_database.cpp +++ b/src/database/implementation/async_database.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index a934bbacec..2232fa1da9 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -1,7 +1,6 @@ #include -#include #include #include #include diff --git a/src/gui/desktop/ui/photos_grouping_dialog.cpp b/src/gui/desktop/ui/photos_grouping_dialog.cpp index e74f186849..33bce56175 100644 --- a/src/gui/desktop/ui/photos_grouping_dialog.cpp +++ b/src/gui/desktop/ui/photos_grouping_dialog.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 2ea9fdb841..ef7926784c 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -721,38 +721,38 @@ Error code: %1 - - + + Cancel operation? - + Do you really want to stop current work and quit? - + Do you really want to stop current work? - + Error during collage generation. Possibly too many images, or height to small or too big. - + photo path - + sequence number - + exposure (EV) diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 8e14eb7f9e..12ab2e3007 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -748,7 +748,7 @@ Kod błędu: %1 Podgląd: - + Cancel operation? Anulować operację? From 047bfad3ad87124b072ba263901206587af30421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 11 Oct 2023 21:42:14 +0200 Subject: [PATCH 120/151] rename file to be more generic --- src/core/{down_cast.hpp => cast.hpp} | 0 src/plugins/implementation/plugin_loader.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/core/{down_cast.hpp => cast.hpp} (100%) diff --git a/src/core/down_cast.hpp b/src/core/cast.hpp similarity index 100% rename from src/core/down_cast.hpp rename to src/core/cast.hpp diff --git a/src/plugins/implementation/plugin_loader.cpp b/src/plugins/implementation/plugin_loader.cpp index 1f080852a8..ae32f99ab6 100644 --- a/src/plugins/implementation/plugin_loader.cpp +++ b/src/plugins/implementation/plugin_loader.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include From 6a8df3157f96bd54fe22667aaf627c866aec59e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 11 Oct 2023 21:42:24 +0200 Subject: [PATCH 121/151] Introduce new cast type --- src/core/cast.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/core/cast.hpp b/src/core/cast.hpp index 16fd0d68da..7a9dd98d3d 100644 --- a/src/core/cast.hpp +++ b/src/core/cast.hpp @@ -3,6 +3,8 @@ #define DOWN_CAST_HPP #include +#include + template T down_cast(R* base) @@ -12,4 +14,17 @@ T down_cast(R* base) return static_cast(base); } + +template +T safe_cast(const F& from) +{ + static_assert(sizeof(F) <= sizeof(T)); // TODO: handle size conversion when needed + static_assert(std::is_nothrow_convertible_v); + + if constexpr (std::is_signed_v && std::is_unsigned_v) // cast from signed to unsigned + assert(from >= 0); + + return static_cast(from); +} + #endif From 6187f1700818ed096a0775a2c30f601c8f51119e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 11 Oct 2023 22:17:03 +0200 Subject: [PATCH 122/151] Remove accepted/dropped faces from list --- src/gui/desktop/utils/batch_face_detector.cpp | 14 ++++++++++++-- src/gui/desktop/utils/batch_face_detector.hpp | 1 + tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/gui/desktop/utils/batch_face_detector.cpp b/src/gui/desktop/utils/batch_face_detector.cpp index a0dffc7ab1..55bcc76a14 100644 --- a/src/gui/desktop/utils/batch_face_detector.cpp +++ b/src/gui/desktop/utils/batch_face_detector.cpp @@ -1,4 +1,5 @@ +#include #include #include #include @@ -140,13 +141,14 @@ bool BatchFaceDetector::setData(const QModelIndex& idx, const QVariant& value, i void BatchFaceDetector::accept(int idx) { - m_faces[idx].first->store(); + m_faces[safe_cast(idx)].first->store(); + removeFace(idx); } void BatchFaceDetector::drop(int idx) { - + removeFace(idx); } @@ -189,6 +191,14 @@ void BatchFaceDetector::appendFaces(std::vector&& faces) } +void BatchFaceDetector::removeFace(int idx) +{ + beginRemoveRows({}, idx, idx); + m_faces.erase(m_faces.begin() + idx); + endRemoveRows(); +} + + void BatchFaceDetector::newPhotos(const QModelIndex &, int first, int last) { std::lock_guard _(m_idsMtx); diff --git a/src/gui/desktop/utils/batch_face_detector.hpp b/src/gui/desktop/utils/batch_face_detector.hpp index e9458a0858..8b08c30a30 100644 --- a/src/gui/desktop/utils/batch_face_detector.hpp +++ b/src/gui/desktop/utils/batch_face_detector.hpp @@ -56,6 +56,7 @@ class BatchFaceDetector: public QAbstractListModel ITaskExecutor::ProcessCoroutine processPhotos(ITaskExecutor::IProcessSupervisor *); void appendFaces(std::vector &&); + void removeFace(int); void newPhotos(const QModelIndex &, int, int); std::optional getNextId(); void loadFacesFromPhoto(const Photo::Id &); diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index ef7926784c..273953341f 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -73,7 +73,7 @@ BatchFaceDetector - + Batch face detector diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 12ab2e3007..9da9f9a4c2 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -74,7 +74,7 @@ BatchFaceDetector - + Batch face detector From 8eb228b3d0e1c387a15f1fa83d8cfc9a14ebe97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sat, 14 Oct 2023 23:00:27 +0200 Subject: [PATCH 123/151] Save name before saving person --- .../desktop/quick_items/Views/BatchFaceDetection.qml | 12 ++++++++++-- tr/photo_broom_en.ts | 2 +- tr/photo_broom_pl.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml index 3456f898e5..efd3c89db8 100644 --- a/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml +++ b/src/gui/desktop/quick_items/Views/BatchFaceDetection.qml @@ -53,6 +53,7 @@ Item { height: 200 Column { + id: faceItem anchors.fill: parent Item { @@ -86,7 +87,10 @@ Item { style: ImageButton.Scale - onClicked: detector.accept(delegateItem.index) + onClicked: { + faceItem.updateModel(); + detector.accept(delegateItem.index); + } } ImageButton { @@ -106,11 +110,15 @@ Item { } LineEdit { + id: personName + anchors.horizontalCenter: parent.horizontalCenter text: display width: parent.width - 40 + } - onEditingFinished: model.edit = text + function updateModel() { + model.edit = personName.text } } } diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 273953341f..0ad228f24f 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -65,7 +65,7 @@ - + Photos to be analyzed diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index 9da9f9a4c2..c9e345aaef 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -66,7 +66,7 @@ - + Photos to be analyzed From a1a33e01116adad117b941d5a39a31e886173b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 19 Oct 2023 21:31:59 +0200 Subject: [PATCH 124/151] Cleanup --- src/gui/desktop/models/faces_model.cpp | 2 +- src/gui/desktop/utils/people_editor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index 2232fa1da9..7c6710be11 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -125,7 +125,7 @@ void FacesModel::updateFaceInformation(std::shared_ptr 0) { - beginInsertRows(QModelIndex(), 0, static_cast(faces_count - 1)); + beginInsertRows({}, 0, faces_count - 1); m_faces = std::move(*faces); endInsertRows(); } diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index ab58f35210..9c701b6024 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -181,7 +181,7 @@ namespace { const int pos = face_recognition.recognize(faceInfo.fingerprint.fingerprint(), known_fingerprints); - if (pos >=0) + if (pos >= 0) { const std::vector& known_people = std::get<1>(people_fingerprints); const Person::Id found_person = known_people[pos]; From 6f2b85df2d2bcefa4222966edee599932ab884fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 25 Oct 2023 22:09:06 +0200 Subject: [PATCH 125/151] drop dead code --- src/gui/desktop/utils/implementation/faces_saver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/desktop/utils/implementation/faces_saver.cpp b/src/gui/desktop/utils/implementation/faces_saver.cpp index f655e4a89f..3f71f1ab95 100644 --- a/src/gui/desktop/utils/implementation/faces_saver.cpp +++ b/src/gui/desktop/utils/implementation/faces_saver.cpp @@ -79,9 +79,8 @@ void FacesSaver::store_fingerprint(FaceInfo& face) void FacesSaver::store_person_information(const FaceInfo& face) { const PersonInfo& faceInfo = face.face; - const PersonFingerprint& fingerprint = face.fingerprint; - m_db.exec([faceInfo, fingerprint](Database::IBackend& backend) + m_db.exec([faceInfo](Database::IBackend& backend) { backend.peopleInformationAccessor().store(faceInfo); }); From aadf9066f7d94133ac0eaa4b0706c66c870e7146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Wed, 25 Oct 2023 22:12:20 +0200 Subject: [PATCH 126/151] Refactor. Store all found faces right after detection. Do not guess people names automatically Simplify architecture of face fanding, no special cache is used now Face locations are stored in db right after detection, but names are not detected and therefore not saved automatically as chance of invalid recognition is significant --- src/gui/desktop/utils/people_editor.cpp | 191 +++++++++--------------- src/gui/desktop/utils/people_editor.hpp | 4 + 2 files changed, 77 insertions(+), 118 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 9c701b6024..99511f973e 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -190,7 +190,7 @@ namespace } } - std::vector findAndRecognizePeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const ILogger& logger) + std::vector findPeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const ILogger& logger) { std::vector result; @@ -204,9 +204,6 @@ namespace //calculate fingerprints calculateMissingFingerprints(result, image, logger); - //recognize people - recognizePeople(result, db, logger); - return result; } @@ -225,158 +222,116 @@ namespace return lhs_face.top() < rhs_face.top(); // when in line - lhs needs to be above }); } +} - std::vector loadCachedFacesFor(Database::IDatabase& db, const Photo::Id& id) - { - std::vector result; - const QByteArray blob = evaluate(db, [id](Database::IBackend& backend) - { - return backend.readBlob(id, CacheBlob); - }); +FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const ILogger& logger) + : m_logger(logger.subLogger("FaceEditor")) + , m_db(db) + , m_core(core) +{ - if (blob.isEmpty() == false) - { - const QJsonDocument json = QJsonDocument::fromJson(blob); - const std::vector storage = JSon::deserialize>(json); +} - for (const auto& faceData: storage) - { - FaceInfo fi(faceData.ph_id, faceData.position); - fi.fingerprint = PersonFingerprint(faceData.fingerprint); - fi.person = PersonName(faceData.name); - result.push_back(fi); - } - } +std::vector> FaceEditor::getFacesFor(const Photo::Id& id) +{ + const QString path = pathFor(m_db, id); + const QFileInfo pathInfo(path); + const QString full_path = pathInfo.absoluteFilePath(); + auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); - return result; - } + const auto faces = findFaces(*image, id); + + auto storage = getFaceSaver(); - void cacheFacesFor(Database::IDatabase& db, const Photo::Id& id, const std::vector& result) + std::vector> result; + + std::ranges::transform(faces, std::back_inserter(result), [&image, &storage](const FaceInfo& fi) { - std::vector storage; + return std::make_unique(fi, image, storage); + }); - for (const auto& faceInfo: result) - storage.emplace_back(faceInfo.face.rect, faceInfo.fingerprint.fingerprint(), faceInfo.person.name(), faceInfo.face.ph_id); + return result; +} - // store detected, but not confirmed by user, faces as blob in database for cache - const QJsonDocument json = JSon::serialize(storage); +std::vector FaceEditor::findFaces(const OrientedImage& image, const Photo::Id& id) +{ + std::vector result; - execute(db, [id, blob = json.toJson()](Database::IBackend& backend) - { - backend.writeBlob(id, CacheBlob, blob); - }); - } + const bool facesNotFound = wasPhotoAnalyzedAndHasNoFaces(m_db, id); - std::vector findFaces(Database::IDatabase& db, - const OrientedImage& image, - const ILogger& logger, - const Photo::Id& id) + // photo not analyzed yet (no records in db) or analyzed and we have data in db + if (facesNotFound == false) { - std::vector result; + const std::vector list_of_faces = fetchFacesFromDb(m_db, id); - const bool facesNotFound = wasPhotoAnalyzedAndHasNoFaces(db, id); - - // photo not analyzed yet (no records in db) or analyzed and we have data in db - if (facesNotFound == false) + // no data in db + if (list_of_faces.empty()) { - const std::vector list_of_faces = fetchFacesFromDb(db, id); + result = findPeople(image, m_db, id, *m_logger); - // no data in db - if (list_of_faces.empty()) + if (result.empty()) { - // check in cache - result = loadCachedFacesFor(db, id); - - if (result.empty()) + // mark photo as one without faces + m_db.exec([id](Database::IBackend& backend) { - result = findAndRecognizePeople(image, db, id, logger); - - if (result.empty()) - { - // mark photo as one without faces - db.exec([id](Database::IBackend& backend) - { - backend.set( - id, - FacesAnalysisState, - FacesAnalysisType::AnalysedAndNotFound); - }); - } - else - cacheFacesFor(db, id, result); - } + backend.set( + id, + FacesAnalysisState, + FacesAnalysisType::AnalysedAndNotFound); + }); + } + else + { + // store face location and fingerprint in db + auto storage = getFaceSaver(); + for (auto& face: result) + storage->store(face); } - else // data in db just use it + } + else // data in db just use it + { + evaluate(m_db, [&result, &list_of_faces](Database::IBackend& backend) { - evaluate(db, [&result, &list_of_faces](Database::IBackend& backend) + std::vector pi_ids; + std::ranges::transform(list_of_faces, std::back_inserter(pi_ids), [](const PersonInfo& pi) { - std::vector pi_ids; - std::ranges::transform(list_of_faces, std::back_inserter(pi_ids), [](const PersonInfo& pi) - { - return pi.id; - }); + return pi.id; + }); - const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(pi_ids); - assert(fingerprints.size() == list_of_faces.size()); + const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(pi_ids); + assert(fingerprints.size() == list_of_faces.size()); - for (const PersonInfo& pi: list_of_faces) - { - FaceInfo fi(pi); - fi.fingerprint = fingerprints.find(pi.id)->second; + for (const PersonInfo& pi: list_of_faces) + { + FaceInfo fi(pi); + fi.fingerprint = fingerprints.find(pi.id)->second; - if (pi.p_id.valid()) - fi.person = backend.peopleInformationAccessor().person(pi.p_id); + if (pi.p_id.valid()) + fi.person = backend.peopleInformationAccessor().person(pi.p_id); - result.push_back(fi); - }; - }); - } + result.push_back(fi); + }; + }); } - - sortFaces(result); - - return result; } -} + sortFaces(result); -FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const ILogger& logger) - : m_logger(logger.subLogger("FaceEditor")) - , m_db(db) - , m_core(core) -{ - + return result; } -std::vector> FaceEditor::getFacesFor(const Photo::Id& id) +std::shared_ptr FaceEditor::getFaceSaver() { - const QString path = pathFor(m_db, id); - const QFileInfo pathInfo(path); - const QString full_path = pathInfo.absoluteFilePath(); - auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); - - const auto faces = findFaces( - m_db, - *image, - *m_logger, - id); - auto storage = m_facesSaver.lock(); + if (storage.get() == nullptr) { storage = std::make_shared(m_db); m_facesSaver = storage; } - std::vector> result; - - std::ranges::transform(faces, std::back_inserter(result), [&image, &storage](const FaceInfo& fi) - { - return std::make_unique(fi, image, storage); - }); - - return result; + return storage; } diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 8014ebba91..6930a15a0b 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -41,6 +41,7 @@ class IFace struct IFacesSaver; +class FaceInfo; class FaceEditor { @@ -54,6 +55,9 @@ class FaceEditor std::unique_ptr m_logger; Database::IDatabase& m_db; ICoreFactoryAccessor& m_core; + + std::vector findFaces(const OrientedImage &, const Photo::Id &); + std::shared_ptr getFaceSaver(); }; #endif From 044f14fa28b103141964eca165cf4ec2a5d079b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 2 Nov 2023 21:22:01 +0100 Subject: [PATCH 127/151] Recognize people after faces detection --- src/gui/desktop/utils/people_editor.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 99511f973e..fb158f878e 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -240,11 +240,11 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) const QFileInfo pathInfo(path); const QString full_path = pathInfo.absoluteFilePath(); auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); - - const auto faces = findFaces(*image, id); - auto storage = getFaceSaver(); + auto faces = findFaces(*image, id); + recognizePeople(faces, m_db, *m_logger); + std::vector> result; std::ranges::transform(faces, std::back_inserter(result), [&image, &storage](const FaceInfo& fi) @@ -255,6 +255,7 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) return result; } + std::vector FaceEditor::findFaces(const OrientedImage& image, const Photo::Id& id) { std::vector result; From 965a9d10cbb51ce6a9c0505e4b3ed853b37b597c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Nov 2023 11:15:27 +0100 Subject: [PATCH 128/151] Drop leftovers --- src/gui/desktop/utils/CMakeLists.txt | 8 ------- src/gui/desktop/utils/people_editor.cpp | 30 ++----------------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 60b4a322e9..2192579204 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -83,12 +83,4 @@ target_include_directories(gui_utils ${CMAKE_CURRENT_BINARY_DIR} ) -ReflectFiles( - ReflectionFiles - TARGET - gui_utils - SOURCES - implementation/people_editor_impl.hpp -) - target_sources(gui_utils PRIVATE ${ReflectionFiles}) diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index fb158f878e..2fd8845220 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -15,9 +15,6 @@ * along with this program. If not, see . */ -#include "people_editor.hpp" -#include "people_editor_impl_r++.hpp" - #include #include #include @@ -27,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -37,36 +33,14 @@ #include "implementation/faces_saver.hpp" #include "implementation/people_editor_impl.hpp" +#include "people_editor.hpp" -using namespace Database::CommonGeneralFlags; - -namespace JSon -{ - template - struct CustomType> - { - using type = QJsonObject; - - static QJsonObject serialize(const Id& id) - { - QJsonObject json; - json["id"] = id.value(); - - return json; - } - static Id deserialize(const QJsonObject& json) - { - return Id(json["id"].toVariant().value()); - } - }; -} +using namespace Database::CommonGeneralFlags; namespace { - constexpr auto CacheBlob = "people_editor_cache"; - Person::Fingerprint average_fingerprint(const std::vector& faces) { if (faces.empty()) From 0a109c44f57419c1f5b9fe83750919b618aa2f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Nov 2023 11:15:31 +0100 Subject: [PATCH 129/151] Cleanup --- src/core/json_serializer.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/json_serializer.hpp b/src/core/json_serializer.hpp index f460bbfe50..632e5bff0c 100644 --- a/src/core/json_serializer.hpp +++ b/src/core/json_serializer.hpp @@ -10,7 +10,6 @@ #include "generic_concepts.hpp" - namespace JSon { template From bb94923bbaefb106433f6100b11cca8e026de900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Nov 2023 20:20:03 +0100 Subject: [PATCH 130/151] Bump reflect++ --- src/core/unit_tests/json_serializer_tests.cpp | 2 +- src/core/unit_tests/json_serializer_tests.hpp | 6 +++--- tools/reflect++ | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/unit_tests/json_serializer_tests.cpp b/src/core/unit_tests/json_serializer_tests.cpp index 5fc25605c0..ae113321f3 100644 --- a/src/core/unit_tests/json_serializer_tests.cpp +++ b/src/core/unit_tests/json_serializer_tests.cpp @@ -82,7 +82,7 @@ TEST(JsonSerializerTest, SerializationDeserialization) { DEF def; def.abc_vec.emplace_back(11, 15.f, -8., "World", QRect(3, 6, 9, 11)); - def.abc_list.emplace_back(-6, -765.f, 89., "World", QRect(-1, -50, 9, 11)); + def.abc_deque.emplace_back(-6, -765.f, 89., "World", QRect(-1, -50, 9, 11)); const QJsonDocument json = JSon::serialize(def); const DEF def2 = JSon::deserialize(json); diff --git a/src/core/unit_tests/json_serializer_tests.hpp b/src/core/unit_tests/json_serializer_tests.hpp index bea74be7b6..7753fdc3c2 100644 --- a/src/core/unit_tests/json_serializer_tests.hpp +++ b/src/core/unit_tests/json_serializer_tests.hpp @@ -1,7 +1,7 @@ #pragma once -#include +#include #include #include #include @@ -27,11 +27,11 @@ namespace struct DEF { std::vector abc_vec; - std::list abc_list; + std::deque abc_deque; bool operator==(const DEF& other) const { - return std::tie(abc_vec, abc_list) == std::tie(other.abc_vec, other.abc_list); + return std::tie(abc_vec, abc_deque) == std::tie(other.abc_vec, other.abc_deque); } }; } diff --git a/tools/reflect++ b/tools/reflect++ index 5bc90a473f..5dc631cb12 160000 --- a/tools/reflect++ +++ b/tools/reflect++ @@ -1 +1 @@ -Subproject commit 5bc90a473f0fdd2319a6cefbf0f2e9be75e5947e +Subproject commit 5dc631cb129dd6a5dbdcab8836ba7e0772c2b02c From 9fc99826b77b5fa8d8299815010bec8c53272b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 5 Nov 2023 21:37:02 +0100 Subject: [PATCH 131/151] FIXUP: Drop leftovers --- .../desktop/utils/implementation/people_editor_impl.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/gui/desktop/utils/implementation/people_editor_impl.hpp b/src/gui/desktop/utils/implementation/people_editor_impl.hpp index 6b995c1ce5..c4ca338191 100644 --- a/src/gui/desktop/utils/implementation/people_editor_impl.hpp +++ b/src/gui/desktop/utils/implementation/people_editor_impl.hpp @@ -11,14 +11,6 @@ #include "../people_editor.hpp" -struct CalculatedData -{ - QRect position; - Person::Fingerprint fingerprint; - QString name; - Photo::Id ph_id; -}; - struct FaceInfo { PersonInfo face; From 7e3b04992637f5e5db898f1444ff2ea9290a565e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 7 Nov 2023 19:13:22 +0100 Subject: [PATCH 132/151] Simplify --- .../database_tools/implementation/json_to_backend.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/database/database_tools/implementation/json_to_backend.cpp b/src/database/database_tools/implementation/json_to_backend.cpp index 3dcd08dfe9..2be6efa936 100644 --- a/src/database/database_tools/implementation/json_to_backend.cpp +++ b/src/database/database_tools/implementation/json_to_backend.cpp @@ -109,10 +109,7 @@ namespace Database std::vector people; const auto array = it.value().toArray(); - std::transform(array.cbegin(), - array.cend(), - std::back_inserter(people), - [](const QJsonValue& value) + std::ranges::transform(array, std::back_inserter(people), [](const QJsonValue& value) { const auto person = value.toObject(); From 81dab11a962ae69a349afbec235bf019f9eeac57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 7 Nov 2023 21:52:34 +0100 Subject: [PATCH 133/151] Improve comments --- .../implementation/apeople_information_accessor.cpp | 5 ++--- src/database/ipeople_information_accessor.hpp | 11 ++++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/database/implementation/apeople_information_accessor.cpp b/src/database/implementation/apeople_information_accessor.cpp index 891919ab5b..e71b598577 100644 --- a/src/database/implementation/apeople_information_accessor.cpp +++ b/src/database/implementation/apeople_information_accessor.cpp @@ -23,15 +23,14 @@ namespace Database for(const auto& person: existing_people) { - if (fd.rect.isValid() && - person.rect == fd.rect) // same, valid rect + if (fd.rect.isValid() && person.rect == fd.rect) // same, valid rect { to_store.id = person.id; break; } else if (person.p_id.valid() && person.p_id == fd.p_id && - person.rect.isValid() == false) // same, valid person but no rect in db + person.rect.isValid() == false) // same, valid person but no rect in db { to_store.id = person.id; break; diff --git a/src/database/ipeople_information_accessor.hpp b/src/database/ipeople_information_accessor.hpp index 25ec5e172a..07fcc78fb4 100644 --- a/src/database/ipeople_information_accessor.hpp +++ b/src/database/ipeople_information_accessor.hpp @@ -54,7 +54,16 @@ namespace Database */ virtual PersonInfo::Id store(const PersonInfo& pi) = 0; - virtual PersonFingerprint::Id store(const PersonFingerprint &) = 0; + /** + * @brief Store person's fingerprint in db + * @arg fp Person's fingerprint + * @return fingerprint id assigned by db + * + * If @a fp has valid id but empty fingerprint, it will be removed from db. + * If @a fp has invalid id but valid fingerprint data, new entry will be added to db. + * If @a fp has both entries valid, fingerpritn data for given id will be updated. + */ + virtual PersonFingerprint::Id store(const PersonFingerprint& fp) = 0; }; } From 6c43f313710bb5afa9e5881ecfcfcaf68cbeced4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 9 Nov 2023 19:58:21 +0100 Subject: [PATCH 134/151] Add mutable accessor --- src/database/explicit_photo_delta.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/database/explicit_photo_delta.hpp b/src/database/explicit_photo_delta.hpp index 0467c5b6d8..c670e6c057 100644 --- a/src/database/explicit_photo_delta.hpp +++ b/src/database/explicit_photo_delta.hpp @@ -103,6 +103,14 @@ namespace Photo return m_data.get(); } + template + typename DeltaTypes::Storage& get() + { + static_assert(has(), "ExplicitDelta has no required Photo::Field"); + + return m_data.get(); + } + template void insert(const typename DeltaTypes::Storage& d) { From 642d64b60ae64d9e6499baeaea31d770f8f4f089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 9 Nov 2023 19:58:46 +0100 Subject: [PATCH 135/151] Format code --- .../backends/sql_backends/people_information_accessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/backends/sql_backends/people_information_accessor.cpp b/src/database/backends/sql_backends/people_information_accessor.cpp index 8fb4ea1205..85fc53c853 100644 --- a/src/database/backends/sql_backends/people_information_accessor.cpp +++ b/src/database/backends/sql_backends/people_information_accessor.cpp @@ -280,7 +280,7 @@ namespace Database { if (fingerprint.fingerprint().empty()) { - const QString delete_query = QString ("DELETE from %1 WHERE id = %2") + const QString delete_query = QString("DELETE from %1 WHERE id = %2") .arg(TAB_FACES_FINGERPRINTS) .arg(fid.value()); From 81171e02bc3908bb89e68ebd0b612647346740a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 9 Nov 2023 20:00:15 +0100 Subject: [PATCH 136/151] Extend people accessor interface --- src/database/apeople_information_accessor.hpp | 5 +++ .../backends/sql_backends/sql_backend.cpp | 10 +---- .../apeople_information_accessor.cpp | 37 +++++++++++++++++++ src/database/ipeople_information_accessor.hpp | 9 ++++- src/database/person_data.hpp | 1 + 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/database/apeople_information_accessor.hpp b/src/database/apeople_information_accessor.hpp index 39c5511115..6d9dac1793 100644 --- a/src/database/apeople_information_accessor.hpp +++ b/src/database/apeople_information_accessor.hpp @@ -12,6 +12,11 @@ namespace Database { public: PersonInfo::Id store(const PersonInfo& pi) override; + PersonInfo::Id store(const Photo::Id&, const PersonFullInfo& pi) override; + + std::vector listPeopleFull(const Photo::Id &) override; + + using IPeopleInformationAccessor::store; private: virtual void dropPersonInfo(const PersonInfo::Id &) = 0; diff --git a/src/database/backends/sql_backends/sql_backend.cpp b/src/database/backends/sql_backends/sql_backend.cpp index 859c5df4a2..3c6a8d80a1 100644 --- a/src/database/backends/sql_backends/sql_backend.cpp +++ b/src/database/backends/sql_backends/sql_backend.cpp @@ -975,14 +975,8 @@ namespace Database const auto& people = data.get(); auto& accessor = peopleInformationAccessor(); - for(const auto& person: people) - { - const auto p_id = accessor.store(person.name); - const auto f_id = accessor.store(person.fingerprint); - - const PersonInfo pf(p_id, data.getId(), f_id, person.position); - accessor.store(pf); - } + for(const PersonFullInfo& person: people) + accessor.store(data.getId(), person); } photoChangeLogOperator().storeDifference(currentStateOfPhoto, data); diff --git a/src/database/implementation/apeople_information_accessor.cpp b/src/database/implementation/apeople_information_accessor.cpp index e71b598577..98b0221c92 100644 --- a/src/database/implementation/apeople_information_accessor.cpp +++ b/src/database/implementation/apeople_information_accessor.cpp @@ -46,4 +46,41 @@ namespace Database return result; } + + PersonInfo::Id APeopleInformationAccessor::store(const Photo::Id& ph_id, const PersonFullInfo& pi) + { + const auto p_id = store(pi.name); + const auto f_id = store(pi.fingerprint); + + const PersonInfo pf(pi.pi_id, p_id, ph_id, f_id, pi.position); + return store(pf); + } + + std::vector Database::APeopleInformationAccessor::listPeopleFull(const Photo::Id& id) + { + const auto simpleList = listPeople(id); + + std::vector pi_ids; + std::ranges::transform(simpleList, std::back_inserter(pi_ids), [](const PersonInfo& pi) + { + return pi.id; + }); + + const auto fingerprints = fingerprintsFor(pi_ids); + + std::vector fullInfo; + std::ranges::transform(simpleList, std::back_inserter(fullInfo), [this, &fingerprints](const PersonInfo& pi) + { + PersonFullInfo pfi; + pfi.pi_id = pi.id; + pfi.position = pi.rect; + pfi.fingerprint = fingerprints.find(pi.id)->second; + pfi.name = person(pi.p_id); + + return pfi; + }); + + return fullInfo; + } + } diff --git a/src/database/ipeople_information_accessor.hpp b/src/database/ipeople_information_accessor.hpp index 07fcc78fb4..df5e9ee03b 100644 --- a/src/database/ipeople_information_accessor.hpp +++ b/src/database/ipeople_information_accessor.hpp @@ -18,6 +18,9 @@ namespace Database /// list people on photo virtual std::vector listPeople(const Photo::Id &) = 0; + /// list full people data for photo + virtual std::vector listPeopleFull(const Photo::Id &) = 0; + /** * \brief get person details * \arg id person id @@ -54,13 +57,15 @@ namespace Database */ virtual PersonInfo::Id store(const PersonInfo& pi) = 0; + virtual PersonInfo::Id store(const Photo::Id&, const PersonFullInfo& pfi) = 0; + /** * @brief Store person's fingerprint in db * @arg fp Person's fingerprint * @return fingerprint id assigned by db * - * If @a fp has valid id but empty fingerprint, it will be removed from db. - * If @a fp has invalid id but valid fingerprint data, new entry will be added to db. + * If @a fp has valid id but empty fingerprint, it will be removed from db. \n + * If @a fp has invalid id but valid fingerprint data, new entry will be added to db. \n * If @a fp has both entries valid, fingerpritn data for given id will be updated. */ virtual PersonFingerprint::Id store(const PersonFingerprint& fp) = 0; diff --git a/src/database/person_data.hpp b/src/database/person_data.hpp index 7fa862b67f..3d018262c5 100644 --- a/src/database/person_data.hpp +++ b/src/database/person_data.hpp @@ -120,6 +120,7 @@ class PersonFullInfo QRect position; PersonFingerprint fingerprint; PersonName name; + PersonInfo::Id pi_id; auto operator<=>(const PersonFullInfo &) const = default; }; From 87a428ecbb8c1f65ccf1480c383819164b83b861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 10 Nov 2023 20:43:13 +0100 Subject: [PATCH 137/151] Add comments and new structure for initialization --- src/database/person_data.hpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/database/person_data.hpp b/src/database/person_data.hpp index 3d018262c5..b27a17c55b 100644 --- a/src/database/person_data.hpp +++ b/src/database/person_data.hpp @@ -77,7 +77,9 @@ class DATABASE_EXPORT PersonFingerprint Id m_id; }; - +/** + * @brief Container for DB IDs of people related entries. + */ class DATABASE_EXPORT PersonInfo { public: @@ -113,10 +115,29 @@ class DATABASE_EXPORT PersonInfo auto operator<=>(const PersonInfo &) const = default; }; +/** + * @brief Container for person data (without DB IDs) + */ +struct PersonData +{ + QRect rect; + Person::Fingerprint fingerprint; + QString name; +}; +/** + * @brief Container for all people related data (DB IDs + values) + */ class PersonFullInfo { public: + PersonFullInfo() = default; + PersonFullInfo(const PersonData& data) + : position(data.rect) + , fingerprint(data.fingerprint) + , name(data.name) + {} + QRect position; PersonFingerprint fingerprint; PersonName name; From e7fcd5e2e3cfeae3b0042cd61013c464bf70d55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 10 Nov 2023 20:46:31 +0100 Subject: [PATCH 138/151] SWAP WITH PREVIOUS Allow deserialization from json part --- src/core/json_serializer.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/json_serializer.hpp b/src/core/json_serializer.hpp index 632e5bff0c..33b5336930 100644 --- a/src/core/json_serializer.hpp +++ b/src/core/json_serializer.hpp @@ -155,7 +155,9 @@ namespace JSon template T getDeserialized(const JT& value) { - if constexpr (hasAssignmentOperator) + if (value.isUndefined()) + return {}; + else if constexpr (hasAssignmentOperator) return value.toVariant().template value(); else if constexpr (isQtStreamable) { @@ -184,6 +186,12 @@ namespace JSon { return impl::deserialize(doc); } + + template + T deserialize(const QJsonValue& value) + { + return impl::deserialize(value); + } } #endif From 161e0a20d8fc8961fb13e7348f54658c623c611c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 10 Nov 2023 20:47:38 +0100 Subject: [PATCH 139/151] PUT DOWN BEFORE JSON USAGE Reflect person data --- src/database/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt index 64674ab931..3602a07c99 100644 --- a/src/database/CMakeLists.txt +++ b/src/database/CMakeLists.txt @@ -10,6 +10,8 @@ find_package(Threads REQUIRED) find_package(MagicEnum REQUIRED) find_package(OpenCV REQUIRED) +include(${PROJECT_SOURCE_DIR}/tools/reflect++/Reflect++.cmake) + set(DATABASE_SOURCES actions.hpp apeople_information_accessor.hpp @@ -91,6 +93,15 @@ target_include_directories(database set_target_properties(database PROPERTIES AUTOMOC TRUE) +ReflectFiles(ReflectionFiles + TARGET + database + SOURCES + person_data.hpp +) + +target_sources(database PRIVATE ${ReflectionFiles}) + generate_export_header(database) hideSymbols(database) From 1db438661024d6ada4fcc892885580ac35141e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 10 Nov 2023 20:44:58 +0100 Subject: [PATCH 140/151] Load people data using json serializer --- .../database_tools/implementation/json_to_backend.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/database/database_tools/implementation/json_to_backend.cpp b/src/database/database_tools/implementation/json_to_backend.cpp index 2be6efa936..8c89e63266 100644 --- a/src/database/database_tools/implementation/json_to_backend.cpp +++ b/src/database/database_tools/implementation/json_to_backend.cpp @@ -7,7 +7,10 @@ #include #include +#include "person_data_r++.hpp" + #include +#include #include "../json_to_backend.hpp" #include "database/ibackend.hpp" #include "database/igroup_operator.hpp" @@ -111,9 +114,9 @@ namespace Database std::ranges::transform(array, std::back_inserter(people), [](const QJsonValue& value) { - const auto person = value.toObject(); - - return PersonFullInfo{.name = PersonName(person["name"].toString())}; + const auto personData = JSon::deserialize(value); + const PersonFullInfo personFullInfo(personData); + return personFullInfo; }); delta.insert(people); From 6e5d4edcbafcaa233e0482c09c171ca260633b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 10 Nov 2023 20:47:03 +0100 Subject: [PATCH 141/151] Do not store empty values --- .../backends/sql_backends/people_information_accessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/backends/sql_backends/people_information_accessor.cpp b/src/database/backends/sql_backends/people_information_accessor.cpp index 85fc53c853..1dcfc4a53d 100644 --- a/src/database/backends/sql_backends/people_information_accessor.cpp +++ b/src/database/backends/sql_backends/people_information_accessor.cpp @@ -241,7 +241,7 @@ namespace Database if (query.numRowsAffected() == 0) // any update? id = Person::Id(); // nope - error } - else // id invalid? add new person or nothing when already exists + else if (d.name().isEmpty() == false) // id invalid? add new person (if name is set) or nothing when already exists { const PersonName pn = person(d.name()); @@ -300,7 +300,7 @@ namespace Database m_executor.exec(query); } } - else + else if (fingerprint_list.isEmpty() == false) { InsertQueryData insertData(TAB_FACES_FINGERPRINTS); insertData.addColumn("fingerprint"); From 3ae0fa33d7ff351f854aa3bd92365e150a49d9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 13 Nov 2023 17:00:39 +0100 Subject: [PATCH 142/151] Introduce IBackend utils --- src/database/backend_utils.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/database/backend_utils.hpp diff --git a/src/database/backend_utils.hpp b/src/database/backend_utils.hpp new file mode 100644 index 0000000000..779f003312 --- /dev/null +++ b/src/database/backend_utils.hpp @@ -0,0 +1,27 @@ + +#ifndef BACKEND_UTILS_HPP_INCLUDED +#define BACKEND_UTILS_HPP_INCLUDED + +#include "ibackend.hpp" +#include "iphoto_operator.hpp" + +namespace Database +{ + template + std::vector> getPhotoDelta(IBackend& backend, const Filter& filter = {}) + { + const auto ids = backend.photoOperator().getPhotos(filter); + std::vector> photos; + + std::ranges::transform(ids, std::back_inserter(photos), [&backend](const auto& id) + { + return backend.getPhotoDelta(id); + }); + + return photos; + } + +} + + +#endif From edd4aba048dbeffdfaf23cc27fa0246c2348f3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 13 Nov 2023 17:01:50 +0100 Subject: [PATCH 143/151] Implement read of Photo::Field::People --- .../memory_backend/memory_backend.cpp | 19 +++++++++++-- .../backends/sql_backends/sql_backend.cpp | 7 +++++ .../apeople_information_accessor.cpp | 9 ++++-- .../unit_tests_for_backends/people_tests.cpp | 28 +++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/database/backends/memory_backend/memory_backend.cpp b/src/database/backends/memory_backend/memory_backend.cpp index 3bfb81040f..2927107b75 100644 --- a/src/database/backends/memory_backend/memory_backend.cpp +++ b/src/database/backends/memory_backend/memory_backend.cpp @@ -253,13 +253,21 @@ namespace Database for (const auto& delta: deltas) { - auto it = m_db->m_photos.find(delta.getId()); - StoregeDelta currentDelta(*it); + auto currentDelta = IBackend::getPhotoDelta< + Photo::Field::Tags, + Photo::Field::Flags, + Photo::Field::Path, + Photo::Field::Geometry, + Photo::Field::GroupInfo, + Photo::Field::PHash, + Photo::Field::People + >(delta.getId()); photoChangeLogOperator().storeDifference(currentDelta, delta); currentDelta |= delta; + auto it = m_db->m_photos.find(delta.getId()); it = m_db->m_photos.erase(it); m_db->m_photos.insert(it, currentDelta); @@ -342,6 +350,13 @@ namespace Database if (fields.contains(Photo::Field::PHash)) delta.insert(data.phash); + if (fields.contains(Photo::Field::People)) + { + auto& peopleAccessor = peopleInformationAccessor(); + const auto peopleData = peopleAccessor.listPeopleFull(data.id); + delta.insert(peopleData); + } + return delta; } diff --git a/src/database/backends/sql_backends/sql_backend.cpp b/src/database/backends/sql_backends/sql_backend.cpp index 3c6a8d80a1..65dd2ec49b 100644 --- a/src/database/backends/sql_backends/sql_backend.cpp +++ b/src/database/backends/sql_backends/sql_backend.cpp @@ -427,6 +427,13 @@ namespace Database if (phash.has_value()) photoData.insert(*phash); } + + if (fields.contains(Photo::Field::People)) + { + auto& peopleAccessor = peopleInformationAccessor(); + const auto people = peopleAccessor.listPeopleFull(id); + photoData.insert(people); + } } return photoData; diff --git a/src/database/implementation/apeople_information_accessor.cpp b/src/database/implementation/apeople_information_accessor.cpp index 98b0221c92..2b9d90777c 100644 --- a/src/database/implementation/apeople_information_accessor.cpp +++ b/src/database/implementation/apeople_information_accessor.cpp @@ -74,8 +74,13 @@ namespace Database PersonFullInfo pfi; pfi.pi_id = pi.id; pfi.position = pi.rect; - pfi.fingerprint = fingerprints.find(pi.id)->second; - pfi.name = person(pi.p_id); + + auto fi_it = fingerprints.find(pi.id); + if (fi_it != fingerprints.end()) + pfi.fingerprint = fingerprints.find(pi.id)->second; + + if (pi.p_id.valid()) + pfi.name = person(pi.p_id); return pfi; }); diff --git a/src/database/unit_tests_for_backends/people_tests.cpp b/src/database/unit_tests_for_backends/people_tests.cpp index 2003a63e96..e53d997d46 100644 --- a/src/database/unit_tests_for_backends/people_tests.cpp +++ b/src/database/unit_tests_for_backends/people_tests.cpp @@ -1,9 +1,15 @@ +#include "database_tools/json_to_backend.hpp" +#include "unit_tests_utils/photos_with_people.json.hpp" +#include "backend_utils.hpp" + #include "common.hpp" // TODO: reenable +using testing::UnorderedElementsAre; + template struct PeopleTest: DatabaseTest { @@ -552,3 +558,25 @@ TYPED_TEST(PeopleTest, removePersonWhenItsRemovedFromTags) }); } */ + +TYPED_TEST(PeopleTest, readPeopleViaDataDelta) +{ + Database::JsonToBackend converter(*this->m_backend); + converter.append(PeopleDB::db); + + const auto photos = Database::getPhotoDelta(*this->m_backend); + + std::vector peopleInfo; + + for(const auto& photo: photos) + for(const auto& person: photo.template get()) + peopleInfo.push_back(person); + + std::set peopleNames; + std::ranges::transform(peopleInfo, std::inserter(peopleNames, peopleNames.end()), [](const auto& personInfo) + { + return personInfo.name.name(); + }); + + EXPECT_THAT(peopleNames, UnorderedElementsAre("person 1", "person 2", "person 3", "person 4", "person 5")); +} From 899f13f89ef5d09c5e4f5243a1699090da51870e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 13 Nov 2023 17:02:10 +0100 Subject: [PATCH 144/151] Fix obvious typo --- src/database/explicit_photo_delta.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/explicit_photo_delta.hpp b/src/database/explicit_photo_delta.hpp index c670e6c057..12be1014bb 100644 --- a/src/database/explicit_photo_delta.hpp +++ b/src/database/explicit_photo_delta.hpp @@ -77,7 +77,7 @@ namespace Photo { for(const Photo::Field field : magic_enum::enum_values()) if (other.has(field) && has(field) == false) - throw std::invalid_argument(std::string("Photo::Field: ") + magic_enum::enum_name(field).data() + " from DataDelta is part of this ExplicitDelta."); + throw std::invalid_argument(std::string("Photo::Field: ") + magic_enum::enum_name(field).data() + " from DataDelta is not part of this ExplicitDelta."); fill(); m_data |= other; From 2b981970138db550c2f02762f1119a83b2d14303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Fri, 17 Nov 2023 21:51:19 +0100 Subject: [PATCH 145/151] Simplify FaceEditor --- src/gui/desktop/utils/CMakeLists.txt | 1 - .../utils/implementation/faces_saver.cpp | 112 --------------- .../utils/implementation/faces_saver.hpp | 28 ---- .../implementation/people_editor_impl.hpp | 50 ++----- src/gui/desktop/utils/people_editor.cpp | 129 ++++++------------ src/gui/desktop/utils/people_editor.hpp | 10 +- 6 files changed, 62 insertions(+), 268 deletions(-) delete mode 100644 src/gui/desktop/utils/implementation/faces_saver.cpp delete mode 100644 src/gui/desktop/utils/implementation/faces_saver.hpp diff --git a/src/gui/desktop/utils/CMakeLists.txt b/src/gui/desktop/utils/CMakeLists.txt index 2192579204..9c6c01c350 100644 --- a/src/gui/desktop/utils/CMakeLists.txt +++ b/src/gui/desktop/utils/CMakeLists.txt @@ -15,7 +15,6 @@ set(UTILS_SOURCES grouppers/generator_utils.hpp grouppers/hdr_generator.cpp grouppers/hdr_generator.hpp - implementation/faces_saver.cpp batch_face_detector.cpp batch_face_detector.hpp collection_scanner.cpp diff --git a/src/gui/desktop/utils/implementation/faces_saver.cpp b/src/gui/desktop/utils/implementation/faces_saver.cpp deleted file mode 100644 index 3f71f1ab95..0000000000 --- a/src/gui/desktop/utils/implementation/faces_saver.cpp +++ /dev/null @@ -1,112 +0,0 @@ - -#include -#include - -#include "faces_saver.hpp" - - -FacesSaver::FacesSaver(Database::IDatabase& db) - : m_db(db) -{ - m_people = fetchPeople(); -} - - -FacesSaver::~FacesSaver() -{ - -} - - -void FacesSaver::store(FaceInfo& face) -{ - store_person_name(face); - store_fingerprint(face); - - // update names assigned to face - face.face.p_id = face.person.id(); - - // update fingerprints assigned to face - if (face.face.f_id.valid() == false) - face.face.f_id = face.fingerprint.id(); - - store_person_information(face); -} - - -void FacesSaver::store_person_name(FaceInfo& face) -{ - const bool nameChanged = - face.person.id().valid() == false && face.person.name().isEmpty() == false; - - if (nameChanged) - { - // introduce name associated with face to db (if needed) - const QString& name = face.person.name(); - - auto it = std::find_if(m_people.cbegin(), m_people.cend(), [name](const PersonName& d) - { - return d.name() == name; - }); - - if (it == m_people.cend()) // new name, store it in db - { - face.person = storeNewPerson(name); - m_people.push_back(face.person); - } - else - face.person = *it; - } -} - - -void FacesSaver::store_fingerprint(FaceInfo& face) -{ - if (face.fingerprint.id().valid() == false) - { - const PersonFingerprint::Id fid = - evaluate(m_db, [fingerprint = face.fingerprint](Database::IBackend& backend) - { - return backend.peopleInformationAccessor().store(fingerprint); - }); - - const PersonFingerprint fingerprint(fid, face.fingerprint.fingerprint()); - face.fingerprint = fingerprint; - } -} - - -void FacesSaver::store_person_information(const FaceInfo& face) -{ - const PersonInfo& faceInfo = face.face; - - m_db.exec([faceInfo](Database::IBackend& backend) - { - backend.peopleInformationAccessor().store(faceInfo); - }); -} - - -std::vector FacesSaver::fetchPeople() const -{ - return evaluate(Database::IBackend &)>(m_db, [](Database::IBackend& backend) - { - auto people = backend.peopleInformationAccessor().listPeople(); - - return people; - }); -} - - -PersonName FacesSaver::storeNewPerson(const QString& name) const -{ - const PersonName person = evaluate - (m_db, [name](Database::IBackend& backend) - { - const PersonName d(Person::Id(), name); - const auto id = backend.peopleInformationAccessor().store(d); - return PersonName(id, name); - }); - - return person; -} diff --git a/src/gui/desktop/utils/implementation/faces_saver.hpp b/src/gui/desktop/utils/implementation/faces_saver.hpp deleted file mode 100644 index b3b2ccbd4e..0000000000 --- a/src/gui/desktop/utils/implementation/faces_saver.hpp +++ /dev/null @@ -1,28 +0,0 @@ - -#ifndef FACES_SAVER_HPP_INCLUDED -#define FACES_SAVER_HPP_INCLUDED - -#include "people_editor_impl.hpp" - - -class FacesSaver: public IFacesSaver -{ - public: - explicit FacesSaver(Database::IDatabase &); - ~FacesSaver(); - - void store(FaceInfo &) override; - - private: - Database::IDatabase& m_db; - std::vector m_people; - - void store_person_name(FaceInfo& face); - void store_fingerprint(FaceInfo& face); - void store_person_information(const FaceInfo& face); - - std::vector fetchPeople() const; - PersonName storeNewPerson(const QString& name) const; -}; - -#endif diff --git a/src/gui/desktop/utils/implementation/people_editor_impl.hpp b/src/gui/desktop/utils/implementation/people_editor_impl.hpp index c4ca338191..ccb020b410 100644 --- a/src/gui/desktop/utils/implementation/people_editor_impl.hpp +++ b/src/gui/desktop/utils/implementation/people_editor_impl.hpp @@ -2,8 +2,7 @@ #ifndef PEOPLE_EDITOR_IMPL_HPP_INCLUDED #define PEOPLE_EDITOR_IMPL_HPP_INCLUDED -#include -#include +#include #include #include @@ -11,48 +10,23 @@ #include "../people_editor.hpp" -struct FaceInfo -{ - PersonInfo face; - PersonName person; - PersonFingerprint fingerprint; - - FaceInfo(const Photo::Id& id, const QRect& r) - { - face.ph_id = id; - face.rect = r; - } - - explicit FaceInfo(const PersonInfo& pi) - : face(pi) - { - - } -}; - -struct IFacesSaver -{ - virtual ~IFacesSaver() = default; - virtual void store(FaceInfo &) = 0; -}; - - struct Face: public IFace { - Face(const FaceInfo& fi, std::shared_ptr image, std::shared_ptr saver) + Face(const Photo::Id& id, const PersonFullInfo& fi, std::shared_ptr image, std::shared_ptr dbClient) : m_faceInfo(fi) , m_image(image) - , m_saver(saver) + , m_dbClient(dbClient) + , m_id(id) {} const QRect& rect() const override { - return m_faceInfo.face.rect; + return m_faceInfo.position; } const QString& name() const override { - return m_faceInfo.person.name(); + return m_faceInfo.name.name(); } const OrientedImage& image() const override @@ -62,17 +36,21 @@ struct Face: public IFace void setName(const QString& name) override { - m_faceInfo.person = PersonName(name); + m_faceInfo.name = PersonName(name); } void store() override { - m_saver->store(m_faceInfo); + m_dbClient->db().exec([id = m_id, pfi = m_faceInfo](Database::IBackend& backend) + { + backend.peopleInformationAccessor().store(id, pfi); + }); } - FaceInfo m_faceInfo; + PersonFullInfo m_faceInfo; std::shared_ptr m_image; - std::shared_ptr m_saver; + std::shared_ptr m_dbClient; + Photo::Id m_id; }; #endif diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index 2fd8845220..c87ce91910 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -30,7 +31,6 @@ #include #include -#include "implementation/faces_saver.hpp" #include "implementation/people_editor_impl.hpp" #include "people_editor.hpp" @@ -76,19 +76,6 @@ namespace }); } - std::vector fetchFacesFromDb(Database::IDatabase& db, const Photo::Id& ph_id) - { - return evaluate(Database::IBackend &)>(db, [ph_id](Database::IBackend& backend) - { - std::vector faces; - - const auto people = backend.peopleInformationAccessor().listPeople(ph_id); - std::ranges::copy_if(people, std::back_inserter(faces), [](const PersonInfo& pi) { return pi.rect.isValid(); }); - - return faces; - }); - } - PersonName personName(Database::IDatabase& db, const Person::Id& id) { const PersonName person = evaluate @@ -132,61 +119,65 @@ namespace return FaceRecognition(logger).fetchFaces(image); } - void calculateMissingFingerprints(std::vector& faces, const OrientedImage& image, const ILogger& logger) + void calculateMissingFingerprints(std::vector& faces, const OrientedImage& image, const ILogger& logger) { FaceRecognition face_recognition(logger); - for (FaceInfo& faceInfo: faces) + for (auto& faceInfo: faces) if (faceInfo.fingerprint.id().valid() == false) { - const auto fingerprint = face_recognition.getFingerprint(image, faceInfo.face.rect); + const auto fingerprint = face_recognition.getFingerprint(image, faceInfo.position); faceInfo.fingerprint = PersonFingerprint(fingerprint); } } - void recognizePeople(std::vector& faces, Database::IDatabase& db, const ILogger& logger) + void recognizePeople(FaceEditor::PeopleData& peopleData, Database::IDatabase& db, const ILogger& logger) { FaceRecognition face_recognition(logger); const auto people_fingerprints = fetchPeopleAndFingerprints(db); const std::vector& known_fingerprints = std::get<0>(people_fingerprints); - for (FaceInfo& faceInfo: faces) - if (faceInfo.person.name().isEmpty()) + for (auto& personData: peopleData.get()) + if (personData.name.name().isEmpty()) { - const int pos = face_recognition.recognize(faceInfo.fingerprint.fingerprint(), known_fingerprints); + const int pos = face_recognition.recognize(personData.fingerprint.fingerprint(), known_fingerprints); if (pos >= 0) { const std::vector& known_people = std::get<1>(people_fingerprints); - const Person::Id found_person = known_people[pos]; - faceInfo.person = personName(db, found_person); + const Person::Id found_person = known_people[safe_cast(pos)]; + personData.name = personName(db, found_person); } } } - std::vector findPeople(const OrientedImage& image, Database::IDatabase& db, const Photo::Id& id, const ILogger& logger) + FaceEditor::PeopleData findPeople(const OrientedImage& image, const Photo::Id& id, const ILogger& logger) { - std::vector result; + FaceEditor::PeopleData data(id); // analyze photo - look for faces const auto detected_faces = detectFaces(image, logger); - std::ranges::transform(detected_faces, std::back_inserter(result), [id](const QRect& rect) + auto& people = data.get(); + std::ranges::transform(detected_faces, std::back_inserter(people), [](const QRect& rect) { - return FaceInfo(id, rect); + PersonFullInfo pfi; + pfi.position = rect; + + return pfi; }); //calculate fingerprints - calculateMissingFingerprints(result, image, logger); + calculateMissingFingerprints(people, image, logger); - return result; + return data; } - void sortFaces(std::vector& faces) + void sortFaces(std::vector& faces) { // sort faces so they appear from left to right - std::sort(faces.begin(), faces.end(), [](const FaceInfo& lhs, const FaceInfo& rhs) { - const auto lhs_face = lhs.face.rect; - const auto rhs_face = rhs.face.rect; + std::sort(faces.begin(), faces.end(), [](const PersonFullInfo& lhs, const PersonFullInfo& rhs) { + const auto& lhs_face = lhs.position; + const auto& rhs_face = rhs.position; if (lhs_face.left() < rhs_face.left()) // lhs if left to rhs? - in order return true; @@ -214,39 +205,42 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) const QFileInfo pathInfo(path); const QString full_path = pathInfo.absoluteFilePath(); auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); - auto storage = getFaceSaver(); auto faces = findFaces(*image, id); recognizePeople(faces, m_db, *m_logger); + std::shared_ptr dbClient = m_db.attach("FaceEditor"); std::vector> result; - std::ranges::transform(faces, std::back_inserter(result), [&image, &storage](const FaceInfo& fi) + std::ranges::transform(faces.get(), std::back_inserter(result), [id = faces.getId(), &image, &dbClient](const auto& personData) { - return std::make_unique(fi, image, storage); + return std::make_unique(id, personData, image, dbClient); }); return result; } -std::vector FaceEditor::findFaces(const OrientedImage& image, const Photo::Id& id) +FaceEditor::PeopleData FaceEditor::findFaces(const OrientedImage& image, const Photo::Id& id) { - std::vector result; + FaceEditor::PeopleData result(id); const bool facesNotFound = wasPhotoAnalyzedAndHasNoFaces(m_db, id); // photo not analyzed yet (no records in db) or analyzed and we have data in db if (facesNotFound == false) { - const std::vector list_of_faces = fetchFacesFromDb(m_db, id); + result = evaluate(m_db, [id](Database::IBackend& backend) + { + return backend.getPhotoDelta(id); + }); // no data in db - if (list_of_faces.empty()) + if (result.get().empty()) { - result = findPeople(image, m_db, id, *m_logger); + result = findPeople(image, id, *m_logger); - if (result.empty()) + if (result.get().empty()) { // mark photo as one without faces m_db.exec([id](Database::IBackend& backend) @@ -259,54 +253,19 @@ std::vector FaceEditor::findFaces(const OrientedImage& image, const Ph } else { - // store face location and fingerprint in db - auto storage = getFaceSaver(); - for (auto& face: result) - storage->store(face); - } - } - else // data in db just use it - { - evaluate(m_db, [&result, &list_of_faces](Database::IBackend& backend) - { - std::vector pi_ids; - std::ranges::transform(list_of_faces, std::back_inserter(pi_ids), [](const PersonInfo& pi) + // store face location and fingerprint in db and update ids + evaluate(m_db, [&result](Database::IBackend& backend) { - return pi.id; - }); + backend.update({result}); - const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(pi_ids); - assert(fingerprints.size() == list_of_faces.size()); - - for (const PersonInfo& pi: list_of_faces) - { - FaceInfo fi(pi); - fi.fingerprint = fingerprints.find(pi.id)->second; - - if (pi.p_id.valid()) - fi.person = backend.peopleInformationAccessor().person(pi.p_id); - - result.push_back(fi); - }; - }); + // refetch data to get updated ids + result = backend.getPhotoDelta(result.getId()); + }); + } } } - sortFaces(result); + sortFaces(result.get()); return result; } - - -std::shared_ptr FaceEditor::getFaceSaver() -{ - auto storage = m_facesSaver.lock(); - - if (storage.get() == nullptr) - { - storage = std::make_shared(m_db); - m_facesSaver = storage; - } - - return storage; -} diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 6930a15a0b..6114b3c425 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -40,24 +40,22 @@ class IFace }; -struct IFacesSaver; -class FaceInfo; - class FaceEditor { public: + using PeopleData = Photo::ExplicitDelta; + FaceEditor(Database::IDatabase &, ICoreFactoryAccessor &, const ILogger &); std::vector> getFacesFor(const Photo::Id &); private: - std::weak_ptr m_facesSaver; + std::unique_ptr m_logger; Database::IDatabase& m_db; ICoreFactoryAccessor& m_core; - std::vector findFaces(const OrientedImage &, const Photo::Id &); - std::shared_ptr getFaceSaver(); + PeopleData findFaces(const OrientedImage &, const Photo::Id &); }; #endif From 51ffdad48dc4a73f941d94b1a4c1ac288671e230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Nov 2023 20:00:42 +0100 Subject: [PATCH 146/151] Move recognition step to Face (from now on it's clients responsibility) --- .../implementation/people_editor_impl.hpp | 22 ++- src/gui/desktop/utils/people_editor.cpp | 130 ++++++++++-------- src/gui/desktop/utils/people_editor.hpp | 6 +- 3 files changed, 95 insertions(+), 63 deletions(-) diff --git a/src/gui/desktop/utils/implementation/people_editor_impl.hpp b/src/gui/desktop/utils/implementation/people_editor_impl.hpp index ccb020b410..468e45ccd7 100644 --- a/src/gui/desktop/utils/implementation/people_editor_impl.hpp +++ b/src/gui/desktop/utils/implementation/people_editor_impl.hpp @@ -9,11 +9,18 @@ #include "../people_editor.hpp" +struct IRecognizePerson +{ + virtual ~IRecognizePerson() = default; + virtual PersonName recognize(const PersonFullInfo &) = 0; +}; + struct Face: public IFace { - Face(const Photo::Id& id, const PersonFullInfo& fi, std::shared_ptr image, std::shared_ptr dbClient) + Face(const Photo::Id& id, const PersonFullInfo& fi, std::shared_ptr recognizer, std::shared_ptr image, std::shared_ptr dbClient) : m_faceInfo(fi) + , m_recognizer(recognizer) , m_image(image) , m_dbClient(dbClient) , m_id(id) @@ -29,6 +36,11 @@ struct Face: public IFace return m_faceInfo.name.name(); } + const PersonName& person() const override + { + return m_faceInfo.name; + } + const OrientedImage& image() const override { return *m_image; @@ -39,6 +51,13 @@ struct Face: public IFace m_faceInfo.name = PersonName(name); } + bool recognize() override + { + m_faceInfo.name = m_recognizer->recognize(m_faceInfo); + + return m_faceInfo.name.id().valid(); + } + void store() override { m_dbClient->db().exec([id = m_id, pfi = m_faceInfo](Database::IBackend& backend) @@ -48,6 +67,7 @@ struct Face: public IFace } PersonFullInfo m_faceInfo; + std::shared_ptr m_recognizer; std::shared_ptr m_image; std::shared_ptr m_dbClient; Photo::Id m_id; diff --git a/src/gui/desktop/utils/people_editor.cpp b/src/gui/desktop/utils/people_editor.cpp index c87ce91910..e3f44a54d5 100644 --- a/src/gui/desktop/utils/people_editor.cpp +++ b/src/gui/desktop/utils/people_editor.cpp @@ -76,44 +76,6 @@ namespace }); } - PersonName personName(Database::IDatabase& db, const Person::Id& id) - { - const PersonName person = evaluate - (db, [id](Database::IBackend& backend) - { - const auto people = backend.peopleInformationAccessor().person(id); - - return people; - }); - - return person; - } - - std::tuple, std::vector> fetchPeopleAndFingerprints(Database::IDatabase& db) - { - typedef std::tuple, std::vector> Result; - - return evaluate(db, [](Database::IBackend& backend) - { - std::vector people_fingerprints; - std::vector people; - - const auto all_people = backend.peopleInformationAccessor().listPeople(); - for(const auto& person: all_people) - { - const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(person.id()); - - if (fingerprints.empty() == false) - { - people_fingerprints.push_back(average_fingerprint(fingerprints)); - people.push_back(person.id()); - } - } - - return std::tuple(people_fingerprints, people); - }); - } - std::vector detectFaces(const OrientedImage& image, const ILogger& logger) { return FaceRecognition(logger).fetchFaces(image); @@ -131,26 +93,6 @@ namespace } } - void recognizePeople(FaceEditor::PeopleData& peopleData, Database::IDatabase& db, const ILogger& logger) - { - FaceRecognition face_recognition(logger); - const auto people_fingerprints = fetchPeopleAndFingerprints(db); - const std::vector& known_fingerprints = std::get<0>(people_fingerprints); - - for (auto& personData: peopleData.get()) - if (personData.name.name().isEmpty()) - { - const int pos = face_recognition.recognize(personData.fingerprint.fingerprint(), known_fingerprints); - - if (pos >= 0) - { - const std::vector& known_people = std::get<1>(people_fingerprints); - const Person::Id found_person = known_people[safe_cast(pos)]; - personData.name = personName(db, found_person); - } - } - } - FaceEditor::PeopleData findPeople(const OrientedImage& image, const Photo::Id& id, const ILogger& logger) { FaceEditor::PeopleData data(id); @@ -189,9 +131,76 @@ namespace } } +class Recognizer: public IRecognizePerson +{ + public: + Recognizer(Database::IDatabase& db, const ILogger& logger) + : m_logger(logger.subLogger("Recognizer")) + { + fetchPeopleAndFingerprints(db); + } + + PersonName recognize(const PersonFullInfo& pi) override + { + FaceRecognition face_recognition(*m_logger); + PersonName result; + + const std::vector& known_fingerprints = std::get<0>(m_fingerprints); + + if (pi.name.name().isEmpty()) + { + const int pos = face_recognition.recognize(pi.fingerprint.fingerprint(), known_fingerprints); + + if (pos >= 0) + { + const std::vector& known_people = std::get<1>(m_fingerprints); + const Person::Id found_person = known_people[safe_cast(pos)]; + result = m_people.at(found_person); + } + } + + return result; + } + + private: + using Fingerprints = std::tuple, std::vector>; + using People = std::map; + Fingerprints m_fingerprints; + People m_people; + std::unique_ptr m_logger; + + void fetchPeopleAndFingerprints(Database::IDatabase& db) + { + typedef std::tuple, std::vector> Result; + + evaluate(db, [this](Database::IBackend& backend) + { + std::vector people_fingerprints; + std::vector people; + + const auto all_people = backend.peopleInformationAccessor().listPeople(); + + for(const auto& person: all_people) + { + m_people.emplace(person.id(), person); + const auto fingerprints = backend.peopleInformationAccessor().fingerprintsFor(person.id()); + + if (fingerprints.empty() == false) + { + people_fingerprints.push_back(average_fingerprint(fingerprints)); + people.push_back(person.id()); + } + } + + m_fingerprints = std::tuple(people_fingerprints, people); + }); + } +}; + FaceEditor::FaceEditor(Database::IDatabase& db, ICoreFactoryAccessor& core, const ILogger& logger) : m_logger(logger.subLogger("FaceEditor")) + , m_recognizer(std::make_shared(db, *m_logger)) , m_db(db) , m_core(core) { @@ -207,14 +216,13 @@ std::vector> FaceEditor::getFacesFor(const Photo::Id& id) auto image = std::make_shared(m_core.getExifReaderFactory().get(), full_path); auto faces = findFaces(*image, id); - recognizePeople(faces, m_db, *m_logger); std::shared_ptr dbClient = m_db.attach("FaceEditor"); std::vector> result; - std::ranges::transform(faces.get(), std::back_inserter(result), [id = faces.getId(), &image, &dbClient](const auto& personData) + std::ranges::transform(faces.get(), std::back_inserter(result), [id = faces.getId(), &image, &dbClient, recognizer = m_recognizer](const auto& personData) { - return std::make_unique(id, personData, image, dbClient); + return std::make_unique(id, personData, recognizer, image, dbClient); }); return result; diff --git a/src/gui/desktop/utils/people_editor.hpp b/src/gui/desktop/utils/people_editor.hpp index 6114b3c425..6aebe0dac8 100644 --- a/src/gui/desktop/utils/people_editor.hpp +++ b/src/gui/desktop/utils/people_editor.hpp @@ -26,6 +26,8 @@ #include +class Recognizer; + class IFace { public: @@ -33,9 +35,11 @@ class IFace virtual const QRect& rect() const = 0; virtual const QString& name() const = 0; + virtual const PersonName& person() const = 0; virtual const OrientedImage& image() const = 0; virtual void setName(const QString &) = 0; + virtual bool recognize() = 0; virtual void store() = 0; }; @@ -50,8 +54,8 @@ class FaceEditor std::vector> getFacesFor(const Photo::Id &); private: - std::unique_ptr m_logger; + std::shared_ptr m_recognizer; Database::IDatabase& m_db; ICoreFactoryAccessor& m_core; From a22ffcd26cf9ddbbe334c28b738f43e507174aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Sun, 19 Nov 2023 20:01:31 +0100 Subject: [PATCH 147/151] Mark automaticaly recognised faces with yellow To indicate it is something guesses, not sure --- src/gui/desktop/models/faces_model.cpp | 16 ++++++++++++++++ src/gui/desktop/models/faces_model.hpp | 3 ++- .../desktop/quick_items/Views/FacesDialog.qml | 5 +++++ tr/photo_broom_en.ts | 8 ++++---- tr/photo_broom_pl.ts | 4 ++-- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index 7c6710be11..96dd9bb809 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -93,6 +93,8 @@ QVariant FacesModel::data(const QModelIndex& index, int role) const return m_faces[row]->name(); else if (role == Roles::FaceRectRole) return m_faces[row]->rect(); + else if (role == Roles::UncertainRole) + return m_isUncertain[row]; } return {}; @@ -127,6 +129,20 @@ void FacesModel::updateFaceInformation(std::shared_ptrperson(); + + if (person.id().valid() == false) + { + m_isUncertain[i] = true; + m_faces[i]->recognize(); + } + } + endInsertRows(); } diff --git a/src/gui/desktop/models/faces_model.hpp b/src/gui/desktop/models/faces_model.hpp index 52864cb693..e51c4341ca 100644 --- a/src/gui/desktop/models/faces_model.hpp +++ b/src/gui/desktop/models/faces_model.hpp @@ -26,7 +26,7 @@ class FacesModel: public QAbstractListModel enum Roles { FaceRectRole = Qt::UserRole + 1, - _lastRole, + UncertainRole, }; Q_ENUMS(Roles) @@ -51,6 +51,7 @@ class FacesModel: public QAbstractListModel Database::IDatabase* m_database = nullptr; ICoreFactoryAccessor* m_core = nullptr; std::vector> m_faces; + std::vector m_isUncertain; QSize m_photoSize; int m_state = 0; diff --git a/src/gui/desktop/quick_items/Views/FacesDialog.qml b/src/gui/desktop/quick_items/Views/FacesDialog.qml index f24c46595a..6e01d1ec72 100644 --- a/src/gui/desktop/quick_items/Views/FacesDialog.qml +++ b/src/gui/desktop/quick_items/Views/FacesDialog.qml @@ -159,12 +159,17 @@ Item { required property var display // DisplayRole required property var index + required property bool uncertain readOnly: true hoverEnabled: true text: display placeholderText: qsTr("unknown") + palette { + base: uncertain? "yellow" : systemPalette.base + } + onPressed: { name.readOnly = false; editedItem(index); diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 0ad228f24f..16026018db 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -210,22 +210,22 @@ FacesDialog - + unknown unknown - + Mark found faces Mark found faces - + Detecting and analyzing faces Detecting and analyzing faces - + Could not detect any face. Could not detect any face. diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index c9e345aaef..d45487a8e9 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -217,12 +217,12 @@ FacesDialog - + unknown nieznane - + Mark found faces Podświetl znalezione twarze From 5669fa2a83a3303c31f76f60421c433b87d99bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Thu, 23 Nov 2023 21:37:25 +0100 Subject: [PATCH 148/151] Format --- src/gui/desktop/quick_items/Views/FacesDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/FacesDialog.qml b/src/gui/desktop/quick_items/Views/FacesDialog.qml index 6e01d1ec72..df6258ee98 100644 --- a/src/gui/desktop/quick_items/Views/FacesDialog.qml +++ b/src/gui/desktop/quick_items/Views/FacesDialog.qml @@ -157,9 +157,9 @@ Item { delegate: TextField { id: name - required property var display // DisplayRole required property var index - required property bool uncertain + required property var display // DisplayRole + required property bool uncertain // UncertainRole readOnly: true hoverEnabled: true From 2e159c5bf03f4f517b8c2db039e9c2d304445479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 25 Dec 2023 09:45:30 +0100 Subject: [PATCH 149/151] Do not save uncertain names in db, only edited ones --- src/gui/desktop/models/faces_model.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index 96dd9bb809..ce1ff37b9c 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -111,7 +111,10 @@ bool FacesModel::setData(const QModelIndex& index, const QVariant& data, int rol { const std::size_t r = static_cast(index.row()); if (role == Qt::EditRole && index.column() == 0 && r < m_faces.size()) + { m_faces[r]->setName(data.toString()); + m_isUncertain[r] = false; + } return true; } @@ -178,6 +181,9 @@ void FacesModel::updateDetectionState(int state) void FacesModel::apply() { - for(auto& face: m_faces) - face->store(); + assert(m_faces.size() == m_isUncertain.size()); + + for(std::size_t i = 0; i < m_faces.size(); i++) + if (not m_isUncertain[i]) + m_faces[i]->store(); } From f453788725576966934b13d27c349c251084c5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Mon, 25 Dec 2023 10:14:04 +0100 Subject: [PATCH 150/151] Notify about changed role --- src/gui/desktop/models/faces_model.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gui/desktop/models/faces_model.cpp b/src/gui/desktop/models/faces_model.cpp index ce1ff37b9c..988f75e6ae 100644 --- a/src/gui/desktop/models/faces_model.cpp +++ b/src/gui/desktop/models/faces_model.cpp @@ -113,7 +113,13 @@ bool FacesModel::setData(const QModelIndex& index, const QVariant& data, int rol if (role == Qt::EditRole && index.column() == 0 && r < m_faces.size()) { m_faces[r]->setName(data.toString()); - m_isUncertain[r] = false; + + if (m_isUncertain[r]) + { + m_isUncertain[r] = false; + + emit dataChanged(index, index, {UncertainRole}); + } } return true; From 6f14052f454d92792a204954e7f9a0a8637f11d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Walenciak?= Date: Tue, 26 Dec 2023 08:53:30 +0100 Subject: [PATCH 151/151] Disable Batch face detector as feature in progress --- src/gui/desktop/quick_items/Views/MainWindow.qml | 2 +- tr/photo_broom_en.ts | 5 ----- tr/photo_broom_pl.ts | 7 +------ 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/gui/desktop/quick_items/Views/MainWindow.qml b/src/gui/desktop/quick_items/Views/MainWindow.qml index bba0313fc1..782f4c3138 100644 --- a/src/gui/desktop/quick_items/Views/MainWindow.qml +++ b/src/gui/desktop/quick_items/Views/MainWindow.qml @@ -90,7 +90,7 @@ ApplicationWindow { Action { text: qsTr("S&eries detector..."); onTriggered: { toolsStackView.currentIndex = 1; mainView.currentIndex = 1; } } Action { text: qsTr("Ph&oto data completion..."); onTriggered: { toolsStackView.currentIndex = 2; mainView.currentIndex = 1; } } Action { text: qsTr("Look for &duplicates"); onTriggered: { toolsStackView.currentIndex = 3; mainView.currentIndex = 1; } } - Action { text: qsTr("&Face detection..."); onTriggered: { toolsStackView.currentIndex = 4; mainView.currentIndex = 1; } } + //Action { text: qsTr("&Face detection..."); onTriggered: { toolsStackView.currentIndex = 4; mainView.currentIndex = 1; } } } Menu { id: settingsMenu diff --git a/tr/photo_broom_en.ts b/tr/photo_broom_en.ts index 16026018db..2e509bec22 100644 --- a/tr/photo_broom_en.ts +++ b/tr/photo_broom_en.ts @@ -370,11 +370,6 @@ Check paths in configuration window. Look for &duplicates - - - &Face detection... - - &Settings diff --git a/tr/photo_broom_pl.ts b/tr/photo_broom_pl.ts index d45487a8e9..19934aa81f 100644 --- a/tr/photo_broom_pl.ts +++ b/tr/photo_broom_pl.ts @@ -375,12 +375,7 @@ Sprawdź poprawność ścieżek w konfiguracji. Wyszukaj &duplikaty - - &Face detection... - - - - + &Settings U&stawienia