[assessment-tool] Save/SaveAs

This commit is contained in:
Yuri Gorshenin 2017-03-29 15:38:12 +03:00 committed by Vladimir Byko-Ianko
parent bf70d80a54
commit b54b9fd163
30 changed files with 469 additions and 126 deletions

View file

@ -24,6 +24,8 @@ set(
engine.cpp
engine.hpp
everywhere_search_params.hpp
feature_loader.cpp
feature_loader.hpp
feature_offset_match.hpp
features_filter.cpp
features_filter.hpp

26
search/feature_loader.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "search/feature_loader.hpp"
#include "indexer/feature_decl.hpp"
#include "base/stl_add.hpp"
namespace search
{
FeatureLoader::FeatureLoader(Index const & index) : m_index(index) {}
bool FeatureLoader::Load(FeatureID const & id, FeatureType & ft)
{
ASSERT(m_checker.CalledOnOriginalThread(), ());
auto const & mwmId = id.m_mwmId;
if (!m_guard || m_guard->GetId() != mwmId)
m_guard = my::make_unique<Index::FeaturesLoaderGuard>(m_index, mwmId);
return m_guard->GetFeatureByIndex(id.m_index, ft);
}
void FeatureLoader::Reset()
{
ASSERT(m_checker.CalledOnOriginalThread(), ());
m_guard.reset();
}
} // namespace search

40
search/feature_loader.hpp Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include "indexer/index.hpp"
#include "indexer/scales.hpp"
#include "base/assert.hpp"
#include "base/macros.hpp"
#include "base/thread_checker.hpp"
#include <memory>
#include <utility>
class FeatureType;
struct FeatureID;
namespace search
{
class FeatureLoader
{
public:
explicit FeatureLoader(Index const & index);
WARN_UNUSED_RESULT bool Load(FeatureID const & id, FeatureType & ft);
void Reset();
template <typename ToDo>
void ForEachInRect(m2::RectD const & rect, ToDo && toDo)
{
ASSERT(m_checker.CalledOnOriginalThread(), ());
m_index.ForEachInRect(std::forward<ToDo>(toDo), rect, scales::GetUpperScale());
}
private:
Index const & m_index;
std::unique_ptr<Index::FeaturesLoaderGuard> m_guard;
ThreadChecker m_checker;
};
} // namespace search

View file

@ -223,26 +223,6 @@ bool House::GetNearbyMatch(ParsedNumber const & number) const
return m_number.IsIntersect(number, HN_NEARBY_DISTANCE);
}
FeatureLoader::FeatureLoader(Index const & index) : m_index(index) {}
void FeatureLoader::CreateLoader(MwmSet::MwmId const & mwmId)
{
if (!m_guard || mwmId != m_guard->GetId())
m_guard = make_unique<Index::FeaturesLoaderGuard>(m_index, mwmId);
}
bool FeatureLoader::Load(FeatureID const & id, FeatureType & f)
{
CreateLoader(id.m_mwmId);
return m_guard->GetFeatureByIndex(id.m_index, f);
}
template <class ToDo>
void FeatureLoader::ForEachInRect(m2::RectD const & rect, ToDo toDo)
{
m_index.ForEachInRect(toDo, rect, scales::GetUpperScale());
}
m2::RectD Street::GetLimitRect(double offsetMeters) const
{
m2::RectD rect;
@ -481,7 +461,7 @@ int HouseDetector::LoadStreets(vector<FeatureID> const & ids)
}
}
m_loader.Free();
m_loader.Reset();
return count;
}

View file

@ -1,4 +1,6 @@
#pragma once
#include "search/feature_loader.hpp"
#include "search/projection_on_street.hpp"
#include "indexer/feature_decl.hpp"
@ -12,26 +14,10 @@
#include "std/string.hpp"
#include "std/queue.hpp"
class Index;
namespace search
{
class FeatureLoader
{
Index const & m_index;
unique_ptr<Index::FeaturesLoaderGuard> m_guard;
void CreateLoader(MwmSet::MwmId const & mwmId);
public:
FeatureLoader(Index const & index);
WARN_UNUSED_RESULT bool Load(FeatureID const & id, FeatureType & f);
inline void Free() { m_guard.reset(); }
template <class ToDo> void ForEachInRect(m2::RectD const & rect, ToDo toDo);
};
struct ParsedNumber
{
string m_fullN;

View file

@ -23,6 +23,7 @@ HEADERS += \
emitter.hpp \
engine.hpp \
everywhere_search_params.hpp \
feature_loader.hpp \
feature_offset_match.hpp \
features_filter.hpp \
features_layer.hpp \
@ -90,6 +91,7 @@ SOURCES += \
dummy_rank_table.cpp \
editor_delegate.cpp \
engine.cpp \
feature_loader.cpp \
features_filter.cpp \
features_layer.cpp \
features_layer_matcher.cpp \

View file

@ -19,7 +19,6 @@ set(
languages_list.hpp
main_model.cpp
main_model.hpp
main_model.hpp
main_view.cpp
main_view.hpp
model.hpp

View file

@ -1,5 +1,12 @@
#include "search/search_quality/assessment_tool/context.hpp"
#include "search/feature_loader.hpp"
#include "search/search_quality/matcher.hpp"
#include "base/assert.hpp"
using namespace std;
// Context -----------------------------------------------------------------------------------------
void Context::Clear()
{
@ -8,6 +15,79 @@ void Context::Clear()
m_initialized = false;
}
search::Sample Context::MakeSample(search::FeatureLoader & loader) const
{
search::Sample sample = m_sample;
if (!m_initialized)
return sample;
auto const & relevances = m_edits.GetRelevances();
auto & results = sample.m_results;
results.clear();
CHECK_EQUAL(m_goldenMatching.size(), m_sample.m_results.size(), ());
CHECK_EQUAL(m_actualMatching.size(), relevances.size(), ());
CHECK_EQUAL(m_actualMatching.size(), m_results.GetCount(), ());
// Iterates over original (loaded from the file with search samples)
// results first.
for (size_t i = 0; i < m_sample.m_results.size(); ++i)
{
auto const j = m_goldenMatching[i];
// Some results weren't matched, so they weren't displayed to the
// assessor. But we want to keep them.
if (j == search::Matcher::kInvalidId)
{
results.push_back(m_sample.m_results[i]);
continue;
}
// No need to keep irrelevant results.
if (relevances[j] == search::Sample::Result::Relevance::Irrelevant)
continue;
auto result = m_sample.m_results[i];
result.m_relevance = relevances[j];
results.push_back(result);
}
// Iterates over results retrieved during assessment.
for (size_t i = 0; i < m_results.GetCount(); ++i)
{
auto const j = m_actualMatching[i];
if (j != search::Matcher::kInvalidId)
{
// This result was processed by the loop above.
continue;
}
// No need to keep irrelevant results.
if (relevances[i] == search::Sample::Result::Relevance::Irrelevant)
continue;
auto const & result = m_results.GetResult(i);
// No need in non-feature results.
if (result.GetResultType() != search::Result::RESULT_FEATURE)
continue;
FeatureType ft;
CHECK(loader.Load(result.GetFeatureID(), ft), ());
results.push_back(search::Sample::Result::Build(ft, relevances[i]));
}
return sample;
}
void Context::ApplyEdits()
{
if (!m_initialized)
return;
m_edits.ResetRelevances(m_edits.GetRelevances());
}
// ContextList -------------------------------------------------------------------------------------
ContextList::ContextList(OnUpdate onUpdate): m_onUpdate(onUpdate) {}
@ -21,14 +101,28 @@ void ContextList::Resize(size_t size)
m_hasChanges.resize(size);
for (size_t i = oldSize; i < size; ++i)
{
m_contexts.emplace_back([this, i]() {
m_contexts.emplace_back([this, i](Edits::Update const & update) {
if (!m_hasChanges[i] && m_contexts[i].HasChanges())
++m_numChanges;
if (m_hasChanges[i] && !m_contexts[i].HasChanges())
--m_numChanges;
m_hasChanges[i] = m_contexts[i].HasChanges();
if (m_onUpdate)
m_onUpdate(i);
m_onUpdate(i, update);
});
}
}
vector<search::Sample> ContextList::MakeSamples(search::FeatureLoader & loader) const
{
vector<search::Sample> samples;
for (auto const & context : m_contexts)
samples.push_back(context.MakeSample(loader));
return samples;
}
void ContextList::ApplyEdits()
{
for (auto & context : m_contexts)
context.ApplyEdits();
}

View file

@ -11,6 +11,11 @@
#include <string>
#include <vector>
namespace search
{
class FeatureLoader;
}
struct Context
{
explicit Context(Edits::OnUpdate onUpdate) : m_edits(onUpdate) {}
@ -18,9 +23,19 @@ struct Context
bool HasChanges() const { return m_initialized && m_edits.HasChanges(); }
void Clear();
// Makes sample in accordance with uncommited edits.
search::Sample MakeSample(search::FeatureLoader & loader) const;
// Commits all edits.
void ApplyEdits();
search::Sample m_sample;
search::Results m_results;
Edits m_edits;
std::vector<size_t> m_goldenMatching;
std::vector<size_t> m_actualMatching;
bool m_initialized = false;
};
@ -48,7 +63,7 @@ public:
ContextList const * m_contexts = nullptr;
};
using OnUpdate = std::function<void(size_t index)>;
using OnUpdate = std::function<void(size_t index, Edits::Update const & update)>;
explicit ContextList(OnUpdate onUpdate);
@ -60,6 +75,12 @@ public:
bool HasChanges() const { return m_numChanges != 0; }
// Generates search samples in accordance with uncommited edits.
std::vector<search::Sample> MakeSamples(search::FeatureLoader & loader) const;
// Commits all edits.
void ApplyEdits();
private:
std::vector<Context> m_contexts;
std::vector<bool> m_hasChanges;

View file

@ -20,10 +20,15 @@ Edits::Relevance Edits::RelevanceEditor::Get() const
return relevances[m_index];
}
bool Edits::RelevanceEditor::HasChanges() const
{
return m_parent.HasChanges(m_index);
}
// Edits -------------------------------------------------------------------------------------------
void Edits::ResetRelevances(std::vector<Relevance> const & relevances)
{
WithObserver([this, &relevances]() {
WithObserver(Update::AllRelevancesUpdate(), [this, &relevances]() {
m_origRelevances = relevances;
m_currRelevances = relevances;
m_numEdits = 0;
@ -32,7 +37,7 @@ void Edits::ResetRelevances(std::vector<Relevance> const & relevances)
bool Edits::SetRelevance(size_t index, Relevance relevance)
{
return WithObserver([this, index, relevance]() {
return WithObserver(Update::SingleRelevanceUpdate(index), [this, index, relevance]() {
CHECK_LESS(index, m_currRelevances.size(), ());
CHECK_EQUAL(m_currRelevances.size(), m_origRelevances.size(), ());
@ -53,7 +58,7 @@ bool Edits::SetRelevance(size_t index, Relevance relevance)
void Edits::Clear()
{
WithObserver([this]() {
WithObserver(Update::AllRelevancesUpdate(), [this]() {
m_origRelevances.clear();
m_currRelevances.clear();
m_numEdits = 0;
@ -61,3 +66,9 @@ void Edits::Clear()
}
bool Edits::HasChanges() const { return m_numEdits != 0; }
bool Edits::HasChanges(size_t index) const
{
CHECK_LESS(index, m_currRelevances.size(), ());
return m_currRelevances[index] != m_origRelevances[index];
}

View file

@ -6,13 +6,38 @@
#include <cstddef>
#include <functional>
#include <limits>
#include <type_traits>
#include <vector>
class Edits
{
public:
using OnUpdate = std::function<void()>;
struct Update
{
static auto constexpr kInvalidIndex = std::numeric_limits<size_t>::max();
enum class Type
{
SingleRelevance,
AllRelevances
};
static Update AllRelevancesUpdate() { return Update{}; }
static Update SingleRelevanceUpdate(size_t index)
{
Update result;
result.m_index = index;
result.m_type = Type::SingleRelevance;
return result;
}
size_t m_index = kInvalidIndex;
Type m_type = Type::AllRelevances;
};
using OnUpdate = std::function<void(Update const & update)>;
using Relevance = search::Sample::Result::Relevance;
class RelevanceEditor
@ -24,6 +49,7 @@ public:
// differs from the original one.
bool Set(Relevance relevance);
Relevance Get() const;
bool HasChanges() const;
private:
Edits & m_parent;
@ -38,18 +64,19 @@ public:
// |relevance| differs from the original one.
bool SetRelevance(size_t index, Relevance relevance);
std::vector<Relevance> const & GetRelevances() { return m_currRelevances; }
std::vector<Relevance> const & GetRelevances() const { return m_currRelevances; }
void Clear();
bool HasChanges() const;
bool HasChanges(size_t index) const;
private:
template <typename Fn>
typename std::result_of<Fn()>::type WithObserver(Fn && fn)
typename std::result_of<Fn()>::type WithObserver(Update const & update, Fn && fn)
{
MY_SCOPE_GUARD(cleanup, [this]() {
MY_SCOPE_GUARD(cleanup, [&]() {
if (m_onUpdate)
m_onUpdate();
m_onUpdate(update);
});
return fn();
}

View file

@ -1,5 +1,6 @@
#include "search/search_quality/assessment_tool/main_model.hpp"
#include "search/feature_loader.hpp"
#include "search/search_quality/assessment_tool/view.hpp"
#include "search/search_quality/matcher.hpp"
@ -18,9 +19,11 @@
#include <algorithm>
#include <fstream>
#include <functional>
#include <iterator>
using Relevance = search::Sample::Result::Relevance;
using namespace std;
// MainModel::SampleContext ------------------------------------------------------------------------
@ -28,24 +31,27 @@ using Relevance = search::Sample::Result::Relevance;
MainModel::MainModel(Framework & framework)
: m_framework(framework)
, m_index(m_framework.GetIndex())
, m_contexts([this](size_t index) { OnUpdate(index); })
, m_contexts([this](size_t index, Edits::Update const & update) { OnUpdate(index, update); })
{
}
void MainModel::Open(std::string const & path)
void MainModel::Open(string const & path)
{
CHECK(m_view, ());
std::ifstream ifs(path);
if (!ifs)
string contents;
{
m_view->ShowError("Can't open file: " + path);
return;
ifstream ifs(path);
if (!ifs)
{
m_view->ShowError("Can't open file: " + path);
return;
}
contents.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
}
std::string const contents((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
std::vector<search::Sample> samples;
vector<search::Sample> samples;
if (!search::Sample::DeserializeFromJSON(contents, samples))
{
m_view->ShowError("Can't parse samples: " + path);
@ -61,10 +67,41 @@ void MainModel::Open(std::string const & path)
context.Clear();
context.m_sample = samples[i];
}
m_path = path;
m_view->SetSamples(ContextList::SamplesSlice(m_contexts));
}
void MainModel::Save()
{
CHECK(HasChanges(), ());
SaveAs(m_path);
}
void MainModel::SaveAs(string const & path)
{
CHECK(HasChanges(), ());
CHECK(!path.empty(), ());
search::FeatureLoader loader(m_index);
string contents;
search::Sample::SerializeToJSON(m_contexts.MakeSamples(loader), contents);
{
ofstream ofs(path);
if (!ofs)
{
m_view->ShowError("Can't open file: " + path);
return;
}
copy(contents.begin(), contents.end(), ostreambuf_iterator<char>(ofs));
}
m_contexts.ApplyEdits();
m_path = path;
}
void MainModel::OnSampleSelected(int index)
{
CHECK(m_threadChecker.CalledOnOriginalThread(), ());
@ -83,7 +120,8 @@ void MainModel::OnSampleSelected(int index)
if (context.m_initialized)
{
OnResults(timestamp, index, context.m_results, context.m_edits.GetRelevances());
OnResults(timestamp, index, context.m_results, context.m_edits.GetRelevances(),
context.m_goldenMatching, context.m_actualMatching);
return;
}
@ -98,19 +136,17 @@ void MainModel::OnSampleSelected(int index)
params.SetPosition(latLon.lat, latLon.lon);
params.m_onResults = [this, index, sample, timestamp](search::Results const & results) {
std::vector<Relevance> relevances;
vector<Relevance> relevances;
vector<size_t> goldenMatching;
vector<size_t> actualMatching;
if (results.IsEndedNormal())
{
search::Matcher matcher(m_index);
std::vector<search::Result> const actual(results.begin(), results.end());
std::vector<size_t> goldenMatching;
{
std::vector<size_t> actualMatching;
matcher.Match(sample.m_results, actual, goldenMatching, actualMatching);
}
search::FeatureLoader loader(m_index);
search::Matcher matcher(loader);
vector<search::Result> const actual(results.begin(), results.end());
matcher.Match(sample.m_results, actual, goldenMatching, actualMatching);
relevances.assign(actual.size(), Relevance::Irrelevant);
for (size_t i = 0; i < goldenMatching.size(); ++i)
{
@ -119,25 +155,27 @@ void MainModel::OnSampleSelected(int index)
}
}
GetPlatform().RunOnGuiThread([this, timestamp, index, results, relevances]() {
OnResults(timestamp, index, results, relevances);
});
GetPlatform().RunOnGuiThread(bind(&MainModel::OnResults, this, timestamp, index, results,
relevances, goldenMatching, actualMatching));
};
m_queryHandle = engine.Search(params, sample.m_viewport);
}
}
void MainModel::OnUpdate(size_t index)
bool MainModel::HasChanges() { return m_contexts.HasChanges(); }
void MainModel::OnUpdate(size_t index, Edits::Update const & update)
{
CHECK_LESS(index, m_contexts.Size(), ());
auto & context = m_contexts[index];
m_view->OnSampleChanged(index, context.HasChanges());
m_view->OnSampleChanged(index, update, context.HasChanges());
m_view->OnSamplesChanged(m_contexts.HasChanges());
}
void MainModel::OnResults(uint64_t timestamp, size_t index, search::Results const & results,
std::vector<Relevance> const & relevances)
vector<Relevance> const & relevances, vector<size_t> goldenMatching,
vector<size_t> actualMatching)
{
CHECK(m_threadChecker.CalledOnOriginalThread(), ());
@ -156,9 +194,11 @@ void MainModel::OnResults(uint64_t timestamp, size_t index, search::Results cons
{
context.m_results = results;
context.m_edits.ResetRelevances(relevances);
context.m_goldenMatching = goldenMatching;
context.m_actualMatching = actualMatching;
context.m_initialized = true;
}
m_view->OnSampleChanged(index, context.HasChanges());
m_view->OnSampleChanged(index, Edits::Update::AllRelevancesUpdate(), context.HasChanges());
m_view->EnableSampleEditing(index, context.m_edits);
}

View file

@ -27,20 +27,29 @@ public:
// Model overrides:
void Open(std::string const & path) override;
void Save() override;
void SaveAs(std::string const & path) override;
void OnSampleSelected(int index) override;
bool HasChanges() override;
private:
void OnUpdate(size_t index);
void OnUpdate(size_t index, Edits::Update const & update);
void OnResults(uint64_t timestamp, size_t index, search::Results const & results,
std::vector<search::Sample::Result::Relevance> const & relevances);
std::vector<search::Sample::Result::Relevance> const & relevances,
std::vector<size_t> goldenMatching, std::vector<size_t> actualMatching);
void ResetSearch();
Framework & m_framework;
Index const & m_index;
ContextList m_contexts;
// Path to the last file search samples were loaded from or saved to.
std::string m_path;
std::weak_ptr<search::ProcessorHandle> m_queryHandle;
uint64_t m_queryTimestamp = 0;
size_t m_numShownResults = 0;

View file

@ -25,6 +25,11 @@
using Relevance = search::Sample::Result::Relevance;
namespace
{
char const kJSON[] = "JSON files (*.json)";
} // namespace
MainView::MainView(Framework & framework) : m_framework(framework)
{
QDesktopWidget const * desktop = QApplication::desktop();
@ -48,6 +53,7 @@ MainView::~MainView()
void MainView::SetSamples(ContextList::SamplesSlice const & samples)
{
m_samplesView->SetSamples(samples);
m_sampleView->Clear();
}
void MainView::ShowSample(size_t index, search::Sample const & sample, bool hasEdits)
@ -57,7 +63,7 @@ void MainView::ShowSample(size_t index, search::Sample const & sample, bool hasE
m_sampleView->SetContents(sample);
m_sampleView->show();
OnSampleChanged(index, hasEdits);
OnSampleChanged(index, Edits::Update::AllRelevancesUpdate(), hasEdits);
}
void MainView::ShowResults(search::Results::Iter begin, search::Results::Iter end)
@ -65,15 +71,16 @@ void MainView::ShowResults(search::Results::Iter begin, search::Results::Iter en
m_sampleView->ShowResults(begin, end);
}
void MainView::OnSampleChanged(size_t index, bool hasEdits)
void MainView::OnSampleChanged(size_t index, Edits::Update const & update, bool hasEdits)
{
if (m_samplesView->IsSelected(index))
{
if (hasEdits)
m_sampleDock->setWindowTitle(tr("Sample *"));
else
m_sampleDock->setWindowTitle(tr("Sample"));
}
m_samplesView->OnUpdate(index);
if (!m_samplesView->IsSelected(index))
return;
if (hasEdits)
m_sampleDock->setWindowTitle(tr("Sample *"));
else
m_sampleDock->setWindowTitle(tr("Sample"));
m_sampleView->Update(update);
}
void MainView::OnSamplesChanged(bool hasEdits)
@ -82,6 +89,8 @@ void MainView::OnSamplesChanged(bool hasEdits)
m_samplesDock->setWindowTitle("Samples *");
else
m_samplesDock->setWindowTitle("Samples");
m_save->setEnabled(hasEdits);
m_saveAs->setEnabled(hasEdits);
}
void MainView::EnableSampleEditing(size_t index, Edits & edits)
@ -113,13 +122,31 @@ void MainView::InitMenuBar()
auto * fileMenu = bar->addMenu(tr("&File"));
{
auto * open = new QAction(tr("&Open queries..."), this /* parent */);
auto * open = new QAction(tr("&Open samples..."), this /* parent */);
open->setShortcuts(QKeySequence::Open);
open->setStatusTip(tr("Open the file with queries for assessment"));
open->setStatusTip(tr("Open the file with samples for assessment"));
connect(open, &QAction::triggered, this, &MainView::Open);
fileMenu->addAction(open);
}
{
m_save = new QAction(tr("Save samples"), this /* parent */);
m_save->setShortcuts(QKeySequence::Save);
m_save->setStatusTip(tr("Save the file with assessed samples"));
m_save->setEnabled(false);
connect(m_save, &QAction::triggered, this, &MainView::Save);
fileMenu->addAction(m_save);
}
{
m_saveAs = new QAction(tr("Save samples as..."), this /* parent */);
m_saveAs->setShortcuts(QKeySequence::SaveAs);
m_saveAs->setStatusTip(tr("Save the file with assessed samples"));
m_saveAs->setEnabled(false);
connect(m_saveAs, &QAction::triggered, this, &MainView::SaveAs);
fileMenu->addAction(m_saveAs);
}
fileMenu->addSeparator();
{
@ -185,8 +212,8 @@ void MainView::Open()
{
CHECK(m_model, ());
auto const file = QFileDialog::getOpenFileName(this, tr("Open samples..."), QString(),
tr("JSON files (*.json)"))
auto const file = QFileDialog::getOpenFileName(this /* parent */, tr("Open samples..."),
QString() /* dir */, tr(kJSON))
.toStdString();
if (file.empty())
return;
@ -194,6 +221,17 @@ void MainView::Open()
m_model->Open(file);
}
void MainView::Save() { m_model->Save(); }
void MainView::SaveAs()
{
auto const file = QFileDialog::getSaveFileName(this /* parent */, tr("Save samples as..."),
QString() /* dir */, tr(kJSON))
.toStdString();
if (!file.empty())
m_model->SaveAs(file);
}
QDockWidget * MainView::CreateDock(std::string const & title, QWidget & widget)
{
auto * dock = new QDockWidget(ToQString(title), this /* parent */, Qt::Widget);

View file

@ -31,7 +31,7 @@ public:
void ShowSample(size_t index, search::Sample const & sample, bool hasEdits) override;
void ShowResults(search::Results::Iter begin, search::Results::Iter end) override;
void OnSampleChanged(size_t index, bool hasEdits) override;
void OnSampleChanged(size_t index, Edits::Update const & update, bool hasEdits) override;
void EnableSampleEditing(size_t index, Edits & edits) override;
void OnSamplesChanged(bool hasEdits) override;
@ -46,6 +46,8 @@ private:
void InitMenuBar();
void Open();
void Save();
void SaveAs();
QDockWidget * CreateDock(std::string const & title, QWidget & widget);
@ -56,4 +58,7 @@ private:
SampleView * m_sampleView = nullptr;
QDockWidget * m_sampleDock = nullptr;
QAction * m_save = nullptr;
QAction * m_saveAs = nullptr;
};

View file

@ -12,7 +12,11 @@ public:
void SetView(View & view) { m_view = &view; }
virtual void Open(std::string const & path) = 0;
virtual void Save() = 0;
virtual void SaveAs(std::string const & path) = 0;
virtual void OnSampleSelected(int index) = 0;
virtual bool HasChanges() = 0;
protected:
View * m_view = nullptr;

View file

@ -61,6 +61,14 @@ void ResultView::EnableEditing(Edits::RelevanceEditor && editor)
setEnabled(true);
}
void ResultView::Update()
{
if (m_editor && m_editor->HasChanges())
setStyleSheet("#result {background: rgba(255, 255, 200, 50%)}");
else
setStyleSheet("");
}
void ResultView::Init()
{
auto * layout = new QVBoxLayout(this /* parent */);
@ -124,9 +132,5 @@ void ResultView::OnRelevanceChanged()
else if (m_vital->isChecked())
relevance = Relevance::Vital;
bool changed = m_editor->Set(relevance);
if (changed)
setStyleSheet("#result {background: rgba(255, 255, 200, 50%)}");
else
setStyleSheet("");
m_editor->Set(relevance);
}

View file

@ -24,6 +24,8 @@ public:
void EnableEditing(Edits::RelevanceEditor && editor);
void Update();
private:
void Init();
void SetContents(search::Result const & result);

View file

@ -32,6 +32,20 @@ ResultView const & ResultsView::Get(size_t i) const
return *m_results[i];
}
void ResultsView::Update(Edits::Update const & update)
{
switch (update.m_type)
{
case Edits::Update::Type::SingleRelevance:
m_results[update.m_index]->Update();
break;
case Edits::Update::Type::AllRelevances:
for (auto * result : m_results)
result->Update();
break;
}
}
void ResultsView::Clear()
{
m_results.clear();

View file

@ -1,5 +1,7 @@
#pragma once
#include "search/search_quality/assessment_tool/edits.hpp"
#include <cstddef>
#include <vector>
@ -22,6 +24,7 @@ public:
ResultView & Get(size_t i);
ResultView const & Get(size_t i) const;
void Update(Edits::Update const & update);
size_t Size() const { return m_results.size(); }

View file

@ -59,6 +59,8 @@ SampleView::SampleView(QWidget * parent) : QWidget(parent)
}
setLayout(mainLayout);
Clear();
}
void SampleView::SetContents(search::Sample const & sample)
@ -86,3 +88,13 @@ void SampleView::EnableEditing(Edits & edits)
for (size_t i = 0; i < numRelevances; ++i)
m_results->Get(i).EnableEditing(Edits::RelevanceEditor(*m_edits, i));
}
void SampleView::Update(Edits::Update const & update) { m_results->Update(update); }
void SampleView::Clear()
{
m_query->setText(QString());
m_langs->Select("default");
m_results->Clear();
m_edits = nullptr;
}

View file

@ -24,6 +24,9 @@ public:
void EnableEditing(Edits & edits);
void Update(Edits::Update const & update);
void Clear();
private:
QLineEdit * m_query = nullptr;
LanguagesList * m_langs = nullptr;

View file

@ -3,7 +3,6 @@
#include "search/search_quality/assessment_tool/helpers.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include <QtGui/QStandardItem>
#include <QtWidgets/QHeaderView>
@ -47,5 +46,5 @@ SamplesView::SamplesView(QWidget * parent) : QTableView(parent)
bool SamplesView::IsSelected(size_t index) const
{
return selectionModel()->isRowSelected(index, QModelIndex());
return selectionModel()->isRowSelected(base::checked_cast<int>(index), QModelIndex());
}

View file

@ -2,6 +2,8 @@
#include "search/search_quality/assessment_tool/context.hpp"
#include "base/checked_cast.hpp"
#include <cstddef>
#include <vector>
@ -15,6 +17,7 @@ public:
void SetSamples(ContextList::SamplesSlice const & samples) { m_model->SetSamples(samples); }
bool IsSelected(size_t index) const;
void OnUpdate(size_t index) { m_model->OnUpdate(index); }
private:
class Model : public QStandardItemModel
@ -27,7 +30,15 @@ private:
removeRows(0, rowCount());
m_samples = samples;
if (m_samples.IsValid())
insertRows(0, m_samples.Size());
insertRows(0, base::checked_cast<int>(m_samples.Size()));
}
void OnUpdate(size_t index)
{
auto const ix = createIndex(base::checked_cast<int>(index) /* row */, 0 /* column */);
// Need to refresh view when some item is updated.
emit dataChanged(ix, ix);
}
// QStandardItemModel overrides:

View file

@ -2,6 +2,7 @@
#include "search/result.hpp"
#include "search/search_quality/assessment_tool/context.hpp"
#include "search/search_quality/assessment_tool/edits.hpp"
#include <cstddef>
#include <string>
@ -21,7 +22,7 @@ public:
virtual void ShowSample(size_t index, search::Sample const & sample, bool hasEdits) = 0;
virtual void ShowResults(search::Results::Iter begin, search::Results::Iter end) = 0;
virtual void OnSampleChanged(size_t index, bool hasEdits) = 0;
virtual void OnSampleChanged(size_t index, Edits::Update const & update, bool hasEdits) = 0;
virtual void EnableSampleEditing(size_t index, Edits & edits) = 0;
virtual void OnSamplesChanged(bool hasEdits) = 0;

View file

@ -1,3 +1,4 @@
#include "search/feature_loader.hpp"
#include "search/ranking_info.hpp"
#include "search/result.hpp"
#include "search/search_quality/helpers.hpp"
@ -162,7 +163,8 @@ int main(int argc, char * argv[])
}
vector<Stats> stats(samples.size());
Matcher matcher(engine);
FeatureLoader loader(engine);
Matcher matcher(loader);
cout << "SampleId,";
RankingInfo::PrintCSVHeader(cout);

View file

@ -1,8 +1,9 @@
#include "search/search_quality/matcher.hpp"
#include "search/feature_loader.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_decl.hpp"
#include "base/string_utils.hpp"
@ -15,7 +16,7 @@ namespace search
// static
size_t constexpr Matcher::kInvalidId;
Matcher::Matcher(Index const & index) : m_index(index) {}
Matcher::Matcher(FeatureLoader & loader) : m_loader(loader) {}
void Matcher::Match(std::vector<Sample::Result> const & golden, std::vector<Result> const & actual,
std::vector<size_t> & goldenMatching, std::vector<size_t> & actualMatching)
@ -49,14 +50,6 @@ void Matcher::Match(std::vector<Sample::Result> const & golden, std::vector<Resu
}
}
bool Matcher::GetFeature(FeatureID const & id, FeatureType & ft)
{
auto const & mwmId = id.m_mwmId;
if (!m_guard || m_guard->GetId() != mwmId)
m_guard = my::make_unique<Index::FeaturesLoaderGuard>(m_index, mwmId);
return m_guard->GetFeatureByIndex(id.m_index, ft);
}
bool Matcher::Matches(Sample::Result const & golden, search::Result const & actual)
{
static double constexpr kToleranceMeters = 50;
@ -65,7 +58,7 @@ bool Matcher::Matches(Sample::Result const & golden, search::Result const & actu
return false;
FeatureType ft;
if (!GetFeature(actual.GetFeatureID(), ft))
if (!m_loader.Load(actual.GetFeatureID(), ft))
return false;
auto const houseNumber = ft.GetHouseNumber();

View file

@ -3,36 +3,27 @@
#include "search/result.hpp"
#include "search/search_quality/sample.hpp"
#include "indexer/index.hpp"
#include "base/macros.hpp"
#include <cstdint>
#include <limits>
#include <memory>
#include <vector>
class FeatureType;
struct FeatureID;
namespace search
{
class FeatureLoader;
class Matcher
{
public:
static size_t constexpr kInvalidId = std::numeric_limits<size_t>::max();
Matcher(Index const & index);
explicit Matcher(FeatureLoader & loader);
void Match(std::vector<Sample::Result> const & golden, std::vector<Result> const & actual,
std::vector<size_t> & goldenMatching, std::vector<size_t> & actualMatching);
private:
WARN_UNUSED_RESULT bool GetFeature(FeatureID const & id, FeatureType & ft);
bool Matches(Sample::Result const & golden, Result const & actual);
Index const & m_index;
std::unique_ptr<Index::FeaturesLoaderGuard> m_guard;
FeatureLoader & m_loader;
};
} // namespace search

View file

@ -2,6 +2,10 @@
#include "search/search_quality/helpers.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_data.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
@ -51,6 +55,22 @@ struct FreeDeletor
namespace search
{
// static
Sample::Result Sample::Result::Build(FeatureType & ft, Relevance relevance)
{
Sample::Result r;
r.m_pos = feature::GetCenter(ft);
{
string name;
ft.GetReadableName(name);
r.m_name = strings::MakeUniString(name);
}
r.m_houseNumber = ft.GetHouseNumber();
r.m_types = feature::TypesHolder(ft).ToObjectNames();
r.m_relevance = relevance;
return r;
}
bool Sample::Result::operator<(Sample::Result const & rhs) const
{
if (m_pos != rhs.m_pos)

View file

@ -10,6 +10,8 @@
#include <string>
#include <vector>
class FeatureType;
namespace search
{
struct Sample
@ -23,6 +25,8 @@ struct Sample
Vital
};
static Result Build(FeatureType & ft, Relevance relevance);
bool operator<(Result const & rhs) const;
bool operator==(Result const & rhs) const;