diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index 39e27d49b9fe1b5debda114528907f189fb87ebb..87ffa1da5179f6e8351b83007885fa9122623d59 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -285,7 +285,9 @@ SOURCES += \ ui/image/tools/EllipseDraftItemTool.cpp \ ui/image/LayersListItemDelegate.cpp \ cv/algorithms/FloodFill.cpp \ - cv/algorithms/CVAlgorithm.cpp + cv/algorithms/CVAlgorithm.cpp \ + engine/processors/EdgesProcessor.cpp \ + pipeline/blocks/EdgesBlock.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -620,7 +622,9 @@ HEADERS += \ ui/image/LayersListItemDelegate.h \ cv/Pixel.impl.h \ cv/algorithms/FloodFill.h \ - cv/algorithms/CVAlgorithm.h + cv/algorithms/CVAlgorithm.h \ + engine/processors/EdgesProcessor.h \ + pipeline/blocks/EdgesBlock.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ diff --git a/Grinder/Version.h b/Grinder/Version.h index 9e6e54205e8cdb074fe9e7e07f6d6b92e21262f6..9de65af87c707ad032d632c2603afb5f577278ba 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 "13.07.2018" +#define GRNDR_INFO_DATE "16.07.2018" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 6 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 219 +#define GRNDR_VERSION_BUILD 220 namespace grndr { diff --git a/Grinder/common/properties/PropertyID.cpp b/Grinder/common/properties/PropertyID.cpp index f673f02e3825b9db6a87eed208d77ee9e115bde1..99de9b1e9c3223282be367096bd1f0f33b2410b3 100644 --- a/Grinder/common/properties/PropertyID.cpp +++ b/Grinder/common/properties/PropertyID.cpp @@ -11,6 +11,8 @@ const char* PropertyID::Default = "Default"; const char* PropertyID::ImageReference = "ImageReference"; const char* PropertyID::Threshold = "Threshold"; +const char* PropertyID::ThresholdLow = "ThresholdLow"; +const char* PropertyID::ThresholdHigh = "ThresholdHigh"; const char* PropertyID::TargetValue = "TargetValue"; const char* PropertyID::Alpha = "Alpha"; const char* PropertyID::Beta = "Beta"; diff --git a/Grinder/common/properties/PropertyID.h b/Grinder/common/properties/PropertyID.h index 73535a83090ce8ae52841ae0bdc8498eb607679c..6ef9489267d956a3fc308a5eafb11e1bcf8b579a 100644 --- a/Grinder/common/properties/PropertyID.h +++ b/Grinder/common/properties/PropertyID.h @@ -18,6 +18,8 @@ namespace grndr static const char* ImageReference; static const char* Threshold; + static const char* ThresholdLow; + static const char* ThresholdHigh; static const char* TargetValue; static const char* Alpha; static const char* Beta; diff --git a/Grinder/cv/CVUtils.cpp b/Grinder/cv/CVUtils.cpp index 69588466feeeda89a65c7a17e3201ceabdff65de..1332b1b4bb9a09adc812673df6665cebed4d0c40 100644 --- a/Grinder/cv/CVUtils.cpp +++ b/Grinder/cv/CVUtils.cpp @@ -76,6 +76,7 @@ std::vector<QColor> CVUtils::generateColors(unsigned int count, float saturation { QColor color; color.setHsvF(i * colorStep, saturation, value); + colors.push_back(color); } return colors; diff --git a/Grinder/engine/processors/EdgesProcessor.cpp b/Grinder/engine/processors/EdgesProcessor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f906e332951f15d8bf95e98e12f4517627fff0b7 --- /dev/null +++ b/Grinder/engine/processors/EdgesProcessor.cpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * File: EdgesProcessor.cpp + * Date: 16.7.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "EdgesProcessor.h" + +#include <opencv2/imgproc.hpp> + +EdgesProcessor::EdgesProcessor(const Block* block) : Processor(block) +{ + +} + +void EdgesProcessor::execute(EngineExecutionContext& ctx) +{ + Processor::execute(ctx); + + if (auto dataBlob = portData(ctx, _block->inPort())) + { + // Use Canny to detect edges + cv::Mat processedImage; + cv::Canny(dataBlob->getMatrix(), processedImage, *_block->thresholdLow(), *_block->thresholdHigh(), 3, true); + + ctx.setContextEntry(_block->outPort(), DataBlob{getPortDataDescriptor(_block->outPort()), std::move(processedImage), dataBlob->getColorSpace()}); + } +} diff --git a/Grinder/engine/processors/EdgesProcessor.h b/Grinder/engine/processors/EdgesProcessor.h new file mode 100644 index 0000000000000000000000000000000000000000..479b69ff0fe73b2d0af988c177aabf257e2c5fdc --- /dev/null +++ b/Grinder/engine/processors/EdgesProcessor.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * File: EdgesProcessor.h + * Date: 16.7.2018 + *****************************************************************************/ + +#ifndef EDGESPROCESSOR_H +#define EDGESPROCESSOR_H + +#include "engine/Processor.h" +#include "pipeline/blocks/EdgesBlock.h" + +namespace grndr +{ + class EdgesProcessor : public Processor<EdgesBlock> + { + public: + EdgesProcessor(const Block* block); + + public: + virtual void execute(EngineExecutionContext& ctx) override; + }; +} + +#endif diff --git a/Grinder/pipeline/BlockCatalog.cpp b/Grinder/pipeline/BlockCatalog.cpp index b9d77e552271db4f5f498b7301bc466428557e6f..f5c77610bb9f9085b6050a26db781b4651d8ac28 100644 --- a/Grinder/pipeline/BlockCatalog.cpp +++ b/Grinder/pipeline/BlockCatalog.cpp @@ -24,6 +24,7 @@ #include "blocks/DilateBlock.h" #include "blocks/ErodeBlock.h" #include "blocks/ContoursBlock.h" +#include "blocks/EdgesBlock.h" #include "blocks/WatershedBlock.h" #define REGISTER_BLOCK_TYPE(cls, desc) registerBlockType(cls::type_value, cls::category_value, [](Pipeline* pipeline, QString name) { return std::make_unique<cls>(pipeline, name); }, desc) @@ -128,6 +129,7 @@ void BlockCatalog::registerStandardBlocks() REGISTER_BLOCK_TYPE(DistanceTransformBlock, "Generates a map of the distances to the closest zero pixel for each pixel of an image."); REGISTER_BLOCK_TYPE(DilateBlock, "Dilates an image by using a specific structuring element."); REGISTER_BLOCK_TYPE(ErodeBlock, "Erodes an image by using a specific structuring element."); - REGISTER_BLOCK_TYPE(ContoursBlock, "Finds and draws contours in binary images."); + REGISTER_BLOCK_TYPE(ContoursBlock, "Finds contours in binary images."); + REGISTER_BLOCK_TYPE(EdgesBlock, "Finds edges in images."); REGISTER_BLOCK_TYPE(WatershedBlock, "Performs a marker-based segmentation using the Watershed algorithm."); } diff --git a/Grinder/pipeline/BlockCategory.cpp b/Grinder/pipeline/BlockCategory.cpp index eec8103d0a707bafe193c55e5fc2b57f1d2dc05c..54c7985220643a5ff3cba3cd028033d24a7dbdfb 100644 --- a/Grinder/pipeline/BlockCategory.cpp +++ b/Grinder/pipeline/BlockCategory.cpp @@ -16,6 +16,7 @@ const char* BlockCategory::Thresholding = "Thresholding"; const char* BlockCategory::Blending = "Blending"; const char* BlockCategory::Filtering = "Filtering"; const char* BlockCategory::Transformation = "Transformation"; +const char* BlockCategory::Contours = "Contours"; const char* BlockCategory::Segmentation = "Segmentation"; const char* BlockCategory::Annotation = "Annotation"; diff --git a/Grinder/pipeline/BlockCategory.h b/Grinder/pipeline/BlockCategory.h index 66fd151214d88a65d93dd7b063900739c26bbb5a..84a95e4ddb343aa4021b1a56e9de9525bf1ca1c5 100644 --- a/Grinder/pipeline/BlockCategory.h +++ b/Grinder/pipeline/BlockCategory.h @@ -27,6 +27,7 @@ namespace grndr static const char* Blending; static const char* Filtering; static const char* Transformation; + static const char* Contours; static const char* Segmentation; static const char* Annotation; diff --git a/Grinder/pipeline/BlockType.cpp b/Grinder/pipeline/BlockType.cpp index 359d55ea052d840a23b63fe117c9e3ee68a41ea0..9134da6ca46b4fa13f6009dfbfd8b7e07ae27a82 100644 --- a/Grinder/pipeline/BlockType.cpp +++ b/Grinder/pipeline/BlockType.cpp @@ -31,6 +31,8 @@ const char* BlockType::Dilate = "Dilate"; const char* BlockType::Erode = "Erode"; const char* BlockType::Contours = "Contours"; +const char* BlockType::Edges = "Edges"; + const char* BlockType::Watershed = "Watershed"; const char* BlockType::ImageTags = "ImageTags"; diff --git a/Grinder/pipeline/BlockType.h b/Grinder/pipeline/BlockType.h index 8d9aaddeec2582fdd1626a97d3bda4a199b7f96b..8f069195470b0ecc8bd09f985f4aa96a3bcb3c1e 100644 --- a/Grinder/pipeline/BlockType.h +++ b/Grinder/pipeline/BlockType.h @@ -38,6 +38,8 @@ namespace grndr static const char* Erode; static const char* Contours; + static const char* Edges; + static const char* Watershed; static const char* ImageTags; diff --git a/Grinder/pipeline/blocks/ContoursBlock.cpp b/Grinder/pipeline/blocks/ContoursBlock.cpp index f74fc6b8eaa6da7bd083ded0c843d9e3a2f47771..b5aa30f3f0a79a2b9764519ccfcb9214c2f486c1 100644 --- a/Grinder/pipeline/blocks/ContoursBlock.cpp +++ b/Grinder/pipeline/blocks/ContoursBlock.cpp @@ -8,7 +8,7 @@ #include "engine/processors/ContoursProcessor.h" const BlockType ContoursBlock::type_value = BlockType::Contours; -const BlockCategory ContoursBlock::category_value = BlockCategory::Segmentation; +const BlockCategory ContoursBlock::category_value = BlockCategory::Contours; ContoursBlock::ContoursBlock(Pipeline* pipeline, QString name) : Block(pipeline, type_value, category_value, name) { diff --git a/Grinder/pipeline/blocks/EdgesBlock.cpp b/Grinder/pipeline/blocks/EdgesBlock.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5cfad2f45dee456ff4aa47a943b12893146a0f9 --- /dev/null +++ b/Grinder/pipeline/blocks/EdgesBlock.cpp @@ -0,0 +1,43 @@ +/****************************************************************************** + * File: EdgesBlock.cpp + * Date: 16.7.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "EdgesBlock.h" +#include "engine/processors/EdgesProcessor.h" + +const BlockType EdgesBlock::type_value = BlockType::Edges; +const BlockCategory EdgesBlock::category_value = BlockCategory::Contours; + +EdgesBlock::EdgesBlock(Pipeline* pipeline, QString name) : Block(pipeline, type_value, category_value, name) +{ + +} + +std::unique_ptr<ProcessorBase> EdgesBlock::createProcessor() const +{ + return std::make_unique<EdgesProcessor>(this); +} + +void EdgesBlock::createProperties() +{ + Block::createProperties(); + + setPropertyGroup("General"); + + _thresholdLow = createProperty<UIntProperty>(PropertyID::ThresholdLow, "Lower threshold.", 100); + thresholdLow()->setDescription("Lower threshold for the hysteresis procedure."); + + _thresholdHigh = createProperty<UIntProperty>(PropertyID::ThresholdHigh, "Upper threshold.", 250); + thresholdHigh()->setDescription("Upper threshold for the hysteresis procedure."); +} + +void EdgesBlock::createPorts() +{ + DataDescriptors inPortDataDescs = {DataDescriptor::imageDescriptor(true), DataDescriptor::imageDescriptor(false)}; + _inPort = createPort(PortType::ImageIn, Port::Direction::In, inPortDataDescs, "In"); + + DataDescriptors outPortDataDescs = {DataDescriptor::imageDescriptor(false)}; + _outPort = createPort(PortType::ImageOut, Port::Direction::Out, outPortDataDescs, "Out"); +} diff --git a/Grinder/pipeline/blocks/EdgesBlock.h b/Grinder/pipeline/blocks/EdgesBlock.h new file mode 100644 index 0000000000000000000000000000000000000000..3b489312133e32c02f8ebff5fa025283e9002108 --- /dev/null +++ b/Grinder/pipeline/blocks/EdgesBlock.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * File: EdgesBlock.h + * Date: 16.7.2018 + *****************************************************************************/ + +#ifndef EDGESBLOCK_H +#define EDGESBLOCK_H + +#include "pipeline/Block.h" + +namespace grndr +{ + class EdgesBlock : public Block + { + Q_OBJECT + + public: + static const BlockType type_value; + static const BlockCategory category_value; + + public: + EdgesBlock(Pipeline* pipeline, QString name = ""); + + public: + virtual std::unique_ptr<ProcessorBase> createProcessor() const override; + + public: + auto thresholdLow() { return dynamic_cast<UIntProperty*>(_thresholdLow.get()); } + auto thresholdLow() const { return dynamic_cast<const UIntProperty*>(_thresholdLow.get()); } + auto thresholdHigh() { return dynamic_cast<UIntProperty*>(_thresholdHigh.get()); } + auto thresholdHigh() const { return dynamic_cast<const UIntProperty*>(_thresholdHigh.get()); } + + Port* inPort() { return _inPort.get(); } + const Port* inPort() const { return _inPort.get(); } + Port* outPort() { return _outPort.get(); } + const Port* outPort() const { return _outPort.get(); } + + protected: + virtual void createProperties() override; + virtual void createPorts() override; + + private: + std::shared_ptr<PropertyBase> _thresholdLow; + std::shared_ptr<PropertyBase> _thresholdHigh; + + std::shared_ptr<Port> _inPort; + std::shared_ptr<Port> _outPort; + }; +} + +#endif diff --git a/Grinder/ui/graph/GraphStyle.cpp b/Grinder/ui/graph/GraphStyle.cpp index 8209963fbbbde944fa19e13552f3fbf635ecddbf..6fe3f9faf4aa88c02f1fd191301a61eab2e3afbf 100644 --- a/Grinder/ui/graph/GraphStyle.cpp +++ b/Grinder/ui/graph/GraphStyle.cpp @@ -5,27 +5,18 @@ #include "Grinder.h" #include "GraphStyle.h" +#include "cv/CVUtils.h" QColor GraphStyle::BlockNodeStyle::getBlockCategoryColor(BlockCategory category) const { - if (category == BlockCategory::Input) - return QColor{220, 0, 20}; - else if (category == BlockCategory::Output) - return QColor{100, 0, 140}; - else if (category == BlockCategory::Conversion) - return QColor{0, 90, 150}; - else if (category == BlockCategory::Filtering) - return QColor{10, 40, 90}; - else if (category == BlockCategory::Thresholding) - return QColor{80, 220, 0}; - else if (category == BlockCategory::Transformation) - return QColor{20, 100, 10}; - else if (category == BlockCategory::Blending) - return QColor{40, 180, 200}; - else if (category == BlockCategory::Annotation) - return QColor{240, 200, 60}; - else if (category == BlockCategory::Segmentation) - return QColor{255, 140, 0}; + static const QStringList categories = {BlockCategory::Input, BlockCategory::Output, BlockCategory::Conversion, BlockCategory::Filtering, BlockCategory::Thresholding, BlockCategory::Transformation, + BlockCategory::Blending, BlockCategory::Annotation, BlockCategory::Contours, BlockCategory::Segmentation}; + static const auto colors = CVUtils::generateColors(categories.size(), 0.9f, 0.9f); + + auto index = categories.indexOf(category); + + if (index != -1) + return colors[index]; else return QColor{255, 255, 255}; }