diff --git a/Modules/Classification/CLActiveLearning/CMakeLists.txt b/Modules/Classification/CLActiveLearning/CMakeLists.txt new file mode 100644 index 0000000000..e6f95d84a6 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/CMakeLists.txt @@ -0,0 +1,7 @@ +MITK_CREATE_MODULE( +# INCLUDE_DIRS Algorithms Interactor ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS MitkCore MitkCLCore MitkSegmentation MitkCLUtilities #MitkCLVigraRandomForest + PACKAGE_DEPENDS PUBLIC ITK +) + +add_subdirectory(test) diff --git a/Modules/Classification/CLActiveLearning/files.cmake b/Modules/Classification/CLActiveLearning/files.cmake new file mode 100644 index 0000000000..07dc5017b8 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/files.cmake @@ -0,0 +1,17 @@ +file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") + +set(CPP_FILES + mitkAbstractClassifierPredictionFilter.cpp + mitkActiveLearningInteractor.cpp + mitkActiveLearningSuggestRegionFilter.cpp + mitkPredictionUncertaintyFilter.cpp + mitkPredictionConfidenceFilter.cpp + mitkPredictionEntropyFilter.cpp + mitkPredictionMarginFilter.cpp + mitkSelectHighestUncertaintyRegionFilter.cpp +) + +set(RESOURCE_FILES + Interactions/Paint.xml + Interactions/PaintConfig.xml +) diff --git a/Modules/Classification/CLActiveLearning/include/mitkAbstractClassifierPredictionFilter.h b/Modules/Classification/CLActiveLearning/include/mitkAbstractClassifierPredictionFilter.h new file mode 100644 index 0000000000..3dd52b27fd --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkAbstractClassifierPredictionFilter.h @@ -0,0 +1,72 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkAbstractClassifierPredictionFilter_h +#define mitkAbstractClassifierPredictionFilter_h + +#include + +// ITK +#include + +// MITK +#include +#include + +namespace mitk +{ + /** \class AbstractClassifierPredictionFilter + * \brief Filter that connects to a classifier and a data source and outputs the prediction for the data + * + * Inputs: + * 0 - Training Data + * 1 - A classifier that is based on mitk::AbstractClassifier + * + * Outputs: + * 0 - Prediction + * + * Proper conversion of input data to Eigen::MatrixXd should be implemented via MakeTrainingData() + */ + + class MITKCLACTIVELEARNING_EXPORT AbstractClassifierPredictionFilter : itk::ProcessObject + { + + public: + + mitkClassMacroItkParent(AbstractClassifierPredictionFilter, itk::ProcessObject) +// itkNewMacro(Self) + + protected: + + AbstractClassifierPredictionFilter(){} + ~AbstractClassifierPredictionFilter(){} + + virtual Eigen::MatrixXd MakeMatrixData(itk::DataObject* /*inputData*/) = 0; + + Eigen::MatrixXd MakeMatrixData(const Eigen::MatrixXd &inputData){return inputData;} + + virtual void GenerateData() override; + + private: + + // ITK examples do this... + AbstractClassifierPredictionFilter(const Self&); + void operator=(const Self&); + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h new file mode 100644 index 0000000000..5f0a33534c --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h @@ -0,0 +1,73 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkActiveLearningInteractor_h +#define mitkActiveLearningInteractor_h + +#include +#include +#include +#include + +#include + +namespace mitk +{ + +class MITKCLACTIVELEARNING_EXPORT ActiveLearningInteractor : public DataInteractor +{ + +public: + + typedef unsigned short AnnotationPixelType; + + mitkClassMacro(ActiveLearningInteractor, DataInteractor) + itkFactorylessNewMacro(Self) + + void SetPaintingPixelValue(AnnotationPixelType value){m_PaintingPixelValue = static_cast(value);} + + void SetSize(unsigned int size){m_Size = size;} + + bool IsUsed(){return m_Used;} + +private: + + ActiveLearningInteractor(); + ~ActiveLearningInteractor(); + + void ConnectActionsAndFunctions() override; + void DataNodeChanged() override; + + void Paint(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + + void PaintInterpolate(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + + void SetWorkingSlice(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + + void WriteBackWorkingSlice(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + + mitk::Image::Pointer m_WorkingSlice; + mitk::PlaneGeometry::Pointer m_WorkingPlane; + + unsigned int m_Size; + mitk::Point3D m_LastPosition; + AnnotationPixelType m_PaintingPixelValue; + bool m_Used; + +}; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningSuggestRegionFilter.h b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningSuggestRegionFilter.h new file mode 100644 index 0000000000..00282a18be --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningSuggestRegionFilter.h @@ -0,0 +1,167 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkActiveLearningSuggestRegionFilter_h +#define mitkActiveLearningSuggestRegionFilter_h + +#include + +// ITK +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// MITK +#include +#include + +namespace mitk +{ + /** \class ActiveLearningSuggestRegionFilter + * \brief Filter that takes an image of prediction uncertainties and outputs an image (mask) containing only the most uncertain region + * + * This is just a composite filter using itkThresholdImageFilter and mitkSelectHighestUncertaintyRegionFilter + */ + + template + class MITKCLACTIVELEARNING_EXPORT ActiveLearningSuggestRegionFilter : public itk::ImageToImageFilter + { + + public: + + typedef itk::ImageToImageFilter SuperClassName; + mitkClassMacroItkParent(ActiveLearningSuggestRegionFilter, SuperClassName) + itkNewMacro(Self) + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::PixelType OutputImagePixelType; + + // We're not holding threshold as a member, so no set and get macro + void SetThreshold(InputImagePixelType threshold) + { + if (m_ThresholdFilter->GetLowerThreshold() != threshold) + { + m_ThresholdFilter->SetLowerThreshold(threshold); + this->Modified(); + } + } + + InputImagePixelType GetThreshold() const + { + return m_ThresholdFilter->GetLowerThreshold(); + } + + protected: + + ActiveLearningSuggestRegionFilter() + { + m_GaussianFilter = itk::DiscreteGaussianImageFilter::New(); + m_ThresholdFilter = itk::BinaryThresholdImageFilter::New(); + m_CCFilter = itk::ConnectedComponentImageFilter::New(); + m_ThresholdOutputFilter = itk::BinaryThresholdImageFilter::New(); + m_OpeningFilter = itk::BinaryMorphologicalOpeningImageFilter::New(); + + m_ThresholdFilter->SetLowerThreshold(0.5); + m_ThresholdFilter->SetOutsideValue(0); + m_ThresholdFilter->SetInsideValue(1); + m_ThresholdOutputFilter->SetOutsideValue(0); + m_ThresholdOutputFilter->SetInsideValue(1); + } + + ~ActiveLearningSuggestRegionFilter(){} + + virtual void GenerateData() override + { + // Let this filter's input be threshold filter's input + auto input = InputImageType::New(); + input->Graft(const_cast(this->GetInput())); + + m_GaussianFilter->SetInput(input); + m_GaussianFilter->Modified(); + + m_ThresholdFilter->SetInput(m_GaussianFilter->GetOutput()); + m_ThresholdFilter->Modified(); + + StructuringElementType structuringElement; + structuringElement.SetRadius(5); + structuringElement.CreateStructuringElement(); + m_OpeningFilter->SetKernel(structuringElement); + m_OpeningFilter->SetInput(m_ThresholdFilter->GetOutput()); + m_OPeningFilter->Modified(); + + // Run pipeline to get image with connected components + m_CCFilter->SetInput(m_OpeningFilter->GetOutput()); + m_CCFilter->Update(); + + // Find sum of uncertainties for all connected components + int ccCount = static_cast(m_CCFilter->GetObjectCount()); + std::map ccSize; + for (int a=1; a<=ccCount; ++a) {ccSize[a] = 0.0;} + auto inputIt = itk::ImageRegionConstIterator(input, input->GetLargestPossibleRegion()); + auto outputIt = itk::ImageRegionConstIterator(m_CCFilter->GetOutput(), m_CCFilter->GetOutput()->GetLargestPossibleRegion()); + while (!outputIt.IsAtEnd()) + { + OutputImagePixelType ccVal = outputIt.Get(); + InputImagePixelType pixelVal = inputIt.Get(); + if (ccVal != 0) {ccSize[ccVal] += pixelVal;} + ++outputIt; + ++inputIt; + } + + // Find component with largest sum + typedef typename std::map::value_type PairType; + auto maxComponent = std::max_element(ccSize.begin(), ccSize.end(), [](const PairType& lhs, const PairType& rhs){return lhs.second < rhs.second;}); + + // Create mask + m_ThresholdOutputFilter->SetLowerThreshold(maxComponent->first); + m_ThresholdOutputFilter->SetUpperThreshold(maxComponent->first); + m_ThresholdOutputFilter->SetInput(m_CCFilter->GetOutput()); + m_ThresholdOutputFilter->GraftOutput(this->GetOutput()); + m_ThresholdOutputFilter->Update(); + this->GraftOutput(m_ThresholdOutputFilter->GetOutput()); + } + +// virtual void PrintSelf(std::ostream & os, itk::Indent indent) const +// { +// Superclass::PrintSelf(os, indent); +// m_ThresholdFilter->PrintSelf(os, indent); +// m_RegionFilter->PrintSelf(os, indent); +// } + + private: + + // ITK examples do this... + ActiveLearningSuggestRegionFilter(const Self&); + void operator=(const Self&); + + typename itk::DiscreteGaussianImageFilter::Pointer m_GaussianFilter; + typename itk::BinaryThresholdImageFilter::Pointer m_ThresholdFilter; + typename itk::ConnectedComponentImageFilter::Pointer m_CCFilter; + typename itk::BinaryThresholdImageFilter::Pointer m_ThresholdOutputFilter; + typedef itk::BinaryBallStructuringElement StructuringElementType; + typename itk::BinaryMorphologicalOpeningImageFilter::Pointer m_OpeningFilter; + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkPredictionConfidenceFilter.h b/Modules/Classification/CLActiveLearning/include/mitkPredictionConfidenceFilter.h new file mode 100644 index 0000000000..8315ab59a9 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkPredictionConfidenceFilter.h @@ -0,0 +1,65 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkPredictionConfidenceFilter_h +#define mitkPredictionConfidenceFilter_h + +#include + +#include +#include + +namespace mitk +{ + + template + class MITKCLACTIVELEARNING_EXPORT PredictionConfidenceFilter : public mitk::PredictionUncertaintyFilter + { + + public: + + typedef mitk::PredictionUncertaintyFilter SuperClassName; + mitkClassMacro(PredictionConfidenceFilter, SuperClassName) + itkNewMacro(Self) + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::PixelType OutputImagePixelType; + + protected: + + OutputImagePixelType CalculateUncertainty(const InputImagePixelType& inputVector) override + { + InputImagePixelType maxValue(0); + InputImagePixelType sum(0); // If input is not normalized + // Is there a better way to find the maximum of a itk::VariableLengthVector? + for (unsigned int i=0; i maxValue) maxValue = inputVector[i]; + } + // We could do something for non-normalized inputs, but this way a negative result indicates error + return static_cast(1 - maxValue / static_cast(sum)); // Double to not lose information + } + + private: + + // ITK examples do this... + PredictionConfidenceFilter(const Self&); + void operator=(const Self&); + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkPredictionEntropyFilter.h b/Modules/Classification/CLActiveLearning/include/mitkPredictionEntropyFilter.h new file mode 100644 index 0000000000..8672fe2f7d --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkPredictionEntropyFilter.h @@ -0,0 +1,74 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkPredictionEntropyFilter_h +#define mitkPredictionEntropyFilter_h + +#include + +#include +#include + +namespace mitk +{ + + template + class MITKCLACTIVELEARNING_EXPORT PredictionEntropyFilter : public mitk::PredictionUncertaintyFilter + { + + public: + + typedef mitk::PredictionUncertaintyFilter SuperClassName; + mitkClassMacro(PredictionEntropyFilter, SuperClassName) + itkNewMacro(Self) + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::PixelType OutputImagePixelType; + + protected: + + PredictionEntropyFilter(){} + ~PredictionEntropyFilter(){} + + OutputImagePixelType CalculateUncertainty(const InputImagePixelType& inputVector) override + { + unsigned int N = inputVector.Size(); + if (N == 1) {return 0;} + + OutputImagePixelType result(0); + OutputImagePixelType sum(0); + for (unsigned int i=0; i(inputVector[i]); +// if (p < m_Epsilon) {p = m_Epsilon;} + p = std::max(p, m_Epsilon); + result -= p * std::log(p); // The result is supposed to be negative + } + result = result / static_cast(std::log(N / static_cast(sum)) * sum); // Normalize + return result; + } + + private: + + OutputImagePixelType m_Epsilon = 0.000001; + + // ITK examples do this... + PredictionEntropyFilter(const Self&); + void operator=(const Self&); + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkPredictionMarginFilter.h b/Modules/Classification/CLActiveLearning/include/mitkPredictionMarginFilter.h new file mode 100644 index 0000000000..bafa6137f6 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkPredictionMarginFilter.h @@ -0,0 +1,72 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkPredictionMarginFilter_h +#define mitkPredictionMarginFilter_h + +#include + +#include +#include + +namespace mitk +{ + + template + class MITKCLACTIVELEARNING_EXPORT PredictionMarginFilter : public mitk::PredictionUncertaintyFilter + { + + public: + + typedef mitk::PredictionUncertaintyFilter SuperClassName; + mitkClassMacro(PredictionMarginFilter, SuperClassName) + itkNewMacro(Self) + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::PixelType OutputImagePixelType; + + protected: + + OutputImagePixelType CalculateUncertainty(const InputImagePixelType& inputVector) override + { + InputImagePixelType firstMax(0); + InputImagePixelType secondMax(0); + InputImagePixelType sum(0); // If input is not normalized + for (unsigned int i=0; i firstMax) + { + secondMax = firstMax; + firstMax = inputVector[i]; + } + else if (inputVector[i] > secondMax) + { + secondMax = inputVector[i]; + } + return static_cast(1 - (firstMax - secondMax) / static_cast(sum)); // Double to not lose information + } + } + + private: + + // ITK examples do this... + PredictionMarginFilter(const Self&); + void operator=(const Self&); + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkPredictionUncertaintyFilter.h b/Modules/Classification/CLActiveLearning/include/mitkPredictionUncertaintyFilter.h new file mode 100644 index 0000000000..376da26e78 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkPredictionUncertaintyFilter.h @@ -0,0 +1,90 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkPredictionUncertaintyFilter_h +#define mitkPredictionUncertaintyFilter_h + +#include + +#include + +// ITK +#include +#include +#include +#include +#include + +namespace mitk +{ + /** \class PredictionUncertaintyFilter + * \brief Base class for filters that calculate an uncertainty measure on a vector image + * + * The input image should be a vector image. For each vector, the entries represent class + * probabilities. From these an uncertainty is calculated, so that one vector of probabilities + * will result in a scalar uncertainty. + * Derivative classes will need to implement CalculateUncertainty. + */ + + template + class MITKCLACTIVELEARNING_EXPORT PredictionUncertaintyFilter : public itk::ImageToImageFilter + { + + public: + + typedef itk::ImageToImageFilter SuperClassName; + mitkClassMacroItkParent(PredictionUncertaintyFilter, SuperClassName) +// itkNewMacro(Self) + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::PixelType OutputImagePixelType; + typedef typename OutputImageType::RegionType OutputImageRegionType; + + protected: + + PredictionUncertaintyFilter(){} + ~PredictionUncertaintyFilter(){} + + virtual OutputImagePixelType CalculateUncertainty(const InputImagePixelType& inputVector) = 0; + + void GenerateData() override +// virtual void ThreadedGenerateData(const OutputImageRegionType& outputRegionForThread, itk::ThreadIdType threadId) override + { + auto input = this->GetInput(); // const + auto output = this->GetOutput(); + +// itk::ImageRegionConstIterator inputIt(input, outputRegionForThread); +// itk::ImageRegionIterator outputIt(output, outputRegionForThread); + itk::ImageRegionConstIterator inputIt(input, input->GetLargestPossibleRegion()); + itk::ImageRegionIterator outputIt(output, output->GetLargestPossibleRegion()); + + while (!outputIt.IsAtEnd()) + { + outputIt.Set(CalculateUncertainty(inputIt.Get())); + ++inputIt; + ++outputIt; + } + } + + private: + + // ITK examples do this... + PredictionUncertaintyFilter(const Self&); + void operator=(const Self&); + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/include/mitkSelectHighestUncertaintyRegionFilter.h b/Modules/Classification/CLActiveLearning/include/mitkSelectHighestUncertaintyRegionFilter.h new file mode 100644 index 0000000000..44a3912c24 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkSelectHighestUncertaintyRegionFilter.h @@ -0,0 +1,82 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkSelectHighestUncertaintyRegionFilter_h +#define mitkSelectHighestUncertaintyRegionFilter_h + +#include + +#include + +// ITK +#include +#include + +namespace mitk +{ + /** \class SelectHighestUncertaintyRegionFilter + * \brief This class will black out everything that is not part of the region with the highest uncertainty + * + * Expects an already thresholded image (i.e. values smaller threshold = 0). Will black out everything that does + * not belong to the region with the highest sum of uncertainties. + */ + + template + class MITKCLACTIVELEARNING_EXPORT SelectHighestUncertaintyRegionFilter : public itk::LabelShapeKeepNObjectsImageFilter + { + + public: + + typedef itk::LabelShapeKeepNObjectsImageFilter SuperClassName; + typedef typename itk::LabelShapeKeepNObjectsImageFilter::AttributeType AttributeType; + mitkClassMacroItkParent(SelectHighestUncertaintyRegionFilter, SuperClassName) + itkNewMacro(Self) + + // We want the attribute to be fixed + void SetAttribute(AttributeType) override {} + void SetAttribute(const std::string&){} + + protected: + + SelectHighestUncertaintyRegionFilter() + { + m_Attribute = Superclass::LabelObjectType::PHYSICAL_SIZE; + Superclass::SetAttribute(m_Attribute); + Superclass::SetBackgroundValue(0); + Superclass::SetNumberOfObjects(1); + Superclass::SetReverseOrdering(false); + } + + ~SelectHighestUncertaintyRegionFilter(){} + + virtual void PrintSelf(std::ostream & os, itk::Indent indent) const + { + Superclass::PrintSelf(os, indent); + os << indent << "Attribute: " << "PhysicalSize" << std::endl; + } + + private: + + // ITK examples do this... + SelectHighestUncertaintyRegionFilter(const Self&); + void operator=(const Self&); + + AttributeType m_Attribute; + + }; + +} + +#endif diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml new file mode 100644 index 0000000000..4e04105daf --- /dev/null +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml new file mode 100644 index 0000000000..88714694d1 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Classification/CLActiveLearning/src/mitkAbstractClassifierPredictionFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkAbstractClassifierPredictionFilter.cpp new file mode 100644 index 0000000000..523bf99e5f --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkAbstractClassifierPredictionFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp new file mode 100644 index 0000000000..3d95c17cd4 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp @@ -0,0 +1,557 @@ + +/*=================================================================== +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. +===================================================================*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Helper function to get an image from a data node. +static mitk::Image::Pointer GetImage(mitk::DataNode::Pointer dataNode) +{ + if (dataNode.IsNull()) + mitkThrow(); + + mitk::Image::Pointer image = dynamic_cast(dataNode->GetData()); + + if (image.IsNull()) + mitkThrow(); + + return image; +} + +// Helper function to get a geometry of an image for a specific time step. +static mitk::BaseGeometry::Pointer GetGeometry(mitk::Image* image, unsigned int timeStep) +{ + if (image == nullptr) + mitkThrow(); + + mitk::TimeGeometry::Pointer timeGeometry = image->GetTimeGeometry(); + + if (timeGeometry.IsNull()) + mitkThrow(); + + auto geometry = timeGeometry->GetGeometryForTimeStep(timeStep); + + if (geometry.IsNull()) + mitkThrow(); + + return geometry; +} + +static double EuclideanDistance(const mitk::Point3D p1, const mitk::Point3D p2) +{ + return std::sqrt( (p1[0] - p2[0])*(p1[0] - p2[0]) + + (p1[1] - p2[1])*(p1[1] - p2[1]) + + (p1[2] - p2[2])*(p1[2] - p2[2]) ); +} + +static double EuclideanDistance2D(const mitk::Point2D p1, const mitk::Point2D p2) +{ + return std::sqrt( (p1[0] - p2[0])*(p1[0] - p2[0]) + + (p1[1] - p2[1])*(p1[1] - p2[1]) ); +} + +static double ScalarProduct(const mitk::Point3D p1, const mitk::Point3D p2) +{ + return p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2]; +} + +static double ScalarProduct2D(const mitk::Point2D p1, const mitk::Point2D p2) +{ + return p1[0]*p2[0] + p1[1]*p2[1]; +} + +static std::vector> InterpolateIndices2D(itk::Index<2> startIndex, itk::Index<2> endIndex, const mitk::PlaneGeometry* geometry, unsigned int size) +{ + if (geometry == nullptr) + mitkThrow(); + + std::vector> resultIndices; + mitk::Point2D startPoint; + mitk::Point2D endPoint; + startPoint[0] = startIndex[0]; + startPoint[1] = startIndex[1]; + endPoint[0] = endIndex[0]; + endPoint[1] = endIndex[1]; + geometry->IndexToWorld(startPoint, startPoint); + geometry->IndexToWorld(endPoint, endPoint); + + // Distance between end points + double dist = EuclideanDistance2D(startPoint, endPoint); + + // Define region between startIndex and endIndex, padded by (size - 1) + int regionBounds[4]; + for (int i=0; i<2; ++i) + { + regionBounds[2*i] = std::min(startIndex[i], endIndex[i]) - (size - 1); + regionBounds[2*i+1] = std::max(startIndex[i], endIndex[i]) + (size - 1); + } + + // We only have a threshold given in pixels (size), but image can be spaced in different units. + // To get the corresponding distances, transform unit vectors and get their lengths. + // The minimum spacing will be what we compare to. + double spacings[2]; + spacings[0] = geometry->GetExtentInMM(0) / geometry->GetExtent(0); + spacings[1] = geometry->GetExtentInMM(1) / geometry->GetExtent(1); + double minSpacing = (spacings[0] > spacings[1]) ? spacings[1] : spacings[0]; + + MITK_INFO << "Interpolating between " << startIndex << " and " << endIndex; + MITK_INFO << "Distance: " << dist; + MITK_INFO << "Minimum extent: " << minSpacing; + + double t = 0; + double d = 0; + for (int x = regionBounds[0]; x<=regionBounds[1]; ++x) + { + for (int y = regionBounds[2]; y<=regionBounds[3]; ++y) + { + mitk::Point2D p; + mitk::Point2D index; + index[0] = x; + index[1] = y; + geometry->IndexToWorld(index, p); + + // if there is no distance between start and end, just get distance to start + if (dist < mitk::eps) + { + d = EuclideanDistance2D(startPoint, p); + } + else + { + t = -1./(dist*dist) * ScalarProduct2D(startPoint - p, endPoint - startPoint); + if (t > 0. && t < 1.) + { + d = std::sqrt( ScalarProduct2D(startPoint - p, startPoint - p) + + 2. * t * ScalarProduct2D(endPoint - startPoint, startPoint - p) + + t * t * dist * dist ); + } + else if (t <= 0.) + { + d = EuclideanDistance2D(startPoint, p); + } + else + { + d = EuclideanDistance2D(endPoint, p); + } + } + + if (d <= minSpacing * static_cast(size) / 2.) + { + resultIndices.push_back(itk::Index<2>({index[0], index[1]})); + } + } + } + + return resultIndices; +} + +static std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, const mitk::BaseGeometry* geometry, unsigned int size) +{ + if (geometry == nullptr) + mitkThrow(); + + std::vector> resultIndices; + mitk::Point3D startPoint; + mitk::Point3D endPoint; + geometry->IndexToWorld(startIndex, startPoint); + geometry->IndexToWorld(endIndex, endPoint); + + // Distance between end points + double dist = EuclideanDistance(startPoint, endPoint); + + // Define region between startIndex and endIndex, padded by (size - 1) + int regionBounds[6]; + for (int i=0; i<3; ++i) + { + regionBounds[2*i] = std::min(startIndex[i], endIndex[i]) - (size - 1); + regionBounds[2*i+1] = std::max(startIndex[i], endIndex[i]) + (size - 1); + } + + // We only have a threshold given in pixels (size), but image can be spaced in different units. + // To get the corresponding distances, transform unit vectors and get their lengths. + // The minimum spacing will be what we compare to. + // Could potentially use the spacing to normalize. + double spacingInIndexSystem[3]; + double minSpacing = -1.; + for (int i=0; i<3; ++i) + { + itk::Index<3> origin; + origin.Fill(0); + itk::Index<3> index; + index.Fill(0); + index[i] = 1; + mitk::Point3D p_origin; + mitk::Point3D p_index; + geometry->IndexToWorld(origin, p_origin); + geometry->IndexToWorld(index, p_index); + double spacing = EuclideanDistance(p_origin, p_index); + if ( (minSpacing > 0. && spacing < minSpacing) || minSpacing < 0. ) + { + minSpacing = spacing; + } + spacingInIndexSystem[i] = spacing; + } + + // Iterate over all indices in the given region and get distance to the line between startPoint and endPoint. + // If distance is smaller than size, add to resultIndices. + // + // Let (x1,y1,z1) = startPoint, (x2,y2,z2) = endPoint, (x0,y0,z0) = p a point. + // + // Line is defined by: + // [x1 + (x2-x1) * t] + // v = [y1 + (y2-y1) * t] + // [z1 + (z2-z1) * t] + // + // Then (with * dot product): + // t(p) = - (startPoint - p) * (endPoint - startPoint) / |endPoint - startPoint|^2 + // + // And (with x cross product): + // d(p) = |(p - startPoint) x (p - endPoint)| / |endPoint - startPoint| + + double t = 0; + double d = 0; + for (int x = regionBounds[0]; x<=regionBounds[1]; ++x) + { + for (int y = regionBounds[2]; y<=regionBounds[3]; ++y) + { + for (int z = regionBounds[4]; z<=regionBounds[5]; ++z) + { + mitk::Point3D p; + itk::Index<3> index = {x,y,z}; + geometry->IndexToWorld(index, p); + + // if there is not distance between start and end, just get distance to start + if (dist < mitk::eps) + { + d = EuclideanDistance(startPoint, p); + } + else + { + t = -1./(dist*dist) * ScalarProduct(startPoint - p, endPoint - startPoint); + if (t > 0. && t < 1.) + { + d = std::sqrt( ScalarProduct(startPoint - p, startPoint - p) + + 2. * t * ScalarProduct(endPoint - startPoint, startPoint - p) + + t * t * dist * dist ); + } + else if (t <= 0.) + { + d = EuclideanDistance(startPoint, p); + } + else + { + d = EuclideanDistance(endPoint, p); + } + } + + if (d <= minSpacing) + { + resultIndices.push_back(index); + } + } + } + } + + return resultIndices; + +// ======= OLD INTERPOLATION ======== + +// std::vector> resultIndices; +// mitk::Point3D startPoint; +// mitk::Point3D endPoint; +// geometry->IndexToWorld(startIndex, startPoint); +// geometry->IndexToWorld(endIndex, endPoint); + +// itk::Index<3> indexDelta; +// int indexDeltaInc[3]; +// for (int i=0; i<3; i++) +// { +// indexDelta[i] = endIndex[i] - startIndex[i]; +// indexDeltaInc[i] = (indexDelta[i] > 0) ? 1 : (indexDelta[i] < 0) ? -1 : 0; +// } + +// int argm[3] = {0, 1, 2}; +// if (abs(indexDelta[1]) > abs(indexDelta[0])) +// { +// argm[0] = 1; +// argm[1] = 0; +// } +// if (abs(indexDelta[2]) > abs(indexDelta[argm[1]])) +// { +// argm[2] = argm[1]; +// argm[1] = 2; +// } +// if (abs(indexDelta[2]) > abs(indexDelta[argm[0]])) +// { +// argm[1] = argm[0]; +// argm[0] = 2; +// } + +// double slopes[2]; +// slopes[0] = (endPoint[argm[1]] - startPoint[argm[1]]) / (endPoint[argm[0]] - startPoint[argm[0]]); +// slopes[1] = (endPoint[argm[2]] - startPoint[argm[2]]) / sqrt((endPoint[argm[1]] - startPoint[argm[1]]) * (endPoint[argm[1]] - startPoint[argm[1]]) + (endPoint[argm[0]] - startPoint[argm[0]]) * (endPoint[argm[0]] - startPoint[argm[0]])); +// itk::Index<3> currentIndex = startIndex; +// mitk::Point3D currentPoint = startPoint; + +// while (currentIndex != endIndex) +// { +// currentIndex[argm[0]] += indexDeltaInc[argm[0]]; +// geometry->IndexToWorld(currentIndex, currentPoint); +// currentPoint[argm[1]] = startPoint[argm[1]] + slopes[0] * (currentPoint[argm[0]] - startPoint[argm[0]]); +// currentPoint[argm[2]] = startPoint[argm[2]] + slopes[1] * sqrt((currentPoint[argm[1]] - startPoint[argm[1]]) * (currentPoint[argm[1]] - startPoint[argm[1]]) + (currentPoint[argm[0]] - startPoint[argm[0]]) * (currentPoint[argm[0]] - startPoint[argm[0]])); +// geometry->WorldToIndex(currentPoint, currentIndex); +// resultIndices.push_back(currentIndex); +// } + +// return resultIndices; +} + +mitk::ActiveLearningInteractor::ActiveLearningInteractor() : + m_WorkingSlice(nullptr), + m_WorkingPlane(nullptr), + m_Size(1), + m_PaintingPixelValue(0) +{ +} + +mitk::ActiveLearningInteractor::~ActiveLearningInteractor() +{ +} + +void mitk::ActiveLearningInteractor::ConnectActionsAndFunctions() +{ + CONNECT_FUNCTION("paint", Paint) + CONNECT_FUNCTION("paint_interpolate", PaintInterpolate) + CONNECT_FUNCTION("set_workingslice", SetWorkingSlice) + CONNECT_FUNCTION("writeback_workingslice", WriteBackWorkingSlice) +} + +void mitk::ActiveLearningInteractor::DataNodeChanged() +{ + this->ResetToStartState(); +} + +void mitk::ActiveLearningInteractor::SetWorkingSlice(mitk::StateMachineAction* /*action*/, + mitk::InteractionEvent *event) +{ + try + { + auto renderer = event->GetSender(); + auto image = GetImage(this->GetDataNode()); + auto timeStep = renderer->GetTimeStep(); + auto geometry = GetGeometry(image, timeStep); + auto planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); + + // Check current plane geometry + if (m_WorkingPlane.IsNotNull()) + { + bool isSameSlice (true); + isSameSlice &= mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(), + m_WorkingPlane->GetIndexToWorldTransform()->GetMatrix()); + isSameSlice &= mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(), + m_WorkingPlane->GetIndexToWorldTransform()->GetOffset()); + if (isSameSlice) return; + } + m_WorkingPlane = renderer->GetCurrentWorldPlaneGeometry()->Clone(); + + // Extract corresponding slice + vtkSmartPointer reslice = vtkSmartPointer::New(); + reslice->SetOverwriteMode(false); + reslice->Modified(); + auto extract = mitk::ExtractSliceFilter::New(reslice); + extract->SetInput(image); + extract->SetTimeStep(timeStep); + extract->SetWorldGeometry(m_WorkingPlane); + extract->SetResliceTransformByGeometry(geometry); + extract->SetVtkOutputRequest(false); + extract->Modified(); + extract->Update(); + m_WorkingSlice = extract->GetOutput(); + } + catch (itk::ExceptionObject& e) + { + mitkThrow() << "Could not set working slice, because:"; + mitkThrow() << e.GetDescription(); + } +} + +void mitk::ActiveLearningInteractor::WriteBackWorkingSlice(mitk::StateMachineAction* /*action*/, + mitk::InteractionEvent *event) +{ + try + { + auto renderer = event->GetSender(); + auto image = GetImage(this->GetDataNode()); + auto timeStep = renderer->GetTimeStep(); + auto geometry = GetGeometry(image, timeStep); + + // Write back + vtkSmartPointer reslice = vtkSmartPointer::New(); + reslice->SetInputSlice(m_WorkingSlice->GetVtkImageData()); + reslice->SetOverwriteMode(true); + reslice->Modified(); + mitk::ExtractSliceFilter::Pointer insert = mitk::ExtractSliceFilter::New(reslice); + insert->SetInput(image); + insert->SetTimeStep(timeStep); + insert->SetWorldGeometry(m_WorkingPlane); + insert->SetResliceTransformByGeometry(geometry); + insert->Modified(); + insert->Update(); + + image->Modified(); + this->GetDataNode()->Modified(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + catch (itk::ExceptionObject& e) + { + mitkThrow() << "Could not set working slice, because:"; + mitkThrow() << e.GetDescription(); + } +} + +void mitk::ActiveLearningInteractor::Paint(mitk::StateMachineAction* /*action*/, + mitk::InteractionEvent* event) +{ + if (m_PaintingPixelValue == -1) mitkThrow() << "Cannot paint negative values"; + + try + { + auto renderer = event->GetSender(); + auto image = GetImage(this->GetDataNode()); + auto timeStep = renderer->GetTimeStep(); + auto geometry = GetGeometry(image, timeStep); + auto positionEvent = dynamic_cast(event); + auto position = positionEvent->GetPositionInWorld(); + + if (!geometry->IsInside(position)) return; + + // Okay, we're safe. Convert the mouse position to the index of the pixel + // we're pointing at. + itk::Index<3> index; + itk::Index<3> oldIndex; + geometry->WorldToIndex(position, index); + geometry->WorldToIndex(m_LastPosition, oldIndex); + + // We don't need to paint over and over again while moving the mouse + // pointer inside the same pixel. That's especially relevant when operating + // on zoomed images. + if (index != oldIndex) + { + // Convert index to slice geometry + m_WorkingSlice->GetGeometry()->WorldToIndex(position, index); + itk::Index<2> indexInPlane2D; + indexInPlane2D[0] = index[0]; + indexInPlane2D[1] = index[1]; + + // Get indices + auto indices = InterpolateIndices2D(indexInPlane2D, indexInPlane2D, m_WorkingPlane, m_Size); + + // Fill indices + mitk::ImagePixelWriteAccessor writeAccessor(m_WorkingSlice, m_WorkingSlice->GetSliceData(0)); + + for (auto i : indices) + { + writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue); + } + + m_LastPosition = position; + m_Used = true; + } + } + catch (itk::ExceptionObject& e) + { + mitkThrow() << "Could not paint, because:"; + mitkThrow() << e.GetDescription(); + } +} + +void mitk::ActiveLearningInteractor::PaintInterpolate(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event) +{ + if (m_PaintingPixelValue == -1) mitkThrow() << "Cannot paint negative values"; + + try + { + auto renderer = event->GetSender(); + auto image = GetImage(this->GetDataNode()); + auto timeStep = renderer->GetTimeStep(); + auto geometry = GetGeometry(image, timeStep); + auto positionEvent = dynamic_cast(event); + auto position = positionEvent->GetPositionInWorld(); + + if (!geometry->IsInside(position)) return; + + // Okay, we're safe. Convert the mouse position to the index of the pixel + // we're pointing at. + itk::Index<3> index; + itk::Index<3> oldIndex; + geometry->WorldToIndex(position, index); + geometry->WorldToIndex(m_LastPosition, oldIndex); + + MITK_INFO << "World: " << m_LastPosition << " to " << position; + MITK_INFO << "Index: " << oldIndex << " to " << index; + + // We don't need to paint over and over again while moving the mouse + // pointer inside the same pixel. That's especially relevant when operating + // on zoomed images. + if (index != oldIndex) + { + // Convert index to slice geometry + m_WorkingSlice->GetGeometry()->WorldToIndex(position, index); + m_WorkingSlice->GetGeometry()->WorldToIndex(m_LastPosition, oldIndex); + + MITK_INFO << "Plane: " << oldIndex << " to " << index; + MITK_INFO << "Slice spacing: " << m_WorkingSlice->GetGeometry()->GetSpacing(); + MITK_INFO << "Plane spacing: " << m_WorkingPlane->GetSpacing(); + + itk::Index<2> indexInPlane2D; + itk::Index<2> oldIndexInPlane2D; + indexInPlane2D[0] = index[0]; + indexInPlane2D[1] = index[1]; + oldIndexInPlane2D[0] = oldIndex[0]; + oldIndexInPlane2D[1] = oldIndex[1]; + + // Get indices + auto indices = InterpolateIndices2D(oldIndexInPlane2D, indexInPlane2D, m_WorkingPlane, m_Size); + + // Fill indices + mitk::ImagePixelWriteAccessor writeAccessor(m_WorkingSlice, m_WorkingSlice->GetSliceData(0)); + for (auto i : indices) + { + writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue); + } + + m_LastPosition = position; + m_Used = true; + } + } + catch (itk::ExceptionObject& e) + { + mitkThrow() << "Could not paint with interpolation, because:"; + mitkThrow() << e.GetDescription(); + } +} diff --git a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningSuggestRegionFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningSuggestRegionFilter.cpp new file mode 100644 index 0000000000..0914554eac --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningSuggestRegionFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkPredictionConfidenceFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkPredictionConfidenceFilter.cpp new file mode 100644 index 0000000000..3072d0df8e --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkPredictionConfidenceFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkPredictionEntropyFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkPredictionEntropyFilter.cpp new file mode 100644 index 0000000000..806da4b109 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkPredictionEntropyFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkPredictionMarginFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkPredictionMarginFilter.cpp new file mode 100644 index 0000000000..438be2f1ab --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkPredictionMarginFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkPredictionUncertaintyFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkPredictionUncertaintyFilter.cpp new file mode 100644 index 0000000000..33cd25ae4b --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkPredictionUncertaintyFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/src/mitkSelectHighestUncertaintyRegionFilter.cpp b/Modules/Classification/CLActiveLearning/src/mitkSelectHighestUncertaintyRegionFilter.cpp new file mode 100644 index 0000000000..b6614d1236 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkSelectHighestUncertaintyRegionFilter.cpp @@ -0,0 +1,16 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include diff --git a/Modules/Classification/CLActiveLearning/test/CMakeLists.txt b/Modules/Classification/CLActiveLearning/test/CMakeLists.txt new file mode 100644 index 0000000000..b4f2e87ef6 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/test/CMakeLists.txt @@ -0,0 +1,5 @@ +MITK_CREATE_MODULE_TESTS() + +if(TARGET ${TESTDRIVER}) + mitk_use_modules(TARGET ${TESTDRIVER} PACKAGES ITK) +endif() diff --git a/Modules/Classification/CLActiveLearning/test/files.cmake b/Modules/Classification/CLActiveLearning/test/files.cmake new file mode 100644 index 0000000000..e158247577 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/test/files.cmake @@ -0,0 +1,3 @@ +set(MODULE_TESTS + mitkActiveLearningTest.cpp +) diff --git a/Modules/Classification/CLActiveLearning/test/mitkActiveLearningTest.cpp b/Modules/Classification/CLActiveLearning/test/mitkActiveLearningTest.cpp new file mode 100644 index 0000000000..474f7d1d50 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/test/mitkActiveLearningTest.cpp @@ -0,0 +1,267 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +// ITK +#include +#include +#include +#include +#include +#include +#include +#include + +// MITK +#include +#include +#include +#include + +// To be tested +#include +#include +#include +#include +#include + + +class mitkActiveLearningTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkActiveLearningTestSuite); + MITK_TEST(PredictionEntropyFilterTest); + MITK_TEST(ActiveLearningSuggestRegionFilterTest); + CPPUNIT_TEST_SUITE_END(); + +public: + + typedef itk::Image ImageType; + typedef itk::Image BinaryImageType; + typedef itk::VectorImage VectorImageType; + typedef typename ImageType::PixelType PixelType; + typedef typename ImageType::IndexType IndexType; + typedef typename ImageType::RegionType RegionType; + typedef typename VectorImageType::IndexType VectorIndexType; + typedef itk::VariableLengthVector VectorType; + + void setUp() + { + // Create random test image + int dim = 100; + itk::Size<3> size; + size.Fill(dim); + auto randImageSource = itk::RandomImageSource::New(); + randImageSource->SetNumberOfThreads(1); // so results are not random + randImageSource->SetSize(size); + randImageSource->Update(); + m_TestImageRandom = randImageSource->GetOutput(); + + // Create gradient test image + dim = 100; + size.Fill(dim); + RegionType region; + region.SetSize(size); + m_TestImageGradient = ImageType::New(); + m_TestImageGradient->SetRegions(region); + m_TestImageGradient->Allocate(); + + auto it = itk::ImageRegionIterator(m_TestImageGradient, m_TestImageGradient->GetLargestPossibleRegion()); + PixelType val(0); + + while (!it.IsAtEnd()) + { + auto index = it.GetIndex(); + float max = static_cast(dim); + float sum = index[0] + index[1] + index[2]; + + // Linear gradient [0, 1] + // val = sum / (3*max); + + // soft spheres + if ((index[0] / max) > 0.5 && (index[1] / max) > 0.5 && (index[2] / max) > 0.5) + { + val = (-std::sin(2 * 3.14 * index[0] / max) * std::sin(2 * 3.14 * index[1] / max) * std::sin(2 * 3.14 * index[2] / max) + 1) / 2.; + } + else + { + val = (std::sin(4 * 3.14 * index[0] / max) * std::sin(4 * 3.14 * index[1] / max) * std::sin(4 * 3.14 * index[2] / max) + 1) / 2.; + } + + it.Set(static_cast(val)); + ++it; + } + + // Create vector image & fill manually + dim = 2; + size.Fill(dim); + region.SetSize(size); + m_TestImageVector = VectorImageType::New(); + m_TestImageVector->SetRegions(region); + m_TestImageVector->SetVectorLength(3); + m_TestImageVector->Allocate(); + + VectorIndexType index; + VectorType vector; + vector.SetSize(3); + + // Absolute certainty (should be 0.0) + index[0] = 0; index[1] = 0; index[2] = 0; + vector[0] = 1.0; vector[1] = 0.0; vector[2] = 0.0; + m_TestImageVector->SetPixel(index, vector); + + // certain + index[0] = 0; index[1] = 0; index[2] = 1; + vector[0] = 0.9; vector[1] = 0.05; vector[2] = 0.05; + m_TestImageVector->SetPixel(index, vector); + + // fairly certain + index[0] = 0; index[1] = 1; index[2] = 0; + vector[0] = 0.6; vector[1] = 0.3; vector[2] = 0.1; + m_TestImageVector->SetPixel(index, vector); + + // zero margin + index[0] = 0; index[1] = 1; index[2] = 1; + vector[0] = 0.45; vector[1] = 0.45; vector[2] = 0.1; + m_TestImageVector->SetPixel(index, vector); + + // absolute uncertainty (should be 1.0) + index[0] = 1; index[1] = 0; index[2] = 0; + vector[0] = 0.333; vector[1] = 0.333; vector[2] = 0.333; + m_TestImageVector->SetPixel(index, vector); + + // normal not normalized + index[0] = 1; index[1] = 0; index[2] = 1; + vector[0] = 1.2; vector[1] = 0.6; vector[2] = 0.1; + m_TestImageVector->SetPixel(index, vector); + + // error case... (should be 1.0) + index[0] = 1; index[1] = 1; index[2] = 0; + vector[0] = 0.0; vector[1] = 0.0; vector[2] = 0.0; + m_TestImageVector->SetPixel(index, vector); + + // Absolute uncertainty & certainty (should be 1.0) + index[0] = 1; index[1] = 1; index[2] = 1; + vector[0] = 1.0; vector[1] = 1.0; vector[2] = 1.0; + m_TestImageVector->SetPixel(index, vector); + + // Save gradient image + auto writer = itk::ImageFileWriter::New(); + writer->SetFileName("/home/jens/Desktop/ALtest/gradientImage.nrrd"); + writer->SetInput(m_TestImageGradient); + writer->Update(); + + auto writerVec = itk::ImageFileWriter::New(); + writerVec->SetFileName("/home/jens/Desktop/ALtest/vectorImage.nrrd"); + writerVec->SetInput(m_TestImageVector); + writerVec->Update(); + + } + + void tearDown() + { + m_TestImageRandom = nullptr; + m_TestImageGradient = nullptr; + m_TestImageVector = nullptr; + } + + void PredictionEntropyFilterTest() + { + auto entropyFilter = mitk::PredictionEntropyFilter::New(); + entropyFilter->SetInput(m_TestImageVector); + entropyFilter->Update(); + + auto writer = itk::ImageFileWriter::New(); + writer->SetFileName("/home/jens/Desktop/ALtest/vectorImageEntropy.nrrd"); + writer->SetInput(entropyFilter->GetOutput()); + writer->Update(); + } + + void ActiveLearningSuggestRegionFilterTest() + { + PixelType threshold = 0.7; + + // Threshold filter + auto thresholdFilter = itk::BinaryThresholdImageFilter::New(); + thresholdFilter->SetOutsideValue(0); + thresholdFilter->SetInsideValue(1); + thresholdFilter->SetLowerThreshold(threshold); + thresholdFilter->SetInput(m_TestImageGradient); + thresholdFilter->Update(); + + // Save thresholded image + auto binaryWriter = itk::ImageFileWriter::New(); + binaryWriter->SetFileName("/home/jens/Desktop/ALtest/gradientImageThresholded.nrrd"); + binaryWriter->SetInput(thresholdFilter->GetOutput()); + binaryWriter->Update(); + + // Connected component filter + auto ccFilter = itk::ConnectedComponentImageFilter::New(); + ccFilter->SetInput(thresholdFilter->GetOutput()); + ccFilter->Update(); + + // Save image with connected components + binaryWriter->SetFileName("/home/jens/Desktop/ALtest/gradientImageCC.nrrd"); + binaryWriter->SetInput(ccFilter->GetOutput()); + binaryWriter->Update(); + + // Select the one with greatest uncertainty sum + int ccCount = static_cast(ccFilter->GetObjectCount()); + MITK_INFO << "Number of components: " << ccCount; + std::map componentSize; + for (int a=1; a<=ccCount; ++a) {componentSize[a] = 0.0;} + auto imageIterator = itk::ImageRegionConstIterator(m_TestImageGradient, m_TestImageGradient->GetLargestPossibleRegion()); + auto binaryImageIterator = itk::ImageRegionConstIterator(ccFilter->GetOutput(), ccFilter->GetOutput()->GetLargestPossibleRegion()); + while (!binaryImageIterator.IsAtEnd()) + { + unsigned short binVal = binaryImageIterator.Get(); + float val = imageIterator.Get(); + if (binVal != 0) {componentSize[binVal] += val;} + ++binaryImageIterator; + ++imageIterator; + } + using PairType = decltype(componentSize)::value_type; + auto maxComp = std::max_element(componentSize.begin(), componentSize.end(), [](const PairType& lhs, const PairType& rhs){return lhs.second < rhs.second;}); + + auto thresholdFilter2 = itk::BinaryThresholdImageFilter::New(); + thresholdFilter2->SetOutsideValue(0); + thresholdFilter2->SetInsideValue(1); + thresholdFilter2->SetLowerThreshold(maxComp->first); + thresholdFilter2->SetUpperThreshold(maxComp->first); + thresholdFilter2->SetInput(ccFilter->GetOutput()); + thresholdFilter2->Update(); + + // Save optimal region + binaryWriter->SetFileName("/home/jens/Desktop/ALtest/gradientImageSuggestedRegionReference.nrrd"); + binaryWriter->SetInput(thresholdFilter2->GetOutput()); + binaryWriter->Update(); + + // Doe the same with suggestion filter + auto suggestFilter = mitk::ActiveLearningSuggestRegionFilter::New(); + suggestFilter->SetThreshold(threshold); + suggestFilter->SetInput(m_TestImageGradient); + suggestFilter->Update(); + + // Save suggested region + binaryWriter->SetFileName("/home/jens/Desktop/ALtest/gradientImageSuggestedRegion.nrrd"); + binaryWriter->SetInput(suggestFilter->GetOutput()); + binaryWriter->Update(); + } + typename ImageType::Pointer m_TestImageRandom; + typename ImageType::Pointer m_TestImageGradient; + typename VectorImageType::Pointer m_TestImageVector; + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkActiveLearning) diff --git a/Modules/Classification/CLMiniApps/CLActiveLearningApp.cpp b/Modules/Classification/CLMiniApps/CLActiveLearningApp.cpp new file mode 100644 index 0000000000..84ec23aa7f --- /dev/null +++ b/Modules/Classification/CLMiniApps/CLActiveLearningApp.cpp @@ -0,0 +1,24 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#ifndef mitkCLActiveLearningApp_cpp +#define mitkCLActiveLearningApp_cpp + +int main(int argc, char* argv[]) +{ + return 0; +} + +#endif diff --git a/Modules/Classification/CLUtilities/files.cmake b/Modules/Classification/CLUtilities/files.cmake index cfcdfcfe53..990b650b01 100644 --- a/Modules/Classification/CLUtilities/files.cmake +++ b/Modules/Classification/CLUtilities/files.cmake @@ -1,24 +1,25 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES Algorithms/itkLabelSampler.cpp Algorithms/itkSmoothedClassProbabilites.cpp Features/itkNeighborhoodFunctorImageFilter.cpp Features/itkLineHistogramBasedMassImageFilter.cpp + Features/itkStructureTensorImageFilter.hxx GlobalImageFeatures/mitkGIFCooccurenceMatrix.cpp GlobalImageFeatures/mitkGIFGrayLevelRunLength.cpp GlobalImageFeatures/mitkGIFFirstOrderStatistics.cpp GlobalImageFeatures/mitkGIFVolumetricStatistics.cpp #GlobalImageFeatures/itkEnhancedScalarImageToRunLengthFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedScalarImageToRunLengthMatrixFilter.hxx #GlobalImageFeatures/itkEnhancedHistogramToRunLengthFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedHistogramToTextureFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedScalarImageToTextureFeaturesFilter.hxx mitkCLUtil.cpp ) set( TOOL_FILES ) diff --git a/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h b/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h new file mode 100644 index 0000000000..30a5c14d54 --- /dev/null +++ b/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h @@ -0,0 +1,144 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +// +// Created by Jean-Marie Mirebeau on 05/03/2014. +// +// + +#ifndef itkStructureTensorImageFilter_h +#define itkStructureTensorImageFilter_h + +#include "itkCastImageFilter.h" +#include "itkGradientRecursiveGaussianImageFilter.h" +#include "itkAddImageFilter.h" +#include "itkVectorIndexSelectionCastImageFilter.h" +#include "itkGradientImageFilter.h" +#include "itkSymmetricSecondRankTensor.h" + +namespace itk +{ +/** + * \class StructureTensorImageFilter + * + * \brief Computes the structure tensor. + * + * Implementation of the structure tensor, defined by + * + * \f[K_\rho (\nabla u_\sigma \otimes \nabla u_\sigma),\f] + * + * where \f$K_\rho\f$ denotes the gaussian kernel of standard deviation \f$\rho\f$, + * and \f$u_\sigma := K_\sigma * u\f$. + * + * \ingroup AnisotropicDiffusionLBR + */ +template< typename TImage, + typename TTensorImage = + Image< SymmetricSecondRankTensor< typename TImage::PixelType,TImage::ImageDimension >, TImage::ImageDimension > > +class StructureTensorImageFilter: + public ImageToImageFilter< TImage, TTensorImage > +{ +public: + typedef StructureTensorImageFilter Self; + typedef ImageToImageFilter< TImage, TImage> Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + /// Method for creation through the object factory. + itkNewMacro(Self); + /// Run-time type information (and related methods). + itkTypeMacro(StructureTensorImageFilter, Superclass); + + typedef TImage ImageType; + typedef typename ImageType::PixelType PixelType; + static const unsigned int Dimension = ImageType::ImageDimension; + typedef TTensorImage TensorImageType; + typedef typename TensorImageType::PixelType TensorType; + typedef typename TensorType::ComponentType ScalarType; + typedef Image ScalarImageType; + + ///Parameter \f$\sigma\f$ of the structure tensor definition. + itkSetMacro(NoiseScale, ScalarType); + ///Parameter \f$\rho\f$ of the structure tensor definition. + itkSetMacro(FeatureScale, ScalarType); + ///Rescales all structure tensors by a common factor, so that the maximum trace is 1. + itkSetMacro(RescaleForUnitMaximumTrace, bool); + + itkGetConstMacro(NoiseScale, ScalarType); + itkGetConstMacro(FeatureScale, ScalarType); + itkGetConstMacro(RescaleForUnitMaximumTrace, bool); + itkGetConstMacro(PostRescaling, ScalarType); /// Global rescaling constant used. + +protected: + virtual void GenerateData() ITK_OVERRIDE; + + ScalarType m_FeatureScale; + ScalarType m_NoiseScale; + bool m_RescaleForUnitMaximumTrace; + ScalarType m_PostRescaling; + bool m_UseGradientRecursiveGaussianImageFilter; + + struct DispatchBase {}; + template< bool > + struct Dispatch: public DispatchBase {}; + + void IntermediateFilter( const Dispatch< true > & ); + void IntermediateFilter( const Dispatch< false > & ); + typename TensorImageType::Pointer m_IntermediateResult; + + typedef CovariantVector CovariantVectorType; + typedef Image CovariantImageType; + + struct OuterFunctor + { + TensorType operator()(const CovariantVectorType & u) const + { + TensorType m; + for( unsigned int i = 0; i < Dimension; ++i ) + { + for( unsigned int j = i; j < Dimension; ++j) + { + m(i,j) = u[i]*u[j]; + } + } + return m; + } + }; + struct TraceFunctor + { + ScalarType operator()(const TensorType & t) const { + return t.GetTrace(); + } + }; + struct ScaleFunctor + { + ScalarType scaling; + TensorType operator()(const TensorType & t) const { + return t*scaling; + } + }; + + StructureTensorImageFilter(); +}; + +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +#include "../src/Features/itkStructureTensorImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx b/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx new file mode 100644 index 0000000000..f08dead158 --- /dev/null +++ b/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx @@ -0,0 +1,167 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +// +// Created by Jean-Marie Mirebeau on 21/11/2014. +// +// + +#ifndef itkStructureTensorImageFilter_hxx +#define itkStructureTensorImageFilter_hxx + +#include "itkStructureTensorImageFilter.h" +#include + +#include + +namespace itk +{ + +template< typename TImage, typename TTensorImage > +StructureTensorImageFilter< TImage, TTensorImage > +::StructureTensorImageFilter(): + m_FeatureScale( 2 ), + m_NoiseScale( 1 ), + m_RescaleForUnitMaximumTrace( false ), + m_UseGradientRecursiveGaussianImageFilter( true ) +{ +} + + +template< typename TImage, typename TTensorImage > +void +StructureTensorImageFilter< TImage, TTensorImage > +::IntermediateFilter( const Dispatch< true > & ) +{ + typedef GradientRecursiveGaussianImageFilter< TImage, Self::CovariantImageType > GradientFilterType; + typename GradientFilterType::Pointer gradientFilter = GradientFilterType::New(); + gradientFilter->SetInput(this->GetInput()); + gradientFilter->SetSigma(this->m_NoiseScale); + + typedef UnaryFunctorImageFilter< Self::CovariantImageType, Self::TensorImageType, Self::OuterFunctor > OuterFilterType; + typename OuterFilterType::Pointer outerFilter = OuterFilterType::New(); + outerFilter->SetInput(gradientFilter->GetOutput()); + + outerFilter->Update(); + this->m_IntermediateResult = outerFilter->GetOutput(); +} + + +template< typename TImage, typename TTensorImage > +void +StructureTensorImageFilter< TImage, TTensorImage > +::IntermediateFilter( const Dispatch< false > & ) +{ + typename Self::ImageType::ConstPointer input = this->GetInput(); + typename Self::TensorImageType::Pointer output = Self::TensorImageType::New(); + output->CopyInformation(input); + output->SetRegions(input->GetRequestedRegion()); + output->Allocate(); + output->FillBuffer(Self::TensorType(0.)); + + for( unsigned int index = 0; index < Self::PixelType::Dimension; ++index ) + { + typedef VectorIndexSelectionCastImageFilter< Self::ImageType, Self::ScalarImageType > SelectionFilterType; + typename SelectionFilterType::Pointer selectionFilter = SelectionFilterType::New(); + selectionFilter->SetIndex(index); + selectionFilter->SetInput(input); + + typedef RecursiveGaussianImageFilter< Self::ScalarImageType > GaussianFilterType; + typedef GradientImageFilter< Self::ScalarImageType, Self::ScalarType, Self::ScalarType, Self::CovariantImageType > GradientFilterType; + typedef GradientRecursiveGaussianImageFilter< Self::ScalarImageType, Self::CovariantImageType > GradientGaussianFilterType; + + typename GaussianFilterType::Pointer gaussianFilter = GaussianFilterType::New(); + typename GradientFilterType::Pointer gradientFilter = GradientFilterType::New(); + typename GradientGaussianFilterType::Pointer gradientGaussianFilter = GradientGaussianFilterType::New(); + + gaussianFilter->SetSigma(this->m_NoiseScale); + gradientGaussianFilter->SetSigma(this->m_NoiseScale); + + typedef UnaryFunctorImageFilter< Self::CovariantImageType, Self::TensorImageType, Self::OuterFunctor > OuterFilterType; + typename OuterFilterType::Pointer outerFilter = OuterFilterType::New(); + + if( this->m_UseGradientRecursiveGaussianImageFilter ) + { + gradientGaussianFilter->SetInput(selectionFilter->GetOutput()); + outerFilter->SetInput(gradientGaussianFilter->GetOutput()); + } + else + { + gaussianFilter->SetInput(selectionFilter->GetOutput()); + gradientFilter->SetInput(gaussianFilter->GetOutput()); + outerFilter->SetInput(gradientFilter->GetOutput()); + } + + typedef AddImageFilter< Self::TensorImageType > AddFilterType; + typename AddFilterType::Pointer addFilter = AddFilterType::New(); + addFilter->InPlaceOn(); + addFilter->SetInput1(output); + addFilter->SetInput2(outerFilter->GetOutput()); + addFilter->Update(); + output = addFilter->GetOutput(); + + this->UpdateProgress(index/float( Self::PixelType::Dimension+1 )); + } + this->m_IntermediateResult = output; +} + + +template< typename TImage, typename TTensorImage > +void +StructureTensorImageFilter< TImage, TTensorImage > +::GenerateData() +{ + this->IntermediateFilter( Dispatch< std::numeric_limits< PixelType >::is_specialized >() ); + + typedef RecursiveGaussianImageFilter GaussianFilterType; + typename GaussianFilterType::Pointer gaussianFilter = GaussianFilterType::New(); + gaussianFilter->SetInput( m_IntermediateResult ); + gaussianFilter->SetSigma( m_FeatureScale ); + + if( !m_RescaleForUnitMaximumTrace ) + { + m_PostRescaling = 1.; + gaussianFilter->Update(); + this->GraftOutput(gaussianFilter->GetOutput()); + return; + } + + // *** Rescaling for normalization of largest trace *** + + typedef UnaryFunctorImageFilter TraceFilterType; + typename TraceFilterType::Pointer traceFilter = TraceFilterType::New(); + traceFilter->SetInput(gaussianFilter->GetOutput()); + + typedef MinimumMaximumImageCalculator MaximumCalculatorType; + typename MaximumCalculatorType::Pointer maximumCalculator = MaximumCalculatorType::New(); + maximumCalculator->SetImage(traceFilter->GetOutput()); + + typedef UnaryFunctorImageFilter ScaleFilterType; + typename ScaleFilterType::Pointer scaleFilter = ScaleFilterType::New(); + scaleFilter->SetInput(gaussianFilter->GetOutput()); + + traceFilter->Update(); + maximumCalculator->ComputeMaximum(); + m_PostRescaling = 1./maximumCalculator->GetMaximum(); + scaleFilter->GetFunctor().scaling = m_PostRescaling; + scaleFilter->Update(); + this->GraftOutput(scaleFilter->GetOutput()); +} + +} // end namespace itk + +#endif diff --git a/Modules/Classification/CLVigraRandomForest/src/Algorithm/itkStructureTensorEigenvalueImageFilter.cpp b/Modules/Classification/CLVigraRandomForest/src/Algorithm/itkStructureTensorEigenvalueImageFilter.cpp index aaacc7ddc2..5d438d5664 100644 --- a/Modules/Classification/CLVigraRandomForest/src/Algorithm/itkStructureTensorEigenvalueImageFilter.cpp +++ b/Modules/Classification/CLVigraRandomForest/src/Algorithm/itkStructureTensorEigenvalueImageFilter.cpp @@ -1,132 +1,133 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef ITKSTRUCTURETENSOREIGENVALUEIMAGEFILTER_CPP #define ITKSTRUCTURETENSOREIGENVALUEIMAGEFILTER_CPP #include #include #include #include +#include template< class TInputImageType, class TOutputImageType, class TMaskImageType> void itk::StructureTensorEigenvalueImageFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); this->GetOutput(0)->SetDirection(this->GetInput()->GetDirection()); this->GetOutput(0)->SetSpacing(this->GetInput()->GetSpacing()); this->GetOutput(0)->SetRegions(this->GetInput()->GetLargestPossibleRegion()); this->GetOutput(0)->Allocate(); this->GetOutput(1)->SetDirection(this->GetInput()->GetDirection()); this->GetOutput(1)->SetSpacing(this->GetInput()->GetSpacing()); this->GetOutput(1)->SetRegions(this->GetInput()->GetLargestPossibleRegion()); this->GetOutput(1)->Allocate(); this->GetOutput(2)->SetDirection(this->GetInput()->GetDirection()); this->GetOutput(2)->SetSpacing(this->GetInput()->GetSpacing()); this->GetOutput(2)->SetRegions(this->GetInput()->GetLargestPossibleRegion()); this->GetOutput(2)->Allocate(); } template< class TInputImageType, class TOutputImageType, class TMaskImageType> void itk::StructureTensorEigenvalueImageFilter::GenerateData() { typedef typename TInputImageType::PixelType InputPixelType; typename TInputImageType::RegionType region = this->GetInput()->GetLargestPossibleRegion(); unsigned int xdim = region.GetSize(0); unsigned int ydim = region.GetSize(1); unsigned int zdim = region.GetSize(2); vigra::Shape3 shape(xdim,ydim,zdim); vigra::MultiArrayView<3, InputPixelType, vigra::StridedArrayTag > input_image_view( shape , this->GetInput()->GetBufferPointer()); vigra::MultiArray<3, vigra::TinyVector > structuretensor_image(shape); vigra::MultiArray<3, vigra::TinyVector > eigenvalues_image(shape); for(int i = 0 ; i < zdim; ++i ) { vigra::Shape2 slice_shape(xdim,ydim); vigra::MultiArrayView<2, InputPixelType, vigra::StridedArrayTag > input_image_slice_view( slice_shape, input_image_view.data()+ (i*xdim*ydim)); vigra::MultiArrayView<2, vigra::TinyVector > structuretensor_image_slice_view( slice_shape, structuretensor_image.data() + (i*xdim*ydim)); vigra::structureTensor(input_image_slice_view, structuretensor_image_slice_view, m_InnerScale, m_OuterScale); vigra::MultiArrayView<2, vigra::TinyVector > eigenvalues_image_slice_view( slice_shape, eigenvalues_image.data() + (i*xdim*ydim)); vigra::tensorEigenRepresentation(structuretensor_image_slice_view, eigenvalues_image_slice_view); } vigra::MultiArrayView<3, InputPixelType, vigra::StridedArrayTag > ev1_image = eigenvalues_image.bindElementChannel(0); vigra::MultiArrayView<3, InputPixelType, vigra::StridedArrayTag > ev2_image = eigenvalues_image.bindElementChannel(1); vigra::MultiArrayView<3, InputPixelType, vigra::StridedArrayTag > ev3_image = eigenvalues_image.bindElementChannel(2); for(unsigned int x = 0 ; x < xdim; ++x) for(unsigned int y = 0 ; y < ydim; ++y) for(unsigned int z = 0 ; z < zdim; ++z) { itk::Index<3> indx = {{x,y,z}}; this->GetOutput(0)->operator [](indx) = ev1_image(x,y,z); this->GetOutput(1)->operator [](indx) = ev2_image(x,y,z); this->GetOutput(2)->operator [](indx) = ev3_image(x,y,z); } } template< class TInputImageType, class TOutputImageType, class TMaskImageType> void itk::StructureTensorEigenvalueImageFilter::SetImageMask(TMaskImageType *maskimage) { this->m_ImageMask = maskimage; } template< class TInputImageType, class TOutputImageType, class TMaskImageType> itk::StructureTensorEigenvalueImageFilter::StructureTensorEigenvalueImageFilter() { this->SetNumberOfIndexedOutputs(3); this->SetNumberOfIndexedInputs(1); this->SetNthOutput( 0, this->MakeOutput(0) ); this->SetNthOutput( 1, this->MakeOutput(1) ); this->SetNthOutput( 2, this->MakeOutput(2) ); } template< class TInputImageType, class TOutputImageType, class TMaskImageType> itk::StructureTensorEigenvalueImageFilter::~StructureTensorEigenvalueImageFilter() { } #endif diff --git a/Modules/Classification/CMakeLists.txt b/Modules/Classification/CMakeLists.txt index 0de91abd41..59d2cb1f41 100644 --- a/Modules/Classification/CMakeLists.txt +++ b/Modules/Classification/CMakeLists.txt @@ -1,8 +1,9 @@ add_subdirectory(CLCore) add_subdirectory(CLUtilities) add_subdirectory(CLMRUtilities) add_subdirectory(CLLibSVM) add_subdirectory(CLVigraRandomForest) add_subdirectory(DataCollection) add_subdirectory(CLImportanceWeighting) add_subdirectory(CLMiniApps) +add_subdirectory(CLActiveLearning) diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index 5314023b3d..6fabc928f4 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,337 +1,347 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkLabelSet.h" #include mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.GetActiveLabel()->GetValue()), m_Layer(other.GetLayer()) { // clone Labels auto otherIt = other.IteratorConstBegin(); for (; otherIt != other.IteratorConstEnd(); ++otherIt) { m_LabelContainer[otherIt->first] = otherIt->second->Clone(); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); } } void mitk::LabelSet::OnLabelModified() { ModifyLabelEvent.Send(); Superclass::Modified(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const { return m_LabelContainer.begin(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() { return m_LabelContainer.begin(); } +mitk::LabelSet::LabelContainerReverseIteratorType mitk::LabelSet::ReverseIteratorEnd() +{ + return m_LabelContainer.rend(); +} + +mitk::LabelSet::LabelContainerReverseIteratorType mitk::LabelSet::ReverseIteratorBegin() +{ + return m_LabelContainer.rbegin(); +} + unsigned int mitk::LabelSet::GetNumberOfLabels() const { return m_LabelContainer.size(); } void mitk::LabelSet::SetLayer(unsigned int layer) { m_Layer = layer; Modified(); } void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) { m_ActiveLabelValue = pixelValue; ActiveLabelEvent.Send(pixelValue); Modified(); } bool mitk::LabelSet::ExistLabel(PixelType pixelValue) { return m_LabelContainer.count(pixelValue) > 0 ? true : false; } // TODO Parameter as Smartpointer void mitk::LabelSet::AddLabel(mitk::Label *label) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LabelContainer.size() >= max_size) return; mitk::Label::Pointer newLabel(label->Clone()); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); PixelType pixelValue; if (m_LabelContainer.empty()) { pixelValue = newLabel->GetValue(); } else { pixelValue = m_LabelContainer.rbegin()->first; if (pixelValue >= newLabel->GetValue() && m_LabelContainer.find(newLabel->GetValue()) != m_LabelContainer.end()) { ++pixelValue; newLabel->SetValue(pixelValue); } else { pixelValue = newLabel->GetValue(); } } // new map entry m_LabelContainer[pixelValue] = newLabel; UpdateLookupTable(pixelValue); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); newLabel->AddObserver(itk::ModifiedEvent(), command); // newLabel->AddObserver(itk::ModifiedEvent(),command); SetActiveLabel(newLabel->GetValue()); AddLabelEvent.Send(); Modified(); } void mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { if (m_LabelContainer.size() > 255) return; mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); AddLabel(newLabel); } void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) { mitk::Label *label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); } void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) { m_LookupTable = lut; Modified(); } void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const { } void mitk::LabelSet::RemoveLabel(PixelType pixelValue) { auto it = m_LabelContainer.rbegin(); PixelType nextActivePixelValue = it->first; for (; it != m_LabelContainer.rend(); it++) { if (it->first == pixelValue) { it->second->RemoveAllObservers(); m_LabelContainer.erase(pixelValue); break; } nextActivePixelValue = it->first; } if (m_ActiveLabelValue == pixelValue) { if (ExistLabel(nextActivePixelValue)) SetActiveLabel(nextActivePixelValue); else SetActiveLabel(m_LabelContainer.rbegin()->first); } RemoveLabelEvent.Send(); Modified(); } void mitk::LabelSet::RemoveAllLabels() { auto _it = IteratorBegin(); for (; _it != IteratorConstEnd();) { RemoveLabelEvent.Send(); m_LabelContainer.erase(_it++); } AllLabelsModifiedEvent.Send(); } void mitk::LabelSet::SetAllLabelsLocked(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) _it->second->SetLocked(value); AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::SetAllLabelsVisible(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) { _it->second->SetVisible(value); UpdateLookupTable(_it->first); } AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) { const mitk::Color &color = GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) { if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) return nullptr; return m_LabelContainer[pixelValue]; } const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const { auto it = m_LabelContainer.find(pixelValue); if (it == m_LabelContainer.end()) return nullptr; return it->second.GetPointer(); } bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // LabelSetmembers MITK_INFO(verbose) << "--- LabelSet Equal ---"; // m_LookupTable; const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // m_ActiveLabel; returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Active label not equal."; return returnValue; ; } // m_Layer; returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); if (!returnValue) { MITK_INFO(verbose) << "Layer index not equal."; return returnValue; ; } // container size; returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // Label container (map) // m_LabelContainer; auto lhsit = leftHandSide.IteratorConstBegin(); auto rhsit = rightHandSide.IteratorConstBegin(); for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) { returnValue = rhsit->first == lhsit->first; if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } diff --git a/Modules/Multilabel/mitkLabelSet.h b/Modules/Multilabel/mitkLabelSet.h index 54afc67191..ccc1594ccb 100644 --- a/Modules/Multilabel/mitkLabelSet.h +++ b/Modules/Multilabel/mitkLabelSet.h @@ -1,237 +1,246 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef __mitkLabelSet_H_ #define __mitkLabelSet_H_ #include "MitkMultilabelExports.h" #include #include #include #include #include namespace mitk { // // Documentation // @brief LabelSet containing the labels corresponding to a segmentation session. // @ingroup Data // class MITKMULTILABEL_EXPORT LabelSet : public itk::Object { public: mitkClassMacroItkParent(LabelSet, itk::Object); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; - typedef std::map LabelContainerType; - typedef LabelContainerType::const_iterator LabelContainerConstIteratorType; - typedef LabelContainerType::iterator LabelContainerIteratorType; + typedef std::map LabelContainerType; + typedef LabelContainerType::const_iterator LabelContainerConstIteratorType; + typedef LabelContainerType::iterator LabelContainerIteratorType; + typedef LabelContainerType::reverse_iterator LabelContainerReverseIteratorType; /** * \brief AddLabelEvent is emitted whenever a new label has been added to the LabelSet. * * Observers should register to this event by calling myLabelSet->AddLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the LabelSet. * Observers should unregister by calling myLabelSet->AddLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message1 object which is thread safe */ Message<> AddLabelEvent; /** * \brief RemoveLabelEvent is emitted whenever a new label has been removed from the LabelSet. * * Observers should register to this event by calling myLabelSet->RemoveLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->RemoveLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message<> RemoveLabelEvent; /** * \brief ModifyLabelEvent is emitted whenever a label has been modified from the LabelSet. * * Observers should register to this event by calling myLabelSet->ModifyLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->ModifyLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message<> ModifyLabelEvent; /** * \brief ActiveLabelEvent is emitted whenever a label has been set as active in the LabelSet. */ Message1 ActiveLabelEvent; /** * \brief AllLabelsModifiedEvent is emitted whenever a new label has been removed from the LabelSet. * * Observers should register to this event by calling myLabelSet->AllLabelsModifiedEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->AllLabelsModifiedEvent.RemoveListener(myObject, * MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message<> AllLabelsModifiedEvent; /** \brief Returns a const iterator poiting to the begining of the container. */ LabelContainerConstIteratorType IteratorConstBegin() const; /** \brief Returns a const iterator pointing to the end of the container. */ LabelContainerConstIteratorType IteratorConstEnd() const; /** \brief Returns a iterator poiting to the begining of the container. */ LabelContainerIteratorType IteratorBegin(); /** \brief Returns a iterator pointing to the end of the container. */ LabelContainerIteratorType IteratorEnd(); + /** \brief Returns a reverse iterator pointing to the end of the container. + */ + LabelContainerReverseIteratorType ReverseIteratorBegin(); + + /** \brief Returns a reverse iterator pointing to the beginning of the container. + */ + LabelContainerReverseIteratorType ReverseIteratorEnd(); + /** \brief * Recall itk::Object::Modified event from a label and send a ModifyLabelEvent */ void OnLabelModified(); /** \brief */ void SetLayer(unsigned int); /** \brief */ void SetActiveLabel(PixelType); /** \brief */ void RemoveLabel(PixelType); /** \brief */ bool ExistLabel(PixelType); /** \brief */ void AddLabel(mitk::Label *label); /** \brief */ void AddLabel(const std::string &name, const Color &color); /** \brief */ void RenameLabel(PixelType, const std::string &, const Color &); /** \brief */ unsigned int GetNumberOfLabels() const; /** \brief */ void SetAllLabelsVisible(bool); /** \brief */ void SetAllLabelsLocked(bool); /** \brief */ void RemoveAllLabels(); /** \brief */ Label *GetActiveLabel() { return GetLabel(m_ActiveLabelValue); } /** \brief */ const Label *GetActiveLabel() const { return GetLabel(m_ActiveLabelValue); } /** \brief */ Label *GetLabel(PixelType pixelValue); /** \brief */ const Label *GetLabel(PixelType pixelValue) const; itkGetMacro(Layer, int) itkGetConstMacro(Layer, int) itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable) /** \brief */ void SetLookupTable(LookupTable *lut); /** \brief */ void UpdateLookupTable(PixelType pixelValue); protected: LabelSet(); LabelSet(const LabelSet &); mitkCloneMacro(Self) virtual ~LabelSet(); void PrintSelf(std::ostream &os, itk::Indent indent) const override; LabelContainerType m_LabelContainer; LookupTable::Pointer m_LookupTable; PixelType m_ActiveLabelValue; unsigned int m_Layer; }; /** * @brief Equal A function comparing two label sets for beeing equal in data * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetmembers * - Label container (map) * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif // __mitkLabelSet_H_ diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 8643f7cd16..bfcb5c17b5 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,963 +1,975 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageReadAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include #include #include #include #include #include //#include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Add a inital LabelSet ans corresponding image data to the stack AddLayer(); } mitk::LabelSetImage::~LabelSetImage() { m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } +void mitk::LabelSetImage::SetLayerImage(unsigned int layer, mitk::Image *image) +{ + if (image != nullptr) + { + m_LayerContainer[layer] = image; + } + else + { + mitkThrow() << "Tried to assign null image to layer."; + } +} + unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer lset) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } unsigned int newLabelSetId = this->AddLayer(newImage, lset); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer lset) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (lset.IsNotNull()) { ls = lset; } else { ls = mitk::LabelSet::New(); ls->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::Concatenate(mitk::LabelSetImage *other) { const unsigned int *otherDims = other->GetDimensions(); const unsigned int *thisDims = this->GetDimensions(); if ((otherDims[0] != thisDims[0]) || (otherDims[1] != thisDims[1]) || (otherDims[2] != thisDims[2])) mitkThrow() << "Dimensions do not match."; try { int numberOfLayers = other->GetNumberOfLayers(); for (int layer = 0; layer < numberOfLayers; ++layer) { this->SetActiveLayer(layer); AccessByItk_1(this, ConcatenateProcessing, other); mitk::LabelSet *ls = other->GetLabelSet(layer); auto it = ls->IteratorConstBegin(); auto end = ls->IteratorConstEnd(); it++; // skip exterior while (it != end) { GetLabelSet()->AddLabel((it->second)); // AddLabelEvent.Send(); it++; } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { AccessByItk(this, ClearBufferProcessing); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { GetLabelSet(layer)->RemoveLabel(VectorOfLabelPixelValues[idx]); EraseLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int i = 0; i < VectorOfLabelPixelValues.size(); i++) { this->EraseLabel(VectorOfLabelPixelValues[i], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue, unsigned int layer) { try { AccessByItk_2(this, EraseLabelProcessing, pixelValue, layer); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } Modified(); } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel();; } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index) { mitk::Image::Pointer mask = mitk::Image::New(); try { mask->Initialize(this); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) { byteSize *= mask->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(mask)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = this->GetTimeGeometry()->Clone(); mask->SetTimeGeometry(geometry); AccessByItk_2(this, CreateLabelMaskProcessing, mask, index); } catch (...) { mitkThrow() << "Could not create a mask out of the selected label."; } return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CreateLabelMaskProcessing(ImageType *itkImage, mitk::Image *mask, PixelType index) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkImage, itkImage->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkMask, itkMask->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); if (sourceValue == index) { targetIter.Set(1); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { // for now, we just retrieve the voxel in the middle typedef itk::ImageRegionConstIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); std::vector indexVector; while (!iter.IsAtEnd()) { // TODO fix comparison warning more effective if (iter.Get() == pixelValue) { indexVector.push_back(iter.GetIndex()); } ++iter; } mitk::Point3D pos; pos.Fill(0.0); if (!indexVector.empty()) { typename itk::ImageRegionConstIteratorWithIndex::IndexType centerIndex; centerIndex = indexVector.at(indexVector.size() / 2); if (centerIndex.GetIndexDimension() == 3) { pos[0] = centerIndex[0]; pos[1] = centerIndex[1]; pos[2] = centerIndex[2]; } else return; } GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } // todo: concatenate all layers and not just the active one template void mitk::LabelSetImage::ConcatenateProcessing(ImageType *itkTarget, mitk::LabelSetImage *other) { typename ImageType::Pointer itkSource = ImageType::New(); mitk::CastToItkImage(other, itkSource); typedef itk::ImageRegionConstIterator ConstIteratorType; typedef itk::ImageRegionIterator IteratorType; ConstIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); IteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); int numberOfTargetLabels = this->GetNumberOfLabels(GetActiveLayer()) - 1; // skip exterior sourceIter.GoToBegin(); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && !this->GetLabel(targetValue)->GetLocked()) // skip exterior and locked labels { targetIter.Set(sourceValue + numberOfTargetLabels); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int /*layer*/) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index 9fa6883017..cce2318a4e 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,346 +1,347 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef __mitkLabelSetImage_H_ #define __mitkLabelSetImage_H_ #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image) itkNewMacro(Self) typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; virtual void Initialize(const mitk::Image *image) override; /** * \brief */ void Concatenate(mitk::LabelSetImage *image); /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes labels from the mitk::LabelSet of given layer. * Calls mitk::LabelSetImage::EraseLabels() which also removes the labels from within the image. * @param VectorOfLabelPixelValues a list of labels to be removed * @param layer the layer in which the labels should be removed */ void RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * @brief Erases the label with the given value in the given layer from the underlying image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the label which will be remove from the image * @param layer the layer in which the label should be removed */ void EraseLabel(PixelType pixelValue, unsigned int layer = 0); /** * @brief Similar to mitk::LabelSetImage::EraseLabel() this funtion erase a list of labels from the image * @param VectorOfLabelPixelValues the list of labels that should be remove * @param layer the layer for which the labels should be removed */ void EraseLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * \brief Returns true if the value exists in one of the labelsets*/ bool ExistLabel(PixelType pixelValue) const; /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ bool ExistLabel(PixelType pixelValue, unsigned int layer) const; /** * \brief Returns true if the labelset exists*/ bool ExistLabelSet(unsigned int layer) const; /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ mitk::Label *GetActiveLabel(unsigned int layer = 0); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise NULL */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer = 0) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or NULL if non is present */ mitk::LabelSet *GetActiveLabelSet(); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or NULL if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * @brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one * @param layer a mitk::LabelSet which will be set as new layer. * @return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer layer = nullptr); /** * \brief Add a layer based on a provided mitk::Image * \param layerImage is added to the vector of label images * \param lset a label set that will be added to the new layer if provided *\return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer lset = nullptr); /** * \brief Add a LabelSet to an existing layer * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); /** * @brief Sets the label which is used as default exterior label when creating a new layer * @param label the label which will be used as new exterior label */ void SetExteriorLabel(mitk::Label *label); /** * @brief Gets the mitk::Label which is used as default exterior label * @return the exterior mitk::Label */ mitk::Label *GetExteriorLabel(); const mitk::Label *GetExteriorLabel() const; protected: mitkCloneMacro(Self) LabelSetImage(); LabelSetImage(const LabelSetImage &other); virtual ~LabelSetImage(); template void ChangeLayerProcessing(ImageType1 *source, ImageType2 *target); template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index, unsigned int layer); // template < typename ImageType > // void ReorderLabelProcessing( ImageType* input, int index, int layer); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void ConcatenateProcessing(ImageType *input, mitk::LabelSetImage *other); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void CreateLabelMaskProcessing(ImageType *input, mitk::Image *mask, PixelType index); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; mitk::Label::Pointer m_ExteriorLabel; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ + MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif // __mitkLabelSetImage_H_ diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index 0c330b64bf..3705b48d37 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,87 +1,88 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:OFF org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF #org.blueberry.test:ON #org.blueberry.uitest:ON #Testing/org.blueberry.core.runtime.tests:ON #Testing/org.blueberry.osgi.tests:ON org.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON org.mitk.core.ext:OFF org.mitk.core.jobs:OFF org.mitk.diffusionimaging:OFF org.mitk.simulation:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.coreapplication:OFF org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.common.legacy:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.diffusionimagingapp:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicom:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.diffusionimaging:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.registration:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.simulation:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.renderwindowmanager:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.echotrack:OFF org.mitk.gui.qt.spectrocamrecorder:OFF org.mitk.gui.qt.classificationsegmentation:OFF org.mitk.gui.qt.overlaymanager:OFF org.mitk.gui.qt.multilabelsegmentation:OFF org.mitk.matchpoint.core.helper:OFF org.mitk.gui.qt.matchpoint.algorithm.browser:OFF org.mitk.gui.qt.matchpoint.algorithm.control:OFF org.mitk.gui.qt.matchpoint.algorithm.batch:OFF org.mitk.gui.qt.matchpoint.mapper:OFF org.mitk.gui.qt.matchpoint.framereg:OFF org.mitk.gui.qt.matchpoint.visualizer:OFF org.mitk.gui.qt.matchpoint.evaluator:OFF org.mitk.gui.qt.matchpoint.manipulator:OFF org.mitk.gui.qt.cest:OFF + org.mitk.gui.qt.activelearning:OFF ) diff --git a/Plugins/org.mitk.gui.qt.activelearning/CMakeLists.txt b/Plugins/org.mitk.gui.qt.activelearning/CMakeLists.txt new file mode 100644 index 0000000000..b8fc390e9b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/CMakeLists.txt @@ -0,0 +1,7 @@ +project(org_mitk_gui_qt_activelearning) + +mitk_create_plugin( + EXPORT_DIRECTIVE ACTIVELEARNING_EXPORT + EXPORTED_INCLUDE_SUFFIXES src + MODULE_DEPENDS MitkQtWidgetsExt MitkSegmentationUI MitkCLActiveLearning MitkCLUtilities MitkCLVigraRandomForest +) diff --git a/Plugins/org.mitk.gui.qt.activelearning/files.cmake b/Plugins/org.mitk.gui.qt.activelearning/files.cmake new file mode 100644 index 0000000000..84cfc4ede5 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/files.cmake @@ -0,0 +1,43 @@ +set(SRC_CPP_FILES + +) + +set(INTERNAL_CPP_FILES + org_mitk_gui_qt_activelearning_Activator.cpp + QmitkActiveLearning.cpp +) + +set(UI_FILES + src/internal/QmitkActiveLearningControls.ui +) + +set(MOC_H_FILES + src/internal/org_mitk_gui_qt_activelearning_Activator.h + src/internal/QmitkActiveLearning.h +) + +# list of resource files which can be used by the plug-in +# system without loading the plug-ins shared library, +# for example the icon used in the menu and tabs for the +# plug-in views in the workbench +set(CACHED_RESOURCE_FILES + resources/icon.xpm + resources/erase_icon_100.png + resources/paint_icon_200.png + plugin.xml +) + +# list of Qt .qrc files which contain additional resources +# specific to this plugin +set(QRC_FILES +) + +set(CPP_FILES ) + +foreach(file ${SRC_CPP_FILES}) + set(CPP_FILES ${CPP_FILES} src/${file}) +endforeach(file ${SRC_CPP_FILES}) + +foreach(file ${INTERNAL_CPP_FILES}) + set(CPP_FILES ${CPP_FILES} src/internal/${file}) +endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.activelearning/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.activelearning/manifest_headers.cmake new file mode 100644 index 0000000000..475bfe7347 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/manifest_headers.cmake @@ -0,0 +1,5 @@ +set(Plugin-Name "ActiveLearning") +set(Plugin-Version "0.1") +set(Plugin-Vendor "DKFZ") +set(Plugin-ContactAddress "") +set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.activelearning/plugin.xml b/Plugins/org.mitk.gui.qt.activelearning/plugin.xml new file mode 100644 index 0000000000..e5b2922598 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/plugin.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.activelearning/resources/erase_icon_100.png b/Plugins/org.mitk.gui.qt.activelearning/resources/erase_icon_100.png new file mode 100644 index 0000000000..5135c341d0 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.activelearning/resources/erase_icon_100.png differ diff --git a/Plugins/org.mitk.gui.qt.activelearning/resources/icon.xpm b/Plugins/org.mitk.gui.qt.activelearning/resources/icon.xpm new file mode 100644 index 0000000000..9057c20bc6 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/resources/icon.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static const char * icon_xpm[] = { +"16 16 2 1", +" c #FF0000", +". c #000000", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/Plugins/org.mitk.gui.qt.activelearning/resources/paint_icon_200.png b/Plugins/org.mitk.gui.qt.activelearning/resources/paint_icon_200.png new file mode 100644 index 0000000000..ffdcf110eb Binary files /dev/null and b/Plugins/org.mitk.gui.qt.activelearning/resources/paint_icon_200.png differ diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp new file mode 100644 index 0000000000..f630f0e1bf --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp @@ -0,0 +1,1402 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +// Blueberry +#include +#include + +// Qt +#include +#include +#include +#include + +// Qmitk +#include "QmitkActiveLearning.h" + +// MITK +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ITK/VTK +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef ActiveLearning::FeaturePixelType FeaturePixelType; +typedef ActiveLearning::AnnotationPixelType AnnotationPixelType; +typedef ActiveLearning::LabelPixelType LabelPixelType; +typedef ActiveLearning::FeatureMatrixType FeatureMatrixType; +typedef ActiveLearning::LabelVectorType LabelVectorType; + +// Returns true if list has at least one entry and all entries are valid mitk::Images, otherwise false +static bool SelectionAllImages(const QList& nodes) +{ + if (nodes.empty()) + { + return false; + } + for (const auto& node : nodes) + { + if(!(node.IsNotNull() && dynamic_cast(node->GetData()) != nullptr)) return false; + } + return true; +} + +// QColor to mitk::Color +static mitk::Color QColorToMitkColor(const QColor& qcolor) +{ + mitk::Color color; + color.SetRed((float)qcolor.red() / 255); + color.SetGreen((float)qcolor.green() / 255); + color.SetBlue((float)qcolor.blue() / 255); + return color; +} + +// For debugging +static void PrintAllLabels(mitk::LabelSetImage* image) +{ + for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it) + { + MITK_INFO << "Key: " << it->first << " - Name: " << it->second->GetName() << " - Value: " << it->second->GetValue() << " - Color: " << it->second->GetColor(); + } +} + +// Make values of labels a consistent range +static void FillLabelValues(mitk::LabelSetImage* image) +{ + int value(0); + for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it) + { + it->second->SetValue(value); + value++; + } + image->GetActiveLabelSet()->SetActiveLabel(0); +} + +// Fill image with zeros +static void FillWithZeros(mitk::Image* image) +{ + unsigned int size = image->GetPixelType().GetSize(); + for (unsigned int i=0; iGetDimension(); i++) + { + size *= image->GetDimension(i); + } + for (unsigned int t=0; tGetTimeSteps(); t++) + { + mitk::ImageWriteAccessor accessor(image, image->GetVolumeData(0)); + memset(accessor.GetData(), 0, size); + } +} + +template +static Eigen::Matrix Transform(const std::vector images) +{ + // Find size for output matrix [number of voxels, number of feature images] + unsigned int size = images[0]->GetDimension(0); + for (unsigned int i=1; i<3; ++i) + { + size *= images[0]->GetDimension(i); + } + + Eigen::Matrix outputMatrix(size, images.size()); + + for (unsigned int i=0; i::Pointer imageItk; + mitk::CastToItkImage(images[i], imageItk); + outputMatrix.col(i) = Eigen::Matrix::Map(imageItk->GetBufferPointer(), size); + } + + return outputMatrix; +} + +template +static mitk::Image::Pointer Transform(const Eigen::Matrix &inputMatrix, + const mitk::Image::Pointer referenceImage) +{ + typename itk::Image::Pointer imageItk; + auto outputImage = mitk::Image::New(); + outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + mitk::CastToItkImage(outputImage, imageItk); + + auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion()); + int i = 0; + while (!it.IsAtEnd()) + { + it.Set(inputMatrix(i, 0)); + ++it; + ++i; + } + + mitk::GrabItkImageMemory(imageItk, outputImage); + return outputImage; +} + +template +static std::vector Transform(const Eigen::Matrix &inputMatrix, + const mitk::Image::Pointer referenceImage) +{ + std::vector resultVector; + + for (int j=0; j::Pointer imageItk; + auto outputImage = mitk::Image::New(); + outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + mitk::CastToItkImage(outputImage, imageItk); + + auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion()); + int i = 0; + while (!it.IsAtEnd()) + { + it.Set(static_cast(inputMatrix(i, j))); + ++it; + ++i; + } + + mitk::GrabItkImageMemory(imageItk, outputImage); + resultVector.push_back(outputImage); + } + + return resultVector; +} + +template +static void PrintMatrix(const Eigen::Matrix dataMatrix, + int maxRows = 0) +{ + if (maxRows == 0 || maxRows > dataMatrix.rows()) maxRows = dataMatrix.rows(); + + MITK_INFO << "---------------------"; + for (int i=0; i +static void PrintMatrix(const Eigen::Matrix dataMatrix, + const Eigen::Matrix labelMatrix, + int maxRows = 0) +{ + if (labelMatrix.rows() < dataMatrix.rows()) return; + + if (maxRows == 0 || maxRows > dataMatrix.rows()) maxRows = dataMatrix.rows(); + + MITK_INFO << "---------------------"; + for (int i=0; i +static void GaussianSmoothing(const itk::Image* inputImage, + const double sigma, mitk::Image::Pointer outputImage) +{ + auto spacing = inputImage->GetSpacing(); + float mean_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.; + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::DiscreteGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetVariance(sigma*sigma*mean_spacing*mean_spacing); + filter->Update(); + mitk::GrabItkImageMemory(filter->GetOutput(), outputImage); +} + +template +static void GaussianGradientMagnitude(const itk::Image* inputImage, + const double sigma, mitk::Image::Pointer outputImage) +{ + auto spacing = inputImage->GetSpacing(); + float mean_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.; + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::GradientMagnitudeRecursiveGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetSigma(sigma*mean_spacing); + filter->Update(); + mitk::GrabItkImageMemory(filter->GetOutput(), outputImage); +} + +template +static void LaplacianOfGaussian(const itk::Image* inputImage, + const double sigma, mitk::Image::Pointer outputImage) +{ + auto spacing = inputImage->GetSpacing(); + float mean_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.; + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::LaplacianRecursiveGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetSigma(sigma*mean_spacing); + filter->Update(); + mitk::GrabItkImageMemory(filter->GetOutput(), outputImage); +} + +template +static void StructureTensorEigenvalues(const itk::Image* inputImage, + float sigma, std::vector outputImages) +{ +// typedef itk::Image ImageType; +// auto filter = itk::StructureTensorEigenvalueImageFilter::New(); +// filter->SetInput(inputImage); +// filter->SetInnerScale(sigma); +// filter->SetOuterScale(sigma); +// filter->Update(); +// for (unsigned int i=0; iGetNumberOfOutputs(); i++) +// { +// mitk::GrabItkImageMemory(filter->GetOutput(i), outputImages[i]); +// } + auto spacing = inputImage->GetSpacing(); + float mean_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.; + + typedef itk::Image ImageType; + typedef itk::Image, imageDimension> TensorImageType; + typename itk::StructureTensorImageFilter::Pointer filter = itk::StructureTensorImageFilter::New(); + filter->SetInput(inputImage); + filter->SetNoiseScale(sigma*mean_spacing); + filter->SetFeatureScale(sigma*mean_spacing); + filter->Update(); + + std::vector eigenValueImages; + std::vector> eigenValueImageIterators; + for (unsigned int i=0; iSetRegions(inputImage->GetLargestPossibleRegion()); + image->Allocate(); + eigenValueImages.push_back(image); + itk::ImageRegionIterator it(image, image->GetLargestPossibleRegion()); + eigenValueImageIterators.push_back(it); + } + + itk::ImageRegionConstIterator tensorImageIterator(filter->GetOutput(), filter->GetOutput()->GetLargestPossibleRegion()); + while (!tensorImageIterator.IsAtEnd()) + { + typename TensorImageType::PixelType::EigenValuesArrayType ev; + tensorImageIterator.Get().ComputeEigenValues(ev); + for (unsigned int i=0; i +static void HessianEigenvalues(const itk::Image* inputImage, + float sigma, std::vector outputImages) +{ + auto spacing = inputImage->GetSpacing(); + float mean_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.; + + typedef itk::Image ImageType; + typedef itk::Image, imageDimension> TensorImageType; + typename itk::HessianRecursiveGaussianImageFilter::Pointer filter = itk::HessianRecursiveGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetSigma(sigma*mean_spacing); + filter->Update(); + + std::vector eigenValueImages; + std::vector> eigenValueImageIterators; + for (unsigned int i=0; iSetRegions(inputImage->GetLargestPossibleRegion()); + image->Allocate(); + eigenValueImages.push_back(image); + itk::ImageRegionIterator it(image, image->GetLargestPossibleRegion()); + eigenValueImageIterators.push_back(it); + } + + itk::ImageRegionConstIterator tensorImageIterator(filter->GetOutput(), filter->GetOutput()->GetLargestPossibleRegion()); + while (!tensorImageIterator.IsAtEnd()) + { + typename TensorImageType::PixelType::EigenValuesArrayType ev; + tensorImageIterator.Get().ComputeEigenValues(ev); + for (unsigned int i=0; i ImageType; +// typedef itk::Image FeatureImageType; +// auto filter = itk::HessianMatrixEigenvalueImageFilter::New(); +// filter->SetInput(inputImage); +// filter->SetSigma(sigma); +// filter->Update(); +// mitk::GrabItkImageMemory(filter->GetOutput(0), outputImages[0]); +// mitk::GrabItkImageMemory(filter->GetOutput(1), outputImages[1]); +// mitk::GrabItkImageMemory(filter->GetOutput(2), outputImages[2]); +} + +/* ================================================================== + * PUBLIC SLOTS + * =============================================================== */ + + +void ActiveLearning::Initialize() +{ + // Get selected nodes and check again if these are all images + m_Nodes = this->GetDataManagerSelection(); + if (!SelectionAllImages(m_Nodes)) return; + + m_Controls.m_InitializePushButton->setDisabled(true); + emit SignalSetProgressMaximum(m_Nodes.length() * 10 + 1); // 6 is number of features, shouldn't be hardcoded + emit SignalResetProgress(); + + // Set names to the label (again) + QString nameList = QString::fromStdString(m_Nodes[0]->GetName()); + if (m_Nodes.length() >= 2) + { + for (int i=1; i"); + nameList += QString::fromStdString(m_Nodes[i]->GetName()); + } + } + m_Controls.m_InitializeLabel->setText(nameList); + + // ======================================= + // SEGMENTATION IMAGE + // ======================================= + + m_SegmentationImage = mitk::Image::New(); + + try + { + mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + m_SegmentationImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + } + catch (mitk::Exception& e) + { + MITK_ERROR << "Exception caught: " << e.GetDescription(); + QMessageBox::information(m_Parent, "Error", "Could not initialize segmentation image"); + return; + } + + FillWithZeros(m_SegmentationImage); + + m_SegmentationNode = mitk::DataNode::New(); + m_SegmentationNode->SetData(m_SegmentationImage); + m_SegmentationNode->SetName("Segmentation"); + m_SegmentationNode->SetColor(1., 1., 1.); + m_SegmentationNode->SetBoolProperty("binary", false); + m_SegmentationNode->SetProperty("reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST)); + this->GetDataStorage()->Add(m_SegmentationNode, m_Nodes[0]); + + // ======================================= + // ANNOTATION IMAGE + // ======================================= + + m_AnnotationImage = mitk::Image::New(); + + try + { + mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + m_AnnotationImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + } + catch (mitk::Exception& e) + { + MITK_ERROR << "Exception caught: " << e.GetDescription(); + QMessageBox::information(m_Parent, "Error", "Could not initialize annotation image"); + return; + } + + FillWithZeros(m_AnnotationImage); + + m_AnnotationNode = mitk::DataNode::New(); + m_AnnotationNode->SetData(m_AnnotationImage); + m_AnnotationNode->SetName("Labels"); + m_AnnotationNode->SetColor(1., 1., 1.); + m_AnnotationNode->SetBoolProperty("binary", false); + m_AnnotationNode->SetProperty("reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST)); + m_AnnotationNode->SetProperty("opacity", mitk::FloatProperty::New(1.0f)); + this->GetDataStorage()->Add(m_AnnotationNode, m_Nodes[0]); + + // ======================================= + // UNCERTAINTY NODE + // ======================================= + + m_UncertaintyNode = mitk::DataNode::New(); + m_UncertaintyNode->SetName("Uncertainty"); + m_UncertaintyNode->SetColor(0., 1., 0.95); + m_UncertaintyNode->SetBoolProperty("binary", false); + m_UncertaintyNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); + m_UncertaintyNode->SetVisibility(false); + this->GetDataStorage()->Add(m_UncertaintyNode, m_Nodes[0]); + + // ======================================= + // UNCERTAINTY REGION NODE + // ======================================= + + m_UncertaintyRegionNode = mitk::DataNode::New(); + m_UncertaintyRegionNode->SetName("Region"); + m_UncertaintyRegionNode->SetColor(1., 1., 1.); + m_UncertaintyRegionNode->SetBoolProperty("binary", false); + m_UncertaintyRegionNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); + m_UncertaintyRegionNode->SetVisibility(true); + m_UncertaintyRegionNode->SetBoolProperty("fixedLayer", true); + m_UncertaintyRegionNode->SetProperty("layer", mitk::IntProperty::New(99)); + this->GetDataStorage()->Add(m_UncertaintyRegionNode, m_Nodes[0]); + + // Convert input images to FeaturePixelType + for (auto node : m_Nodes) + { + mitk::Image::Pointer image = dynamic_cast(node->GetData()); + auto itkImage = itk::Image::New(); + mitk::CastToItkImage(image, itkImage); + mitk::GrabItkImageMemory(itkImage, image); + node->SetData(image); + } + + emit SignalSetProgress(1); + + // Calculate features + for (const auto node : m_Nodes) + { + mitk::Image::Pointer currentImage = dynamic_cast(node->GetData()); + QFuture>> future; + future = QtConcurrent::run(this, &ActiveLearning::CalculateFeatures, currentImage); + auto futureWatcher = new QFutureWatcher>>(); + futureWatcher->setFuture(future); + connect(futureWatcher, SIGNAL(finished()), this, SLOT(OnInitializationFinished())); + m_FeatureCalculationWatchers.push_back(futureWatcher); + } + + // Interactor + auto activeLearningLib = us::ModuleRegistry::GetModule("MitkCLActiveLearning"); + m_Interactor = mitk::ActiveLearningInteractor::New(); + m_Interactor->LoadStateMachine("Paint.xml", activeLearningLib); + m_Interactor->SetEventConfig("PaintConfig.xml", activeLearningLib); + m_Interactor->SetDataNode(m_AnnotationNode); + + // Automatically add first label + OnAddLabelPushButtonClicked(); + + m_Active = true; +} + + +/* ================================================================== + * PUBLIC + * =============================================================== */ + + +ActiveLearning::ActiveLearning() : + m_Parent(nullptr), + m_AnnotationImage(nullptr), + m_AnnotationNode(nullptr), + m_SegmentationImage(nullptr), + m_SegmentationNode(nullptr), + m_Active(false), + m_Guidance(false), + m_NumberOfTrees(50), + m_MaximumTreeDepth(10), + m_SamplesPerTree(0.66), + m_PredictionMatrix(nullptr) +{ + +} + +ActiveLearning::~ActiveLearning() +{ + +} + +void ActiveLearning::CreateQtPartControl( QWidget *parent ) +{ + m_Controls.setupUi(parent); + m_Parent = parent; + + // Label model + m_LabelListModel = new QStandardItemModel(0, 3, this); + m_Controls.m_LabelTableView->setModel(m_LabelListModel); + m_Controls.m_LabelTableView->horizontalHeader()->setDefaultSectionSize(20); + m_Controls.m_LabelTableView->verticalHeader()->setDefaultSectionSize(20); + NotEditableDelegate* itemDelegate = new NotEditableDelegate(parent); + m_Controls.m_LabelTableView->setItemDelegateForColumn(1, itemDelegate); + + // Connects + connect(m_Controls.m_LabelTableView, SIGNAL(doubleClicked(QModelIndex)), + this, SLOT(OnColorIconDoubleClicked(QModelIndex))); + connect(m_Controls.m_LabelTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(OnLabelListSelectionChanged(QItemSelection, QItemSelection))); + connect(m_LabelListModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), + this, SLOT(OnLabelNameChanged(QModelIndex, QModelIndex))); + connect(m_Controls.m_InitializePushButton, SIGNAL(clicked()), + this, SLOT(Initialize())); + connect(m_Controls.m_AddLabelPushButton, SIGNAL(clicked()), + this, SLOT(OnAddLabelPushButtonClicked())); + connect(m_Controls.m_RemoveLabelPushButton, SIGNAL(clicked()), + this, SLOT(OnRemoveLabelPushButtonClicked())); + connect(m_Controls.m_PaintToolButton, SIGNAL(clicked()), + this, SLOT(OnPaintToolButtonClicked())); + connect(m_Controls.m_EraseToolButton, SIGNAL(clicked()), + this, SLOT(OnEraseToolButtonClicked())); + connect(m_Controls.m_SaveSegmentationPushButton, SIGNAL(clicked()), + this, SLOT(OnSaveSegmentationPushButtonClicked())); + connect(m_Controls.m_SavePredictionsPushButton, SIGNAL(clicked()), + this, SLOT(OnSavePredictionsPushButtonClicked())); + connect(m_Controls.m_UpdatePredictionsPushButton, SIGNAL(clicked()), + this, SLOT(OnUpdatePredictionsPushButtonClicked())); + connect(this, SIGNAL(SignalIncrementProgress()), + this, SLOT(OnSignalIncrementProgress())); + connect(this, SIGNAL(SignalSetProgress(int)), + this, SLOT(OnSignalSetProgress(int))); + connect(this, SIGNAL(SignalResetProgress()), + this, SLOT(OnSignalResetProgress())); + connect(this, SIGNAL(SignalSetProgressMaximum(int)), + this, SLOT(OnSignalSetProgressMaximum(int))); + connect(m_Controls.m_BrushSizeSlider, SIGNAL(valueChanged(int)), + this, SLOT(OnBrushSizeSliderValueChanged(int))); + connect(m_Controls.m_ActiveGuidanceCheckBox, SIGNAL(stateChanged(int)), + this, SLOT(OnActiveGuidanceCheckBoxToggled(int))); + + // Presets + m_Controls.m_PresetComboBox->addItem("Presets"); + m_Controls.m_PresetComboBox->addItem("Glioblastoma"); + + // Set start configuration + m_Controls.m_LabelControlsFrame->setVisible(false); + SetInitializeReady(false); +} + +void ActiveLearning::ResetLabels() +{ + for (int i=0; irowCount(); i++) + { + m_LabelListModel->item(i, 1)->setText(QString::number(i + 1)); + } +} + +std::vector > ActiveLearning::CalculateFeatures(const mitk::Image::Pointer inputImage) +{ + std::vector> result; + + // TODO: Get features from preference page + std::vector sigmas = {0.7, 1.6}; + + for (auto sigma : sigmas) + { + std::stringstream ss; + + auto gaussImage = mitk::Image::New(); + AccessByItk_n(inputImage, GaussianSmoothing, (sigma, gaussImage)); + gaussImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + ss << "GaussianSmoothing (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(gaussImage, ss.str())); + ss.str(""); + emit SignalIncrementProgress(); + + auto gradMagImage = mitk::Image::New(); + AccessByItk_n(inputImage, GaussianGradientMagnitude, (sigma, gradMagImage)); + gradMagImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + ss << "GaussianGradientMagnitude (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(gradMagImage, ss.str())); + ss.str(""); + emit SignalIncrementProgress(); + + auto logImage = mitk::Image::New(); + AccessByItk_n(inputImage, LaplacianOfGaussian, (sigma, logImage)); + logImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + ss << "LaplacianOfGaussian (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(logImage, ss.str())); + ss.str(""); + emit SignalIncrementProgress(); + + auto structImage1 = mitk::Image::New(); + auto structImage2 = mitk::Image::New(); + auto structImage3 = mitk::Image::New(); + std::vector structImages = {structImage1, structImage2, structImage3}; + AccessByItk_n(inputImage, StructureTensorEigenvalues, (sigma, structImages)); + structImage1->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + structImage2->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + structImage3->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + ss << "StructureTensorEV1 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(structImage1, ss.str())); + ss.str(""); + ss << "StructureTensorEV2 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(structImage2, ss.str())); + ss.str(""); + if (inputImage->GetDimension() == 3) + { + ss << "StructureTensorEV3 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(structImage3, ss.str())); + ss.str(""); + } + emit SignalIncrementProgress(); + + auto hessianImage1 = mitk::Image::New(); + auto hessianImage2 = mitk::Image::New(); + auto hessianImage3 = mitk::Image::New(); + std::vector hessianImages = {hessianImage1, hessianImage2, hessianImage3}; + AccessByItk_n(inputImage, HessianEigenvalues, (sigma, hessianImages)); + hessianImage1->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + hessianImage2->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + hessianImage3->SetClonedTimeGeometry(inputImage->GetTimeGeometry()); + ss << "HessianEV1 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(hessianImage1, ss.str())); + ss.str(""); + ss << "HessianEV2 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(hessianImage2, ss.str())); + ss.str(""); + if (inputImage->GetDimension() == 3) + { + ss << "HessianEV3 (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(hessianImage3, ss.str())); + ss.str(""); + } + emit SignalIncrementProgress(); + } + + return result; +} + +std::pair> ActiveLearning::CalculatePrediction(const mitk::Image::Pointer annotationImage, + const std::vector &featureImageVector, + const mitk::Image::Pointer referenceImage, + mitk::AbstractClassifier* classifier, + std::shared_ptr predictionMatrix) +{ + // Create prediction matrix if necessary + if (predictionMatrix == nullptr) + { + FeatureMatrixType mat = Transform(featureImageVector); + predictionMatrix = std::make_shared(mat); + } + emit SignalIncrementProgress(); + + // Get training data and train + auto training = GetTrainingData(annotationImage, featureImageVector); + classifier->Train(*training.second, *training.first); + emit SignalIncrementProgress(); + + // Get result + LabelVectorType segmentation = classifier->Predict(*predictionMatrix); + emit SignalIncrementProgress(); + FeatureMatrixType prediction = classifier->GetPointWiseProbabilities(); + mitk::Image::Pointer segmentationImage = Transform(segmentation, referenceImage); + std::vector predictionImages = Transform(prediction, referenceImage); + emit SignalIncrementProgress(); + + std::pair> result = std::make_pair(segmentationImage, predictionImages); + return result; +} + +std::pair ActiveLearning::CalculateUncertainty(const std::vector &probabilityImageVector, + const mitk::Image::Pointer referenceImage, + bool guidance) +{ + typedef itk::Image ImageType; + std::vector itkImages; + std::vector> iterators; + + for (auto image : probabilityImageVector) + { + typename ImageType::Pointer itkImage; + mitk::CastToItkImage(image, itkImage); + itkImages.push_back(itkImage); + itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion()); + iterators.push_back(it); + } + + auto outputImage = mitk::Image::New(); + outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + typename ImageType::Pointer uncertaintyImage; + mitk::CastToItkImage(outputImage, uncertaintyImage); + itk::ImageRegionIterator uit(uncertaintyImage, uncertaintyImage->GetLargestPossibleRegion()); + + while (!uit.IsAtEnd()) + { + FeaturePixelType value = 0; + for (unsigned int i=0; iInitialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone())); + auto regionFilter = mitk::ActiveLearningSuggestRegionFilter>::New(); + regionFilter->SetInput(uncertaintyImage); + regionFilter->SetThreshold(0.5); + regionFilter->Update(); + mitk::GrabItkImageMemory(regionFilter->GetOutput(), region); + } + + mitk::GrabItkImageMemory(uncertaintyImage, outputImage); + std::pair result = std::make_pair(outputImage, region); + return result; +} + +std::pair, std::shared_ptr> ActiveLearning::GetTrainingData(const mitk::Image::Pointer annotationImage, + const std::vector &featureImageVector) +{ + // Get indices and labels + std::vector> indices; + std::vector labels; + itk::Image::Pointer annotationImageItk; + mitk::CastToItkImage(annotationImage, annotationImageItk); + + itk::ImageRegionIteratorWithIndex> it(annotationImageItk, annotationImageItk->GetLargestPossibleRegion()); + while (!it.IsAtEnd()) + { + if (it.Get() != 0) + { + indices.push_back(it.GetIndex()); + labels.push_back(it.Get()); + } + ++it; + } + + FeatureMatrixType trainingData(indices.size(), featureImageVector.size()); + LabelVectorType trainingLabels = LabelVectorType::Map(labels.data(), labels.size()); + + int j = 0; + for (mitk::Image::Pointer feature : featureImageVector) + { + int i = 0; + mitk::ImagePixelReadAccessor access(feature, feature->GetVolumeData()); + for (auto index : indices) + { + trainingData(i, j) = access.GetPixelByIndexSafe(index); + i++; + } + j++; + } + + auto trainingLabelsPtr = std::make_shared(trainingLabels); + auto trainingDataPtr = std::make_shared(trainingData); + std::pair, std::shared_ptr> result = std::make_pair(trainingLabelsPtr, trainingDataPtr); + + return result; +} + +void ActiveLearning::UpdateLookupTables() +{ + // Create new lookup table from list + // Annotation type is int, but we only use a ushort lookup table + auto lut = vtkSmartPointer::New(); + int lowlim = std::numeric_limits::min(); + int uplim = std::numeric_limits::max(); + lut->SetNumberOfTableValues(uplim - lowlim + 1); + lut->SetTableRange(lowlim, uplim); + for (long i=0; i<(uplim-lowlim+1); ++i) + { + lut->SetTableValue(i, 0.0, 0.0, 0.0, 0.0); + } + for (int j=0; jrowCount(); ++j) + { + int value = m_LabelListModel->item(j, 1)->text().toInt(); + const QColor color = m_LabelListModel->item(j, 0)->background().color(); + lut->SetTableValue(value, color.redF(), color.greenF(), color.blueF(), 1.0); + } + + auto lutMitk = mitk::LookupTable::New(); + lutMitk->SetVtkLookupTable(lut); + + // Set to annotation image and segmentation image + auto * lut_prop = dynamic_cast(m_AnnotationNode->GetProperty("LookupTable")); + lut_prop->SetLookupTable(lutMitk); + m_AnnotationNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + m_AnnotationNode->Modified(); + lut_prop = dynamic_cast(m_SegmentationNode->GetProperty("LookupTable")); + lut_prop->SetLookupTable(lutMitk); + m_SegmentationNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + m_SegmentationNode->Modified(); +} + +void ActiveLearning::UpdatePredictionNodes() +{ + if (m_LabelListModel->rowCount() == 0) return; + + // Build lookup table + vtkSmartPointer lut = vtkSmartPointer::New(); + lut->SetTableRange (0, 1); + lut->SetSaturationRange (0, 0); + lut->SetHueRange (0, 0); + lut->SetValueRange (0, 1); + lut->SetAlphaRange (0, 1); + lut->Build(); + auto lutMitk = mitk::LookupTable::New(); + lutMitk->SetVtkLookupTable(lut); + + for (int i=0; irowCount(); ++i) + { + auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt())); + auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property); + if (nodes->Size() == 1) + { + auto node = nodes->GetElement(0); + QString name = "Prediction "; + name += m_LabelListModel->item(i, 2)->text(); + node->SetName(name.toStdString()); + auto * lut_prop = dynamic_cast(node->GetProperty("LookupTable")); + lut_prop->SetLookupTable(lutMitk); + node->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + node->SetColor(QColorToMitkColor(m_LabelListModel->item(i, 0)->background().color())); + node->Modified(); + } + if (nodes->Size() > 1) mitkThrow(); + } + + // Uncertainty image + auto *lut_prop = dynamic_cast(m_UncertaintyNode->GetProperty("LookupTable")); + if (lut_prop != nullptr) + { + lut_prop->SetLookupTable(lutMitk); + m_UncertaintyNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + } + + vtkSmartPointer lut2 = vtkSmartPointer::New(); + lut2->SetRampToLinear(); + lut2->SetSaturationRange (0., 0.); + lut2->SetHueRange (0., 0.); + lut2->SetValueRange (0., 1.); + lut2->SetAlphaRange (1., 0.); + lut2->Build(); + auto lutMitk2 = mitk::LookupTable::New(); + lutMitk2->SetVtkLookupTable(lut2); + + lut_prop = dynamic_cast(m_UncertaintyRegionNode->GetProperty("LookupTable")); + if (lut_prop != nullptr) + { + lut_prop->SetLookupTable(lutMitk2); + m_UncertaintyRegionNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + } + + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +} + +const std::string ActiveLearning::VIEW_ID = "org.mitk.views.activelearning"; + + +/* ================================================================== + * PROTECTED SLOTS + * =============================================================== */ + + +void ActiveLearning::OnSignalIncrementProgress() +{ + m_Controls.m_ProgressBar->setValue(m_Controls.m_ProgressBar->value() + 1); +} + +void ActiveLearning::OnSignalSetProgress(int value) +{ + m_Controls.m_ProgressBar->setValue(value); +} + +void ActiveLearning::OnSignalResetProgress() +{ + m_Controls.m_ProgressBar->setValue(0); +} + +void ActiveLearning::OnSignalSetProgressMaximum(int value) +{ + m_Controls.m_ProgressBar->setMaximum(value); +} + +void ActiveLearning::OnAddLabelPushButtonClicked() +{ + QString labelName = QString("Label ") + QString::number(m_LabelListModel->rowCount() + 1); + QColor labelColor = Qt::GlobalColor(m_LabelListModel->rowCount() % 12 + 7); // We only want Qt default colors 7 to 18 + + // Create icon + QStandardItem* colorSquare = new QStandardItem; + colorSquare->setBackground(labelColor); + colorSquare->setEditable(false); + QPixmap colorPixmap(20, 20); + colorPixmap.fill(labelColor); + colorSquare->setIcon(QIcon(colorPixmap)); + + // Key is the highest existing key + 1 + int value = 1; + if (m_LabelListModel->rowCount() >= 1) + { + value = m_LabelListModel->item(m_LabelListModel->rowCount() - 1, 1)->text().toInt() + 1; + } + QStandardItem* valueItem = new QStandardItem; + valueItem->setText(QString::number(value)); + + // Create label item + QStandardItem* label = new QStandardItem(labelName); + + // Make list and insert + QList list; + list.append(colorSquare); + list.append(valueItem); + list.append(label); + m_LabelListModel->appendRow(list); + m_Controls.m_LabelTableView->selectRow(m_LabelListModel->rowCount() - 1); + + // If this is the first label, we activate the paint button + // We also have to set the data node color for this one, because for 1 values that color seems to define the rendered color + if (m_LabelListModel->rowCount() == 1) + { + OnPaintToolButtonClicked(); + } + + // Update colors + UpdateLookupTables(); +} + +void ActiveLearning::OnRemoveLabelPushButtonClicked() +{ + // can't remove last label + if (m_LabelListModel->rowCount() <= 1) return; + + QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel(); + if (selection->hasSelection()) + { + unsigned int removeIndex = selection->selectedRows().first().row(); + QString removeMessage = QString("Remove label '") + m_LabelListModel->item(removeIndex, 2)->text() + QString("'?"); + QMessageBox::StandardButton removeReply; + removeReply = QMessageBox::question(m_Parent, + "Remove Label", + removeMessage, + QMessageBox::Yes | QMessageBox::No); + if (removeReply == QMessageBox::Yes) + { + AnnotationPixelType removeValue = m_LabelListModel->item(removeIndex, 1)->text().toInt(); + m_LabelListModel->removeRow(removeIndex); + if (!m_Interactor->IsUsed()) + { + ResetLabels(); + } + else + { + itk::Image::Pointer imageItk; + mitk::CastToItkImage(m_AnnotationImage, imageItk); + auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion()); + while (!it.IsAtEnd()) + { + if (it.Get() == removeValue) + it.Set(0); + ++it; + } + mitk::GrabItkImageMemory(imageItk, m_AnnotationImage); + UpdateLookupTables(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + } +} + +void ActiveLearning::OnPaintToolButtonClicked() +{ + m_Controls.m_PaintToolButton->setChecked(true); + QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel(); + int row(0); + if (selection->hasSelection()) + { + row = selection->selectedRows().first().row(); + } + else + { + m_Controls.m_LabelTableView->selectRow(0); + } + m_Interactor->SetPaintingPixelValue(m_LabelListModel->item(row, 1)->text().toInt()); +} + +void ActiveLearning::OnEraseToolButtonClicked() +{ + m_Controls.m_EraseToolButton->setChecked(true); + m_Interactor->SetPaintingPixelValue(0); +} + +void ActiveLearning::OnSaveSegmentationPushButtonClicked() +{ + auto newNode = mitk::DataNode::New(); + newNode->SetName("Segmentation"); + newNode->SetBoolProperty("binary", false); + newNode->SetOpacity(1.0); + newNode->SetVisibility(false); + newNode->SetData(m_SegmentationImage->Clone()); + this->GetDataStorage()->Add(newNode); +} + +void ActiveLearning::OnSavePredictionsPushButtonClicked() +{ + if (m_LabelListModel->rowCount() < 1) return; + + for (int i=0; irowCount(); ++i) + { + auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt())); + auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property); + if (nodes->Size() == 1) + { + auto sourceNode = nodes->GetElement(0); + mitk::Image::Pointer sourceImage = dynamic_cast(sourceNode->GetData()); + auto newNode = mitk::DataNode::New(); + QString name = "Prediction "; + name += m_LabelListModel->item(i, 2)->text(); + newNode->SetName(name.toStdString()); + newNode->SetBoolProperty("binary", false); + newNode->SetOpacity(1.0); + newNode->SetVisibility(false); + newNode->SetProperty("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt())); + newNode->SetData(sourceImage->Clone()); + this->GetDataStorage()->Add(newNode); + } + if (nodes->Size() > 1) mitkThrow(); + } +} + +void ActiveLearning::OnActiveGuidanceCheckBoxToggled(int toggled) +{ + m_Guidance = (toggled > 0); +} + +void ActiveLearning::OnColorIconDoubleClicked(const QModelIndex& index) +{ + // Check if click is really from color icon + if (index.column() != 0) + { + return; + } + else + { + // Color change dialog + QColor setColor = QColorDialog::getColor(m_LabelListModel->itemFromIndex(index)->background().color(), m_Parent, "Select Label Color"); + if (setColor.isValid()) + { + m_LabelListModel->itemFromIndex(index)->setBackground(setColor); + QPixmap colorPixmap(20, 20); + colorPixmap.fill(setColor); + m_LabelListModel->itemFromIndex(index)->setIcon(QIcon(colorPixmap)); + UpdateLookupTables(); + UpdatePredictionNodes(); + } + } +} + +void ActiveLearning::OnLabelListSelectionChanged(const QItemSelection& selected, + const QItemSelection& /*deselected*/) +{ + if (selected.empty()) return; + if (m_Controls.m_EraseToolButton->isChecked()) OnPaintToolButtonClicked(); + + // This assumes that only one item can be selected (single selection table view) + try + { + int labelValue = m_LabelListModel->item(selected.indexes()[0].row(), 1)->text().toInt(); + m_Interactor->SetPaintingPixelValue(labelValue); + } + catch (...) + { + m_Interactor->SetPaintingPixelValue(-1); + } +} + +void ActiveLearning::OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/) +{ + UpdatePredictionNodes(); +} + +void ActiveLearning::OnInitializationFinished() +{ + // Check if all futures are finished + for (auto watcher : m_FeatureCalculationWatchers) + { + if (watcher->isFinished() == false) {return;} + } + + // Empty feature vector + m_FeatureImageVector.clear(); + + // Insert features into feature vector and data storage + for (unsigned int i=0; iresult(); + for (unsigned int j=0; jSetData(result[j].first); + node->SetName(result[j].second); + node->SetBoolProperty("helper object", true); + node->SetVisibility(false); + this->GetDataStorage()->Add(node, m_Nodes[i]); + } + } + + // Show controls + m_Controls.m_LabelControlsFrame->setVisible(true); + m_Controls.m_InitializePushButton->setHidden(true); + emit SignalResetProgress(); + + // Delete watchers + for (auto watcher : m_FeatureCalculationWatchers) + { + delete watcher; + } + m_FeatureCalculationWatchers.clear(); +} + +void ActiveLearning::OnUpdatePredictionsPushButtonClicked() +{ + if (m_LabelListModel->rowCount() < 1) return; + + m_Controls.m_UpdatePredictionsPushButton->setDisabled(true); + emit SignalSetProgressMaximum(4); + + // Clear old predictions + for (int i=0; irowCount(); ++i) + { + auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt())); + auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property); + if (nodes->Size() == 1) + { + this->GetDataStorage()->Remove(nodes); + } + if (nodes->Size() > 1) mitkThrow(); + } + + // Classifier + auto classifier = mitk::VigraRandomForestClassifier::New(); + classifier->SetTreeCount(m_NumberOfTrees); + classifier->SetMaximumTreeDepth(m_MaximumTreeDepth); + classifier->SetSamplesPerTree(m_SamplesPerTree); + + mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + QFuture>> future; + future = QtConcurrent::run(this, &ActiveLearning::CalculatePrediction, m_AnnotationImage, m_FeatureImageVector, referenceImage, classifier, m_PredictionMatrix); + m_PredictionCalculationWatcher = new QFutureWatcher>>(); + m_PredictionCalculationWatcher->setFuture(future); + + connect(m_PredictionCalculationWatcher, SIGNAL(finished()), + this, SLOT(OnPredictionCalculationFinished())); +} + +void ActiveLearning::OnPresetComboBoxStateChanged(int state) +{ + if (state == 0) return; + LoadPreset(m_PresetNames[state]); +} + +void ActiveLearning::OnPredictionCalculationFinished() +{ + auto result = m_PredictionCalculationWatcher->result(); + + m_SegmentationImage = result.first; + m_SegmentationImage->Modified(); + m_SegmentationNode->SetData(m_SegmentationImage); + m_SegmentationNode->Modified(); + + for (unsigned int i=0; iitem(i, 2)->text(); + node->SetName(name.toStdString()); + node->SetBoolProperty("binary", false); + node->SetVisibility(false); + node->SetOpacity(0.3); + node->SetColor(QColorToMitkColor(m_LabelListModel->item(i, 0)->background().color())); + node->SetProperty("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt())); + node->SetData(result.second[i]); + this->GetDataStorage()->Add(node, m_Nodes[0]); + } + + QFuture> future; + future = QtConcurrent::run(this, &ActiveLearning::CalculateUncertainty, result.second, m_SegmentationImage, m_Guidance); + m_UncertaintyCalculationWatcher = new QFutureWatcher>(); + m_UncertaintyCalculationWatcher->setFuture(future); + connect(m_UncertaintyCalculationWatcher, SIGNAL(finished()), + this, SLOT(OnUncertaintyCalculationFinished())); +} + +void ActiveLearning::OnUncertaintyCalculationFinished() +{ + m_UncertaintyImage = m_UncertaintyCalculationWatcher->result().first; + m_UncertaintyImage->Modified(); + m_UncertaintyNode->SetData(m_UncertaintyImage); + m_UncertaintyNode->Modified(); + if (m_UncertaintyCalculationWatcher->result().second.IsNotNull()) + { + m_UncertaintyRegion = m_UncertaintyCalculationWatcher->result().second; + m_UncertaintyRegion->Modified(); + m_UncertaintyRegionNode->SetData(m_UncertaintyRegion); + m_UncertaintyRegionNode->Modified(); + } + + UpdateLookupTables(); + UpdatePredictionNodes(); + + emit SignalResetProgress(); + m_Controls.m_UpdatePredictionsPushButton->setEnabled(true); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +} + +void ActiveLearning::OnBrushSizeSliderValueChanged(int value) +{ + QString labelText = "Size "; + labelText += QString::number(value); + m_Controls.m_BrushSizeLabel->setText(labelText); + m_Interactor->SetSize(value); +} + +/* ================================================================== + * PROTECTED + * =============================================================== */ + + +void ActiveLearning::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, + const QList& nodes) +{ + if (!SelectionAllImages(nodes)) + { + SetInitializeReady(false); + return; + } + + if (nodes.length() >= 2) + { + // First selection is the reference (could be any other) + mitk::Image::Pointer referenceImage = dynamic_cast(nodes[0]->GetData()); + mitk::BaseGeometry* referenceGeometry = referenceImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps + + for (int i=1; i(nodes[i]->GetData()); + mitk::BaseGeometry* currentGeometry = currentImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps + + if (!mitk::Equal(*currentGeometry, *referenceGeometry, mitk::eps, true)) + { + SetInitializeReady(false); + return; + } + } + } + + // All nodes have the same geometry, allow init + SetInitializeReady(true); +} + +void ActiveLearning::SetFocus() +{ +} + + +/* ================================================================== + * PRIVATE + * =============================================================== */ + + +void ActiveLearning::SetInitializeReady(bool ready) +{ + if (ready) + { + // get selection, check again just to be sure + auto nodes = this->GetDataManagerSelection(); + if (!SelectionAllImages(nodes)) return; + + m_Controls.m_InitializePushButton->setEnabled(true); + + if (!m_Active) + { + QString nameList = QString::fromStdString(nodes[0]->GetName()); + if (nodes.length() >= 2) + { + for (int i=1; i"); + nameList += QString::fromStdString(nodes[i]->GetName()); + } + } + m_Controls.m_InitializeLabel->setText(nameList); + } + } + else + { + m_Controls.m_InitializePushButton->setDisabled(true); + if (!m_Active) + { + m_Controls.m_InitializeLabel->setText("Selected images must have matching geometries"); + + } + } +} diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h new file mode 100644 index 0000000000..8de1efde7b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h @@ -0,0 +1,208 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef ActiveLearning_h +#define ActiveLearning_h + +#include +#include +#include "ui_QmitkActiveLearningControls.h" + +// Qt +#include +#include +#include +#include + +// MITK +#include +#include +#include + +#include + +/** +\brief ActiveLearning + +\warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. + +\sa QmitkAbstractView +\ingroup ${plugin_target}_internal +*/ + + + +// Just a helper class +class NotEditableDelegate : public QItemDelegate +{ + Q_OBJECT +public: + explicit NotEditableDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {} +protected: + QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const {return Q_NULLPTR;} +}; + + + +class ActiveLearning : public QmitkAbstractView +{ + + Q_OBJECT + +public slots: + + void Initialize(); + +public: + + typedef unsigned short AnnotationPixelType; + typedef double FeaturePixelType; + typedef int LabelPixelType; + typedef Eigen::Matrix FeatureMatrixType; + typedef Eigen::Matrix LabelVectorType; + + ActiveLearning(); + ~ActiveLearning(); + + void CreateQtPartControl(QWidget *parent) override; + + std::vector> CalculateFeatures(const mitk::Image::Pointer inputImage); + + std::pair > CalculatePrediction(const mitk::Image::Pointer annotationImage, + const std::vector &featureImageVector, + const mitk::Image::Pointer referenceImage, + mitk::AbstractClassifier *classifier, + std::shared_ptr predictionMatrix); + + std::pair, std::shared_ptr > GetTrainingData(const mitk::Image::Pointer annotationImage, + const std::vector &featureImageVector); + + std::pair CalculateUncertainty(const std::vector &probabilityImageVector, + const mitk::Image::Pointer referenceImage, + bool guidance); + + void UpdateLookupTables(); + + void UpdatePredictionNodes(); + + void LoadPreset(const std::string); + + static const std::string VIEW_ID; + +signals: + + void SignalIncrementProgress(); + + void SignalSetProgress(int value); + + void SignalResetProgress(); + + void SignalSetProgressMaximum(int value); + +protected slots: + + void OnSignalIncrementProgress(); + + void OnSignalSetProgress(int value); + + void OnSignalResetProgress(); + + void OnSignalSetProgressMaximum(int value); + + void OnAddLabelPushButtonClicked(); + + void OnRemoveLabelPushButtonClicked(); + + void OnPaintToolButtonClicked(); + + void OnEraseToolButtonClicked(); + + void OnSaveSegmentationPushButtonClicked(); + + void OnSavePredictionsPushButtonClicked(); + + void OnActiveGuidanceCheckBoxToggled(int toggled); + + void OnColorIconDoubleClicked(const QModelIndex& index); + + void OnLabelListSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/); + + void OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/); + + void OnBrushSizeSliderValueChanged(int value); + + void OnUpdatePredictionsPushButtonClicked(); + + void OnPresetComboBoxStateChanged(); + + void OnPredictionCalculationFinished(); + + void OnUncertaintyCalculationFinished(); + + void OnInitializationFinished(); + +protected: + + void OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, + const QList& nodes) override; + + void SetFocus() override; + + void ResetLabels(); + + void SetInitializeReady(bool ready); + + Ui::ActiveLearningControls m_Controls; + QWidget* m_Parent; + + mitk::Image::Pointer m_AnnotationImage; + mitk::DataNode::Pointer m_AnnotationNode; + + mitk::Image::Pointer m_SegmentationImage; + mitk::DataNode::Pointer m_SegmentationNode; + + mitk::Image::Pointer m_UncertaintyImage; + mitk::DataNode::Pointer m_UncertaintyNode; + + mitk::Image::Pointer m_UncertaintyRegion; + mitk::DataNode::Pointer m_UncertaintyRegionNode; + + std::vector m_FeatureImageVector; + std::vector>>*> m_FeatureCalculationWatchers; + + QFutureWatcher>>* m_PredictionCalculationWatcher; + QFutureWatcher>* m_UncertaintyCalculationWatcher; + + QStandardItemModel* m_LabelListModel; + + mitk::ActiveLearningInteractor::Pointer m_Interactor; + + QList m_Nodes; + +private: + + bool m_Active; + bool m_Guidance; + + int m_NumberOfTrees; + int m_MaximumTreeDepth; + float m_SamplesPerTree; + + std::shared_ptr m_PredictionMatrix; + +}; + +#endif // ActiveLearning_h diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearningControls.ui b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearningControls.ui new file mode 100644 index 0000000000..2940b133e7 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearningControls.ui @@ -0,0 +1,554 @@ + + + ActiveLearningControls + + + + 0 + 0 + 352 + 581 + + + + + 0 + 0 + + + + QmitkTemplate + + + + + + Initialize with selection + + + false + + + false + + + false + + + false + + + + + + + <font color="red">Selection must have matching dimensions and transforms</font> + + + true + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + QFrame::Sunken + + + 1 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add Label + + + + + + + Remove Label + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::Sunken + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + true + + + false + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + + + + :/org.mitk.gui.qt.activelearning/resources/paint_icon_200.png:/org.mitk.gui.qt.activelearning/resources/paint_icon_200.png + + + + 25 + 25 + + + + true + + + true + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + ... + + + + :/org.mitk.gui.qt.activelearning/resources/erase_icon_100.png:/org.mitk.gui.qt.activelearning/resources/erase_icon_100.png + + + + 25 + 25 + + + + true + + + true + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 52 + 0 + + + + Size 1 + + + + + + + 1 + + + 20 + + + Qt::Horizontal + + + + + + + + + + Active Guidance + + + + + + + + 0 + 40 + + + + Update + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Save to Data Manager + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Segmentation + + + + + + + Predictions + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Qt::Horizontal + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + 0 + + + false + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.cpp b/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.cpp new file mode 100644 index 0000000000..4afbb3822f --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.cpp @@ -0,0 +1,33 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#include "org_mitk_gui_qt_activelearning_Activator.h" +#include "QmitkActiveLearning.h" + +namespace mitk { + +void org_mitk_gui_qt_activelearning_Activator::start(ctkPluginContext* context) +{ + BERRY_REGISTER_EXTENSION_CLASS(ActiveLearning, context) +} + +void org_mitk_gui_qt_activelearning_Activator::stop(ctkPluginContext* context) +{ + Q_UNUSED(context) +} + +} diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.h b/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.h new file mode 100644 index 0000000000..02f242903c --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/org_mitk_gui_qt_activelearning_Activator.h @@ -0,0 +1,41 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef org_mitk_gui_qt_activelearning_Activator_h +#define org_mitk_gui_qt_activelearning_Activator_h + +#include + +namespace mitk { + +class org_mitk_gui_qt_activelearning_Activator : + public QObject, public ctkPluginActivator +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_activelearning") + Q_INTERFACES(ctkPluginActivator) + +public: + + void start(ctkPluginContext* context); + void stop(ctkPluginContext* context); + +}; // org_mitk_gui_qt_activelearning_Activator + +} + +#endif // org_mitk_gui_qt_activelearning_Activator_h