diff --git a/CMakeLists.txt b/CMakeLists.txt index 378b0bb..18b005c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ else() set(CMAKE_EXE_LINKER_FLAGS "-Wl,--enable-auto-import") # Fixes a linker warning set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--enable-auto-import") elseif(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /DNOMINMAX /D_CRT_SECURE_NO_WARNINGS /wd4018 /wd4244 /wd4267 /wd4305 /wd4308 /wd4307 /wd4554 /wd4996 /nologo /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /DNOMINMAX /D_CRT_SECURE_NO_WARNINGS /wd4018 /wd4244 /wd4267 /wd4305 /wd4308 /wd4307 /wd4554 /wd4996 /w34100 /nologo /MP") endif() endif() diff --git a/openbr/core/core.cpp b/openbr/core/core.cpp index a9eec23..42660f4 100644 --- a/openbr/core/core.cpp +++ b/openbr/core/core.cpp @@ -23,24 +23,36 @@ namespace br { +void noDelete(Transform *target) +{ + (void) target; +} + struct AlgorithmCore { + enum CompareMode + { + None, + DistanceCompare, + TransformCompare, + }; + QSharedPointer transform; + QSharedPointer simplifiedTransform; + QSharedPointer comparison; QSharedPointer distance; - QString galleryCompareString; - - QString transformString; - QString distanceString; + QSharedPointer progressCounter; AlgorithmCore(const QString &name) { this->name = name; init(name); + progressCounter = QSharedPointer(Transform::make("ProgressCounter", NULL)); } bool isClassifier() const { - return distance.isNull(); + return comparison.isNull(); } void train(const File &input, const QString &model) @@ -48,15 +60,7 @@ struct AlgorithmCore qDebug("Training on %s%s", qPrintable(input.flat()), model.isEmpty() ? "" : qPrintable(" to " + model)); - QScopedPointer trainingWrapper(Transform::make("DirectStream(readMode=DistributeFrames)", NULL)); - - CompositeTransform *downcast = dynamic_cast(trainingWrapper.data()); - if (downcast == NULL) - qFatal("downcast failed?"); - downcast->transforms.append(this->transform.data()); - - downcast->init(); - + QScopedPointer trainingWrapper(br::wrapTransform(transform.data(), "Stream(readMode=DistributeFrames)")); TemplateList data(TemplateList::fromGallery(input)); if (transform.isNull()) qFatal("Null transform."); @@ -65,14 +69,14 @@ struct AlgorithmCore Globals->startTime.start(); qDebug("Training Enrollment"); - downcast->train(data); + trainingWrapper->train(data); if (!distance.isNull()) { if (Globals->crossValidate > 0) for (int i=data.size()-1; i>=0; i--) if (data[i].file.get("allPartitions",false)) data.removeAt(i); qDebug("Projecting Enrollment"); - downcast->projectUpdate(data,data); + trainingWrapper->projectUpdate(data,data); qDebug("Training Comparison"); distance->train(data); @@ -84,6 +88,18 @@ struct AlgorithmCore } qDebug("Training Time: %s", qPrintable(QtUtils::toTime(Globals->startTime.elapsed()/1000.0f))); + + simplifyTransform(); + } + + void simplifyTransform() + { + bool newTForm = false; + Transform *temp = transform->simplify(newTForm); + if (newTForm) + simplifiedTransform = QSharedPointer(temp); + else + simplifiedTransform = QSharedPointer(temp, noDelete); } void store(const QString &model) const @@ -93,11 +109,21 @@ struct AlgorithmCore QDataStream out(&data, QFile::WriteOnly); // Serialize algorithm to stream - out << name; - transform->store(out); - const bool hasComparer = !distance.isNull(); - out << hasComparer; - if (hasComparer) distance->store(out); + transform->serialize(out); + + qint32 mode = None; + if (!distance.isNull()) + mode = DistanceCompare; + else if (!comparison.isNull()) + mode = TransformCompare; + + out << mode; + + if (mode == DistanceCompare) + distance->serialize(out); + + if (mode == TransformCompare) + comparison->serialize(out); // Compress and save to file QtUtils::writeFile(model, data, -1); @@ -113,10 +139,21 @@ struct AlgorithmCore QDataStream in(&data, QFile::ReadOnly); // Load algorithm - in >> name; init(Globals->abbreviations.contains(name) ? Globals->abbreviations[name] : name); - transform->load(in); - bool hasDistance; in >> hasDistance; - if (hasDistance) distance->load(in); + transform = QSharedPointer(Transform::deserialize(in)); + + qint32 mode; + in >> mode; + + if (mode == DistanceCompare) { + QString distanceDescription; + in >> distanceDescription; + distance = QSharedPointer(Distance::make(distanceDescription, NULL)); + distance->load(in); + comparison = QSharedPointer(Transform::make("GalleryCompare", NULL)); + comparison->setPropertyRecursive("distance", QVariant::fromValue(distance.data())); + } + if (mode == TransformCompare) + comparison = QSharedPointer(Transform::deserialize(in)); } File getMemoryGallery(const File &file) const @@ -148,54 +185,38 @@ struct AlgorithmCore Gallery *temp = Gallery::make(input); qint64 total = temp->totalSize(); - QScopedPointer basePipe; - - QString pipeDesc = "GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(total)+")+Discard"; + Transform *enroll = simplifiedTransform.data(); - if (!multiProcess) { - basePipe.reset(Transform::make(pipeDesc,NULL)); - CompositeTransform *downcast = dynamic_cast(basePipe.data()); + if (multiProcess) + enroll = wrapTransform(enroll, "ProcessWrapper"); - if (downcast == NULL) qFatal("downcast failed?"); - - downcast->transforms.prepend(this->transform.data()); - if (fileExclusion) { - Transform *temp = Transform::make("FileExclusion(" + gallery.flat() + ")", downcast); - downcast->transforms.prepend(temp); - } - - // call init on the pipe to collapse the algorithm (if its top level is a pipe) - downcast->init(); - } - else { - pipeDesc = "ProcessWrapper("+transformString+")+"+pipeDesc; - if (fileExclusion) - pipeDesc = "FileExclusion(" + gallery.flat() +")+" + pipeDesc; - - basePipe.reset(Transform::make(pipeDesc,NULL)); - } + QList stages; + stages.append(enroll); - // Next, we make a Stream (with placeholder transform) - QString streamDesc = "Stream(readMode=StreamGallery)"; - QScopedPointer baseStream(Transform::make(streamDesc, NULL)); - WrapperTransform *wrapper = dynamic_cast (baseStream.data()); + QString outputDesc; + if (fileExclusion) + outputDesc = "FileExclusion(" + gallery.flat() + ")+"; + outputDesc.append("GalleryOutput("+gallery.flat()+")"); + QScopedPointer outputTform(Transform::make(outputDesc, NULL)); + stages.append(outputTform.data()); + stages.append(progressCounter.data()); + QScopedPointer discard(Transform::make("Discard",NULL)); + stages.append(discard.data()); - // replace that placeholder with the pipe we built - wrapper->transform = basePipe.data(); + QScopedPointer pipeline(br::pipeTransforms(stages)); - // and get the final stream's stages by reinterpreting the pipe. Perfectly straightforward. - wrapper->init(); - - Globals->startTime.start(); - Globals->currentStep = 0; - Globals->totalSteps = total; + QScopedPointer stream(br::wrapTransform(pipeline.data(), "Stream(readMode=StreamGallery)")); TemplateList data, output; data.append(input); - wrapper->projectUpdate(data, output); + progressCounter->setPropertyRecursive("totalProgress", QString::number(total)); + stream->projectUpdate(data, output); files.append(output.files()); + if (multiProcess) + delete enroll; + return files; } @@ -364,7 +385,6 @@ struct AlgorithmCore targetMetadata = FileList::fromGallery(targetGallery, true); queryMetadata = FileList::fromGallery(queryGallery, true); - // Is the target or query set larger? We will use the larger as the rows of our comparison matrix (and transpose the output if necessary) transposeMode = targetMetadata.size() > queryMetadata.size(); @@ -389,12 +409,12 @@ struct AlgorithmCore // simple make sure the enrolled data is stored in a memGallery, but in multi-process mode we save the enrolled // data to disk (as a .gal file) so that each worker process can read it without re-doing enrollment. File colEnrolledGallery = colGallery; - QString targetExtension = multiProcess ? "gal" : "mem"; + QString targetExtension = "mem"; // If the column gallery is not already of the appropriate type, we need to do something if (colGallery.suffix() != targetExtension) { // Build the name of a gallery containing the enrolled data, of the appropriate type. - colEnrolledGallery = colGallery.baseName() + colGallery.hash() + (multiProcess ? ".gal" : ".mem"); + colEnrolledGallery = colGallery.baseName() + colGallery.hash() + '.' + targetExtension; // Check if we have to do real enrollment, and not just convert the gallery's type. if (!(QStringList() << "gal" << "template" << "mem").contains(colGallery.suffix())) @@ -435,37 +455,24 @@ struct AlgorithmCore // The actual comparison step is done by a GalleryCompare transform, which has a Distance, and a gallery as data. // Incoming templates are compared against the templates in the gallery, and the output is the resulting score // vector. + TemplateList tlist = TemplateList::fromGallery(colEnrolledGallery); + comparison->train(tlist); + comparison->setPropertyRecursive("galleryName",""); + QString compareRegionDesc; + QList enrollCompare; + enrollCompare.append(comparison.data()); - if (this->galleryCompareString.isEmpty() ) - compareRegionDesc = "Pipe([GalleryCompare("+Globals->algorithm+",galleryName="+colEnrolledGallery.flat()+")])"; - else - compareRegionDesc = "Pipe(["+galleryCompareString+"(galleryName="+colEnrolledGallery.flat()+")])"; - - QScopedPointer compareRegion; - // If we need to enroll the row set, we add the current algorithm's enrollment transform before the - // GalleryCompare in a pipe. - if (needEnrollRows) { - if (!multiProcess) { - compareRegionDesc = compareRegionDesc; - compareRegion.reset(Transform::make(compareRegionDesc,NULL)); - CompositeTransform *downcast = dynamic_cast (compareRegion.data()); - if (downcast == NULL) - qFatal("Pipe downcast failed in compare"); - - downcast->transforms.prepend(this->transform.data()); - downcast->init(); - } - else { - compareRegionDesc = "ProcessWrapper(" + this->transformString + "+" + compareRegionDesc + ")"; - compareRegion.reset(Transform::make(compareRegionDesc, NULL)); - } - } - else { - if (multiProcess) - compareRegionDesc = "ProcessWrapper(" + compareRegionDesc + ")"; - compareRegion.reset(Transform::make(compareRegionDesc,NULL)); - } + // if we have to enroll the row gallery, add that transform to the list + if (needEnrollRows) + enrollCompare.prepend(simplifiedTransform.data()); + + Transform *compareRegionBase = pipeTransforms(enrollCompare); + // If in multi-process mode, wrap the enroll+compare structure in a ProcessWrapper. + if (multiProcess) + compareRegionBase = wrapTransform(compareRegionBase, "ProcessWrapper"); + + QScopedPointer compareRegion(compareRegionBase); // At this point, compareRegion is a transform, which optionally does enrollment, then compares the row // set against the column set. If in multi-process mode, the enrollment and comparison are wrapped in a @@ -473,47 +480,36 @@ struct AlgorithmCore // We also need to add Output and progress counting to the algorithm we are building, so we will assign them to // two stages of a pipe. - QString joinDesc = "Pipe()"; - QScopedPointer join(Transform::make(joinDesc, NULL)); + QList compareOutput; + compareOutput.append(compareRegion.data()); // The output transform takes the metadata memGalleries we set up previously as input, along with the // output specification we were passed. Gallery metadata is necessary for some Outputs to function correctly. QString outputString = output.flat().isEmpty() ? "Empty" : output.flat(); QString outputRegionDesc = "Output("+ outputString +"," + targetGallery.flat() +"," + queryGallery.flat() + ","+ QString::number(transposeMode ? 1 : 0) + ")"; - // The ProgressCounter transform will simply provide a display about the number of rows completed. - outputRegionDesc += "+ProgressCounter("+QString::number(rowSize)+")+Discard"; - QScopedPointer outputTform(Transform::make(outputRegionDesc, NULL)); + QScopedPointer outputTForm(Transform::make(outputRegionDesc,NULL)); + compareOutput.append(outputTForm.data()); - // Assign the comparison transform we previously built, and the output transform we just built to - // two stages of a pipe. - CompositeTransform *downcast = dynamic_cast (join.data()); - downcast->transforms.append(compareRegion.data()); - downcast->transforms.append(outputTform.data()); + // The ProgressCounter transform will simply provide a display about the number of rows completed. + compareOutput.append(progressCounter.data()); + QScopedPointer discard(Transform::make("Discard",NULL)); + compareOutput.append(discard.data()); // With this, we have set up a transform which (optionally) enrolls templates, compares them // against a gallery, and outputs them. - join->init(); + Transform *pipeline = br::pipeTransforms(compareOutput); // Now, we will give that base transform to a stream, which will incrementally read the row gallery // and pass the transforms it reads through the base algorithm. - QString streamDesc = "Stream(readMode=StreamGallery)"; - QScopedPointer streamBase(Transform::make(streamDesc, NULL)); - WrapperTransform *streamWrapper = dynamic_cast (streamBase.data()); - streamWrapper->transform = join.data(); - - // The transform we will use is now complete. - streamWrapper->init(); + QScopedPointer streamWrapper(br::wrapTransform(pipeline, "Stream(readMode=StreamGallery)")); // We set up a template containing the rowGallery we want to compare. TemplateList rowGalleryTemplate; rowGalleryTemplate.append(Template(rowGallery)); TemplateList outputGallery; - // Set up progress counting variables - Globals->currentStep = 0; - Globals->currentProgress = 0; - Globals->totalSteps = rowSize; - Globals->startTime.start(); + // initialize the progress counter + progressCounter->setPropertyRecursive("totalProgress", QString::number(rowSize)); // Do the actual comparisons streamWrapper->projectUpdate(rowGalleryTemplate, outputGallery); @@ -547,37 +543,39 @@ private: void init(const QString &description) { - if (loadOrExpand(description)) + bool newTForm = false; + + if (loadOrExpand(description)) { + simplifyTransform(); return; + } // check if the description is an abbreviation or model file with additional arguments supplied File parsed("."+description); if (loadOrExpand(parsed.suffix())) { applyAdditionalProperties(parsed, transform.data()); + simplifyTransform(); return; } + //! [Parsing the algorithm description] const bool compareTransform = description.contains('!'); QStringList words = QtUtils::parse(description, compareTransform ? '!' : ':'); if ((words.size() < 1) || (words.size() > 2)) qFatal("Invalid algorithm format."); - //! [Parsing the algorithm description] - transformString = words[0]; - //! [Creating the template generation and comparison methods] transform = QSharedPointer(Transform::make(words[0], NULL)); + simplifyTransform(); + if (words.size() > 1) { if (!compareTransform) { distance = QSharedPointer(Distance::make(words[1], NULL)); - distanceString = words[1]; - galleryCompareString.clear(); - } - else { - galleryCompareString = words[1]; - distanceString.clear(); + comparison = QSharedPointer(Transform::make("GalleryCompare", NULL)); + comparison->setPropertyRecursive("distance", QVariant::fromValue(distance.data())); } - + else + comparison = QSharedPointer(Transform::make(words[1], NULL)); } //! [Creating the template generation and comparison methods] } diff --git a/openbr/core/eval.cpp b/openbr/core/eval.cpp index b169e39..a0e8e68 100644 --- a/openbr/core/eval.cpp +++ b/openbr/core/eval.cpp @@ -81,6 +81,8 @@ static cv::Mat constructMatchingMask(const cv::Mat &scores, const FileList &targ // otherwise, we fail else qFatal("Unable to construct mask for %d by %d score matrix from %d element query set, and %d element target set ", scores.rows, scores.cols, query.length(), target.length()); + + return cv::Mat(); } float Evaluate(const cv::Mat &scores, const FileList &target, const FileList &query, const QString &csv, int partition) diff --git a/openbr/core/qtutils.cpp b/openbr/core/qtutils.cpp index 085a7a5..9e9b1e9 100644 --- a/openbr/core/qtutils.cpp +++ b/openbr/core/qtutils.cpp @@ -105,7 +105,7 @@ void writeFile(const QString &file, const QStringList &lines) if (!f.open(QFile::WriteOnly)) qFatal("Failed to open %s for writing.", qPrintable(file)); - foreach (const QString & line, lines) + foreach (const QString &line, lines) f.write((line+"\n").toLocal8Bit() ); f.close(); diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index 0d1c9c8..f1a3ae4 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -28,7 +28,7 @@ using namespace br; -static int partialCopy(const QString & string, char * buffer, int buffer_length) +static int partialCopy(const QString &string, char *buffer, int buffer_length) { QByteArray byteArray = string.toLocal8Bit(); @@ -109,7 +109,7 @@ float br_eval(const char *simmat, const char *mask, const char *csv) return Evaluate(simmat, mask, csv); } -float br_inplace_eval(const char * simmat, const char *target, const char *query, const char *csv) +float br_inplace_eval(const char *simmat, const char *target, const char *query, const char *csv) { return InplaceEval(simmat, target, query, csv); } @@ -119,7 +119,7 @@ void br_eval_classification(const char *predicted_gallery, const char *truth_gal EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property); } -void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property) +void br_eval_clustering(const char *csv, const char *gallery, const char *truth_property) { EvalClustering(csv, gallery, truth_property); } @@ -178,12 +178,12 @@ void br_make_pairwise_mask(const char *target_input, const char *query_input, co BEE::makePairwiseMask(target_input, query_input, mask); } -int br_most_recent_message(char * buffer, int buffer_length) +int br_most_recent_message(char *buffer, int buffer_length) { return partialCopy(Globals->mostRecentMessage, buffer, buffer_length); } -int br_objects(char * buffer, int buffer_length, const char *abstractions, const char *implementations, bool parameters) +int br_objects(char *buffer, int buffer_length, const char *abstractions, const char *implementations, bool parameters) { return partialCopy(br::Context::objects(abstractions, implementations, parameters).join('\n'), buffer, buffer_length); } @@ -240,7 +240,7 @@ void br_read_pipe(const char *pipe, int *argc, char ***argv) *argv = rawCharArrayList.data(); } -int br_scratch_path(char * buffer, int buffer_length) +int br_scratch_path(char *buffer, int buffer_length) { return partialCopy(Context::scratchPath(), buffer, buffer_length); } @@ -298,9 +298,9 @@ const char *br_version() return version.data(); } -void br_slave_process(const char * baseName) +void br_slave_process(const char *baseName) { - WorkerProcess * worker = new WorkerProcess; + WorkerProcess *worker = new WorkerProcess; worker->transform = Globals->algorithm; worker->baseName = baseName; worker->mainLoop(); @@ -371,7 +371,7 @@ bool br_img_is_empty(br_template tmpl) return t->m().empty(); } -int br_get_filename(char * buffer, int buffer_length, br_template tmpl) +int br_get_filename(char *buffer, int buffer_length, br_template tmpl) { return partialCopy(reinterpret_cast(tmpl)->file.name, buffer, buffer_length); } @@ -382,7 +382,7 @@ void br_set_filename(br_template tmpl, const char *filename) t->file.name = filename; } -int br_get_metadata_string(char * buffer, int buffer_length, br_template tmpl, const char *key) +int br_get_metadata_string(char *buffer, int buffer_length, br_template tmpl, const char *key) { Template *t = reinterpret_cast(tmpl); QVariant qvar = t->file.value(key); diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 0a2e9dc..f456f52 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -585,16 +585,51 @@ QStringList Object::parameters() const return parameters; } -QStringList Object::arguments() const +QStringList Object::prunedArguments(bool expanded) const { QStringList arguments; - for (int i=metaObject()->propertyOffset(); ipropertyCount(); i++) - arguments.append(argument(i)); + QString className = this->metaObject()->className(); + QScopedPointer shellObject; + + if (className.startsWith("br::")) + className = className.mid(4); + if (!className.startsWith(".")) + className = "." + className; + + if (className.endsWith("Distance")) { + className.chop(QString("Distance").size()); + shellObject.reset(Factory::make(className)); + } + else if (className.endsWith("Transform")) { + className.chop(QString("Transform").size()); + shellObject.reset(Factory::make(className)); + } + else if (className.endsWith("Format")) { + className.chop(QString("Format").size()); + shellObject.reset(Factory::make(className)); + } + else if (className.endsWith("Initializer")) { + className.chop(QString("Initializer").size()); + shellObject.reset(Factory::make(className)); + } + else if (className.endsWith("Output")) { + className.chop(QString("Output").size()); + shellObject.reset(Factory::make(className)); + } + + for (int i=firstAvailablePropertyIdx; ipropertyCount(); i++) { + const char *name = metaObject()->property(i).name(); + + QVariant defaultVal = shellObject->property(name); + + if (defaultVal != property(name)) + arguments.append(name + QString("=") + argument(i, expanded)); + } return arguments; } -QString Object::argument(int index) const +QString Object::argument(int index, bool expanded) const { if ((index < 0) || (index > metaObject()->propertyCount())) return ""; const QMetaProperty property = metaObject()->property(index); @@ -612,19 +647,19 @@ QString Object::argument(int index) const strings.append(QString::number(value)); } else if (type == "QList") { foreach (Transform *transform, variant.value< QList >()) - strings.append(transform->description()); + strings.append(transform->description(expanded)); } else if (type == "QList") { foreach (Distance *distance, variant.value< QList >()) - strings.append(distance->description()); + strings.append(distance->description(expanded)); } else { qFatal("Unrecognized type: %s", qPrintable(type)); } return "[" + strings.join(",") + "]"; } else if (type == "br::Transform*") { - return variant.value()->description(); + return variant.value()->description(expanded); } else if (type == "br::Distance*") { - return variant.value()->description(); + return variant.value()->description(expanded); } else if (type == "QStringList") { return "[" + variant.toStringList().join(",") + "]"; } @@ -632,9 +667,12 @@ QString Object::argument(int index) const return variant.toString(); } -QString Object::description() const +QString Object::description(bool expanded) const { - QString argumentString = arguments().join(","); + QString argumentString = prunedArguments(expanded).join(","); + if (argumentString.endsWith(",")) + argumentString.chop(1); + return objectName() + (argumentString.isEmpty() ? "" : ("(" + argumentString + ")")); } @@ -1292,7 +1330,7 @@ Transform *Transform::make(QString str, QObject *parent) Transform *Transform::clone() const { - Transform *clone = Factory::make(file.flat()); + Transform *clone = Factory::make("."+description(false)); return clone; } @@ -1461,3 +1499,17 @@ void br::applyAdditionalProperties(const File &temp, Transform *target) target->setPropertyRecursive(i.key(), i.value() ); } } + +Transform *br::wrapTransform(Transform *base, const QString &target) +{ + Transform *res = Transform::make(target, NULL); + res->setPropertyRecursive("transform", QVariant::fromValue(base)); + return res; +} + +Transform *br::pipeTransforms(QList &transforms) +{ + Transform *res = Transform::make("Pipe",NULL); + res->setPropertyRecursive("transforms", QVariant::fromValue(transforms)); + return res; +} diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 88ecdaf..a329910 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -593,10 +593,18 @@ public: virtual void store(QDataStream &stream) const; /*!< \brief Serialize the object. */ virtual void load(QDataStream &stream); /*!< \brief Deserialize the object. Default implementation calls init() after deserialization. */ + /*!< \brief Serialize an object created via the plugin system, including the string used to build the base object, allowing re-creation of the object without knowledge of its base string*/ + virtual void serialize(QDataStream &stream) const + { + stream << description(); + store(stream); + } + QStringList parameters() const; /*!< \brief A string describing the parameters the object takes. */ - QStringList arguments() const; /*!< \brief A string describing the values the object has. */ - QString argument(int index) const; /*!< \brief A string value for the argument at the specified index. */ - QString description() const; /*!< \brief Returns a string description of the object. */ + QStringList prunedArguments(bool expanded = false) const; /*!< \brief A string describing the values the object has, default valued parameters will not be listed. If expanded is true, all abbreviations and model file names should be replaced with a description of the object generated from those names. */ + QString argument(int index, bool expanded) const; /*!< \brief A string value for the argument at the specified index. */ + virtual QString description(bool expanded = false) const; /*!< \brief Returns a string description of the object. */ + void setProperty(const QString &name, QVariant value); /*!< \brief Overload of QObject::setProperty to handle OpenBR data types. */ virtual bool setPropertyRecursive(const QString &name, QVariant value); /*!< \brief Recursive version of setProperty, try to set the property on this object, or its children, returns true if successful. */ @@ -1274,6 +1282,21 @@ public: */ QList getChildren() const; + static Transform *deserialize(QDataStream &stream) + { + QString desc; + stream >> desc; + Transform *res = Transform::make(desc, NULL); + res->load(stream); + return res; + } + + /*! + * \brief Return a pointer to a simplified version of this transform (if possible). Transforms which are only active during training should remove + * themselves by either returning their child transforms (where relevant) or returning NULL. Set newTransform to true if the transform returned is newly allocated. + */ + virtual Transform * simplify(bool & newTransform) { newTransform = false; return this; } + protected: Transform(bool independent = true, bool trainable = true); /*!< \brief Construct a transform. */ inline Transform *make(const QString &description) { return make(description, this); } /*!< \brief Make a subtransform. */ diff --git a/openbr/plugins/distance.cpp b/openbr/plugins/distance.cpp index 34e91e8..7ad1dd7 100644 --- a/openbr/plugins/distance.cpp +++ b/openbr/plugins/distance.cpp @@ -251,6 +251,7 @@ private: default: qFatal("Invalid operation."); } + return 0; } void store(QDataStream &stream) const @@ -460,13 +461,12 @@ BR_REGISTER(Distance, SumDistance) class GalleryCompareTransform : public Transform { Q_OBJECT - Q_PROPERTY(QString distanceAlgorithm READ get_distanceAlgorithm WRITE set_distanceAlgorithm RESET reset_distanceAlgorithm STORED false) + Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED true) Q_PROPERTY(QString galleryName READ get_galleryName WRITE set_galleryName RESET reset_galleryName STORED false) - BR_PROPERTY(QString, distanceAlgorithm, "") + BR_PROPERTY(br::Distance*, distance, NULL) BR_PROPERTY(QString, galleryName, "") TemplateList gallery; - QSharedPointer distance; void project(const Template &src, Template &dst) const { @@ -480,16 +480,29 @@ class GalleryCompareTransform : public Transform void init() { - if (!galleryName.isEmpty()) { + if (!galleryName.isEmpty()) gallery = TemplateList::fromGallery(galleryName); - } - if (!distanceAlgorithm.isEmpty()) - { - distance = Distance::fromAlgorithm(distanceAlgorithm); - } } + + void train(const TemplateList &data) + { + gallery = data; + } + + void store(QDataStream &stream) const + { + br::Object::store(stream); + stream << gallery; + } + + void load(QDataStream &stream) + { + br::Object::load(stream); + stream >> gallery; + } + public: - GalleryCompareTransform() : Transform(false, false) {} + GalleryCompareTransform() : Transform(false, true) {} }; BR_REGISTER(Transform, GalleryCompareTransform) diff --git a/openbr/plugins/frames.cpp b/openbr/plugins/frames.cpp index fb7f507..100df34 100644 --- a/openbr/plugins/frames.cpp +++ b/openbr/plugins/frames.cpp @@ -37,7 +37,7 @@ private: dst.append(out); } - void finalize(TemplateList & output) + void finalize(TemplateList &output) { (void) output; buffer.clear(); diff --git a/openbr/plugins/independent.cpp b/openbr/plugins/independent.cpp index 2a627c1..be61582 100644 --- a/openbr/plugins/independent.cpp +++ b/openbr/plugins/independent.cpp @@ -94,6 +94,12 @@ class DownsampleTrainingTransform : public Transform BR_PROPERTY(QStringList, subjects, QStringList()) + Transform *simplify(bool &newTForm) + { + Transform *res = transform->simplify(newTForm); + return res; + } + void project(const Template &src, Template &dst) const { transform->project(src,dst); @@ -126,6 +132,10 @@ class IndependentTransform : public MetaTransform QList transforms; + QString description(bool expanded) const + { + return transform->description(expanded); + } bool setPropertyRecursive(const QString &name, QVariant value) { @@ -141,6 +151,54 @@ class IndependentTransform : public MetaTransform return true; } + Transform *simplify(bool &newTransform) + { + newTransform = false; + bool newChild = false; + Transform *temp = transform->simplify(newChild); + if (temp == transform) { + return this; + } + IndependentTransform* indep = new IndependentTransform(); + indep->transform = temp; + + bool subInd = false; + IndependentTransform *test = dynamic_cast (temp); + if (test) { + // child was independent? this changes things... + subInd = true; + indep->transform = test->transform; + for (int i=0; i < transforms.size(); i++) { + bool newThing = false; + IndependentTransform *probe = dynamic_cast (transforms[i]->simplify(newThing)); + indep->transforms.append(probe->transform); + if (newThing) + probe->setParent(indep); + } + indep->file = indep->transform->file; + indep->trainable = indep->transform->trainable; + indep->setObjectName(indep->transform->objectName()); + + return indep; + } + + if (newChild) + indep->transform->setParent(indep); + + for (int i=0; i < transforms.size();i++) { + bool subTform = false; + indep->transforms.append(transforms[i]->simplify(subTform)); + if (subTform) + indep->transforms[i]->setParent(indep); + } + + indep->file = indep->transform->file; + indep->trainable = indep->transform->trainable; + indep->setObjectName(indep->transform->objectName()); + + return indep; + } + void init() { transforms.clear(); diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index dc2d6e9..c3777e3 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -482,17 +482,32 @@ BR_REGISTER(Transform, CacheTransform) class LoadStoreTransform : public MetaTransform { Q_OBJECT - Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) + Q_PROPERTY(QString transformString READ get_transformString WRITE set_transformString RESET reset_transformString STORED false) Q_PROPERTY(QString fileName READ get_fileName WRITE set_fileName RESET reset_fileName STORED false) - BR_PROPERTY(QString, description, "Identity") + BR_PROPERTY(QString, transformString, "Identity") BR_PROPERTY(QString, fileName, QString()) +public: Transform *transform; QString baseName; -public: LoadStoreTransform() : transform(NULL) {} + QString description(bool expanded = false) const + { + if (expanded) { + QString res = transform->description(expanded); + return res; + } + return br::Object::description(expanded); + } + + Transform *simplify(bool &newTForm) + { + Transform *res = transform->simplify(newTForm); + return res; + } + bool setPropertyRecursive(const QString &name, QVariant value) { if (br::Object::setPropertyRecursive(name, value)) @@ -500,13 +515,16 @@ public: return transform->setPropertyRecursive(name, value); } private: + void init() { if (transform != NULL) return; - if (fileName.isEmpty()) baseName = QRegExp("^[a-zA-Z0-9]+$").exactMatch(description) ? description : QtUtils::shortTextHash(description); + if (fileName.isEmpty()) baseName = QRegExp("^[a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString); else baseName = fileName; - if (!tryLoad()) transform = make(description); - else trainable = false; + if (!tryLoad()) + transform = make(transformString); + else + trainable = false; } bool timeVarying() const @@ -524,7 +542,7 @@ private: qDebug("Storing %s", qPrintable(baseName)); QByteArray byteArray; QDataStream stream(&byteArray, QFile::WriteOnly); - stream << description; + stream << transform->description(); transform->store(stream); QtUtils::writeFile(baseName, byteArray, -1); } @@ -570,8 +588,8 @@ private: QByteArray data; QtUtils::readFile(file, data, true); QDataStream stream(&data, QFile::ReadOnly); - stream >> description; - transform = Transform::make(description); + stream >> transformString; + transform = Transform::make(transformString); transform->load(stream); return true; } diff --git a/openbr/plugins/misc.cpp b/openbr/plugins/misc.cpp index 35db597..dc7d76a 100644 --- a/openbr/plugins/misc.cpp +++ b/openbr/plugins/misc.cpp @@ -617,8 +617,8 @@ class ProgressCounterTransform : public TimeVaryingTransform { Q_OBJECT - Q_PROPERTY(int totalTemplates READ get_totalTemplates WRITE set_totalTemplates RESET reset_totalTemplates STORED false) - BR_PROPERTY(int, totalTemplates, 1) + Q_PROPERTY(int totalProgress READ get_totalProgress WRITE set_totalProgress RESET reset_totalProgress STORED false) + BR_PROPERTY(int, totalProgress, 1) void projectUpdate(const TemplateList &src, TemplateList &dst) { @@ -658,16 +658,20 @@ class ProgressCounterTransform : public TimeVaryingTransform (void) data; float p = br_progress(); qDebug("\r%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep); + timer.start(); + Globals->startTime.start(); Globals->currentStep = 0; Globals->currentProgress = 0; - Globals->totalSteps = 0; + Globals->totalSteps = totalProgress; } void init() { timer.start(); + Globals->startTime.start(); Globals->currentStep = 0; Globals->currentProgress = 0; + Globals->totalSteps = totalProgress; } public: diff --git a/openbr/plugins/openbr_internal.h b/openbr/plugins/openbr_internal.h index 2283760..91ad9cb 100644 --- a/openbr/plugins/openbr_internal.h +++ b/openbr/plugins/openbr_internal.h @@ -201,6 +201,33 @@ public: this->trainable = transform->trainable; } + virtual Transform *simplify(bool &newTransform) + { + newTransform = false; + bool newChild = false; + Transform *temp = transform->simplify(newTransform); + if (temp == transform) + return this; + + if (!temp) + return NULL; + + // else make a copy to point at the new transform + Transform *child = transform; + transform = NULL; + WrapperTransform *output = dynamic_cast(Transform::make(description(), NULL)); + transform = child; + + output->transform = temp; + + if (newChild) + temp->setParent(output); + + newTransform = true; + return output; + } + + bool setPropertyRecursive(const QString &name, QVariant value) { if (br::Object::setPropertyRecursive(name, value)) @@ -212,6 +239,34 @@ public: } return false; } + + Transform *smartCopy(bool &newTransform) + { + if (!timeVarying()) { + newTransform = false; + return this; + } + newTransform = true; + Transform *temp = transform; + transform = NULL; + WrapperTransform *output = dynamic_cast(Transform::make(description(), NULL)); + transform = temp; + + if (output == NULL) + qFatal("Dynamic cast failed!"); + + bool newItem = false; + Transform *maybe_copy = transform->smartCopy(newItem); + if (newItem) + maybe_copy->setParent(output); + output->transform = maybe_copy; + + output->file = this->file; + output->init(); + + return output; + } + }; /*! @@ -270,21 +325,10 @@ public: } newTransform = true; - QString name = metaObject()->className(); - - name.replace("Transform",""); - name += "([]"; - - QStringList arguments = this->arguments(); - if (!arguments.isEmpty()) { - name += ","; - name += this->arguments().join(","); - } - - name += ")"; - name.replace("br::",""); - - CompositeTransform *output = dynamic_cast(Transform::make(name, NULL)); + QList temp = transforms; + transforms = QList(); + CompositeTransform *output = dynamic_cast(Transform::make(description(), NULL)); + transforms = temp; if (output == NULL) qFatal("Dynamic cast failed!"); @@ -303,6 +347,51 @@ public: return output; } + virtual Transform *simplify(bool &newTransform) + { + newTransform = false; + QList newTransforms; + bool anyNew = false; + + QList newChildren; + for (int i=0; i < transforms.size();i++) + { + bool newChild = false; + Transform *temp = transforms[i]->simplify(newChild); + if (temp == NULL) { + anyNew = true; + continue; + } + newTransforms.append(temp); + newChildren.append(newChild); + if (temp != transforms[i]) + anyNew = true; + } + + if (newTransforms.empty() ) + return NULL; + + if (!anyNew) + return this; + + // make a copy of the current object, with empty transforms + QList children = transforms; + transforms = QList (); + CompositeTransform *output = dynamic_cast(Transform::make(description(false), NULL)); + transforms = children; + + output->transforms = newTransforms; + for (int i=0;i < newChildren.size();i++) + { + if (newChildren[i]) + output->transforms[i]->setParent(output); + } + output->init(); + + newTransform = true; + return output; + } + bool setPropertyRecursive(const QString &name, QVariant value) { if (br::Object::setPropertyRecursive(name, value)) @@ -388,6 +477,10 @@ public: void applyAdditionalProperties(const File &temp, Transform *target); +Transform *wrapTransform(Transform *base, const QString &target); + +Transform *pipeTransforms(QList &transforms); + } #endif // OPENBR_INTERNAL_H diff --git a/openbr/plugins/pp5.cpp b/openbr/plugins/pp5.cpp index e605839..8c6fcba 100644 --- a/openbr/plugins/pp5.cpp +++ b/openbr/plugins/pp5.cpp @@ -450,8 +450,9 @@ class PP5GalleryTransform: public UntrainableMetaTransform ppr_gallery_type target; QList targetIDs; + TemplateList gallery; - void project(const Template & src, Template & dst) const + void project(const Template &src, Template &dst) const { TemplateList temp, output; temp.append(src); @@ -460,7 +461,7 @@ class PP5GalleryTransform: public UntrainableMetaTransform dst = output[0]; } - void project(const TemplateList & src, TemplateList & dst) const + void project(const TemplateList &src, TemplateList &dst) const { dst.clear(); QList queryIDs; @@ -495,11 +496,33 @@ class PP5GalleryTransform: public UntrainableMetaTransform void init() { - // set up the gallery - ppr_create_gallery(context, &target); - TemplateList templates = TemplateList::fromGallery(galleryName); - enroll(templates,&target, targetIDs); + if (!galleryName.isEmpty() || !gallery.isEmpty()) { + // set up the gallery + ppr_create_gallery(context, &target); + if (gallery.isEmpty() ) + gallery = TemplateList::fromGallery(galleryName); + enroll(gallery, &target, targetIDs); + } + } + + void train(const TemplateList &data) + { + gallery = data; + } + + void store(QDataStream &stream) const + { + br::Object::store(stream); + stream << gallery; + } + + void load(QDataStream &stream) + { + br::Object::load(stream); + stream >> gallery; + init(); } + }; BR_REGISTER(Transform, PP5GalleryTransform) diff --git a/openbr/plugins/process.cpp b/openbr/plugins/process.cpp index b8c519c..4169c05 100644 --- a/openbr/plugins/process.cpp +++ b/openbr/plugins/process.cpp @@ -21,7 +21,7 @@ class CommunicationManager : public QObject { Q_OBJECT public: - QThread * basis; + QThread *basis; CommunicationManager() { basis = new QThread; @@ -294,7 +294,7 @@ public: QString serverName; QString remoteName; - QLocalSocket * inbound; + QLocalSocket *inbound; QLocalSocket outbound; QLocalServer server; @@ -305,13 +305,11 @@ public: while (!inbound || inbound->state() != QLocalSocket::ConnectedState) { bool res = receivedWait.wait(&receivedLock,30*1000); if (!res) - { qDebug() << key << " " << QThread::currentThread() << " waiting timed out, server thread is " << server.thread() << " base thread " << basis; - } } } - void connectToRemote(const QString & remoteName) + void connectToRemote(const QString &remoteName) { emit pulseOutboundConnect(remoteName); @@ -330,7 +328,7 @@ public: template - bool readData(T & input) + bool readData(T &input) { emit pulseReadSerialized(); QDataStream deserializer(readArray); @@ -338,8 +336,18 @@ public: return true; } + Transform *readTForm() + { + emit pulseReadSerialized(); + + QByteArray data = readArray; + QDataStream deserializer(data); + Transform *res = Transform::deserialize(deserializer); + return res; + } + template - bool sendData(const T & output) + bool sendData(const T &output) { QBuffer buffer; buffer.open(QBuffer::ReadWrite); @@ -378,7 +386,7 @@ class EnrollmentWorker : public QObject { Q_OBJECT public: - CommunicationManager * comm; + CommunicationManager *comm; QString name; ~EnrollmentWorker() @@ -389,10 +397,10 @@ public: delete comm; } - br::Transform * transform; + br::Transform *transform; public: - void connections(const QString & baseName) + void connections(const QString &baseName) { comm = new CommunicationManager(); name = baseName; @@ -401,6 +409,8 @@ public: comm->connectToRemote(baseName+"_master"); comm->waitForInbound(); + + transform = comm->readTForm(); } void workerLoop() @@ -428,7 +438,7 @@ public: void WorkerProcess::mainLoop() { processInterface = new EnrollmentWorker(); - processInterface->transform = Transform::make(this->transform,NULL); + processInterface->transform = NULL; processInterface->connections(baseName); processInterface->workerLoop(); delete processInterface; @@ -478,63 +488,99 @@ protected slots: } }; +struct ProcessData +{ + CommunicationManager comm; + ProcessInterface proc; + bool initialized; + ProcessData() + { + initialized = false; + } + + ~ProcessData() + { + comm.sendSignal(CommunicationManager::SHOULD_END); + proc.endProcess(); + comm.shutdown(); + comm.shutDownThread(); + } +}; + + /*! * \ingroup transforms * \brief Interface to a separate process * \author Charles Otto \cite caotto */ -class ProcessWrapperTransform : public TimeVaryingTransform +class ProcessWrapperTransform : public WrapperTransform { Q_OBJECT - Q_PROPERTY(QString transform READ get_transform WRITE set_transform RESET reset_transform) - BR_PROPERTY(QString, transform, "") - QString baseKey; - ProcessInterface workerProcess; - CommunicationManager * comm; + Resource processes; - void projectUpdate(const TemplateList &src, TemplateList &dst) + Transform *smartCopy(bool &newTransform) + { + newTransform = false; + return this; + } + + void project(const TemplateList &src, TemplateList &dst) const { if (src.empty()) return; + + ProcessData *data = processes.acquire(); + if (!data->initialized) + activateProcess(data); - if (!processActive) - { - activateProcess(); - } - comm->sendSignal(CommunicationManager::INPUT_AVAILABLE); - - comm->sendData(src); + CommunicationManager *localComm = &(data->comm); + localComm->sendSignal(CommunicationManager::INPUT_AVAILABLE); - comm->readData(dst); + localComm->sendData(src); + localComm->readData(dst); + processes.release(data); } - void train(const TemplateList& data) { (void) data; } - // create the process void init() { processActive = false; + serialized.clear(); + if (transform) { + QDataStream out(&serialized, QFile::WriteOnly); + transform->serialize(out); + } } - void activateProcess() + QByteArray serialized; + void transmitTForm(CommunicationManager *localComm) const { - comm = new CommunicationManager(); - processActive = true; + if (serialized.isEmpty() ) + qFatal("Trying to transmit empty transform!"); + + localComm->writeArray = serialized; + emit localComm->pulseSendSerialized(); + } + void activateProcess(ProcessData *data) const + { + data->initialized = true; // generate a uuid for our local servers QUuid id = QUuid::createUuid(); - baseKey = id.toString(); + QString baseKey = id.toString(); QStringList argumentList; + // We serialize and transmit the transform directly, so algorithm doesn't matter. + argumentList.append("-quiet"); argumentList.append("-algorithm"); - argumentList.append(transform); + argumentList.append("Identity"); if (!Globals->path.isEmpty()) { argumentList.append("-path"); argumentList.append(Globals->path); @@ -544,36 +590,25 @@ class ProcessWrapperTransform : public TimeVaryingTransform argumentList.append("-slave"); argumentList.append(baseKey); - comm->key = "master_"+baseKey.mid(1,5); + data->comm.key = "master_"+baseKey.mid(1,5); - comm->startServer(baseKey+"_master"); - workerProcess.startProcess(argumentList); - comm->waitForInbound(); - comm->connectToRemote(baseKey+"_worker"); - } + data->comm.startServer(baseKey+"_master"); - bool timeVarying() const { - return false; + data->proc.startProcess(argumentList); + data->comm.waitForInbound(); + data->comm.connectToRemote(baseKey+"_worker"); + + transmitTForm(&(data->comm)); } - ~ProcessWrapperTransform() + bool timeVarying() const { - // end the process - if (this->processActive) { - comm->sendSignal(CommunicationManager::SHOULD_END); - - workerProcess.endProcess(); - processActive = false; - comm->shutdown(); - comm->shutDownThread(); - - delete comm; - } + return false; } public: bool processActive; - ProcessWrapperTransform() : TimeVaryingTransform(false,false) { processActive = false; } + ProcessWrapperTransform() : WrapperTransform(false) { processActive = false; } }; BR_REGISTER(Transform, ProcessWrapperTransform) diff --git a/openbr/plugins/validate.cpp b/openbr/plugins/validate.cpp index 75bca83..1310d12 100644 --- a/openbr/plugins/validate.cpp +++ b/openbr/plugins/validate.cpp @@ -7,7 +7,7 @@ namespace br { -static void _train(Transform * transform, TemplateList data) // think data has to be a copy -cao +static void _train(Transform *transform, TemplateList data) // think data has to be a copy -cao { transform->train(data); } diff --git a/share/openbr/models b/share/openbr/models index bcbff8c..79938fe 160000 --- a/share/openbr/models +++ b/share/openbr/models @@ -1 +1 @@ -Subproject commit bcbff8c485f19daddb2e6b2abd5a505ed8c1e526 +Subproject commit 79938fe401faafead086b4711dd0b8f898a7a21e