diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 84896c29a..3925d2808 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -8,5 +8,7 @@ if(NOT BR_PACKAGE_THIRDPARTY) # Build additional OpenBR utilities add_subdirectory(br-gui) + + add_subdirectory(br-docs) endif() endif() diff --git a/app/br-docs/CMakeLists.txt b/app/br-docs/CMakeLists.txt new file mode 100644 index 000000000..6fcacfa4f --- /dev/null +++ b/app/br-docs/CMakeLists.txt @@ -0,0 +1,9 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +add_executable(br-docs br-docs.cpp ${BR_RESOURCES}) + +foreach(QT_DEPENDENCY IN LISTS QT_DEPENDENCIES) + target_link_libraries(br-docs "Qt6::${QT_DEPENDENCY}") +endforeach() + +target_link_libraries(br-docs openbr ${BR_THIRDPARTY_LIBS} opencv_core) +install(TARGETS br-docs RUNTIME DESTINATION bin) diff --git a/app/br-docs/br-docs.cpp b/app/br-docs/br-docs.cpp new file mode 100644 index 000000000..e89e08327 --- /dev/null +++ b/app/br-docs/br-docs.cpp @@ -0,0 +1,27 @@ + +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + QRegularExpression regex; + + for (int i = 1; i < argc; i++) { + QString arg = QString::fromLocal8Bit(argv[i]); + if (arg == "--help" || arg == "-h") { + printf("Usage: br-docs\n"); + printf(" --regex Only generate docs for transforms matching \n"); + return 0; + } else if (arg == "--regex") { + regex = QRegularExpression(QString::fromLocal8Bit(argv[++i])); + } + } + + br::Context::initialize(argc, argv); + + br::AllDocs(regex); + + br::Context::finalize(); +} diff --git a/app/br/br.cpp b/app/br/br.cpp index ee362ec09..0b03d573d 100644 --- a/app/br/br.cpp +++ b/app/br/br.cpp @@ -210,6 +210,9 @@ class FakeMain : public QRunnable else if (!strcmp(fun, "help")) { check(parc == 0, "No parameters expected for 'help'."); help(); + } else if (!strcmp(fun, "docs")) { + check(parc == 0, "No parameters expected for 'docs'."); + br_docs(); } else if (!strcmp(fun, "gui")) { // Do nothing because we checked for this flag prior to initialization } else if (!strcmp(fun, "objects")) { diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index 9fb8ee396..728144440 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -45,12 +45,11 @@ struct AlgorithmCore AlgorithmCore(const QString &name) { - if (name == "algorithm") { - this->name = Globals->algorithm; - init(Globals->algorithm); - } else { - this->name = name; - init(name); + this->name = name == "algorithm" ? Globals->algorithm : name; + try { + init(this->name); + } catch (...) { + qFatal("Failed to initialize%s: %s", qPrintable(name == "algorithm" ? " algorithm" : ""), qPrintable(this->name)); } progressCounter = QSharedPointer(Transform::make("ProgressCounter", NULL)); @@ -653,6 +652,30 @@ void br::Train(const File &input, const File &model) AlgorithmManager::getAlgorithm(model.get("algorithm"))->train(input, model); } +void br::Docs() +{ + QString docs = AlgorithmManager::getAlgorithm("algorithm")->transform->docs(0); + printf("%s\n", docs.toStdString().c_str()); +} + +void br::AllDocs(QRegularExpression regex) +{ + QStringList names = Factory::names(); + printf("Registered Transforms: %d\n", names.size()); + + foreach (const QString &name, names) { + if (!regex.pattern().isEmpty() && !regex.match(name).hasMatch()) + continue; + try { + br::Transform* transform = br::Factory::make("." + name); + QString docs = transform->docs(4); + printf("%s", docs.toStdString().c_str()); + } catch (...) { + printf(" %s(?\?\?)\n", name.toStdString().c_str()); + } + } +} + void br::Enroll(const File &input, const File &gallery) { AlgorithmManager::getAlgorithm(gallery.get("algorithm"))->enroll(input, gallery); diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index 78c4bbb61..270725fcc 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -151,6 +151,11 @@ void br_eval_eer(const char *predicted_xml, const char *gt_property, const char EvalEER(predicted_xml, gt_property, distribution_property, pdf); } +void br_docs() +{ + Docs(); +} + void br_finalize() { Context::finalize(); @@ -293,7 +298,11 @@ void br_set_header(const char *matrix, const char *target_gallery, const char *q void br_set_property(const char *key, const char *value) { - Globals->setProperty(key, value); + try { + Globals->setProperty(key, value); + } catch (...) { + qFatal("Failed to set property %s to %s", key, value); + } } int br_time_remaining() diff --git a/openbr/openbr.h b/openbr/openbr.h index 124e122c4..8b3c10432 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -26,6 +26,8 @@ extern "C" { BR_EXPORT const char *br_about(); +BR_EXPORT void br_docs(); + BR_EXPORT void br_cat(int num_input_galleries, const char *input_galleries[], const char *output_gallery); BR_EXPORT void br_deduplicate(const char *input_gallery, const char *output_gallery, const char *threshold); diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 2df3497e4..29aa5cac4 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -681,7 +681,7 @@ QString Object::argument(int index, bool expanded) const return "[" + strings.join(",") + "]"; } else if (type == "br::Transform*") { - return variant.value()->description(expanded); + return variant.isNull() ? "" : variant.value()->description(expanded); } else if (type == "br::Distance*") { return variant.value()->description(expanded); } else if (type == "br::Representation*") { @@ -886,7 +886,10 @@ void Object::setProperty(const QString &name, QVariant value) { QString type; int index = metaObject()->indexOfProperty(qPrintable(name)); - if (index != -1) type = metaObject()->property(index).typeName(); + if (index != -1) + type = metaObject()->property(index).typeName(); + else + printf("Object %s does not have property %s! Can not set its value to %s!\n", metaObject()->className(), qPrintable(name), qPrintable(value.toString())); if (metaObject()->property(index).isEnumType()) { // This is necessary because setProperty can only set enums @@ -995,8 +998,7 @@ void Object::setProperty(const QString &name, QVariant value) } if (!QObject::setProperty(qPrintable(name), value) && !type.isEmpty()) - qFatal("Failed to set %s %s::%s to: %s", - qPrintable(type), metaObject()->className(), qPrintable(name), qPrintable(value.toString())); + throw QString("Failed to set %1 %2::%3 to: %4").arg(type).arg(metaObject()->className()).arg(name).arg(value.toString()).toStdString(); } QStringList Object::parse(const QString &string, char split) @@ -1271,8 +1273,13 @@ QStringList br::Context::objects(const char *abstractions, const char *implement } if (abstractionsRegExp.match("Transform").hasMatch()) { foreach (const QString &name, Factory::names()) - if (implementationsRegExp.match(name).hasMatch()) - objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + if (implementationsRegExp.match(name).hasMatch()) { + try { + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + } catch (...) { + // Exclude transforms that fail to initialize + } + } } if (abstractionsRegExp.match("Representation").hasMatch()) { foreach (const QString &name, Factory::names()) @@ -1598,6 +1605,19 @@ void Transform::train(const QList &data) train(combined); } + +QString Transform::get_doc_header(QString doc, int indent) const +{ + QString name = this->file.suffix(); + QString params; + try { + params = Factory::parameters("."+description(false)); + } catch (...) { + params = "?\?\?"; + } + return QString("%1%2(%3): %4\n").arg(QString(indent, ' ')).arg(name).arg(params).arg(doc); +} + /* Distance - public methods */ Distance *Distance::make(QString str, QObject *parent) { diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 344637c0f..6833fa10f 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -752,6 +753,20 @@ class BR_EXPORT Transform : public Object virtual void train(const TemplateList &data); virtual void train(const QList &data); + QString get_doc_header(QString doc, int indent) const; + + QString get_doc(QString doc, int indent) const { + return QString("%1%2\n").arg(QString(indent, ' ')).arg(doc); + } + + virtual QString docs(int indent) const { + return get_doc_header(docs(), indent); + } + + virtual const QString docs() const + { + return ""; + } virtual void project(const Template &src, Template &dst) const = 0; virtual void project(const TemplateList &src, TemplateList &dst) const; @@ -918,6 +933,10 @@ BR_EXPORT bool IsClassifier(const QString &algorithm); BR_EXPORT void Train(const File &input, const File &model); +BR_EXPORT void Docs(); + +BR_EXPORT void AllDocs(QRegularExpression regex); + BR_EXPORT void Enroll(const File &input, const File &gallery = File()); BR_EXPORT void Enroll(TemplateList &tmpl); diff --git a/openbr/plugins/core/discard.cpp b/openbr/plugins/core/discard.cpp index ceab90276..9bbf3fe3e 100644 --- a/openbr/plugins/core/discard.cpp +++ b/openbr/plugins/core/discard.cpp @@ -33,6 +33,11 @@ class DiscardTransform : public UntrainableMetaTransform { dst.file = src.file; } + + const QString docs() const + { + return "Removes all matrices from a template."; + } }; BR_REGISTER(Transform, DiscardTransform) diff --git a/openbr/plugins/core/fork.cpp b/openbr/plugins/core/fork.cpp index 961498992..064fa959e 100644 --- a/openbr/plugins/core/fork.cpp +++ b/openbr/plugins/core/fork.cpp @@ -105,6 +105,18 @@ class ForkTransform : public CompositeTransform } } + QString docs(int indent) const + { + QString docs = get_doc_header("Project the input template(s) through each transform and concatenate the results", indent); + foreach (const Transform *f, transforms) { + // We need to create a new instance of the transform for any independent transforms + // because they are wrapped by MetaTransform until project() is called. + QString description = "." + f->description(false); // Needs to start with a . + docs += Factory::make(description)->docs(indent + 4); + } + return docs; + } + protected: // Apply each transform to src, concatenate the results diff --git a/openbr/plugins/core/identity.cpp b/openbr/plugins/core/identity.cpp index 8b5bf42bf..45c19d52e 100644 --- a/openbr/plugins/core/identity.cpp +++ b/openbr/plugins/core/identity.cpp @@ -33,6 +33,11 @@ class IdentityTransform : public UntrainableMetaTransform { dst = src; } + + const QString docs() const + { + return "Noop transform"; + } }; BR_REGISTER(Transform, IdentityTransform) diff --git a/openbr/plugins/core/independent.cpp b/openbr/plugins/core/independent.cpp index 8dfbdbe54..4477626d0 100644 --- a/openbr/plugins/core/independent.cpp +++ b/openbr/plugins/core/independent.cpp @@ -41,7 +41,7 @@ class IndependentTransform : public MetaTransform QString description(bool expanded) const { - return transform->description(expanded); + return transform != NULL ? transform->description(expanded) : "Identity"; } // can't use general setPropertyRecursive because of transforms oddness diff --git a/openbr/plugins/core/pipe.cpp b/openbr/plugins/core/pipe.cpp index 1cd239a9b..c1c4a580c 100644 --- a/openbr/plugins/core/pipe.cpp +++ b/openbr/plugins/core/pipe.cpp @@ -196,6 +196,17 @@ class PipeTransform : public CompositeTransform return result; } + QString docs(int indent) const + { + QString docs = get_doc_header("Project the output of each Transform into the next", indent); + foreach (const Transform *f, transforms) { + // We need to create a new instance of the transform for any independent transforms + // because they are wrapped by MetaTransform until project() is called. + QString description = "." + f->description(false); // Needs to start with a . + docs += Factory::make(description)->docs(indent + 4); + } + return docs; + } protected: // Template list project -- process templates in parallel through Transform::project // or if parallelism is disabled, handle them sequentially diff --git a/openbr/plugins/distance/dist.cpp b/openbr/plugins/distance/dist.cpp index c51d59280..5ca2557e4 100644 --- a/openbr/plugins/distance/dist.cpp +++ b/openbr/plugins/distance/dist.cpp @@ -44,7 +44,8 @@ class DistDistance : public UntrainableDistance L1, L2, Cosine, - Dot}; + Dot, + Default = L2}; private: BR_PROPERTY(Metric, metric, L2) diff --git a/openbr/plugins/gui/show.cpp b/openbr/plugins/gui/show.cpp index e6d3f2fb8..3bd93ca81 100644 --- a/openbr/plugins/gui/show.cpp +++ b/openbr/plugins/gui/show.cpp @@ -611,10 +611,8 @@ class ShowTransform : public TimeVaryingTransform template void initActual() { - if (!Globals->useGui) { - qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className()); - return; - } + if (!Globals->useGui) + throw QString("GUI transform %1 created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.").arg(this->metaObject()->className()).toStdString(); if (displayBuffer) delete displayBuffer; @@ -863,10 +861,8 @@ class ElicitTransform : public ShowTransform template void initActual() { - if (!Globals->useGui) { - qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className()); - return; - } + if (!Globals->useGui) + throw QString("GUI transform %1 created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.").arg(this->metaObject()->className()).toStdString(); TimeVaryingTransform::init(); diff --git a/openbr/plugins/imgproc/cvtfloat.cpp b/openbr/plugins/imgproc/cvtfloat.cpp index 993392836..d0ae1208c 100644 --- a/openbr/plugins/imgproc/cvtfloat.cpp +++ b/openbr/plugins/imgproc/cvtfloat.cpp @@ -34,6 +34,11 @@ class CvtFloatTransform : public UntrainableTransform { src.m().convertTo(dst, CV_32F); } + + const QString docs() const + { + return "Convert the matrix to float32"; + } }; BR_REGISTER(Transform, CvtFloatTransform) diff --git a/openbr/plugins/imgproc/cvtuchar.cpp b/openbr/plugins/imgproc/cvtuchar.cpp index dc98ef57d..13048d200 100644 --- a/openbr/plugins/imgproc/cvtuchar.cpp +++ b/openbr/plugins/imgproc/cvtuchar.cpp @@ -33,6 +33,11 @@ class CvtUCharTransform : public UntrainableTransform { OpenCVUtils::cvtUChar(src, dst); } + + const QString docs() const + { + return "Convert the matrix to uint8"; + } }; BR_REGISTER(Transform, CvtUCharTransform) diff --git a/openbr/plugins/imgproc/ensurechannels.cpp b/openbr/plugins/imgproc/ensurechannels.cpp index f525f126f..3d5edadd0 100644 --- a/openbr/plugins/imgproc/ensurechannels.cpp +++ b/openbr/plugins/imgproc/ensurechannels.cpp @@ -58,6 +58,11 @@ class EnsureChannelsTransform : public UntrainableTransform merge(mv, dst); } } + + const QString docs() const + { + return "Ensure the matrix has n channels by adding or removing channels."; + } }; BR_REGISTER(Transform, EnsureChannelsTransform) diff --git a/openbr/plugins/imgproc/if.cpp b/openbr/plugins/imgproc/if.cpp index bba44f887..57c2c52c2 100644 --- a/openbr/plugins/imgproc/if.cpp +++ b/openbr/plugins/imgproc/if.cpp @@ -89,6 +89,10 @@ class IfTransform : public MetaTransform dst.append(ifFalse); } + const QString docs() const + { + return "For each template that pases the comparison, project it thru the transform"; + } }; BR_REGISTER(Transform, IfTransform) diff --git a/openbr/plugins/io/print.cpp b/openbr/plugins/io/print.cpp index d8bcb670e..6862176dc 100644 --- a/openbr/plugins/io/print.cpp +++ b/openbr/plugins/io/print.cpp @@ -46,6 +46,11 @@ class PrintTransform : public UntrainableMetaTransform QString fteString = src.file.fte ? "\n FTE=true" : QString(); fprintf(error ? stderr : stdout, "%s%s\n %s\n%s", qPrintable(nameString), qPrintable(fteString), qPrintable(matricies.join(",")), qPrintable(dataString)); } + + const QString docs() const + { + return "Print the specified keys (all if empty)"; + } }; BR_REGISTER(Transform, PrintTransform) diff --git a/openbr/plugins/io/read.cpp b/openbr/plugins/io/read.cpp index 4706f6daa..8126e0540 100644 --- a/openbr/plugins/io/read.cpp +++ b/openbr/plugins/io/read.cpp @@ -71,6 +71,11 @@ class ReadTransform : public UntrainableMetaTransform if (dst.file.fte && Globals->verbose) qWarning("Error opening %s", qPrintable(src.file.flat())); } + + const QString docs() const + { + return "Read the image from disk"; + } }; BR_REGISTER(Transform, ReadTransform) diff --git a/openbr/plugins/metadata/eyes.cpp b/openbr/plugins/metadata/eyes.cpp index 4b52404cb..90b7c76df 100644 --- a/openbr/plugins/metadata/eyes.cpp +++ b/openbr/plugins/metadata/eyes.cpp @@ -71,7 +71,8 @@ class ASEFEyesTransform : public UntrainableTransform // Open the eye locator model file.setFileName(Globals->sdkPath + "/share/openbr/models/EyeLocatorASEF128x128.fel"); - if (!file.open(QFile::ReadOnly)) qFatal("Failed to open %s for reading.", qPrintable(file.fileName())); + if (!file.open(QFile::ReadOnly)) + throw QString("Failed to open %1 for reading.").arg(file.fileName()).toStdString(); // Check the first line if (file.readLine().simplified() != "CFEL") qFatal("Invalid header."); diff --git a/openbr/plugins/metadata/removefte.cpp b/openbr/plugins/metadata/removefte.cpp index 6032d3bfe..72ff13f0e 100644 --- a/openbr/plugins/metadata/removefte.cpp +++ b/openbr/plugins/metadata/removefte.cpp @@ -24,6 +24,11 @@ class RemoveFTETransform : public UntrainableMetaTransform if (!src[i].file.fte) dst.append(src[i]); } + + const QString docs() const + { + return "Remove any templates marked FTE"; + } }; BR_REGISTER(Transform, RemoveFTETransform) diff --git a/openbr/plugins/metadata/savemat.cpp b/openbr/plugins/metadata/savemat.cpp index 9a303bcbc..d9ec1e128 100644 --- a/openbr/plugins/metadata/savemat.cpp +++ b/openbr/plugins/metadata/savemat.cpp @@ -36,6 +36,11 @@ class SaveMatTransform : public UntrainableMetaTransform dst = src; dst.file.set(propName, QVariant::fromValue(dst.m())); } + + const QString docs() const + { + return "Save the template matrix into the metadata under the specified key"; + } }; BR_REGISTER(Transform, SaveMatTransform) @@ -65,6 +70,19 @@ class JustTransform : public UntrainableMetaTransform dst.file.set(key, tmp.file.value(key)); } } + + QString docs(int indent) const + { + QString docs = get_doc_header("Preserve the input template and only updated the specified keys", indent); + if (transform) + { + // We need to create a new instance of the transform for any independent transforms + // because they are wrapped by MetaTransform until project() is called. + QString description = "." + transform->description(false); // Needs to start with a . + docs += Factory::make(description)->docs(indent + 4); + } + return docs; + } }; BR_REGISTER(Transform, JustTransform)