diff --git a/Modules/Classification/CLActiveLearning/CMakeLists.txt b/Modules/Classification/CLActiveLearning/CMakeLists.txt new file mode 100644 index 0000000000..3aec02f60a --- /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 #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..3875cc63ea --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h @@ -0,0 +1,60 @@ +/*=================================================================== + +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 + +namespace mitk +{ + + class MITKCLACTIVELEARNING_EXPORT ActiveLearningInteractor : public DataInteractor + { + + public: + + mitkClassMacro(ActiveLearningInteractor, DataInteractor) + itkFactorylessNewMacro(Self) + + void SetPaintingPixelValue(mitk::LabelSetImage::PixelType value){m_PaintingPixelValue = value;} + + 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); + + itk::Index<3> m_LastPixelIndex; + mitk::LabelSetImage::PixelType 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..9615570182 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningSuggestRegionFilter.h @@ -0,0 +1,149 @@ +/*=================================================================== + +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 + +// 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_ThresholdFilter = itk::BinaryThresholdImageFilter::New(); + m_CCFilter = itk::ConnectedComponentImageFilter::New(); + m_ThresholdOutputFilter = itk::BinaryThresholdImageFilter::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_ThresholdFilter->SetInput(input); + m_ThresholdFilter->Modified(); + + // Run pipeline to get image with connected components + m_CCFilter->SetInput(m_ThresholdFilter->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::BinaryThresholdImageFilter::Pointer m_ThresholdFilter; + typename itk::ConnectedComponentImageFilter::Pointer m_CCFilter; + typename itk::BinaryThresholdImageFilter::Pointer m_ThresholdOutputFilter; + + }; + +} + +#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..c62371b15f --- /dev/null +++ b/Modules/Classification/CLActiveLearning/include/mitkSelectHighestUncertaintyRegionFilter.h @@ -0,0 +1,81 @@ +/*=================================================================== + +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::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..871d3ff218 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml new file mode 100644 index 0000000000..2d2fa3965a --- /dev/null +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + 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..230da68305 --- /dev/null +++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp @@ -0,0 +1,245 @@ + +/*=================================================================== +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 + + +// Helper function to get an image from a data node. +static mitk::LabelSetImage::Pointer GetImage(mitk::DataNode::Pointer dataNode) +{ + if (dataNode.IsNull()) + mitkThrow(); + + mitk::LabelSetImage::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::LabelSetImage::Pointer image, unsigned int timeStep) +{ + mitk::TimeGeometry::Pointer timeGeometry = image->GetTimeGeometry(); + + if (timeGeometry.IsNull()) + mitkThrow(); + + auto geometry = timeGeometry->GetGeometryForTimeStep(timeStep); + + if (geometry.IsNull()) + mitkThrow(); + + return geometry; +} + +static std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, mitk::BaseGeometry* geometry) +{ + 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; +} + +//// Helper function to multiplex the actual Paint function call for different +//// pixel types. As it's cumbersome and ugly, you may want to avoid such +//// functions by using ITK for the actual painting and use the ITK access +//// macros like we did for the ActiveLearningFilter. +//static void Paint(mitk::Image::Pointer image, itk::Index<3> index, unsigned int timeStep) +//{ +// switch (image->GetPixelType().GetComponentType()) +// { +// case itk::ImageIOBase::CHAR: +// Paint(image, index, timeStep); +// break; + +// case itk::ImageIOBase::UCHAR: +// Paint(image, index, timeStep); +// break; + +// case itk::ImageIOBase::SHORT: +// Paint(image, index, timeStep); +// break; + +// case itk::ImageIOBase::USHORT: +// Paint(image, index, timeStep); +// break; + +// case itk::ImageIOBase::INT: +// Paint(image, index, timeStep); +// break; + +// case itk::ImageIOBase::UINT: +// Paint(image, index, timeStep); +// break; + +// default: +// mitkThrow(); +// } +//} + +mitk::ActiveLearningInteractor::ActiveLearningInteractor() : + m_PaintingPixelValue(0) +{ +} + +mitk::ActiveLearningInteractor::~ActiveLearningInteractor() +{ +} + +void mitk::ActiveLearningInteractor::ConnectActionsAndFunctions() +{ + CONNECT_FUNCTION("paint", Paint) + CONNECT_FUNCTION("paint_interpolate", PaintInterpolate) +} + +void mitk::ActiveLearningInteractor::DataNodeChanged() +{ + this->ResetToStartState(); +} + +void mitk::ActiveLearningInteractor::Paint(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 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; + geometry->WorldToIndex<3>(position, 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 != m_LastPixelIndex) + { + mitk::ImagePixelWriteAccessor writeAccessor(image.GetPointer(), image->GetVolumeData(timeStep)); + writeAccessor.SetPixelByIndexSafe(index, m_PaintingPixelValue); + image->Modified(); + this->GetDataNode()->Modified(); + +// mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + m_LastPixelIndex = index; + m_Used = true; + } + } + catch (...) + { + return; + } +} + +void mitk::ActiveLearningInteractor::PaintInterpolate(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 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; + geometry->WorldToIndex<3>(position, 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 != m_LastPixelIndex) + { + // And finally... + mitk::ImagePixelWriteAccessor writeAccessor(image.GetPointer(), image->GetVolumeData(timeStep)); + + // Paint all points between current and last pixel + auto indices = InterpolateIndices(m_LastPixelIndex, index, geometry); + for (auto i : indices) + { + writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue); + } + + image->Modified(); + this->GetDataNode()->Modified(); + +// mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + m_LastPixelIndex = index; + m_Used = true; + } + } + catch (...) + { + return; + } +} 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/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 661c653248..1b878040f7 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,334 +1,344 @@ /*=================================================================== 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 117116171f..f322632745 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 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/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index b07a6c786a..30ca74ea4c 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,75 +1,76 @@ # 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.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.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..832edc158f --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp @@ -0,0 +1,854 @@ +/*=================================================================== + +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 + +// ITK +#include +#include +#include +#include +#include +#include +#include +#include + +typedef double FeaturePixelType; + +// 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); + } +} + +/* ================================================================== + * FEATURES + * =============================================================== */ + +template +static void GaussianSmoothing(const itk::Image* inputImage, double sigma, mitk::Image::Pointer outputImage) +{ + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::DiscreteGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetVariance(sigma*sigma); + filter->Update(); + mitk::GrabItkImageMemory(filter->GetOutput(), outputImage); +} + +template +static void GaussianGradientMagnitude(const itk::Image* inputImage, double sigma, mitk::Image::Pointer outputImage) +{ + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::GradientMagnitudeRecursiveGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetSigma(sigma); + filter->Update(); + mitk::GrabItkImageMemory(filter->GetOutput(), outputImage); +} + +template +static void LaplacianOfGaussian(const itk::Image* inputImage, double sigma, mitk::Image::Pointer outputImage) +{ + typedef itk::Image ImageType; + typedef itk::Image FeatureImageType; + auto filter = itk::LaplacianRecursiveGaussianImageFilter::New(); + filter->SetInput(inputImage); + filter->SetSigma(sigma); + 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]); +// } +//} + +//template +//static void HessianEigenvalues(const itk::Image* inputImage, float sigma, std::vector outputImages) +//{ +// typedef itk::Image ImageType; +// typedef itk::Image, imageDimension> TensorImageType; +// auto filter = itk::HessianRecursiveGaussianImageFilter::New(); + +// ImageType::Pointer o1, o2, o3; +// o1->Allocate(); +// o2->Allocate(); +// o3->Allocate(); + +// filter->SetInput(inputImage); +// filter->SetSigma(sigma); +// filter->Update(); +// TensorImageType::Pointer tensorImage = filter->GetOutput(); + +// itk::ImageRegionIterator tensorIt(tensorImage, tensorImage->GetLargestPossibleRegion()); +// itk::ImageRegionIterator o1It(o1, o1->GetLargestPossibleRegion()); +// itk::ImageRegionIterator o2It(o2, o2->GetLargestPossibleRegion()); +// itk::ImageRegionIterator o3It(o3, o3->GetLargestPossibleRegion()); + +// while (!tensorIt.IsAtEnd()) +// { +// itk::SymmetricSecondRankTensor::EigenValue + +// for (unsigned int i=0; iGetNumberOfOutputs(); i++) +// { +// mitk::GrabItkImageMemory(filter->GetOutput(i), outputImages[i]); +// } +//} + +/* ================================================================== + * 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); + + // 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); + + // ======================================= + // ANNOTATION IMAGE + // ======================================= + + m_AnnotationImage = mitk::LabelSetImage::New(); + + try + { + auto referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + m_AnnotationImage->Initialize(referenceImage); + } + 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(0., 0., 0.); +// m_AnnotationNode->SetBoolProperty("helper object", true); + m_AnnotationImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(m_Nodes[0]->GetName().c_str())); + m_AnnotationImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New("Labels")); + this->GetDataStorage()->Add(m_AnnotationNode, m_Nodes[0]); + + // ======================================= + // PREDICTION IMAGE + // ======================================= + + m_PredictionImage = mitk::Image::New(); + + try + { + mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + m_PredictionImage->Initialize(referenceImage); + } + catch (mitk::Exception& e) + { + MITK_ERROR << "Exception caught: " << e.GetDescription(); + QMessageBox::information(m_Parent, "Error", "Could not initialize prediction image"); + return; + } + + FillWithZeros(m_PredictionImage); + + m_PredictionNode = mitk::DataNode::New(); + m_PredictionNode->SetData(m_PredictionImage); + m_PredictionNode->SetName("Predictions"); + m_PredictionNode->SetColor(0., 0., 0.); +// m_PredictionNode->SetBoolProperty("helper object", true); +// m_PredictionImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(nodes[0]->GetName().c_str())); +// m_PredictionImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New("Predictions")); + this->GetDataStorage()->Add(m_PredictionNode, m_Nodes[0]); + + // ======================================= + // SEGMENTATION IMAGE + // ======================================= + + m_SegmentationImage = mitk::LabelSetImage::New(); + + try + { + auto referenceImage = dynamic_cast(m_Nodes[0]->GetData()); + m_SegmentationImage->Initialize(referenceImage); + } + 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(0., 0., 0.); +// m_PredictionNode->SetBoolProperty("helper object", true); + m_SegmentationImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(m_Nodes[0]->GetName().c_str())); + m_SegmentationImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New("Segmentation")); + this->GetDataStorage()->Add(m_SegmentationNode, 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); + image = mitk::GrabItkImageMemory(itkImage); + node->SetData(image); + } + + // 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); + + // Classifier +// m_Classifier = mitk::VigraRandomForestClassifier::New(); +// m_Classifier->SetTreeCount(50); +// m_Classifier->SetMaximumTreeDepth(10); + + // Automatically add first label + OnAddLabelPushButtonClicked(); + + m_Active = true; +} + + +/* ================================================================== + * PUBLIC + * =============================================================== */ + + +ActiveLearning::ActiveLearning() : + m_Parent(nullptr), + m_AnnotationImage(nullptr), + m_AnnotationNode(nullptr), + m_PredictionImage(nullptr), + m_PredictionNode(nullptr), + m_SegmentationImage(nullptr), + m_SegmentationNode(nullptr), + m_Active(false) +{ + +} + +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); + 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))); + + // Buttons + 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())); + + // Set start configuration + m_Controls.m_LabelControlsFrame->setVisible(false); + SetInitializeReady(false); +} + +void ActiveLearning::ResetLabels() +{ + // Remove all labels but the first + for (auto it=m_AnnotationImage->GetActiveLabelSet()->IteratorBegin(); it!=m_AnnotationImage->GetActiveLabelSet()->IteratorConstEnd(); it++) + { + if (it->first != 0) + { + m_AnnotationImage->GetActiveLabelSet()->RemoveLabel(it->first); + } + } + + // Fill with labels from list + for (int i=0; irowCount(); i++) + { + QString name = m_LabelListModel->item(i, 2)->text(); + m_LabelListModel->item(i, 1)->setText(QString::number(i + 1)); + QColor color = m_LabelListModel->item(i)->background().color(); + m_AnnotationImage->GetActiveLabelSet()->AddLabel(name.toStdString(), QColorToMitkColor(color)); + } +} + +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)); + ss << "GaussianSmoothing (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(gaussImage, ss.str())); + ss.str(""); + + auto gradMagImage = mitk::Image::New(); + AccessByItk_n(inputImage, GaussianGradientMagnitude, (sigma, gradMagImage)); + ss << "GaussianGradientMagnitude (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(gradMagImage, ss.str())); + ss.str(""); + + auto logImage = mitk::Image::New(); + AccessByItk_n(inputImage, LaplacianOfGaussian, (sigma, logImage)); + ss << "LaplacianOfGaussian (" << std::fixed << std::setprecision(2) << sigma << ")"; + result.push_back(std::pair(logImage, ss.str())); + ss.str(""); + +// 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)); +// ss << "StructureTensorEigenvalue1 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(structImage1, ss.str())); +// ss.str(""); +// ss << "StructureTensorEigenvalue2 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(structImage2, ss.str())); +// ss.str(""); +// if (inputImage->GetDimension() == 3) +// { +// ss << "StructureTensorEigenvalue3 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(structImage3, ss.str())); +// ss.str(""); +// } + +// 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)); +// ss << "HessianEigenvalue1 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(hessianImage1, ss.str())); +// ss.str(""); +// ss << "HessianEigenvalue2 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(hessianImage2, ss.str())); +// ss.str(""); +// if (inputImage->GetDimension() == 3) +// { +// ss << "HessianEigenvalue3 (" << std::fixed << std::setprecision(2) << sigma << ")"; +// result.push_back(std::pair(hessianImage3, ss.str())); +// ss.str(""); +// } + } + + return result; +} + +std::pair ActiveLearning::GetTrainingData(const mitk::LabelSetImage::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()); + } + } + + Eigen::MatrixXd trainingData(indices.size(), featureImageVector.size()); + Eigen::VectorXi trainingLabels = Eigen::VectorXi::Map(labels.data(), labels.size()); + + int i = 0; + int j = 0; + for (mitk::Image::Pointer feature : featureImageVector) + { + mitk::ImagePixelReadAccessor access(feature, feature->GetVolumeData()); + for (auto index : indices) + { + trainingData(i, j) = access.GetPixelByIndexSafe(index); + i++; + } + j++; + } + + return std::pair(&trainingLabels, &trainingData); +} + +const std::string ActiveLearning::VIEW_ID = "org.mitk.views.activelearning"; + + +/* ================================================================== + * PROTECTED SLOTS + * =============================================================== */ + + +void ActiveLearning::OnAddLabelPushButtonClicked() +{ + QString labelName = QString("Label ") + QString::number(m_AnnotationImage->GetActiveLabelSet()->ReverseIteratorBegin()->first + 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 = (int)m_AnnotationImage->GetActiveLabelSet()->ReverseIteratorBegin()->first + 1; + QStandardItem* valueItem = new QStandardItem(); + valueItem->setText(QString::number(value)); + + // Create label item + QStandardItem* label = new QStandardItem(labelName); + + // Add to image + m_AnnotationImage->GetActiveLabelSet()->AddLabel(labelName.toStdString(), QColorToMitkColor(labelColor)); + PrintAllLabels(m_AnnotationImage); + + // Make list and insert + QList list; + list.append(colorSquare); + list.append(valueItem); + list.append(label); + m_LabelListModel->appendRow(list); + + // 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(); + m_AnnotationNode->SetColor(QColorToMitkColor(labelColor)); + } + + // Select newly added label + m_Controls.m_LabelTableView->selectRow(m_LabelListModel->rowCount() - 1); +} + +void ActiveLearning::OnRemoveLabelPushButtonClicked() +{ + 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) + { + // if there are no annotations, reset labels + if (m_Interactor->IsUsed()) + { + std::vector labels; + labels.push_back(m_LabelListModel->item(removeIndex, 1)->text().toInt()); + m_AnnotationImage->RemoveLabels(labels); + m_LabelListModel->removeRow(removeIndex); + } + else + { + m_LabelListModel->removeRow(removeIndex); + ResetLabels(); + } + PrintAllLabels(m_AnnotationImage); + } + } +} + +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()); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(m_LabelListModel->item(row, 1)->text().toInt()); +} + +void ActiveLearning::OnEraseToolButtonClicked() +{ + m_Controls.m_EraseToolButton->setChecked(true); + m_Interactor->SetPaintingPixelValue(0); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(0); +} + +void ActiveLearning::OnActivateGuidancePushButtonToggled(bool toggled) +{ + +} + +void ActiveLearning::OnSaveSegmentationPushButtonClicked() +{ + +} + +void ActiveLearning::OnSavePredictionsPushButtonClicked() +{ + +} + +void ActiveLearning::OnExportSegmentationPushButtonClicked() +{ + +} + +void ActiveLearning::OnExportPredictionsPushButtonClicked() +{ + +} + +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)); + + // Set color on label + m_AnnotationImage->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(index.row(), 1)->text().toInt())->SetColor(QColorToMitkColor(setColor)); + m_AnnotationImage->GetActiveLabelSet()->UpdateLookupTable(m_LabelListModel->item(index.row(), 1)->text().toInt()); + PrintAllLabels(m_AnnotationImage); + + // If this is the label with value 1 we have to change the data node color + if (m_LabelListModel->item(index.row(), 1)->text().toInt() == 1) + { + m_AnnotationNode->SetColor(QColorToMitkColor(setColor)); + } + } + } +} + +void ActiveLearning::OnLabelListSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) +{ + if (selected.empty()) return; + // 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); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(labelValue); + } + catch (...) + { + m_Interactor->SetPaintingPixelValue(0); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(0); + } +} + +void ActiveLearning::OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/) +{ + auto item = m_LabelListModel->itemFromIndex(topLeft); + if (item->column() != 2) return; + m_AnnotationImage->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(item->row(), 1)->text().toInt())->SetName(item->text().toStdString()); +} + +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); + MITK_INFO << "Features: " << m_FeatureImageVector.size(); + + // Delete watchers + for (auto watcher : m_FeatureCalculationWatchers) + { + delete watcher; + } + m_FeatureCalculationWatchers.clear(); +} + +void ActiveLearning::OnUpdatePredictionsPushButtonClicked() +{ + +} + +/* ================================================================== + * 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..e0ce932d75 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h @@ -0,0 +1,148 @@ +/*=================================================================== + +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: + + ActiveLearning(); + ~ActiveLearning(); + + void CreateQtPartControl(QWidget *parent) override; + + std::vector> CalculateFeatures(const mitk::Image::Pointer inputImage); + + std::pair GetTrainingData(const mitk::LabelSetImage::Pointer annotationImage, const std::vector featureImageVector); + + static const std::string VIEW_ID; + +protected slots: + + void OnAddLabelPushButtonClicked(); + + void OnRemoveLabelPushButtonClicked(); + + void OnPaintToolButtonClicked(); + + void OnEraseToolButtonClicked(); + + void OnActivateGuidancePushButtonToggled(bool toggled); + + void OnSaveSegmentationPushButtonClicked(); + + void OnSavePredictionsPushButtonClicked(); + + void OnExportSegmentationPushButtonClicked(); + + void OnExportPredictionsPushButtonClicked(); + + void OnColorIconDoubleClicked(const QModelIndex& index); + + void OnLabelListSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/); + + void OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/); + + void OnUpdatePredictionsPushButtonClicked(); + + 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::LabelSetImage::Pointer m_AnnotationImage; + mitk::DataNode::Pointer m_AnnotationNode; + + mitk::Image::Pointer m_PredictionImage; + mitk::DataNode::Pointer m_PredictionNode; + + mitk::LabelSetImage::Pointer m_SegmentationImage; + mitk::DataNode::Pointer m_SegmentationNode; + + std::vector m_FeatureImageVector; + std::vector>>*> m_FeatureCalculationWatchers; + + QStandardItemModel* m_LabelListModel; + + mitk::ActiveLearningInteractor::Pointer m_Interactor; + + QList m_Nodes; + +// mitk::VigraRandomForestClassifier::Pointer m_Classifier; + +private: + + bool m_Active; + +}; + +#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..2d6d40291f --- /dev/null +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearningControls.ui @@ -0,0 +1,469 @@ + + + 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 + + + + + + + + + + + 0 + 40 + + + + Update Predictions + + + + + + + + 0 + 40 + + + + Activate Guidance + + + true + + + + + + + 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 + + + + + + + + + + Export + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Segmentation + + + + + + + Predictions + + + + + + + + + + + + + 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