From 646c94908b2ebd35f5ac03938c1fef2251de36b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de> Date: Mon, 13 Jan 2020 22:27:47 +0100 Subject: [PATCH] * Added plenty of properties to the Random Forest ML block --- Grinder/Grinder.pro | 17 +- Grinder/Version.h | 4 +- Grinder/common/properties/PropertyID.cpp | 9 + Grinder/common/properties/PropertyID.h | 9 + .../RandomForestClassifierConfiguration.cpp | 63 +- .../rf/RandomForestClassifierConfiguration.h | 47 ++ Grinder/ml/rf/RandomForestFeatures.cpp | 88 +++ Grinder/ml/rf/RandomForestFeatures.h | 221 ++++++ .../rf/blocks/RandomForestClassifierBlock.cpp | 69 ++ .../rf/blocks/RandomForestClassifierBlock.h | 46 ++ .../rf/properties/MaxFeaturesTypeProperty.cpp | 17 + .../rf/properties/MaxFeaturesTypeProperty.h | 26 + .../RandomForestFeaturesProperty.cpp | 30 + .../properties/RandomForestFeaturesProperty.h | 29 + Grinder/res/Grinder.qrc | 18 + Grinder/res/gabor/filter0.png | Bin 0 -> 1522 bytes Grinder/res/gabor/filter1.png | Bin 0 -> 862 bytes Grinder/res/gabor/filter10.png | Bin 0 -> 1728 bytes Grinder/res/gabor/filter11.png | Bin 0 -> 1179 bytes Grinder/res/gabor/filter12.png | Bin 0 -> 1313 bytes Grinder/res/gabor/filter13.png | Bin 0 -> 917 bytes Grinder/res/gabor/filter14.png | Bin 0 -> 1572 bytes Grinder/res/gabor/filter15.png | Bin 0 -> 1595 bytes Grinder/res/gabor/filter2.png | Bin 0 -> 1779 bytes Grinder/res/gabor/filter3.png | Bin 0 -> 1497 bytes Grinder/res/gabor/filter4.png | Bin 0 -> 1310 bytes Grinder/res/gabor/filter5.png | Bin 0 -> 921 bytes Grinder/res/gabor/filter6.png | Bin 0 -> 1565 bytes Grinder/res/gabor/filter7.png | Bin 0 -> 1596 bytes Grinder/res/gabor/filter8.png | Bin 0 -> 1165 bytes Grinder/res/gabor/filter9.png | Bin 0 -> 719 bytes .../rf/RandomForestFeaturesPropertyEditor.cpp | 22 + .../rf/RandomForestFeaturesPropertyEditor.h | 26 + .../ui/ml/rf/RandomForestFeaturesDialog.cpp | 216 ++++++ Grinder/ui/ml/rf/RandomForestFeaturesDialog.h | 61 ++ .../ui/ml/rf/RandomForestFeaturesDialog.ui | 734 ++++++++++++++++++ 36 files changed, 1746 insertions(+), 6 deletions(-) create mode 100644 Grinder/ml/rf/RandomForestFeatures.cpp create mode 100644 Grinder/ml/rf/RandomForestFeatures.h create mode 100644 Grinder/ml/rf/properties/MaxFeaturesTypeProperty.cpp create mode 100644 Grinder/ml/rf/properties/MaxFeaturesTypeProperty.h create mode 100644 Grinder/ml/rf/properties/RandomForestFeaturesProperty.cpp create mode 100644 Grinder/ml/rf/properties/RandomForestFeaturesProperty.h create mode 100644 Grinder/res/gabor/filter0.png create mode 100644 Grinder/res/gabor/filter1.png create mode 100644 Grinder/res/gabor/filter10.png create mode 100644 Grinder/res/gabor/filter11.png create mode 100644 Grinder/res/gabor/filter12.png create mode 100644 Grinder/res/gabor/filter13.png create mode 100644 Grinder/res/gabor/filter14.png create mode 100644 Grinder/res/gabor/filter15.png create mode 100644 Grinder/res/gabor/filter2.png create mode 100644 Grinder/res/gabor/filter3.png create mode 100644 Grinder/res/gabor/filter4.png create mode 100644 Grinder/res/gabor/filter5.png create mode 100644 Grinder/res/gabor/filter6.png create mode 100644 Grinder/res/gabor/filter7.png create mode 100644 Grinder/res/gabor/filter8.png create mode 100644 Grinder/res/gabor/filter9.png create mode 100644 Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.cpp create mode 100644 Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h create mode 100644 Grinder/ui/ml/rf/RandomForestFeaturesDialog.cpp create mode 100644 Grinder/ui/ml/rf/RandomForestFeaturesDialog.h create mode 100644 Grinder/ui/ml/rf/RandomForestFeaturesDialog.ui diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index dc51a21..0c119b4 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -488,7 +488,12 @@ SOURCES += \ ui/image/commands/CreateLayerUndoCommand.cpp \ ui/image/commands/RemoveLayerUndoCommand.cpp \ pipeline/blocks/MaskInputBlock.cpp \ - engine/processors/MaskInputProcessor.cpp + engine/processors/MaskInputProcessor.cpp \ + ml/rf/properties/MaxFeaturesTypeProperty.cpp \ + ml/rf/RandomForestFeatures.cpp \ + ml/rf/properties/RandomForestFeaturesProperty.cpp \ + ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.cpp \ + ui/ml/rf/RandomForestFeaturesDialog.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -1070,7 +1075,12 @@ HEADERS += \ ui/image/commands/RemoveLayerUndoCommand.h \ util/MathUtils.impl.h \ pipeline/blocks/MaskInputBlock.h \ - engine/processors/MaskInputProcessor.h + engine/processors/MaskInputProcessor.h \ + ml/rf/properties/MaxFeaturesTypeProperty.h \ + ml/rf/RandomForestFeatures.h \ + ml/rf/properties/RandomForestFeaturesProperty.h \ + ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h \ + ui/ml/rf/RandomForestFeaturesDialog.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ @@ -1090,7 +1100,8 @@ FORMS += \ ui/cmd/tasks/CommandInterfaceTaskWidget.ui \ ui/cmd/tasks/CommandInterfaceTestTaskWidget.ui \ ui/dlg/BrowseDialog.ui \ - ui/image/LayerSettingsDialog.ui + ui/image/LayerSettingsDialog.ui \ + ui/ml/rf/RandomForestFeaturesDialog.ui RESOURCES += \ res/Grinder.qrc diff --git a/Grinder/Version.h b/Grinder/Version.h index 777de0e..d65d4a8 100644 --- a/Grinder/Version.h +++ b/Grinder/Version.h @@ -10,14 +10,14 @@ #define GRNDR_INFO_TITLE "Grinder" #define GRNDR_INFO_COPYRIGHT "Copyright (c) WWU Muenster" -#define GRNDR_INFO_DATE "30.12.2019" +#define GRNDR_INFO_DATE "13.01.2020" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 16 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 419 +#define GRNDR_VERSION_BUILD 422 namespace grndr { diff --git a/Grinder/common/properties/PropertyID.cpp b/Grinder/common/properties/PropertyID.cpp index edcc885..55dd100 100644 --- a/Grinder/common/properties/PropertyID.cpp +++ b/Grinder/common/properties/PropertyID.cpp @@ -76,3 +76,12 @@ const char* PropertyID::SnapshotInterval = "SnapshotInterval"; const char* PropertyID::Renderable = "Renderable"; const char* PropertyID::State = "State"; const char* PropertyID::Filename = "Filename"; +const char* PropertyID::Estimators = "Estimators"; +const char* PropertyID::MaxFeaturesType = "MaxFeaturesType"; +const char* PropertyID::MaxFeatures = "MaxFeatures"; +const char* PropertyID::MaxDepth = "MaxDepth"; +const char* PropertyID::MaxLeafNodes = "MaxLeafNodes"; +const char* PropertyID::MinSamples = "MinSamples"; +const char* PropertyID::Count = "Count"; +const char* PropertyID::Verbose = "Verbose"; +const char* PropertyID::Features = "Features"; diff --git a/Grinder/common/properties/PropertyID.h b/Grinder/common/properties/PropertyID.h index 9ca6d4c..173bd94 100644 --- a/Grinder/common/properties/PropertyID.h +++ b/Grinder/common/properties/PropertyID.h @@ -83,6 +83,15 @@ namespace grndr static const char* Renderable; static const char* State; static const char* Filename; + static const char* Estimators; + static const char* MaxFeaturesType; + static const char* MaxFeatures; + static const char* MaxDepth; + static const char* MaxLeafNodes; + static const char* MinSamples; + static const char* Count; + static const char* Verbose; + static const char* Features; public: using QString::QString; diff --git a/Grinder/ml/rf/RandomForestClassifierConfiguration.cpp b/Grinder/ml/rf/RandomForestClassifierConfiguration.cpp index 2cda51d..2f96e5d 100644 --- a/Grinder/ml/rf/RandomForestClassifierConfiguration.cpp +++ b/Grinder/ml/rf/RandomForestClassifierConfiguration.cpp @@ -36,9 +36,70 @@ void RandomForestClassifierConfiguration::verifyConfiguration() const throw MachineLearningException{_EXCPT("No or an invalid Python interpreter was specified; please configure a valid interpreter in the Grinder options")}; ExternalClassifierConfiguration::verifyConfiguration(); + + if (_features.getActiveFeatures().empty()) + throw MachineLearningException{_EXCPT("At least one feature must be selected")}; + + if (_estimators == 0) + throw MachineLearningException{_EXCPT("The random forest must consist of at least one tree")}; + + if (_maxFeaturesType == MaxFeaturesType::Value) + { + if (_maxFeatures <= 0.0f) + throw MachineLearningException{_EXCPT("The maximum number of features must be greater than 0.0")}; + } + + if (_minSamples <= 0.0f) + throw MachineLearningException{_EXCPT("The minimum number of samples must be greater than 0.0")}; } QString RandomForestClassifierConfiguration::getUserData() const { - return ""; + QStringList userData; + + // Features + + userData << _features.getFeaturesUserData(); + + // Settings + + userData << QString{"Estimators:%1"}.arg(_estimators); + + switch (_maxFeaturesType) + { + case MaxFeaturesType::Value: + userData << QString{"Max Features:%1"}.arg(_maxFeatures); + break; + + case MaxFeaturesType::Sqrt: + userData << QString{"Max Features:sqrt"}; + break; + + case MaxFeaturesType::Log2: + userData << QString{"Max Features:log2"}; + break; + + case MaxFeaturesType::All: + userData << QString{"Max Features:None"}; + break; + } + + if (_maxDepth > 0) + userData << QString{"Max Depth:%1"}.arg(_maxDepth); + + if (_maxLeafNodes > 0) + userData << QString{"Max Leaf Nodes:%1"}.arg(_maxLeafNodes); + + userData << QString{"Min Samples Split:%1"}.arg(_minSamples); + + // Performance + int jobCount = _jobCount != 0 ? static_cast<int>(_jobCount) : -1; + userData << QString{"Jobs:%1"}.arg(jobCount); + + // Miscellaneous + + if (_verbose) + userData << QString{"Verbose"}; + + return userData.join("\n"); } diff --git a/Grinder/ml/rf/RandomForestClassifierConfiguration.h b/Grinder/ml/rf/RandomForestClassifierConfiguration.h index 893f1d3..9a88c5a 100644 --- a/Grinder/ml/rf/RandomForestClassifierConfiguration.h +++ b/Grinder/ml/rf/RandomForestClassifierConfiguration.h @@ -7,6 +7,7 @@ #define RANDOMFORESTCLASSIFIERCONFIGURATION_H #include "ml/external/ExternalClassifierConfiguration.h" +#include "ml/rf/RandomForestFeatures.h" namespace grndr { @@ -14,6 +15,15 @@ namespace grndr { Q_OBJECT + public: + enum class MaxFeaturesType + { + Value, + Sqrt, + Log2, + All, + }; + public: RandomForestClassifierConfiguration(); @@ -26,6 +36,43 @@ namespace grndr public: virtual QString getUserData() const override; + + public: + RandomForestFeatures getFeatures() { return _features; } + void setFeatures(const RandomForestFeatures& features) { setValue(_features, features); } + + unsigned int getEstimators() const { return _estimators; } + void setEstimators(unsigned int estimators) { setValue(_estimators, estimators); } + MaxFeaturesType getMaxFeaturesType() const { return _maxFeaturesType; } + void setMaxFeaturesType(MaxFeaturesType featuresType) { setValue(_maxFeaturesType, featuresType); } + float getMaxFeatures() const { return _maxFeatures; } + void setMaxFeatures(float maxFeatures) { if (maxFeatures > 1.0f) maxFeatures = static_cast<float>(static_cast<int>(maxFeatures)); setValue(_maxFeatures, maxFeatures); } + unsigned int getMaxDepth() const { return _maxDepth; } + void setMaxDepth(unsigned int maxDepth) { setValue(_maxDepth, maxDepth); } + unsigned int getMaxLeafNodes() const { return _maxLeafNodes; } + void setMaxLeafNodes(unsigned int leafNodes) { setValue(_maxLeafNodes, leafNodes); } + float getMinSamples() const { return _minSamples; } + void setMinSamples(float minSamples) { if (minSamples > 1.0f) minSamples = static_cast<float>(static_cast<int>(minSamples)); setValue(_minSamples, minSamples); } + + unsigned int getJobCount() const { return _jobCount; } + void setJobCount(unsigned int jobCount) { setValue(_jobCount, jobCount); } + + bool isVerbose() const { return _verbose; } + void setVerbose(bool verbose) { setValue(_verbose, verbose); } + + private: + RandomForestFeatures _features; + + unsigned int _estimators{10}; + MaxFeaturesType _maxFeaturesType{MaxFeaturesType::Sqrt}; + float _maxFeatures{1.0f}; + unsigned int _maxDepth{0}; + unsigned int _maxLeafNodes{0}; + float _minSamples{0.05f}; + + unsigned int _jobCount{0}; + + bool _verbose{false}; }; } diff --git a/Grinder/ml/rf/RandomForestFeatures.cpp b/Grinder/ml/rf/RandomForestFeatures.cpp new file mode 100644 index 0000000..1d4be36 --- /dev/null +++ b/Grinder/ml/rf/RandomForestFeatures.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * File: RandomForestFeatures.cpp + * Date: 13.1.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "RandomForestFeatures.h" +#include "util/StringConv.h" + +const char* RandomForestFeatures::Serialization_Value_GaborFilters = "GaborFilters"; +const char* RandomForestFeatures::Serialization_Value_Sobel = "Sobel"; +const char* RandomForestFeatures::Serialization_Value_Gaussian = "Gaussian"; +const char* RandomForestFeatures::Serialization_Value_Color = "Color"; +const char* RandomForestFeatures::Serialization_Value_DifferenceOfGaussians = "DoG"; +const char* RandomForestFeatures::Serialization_Value_LaplacianOfGaussians = "LoG"; +const char* RandomForestFeatures::Serialization_Value_Hessian = "Hessian"; +const char* RandomForestFeatures::Serialization_Value_StructureTensor = "StructureTensor"; + +std::vector<const RandomForestFeatures::Feature*> RandomForestFeatures::getActiveFeatures() const +{ + std::vector<const Feature*> features; + + for (const auto& feat : allFeatures()) + { + if (feat->isActive) + features.push_back(feat); + } + + return features; +} + +QStringList RandomForestFeatures::getFeaturesUserData() const +{ + QStringList features; + + for (const auto& feat : allFeatures()) + { + if (feat->isActive) + { + QString value = feat->getFeatureValue(); + + if (!value.isEmpty()) + features << QString{"%1:%2"}.arg(feat->getUserDataName()).arg(value); + else + features << QString{"%1"}.arg(feat->getUserDataName()); + } + } + + return features; +} + +bool RandomForestFeatures::operator ==(const RandomForestFeatures& other) const +{ + auto features = allFeatures(); + auto otherFeatures = other.allFeatures(); + + for (unsigned int i = 0; i < features.size(); ++i) + { + if (!features[i]->compare(*otherFeatures[i])) + return false; + } + + return true; +} + +void RandomForestFeatures::serialize(SerializationContext& ctx) const +{ + for (const auto& feat : allFeatures()) + ctx.settings()(feat->serializationValueName) = QString{"%1|%2"}.arg(feat->isActive ? "on" : "off").arg(feat->getFeatureValue(true)); +} + +void RandomForestFeatures::deserialize(DeserializationContext& ctx) +{ + for (auto& feat : allFeatures()) + { + QString value = ctx.settings()(feat->serializationValueName).toString(); + QStringList tokens = value.split("|"); + + if (tokens.size() >= 1) + { + QString active = tokens.front(); + tokens.pop_front(); + + feat->isActive = (active.compare("on", Qt::CaseInsensitive) == 0); + feat->setFeatureValue(tokens); + } + } +} diff --git a/Grinder/ml/rf/RandomForestFeatures.h b/Grinder/ml/rf/RandomForestFeatures.h new file mode 100644 index 0000000..ef76b0b --- /dev/null +++ b/Grinder/ml/rf/RandomForestFeatures.h @@ -0,0 +1,221 @@ +/****************************************************************************** + * File: RandomForestFeatures.h + * Date: 13.1.2020 + *****************************************************************************/ + +#ifndef RANDOMFORESTFEATURES_H +#define RANDOMFORESTFEATURES_H + +#include "common/serialization/SerializationContext.h" +#include "common/serialization/DeserializationContext.h" +#include "util/StringConv.h" + +namespace grndr +{ + class RandomForestFeatures + { + public: + static const char* Serialization_Value_GaborFilters; + static const char* Serialization_Value_Sobel; + static const char* Serialization_Value_Gaussian; + static const char* Serialization_Value_Color; + static const char* Serialization_Value_DifferenceOfGaussians; + static const char* Serialization_Value_LaplacianOfGaussians; + static const char* Serialization_Value_Hessian; + static const char* Serialization_Value_StructureTensor; + + public: + enum class MatrixFeatureType + { + Raw, + Eigen, + Both, + }; + + public: + struct Feature + { + QString name{""}; + QString serializationValueName{""}; + + bool isActive{false}; + + Feature(QString n, QString v) : name{n}, serializationValueName{v} { } + + virtual bool compare(const Feature& other) const { + return name == other.name && serializationValueName == other.serializationValueName && isActive == other.isActive; + } + + virtual QString getUserDataName() const { return name; } + virtual QString getFeatureValue(bool serialization = false) const { Q_UNUSED(serialization); return ""; } + virtual void setFeatureValue(QStringList valueTokens) { Q_UNUSED(valueTokens); } + }; + + struct GaussFeature : Feature + { + using Feature::Feature; + + float stdDeviation{0.5f}; + + virtual bool compare(const Feature& other) const override { + const GaussFeature& o = dynamic_cast<const GaussFeature&>(other); return stdDeviation == o.stdDeviation && Feature::compare(other); + } + + virtual QString getFeatureValue(bool serialization = false) const override { Q_UNUSED(serialization); return QString{"%1"}.arg(stdDeviation); } + virtual void setFeatureValue(QStringList valueTokens) override { + if (valueTokens.size() >= 1) + stdDeviation = StringConv::convertString<float>(valueTokens[0]); + } + }; + + struct DualGaussFeature : Feature + { + using Feature::Feature; + + float stdDeviation1{0.5f}; + float stdDeviation2{0.33f}; + + virtual bool compare(const Feature& other) const override { + const DualGaussFeature& o = dynamic_cast<const DualGaussFeature&>(other); return stdDeviation1 == o.stdDeviation1 && stdDeviation2 == o.stdDeviation2 && Feature::compare(other); + } + + virtual QString getFeatureValue(bool serialization = false) const override { Q_UNUSED(serialization); return QString{"%1|%2"}.arg(stdDeviation1).arg(stdDeviation2); } + virtual void setFeatureValue(QStringList valueTokens) override { + if (valueTokens.size() >= 2) + { + stdDeviation1 = StringConv::convertString<float>(valueTokens[0]); + stdDeviation2 = StringConv::convertString<float>(valueTokens[1]); + } + } + }; + + struct MatrixFeature : Feature + { + using Feature::Feature; + + MatrixFeatureType matrixType{MatrixFeatureType::Both}; + float stdDeviation{0.5f}; + + virtual bool compare(const Feature& other) const override { + const MatrixFeature& o = dynamic_cast<const MatrixFeature&>(other); return matrixType == o.matrixType && stdDeviation == o.stdDeviation && Feature::compare(other); + } + + virtual QString getUserDataName() const override { + QString fullName = name; + + switch (matrixType) + { + case MatrixFeatureType::Raw: + fullName += " Raw"; + break; + + case MatrixFeatureType::Eigen: + fullName += " Eigen"; + break; + + case MatrixFeatureType::Both: + break; + } + + return fullName; + } + + virtual QString getFeatureValue(bool serialization = false) const override { + if (serialization) + return QString{"%1|%2"}.arg(stdDeviation).arg(static_cast<int>(matrixType)); + else + return QString{"%1"}.arg(stdDeviation); + } + + virtual void setFeatureValue(QStringList valueTokens) override { + if (valueTokens.size() >= 2) + { + stdDeviation = StringConv::convertString<float>(valueTokens[0]); + matrixType = static_cast<MatrixFeatureType>(StringConv::convertString<int>(valueTokens[1])); + } + } + }; + + struct GaborFilters : Feature + { + using Feature::Feature; + + unsigned short activeFilters{0}; + + virtual bool compare(const Feature& other) const override { + const GaborFilters& o = dynamic_cast<const GaborFilters&>(other); return activeFilters == o.activeFilters && Feature::compare(other); + } + + virtual QString getFeatureValue(bool serialization = false) const override { + Q_UNUSED(serialization); + + QStringList filters; + + for (unsigned short i = 0; i < 16; ++i) + { + if (activeFilters & (1 << i)) + filters << QString{"%1"}.arg(i); + } + + return filters.join("|"); + } + + virtual void setFeatureValue(QStringList valueTokens) override { + activeFilters = 0; + + for (int i = 0; i < valueTokens.size(); ++i) + { + int value = StringConv::convertString<int>(valueTokens[i]); + activeFilters |= 1 << value; + } + } + }; + + public: + GaborFilters& gaborFilters() { return _gaborFilters; } + const GaborFilters& gaborFilters() const { return _gaborFilters; } + Feature& sobel() { return _sobel; } + const Feature& sobel() const { return _sobel; } + GaussFeature& gaussian() { return _gaussian; } + const GaussFeature& gaussian() const { return _gaussian; } + GaussFeature& color() { return _color; } + const GaussFeature& color() const { return _color; } + DualGaussFeature& differenceOfGaussians() { return _dog; } + const DualGaussFeature& differenceOfGaussians() const { return _dog; } + GaussFeature& laplacianOfGaussians() { return _log; } + const GaussFeature& laplacianOfGaussians() const { return _log; } + MatrixFeature& hessian() { return _hessian; } + const MatrixFeature& hessian() const { return _hessian; } + MatrixFeature& structureTensor() { return _structureTensor; } + const MatrixFeature& structureTensor() const { return _structureTensor; } + + public: + std::vector<const Feature*> getActiveFeatures() const; + + QStringList getFeaturesUserData() const; + + public: + bool operator ==(const RandomForestFeatures& other) const; + bool operator !=(const RandomForestFeatures& other) const { return !operator ==(other); } + + public: + void serialize(SerializationContext& ctx) const; + void deserialize(DeserializationContext& ctx); + + private: + std::vector<Feature*> allFeatures() { return {&_gaborFilters, &_sobel, &_gaussian, &_color, &_dog, &_log, &_hessian, &_structureTensor}; } + std::vector<const Feature*> allFeatures() const { return {&_gaborFilters, &_sobel, &_gaussian, &_color, &_dog, &_log, &_hessian, &_structureTensor}; } + + private: + GaborFilters _gaborFilters{"Gabor", Serialization_Value_GaborFilters}; + Feature _sobel{"Sobel", Serialization_Value_Sobel}; + GaussFeature _gaussian{"Gaussian", Serialization_Value_Gaussian}; + GaussFeature _color{"Color", Serialization_Value_Color}; + DualGaussFeature _dog{"DoG", Serialization_Value_DifferenceOfGaussians}; + GaussFeature _log{"LoG", Serialization_Value_LaplacianOfGaussians}; + MatrixFeature _hessian{"Hessian", Serialization_Value_Hessian}; + MatrixFeature _structureTensor{"Structure Tensor", Serialization_Value_StructureTensor}; + }; +} + +#endif diff --git a/Grinder/ml/rf/blocks/RandomForestClassifierBlock.cpp b/Grinder/ml/rf/blocks/RandomForestClassifierBlock.cpp index 1225183..7854d67 100644 --- a/Grinder/ml/rf/blocks/RandomForestClassifierBlock.cpp +++ b/Grinder/ml/rf/blocks/RandomForestClassifierBlock.cpp @@ -12,3 +12,72 @@ RandomForestClassifierBlock::RandomForestClassifierBlock(Pipeline* pipeline, QSt { } + +void RandomForestClassifierBlock::createProperties() +{ + ExternalClassifierBlock::createProperties(); + + setPropertyGroup("Features"); + + _features = createProperty<RandomForestFeaturesProperty>(PropertyID::Features, "Image features"); + features()->setDescription("The features to extract and use from the input image."); + + setPropertyGroup("Settings"); + + _estimators = createProperty<UIntProperty>(PropertyID::Estimators, "Tree count", _method.config().getEstimators()); + estimators()->createConstraint<RangeConstraint>(1, std::numeric_limits<unsigned int>::max()); + estimators()->setDescription("The number of trees in the forest."); + + _maxFeaturesType = createProperty<MaxFeaturesTypeProperty>(PropertyID::MaxFeaturesType, "Max. features type", static_cast<int>(_method.config().getMaxFeaturesType())); + maxFeaturesType()->setDescription("How to choose the number of features to consider when looking for the best split."); + + _maxFeatures = createProperty<RealProperty>(PropertyID::MaxFeatures, "Max. features", _method.config().getMaxFeatures()); + maxFeatures()->setDescription("The number of features to consider when looking for the best split."); + + _maxDepth = createProperty<UIntProperty>(PropertyID::MaxDepth, "Max. depth", _method.config().getMaxDepth()); + maxDepth()->setDescription("The maximum depth of a tree. A value of 0 means an unlimited depth."); + + _maxLeafNodes = createProperty<UIntProperty>(PropertyID::MaxLeafNodes, "Max. leaf nodes", _method.config().getMaxLeafNodes()); + maxLeafNodes()->setDescription("The maximum number of leafs in a tree. A value of 0 means an unlimited number of leafs."); + + _minSamples = createProperty<RealProperty>(PropertyID::MinSamples, "Min. samples per leaf", _method.config().getMinSamples()); + minSamples()->setDescription("The minimum number of samples required for a leaf node. Values smaller than 1.0 are treated as fractions of the number of samples."); + + setPropertyGroup("Performance"); + + _jobCount = createProperty<UIntProperty>(PropertyID::Count, "Job count", _method.config().getJobCount()); + jobCount()->setDescription("The number of jobs to run in parallel. If 0, all processors will be used."); + + setPropertyGroup("Miscellaneous"); + + _verbose = createProperty<BoolProperty>(PropertyID::Verbose, "Verbose", _method.config().isVerbose()); + verbose()->setDescription("Send status updates; note that this will increase the overall computation time."); +} + +bool RandomForestClassifierBlock::updateProperties(PropertyBase* updatedProp) +{ + bool updated = ExternalClassifierBlock::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _maxFeaturesType, _maxFeatures, [this]() { return *maxFeaturesType() == RandomForestClassifierConfiguration::MaxFeaturesType::Value; }); + + return updated; +} + +void RandomForestClassifierBlock::updateConfiguration() +{ + ExternalClassifierBlock::updateConfiguration(); + + _method.config().setFeatures(features()->object()); + + _method.config().setEstimators(*estimators()); + _method.config().setMaxFeaturesType(*maxFeaturesType()); + _method.config().setMaxFeatures(*maxFeatures()); + _method.config().setMaxDepth(*maxDepth()); + _method.config().setMaxLeafNodes(*maxLeafNodes()); + _method.config().setMinSamples(*minSamples()); + + _method.config().setJobCount(*jobCount()); + + _method.config().setVerbose(*verbose()); +} diff --git a/Grinder/ml/rf/blocks/RandomForestClassifierBlock.h b/Grinder/ml/rf/blocks/RandomForestClassifierBlock.h index ba6594f..b1c08fd 100644 --- a/Grinder/ml/rf/blocks/RandomForestClassifierBlock.h +++ b/Grinder/ml/rf/blocks/RandomForestClassifierBlock.h @@ -8,6 +8,8 @@ #include "ml/external/blocks/ExternalClassifierBlock.h" #include "ml/rf/RandomForestClassifierMethod.h" +#include "ml/rf/properties/RandomForestFeaturesProperty.h" +#include "ml/rf/properties/MaxFeaturesTypeProperty.h" namespace grndr { @@ -20,6 +22,50 @@ namespace grndr public: RandomForestClassifierBlock(Pipeline* pipeline, QString name = ""); + + public: + auto features() { return dynamic_cast<RandomForestFeaturesProperty*>(_features.get()); } + auto features() const { return dynamic_cast<const RandomForestFeaturesProperty*>(_features.get()); } + + auto estimators() { return dynamic_cast<UIntProperty*>(_estimators.get()); } + auto estimators() const { return dynamic_cast<const UIntProperty*>(_estimators.get()); } + auto maxFeaturesType() { return dynamic_cast<MaxFeaturesTypeProperty*>(_maxFeaturesType.get()); } + auto maxFeaturesType() const { return dynamic_cast<const MaxFeaturesTypeProperty*>(_maxFeaturesType.get()); } + auto maxFeatures() { return dynamic_cast<RealProperty*>(_maxFeatures.get()); } + auto maxFeatures() const { return dynamic_cast<const RealProperty*>(_maxFeatures.get()); } + auto maxDepth() { return dynamic_cast<UIntProperty*>(_maxDepth.get()); } + auto maxDepth() const { return dynamic_cast<const UIntProperty*>(_maxDepth.get()); } + auto maxLeafNodes() { return dynamic_cast<UIntProperty*>(_maxLeafNodes.get()); } + auto maxLeafNodes() const { return dynamic_cast<const UIntProperty*>(_maxLeafNodes.get()); } + auto minSamples() { return dynamic_cast<RealProperty*>(_minSamples.get()); } + auto minSamples() const { return dynamic_cast<const RealProperty*>(_minSamples.get()); } + + auto jobCount() { return dynamic_cast<UIntProperty*>(_jobCount.get()); } + auto jobCount() const { return dynamic_cast<const UIntProperty*>(_jobCount.get()); } + + auto verbose() { return dynamic_cast<BoolProperty*>(_verbose.get()); } + auto verbose() const { return dynamic_cast<const BoolProperty*>(_verbose.get()); } + + protected: + virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; + + protected: + virtual void updateConfiguration() override; + + private: + std::shared_ptr<PropertyBase> _features; + + std::shared_ptr<PropertyBase> _estimators; + std::shared_ptr<PropertyBase> _maxFeaturesType; + std::shared_ptr<PropertyBase> _maxFeatures; + std::shared_ptr<PropertyBase> _maxDepth; + std::shared_ptr<PropertyBase> _maxLeafNodes; + std::shared_ptr<PropertyBase> _minSamples; + + std::shared_ptr<PropertyBase> _jobCount; + + std::shared_ptr<PropertyBase> _verbose; }; } diff --git a/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.cpp b/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.cpp new file mode 100644 index 0000000..be56f94 --- /dev/null +++ b/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.cpp @@ -0,0 +1,17 @@ +/****************************************************************************** + * File: MaxFeaturesTypeProperty.h + * Date: 13.1.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "MaxFeaturesTypeProperty.h" + +void MaxFeaturesTypeProperty::initProperty() +{ + property_type::initProperty(); + + _enumMap[RandomForestClassifierConfiguration::MaxFeaturesType::Value] = "Value"; + _enumMap[RandomForestClassifierConfiguration::MaxFeaturesType::Sqrt] = "Square root"; + _enumMap[RandomForestClassifierConfiguration::MaxFeaturesType::Log2] = "Log_2"; + _enumMap[RandomForestClassifierConfiguration::MaxFeaturesType::All] = "All"; +} diff --git a/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.h b/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.h new file mode 100644 index 0000000..0d27349 --- /dev/null +++ b/Grinder/ml/rf/properties/MaxFeaturesTypeProperty.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * File: MaxFeaturesTypeProperty.h + * Date: 13.1.2020 + *****************************************************************************/ + +#ifndef MAXFEATURESTYPEPROPERTY_H +#define MAXFEATURESTYPEPROPERTY_H + +#include "common/properties/types/EnumProperty.h" +#include "ml/rf/RandomForestClassifierConfiguration.h" + +namespace grndr +{ + class MaxFeaturesTypeProperty : public EnumProperty<RandomForestClassifierConfiguration::MaxFeaturesType> + { + Q_OBJECT + + public: + using EnumProperty<RandomForestClassifierConfiguration::MaxFeaturesType>::EnumProperty; + + public: + virtual void initProperty() override; + }; +} + +#endif diff --git a/Grinder/ml/rf/properties/RandomForestFeaturesProperty.cpp b/Grinder/ml/rf/properties/RandomForestFeaturesProperty.cpp new file mode 100644 index 0000000..1e4c0d5 --- /dev/null +++ b/Grinder/ml/rf/properties/RandomForestFeaturesProperty.cpp @@ -0,0 +1,30 @@ +/****************************************************************************** + * File: RandomForestFeaturesProperty.cpp + * Date: 13.1.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "RandomForestFeaturesProperty.h" +#include "ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h" + +QWidget* RandomForestFeaturesProperty::createEditor(QWidget* parent) +{ + return new RandomForestFeaturesPropertyEditor{this, parent}; +} + +QString RandomForestFeaturesProperty::toString() const +{ + auto features = _object.getActiveFeatures(); + + if (!features.empty()) + { + QStringList names; + + for (auto feat : features) + names << feat->name; + + return names.join(", "); + } + else + return "No features active"; +} diff --git a/Grinder/ml/rf/properties/RandomForestFeaturesProperty.h b/Grinder/ml/rf/properties/RandomForestFeaturesProperty.h new file mode 100644 index 0000000..087be08 --- /dev/null +++ b/Grinder/ml/rf/properties/RandomForestFeaturesProperty.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * File: RandomForestFeaturesProperty.h + * Date: 13.1.2020 + *****************************************************************************/ + +#ifndef RANDOMFORESTFEATURESPROPERTY_H +#define RANDOMFORESTFEATURESPROPERTY_H + +#include "common/properties/ObjectProperty.h" +#include "ml/rf/RandomForestFeatures.h" + +namespace grndr +{ + class RandomForestFeaturesProperty : public ObjectProperty<RandomForestFeatures> + { + Q_OBJECT + + public: + using ObjectProperty::ObjectProperty; + + public: + virtual QWidget* createEditor(QWidget* parent) override; + + public: + virtual QString toString() const override; + }; +} + +#endif diff --git a/Grinder/res/Grinder.qrc b/Grinder/res/Grinder.qrc index a5bc8b4..e011e85 100644 --- a/Grinder/res/Grinder.qrc +++ b/Grinder/res/Grinder.qrc @@ -118,4 +118,22 @@ <file>cursors/polyline-cursor-add.png</file> <file>cursors/polyline-cursor-del.png</file> </qresource> + <qresource prefix="/gabor"> + <file>gabor/filter0.png</file> + <file>gabor/filter1.png</file> + <file>gabor/filter2.png</file> + <file>gabor/filter3.png</file> + <file>gabor/filter4.png</file> + <file>gabor/filter5.png</file> + <file>gabor/filter6.png</file> + <file>gabor/filter7.png</file> + <file>gabor/filter8.png</file> + <file>gabor/filter9.png</file> + <file>gabor/filter10.png</file> + <file>gabor/filter11.png</file> + <file>gabor/filter12.png</file> + <file>gabor/filter13.png</file> + <file>gabor/filter14.png</file> + <file>gabor/filter15.png</file> + </qresource> </RCC> diff --git a/Grinder/res/gabor/filter0.png b/Grinder/res/gabor/filter0.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c17e3d2dbc523c92c68e01b46764e51b7dcdca GIT binary patch literal 1522 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-)+A3C$B>G+w|5Lzi8@NOTwI>#U|?DlzV7seqgh1>kMa*V&9bY!_%+<> z&TqrdFQ=Yg&*9!R??~!IQRRmo7bow~*_5Orqgf?1-8HPIMcLK=(WVn!6APBS@Qe|Q zG!*I+vF$vqr0sTS;v)6J$dscV7C~QB^t#tba8lcl|JSC!yRB^>C)g*VduHRqvxlHA zJR~E1PY3Fjp(pWi>+heZ-iHGNmb*BoVsil`2)7s<Z!_nHhWL<{aDV#U?Be%#*R|^* z5hr-}qI{RwvyOK!_<=rlDgV;GsMT_j^)Kg(hh#3w{#}@n*!QCEU%(av_Y(Jdji?#Q zyB6C6<C{WlHTJ7&)@s!Vch}T+58I$V-d2A?`wxHo6Z`*4z??g@^96GL{;#`#d#Bxg lrNK}NVU&mexAyn%uRp<l`tMY89$=Bo;OXk;vd$@?2>=woeOv$l literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter1.png b/Grinder/res/gabor/filter1.png new file mode 100644 index 0000000000000000000000000000000000000000..c6928391b89b49e8938a8777d596090f4394377e GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-rgNSyjv*CsZ|`i(lyDGX2^4+6CBq^5s4jNdT%H^o@fX@Nrt8<J+3x5+ zKmYA(&d2jty<qRETR;>cxA_0J2(S8i2GcCABxs7((w4M!`~P&~lz(=KI)y6^25J2& zpaWUe^Y0<hPFuR#Ip<?k>V_#5^^+yMWwJI*>9V5-DYy8u^SpyWkIvh7ZalJ(&3omG z!!#vd8vfe;v+B@~<yyZQ7s+c7Lu~*3=MTRYZ_KI%J;v3*<j>&g>gTe~DWM4f)0>D@ literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter10.png b/Grinder/res/gabor/filter10.png new file mode 100644 index 0000000000000000000000000000000000000000..afba2eebd7800d6b26142b6a5d2e3456dd240e32 GIT binary patch literal 1728 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRn)2WMpDuVqsxnWo2b!V`FD$=iuPr<mBYy;^OA!=HcPt<>lq$ z;}Z}N5EK*?5)u*-5fKv;6BiekkdTm)l9H8`m6w-SP*6}-R#sP6*U-?=*4Eb3(=#wI zu(!8&baZrgclY-84hRT{jg3uAOiWEp&CJZq&(H7d?3^%R!u<L37c5w?a^=e1yLX>I zfByFE+s~gr|Mcn8|NsAiZW{%oAwbg*xas`p7tjLE0*}aI1_r*vAk26?e?<yVP_o1| zq9iy!t)x7$D3zfgF*C13FE6!3!9>qM&&cqjLgrLpE>iJyaSX|DetY|PUQD<EL!jmC zbC$P@&fWa~KcDTFNMm`T9&Zmr>DzlH<x|Z>+?y{h-pyX|bN`-_r$^Z{TKd*4vAglZ z@pt&9MfV()OG;F~9z4c>YyTwCuY6Jq7i+KNKUdN1apW)4Jo$Om43n?jQSoSZx?yq4 zVY^GD>MPk9Tb`*oT8SJwcCk&lZIkLe*?9*SFJuQPOYn~6kam~8wp;a-R$%_)X3y+A zp$$v*TGW;9@qh&vRwZ4UHJc@8WnjtUy<D&F3Z+h6QJw%7GEaOALJ#f!o%)~^Cx7qI z?!)XqDt@rc=d%S_${3wBcaik>By<a|Y5qD9eAE2HxetPN@^X)7Ka8$1{{l6&v@7pU zcW%V{X&S3t_V)6tt=;K)U|Q7j7rBq;{km8uEzcMK;PpfHKPG>=?zhSLL={hN(B0~H zvA237FT{1*KOS6SYR-8h=(Wh+{@;!j{B^Olo$uS{fBgMH{hw5oLFQRsrfpZOT+QDd zHCt2eJtcI}y<^a@SR4Fh+sEr4o_(;hliqiz|FHg#<R8-ic?fuASN+KfESBHhVP3ZP z^yg{ozt`X6`F8HY?IZV$|K;Z^?7h+UHLr7%*Ol8p;6AoH`tje~cPIG|)#)%TS|jw; z%nlxU{)Kz|=i5qtb-u5D&2Hbj%{lTp-!-?#hZAsAmHd8AX)yZ!ai1-7{OXt1X8Q^* zZ_lq0UH<;54Agj$yA|sXRUr}yFup|YTFi<B2mbw?A#b5UDGo|HXs+gdc(VI&U_INe zzx<;7V4u7U0HviD;D}>}CPItH7avV^d0>}GFbV75x%-zpW9FW9ul0K+Kv~(-)z4*} HQ$iB}g8T3a literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter11.png b/Grinder/res/gabor/filter11.png new file mode 100644 index 0000000000000000000000000000000000000000..703d7087a6b5f1d29785aa3975f222fb874584df GIT binary patch literal 1179 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2W0T+@3CuAr*0N?`~Wx!z$AHP&}|{^#blJ1=FG?mxK?(Wepx492y5KH79Ww z%9}jonPhMAf9}L*tn>Ds{CR0=+4;YVr@Gy$ty<Tf{e`u6--3(cCcm28;xD}5mJ|GY zz4?2(-JPC(LpD~o`ztT1V<%n;Ud+xw(;O%D_wLMizI*e2zI<@pIG?Z1;`6Tt@t*A* zH9dwJ2DdEqCmiS~T?#Z&`Z=qR#Kv_dzm8-)-s8Zed~K@Re2LsS;vNY{c3pZQytwx> z6K79ExKwS@mclrNM#0yoycb^&zw&}xs@8F_KFr!V?$0H2A3R+5x9r35nfv+d=KcKr zV7~EuzKVkt-3N|KG&4cXX<Tp(ZYJsMGP%8ZSJ7;?`RH-A&hEj=wI7d|*VI4T-JE}Z zkJXQ7XD&19OXLgwkVp<lfH+)R;0oLmWUzltEkyD!IF3FXVGen0nf>GMnfdHC?@Z^( zx1Ue{XZxe**?#6YgL_IJ438~1014DEg%zf-049U|Rr@g{Ng#ze|1wzIF*&Y<$0Hf+ zcu9DQgBkz)Vr7F=#ks6MT<#C1TlL)k!U6PNoubMIZKY$MA0Oa87R_RJOhN>jOt@IK z!jlXc?9VSsSA)|#%=X@W50+l6T$1(Ye9(jaGkTV9YpgFbn)ie4pZcjE?kmx4$DT;N z7W?;@o#R$9NQ%w;(s}V%Wh0B*8tq;?Lukr>y7b~}?JTrBBPTdFeapkYl5+3vwUM6X e_`m=8V{e<a(5mr%%r0Qr!QkoY=d#Wzp$P!75C}^E literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter12.png b/Grinder/res/gabor/filter12.png new file mode 100644 index 0000000000000000000000000000000000000000..14f058f807ebe87a04992a5d7da5aa9062a1b1b4 GIT binary patch literal 1313 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2W0T6FprVLn`9l-Z5Mi6E5I-G5;~6MZmj2&97TbVl*QIKIUC;71{c!skpqI z|JUo8`|Un`cb9Q*%X^eMv1{Vv6Bm5<=mZ%S$w=<%obIaS)~D=j{&ADX(G!I!FVy11 zR!Ug*@!UOfT1iy-_{4>?D<YR9J+Uw<5!UYx>7lwI_Sfw1Ztwr|ePX@+BXR8~70xG| zj(ULL38y~u7M+kD5Ny#IY7#4IOgmLH|NTGF-(T~;<6EoDMOS$zu_71oT8_vQg0Wu| zQxx4xF1Amgg+%;5YgfB&(JB{v`$rZ=TP7&)dU5!IQ{Rh4{Lb8#i@NQ2bf0wS{W745 z#NqR$`@%%`b-Gr$?EC7vPn%9&h6L-+^Se%!FL(NVB7LLBq!aa$VnurtQx+yI0Ya4& z#pBXWVp?t>2#km!Ch=z@Fg^WA8eCZyR*3xCU0eH?Nh#i>x$1CDBd`Qx@O1TaS?83{ F1OTsK9Mb>* literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter13.png b/Grinder/res/gabor/filter13.png new file mode 100644 index 0000000000000000000000000000000000000000..3302326b020d4db80be50eb3ec8c3b4cd9e5f41b GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-W>!xZ$B>G+w|6%7-f<9Nx#;a8$&q~`z>Diq)ATt^xAd3}PYgV-{%diD z(C+y8+yB%EPX51oQGCD`?yfq=i~1VBSY7uocp)zGw+V;f2ZLYw@4qYC#~qP2KA|XD z%aJ+3S?ia;C585oFN#YX`BuJg_Gp#~y4XH}&2q^iens9Q4_A37sV5V<>{?97&`|qv zi;K|X-xKZoYiymWo4dd!i!KG3tmOqVIb;gR<P{nqrvwCooU&jM*eR}Hr;x3o{o+lN z7m9ytTiR9mOhFE}xB~Ly!vL`FoWcI0iNu!=?QuC$HNul$D1Kb)|7iCjM+$vHd{|vN z{K6*p!aq@!i9ht6sTqs7(h1PQ^$&aZHl6(3W#=<5{7d4eJ&X7!KNqc?VYB|l;U|@@ p@@nU`e;HK9U2G>>$+w?>m{+Y-jh0(pq6Ew}44$rjF6*2UngE=#a+d%A literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter14.png b/Grinder/res/gabor/filter14.png new file mode 100644 index 0000000000000000000000000000000000000000..cb7a47c0355dca7c255bf6698f685fda24373267 GIT binary patch literal 1572 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRn)2WMpDuVqsxnWo2b!V`FD$=iuPr<mBYy;^OA!=HcPt<KyG! z=NAwV5E2p+6BCn=kdTy=l#-H?mX?;4m6ey5S5{V5QBl#>*4ES0GcYhPHa0dfF|oI| zcXM;|^z;l44h{_sO-@cuNlD4g&8@4etFN!`?ChK~XU_8F%XjbIy?_7y3l}atd-m+@ z+qeJ!{|7p06pV%deL`UElhgBnHgOhsL>4nJ@ErzW#^d=bQh<VzC9V-A!TD(=<%vb9 z3<Zgqc_n&zsU->~dIow%h94C&r!p`wb$GfshGaOuy?r|DQG$SLV(Sero;Hsuv+qpp zUhw6&eXRdJmAxf*B)4o$k8FH1s~{}&>2s~4wXSV;a{C_DT|2C=A{x8HC$Av7uzf<i z&6Bq$G_%vEB{wLZ>SD5+zIvhiRF%2<E0(TqTh;}Vws-K0wEpbgV{mXyQkUVCppH`} zPv68ml*-Cj#Zh^}`Cebl{!8MT&5X}quzIi2Hj7B?Th3*=WX|=h9YMj%m3t;Ztmswb zk7|1uu*}C|`uZE8G8>MIb$6w&Sjab5D)KTf$e49^Tmq&=waxU141RY{`<%tYj_q8g zIw^j#R*RM@K2hZV$v;6l!YJ82aEAM`UsHQ+tOMqqpTFY)bC!ji>b<ab<L?g+u`Q0A z(Pr~RG2i{&u5|s0&%f~|e?H;0f0MZ8a^dGs&12&3*v+j;e9x%OFC%PXeH!AhzxV2Q zzWb`;q^S5zC|TRF&wb{ngOw94=NrAKndZf=xpiVxFuxwx&S|Y+gV)vPUwF9O>Yuvs zfkQ4IzAbB8>|q%=@2trC_se?bu1wD5nmN<!)TKF}1U|97o6!E}|K!^)Ww~n7S0>y_ zwOgIIaY9A(-&1k(WHrjIe_sm!eR*l^t0Q%#7EkypPyBDPe)8W%FKU*t>pk0R?Yvh4 z+RshqOV5*wd>L-0l)Cp>OqKkvM}J?O_<iZ%0qHAsC!FOzl|Av}x_e?)-t^D66PZny ztlYSGL%zgp_hnISH+_I1?S9VM_LldPosFv8;E1|&@_w|bNoc_}j>Re)53VSmmZj;# zlDaZgqH9Xy`K#y7a6bPHiST#!KIOpZUGVFh`HCa^w#3b-N=oC&ys-Yu-Su<7FERqh dSCftJe}<OdGis0i)64?p8&6k1mvv4FO#n3%nPLC{ literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter15.png b/Grinder/res/gabor/filter15.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbe08527e55909b7bdf4c212fe21ec84972a161 GIT binary patch literal 1595 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRrKtii*n0%BrZSn3$MYSy}n}`v(LBgoK2Ig@uKOhet+6#>U3R z$HylmBqSy#CMPGSrlzK)rDbGfWM*b&XJ_Z+<mBe&=H=z(=jRs`6ciQ~78e(nmX?;4 zl~q?)*Vfk7*VnhTwRLoK^z`)f_V!MnK7H1#S@Y)2n?Ha4qD6}qFJ8QA)vD8{Pk;FE z;s5{tK=+J-(GZ|p2<#1Q-ww2lv%n*=n1O-sFbFdq&tH)O6qGD+jVKAuPb(=;EJ|f4 zNX*PD(aTFMQ83Xn&@(dpsE|38fq`khr;B4qhV$FoC!-z(2(%?m@NDr=ND^XWlyu$l z{{OqG&uqUg=9Y`)*6!w+vp(WO^l~BTpW(|Ynhw9*lf?P`d9shzob%}#AO1|8aQ^{U z<%$0*_x$l{6O>82$t4}J%R?*3B3`2^@8PB=d_cJdttbEI$JF`G)jut+lW=rNc;d^F zZrhrDI_woKQtMpL$ciiSSDtu1@pbq-6_06!dT|@wwlC^c<g*CNNJ!%kIJ~vv(AUTl z&T^lso*1RAN|oqM@jA4#?)bVz&1%0a!uK>LsTH#w()9hRUkOq4Z{K-WpJ|zrRZ2^} zcm7^sv9Oixo%WT2X_oG`ZZD%%J2{W3JW=HT$v<Iss$Q>jn6~DWSK+rlt#Pijs(sXC zy<*|T2^F=n=MMIzt?$?5`h2MnV(P4}I~IkT4fAi@>gV~Y?K|;OR$*M-!G}kmhMi`g zB_<J7;Jx8yz%z(Xe(&x7`>)$c>5b|U$D}W+9*Y%&-&yDzJr8D^>ld_AdFibn?Wswt zr^ZCL**sBP@4l{n{)^|6UE1Sc{pc@aNVjhKU2SUO;??~r#sA~S#OWJ1zkYG6^~)Qt zO=s*<<vvYl|HFR5RCK3^SMcUjjW?g3(fRDl`+W0}*RR880e!La_RQ(~X3t+&yZT5? zsfkhHT!<Zhg|&ejKfL*Paq|iG>*+?%E3Q10dE?&pYw9+Smt~gM=e6&vvfCwK82OCz zRWQWEQ|{lF4LZE|jN}W$Q<wVE^6zaB^;<b@;q}RyoX-#a_S3hEpJtRM1$53Hxt-?q z^XKY&o5pl+D9JjiQn=5+?~a7|8kG~@#i!`jIRBs24)o`L$4}bR_U&8o;@BOQWFtd; zS?l@02oSZMe|mlX|Ej0|q!srcl>Ic}e{JQw|7-_-x%^r%t129nqdZ;xT-G@yGywo! Cz`+y% literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter2.png b/Grinder/res/gabor/filter2.png new file mode 100644 index 0000000000000000000000000000000000000000..206e8c58ee23a84f2304a3bc0a4e49426abc857f GIT binary patch literal 1779 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRn)2WMpDuVqsxnWo2b!V`FD$=iuPr<mBYy;^OA!=HcPt<>lq$ z;}Z}N5EK*?5)u*-5fKv;6BiekkdTm)l9H8`m6w-SP*6}-R#sP6*U-?=*4Eb3(=#wI zu(!8&baZrgclY-84hRT{jg3uAOiWEp&CJZq&(H7d?3^%R!u<L37c5w?a^=e1yLX>I zfByFE+s~gr|Mcn8|NsAiZW{%oAwbg*xas`p7tjLE0*}aI1_r*vAk26?e?<yVP_o1| zq9iy!t)x7$D3zfgF*C13FE6!3!9>qM&&cqjLgrLpE(-T_aSX|DetY|R-V-(f)<Da* zQGKuHT()}t<^TV*1+O_28d;AWII%%)R~*Zx-}f#xa|Ft~diL%9N3T6qPqrR(j1b;+ z<dpuxsmft>2WDMRm7DkH=|RWRQ}OcmI?UPGeb-D4EXcCn_(^#A*RCzg9z_4Va3o5S zb@?{o@=s=dGeX=yUi*-@$J`<#X>AWvsk=|uyElT9krck_oAGCVQTxXq9ld%Fwm)uu zIQd6O0oaIFO&VcxW=`9b)@uF;voqNk=GM4Nq072*vAcW9d>;8@&ks-kc=f@8&aFmV zS6t;TO)bCVb|P$vDeL`H?P}LfX#|K@ddRozx#;bla$nM}d->t=kB9NA&i{3@{?3b5 z<xTJT<Bxnl?EmpIe)aM#e~v!<sk}*^H~x74;rx%k@hi{&W%_vq_rsk#Bvb6=_I1X$ z**{u;&`~%$RpiPtEB=?KtTPvv-b4zStt-zt|7v*h)^Qs<|NUd@56}Pj9lr(Vi>^CY zZ;CZrw@^<b{JL#?mRZ#FwI11A`=;`PVkuzuE1M<S@0avmOVz$KtLc5!&gz|cZ|*#8 z%`tC&$FtkF<b1_n-X-hjZV}uhn(2FR`=^EQp#AP%EzXqk+xdR{Ut6XRHV^K}@s(hP zcKAI{X~pP?*F<WrpSG=icil(%#J3-}HouX}sXkTvS2;i4I#W3XWOe>iXOOp7bc5WJ zzF#oqcgCgXQ))9_3ZJR|ZLxVWTBM(F-u21v76&+NcAHn4H09{usrbDtys+xShV|;M zV57JfMO5B^Dv8TG{7`Um+_Eoesp})Fo`3o!ZctnLt;5qbD)__N8jOUv+x%zeWw-xI z0eAm7yRHGtHwEABY*(*myLz{l|Kt5Xr*9ZvI3=iUpWypKNPB-vSl^DIy6WHj88i2+ UGg<Y1A1L>Gy85}Sb4q9e06cFIL;wH) literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter3.png b/Grinder/res/gabor/filter3.png new file mode 100644 index 0000000000000000000000000000000000000000..ec9e63cb3ba44ad7bceb652523bb3acaf906e2f5 GIT binary patch literal 1497 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRrLAv9XDXiRtL*I6FIshK9z)#l^?RCnhGQrKM$NX6EMR=H=z( z=jRs`6ciQ~78e(nl$4Z~mR43)R##UyG&Hofwsv-QPMtb+!GZ-VSFT*MX3dEcCmuh3 z{Nu-u|Ns93%^wA$Auwn|KzVCH5zuj*1s;*b3=DjSL74G){)!Z!pk#?_L`iUdT1k0g zQ7S`0VrE{6US4X6f{C7io{`~4h0Li83{1A3E{-7?&Tns@_PdiH;F2gb$${x)!D-f_ zhkx%2t-PXRm#Nd!BC7xD&8@q!>8T%YC;RcOe-u6Ak(Q5Ry6V*REUr%*&v}*Uxfe<< z+rb%pAu)aCsp*OwKv6#H(=t<wZ!h>Ev}^}AP@>&rg^YwcL}tmse^25Mi)>Hq`fPBt z-+)!N%%k~KV_{Q3h}??}&QdZ{<6AWMwQJh7Y1XxBf<!|2dW2gReR61*y_6tc)K~MT z^_S$b9|FsM$SzxOm@l(J>$%cnmW3;9K-Sot|Mc46=yL;B^DCJ%9{JB`;<H}zaOIqC zxCtNLIGtnWYd`qg;3yE?2U~wQ*iu{)=DLTLTK3$*HZ7X>S~dSc95C<H&Jz}n8e04% zHxzl#|9dk3kcf=1{~?k7L@uj06WW#+=^6P6be&=b`s8qN$horza}V=wJ-hZ8T#x_L zn3OpRBDYUiekzoBGGoVC#z}Dh&O23^@w{*zdu<%!o|~r`k&J@4>geo~m7-_8v*O}E zC(TY)0@;D$+{$Up_70yetJp^sIbXyG=$f#@?B+6T+kbkr!)*-Vt1&vs%vZl~+fRnc zNTR?nN|LrLc=C8YdjM)&?$Ke-lNKon3;O8=ilsGA>`(ExtMmBOcd1k!&;sQTPgg&e IbxsLQ0GV4-`2YX_ literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter4.png b/Grinder/res/gabor/filter4.png new file mode 100644 index 0000000000000000000000000000000000000000..a4f16a4123bbe42af5425574d94686cba9c73c63 GIT binary patch literal 1310 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2W0TeV#6kAr*0N?-+7vgbKJGT)%^Xxi~!a)wY;*QX(ZaY&9O2r<&g>zts5i z;~D??fBtknK5@Z!k4}(bk&NW7&grgNZhgwm<{vkC96eE(@<J_6Y^8){AJ5$*r<FvN zk561UyCQN)(i01#5@G%BkRBQLw!BBF6T2o-+mK)TpB5fnKmX6allk-iTsrx&Z9;1o z(3-A^6Ix~RIU-j`tOP>N$P+_MBJgLHkY~`Jk0;~j|M}VZtySittGts~k&AdON8|~? z*e{AHitZ&B+ku{>zC^s;p1-HlH#+sdc<&F4H?XY|Q6O6rGr+bk1lvjziP}Fg&$B}x z&W>2({v<m7$?hcDc@r}zC)QsJ{Wl>zL;k0Zn5WRs8DT>$)!<I#`=74%d{RjD+((si f;8*<bkB{Y*;!T>><LpiYi!TOGS3j3^P6<r_d3-9_ literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter5.png b/Grinder/res/gabor/filter5.png new file mode 100644 index 0000000000000000000000000000000000000000..c9fb4e7aa2d069b55a04cbb3b8cea62ae2fce22b GIT binary patch literal 921 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-W=>BR$B>G+w|6%7Mg>Z+CQ22uiydUkJL$TkBH!pz=X1~LyARfI3jX3( z-lSf!w|?7w`6DMkciGLcS^wfNRT%FROVXtF+r2oi94}|NWD&n2Z;^+qypz<E30-zA zCMOg{YdJC}IBWe9xTMe?@<nlpBj3sw&K}J&K^NO6u#usm{o>DpW3MOf@BdTX@{KKC z18iCs*fi0lAnUcfK-Py$0a?F717v+bAjtX!i@?^qlBwZRcX;OnHrplt`#+?0E(E*W z8SK?Iuvd9ZL0*-}0(sTo3dpO8D?na7902wz1seDlf8KKH!OR&h{GI8wLW;ce;%MVR zEI}yu??QL|3F8d?j}Psd^rui7b;Kl&mx*84XZ#eL^JisOo#RD)jbE&;`xm?r7x~+? lC_dl|H+I2)h3~(cuUe@ZUEeEy5SViqJYD@<);T3K0RR*6dOrXF literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter6.png b/Grinder/res/gabor/filter6.png new file mode 100644 index 0000000000000000000000000000000000000000..5073215f98572823eb472c52d3ccc4e615843bfd GIT binary patch literal 1565 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRn)2WMpDuVqsxnWo2b!V`FD$=iuPr<mBYy;^OA!=HcPt<KyG! z=NAwV5E2p+6BCn=kdTy=l#-H?mX?;4m6ey5S5{V5QBl#>*4ES0GcYhPHa0dfF|oI| zcXM;|^z;l44h{_sO-@cuNlD4g&8@4etFN!`?ChK~XU_8F%XjbIy?_7y3l}atd-m+@ z+qeJ!{|7p06pV%deL`UElhgBnHgOhsL>4nJ@ErzW#^d=bQh<VzC9V-A!TD(=<%vb9 z3<Zgqc_n&zsU->~dIow%h94C&r!p`wHF~-@hGaOuy?rw5QG!5QVyZw#LWj%DlrT2O zm%sP#x@Y5Qv+JGCt*Y`l4)<<-n7iuK{MmX|thrk*wDhmNCR_F5@k49o9ryO@dkB1* z(7s2`GWBInv8nX^1$kR7sx^(CuqyLBtX?U#O3O*^(}e#_AnALL*zWMIoT3tQlg0Q9 z_cDpcf>|q8aa5jgj_Z#p+M*%FdrsQ)PLP^EEBBg>N0%kEimyo2DQ<I?J*V(Qk-yTm z=;Eiv<r>aUB~LfW@>wLsf8a7*lX5Pk-%x61h8)lqu6Gmu3-9b%`9R0HwdiM9!68ZU zJwPM$i_NNfcFt5fztj02>$?f<fBsLtojUFGnR%5em)Gk&x!L!r<ktbw`2mNYDt+v; znzObu?b)%&w1k)KHcu4SyZc4H6Au2})>)adHr}#h%IBG%f3e%e#oEpP```gvw)q@^ zC7P-F5a<2>zVCniG|!fqEjPS+7PIV(NjatdJoH)3i3OKEwcakda;Vq%RGk01yRBe@ z*VWfwIRAVnU;Vnr_h&GCQ)~KOZW`lqYUZbuWfj%NsXD&0{>vsD2?-JehVCEnleuc{ z%DYdky!-T&-se!>-+PZ(zY6F3l;gfL=GJt(VzqB8Ma|Zood9(6*AuppG8LCBWU9Vd zeAD^l%Bwwd^UW6p+n!C_T9>LAv?}1E&6neh9iNs2pKzA@RP}^y+P<ZZv#h+otZ3VK zX67ryXT`#2&+JP~k~^ufI^dxWzeC5gw9B0<${?!!{@q)>aFx?Dp05(h#;)^<Q}>0x z6IMMweX`#Iow>Q{bF6_8b)vp8MoSeKwHF@p_?U>U`)+wURqgBf@U<G=>Bo0R-37VQ d^u+GF`&)j`sI7H6dj^zYJYD@<);T3K0RY#LmKp#6 literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter7.png b/Grinder/res/gabor/filter7.png new file mode 100644 index 0000000000000000000000000000000000000000..46d300f5edd01d66228b8a8a9b3b1b42cf589bcb GIT binary patch literal 1596 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv3?%0qQ0W0ujKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRrKtii*n0%BrZSn3$MYSy}n}`v(LBgoK2Ig@uKOhet+6#>U3R z$HylmBqSy#CMPGSrlzK)rDbGfWM*b&XJ_Z+<mBe&=H=z(=jRs`6ciQ~78e(nmX?;4 zl~q?)*Vfk7*VnhTwRLoK^z`)f_V!MnK7H1#S@Y)2n?Ha4qD6}qFJ8QA)vD8{Pk;FE z;s5{tK=+J-(GZ|p2<#1Q-ww2lv%n*=n1O-sFbFdq&tH)O6qGD+jVKAuPb(=;EJ|f4 zNX*PD(aTFMQ83Xn&@(dpsE|38fq`j(r;B4qhV$For^6lv2)HFqn5{9<p+!Z3sYvu< z-T&>c<!TJGw_bk7v*laQggLRo|E{cTx%MZYOJ_oIlwsrJpIoQAB;_7HKJ(L5as5NS z$`k)r?^(0qP!O+c*OEgYle$eebs5&2(TO=M`)R`eM(Zc{!}FFlZJK8HKu%BRG;>)% zjMa*Olus?`JND_Q-qU%a$X{t(^!F4{#l3^yyMvDkDwkYbf8zmbR>dUC$YqBsc`8ph z-|M+kYdc*eQ!DtLrTF?CAv_n|_<whquSirb7P387p_7~jH08wXiPGjib0?_e{aET@ z85ozR9rfZt&v!1<H>rN{{e}XDk;`%;+iadF{%0)Oe5TWGnZ_G;zF$)p+eim(i!$Hw zh&jt*zG|F*`(@F&D>;u<<;Lye0$UxUJXu;jZ~HS|>)#V6)-BZyvXVXbTK`AI*V5&W zo3^pKiQN&****oRRPIySle+iX`~QE9Qd%OkNZYVV=t<2qP5C+DGmDj*qjXoAObK#d zSvn!fRq?tS#KEucPyh8(=ki1Tl0UW94SQt|{@yKhPHCz0%}vX9?A>~L$BoiCZ=Ae# zPg2?FKNITo$tPx>d{XhI<;a_&<Y_;bwf&8I^!1B&Zl&3hJuzpz<InnwKU?{Ik+H9- z5!6ZZbnLoj=zM<j@#N+c;-)z%e*4!SetE0E=Zm)c#3HLgJsbYLpKHE2$_MX{ll(NH zeUIeMo|!uzPG4HyIq&KR6;J>tC};+KZkzk}#KEtrn!qTqUwUiWFWwas+HIbkJ#qJX zvG}zUbM*E3)72!hA0JsemCIGF^HI=o`=w%f-uV{if#D*xYC?VdPj#X9r|-{Ovms{I z^mTLQ&rbF0kzN*jy!h{_=dY(u0i`8ic>mv9WA~r;z%Q3y@y7K`pe*I->gTe~DWM4f D(F4BH literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter8.png b/Grinder/res/gabor/filter8.png new file mode 100644 index 0000000000000000000000000000000000000000..28638380ca58be737358b33e8b75eb59df04f626 GIT binary patch literal 1165 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-=D(gUjv*CsZ|@kc3JDZ&y|~`b!N9aAtnc)NgISvvJc>Q&x+E-aPjUIJ z3-bSW_-pr_@9UcQc=O_<CqLe-kg&9~)^ejz=>M7N?_Qt!E_!_8!d995A88L$CjxCa zalxt2yk_%<O&&*26sBY-9+%!1SrNG;>4}BWmI;q}@9XT*2{J5_k&HU=u`ORLPHd%w zWgn03lZwOU-1^-iJu>cXVnur%&TgIV3Ur{dGq>fv4`&aZRuWaFr!`aWM=i>~dwy!X zHrNS^y6u2Y?9lsVuw{bst`~=aUVO2LALusfOW4`1|MQ}5{h!D}P!I{meo4$wbT7Hs z4h-UpuJXXJaS^YTpoxV2bzYkI2xqAMNsoOh|B37WVey~m_Yd8)f;;8cAIsOJZk7XQ f-}_%aeiW}i!G3yLx=Jvxv|#Xb^>bP0l+XkKU|bPo literal 0 HcmV?d00001 diff --git a/Grinder/res/gabor/filter9.png b/Grinder/res/gabor/filter9.png new file mode 100644 index 0000000000000000000000000000000000000000..c659e0778fddc03f94a1e2da3bd44725bc6cf138 GIT binary patch literal 719 zcmeAS@N?(olHy`uVBq!ia0vp^H6YBv1SC~zoL>Sd#^NA%Cx&(BWI!C2bVpxD28NCO z+<y{Tfqc#akH}&M2EM}}%y>M1MG8<*vcxr_Bsf2<q&%@Gm7yRpGp|H1FSSI$M9)Ca z$nc{==2Qj-CVNj8$B>G+w|6#nrZ@_)Tr_;36Om})eD1z!u|KDV<-Y^Vg#7hAoN`}W zx8A?*1+%NX#xIA9>|J&NUm6$ji_|W_D#X9IzOr7f()Q5CBLU4aK^OU*d5b(;?RiX3 zD2o0yxT4S=^5rmfB`zI)@zaH%yP&R*tC#=zyw?8HXO81SG<O+_@4v>F;|)0E;BCM7 zQ}O%*dAm;g|0;c|ZBkDrbk#{@O>oxwmAJx@Z{>^jg>05f7RggtLeA~HI)T`QIr87@ bd++~i@y4t=U*aeLOm7UHu6{1-oD!M<r~nzp literal 0 HcmV?d00001 diff --git a/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.cpp b/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.cpp new file mode 100644 index 0000000..188c7c5 --- /dev/null +++ b/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.cpp @@ -0,0 +1,22 @@ +/****************************************************************************** + * File: RandomForestFeaturesPropertyEditor.cpp + * Date: 13.1.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "RandomForestFeaturesPropertyEditor.h" +#include "ui/ml/rf/RandomForestFeaturesDialog.h" + +RandomForestFeaturesPropertyEditor::RandomForestFeaturesPropertyEditor(RandomForestFeaturesProperty* property, QWidget* parent) : DialogPropertyEditor(property, parent) +{ + +} + +void RandomForestFeaturesPropertyEditor::invokeDialog() +{ + RandomForestFeaturesDialog dlg{&_property->object(), this}; + dlg.exec(); + + // The features have been modified, so notify the property + _property->objectModified(); +} diff --git a/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h b/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h new file mode 100644 index 0000000..3a288e5 --- /dev/null +++ b/Grinder/ui/ml/editors/rf/RandomForestFeaturesPropertyEditor.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * File: RandomForestFeaturesPropertyEditor.h + * Date: 13.1.2020 + *****************************************************************************/ + +#ifndef RANDOMFORESTFEATURESPROPERTYEDITOR_H +#define RANDOMFORESTFEATURESPROPERTYEDITOR_H + +#include "ui/properties/editors/DialogPropertyEditor.h" +#include "ml/rf/properties/RandomForestFeaturesProperty.h" + +namespace grndr +{ + class RandomForestFeaturesPropertyEditor : public DialogPropertyEditor<RandomForestFeaturesProperty> + { + Q_OBJECT + + public: + RandomForestFeaturesPropertyEditor(RandomForestFeaturesProperty* property, QWidget *parent = nullptr); + + public: + virtual void invokeDialog() override; + }; +} + +#endif diff --git a/Grinder/ui/ml/rf/RandomForestFeaturesDialog.cpp b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.cpp new file mode 100644 index 0000000..b3d9eb1 --- /dev/null +++ b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.cpp @@ -0,0 +1,216 @@ +/****************************************************************************** + * File: RandomForestFeaturesDialog.cpp + * Date: 13.1.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "RandomForestFeaturesDialog.h" +#include "ui_RandomForestFeaturesDialog.h" + +RandomForestFeaturesDialog::RandomForestFeaturesDialog(RandomForestFeatures* features, QWidget* parent) : QDialog(parent, Qt::Dialog|Qt::WindowTitleHint|Qt::WindowCloseButtonHint), + ui{new Ui::RandomForestFeaturesDialog}, _features{features} +{ + if (!features) + throw std::invalid_argument{_EXCPT("features may not be null")}; + + setupUi(); + updateUi(false); +} + +RandomForestFeaturesDialog::~RandomForestFeaturesDialog() +{ + delete ui; +} + +void RandomForestFeaturesDialog::accept() +{ + if (ui->chkGabor->isChecked()) + { + if (getSelectedGaborFilters() == 0) + { + QMessageBox::warning(nullptr, "Gabor filters", "Please select at least one Gabor filter to use."); + return; + } + } + + updateUi(true); + QDialog::accept(); +} + +void RandomForestFeaturesDialog::setupUi() +{ + ui->setupUi(this); + + fillGaborFilters(); + fillMatrixTypes(ui->lstTypeHessian); + fillMatrixTypes(ui->lstTypeStructureTensor); +} + +void RandomForestFeaturesDialog::updateUi(bool save) +{ + std::map<RandomForestFeatures::Feature*, QCheckBox*> checkBoxes; + + checkBoxes[&_features->gaborFilters()] = ui->chkGabor; + checkBoxes[&_features->sobel()] = ui->chkSobel; + checkBoxes[&_features->gaussian()] = ui->chkGaussian; + checkBoxes[&_features->color()] = ui->chkColor; + checkBoxes[&_features->differenceOfGaussians()] = ui->chkDoG; + checkBoxes[&_features->laplacianOfGaussians()] = ui->chkLoG; + checkBoxes[&_features->hessian()] = ui->chkHessian; + checkBoxes[&_features->structureTensor()] = ui->chkStructureTensor; + + if (save) + { + for (auto it : checkBoxes) + it.first->isActive = it.second->isChecked(); + + _features->gaborFilters().activeFilters = getSelectedGaborFilters(); + + _features->gaussian().stdDeviation = ui->txtStdDevGaussian->value(); + _features->color().stdDeviation = ui->txtStdDevColor->value(); + _features->differenceOfGaussians().stdDeviation1 = ui->txtStdDev1DoG->value(); + _features->differenceOfGaussians().stdDeviation2 = ui->txtStdDev2DoG->value(); + _features->laplacianOfGaussians().stdDeviation = ui->txtStdDevLoG->value(); + _features->hessian().stdDeviation = ui->txtStdDevHessian->value(); + _features->structureTensor().stdDeviation = ui->txtStdDevStructureTensor->value(); + + _features->hessian().matrixType = getSelectedMatrixType(ui->lstTypeHessian); + _features->structureTensor().matrixType = getSelectedMatrixType(ui->lstTypeStructureTensor); + } + else + { + for (auto it : checkBoxes) + it.second->setChecked(it.first->isActive); + + on_chkGabor_stateChanged(); + on_chkGaussian_stateChanged(); + on_chkColor_stateChanged(); + on_chkDoG_stateChanged(); + on_chkLoG_stateChanged(); + on_chkHessian_stateChanged(); + on_chkStructureTensor_stateChanged(); + + setSelectedGaborFilters(_features->gaborFilters().activeFilters); + + ui->txtStdDevGaussian->setValue(_features->gaussian().stdDeviation); + ui->txtStdDevColor->setValue(_features->color().stdDeviation); + ui->txtStdDev1DoG->setValue(_features->differenceOfGaussians().stdDeviation1); + ui->txtStdDev2DoG->setValue(_features->differenceOfGaussians().stdDeviation2); + ui->txtStdDevLoG->setValue(_features->laplacianOfGaussians().stdDeviation); + ui->txtStdDevHessian->setValue(_features->hessian().stdDeviation); + ui->txtStdDevStructureTensor->setValue(_features->structureTensor().stdDeviation); + + setSelectedMatrixType(ui->lstTypeHessian, _features->hessian().matrixType); + setSelectedMatrixType(ui->lstTypeStructureTensor, _features->structureTensor().matrixType); + } +} + +void RandomForestFeaturesDialog::fillGaborFilters() +{ + for (int i = 0; i < 16; ++i) + { + auto item = new QListWidgetItem(QIcon{QString{":/gabor/gabor/filter%1.png"}.arg(i)}, QString{"Filter %1"}.arg(i + 1), ui->lstGabor); + item->setCheckState(Qt::Unchecked); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsUserCheckable); + } +} + +void RandomForestFeaturesDialog::fillMatrixTypes(QComboBox* comboBox) +{ + comboBox->addItem("Matrix", static_cast<int>(RandomForestFeatures::MatrixFeatureType::Raw)); + comboBox->addItem("Eigenvalues", static_cast<int>(RandomForestFeatures::MatrixFeatureType::Eigen)); + comboBox->addItem("Both", static_cast<int>(RandomForestFeatures::MatrixFeatureType::Both)); +} + +unsigned short RandomForestFeaturesDialog::getSelectedGaborFilters() const +{ + unsigned short filters = 0; + + for (int i = 0; i < ui->lstGabor->count(); ++i) + { + if (ui->lstGabor->item(i)->checkState() == Qt::Checked) + filters |= 1 << i; + } + + return filters; +} + +void RandomForestFeaturesDialog::setSelectedGaborFilters(unsigned short filters) +{ + for (int i = 0; i < ui->lstGabor->count(); ++i) + { + if (filters & (1 << i)) + ui->lstGabor->item(i)->setCheckState(Qt::Checked); + else + ui->lstGabor->item(i)->setCheckState(Qt::Unchecked); + } +} + +RandomForestFeatures::MatrixFeatureType RandomForestFeaturesDialog::getSelectedMatrixType(QComboBox* comboBox) const +{ + return static_cast<RandomForestFeatures::MatrixFeatureType>(comboBox->currentData().toInt()); +} + +void RandomForestFeaturesDialog::setSelectedMatrixType(QComboBox* comboBox, RandomForestFeatures::MatrixFeatureType type) +{ + for (int i = 0; i < comboBox->count(); ++i) + { + if (comboBox->itemData(i).toInt() == static_cast<int>(type)) + { + comboBox->setCurrentIndex(i); + break; + } + } +} + +void RandomForestFeaturesDialog::on_chkGabor_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->lstGabor->setEnabled(ui->chkGabor->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkGaussian_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDevGaussian->setEnabled(ui->chkGaussian->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkColor_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDevColor->setEnabled(ui->chkColor->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkDoG_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDev1DoG->setEnabled(ui->chkDoG->isChecked()); + ui->txtStdDev2DoG->setEnabled(ui->chkDoG->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkLoG_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDevLoG->setEnabled(ui->chkLoG->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkHessian_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDevHessian->setEnabled(ui->chkHessian->isChecked()); + ui->lstTypeHessian->setEnabled(ui->chkHessian->isChecked()); +} + +void RandomForestFeaturesDialog::on_chkStructureTensor_stateChanged(int checkState) +{ + Q_UNUSED(checkState); + + ui->txtStdDevStructureTensor->setEnabled(ui->chkStructureTensor->isChecked()); + ui->lstTypeStructureTensor->setEnabled(ui->chkStructureTensor->isChecked()); +} diff --git a/Grinder/ui/ml/rf/RandomForestFeaturesDialog.h b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.h new file mode 100644 index 0000000..e56d375 --- /dev/null +++ b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * File: RandomForestFeaturesDialog.h + * Date: 13.1.2020 + *****************************************************************************/ + +#ifndef RANDOMFORESTFEATURESDIALOG_H +#define RANDOMFORESTFEATURESDIALOG_H + +#include <QDialog> +#include <QComboBox> + +#include "ml/rf/RandomForestFeatures.h" + +namespace Ui +{ + class RandomForestFeaturesDialog; +} + +namespace grndr +{ + class RandomForestFeaturesDialog : public QDialog + { + Q_OBJECT + + public: + RandomForestFeaturesDialog(RandomForestFeatures* features, QWidget *parent = nullptr); + ~RandomForestFeaturesDialog(); + + public: + virtual void accept() override; + + private: + void setupUi(); + void updateUi(bool save); + Ui::RandomForestFeaturesDialog *ui; + + private: + void fillGaborFilters(); + void fillMatrixTypes(QComboBox* comboBox); + + unsigned short getSelectedGaborFilters() const; + void setSelectedGaborFilters(unsigned short filters); + + RandomForestFeatures::MatrixFeatureType getSelectedMatrixType(QComboBox* comboBox) const; + void setSelectedMatrixType(QComboBox* comboBox, RandomForestFeatures::MatrixFeatureType type); + + private slots: + void on_chkGabor_stateChanged(int checkState = 0); + void on_chkGaussian_stateChanged(int checkState = 0); + void on_chkColor_stateChanged(int checkState = 0); + void on_chkDoG_stateChanged(int checkState = 0); + void on_chkLoG_stateChanged(int checkState = 0); + void on_chkHessian_stateChanged(int checkState = 0); + void on_chkStructureTensor_stateChanged(int checkState = 0); + + private: + RandomForestFeatures* _features{nullptr}; + }; +} + +#endif diff --git a/Grinder/ui/ml/rf/RandomForestFeaturesDialog.ui b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.ui new file mode 100644 index 0000000..803bee1 --- /dev/null +++ b/Grinder/ui/ml/rf/RandomForestFeaturesDialog.ui @@ -0,0 +1,734 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RandomForestFeaturesDialog</class> + <widget class="QDialog" name="RandomForestFeaturesDialog"> + <property name="windowModality"> + <enum>Qt::ApplicationModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>411</width> + <height>730</height> + </rect> + </property> + <property name="windowTitle"> + <string>Random Forest features</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Image features</string> + </property> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="5" column="0" colspan="3"> + <widget class="QWidget" name="widget_4" native="true"> + <layout class="QGridLayout" name="gridLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDevLoG"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Standard deviation:</string> + </property> + <property name="buddy"> + <cstring>txtStdDevLoG</cstring> + </property> + </widget> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="chkLoG"> + <property name="text"> + <string>&Laplacian of Gaussians</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="chkSobel"> + <property name="text"> + <string>&Sobel</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0" colspan="3"> + <widget class="QWidget" name="widget_2" native="true"> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDevColor"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Standard deviation:</string> + </property> + <property name="buddy"> + <cstring>txtStdDevColor</cstring> + </property> + </widget> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="chkColor"> + <property name="text"> + <string>&Color</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="4" column="0" colspan="3"> + <widget class="QWidget" name="widget_3" native="true"> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="1" column="0"> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Standard deviation 1:</string> + </property> + <property name="buddy"> + <cstring>txtStdDev1DoG</cstring> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDev1DoG"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Standard deviation 2:</string> + </property> + <property name="buddy"> + <cstring>txtStdDev2DoG</cstring> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDev2DoG"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="chkDoG"> + <property name="text"> + <string>&Difference of Gaussians</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QWidget" name="widget_7" native="true"> + <layout class="QGridLayout" name="gridLayout_9"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="1" column="1"> + <widget class="QListWidget" name="lstGabor"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="viewMode"> + <enum>QListView::IconMode</enum> + </property> + <property name="uniformItemSizes"> + <bool>true</bool> + </property> + <property name="itemAlignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_14"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QCheckBox" name="chkGabor"> + <property name="text"> + <string>Gabor &filters</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="0" colspan="3"> + <widget class="QWidget" name="widget_5" native="true"> + <layout class="QGridLayout" name="gridLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="5" column="1" colspan="2"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Standard deviation:</string> + </property> + <property name="buddy"> + <cstring>txtStdDevHessian</cstring> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QDoubleSpinBox" name="txtStdDevHessian"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="2" column="0"> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="4"> + <spacer name="horizontalSpacer_12"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>162</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="3" colspan="2"> + <widget class="QComboBox" name="lstTypeHessian"/> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Type:</string> + </property> + <property name="buddy"> + <cstring>lstTypeHessian</cstring> + </property> + </widget> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="chkHessian"> + <property name="text"> + <string>&Hessian</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="7" column="0" colspan="3"> + <widget class="QWidget" name="widget_6" native="true"> + <layout class="QGridLayout" name="gridLayout_7"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="2" column="0"> + <spacer name="horizontalSpacer_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Standard deviation:</string> + </property> + <property name="buddy"> + <cstring>txtStdDevStructureTensor</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Type:</string> + </property> + <property name="buddy"> + <cstring>lstTypeStructureTensor</cstring> + </property> + </widget> + </item> + <item row="0" column="0" colspan="5"> + <widget class="QCheckBox" name="chkStructureTensor"> + <property name="text"> + <string>&Structure Tensor</string> + </property> + </widget> + </item> + <item row="2" column="2" colspan="3"> + <widget class="QComboBox" name="lstTypeStructureTensor"/> + </item> + <item row="5" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDevStructureTensor"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="5" column="3" colspan="2"> + <spacer name="horizontalSpacer_13"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>34</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="3"> + <widget class="QWidget" name="widget" native="true"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="txtStdDevGaussian"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>999999.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.500000000000000</double> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Standard deviation:</string> + </property> + <property name="buddy"> + <cstring>txtStdDevGaussian</cstring> + </property> + </widget> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="chkGaussian"> + <property name="text"> + <string>&Gaussian</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>200</width> + <height>17</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <tabstops> + <tabstop>chkSobel</tabstop> + <tabstop>chkGaussian</tabstop> + <tabstop>txtStdDevGaussian</tabstop> + <tabstop>chkColor</tabstop> + <tabstop>txtStdDevColor</tabstop> + <tabstop>chkDoG</tabstop> + <tabstop>txtStdDev1DoG</tabstop> + <tabstop>txtStdDev2DoG</tabstop> + <tabstop>chkLoG</tabstop> + <tabstop>txtStdDevLoG</tabstop> + <tabstop>chkHessian</tabstop> + <tabstop>lstTypeHessian</tabstop> + <tabstop>txtStdDevHessian</tabstop> + <tabstop>chkStructureTensor</tabstop> + <tabstop>lstTypeStructureTensor</tabstop> + <tabstop>txtStdDevStructureTensor</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>RandomForestFeaturesDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>194</x> + <y>480</y> + </hint> + <hint type="destinationlabel"> + <x>13</x> + <y>445</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>RandomForestFeaturesDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>276</x> + <y>486</y> + </hint> + <hint type="destinationlabel"> + <x>246</x> + <y>450</y> + </hint> + </hints> + </connection> + </connections> +</ui> -- GitLab