diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp index b049655..68b7281 100644 --- a/openbr/plugins/distance.cpp +++ b/openbr/plugins/distance.cpp @@ -21,6 +21,7 @@ #include #include "openbr_internal.h" +#include "openbr/core/common.h" #include "openbr/core/distance_sse.h" #include "openbr/core/qtutils.h" #include "openbr/core/opencvutils.h" @@ -391,31 +392,74 @@ BR_REGISTER(Distance, OnlineDistance) /*! * \ingroup distances - * \brief Attenuation function based distance from attributes + * \brief Unmaps turk votes to be used in a distance * \author Scott Klum \cite sklum */ -class AttributeDistance : public Distance +class TurkDistance : public Distance { Q_OBJECT - Q_PROPERTY(QString targetAttribute READ get_targetAttribute WRITE set_targetAttribute RESET reset_targetAttribute STORED false) - Q_PROPERTY(QString queryAttribute READ get_queryAttribute WRITE set_queryAttribute RESET reset_queryAttribute STORED false) - BR_PROPERTY(QString, targetAttribute, QString()) - BR_PROPERTY(QString, queryAttribute, QString()) - + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance) + Q_PROPERTY(QString category READ get_category WRITE set_category RESET reset_category) + Q_PROPERTY(QStringList targetAttributes READ get_targetAttributes WRITE set_targetAttributes RESET reset_targetAttributes STORED false) + Q_PROPERTY(QStringList queryAttributes READ get_queryAttributes WRITE set_queryAttributes RESET reset_queryAttributes STORED false) + BR_PROPERTY(br::Distance*, distance, NULL) + BR_PROPERTY(QString, category, QString()) + BR_PROPERTY(QStringList, targetAttributes, QStringList()) + BR_PROPERTY(QStringList, queryAttributes, QStringList()) +; float compare(const Template &target, const Template &query) const { - float queryValue = query.file.get(queryAttribute); - float targetValue = target.file.get(targetAttribute); + Template t = unmap(target,category); + Template q = unmap(query,category); + + QList targetValues, queryValues; + foreach(const QString &s, targetAttributes) targetValues.append(t.file.get(s)); + foreach(const QString &s, queryAttributes) queryValues.append(q.file.get(s)); - // TODO: Set this magic number to something meaningful - float stddev = .5; + float stddev = .75; + float score = 0; + for(int i=0; i map = t.file.get >(variable); + QMapIterator i(map); + + while (i.hasNext()) { + i.next(); + float value = i.value().toFloat(); + expandedT.file.set(i.key(),value); + } + + return expandedT; } }; -BR_REGISTER(Distance, AttributeDistance) +BR_REGISTER(Distance, TurkDistance) + +/*! + * \ingroup distances + * \brief Attenuation function based distance + * \author Scott Klum \cite sklum + */ +class AttenuationDistance : public Distance +{ + Q_OBJECT +; + float compare(const Template &target, const Template &query) const + { + return 1; + } +}; + +BR_REGISTER(Distance, AttenuationDistance) /*! * \ingroup distances @@ -435,18 +479,18 @@ class SumDistance : public Distance futures.addFuture(QtConcurrent::run(distance, &Distance::train, data)); futures.waitForFinished(); } -; + float compare(const Template &target, const Template &query) const { float result = 0; - QList scores; foreach (br::Distance *distance, distances) { - scores.append(distance->compare(target, query)); - result += distance->compare(target, query); + float score = distance->compare(target, query); - if (result == -std::numeric_limits::max()) + if (score == -std::numeric_limits::max()) return result; + + result += score; } return result; diff --git a/openbr/plugins/template.cpp b/openbr/plugins/template.cpp index e8f750f..538dfb2 100644 --- a/openbr/plugins/template.cpp +++ b/openbr/plugins/template.cpp @@ -95,42 +95,76 @@ BR_REGISTER(Transform, SelectPointsTransform) /*! * \ingroup transforms - * \brief Converts Amazon MTurk labels + * \brief Converts Amazon MTurk labels to a non-map format for use in a transform + * Also optionally normalizes and/or classifies the votes * \author Scott Klum \cite sklum */ -class MTurkTransform : public UntrainableTransform +/* +class TurkTransform : public Transform { Q_OBJECT Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) Q_PROPERTY(float maxVotes READ get_maxVotes WRITE set_maxVotes RESET reset_maxVotes STORED false) + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED true) Q_PROPERTY(bool classify READ get_classify WRITE set_classify RESET reset_classify STORED false) Q_PROPERTY(bool consensusOnly READ get_consensusOnly WRITE set_consensusOnly RESET reset_consensusOnly STORED false) BR_PROPERTY(QString, inputVariable, QString()) + BR_PROPERTY(QString, outputVariable, QString()) BR_PROPERTY(float, maxVotes, 1.) + BR_PROPERTY(br::Transform*, transform, NULL) BR_PROPERTY(bool, classify, false) BR_PROPERTY(bool, consensusOnly, false) + void train(const TemplateList &data) + { + TemplateList expandedData; + + foreach(const Template &t, data) + expandedData.append(expandVotes(t)); + + transform->train(expandedData); + } + void project(const Template &src, Template &dst) const { + // Unmap, project, remap + transform->project(expandVotes(src),dst); + + QMap map = src.file.get >(inputVariable); + // We expect that whatever transform does to the inputVariable, + // the outputVariable will be in the form of (or convertible to) a float + map.insert(outputVariable,dst.file.get(outputVariable)); + dst = src; + dst.file.set(inputVariable,map); + } - QMap map = dst.file.get >(inputVariable); + Template expandVotes(const Template &t) const { + // Create a new template matching the one containing the votes in the map structure + // but remove the map structure + Template expandedT = t; + expandedT.file.remove(inputVariable); + QMap map = t.file.get >(inputVariable); + QMapIterator i(map); bool ok; - QMapIterator i(map); while (i.hasNext()) { i.next(); // Normalize to [-1,1] float value = i.value().toFloat(&ok)/maxVotes;//* 2./maxVotes - 1; + if (!ok) qFatal("Failed to expand Turk votes for %s", inputVariable); if (classify) (value > 0) ? value = 1 : value = -1; else if (consensusOnly && (value != 1 && value != -1)) continue; - dst.file.set(i.key(),value); + expandedT.file.set(i.key(),value); } + + return expandedT; } }; -BR_REGISTER(Transform, MTurkTransform) +BR_REGISTER(Transform, TurkTransform)*/ } // namespace br diff --git a/openbr/plugins/turk.cpp b/openbr/plugins/turk.cpp new file mode 100644 index 0000000..8482620 --- /dev/null +++ b/openbr/plugins/turk.cpp @@ -0,0 +1,99 @@ +#include "openbr_internal.h" + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Converts Amazon MTurk labels to a non-map format for use in a transform + * Also optionally normalizes and/or classifies the votes + * \author Scott Klum \cite sklum + */ +class TurkTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) + Q_PROPERTY(float maxVotes READ get_maxVotes WRITE set_maxVotes RESET reset_maxVotes STORED false) + Q_PROPERTY(bool classify READ get_classify WRITE set_classify RESET reset_classify STORED false) + Q_PROPERTY(bool consensusOnly READ get_consensusOnly WRITE set_consensusOnly RESET reset_consensusOnly STORED false) + BR_PROPERTY(QString, inputVariable, QString()) + BR_PROPERTY(float, maxVotes, 1.) + BR_PROPERTY(bool, classify, false) + BR_PROPERTY(bool, consensusOnly, false) + + void project(const Template &src, Template &dst) const + { + dst = unmap(src); + } + + Template unmap(const Template &t) const { + // Create a new template matching the one containing the votes in the map structure + // but remove the map structure + Template expandedT = t; + expandedT.file.remove(inputVariable); + + QMap map = t.file.get >(inputVariable); + QMapIterator i(map); + bool ok; + + while (i.hasNext()) { + i.next(); + // Normalize to [-1,1] + float value = i.value().toFloat(&ok)/maxVotes;//* 2./maxVotes - 1; + if (!ok) qFatal("Failed to expand Turk votes for %s", inputVariable); + if (classify) (value > 0) ? value = 1 : value = -1; + else if (consensusOnly && (value != 1 && value != -1)) continue; + expandedT.file.set(i.key(),value); + } + + return expandedT; + } +}; + +BR_REGISTER(Transform, TurkTransform) + +/*! + * \ingroup transforms + * \brief Converts metadata into a map structure + * \author Scott Klum \cite sklum + */ +class MapTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(QStringList inputVariables READ get_inputVariables WRITE set_inputVariables RESET reset_inputVariables STORED false) + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) + BR_PROPERTY(QStringList, inputVariables, QStringList()) + BR_PROPERTY(QString, outputVariable, QString()) + + void project(const Template &src, Template &dst) const + { + dst = map(src); + } + + Template map(const Template &t) const { + Template mappedT = t; + QMap map; + + foreach(const QString &s, inputVariables) { + // Get checks if the variant stored in m_metdata can be + // converted to the type T. For some reason, you cannot + // convert from a QVariant to a QVariant. Thus, this transform + // has to assume that the metadata we want to organize can be + // converted to a float, resulting in a loss of generality :(. + if (t.file.contains(s)) { + map.insert(s,t.file.get(s)); + mappedT.file.remove(s); + } + } + + if (!map.isEmpty()) mappedT.file.set(outputVariable,map); + + return mappedT; + } +}; + +BR_REGISTER(Transform, MapTransform) + +} // namespace br + +#include "turk.moc"