diff --git a/CMakeLists.txt b/CMakeLists.txt index 64e45fe..62899a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ endif() # Install install(FILES CHANGELOG.md LICENSE.txt README.md DESTINATION .) -install(DIRECTORY data share DESTINATION .) +install(DIRECTORY share DESTINATION .) install(DIRECTORY ${BR_THIRDPARTY_SHARE} DESTINATION share) # Package diff --git a/app/br/br.cpp b/app/br/br.cpp index 58b49dc..50204a8 100644 --- a/app/br/br.cpp +++ b/app/br/br.cpp @@ -24,9 +24,9 @@ /*! * \defgroup cli Command Line Interface - * \brief Command line application for running algorithms and evaluating results. + * \brief Command line wrapper of the \ref c_sdk. * - * The easiest and fastest way to leverage the project, we use it all the time! + * The easiest and fastest way to run algorithms and evaluating results, we use it all the time! * Commands are designed to mirror the \ref c_sdk and are evaluated in the order they are entered. * To get started, try running: * \code diff --git a/openbr/openbr.h b/openbr/openbr.h index 0ba39bd..d4add7d 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -25,7 +25,7 @@ extern "C" { /*! * \defgroup c_sdk C SDK - * \brief High-level API for running algorithms and evaluating results with wrappers available for other programming languages. + * \brief High-level API for running algorithms and evaluating results. * * In order to provide a high-level interface that is usable from the command line and callable from other programming languages, * the API is designed to operate at the "file system" level. @@ -300,6 +300,7 @@ BR_EXPORT float br_progress(); * * Used by the \ref cli to implement \c -daemon, generally not useful otherwise. * Guaranteed to return at least one argument. + * \param pipe Pipe name * \param[out] argc argument count * \param[out] argv argument list * \note \ref managed_return_value diff --git a/openbr/openbr_export.cpp b/openbr/openbr_export.cpp index 3f3a9cc..0696346 100644 --- a/openbr/openbr_export.cpp +++ b/openbr/openbr_export.cpp @@ -17,22 +17,27 @@ /*! * \mainpage * \section overview Overview - * OpenBR \cite openbr is a toolkit for biometric recognition and evaluation. - * Supported use cases include training new recognition algorithms, interfacing with commercial systems, and measuring algorithm performance. - * Free algorithms are also available for specific modalities including \ref cpp_face_recognition, \ref cpp_age_estimation, and \ref cpp_gender_estimation. + * OpenBR \cite openbr is a framework for investigating new modalities, improving existing algorithms, interfacing with commercial systems, measuring recognition performance, and deploying automated biometric systems. + * The project is designed to facilitate rapid algorithm prototyping, and features a mature core framework, flexible plugin system, and support for open and closed source development. + * Off-the-shelf algorithms are also available for specific modalities including \ref cpp_face_recognition, \ref cpp_age_estimation, and \ref cpp_gender_estimation. * - * There are three modules users may interact with: - * - \ref cli - \copybrief cli - * - \ref c_sdk - \copybrief c_sdk - * - \ref cpp_plugin_sdk - \copybrief cpp_plugin_sdk + * OpenBR originated within The MITRE Corporation from a need to streamline the process of prototyping new algorithms. + * The project was later published as open source software under the Apache 2 license and is free for academic and commercial use. + * + * \image html "share/openbr/abstraction.svg" "The two principal software artifacts are the shared library 'openbr' and command line application 'br'." * * \section get_started Get Started * - \ref installation - \copybrief installation + * + * \section learn_more Learn More + * - \ref cli - \copybrief cli + * - \ref c_sdk - \copybrief c_sdk + * - \ref cpp_plugin_sdk - \copybrief cpp_plugin_sdk */ /*! * \page installation Installation - * \brief A hacker's guide to building, editing, and running the project. + * \brief A hacker's guide to building, editing, and running OpenBR. * * \section installation_from_source From Source * Installation from source is the recommended method for getting OpenBR on your machine. @@ -101,7 +106,7 @@ $ br -help * $ nmake install * $ nmake clean * \endcode -* -# Download Qt 5.0.1 and unzip. + * -# Download Qt 5.0.1 and unzip. * -# Install Perl/Python/Ruby dependencies as explained in the "Windows" section of "README". Make sure they are added to "path" when given the option during installation. * -# Download Direct X Software Developement Kit and install. * -# From the VS2012 x64 Cross Tools Command Prompt: diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 284b0be..8e5a725 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -25,13 +25,12 @@ #include #include #include -#include -#include #ifndef BR_EMBEDDED #include #endif +#include "openbr_plugin.h" #include "version.h" #include "core/bee.h" #include "core/common.h" @@ -634,62 +633,73 @@ void Object::load(QDataStream &stream) init(); } -void Object::setProperty(const QString &name, const QString &value) +void Object::setProperty(const QString &name, QVariant value) { QString type; int index = metaObject()->indexOfProperty(qPrintable(name)); if (index != -1) type = metaObject()->property(index).typeName(); - else type = ""; - QVariant variant; - if (type.startsWith("QList<") && type.endsWith(">")) { - if (!value.startsWith('[')) qFatal("Expected a list."); - const QStringList strings = parse(value.mid(1, value.size()-2)); + if ((type.startsWith("QList<") && type.endsWith(">")) || (type == "QStringList")) { + QVariantList elements; + if (value.canConvert()) { + elements = value.value(); + } else if (value.canConvert()) { + QString string = value.value(); + if (!string.startsWith('[') || !string.endsWith(']')) + qFatal("Expected a list to start with '[' and end with 'brackets']'."); + foreach (const QString &element, parse(string.mid(1, string.size()-2))) + elements.append(element); + } else { + qFatal("Expected a list."); + } - if (type == "QList") { - QList values; - foreach (const QString &string, strings) - values.append(string.toFloat()); - variant.setValue(values); + if ((type == "QList") || (type == "QStringList")) { + QStringList parsedValues; + foreach (const QVariant &element, elements) + parsedValues.append(element.toString()); + value.setValue(parsedValues); + } else if (type == "QList") { + QList parsedValues; bool ok; + foreach (const QVariant &element, elements) { + parsedValues.append(element.toFloat(&ok)); + if (!ok) qFatal("Failed to convert element to floating point format."); + } + value.setValue(parsedValues); } else if (type == "QList") { - QList values; - foreach (const QString &string, strings) - values.append(string.toInt()); - variant.setValue(values); + QList parsedValues; bool ok; + foreach (const QVariant &element, elements) { + parsedValues.append(element.toInt(&ok)); + if (!ok) qFatal("Failed to convert element to integer format."); + } + value.setValue(parsedValues); } else if (type == "QList") { - QList values; - foreach (const QString &string, strings) - values.append(Transform::make(string, this)); - variant.setValue(values); + QList parsedValues; + foreach (const QVariant &element, elements) + if (element.canConvert()) parsedValues.append(Transform::make(element.toString(), this)); + else parsedValues.append(element.value()); + value.setValue(parsedValues); } else if (type == "QList") { - QList values; - foreach (const QString &string, strings) - values.append(Distance::make(string, this)); - variant.setValue(values); + QList parsedValues; + foreach (const QVariant &element, elements) + if (element.canConvert()) parsedValues.append(Distance::make(element.toString(), this)); + else parsedValues.append(element.value()); + value.setValue(parsedValues); } else { qFatal("Unrecognized type: %s", qPrintable(type)); } } else if (type == "br::Transform*") { - variant.setValue(Transform::make(value, this)); + if (value.canConvert()) + value.setValue(Transform::make(value.toString(), this)); } else if (type == "br::Distance*") { - variant.setValue(Distance::make(value, this)); - } else if (type == "QStringList") { - variant.setValue(parse(value.mid(1, value.size()-2))); + if (value.canConvert()) + value.setValue(Distance::make(value.toString(), this)); } else if (type == "bool") { - if (value.isEmpty()) variant = true; - else if (value == "false") variant = false; - else if (value == "true") variant = true; - else variant = value; - } else { - variant = value; + if (value.isNull()) value = true; + else if (value == "false") value = false; + else if (value == "true") value = true; } - setProperty(name, variant, !type.isEmpty()); -} - -void Object::setProperty(const QString &name, const QVariant &value, bool failOnError) -{ - if (!QObject::setProperty(qPrintable(name), value) && failOnError) + if (!QObject::setProperty(qPrintable(name), value) && !type.isEmpty()) qFatal("Failed to set %s::%s to: %s", metaObject()->className(), qPrintable(name), qPrintable(value.toString())); } @@ -702,42 +712,27 @@ QStringList Object::parse(const QString &string, char split) /* Object - private methods */ void Object::init(const File &file_) { - this->file = file_; - - // Set name - QString name = metaObject()->className(); - if (name.startsWith("br::")) name = name.right(name.size()-4); + file = file_; - firstAvailablePropertyIdx = metaObject()->propertyCount(); - - const QMetaObject * baseClass = metaObject(); - const QMetaObject * superClass = metaObject()->superClass(); - - while (superClass != NULL) { - const QMetaObject * nextClass = superClass->superClass(); - - // baseClass <- something <- br::Object - // baseClass is the highest class whose properties we can set via positional arguments - if (nextClass && !strcmp(nextClass->className(),"br::Object")) { + // Determine interfaceName and firstAvailablePropertyIdx + QString interfaceName; + const QMetaObject *baseClass = metaObject(); + while (true) { + if (!strcmp(baseClass->superClass()->className(), "br::Object") /* Special case for classes that inherit directly from br::Object */ || + !strcmp(baseClass->superClass()->superClass()->className(), "br::Object") /* General case for plugins that inherit indirectly from br::Object */) { firstAvailablePropertyIdx = baseClass->propertyOffset(); + interfaceName = QString(baseClass->superClass()->className()).remove("br::"); + break; } - - QString superClassName = superClass->className(); - - // strip br:: prefix from superclass name - if (superClassName.startsWith("br::")) - superClassName = superClassName.right(superClassName.size()-4); - - // Strip superclass name from base class name (e.g. PipeTransform -> Pipe) - if (name.endsWith(superClassName)) - name = name.left(name.size() - superClassName.size()); - baseClass = superClass; - superClass = superClass->superClass(); - + baseClass = baseClass->superClass(); } + + // Strip interface name from object name (e.g. PipeTransform -> Pipe) + QString name = QString(metaObject()->className()).remove("br::"); + if (name.endsWith(interfaceName)) name = name.left(name.size() - interfaceName.size()); setObjectName(name); - // Reset all properties + // Set properties to their default values for (int i=0; ipropertyCount(); i++) { QMetaProperty property = metaObject()->property(i); if (property.isResettable()) @@ -747,20 +742,16 @@ void Object::init(const File &file_) foreach (QString key, file.localKeys()) { const QVariant value = file.value(key); - const QString valueString = value.toString(); - if (key.startsWith(("_Arg"))) { int argumentNumber = key.mid(4).toInt(); int targetIdx = argumentNumber + firstAvailablePropertyIdx; if (targetIdx >= metaObject()->propertyCount()) { - qWarning("too many arguments for transform %s, ignoring %s", qPrintable(objectName()), qPrintable(valueString)); + qWarning("Too many arguments for object: %s, ignoring: %s", qPrintable(objectName()), qPrintable(value.toString())); continue; } key = metaObject()->property(targetIdx).name(); } - - if (valueString.isEmpty()) setProperty(key, value); // Set the property directly - else setProperty(key, valueString); // Parse the value first + setProperty(key, value); } init(); @@ -1121,14 +1112,9 @@ Transform *Transform::make(QString str, QObject *parent) Transform *transform = Factory::make(f); if (transform->independent) { -// Transform *independentTransform = Factory::make(".Independent"); -// static_cast(independentTransform)->setProperty("transform", qVariantFromValue(transform)); -// independentTransform->init(); -// transform = independentTransform; - - File independent(".Independent"); - independent.set("transform", qVariantFromValue(transform)); - transform = Factory::make(independent); + File independent(".Independent"); + independent.set("transform", qVariantFromValue(transform)); + transform = Factory::make(independent); } transform->setParent(parent); diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 9164e4c..430e7ab 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -44,7 +44,7 @@ /*! * \defgroup cpp_plugin_sdk C++ Plugin SDK - * \brief Plugin API for developing new algorithms. + * \brief Plugin API for extending OpenBR functionality. * * \code * #include @@ -96,7 +96,7 @@ namespace br * * \b Example:
* Note the symmetry between \c BR_PROPERTY and \c Q_PROPERTY. - * \snippet sdk/plugins/misc.cpp example_transform + * \snippet openbr/plugins/misc.cpp example_transform */ #define BR_PROPERTY(TYPE,NAME,DEFAULT) \ TYPE NAME; \ @@ -107,19 +107,28 @@ void reset_##NAME() { NAME = DEFAULT; } /*! * \brief A file path with associated metadata. * - * The br::File is one of the workhorse classes in OpenBR. + * The File is one of two important data structures in OpenBR (the Template is the other). * It is typically used to store the path to a file on disk with associated metadata. - * The ability to associate a metadata map with the file helps keep the API simple and stable while providing customizable behavior when appropriate. + * The ability to associate a key/value metadata table with the file helps keep the API simple while providing customizable behavior. * - * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata. - * If the key does not exist in the local map then it will be resolved using the properities in the global br::Context. - * This has the desirable effect that file metadata may optionally be set globally using br::Context::set to operate on all files. + * When querying the value of a metadata key, the value will first try to be resolved against the file's private metadata table. + * If the key does not exist in its local table then it will be resolved against the properities in the global Context. + * By design file metadata may be set globally using Context::setProperty to operate on all files. * * Files have a simple grammar that allow them to be converted to and from strings. * If a string ends with a \c ] or \c ) then the text within the final \c [] or \c () are parsed as comma sperated metadata fields. - * Fields within \c [] are expected to have the format [key1=value1, key2=value2, ..., keyN=valueN]. - * Fields within \c () are expected to have the format (value1, value2, ..., valueN) with the keys determined from the order of \c Q_PROPERTY. - * The rest of the string is assigned to #name. + * By convention, fields within \c [] are expected to have the format [key1=value1, key2=value2, ..., keyN=valueN] where order is irrelevant. + * Fields within \c () are expected to have the format (value1, value2, ..., valueN) where order matters and the key context dependent. + * The left hand side of the string not parsed in a manner described above is assigned to #name. + * + * Values are not necessarily stored as strings in the metadata table. + * The system will attempt to infer and convert them to their "native" type. + * The conversion logic is as follows: + * -# If the value starts with \c [ and ends with \c ] then it is treated as a comma separated list and represented with \c QVariantList. Each value in the list is parsed recursively. + * -# If the value starts with \c ( and ends with \c ) and contains four comma separated elements, each convertable to a floating point number, then it is represented with \c QRectF. + * -# If the value starts with \c ( and ends with \c ) and contains two comma separated elements, each convertable to a floating point number, then it is represented with \c QPointF. + * -# If the value is convertable to a floating point number then it is represented with \c float. + * -# Otherwise, it is represented with \c QString. * * The metadata keys \c Subject and \c Label have special significance in the system. * \c Subject is a string specifying a unique identifier used to determine ground truth match/non-match. @@ -298,7 +307,7 @@ struct BR_EXPORT FileList : public QList /*! * \brief A list of matrices associated with a file. * - * The br::Template is one of the workhorse classes in OpenBR. + * The Template is one of two important data structures in OpenBR (the File is the other). * A template represents a biometric at various stages of enrollment and can be modified by br::Transform and compared to other templates with br::Distance. * * While there exist many cases (ex. video enrollment, multiple face detects, per-patch subspace learning, ...) where the template will contain more than one matrix, @@ -489,9 +498,7 @@ struct TemplateList : public QList