diff options
author | Chris Xiong <chirs241097@gmail.com> | 2022-09-22 00:03:01 -0400 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2022-09-22 00:03:01 -0400 |
commit | 8ece6d3ec1b0105047c192c0aa044e4257118e01 (patch) | |
tree | ca4202e6e41d31c6f73fc7514c237a5b2b2c2764 | |
parent | 1d41325a9685cf677f8eeaa4940f032931fd8780 (diff) | |
download | deduper-8ece6d3ec1b0105047c192c0aa044e4257118e01.tar.xz |
Add "reverse image search".
Fixed a stupid performance degradation in the signature library
in the process.
-rw-r--r-- | qdeduper/imageitem.cpp | 1 | ||||
-rw-r--r-- | qdeduper/mingui.cpp | 69 | ||||
-rw-r--r-- | qdeduper/mingui.hpp | 21 | ||||
-rw-r--r-- | qdeduper/sigdb_qt.cpp | 16 | ||||
-rw-r--r-- | qdeduper/sigdb_qt.hpp | 3 | ||||
-rw-r--r-- | xsig/include/signature_db.hpp | 2 | ||||
-rw-r--r-- | xsig/src/signature.cpp | 6 | ||||
-rw-r--r-- | xsig/src/signature_db.cpp | 79 |
8 files changed, 150 insertions, 47 deletions
diff --git a/qdeduper/imageitem.cpp b/qdeduper/imageitem.cpp index f18d32c..9ee7c6f 100644 --- a/qdeduper/imageitem.cpp +++ b/qdeduper/imageitem.cpp @@ -9,7 +9,6 @@ #include <QFontMetrics> #include <QPainter> #include <QLocale> -#include <qnamespace.h> #define DEBUGPAINT 0 diff --git a/qdeduper/mingui.cpp b/qdeduper/mingui.cpp index ba802c7..f93fda3 100644 --- a/qdeduper/mingui.cpp +++ b/qdeduper/mingui.cpp @@ -126,11 +126,11 @@ DeduperMainWindow::DeduperMainWindow() auto &k = keys[i]; QAction *a = new QAction(); a->setShortcut(QKeySequence(k)); - QObject::connect(a, &QAction::triggered, [this, i](){this->mark_toggle(i);}); + QObject::connect(a, &QAction::triggered, std::bind(&DeduperMainWindow::mark_toggle, this, i)); selhk.push_back(a); QAction *sa = new QAction(); sa->setShortcut(QKeySequence(Qt::Modifier::SHIFT | k)); - QObject::connect(sa, &QAction::triggered, [this, i](){this->mark_all_but(i);}); + QObject::connect(sa, &QAction::triggered, std::bind(&DeduperMainWindow::mark_all_but, this, i)); selhk.push_back(a); } this->addActions(selhk); @@ -220,7 +220,31 @@ void DeduperMainWindow::setup_menu() this->addAction(loadlist); file->addSeparator(); - file->addAction("Search for Image..."); + QAction *search_img = file->addAction("Search for Image..."); + QObject::connect(search_img, &QAction::triggered, [this]{ + QString fpath = QFileDialog::getOpenFileName(this, "Select Image", QString(), "Image file"); + if (fpath.isNull()) return; + auto sim = this->sdb->search_file(qstring_to_path(fpath)); + if (sim.empty()) + { + this->sb->showMessage("No similar image found.", 2000); + return; + } + this->vm = ViewMode::view_searchresult; + std::vector<fs::path> ps; + std::map<std::pair<size_t, size_t>, double> dm; + for (size_t i = 0; i < sim.size(); ++i) + { + auto &s = sim[i]; + ps.push_back(this->sdb->get_image_path(s.first)); + dm[std::make_pair(0, i)] = s.second; + } + this->show_images(ps); + this->update_distances(dm); + this->sb->showMessage("Use next group / previous group to go back."); + this->permamsg->setText("Viewing image search result"); + }); + menuact["search_image"] = search_img; file->addSeparator(); file->addAction("Preferences..."); file->addAction("Exit"); @@ -230,6 +254,7 @@ void DeduperMainWindow::setup_menu() menuact["next_group"] = nxtgrp; nxtgrp->setShortcut(QKeySequence(Qt::Key::Key_M)); QObject::connect(nxtgrp, &QAction::triggered, [this] { + if (this->vm == ViewMode::view_searchresult) { this->show_group(curgroup); return; } if (this->sdb && curgroup + 1 < this->sdb->num_groups()) this->show_group(++curgroup); }); @@ -240,6 +265,7 @@ void DeduperMainWindow::setup_menu() menuact["prev_group"] = prvgrp; prvgrp->setShortcut(QKeySequence(Qt::Key::Key_Z)); QObject::connect(prvgrp, &QAction::triggered, [this] { + if (this->vm == ViewMode::view_searchresult) { this->show_group(curgroup); return; } if (this->sdb && curgroup > 0) this->show_group(--curgroup); }); @@ -306,14 +332,21 @@ void DeduperMainWindow::update_actions() menuact["save_db"]->setEnabled(false); menuact["load_list"]->setEnabled(false); menuact["save_list"]->setEnabled(false); + menuact["search_image"]->setEnabled(false); return; } menuact["skip_group"]->setEnabled(true); menuact["prev_group"]->setEnabled(curgroup > 0); menuact["next_group"]->setEnabled(curgroup + 1 < sdb->num_groups()); + menuact["search_image"]->setEnabled(true); menuact["save_db"]->setEnabled(true); menuact["load_list"]->setEnabled(true); menuact["save_list"]->setEnabled(true); + if (vm == ViewMode::view_searchresult) + { + menuact["next_group"]->setEnabled(true); + menuact["prev_group"]->setEnabled(true); + } } void DeduperMainWindow::show_images(const std::vector<fs::path> &fns) @@ -350,7 +383,10 @@ void DeduperMainWindow::update_distances(const std::map<std::pair<size_t, size_t ka = QKeySequence(keys[p.first.first]).toString(); if (p.first.second < keys.size()) kb = QKeySequence(keys[p.first.second]).toString(); - r += QString("%1 <-> %2: %3\n").arg(ka).arg(kb).arg(QString::number(p.second)); + if (vm == ViewMode::view_normal) + r += QString("%1 <-> %2: %3\n").arg(ka).arg(kb).arg(QString::number(p.second)); + else + r += QString("%1 : %2\n").arg(kb).arg(QString::number(p.second));; } infopanel->setText(r); } @@ -444,6 +480,7 @@ void DeduperMainWindow::scan_dirs(std::vector<std::pair<fs::path, bool>> paths) } int flsize = fs->file_list().size() - 1; QMetaObject::invokeMethod(this->pd, [flsize, this] {this->pd->setMaximum(flsize);}, Qt::ConnectionType::QueuedConnection); + if (this->sdb) delete this->sdb; this->sdb = new SignatureDB(); QObject::connect(this->sdb, &SignatureDB::image_scanned, this, [this](size_t n) { static auto lt = std::chrono::steady_clock::now(); @@ -470,6 +507,7 @@ void DeduperMainWindow::scan_dirs(std::vector<std::pair<fs::path, bool>> paths) this->pd->reset(); this->pd->close(); this->curgroup = 0; + this->vm = ViewMode::view_normal; this->show_group(this->curgroup); }, Qt::ConnectionType::QueuedConnection); QObject::connect(pd, &QProgressDialog::canceled, [this] { @@ -482,6 +520,7 @@ void DeduperMainWindow::show_group(size_t gid) { if (!sdb || gid >= sdb->num_groups()) return; + this->vm = ViewMode::view_normal; auto g = sdb->get_group(gid); current_set.clear(); std::for_each(g.begin(), g.end(), [this](auto id){current_set.push_back(sdb->get_image_path(id));}); @@ -493,6 +532,12 @@ void DeduperMainWindow::show_group(size_t gid) void DeduperMainWindow::mark_toggle(size_t x) { + if (vm == ViewMode::view_searchresult) + { + mark_none(false); + sb->showMessage("Marking images in search result is disabled.", 2000); + return; + } if (x < marks.size()) { marks[x] = !marks[x]; @@ -507,6 +552,12 @@ void DeduperMainWindow::mark_toggle(size_t x) void DeduperMainWindow::mark_all_but(size_t x) { + if (vm == ViewMode::view_searchresult) + { + mark_none(false); + sb->showMessage("Marking images in search result is disabled.", 2000); + return; + } if (x < marks.size()) { for (size_t i = 0; i < marks.size(); ++i) @@ -524,6 +575,12 @@ void DeduperMainWindow::mark_all_but(size_t x) void DeduperMainWindow::mark_all() { + if (vm == ViewMode::view_searchresult) + { + mark_none(false); + sb->showMessage("Marking images in search result is disabled.", 2000); + return; + } for (size_t i = 0; i < marks.size(); ++i) { marks[i] = true; @@ -532,7 +589,7 @@ void DeduperMainWindow::mark_all() mark_view_update(); } -void DeduperMainWindow::mark_none() +void DeduperMainWindow::mark_none(bool msg) { for (size_t i = 0; i < marks.size(); ++i) { @@ -540,7 +597,7 @@ void DeduperMainWindow::mark_none() if (marked.find(current_set[i]) != marked.end()) marked.erase(marked.find(current_set[i])); } - mark_view_update(); + mark_view_update(msg); } void DeduperMainWindow::mark_view_update(bool update_msg) diff --git a/qdeduper/mingui.hpp b/qdeduper/mingui.hpp index e4b1c3f..9f3ff2a 100644 --- a/qdeduper/mingui.hpp +++ b/qdeduper/mingui.hpp @@ -28,6 +28,12 @@ class ImageItemDelegate; namespace fs = std::filesystem; +enum ViewMode +{ + view_normal, + view_searchresult +}; + class DeduperMainWindow : public QMainWindow { Q_OBJECT @@ -46,12 +52,13 @@ private: SignatureDB *sdb = nullptr; FileScanner *fsc = nullptr; - std::size_t curgroup; + size_t curgroup; + ViewMode vm; bool nohotkeywarn; - void mark_toggle(std::size_t x); - void mark_all_but(std::size_t x); + void mark_toggle(size_t x); + void mark_all_but(size_t x); void mark_all(); - void mark_none(); + void mark_none(bool msg = true); void mark_view_update(bool update_msg = true); fs::path::string_type common_prefix(const std::vector<fs::path> &fns); std::vector<bool> marks; @@ -65,8 +72,8 @@ public: void setup_menu(); void show_images(const std::vector<fs::path> &fns); - void update_distances(const std::map<std::pair<std::size_t, std::size_t>, double> &d); - void update_viewstatus(std::size_t cur, std::size_t size); + void update_distances(const std::map<std::pair<size_t, size_t>, double> &d); + void update_viewstatus(size_t cur, size_t size); void save_list(); void load_list(); @@ -78,7 +85,7 @@ public Q_SLOTS: Q_SIGNALS: void next(); void prev(); - void switch_group(std::size_t group); + void switch_group(size_t group); }; #endif diff --git a/qdeduper/sigdb_qt.cpp b/qdeduper/sigdb_qt.cpp index 67cc3e6..733a198 100644 --- a/qdeduper/sigdb_qt.cpp +++ b/qdeduper/sigdb_qt.cpp @@ -97,6 +97,22 @@ void SignatureDB::interrupt_scan() sdb->populate_interrupt(); } +std::vector<std::pair<size_t, double>> SignatureDB::search_file(const fs::path &files) +{ + populate_cfg_t pcfg = { + 3, + 3, + cfg_full, + cfg_subslice, + 0.3, + nullptr, + 0 + }; + if(sdb) + return sdb->search_image(files, pcfg, false); + return {}; +} + size_t SignatureDB::num_groups() { return groups.size(); diff --git a/qdeduper/sigdb_qt.hpp b/qdeduper/sigdb_qt.hpp index 112ffa9..772c264 100644 --- a/qdeduper/sigdb_qt.hpp +++ b/qdeduper/sigdb_qt.hpp @@ -33,6 +33,9 @@ public: void scan_files(const std::vector<fs::path> &files, int njobs); void interrupt_scan(); + + std::vector<std::pair<size_t, double>> search_file(const fs::path &files); + size_t num_groups(); std::vector<size_t> get_group(size_t gid); std::map<std::pair<size_t, size_t>, double> group_distances(size_t gid); diff --git a/xsig/include/signature_db.hpp b/xsig/include/signature_db.hpp index a74e90b..9b14fbb 100644 --- a/xsig/include/signature_db.hpp +++ b/xsig/include/signature_db.hpp @@ -87,6 +87,8 @@ public: void populate(const std::vector<fs::path> &paths, const populate_cfg_t &cfg); void populate_interrupt(); + std::vector<std::pair<size_t, double>> search_image(const fs::path &path, const populate_cfg_t &cfg, bool insert = false); + //disjoint set for keeping similar images in the same group //some of these probably shouldn't be public. TBD... void ds_init(); diff --git a/xsig/src/signature.cpp b/xsig/src/signature.cpp index 1f0ec28..0f0b2e9 100644 --- a/xsig/src/signature.cpp +++ b/xsig/src/signature.cpp @@ -226,19 +226,19 @@ signature signature::clone() const double signature::length() const { - if (!p) {fprintf(stderr, "length: null signature"); return -1;} + if (!p) {fprintf(stderr, "length: null signature\n"); return -1;} return p->length(); } double signature::distance(const signature &o) const { - if (!p || !o.p) {fprintf(stderr, "distance: null signature"); return -1;} + if (!p || !o.p) {fprintf(stderr, "distance: null signature\n"); return -1;} return p->distance(*o.p); } bool signature::operator==(const signature &o) const { - if (!p || !o.p) {fprintf(stderr, "eq: null signature"); return false;} + if (!p || !o.p) {fprintf(stderr, "eq: null signature\n"); return false;} return *p == *o.p; } diff --git a/xsig/src/signature_db.cpp b/xsig/src/signature_db.cpp index 6b328d6..5396d1d 100644 --- a/xsig/src/signature_db.cpp +++ b/xsig/src/signature_db.cpp @@ -398,36 +398,7 @@ void signature_db::populate(const std::vector<fs::path> &paths, const populate_c std::atomic<size_t> count(0); auto job_func = [&, this](int thid, const fs::path& path) { - subsliced_signature ss = subsliced_signature::from_path(path, cfg.nsliceh, cfg.nslicev, cfg.scfg_full, cfg.scfg_subslice); - - this->lock(); - std::set<size_t> v; - size_t dbid = this->put_signature(path, ss.full); - - this->batch_find_subslice_begin(); - for (size_t i = 0; i < cfg.nsliceh * cfg.nslicev; ++i) - { - std::vector<subslice_t> ssmatches = this->find_subslice(ss.subslices[i]); - for (auto &match : ssmatches) - { - if (match.slice == i && v.find(match.id) == v.end()) - { - signature othersig; - std::tie(std::ignore, othersig) = this->get_signature(match.id); - double dist = ss.full.distance(othersig); - if (dist < cfg.threshold) - this->put_dupe_pair(dbid, match.id, dist); - } - } - } - this->batch_find_subslice_end(); - - this->batch_put_subslice_begin(); - for (size_t i = 0; i < cfg.nsliceh * cfg.nslicev; ++i) - this->put_subslice(dbid, i, ss.subslices[i]); - this->batch_put_subslice_end(); - - this->unlock(); + this->search_image(path, cfg, true); ++count; cfg.callback(count.load(), thid); }; @@ -441,12 +412,60 @@ void signature_db::populate(const std::vector<fs::path> &paths, const populate_c delete p->tp; p->tp = nullptr; } + void signature_db::populate_interrupt() { if (p->tp) p->tp->terminate(); } +std::vector<std::pair<size_t, double>> signature_db::search_image(const fs::path &path, const populate_cfg_t &cfg, bool insert) +{ + subsliced_signature ss = subsliced_signature::from_path(path, cfg.nsliceh, cfg.nslicev, cfg.scfg_full, cfg.scfg_subslice); + if (!ss.full.valid()) return {}; + + this->lock(); + std::set<size_t> v; + std::vector<std::pair<size_t, double>> ret; + size_t dbid = 0; + if (insert) dbid = this->put_signature(path, ss.full); + + this->batch_find_subslice_begin(); + for (size_t i = 0; i < cfg.nsliceh * cfg.nslicev; ++i) + { + std::vector<subslice_t> ssmatches = this->find_subslice(ss.subslices[i]); + for (auto &match : ssmatches) + { + if (match.slice == i && v.find(match.id) == v.end()) + { + signature othersig; + std::tie(std::ignore, othersig) = this->get_signature(match.id); + double dist = ss.full.distance(othersig); + if (dist < cfg.threshold) + { + if (insert) + this->put_dupe_pair(dbid, match.id, dist); + else + ret.emplace_back(match.id, dist); + v.insert(match.id); + } + } + } + } + this->batch_find_subslice_end(); + + if (insert) + { + this->batch_put_subslice_begin(); + for (size_t i = 0; i < cfg.nsliceh * cfg.nslicev; ++i) + this->put_subslice(dbid, i, ss.subslices[i]); + this->batch_put_subslice_end(); + } + + this->unlock(); + return ret; +} + void signature_db::ds_init() { sqlite3_exec(p->db, R"sql( |