aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2022-09-22 00:03:01 -0400
committerGravatar Chris Xiong <chirs241097@gmail.com> 2022-09-22 00:03:01 -0400
commit8ece6d3ec1b0105047c192c0aa044e4257118e01 (patch)
treeca4202e6e41d31c6f73fc7514c237a5b2b2c2764
parent1d41325a9685cf677f8eeaa4940f032931fd8780 (diff)
downloaddeduper-8ece6d3ec1b0105047c192c0aa044e4257118e01.tar.xz
Add "reverse image search".
Fixed a stupid performance degradation in the signature library in the process.
-rw-r--r--qdeduper/imageitem.cpp1
-rw-r--r--qdeduper/mingui.cpp69
-rw-r--r--qdeduper/mingui.hpp21
-rw-r--r--qdeduper/sigdb_qt.cpp16
-rw-r--r--qdeduper/sigdb_qt.hpp3
-rw-r--r--xsig/include/signature_db.hpp2
-rw-r--r--xsig/src/signature.cpp6
-rw-r--r--xsig/src/signature_db.cpp79
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(