diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml
index ef1d27da29..4e04105daf 100644
--- a/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml
+++ b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml
@@ -1,29 +1,32 @@
+
+
+
diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml
index 2d2fa3965a..88714694d1 100644
--- a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml
+++ b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml
@@ -1,17 +1,20 @@
+
+
+
diff --git a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp
index c74ca8be65..3d95c17cd4 100644
--- a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp
+++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp
@@ -1,549 +1,557 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Helper function to get an image from a data node.
static mitk::Image::Pointer GetImage(mitk::DataNode::Pointer dataNode)
{
if (dataNode.IsNull())
mitkThrow();
mitk::Image::Pointer image = dynamic_cast(dataNode->GetData());
if (image.IsNull())
mitkThrow();
return image;
}
// Helper function to get a geometry of an image for a specific time step.
static mitk::BaseGeometry::Pointer GetGeometry(mitk::Image* image, unsigned int timeStep)
{
if (image == nullptr)
mitkThrow();
mitk::TimeGeometry::Pointer timeGeometry = image->GetTimeGeometry();
if (timeGeometry.IsNull())
mitkThrow();
auto geometry = timeGeometry->GetGeometryForTimeStep(timeStep);
if (geometry.IsNull())
mitkThrow();
return geometry;
}
static double EuclideanDistance(const mitk::Point3D p1, const mitk::Point3D p2)
{
return std::sqrt( (p1[0] - p2[0])*(p1[0] - p2[0]) +
(p1[1] - p2[1])*(p1[1] - p2[1]) +
(p1[2] - p2[2])*(p1[2] - p2[2]) );
}
static double EuclideanDistance2D(const mitk::Point2D p1, const mitk::Point2D p2)
{
return std::sqrt( (p1[0] - p2[0])*(p1[0] - p2[0]) +
(p1[1] - p2[1])*(p1[1] - p2[1]) );
}
static double ScalarProduct(const mitk::Point3D p1, const mitk::Point3D p2)
{
return p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2];
}
static double ScalarProduct2D(const mitk::Point2D p1, const mitk::Point2D p2)
{
return p1[0]*p2[0] + p1[1]*p2[1];
}
static std::vector> InterpolateIndices2D(itk::Index<2> startIndex, itk::Index<2> endIndex, const mitk::PlaneGeometry* geometry, unsigned int size)
{
if (geometry == nullptr)
mitkThrow();
std::vector> resultIndices;
mitk::Point2D startPoint;
mitk::Point2D endPoint;
startPoint[0] = startIndex[0];
startPoint[1] = startIndex[1];
endPoint[0] = endIndex[0];
endPoint[1] = endIndex[1];
geometry->IndexToWorld(startPoint, startPoint);
geometry->IndexToWorld(endPoint, endPoint);
// Distance between end points
double dist = EuclideanDistance2D(startPoint, endPoint);
// Define region between startIndex and endIndex, padded by (size - 1)
int regionBounds[4];
for (int i=0; i<2; ++i)
{
regionBounds[2*i] = std::min(startIndex[i], endIndex[i]) - (size - 1);
regionBounds[2*i+1] = std::max(startIndex[i], endIndex[i]) + (size - 1);
}
// We only have a threshold given in pixels (size), but image can be spaced in different units.
// To get the corresponding distances, transform unit vectors and get their lengths.
// The minimum spacing will be what we compare to.
double spacings[2];
spacings[0] = geometry->GetExtentInMM(0) / geometry->GetExtent(0);
spacings[1] = geometry->GetExtentInMM(1) / geometry->GetExtent(1);
- double minSpacing = (spacings[0] > spacings[1]) ? spacings[0] : spacings[1];
+ double minSpacing = (spacings[0] > spacings[1]) ? spacings[1] : spacings[0];
MITK_INFO << "Interpolating between " << startIndex << " and " << endIndex;
MITK_INFO << "Distance: " << dist;
MITK_INFO << "Minimum extent: " << minSpacing;
double t = 0;
double d = 0;
for (int x = regionBounds[0]; x<=regionBounds[1]; ++x)
{
for (int y = regionBounds[2]; y<=regionBounds[3]; ++y)
{
mitk::Point2D p;
mitk::Point2D index;
index[0] = x;
index[1] = y;
geometry->IndexToWorld(index, p);
- // if there is not distance between start and end, just get distance to start
+ // if there is no distance between start and end, just get distance to start
if (dist < mitk::eps)
{
d = EuclideanDistance2D(startPoint, p);
}
else
{
t = -1./(dist*dist) * ScalarProduct2D(startPoint - p, endPoint - startPoint);
if (t > 0. && t < 1.)
{
d = std::sqrt( ScalarProduct2D(startPoint - p, startPoint - p) +
2. * t * ScalarProduct2D(endPoint - startPoint, startPoint - p) +
t * t * dist * dist );
}
else if (t <= 0.)
{
d = EuclideanDistance2D(startPoint, p);
}
else
{
d = EuclideanDistance2D(endPoint, p);
}
}
if (d <= minSpacing * static_cast(size) / 2.)
{
resultIndices.push_back(itk::Index<2>({index[0], index[1]}));
}
}
}
return resultIndices;
}
static std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, const mitk::BaseGeometry* geometry, unsigned int size)
{
if (geometry == nullptr)
mitkThrow();
std::vector> resultIndices;
mitk::Point3D startPoint;
mitk::Point3D endPoint;
geometry->IndexToWorld(startIndex, startPoint);
geometry->IndexToWorld(endIndex, endPoint);
// Distance between end points
double dist = EuclideanDistance(startPoint, endPoint);
// Define region between startIndex and endIndex, padded by (size - 1)
int regionBounds[6];
for (int i=0; i<3; ++i)
{
regionBounds[2*i] = std::min(startIndex[i], endIndex[i]) - (size - 1);
regionBounds[2*i+1] = std::max(startIndex[i], endIndex[i]) + (size - 1);
}
// We only have a threshold given in pixels (size), but image can be spaced in different units.
// To get the corresponding distances, transform unit vectors and get their lengths.
// The minimum spacing will be what we compare to.
// Could potentially use the spacing to normalize.
double spacingInIndexSystem[3];
double minSpacing = -1.;
for (int i=0; i<3; ++i)
{
itk::Index<3> origin;
origin.Fill(0);
itk::Index<3> index;
index.Fill(0);
index[i] = 1;
mitk::Point3D p_origin;
mitk::Point3D p_index;
geometry->IndexToWorld(origin, p_origin);
geometry->IndexToWorld(index, p_index);
double spacing = EuclideanDistance(p_origin, p_index);
if ( (minSpacing > 0. && spacing < minSpacing) || minSpacing < 0. )
{
minSpacing = spacing;
}
spacingInIndexSystem[i] = spacing;
}
// Iterate over all indices in the given region and get distance to the line between startPoint and endPoint.
// If distance is smaller than size, add to resultIndices.
//
// Let (x1,y1,z1) = startPoint, (x2,y2,z2) = endPoint, (x0,y0,z0) = p a point.
//
// Line is defined by:
// [x1 + (x2-x1) * t]
// v = [y1 + (y2-y1) * t]
// [z1 + (z2-z1) * t]
//
// Then (with * dot product):
// t(p) = - (startPoint - p) * (endPoint - startPoint) / |endPoint - startPoint|^2
//
// And (with x cross product):
// d(p) = |(p - startPoint) x (p - endPoint)| / |endPoint - startPoint|
double t = 0;
double d = 0;
for (int x = regionBounds[0]; x<=regionBounds[1]; ++x)
{
for (int y = regionBounds[2]; y<=regionBounds[3]; ++y)
{
for (int z = regionBounds[4]; z<=regionBounds[5]; ++z)
{
mitk::Point3D p;
itk::Index<3> index = {x,y,z};
geometry->IndexToWorld(index, p);
// if there is not distance between start and end, just get distance to start
if (dist < mitk::eps)
{
d = EuclideanDistance(startPoint, p);
}
else
{
t = -1./(dist*dist) * ScalarProduct(startPoint - p, endPoint - startPoint);
if (t > 0. && t < 1.)
{
d = std::sqrt( ScalarProduct(startPoint - p, startPoint - p) +
2. * t * ScalarProduct(endPoint - startPoint, startPoint - p) +
t * t * dist * dist );
}
else if (t <= 0.)
{
d = EuclideanDistance(startPoint, p);
}
else
{
d = EuclideanDistance(endPoint, p);
}
}
if (d <= minSpacing)
{
resultIndices.push_back(index);
}
}
}
}
return resultIndices;
// ======= OLD INTERPOLATION ========
// std::vector> resultIndices;
// mitk::Point3D startPoint;
// mitk::Point3D endPoint;
// geometry->IndexToWorld(startIndex, startPoint);
// geometry->IndexToWorld(endIndex, endPoint);
// itk::Index<3> indexDelta;
// int indexDeltaInc[3];
// for (int i=0; i<3; i++)
// {
// indexDelta[i] = endIndex[i] - startIndex[i];
// indexDeltaInc[i] = (indexDelta[i] > 0) ? 1 : (indexDelta[i] < 0) ? -1 : 0;
// }
// int argm[3] = {0, 1, 2};
// if (abs(indexDelta[1]) > abs(indexDelta[0]))
// {
// argm[0] = 1;
// argm[1] = 0;
// }
// if (abs(indexDelta[2]) > abs(indexDelta[argm[1]]))
// {
// argm[2] = argm[1];
// argm[1] = 2;
// }
// if (abs(indexDelta[2]) > abs(indexDelta[argm[0]]))
// {
// argm[1] = argm[0];
// argm[0] = 2;
// }
// double slopes[2];
// slopes[0] = (endPoint[argm[1]] - startPoint[argm[1]]) / (endPoint[argm[0]] - startPoint[argm[0]]);
// slopes[1] = (endPoint[argm[2]] - startPoint[argm[2]]) / sqrt((endPoint[argm[1]] - startPoint[argm[1]]) * (endPoint[argm[1]] - startPoint[argm[1]]) + (endPoint[argm[0]] - startPoint[argm[0]]) * (endPoint[argm[0]] - startPoint[argm[0]]));
// itk::Index<3> currentIndex = startIndex;
// mitk::Point3D currentPoint = startPoint;
// while (currentIndex != endIndex)
// {
// currentIndex[argm[0]] += indexDeltaInc[argm[0]];
// geometry->IndexToWorld(currentIndex, currentPoint);
// currentPoint[argm[1]] = startPoint[argm[1]] + slopes[0] * (currentPoint[argm[0]] - startPoint[argm[0]]);
// currentPoint[argm[2]] = startPoint[argm[2]] + slopes[1] * sqrt((currentPoint[argm[1]] - startPoint[argm[1]]) * (currentPoint[argm[1]] - startPoint[argm[1]]) + (currentPoint[argm[0]] - startPoint[argm[0]]) * (currentPoint[argm[0]] - startPoint[argm[0]]));
// geometry->WorldToIndex(currentPoint, currentIndex);
// resultIndices.push_back(currentIndex);
// }
// return resultIndices;
}
mitk::ActiveLearningInteractor::ActiveLearningInteractor() :
m_WorkingSlice(nullptr),
m_WorkingPlane(nullptr),
m_Size(1),
m_PaintingPixelValue(0)
{
}
mitk::ActiveLearningInteractor::~ActiveLearningInteractor()
{
}
void mitk::ActiveLearningInteractor::ConnectActionsAndFunctions()
{
CONNECT_FUNCTION("paint", Paint)
CONNECT_FUNCTION("paint_interpolate", PaintInterpolate)
CONNECT_FUNCTION("set_workingslice", SetWorkingSlice)
CONNECT_FUNCTION("writeback_workingslice", WriteBackWorkingSlice)
}
void mitk::ActiveLearningInteractor::DataNodeChanged()
{
this->ResetToStartState();
}
void mitk::ActiveLearningInteractor::SetWorkingSlice(mitk::StateMachineAction* /*action*/,
mitk::InteractionEvent *event)
{
try
{
auto renderer = event->GetSender();
auto image = GetImage(this->GetDataNode());
auto timeStep = renderer->GetTimeStep();
auto geometry = GetGeometry(image, timeStep);
auto planeGeometry = renderer->GetCurrentWorldPlaneGeometry();
// Check current plane geometry
if (m_WorkingPlane.IsNotNull())
{
bool isSameSlice (true);
isSameSlice &= mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(),
m_WorkingPlane->GetIndexToWorldTransform()->GetMatrix());
isSameSlice &= mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(),
m_WorkingPlane->GetIndexToWorldTransform()->GetOffset());
if (isSameSlice) return;
}
m_WorkingPlane = renderer->GetCurrentWorldPlaneGeometry()->Clone();
// Extract corresponding slice
vtkSmartPointer reslice = vtkSmartPointer::New();
reslice->SetOverwriteMode(false);
reslice->Modified();
auto extract = mitk::ExtractSliceFilter::New(reslice);
extract->SetInput(image);
extract->SetTimeStep(timeStep);
extract->SetWorldGeometry(m_WorkingPlane);
extract->SetResliceTransformByGeometry(geometry);
extract->SetVtkOutputRequest(false);
extract->Modified();
extract->Update();
m_WorkingSlice = extract->GetOutput();
}
catch (itk::ExceptionObject& e)
{
mitkThrow() << "Could not set working slice, because:";
mitkThrow() << e.GetDescription();
}
}
void mitk::ActiveLearningInteractor::WriteBackWorkingSlice(mitk::StateMachineAction* /*action*/,
mitk::InteractionEvent *event)
{
try
{
auto renderer = event->GetSender();
auto image = GetImage(this->GetDataNode());
auto timeStep = renderer->GetTimeStep();
auto geometry = GetGeometry(image, timeStep);
// Write back
vtkSmartPointer reslice = vtkSmartPointer::New();
reslice->SetInputSlice(m_WorkingSlice->GetVtkImageData());
reslice->SetOverwriteMode(true);
reslice->Modified();
mitk::ExtractSliceFilter::Pointer insert = mitk::ExtractSliceFilter::New(reslice);
insert->SetInput(image);
insert->SetTimeStep(timeStep);
insert->SetWorldGeometry(m_WorkingPlane);
insert->SetResliceTransformByGeometry(geometry);
insert->Modified();
insert->Update();
image->Modified();
this->GetDataNode()->Modified();
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
}
catch (itk::ExceptionObject& e)
{
mitkThrow() << "Could not set working slice, because:";
mitkThrow() << e.GetDescription();
}
}
void mitk::ActiveLearningInteractor::Paint(mitk::StateMachineAction* /*action*/,
mitk::InteractionEvent* event)
{
if (m_PaintingPixelValue == -1) mitkThrow() << "Cannot paint negative values";
try
{
auto renderer = event->GetSender();
auto image = GetImage(this->GetDataNode());
auto timeStep = renderer->GetTimeStep();
auto geometry = GetGeometry(image, timeStep);
auto positionEvent = dynamic_cast(event);
auto position = positionEvent->GetPositionInWorld();
if (!geometry->IsInside(position)) return;
// Okay, we're safe. Convert the mouse position to the index of the pixel
// we're pointing at.
itk::Index<3> index;
itk::Index<3> oldIndex;
geometry->WorldToIndex(position, index);
geometry->WorldToIndex(m_LastPosition, oldIndex);
// We don't need to paint over and over again while moving the mouse
// pointer inside the same pixel. That's especially relevant when operating
// on zoomed images.
if (index != oldIndex)
{
// Convert index to slice geometry
m_WorkingSlice->GetGeometry()->WorldToIndex(position, index);
itk::Index<2> indexInPlane2D;
indexInPlane2D[0] = index[0];
indexInPlane2D[1] = index[1];
// Get indices
auto indices = InterpolateIndices2D(indexInPlane2D, indexInPlane2D, m_WorkingPlane, m_Size);
// Fill indices
mitk::ImagePixelWriteAccessor writeAccessor(m_WorkingSlice, m_WorkingSlice->GetSliceData(0));
for (auto i : indices)
{
writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue);
}
m_LastPosition = position;
m_Used = true;
}
}
catch (itk::ExceptionObject& e)
{
mitkThrow() << "Could not paint, because:";
mitkThrow() << e.GetDescription();
}
}
void mitk::ActiveLearningInteractor::PaintInterpolate(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event)
{
if (m_PaintingPixelValue == -1) mitkThrow() << "Cannot paint negative values";
try
{
auto renderer = event->GetSender();
auto image = GetImage(this->GetDataNode());
auto timeStep = renderer->GetTimeStep();
auto geometry = GetGeometry(image, timeStep);
auto positionEvent = dynamic_cast(event);
auto position = positionEvent->GetPositionInWorld();
if (!geometry->IsInside(position)) return;
// Okay, we're safe. Convert the mouse position to the index of the pixel
// we're pointing at.
itk::Index<3> index;
itk::Index<3> oldIndex;
geometry->WorldToIndex(position, index);
geometry->WorldToIndex(m_LastPosition, oldIndex);
+ MITK_INFO << "World: " << m_LastPosition << " to " << position;
+ MITK_INFO << "Index: " << oldIndex << " to " << index;
+
// We don't need to paint over and over again while moving the mouse
// pointer inside the same pixel. That's especially relevant when operating
// on zoomed images.
if (index != oldIndex)
{
// Convert index to slice geometry
m_WorkingSlice->GetGeometry()->WorldToIndex(position, index);
m_WorkingSlice->GetGeometry()->WorldToIndex(m_LastPosition, oldIndex);
+
+ MITK_INFO << "Plane: " << oldIndex << " to " << index;
+ MITK_INFO << "Slice spacing: " << m_WorkingSlice->GetGeometry()->GetSpacing();
+ MITK_INFO << "Plane spacing: " << m_WorkingPlane->GetSpacing();
+
itk::Index<2> indexInPlane2D;
itk::Index<2> oldIndexInPlane2D;
indexInPlane2D[0] = index[0];
indexInPlane2D[1] = index[1];
oldIndexInPlane2D[0] = oldIndex[0];
oldIndexInPlane2D[1] = oldIndex[1];
// Get indices
auto indices = InterpolateIndices2D(oldIndexInPlane2D, indexInPlane2D, m_WorkingPlane, m_Size);
// Fill indices
mitk::ImagePixelWriteAccessor writeAccessor(m_WorkingSlice, m_WorkingSlice->GetSliceData(0));
for (auto i : indices)
{
writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue);
}
m_LastPosition = position;
m_Used = true;
}
}
catch (itk::ExceptionObject& e)
{
mitkThrow() << "Could not paint with interpolation, because:";
mitkThrow() << e.GetDescription();
}
}
diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp
index 61d9a996d5..f96623b7d6 100644
--- a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp
+++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp
@@ -1,1380 +1,1380 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
// Blueberry
#include
#include
// Qt
#include
#include
#include
#include
// Qmitk
#include "QmitkActiveLearning.h"
// MITK
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// ITK/VTK
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef ActiveLearning::FeaturePixelType FeaturePixelType;
typedef ActiveLearning::AnnotationPixelType AnnotationPixelType;
typedef ActiveLearning::LabelPixelType LabelPixelType;
typedef ActiveLearning::FeatureMatrixType FeatureMatrixType;
typedef ActiveLearning::LabelVectorType LabelVectorType;
// Returns true if list has at least one entry and all entries are valid mitk::Images, otherwise false
static bool SelectionAllImages(const QList& nodes)
{
if (nodes.empty())
{
return false;
}
for (const auto& node : nodes)
{
if(!(node.IsNotNull() && dynamic_cast(node->GetData()) != nullptr)) return false;
}
return true;
}
// QColor to mitk::Color
static mitk::Color QColorToMitkColor(const QColor& qcolor)
{
mitk::Color color;
color.SetRed((float)qcolor.red() / 255);
color.SetGreen((float)qcolor.green() / 255);
color.SetBlue((float)qcolor.blue() / 255);
return color;
}
// For debugging
static void PrintAllLabels(mitk::LabelSetImage* image)
{
for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it)
{
MITK_INFO << "Key: " << it->first << " - Name: " << it->second->GetName() << " - Value: " << it->second->GetValue() << " - Color: " << it->second->GetColor();
}
}
// Make values of labels a consistent range
static void FillLabelValues(mitk::LabelSetImage* image)
{
int value(0);
for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it)
{
it->second->SetValue(value);
value++;
}
image->GetActiveLabelSet()->SetActiveLabel(0);
}
// Fill image with zeros
static void FillWithZeros(mitk::Image* image)
{
unsigned int size = image->GetPixelType().GetSize();
for (unsigned int i=0; iGetDimension(); i++)
{
size *= image->GetDimension(i);
}
for (unsigned int t=0; tGetTimeSteps(); t++)
{
mitk::ImageWriteAccessor accessor(image, image->GetVolumeData(0));
memset(accessor.GetData(), 0, size);
}
}
template
static Eigen::Matrix Transform(const std::vector images)
{
// Find size for output matrix [number of voxels, number of feature images]
unsigned int size = images[0]->GetDimension(0);
for (unsigned int i=1; i<3; ++i)
{
size *= images[0]->GetDimension(i);
}
Eigen::Matrix outputMatrix(size, images.size());
for (unsigned int i=0; i::Pointer imageItk;
mitk::CastToItkImage(images[i], imageItk);
outputMatrix.col(i) = Eigen::Matrix::Map(imageItk->GetBufferPointer(), size);
}
return outputMatrix;
}
template
static mitk::Image::Pointer Transform(const Eigen::Matrix &inputMatrix,
const mitk::Image::Pointer referenceImage)
{
typename itk::Image::Pointer imageItk;
auto outputImage = mitk::Image::New();
outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
mitk::CastToItkImage(outputImage, imageItk);
auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion());
int i = 0;
while (!it.IsAtEnd())
{
it.Set(inputMatrix(i, 0));
++it;
++i;
}
mitk::GrabItkImageMemory(imageItk, outputImage);
return outputImage;
}
template
static std::vector Transform(const Eigen::Matrix &inputMatrix,
const mitk::Image::Pointer referenceImage)
{
std::vector resultVector;
for (int j=0; j::Pointer imageItk;
auto outputImage = mitk::Image::New();
outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
mitk::CastToItkImage(outputImage, imageItk);
auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion());
int i = 0;
while (!it.IsAtEnd())
{
it.Set(static_cast(inputMatrix(i, j)));
++it;
++i;
}
mitk::GrabItkImageMemory(imageItk, outputImage);
resultVector.push_back(outputImage);
}
return resultVector;
}
template
static void PrintMatrix(const Eigen::Matrix dataMatrix,
int maxRows = 0)
{
if (maxRows == 0 || maxRows > dataMatrix.rows()) maxRows = dataMatrix.rows();
MITK_INFO << "---------------------";
for (int i=0; i
static void PrintMatrix(const Eigen::Matrix dataMatrix,
const Eigen::Matrix labelMatrix,
int maxRows = 0)
{
if (labelMatrix.rows() < dataMatrix.rows()) return;
if (maxRows == 0 || maxRows > dataMatrix.rows()) maxRows = dataMatrix.rows();
MITK_INFO << "---------------------";
for (int i=0; i
static void GaussianSmoothing(const itk::Image* inputImage,
const double sigma, mitk::Image::Pointer outputImage)
{
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,
const 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,
const 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]);
// }
typedef itk::Image ImageType;
typedef itk::Image, imageDimension> TensorImageType;
typename itk::StructureTensorImageFilter::Pointer filter = itk::StructureTensorImageFilter::New();
filter->SetInput(inputImage);
filter->SetNoiseScale(sigma);
- filter->SetFeatureScale(sigma);
+ filter->SetFeatureScale(1.3*sigma);
filter->Update();
std::vector eigenValueImages;
std::vector> eigenValueImageIterators;
for (unsigned int i=0; iSetRegions(inputImage->GetLargestPossibleRegion());
image->Allocate();
eigenValueImages.push_back(image);
itk::ImageRegionIterator it(image, image->GetLargestPossibleRegion());
eigenValueImageIterators.push_back(it);
}
itk::ImageRegionConstIterator tensorImageIterator(filter->GetOutput(), filter->GetOutput()->GetLargestPossibleRegion());
while (!tensorImageIterator.IsAtEnd())
{
typename TensorImageType::PixelType::EigenValuesArrayType ev;
tensorImageIterator.Get().ComputeEigenValues(ev);
for (unsigned int i=0; i
static void HessianEigenvalues(const itk::Image* inputImage,
float sigma, std::vector outputImages)
{
typedef itk::Image ImageType;
typedef itk::Image, imageDimension> TensorImageType;
typename itk::HessianRecursiveGaussianImageFilter::Pointer filter = itk::HessianRecursiveGaussianImageFilter::New();
filter->SetInput(inputImage);
filter->SetSigma(sigma);
filter->Update();
std::vector eigenValueImages;
std::vector> eigenValueImageIterators;
for (unsigned int i=0; iSetRegions(inputImage->GetLargestPossibleRegion());
image->Allocate();
eigenValueImages.push_back(image);
itk::ImageRegionIterator it(image, image->GetLargestPossibleRegion());
eigenValueImageIterators.push_back(it);
}
itk::ImageRegionConstIterator tensorImageIterator(filter->GetOutput(), filter->GetOutput()->GetLargestPossibleRegion());
while (!tensorImageIterator.IsAtEnd())
{
typename TensorImageType::PixelType::EigenValuesArrayType ev;
tensorImageIterator.Get().ComputeEigenValues(ev);
for (unsigned int i=0; i ImageType;
// typedef itk::Image FeatureImageType;
// auto filter = itk::HessianMatrixEigenvalueImageFilter::New();
// filter->SetInput(inputImage);
// filter->SetSigma(sigma);
// filter->Update();
// mitk::GrabItkImageMemory(filter->GetOutput(0), outputImages[0]);
// mitk::GrabItkImageMemory(filter->GetOutput(1), outputImages[1]);
// mitk::GrabItkImageMemory(filter->GetOutput(2), outputImages[2]);
}
/* ==================================================================
* PUBLIC SLOTS
* =============================================================== */
void ActiveLearning::Initialize()
{
// Get selected nodes and check again if these are all images
m_Nodes = this->GetDataManagerSelection();
if (!SelectionAllImages(m_Nodes)) return;
m_Controls.m_InitializePushButton->setDisabled(true);
emit SignalSetProgressMaximum(m_Nodes.length() * 10 + 1); // 6 is number of features, shouldn't be hardcoded
emit SignalResetProgress();
// Set names to the label (again)
QString nameList = QString::fromStdString(m_Nodes[0]->GetName());
if (m_Nodes.length() >= 2)
{
for (int i=1; i");
nameList += QString::fromStdString(m_Nodes[i]->GetName());
}
}
m_Controls.m_InitializeLabel->setText(nameList);
// =======================================
// SEGMENTATION IMAGE
// =======================================
m_SegmentationImage = mitk::Image::New();
try
{
mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData());
m_SegmentationImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
}
catch (mitk::Exception& e)
{
MITK_ERROR << "Exception caught: " << e.GetDescription();
QMessageBox::information(m_Parent, "Error", "Could not initialize segmentation image");
return;
}
FillWithZeros(m_SegmentationImage);
m_SegmentationNode = mitk::DataNode::New();
m_SegmentationNode->SetData(m_SegmentationImage);
m_SegmentationNode->SetName("Segmentation");
m_SegmentationNode->SetColor(1., 1., 1.);
m_SegmentationNode->SetBoolProperty("binary", false);
m_SegmentationNode->SetProperty("reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST));
this->GetDataStorage()->Add(m_SegmentationNode, m_Nodes[0]);
// =======================================
// ANNOTATION IMAGE
// =======================================
m_AnnotationImage = mitk::Image::New();
try
{
mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData());
m_AnnotationImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
}
catch (mitk::Exception& e)
{
MITK_ERROR << "Exception caught: " << e.GetDescription();
QMessageBox::information(m_Parent, "Error", "Could not initialize annotation image");
return;
}
FillWithZeros(m_AnnotationImage);
m_AnnotationNode = mitk::DataNode::New();
m_AnnotationNode->SetData(m_AnnotationImage);
m_AnnotationNode->SetName("Labels");
m_AnnotationNode->SetColor(1., 1., 1.);
m_AnnotationNode->SetBoolProperty("binary", false);
m_AnnotationNode->SetProperty("reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST));
m_AnnotationNode->SetProperty("opacity", mitk::FloatProperty::New(1.0f));
this->GetDataStorage()->Add(m_AnnotationNode, m_Nodes[0]);
// =======================================
// UNCERTAINTY NODE
// =======================================
m_UncertaintyNode = mitk::DataNode::New();
m_UncertaintyNode->SetName("Uncertainty");
m_UncertaintyNode->SetColor(0., 1., 0.95);
m_UncertaintyNode->SetBoolProperty("binary", false);
m_UncertaintyNode->SetProperty("opacity", mitk::FloatProperty::New(0.6));
m_UncertaintyNode->SetVisibility(false);
this->GetDataStorage()->Add(m_UncertaintyNode, m_Nodes[0]);
// =======================================
// UNCERTAINTY REGION NODE
// =======================================
m_UncertaintyRegionNode = mitk::DataNode::New();
m_UncertaintyRegionNode->SetName("Region");
m_UncertaintyRegionNode->SetColor(1., 1., 1.);
m_UncertaintyRegionNode->SetBoolProperty("binary", false);
m_UncertaintyRegionNode->SetProperty("opacity", mitk::FloatProperty::New(0.5));
m_UncertaintyRegionNode->SetVisibility(true);
m_UncertaintyRegionNode->SetBoolProperty("fixedLayer", true);
m_UncertaintyRegionNode->SetProperty("layer", mitk::IntProperty::New(99));
this->GetDataStorage()->Add(m_UncertaintyRegionNode, m_Nodes[0]);
// Convert input images to FeaturePixelType
for (auto node : m_Nodes)
{
mitk::Image::Pointer image = dynamic_cast(node->GetData());
auto itkImage = itk::Image::New();
mitk::CastToItkImage(image, itkImage);
mitk::GrabItkImageMemory(itkImage, image);
node->SetData(image);
}
emit SignalSetProgress(1);
// Calculate features
for (const auto node : m_Nodes)
{
mitk::Image::Pointer currentImage = dynamic_cast(node->GetData());
QFuture>> future;
future = QtConcurrent::run(this, &ActiveLearning::CalculateFeatures, currentImage);
auto futureWatcher = new QFutureWatcher>>();
futureWatcher->setFuture(future);
connect(futureWatcher, SIGNAL(finished()), this, SLOT(OnInitializationFinished()));
m_FeatureCalculationWatchers.push_back(futureWatcher);
}
// Interactor
auto activeLearningLib = us::ModuleRegistry::GetModule("MitkCLActiveLearning");
m_Interactor = mitk::ActiveLearningInteractor::New();
m_Interactor->LoadStateMachine("Paint.xml", activeLearningLib);
m_Interactor->SetEventConfig("PaintConfig.xml", activeLearningLib);
m_Interactor->SetDataNode(m_AnnotationNode);
// Automatically add first label
OnAddLabelPushButtonClicked();
m_Active = true;
}
/* ==================================================================
* PUBLIC
* =============================================================== */
ActiveLearning::ActiveLearning() :
m_Parent(nullptr),
m_AnnotationImage(nullptr),
m_AnnotationNode(nullptr),
m_SegmentationImage(nullptr),
m_SegmentationNode(nullptr),
m_Active(false),
m_Guidance(false),
m_NumberOfTrees(50),
m_MaximumTreeDepth(10),
m_SamplesPerTree(0.66),
m_PredictionMatrix(nullptr)
{
}
ActiveLearning::~ActiveLearning()
{
}
void ActiveLearning::CreateQtPartControl( QWidget *parent )
{
m_Controls.setupUi(parent);
m_Parent = parent;
// Label model
m_LabelListModel = new QStandardItemModel(0, 3, this);
m_Controls.m_LabelTableView->setModel(m_LabelListModel);
m_Controls.m_LabelTableView->horizontalHeader()->setDefaultSectionSize(20);
m_Controls.m_LabelTableView->verticalHeader()->setDefaultSectionSize(20);
NotEditableDelegate* itemDelegate = new NotEditableDelegate(parent);
m_Controls.m_LabelTableView->setItemDelegateForColumn(1, itemDelegate);
// Connects
connect(m_Controls.m_LabelTableView, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(OnColorIconDoubleClicked(QModelIndex)));
connect(m_Controls.m_LabelTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this, SLOT(OnLabelListSelectionChanged(QItemSelection, QItemSelection)));
connect(m_LabelListModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
this, SLOT(OnLabelNameChanged(QModelIndex, QModelIndex)));
connect(m_Controls.m_InitializePushButton, SIGNAL(clicked()),
this, SLOT(Initialize()));
connect(m_Controls.m_AddLabelPushButton, SIGNAL(clicked()),
this, SLOT(OnAddLabelPushButtonClicked()));
connect(m_Controls.m_RemoveLabelPushButton, SIGNAL(clicked()),
this, SLOT(OnRemoveLabelPushButtonClicked()));
connect(m_Controls.m_PaintToolButton, SIGNAL(clicked()),
this, SLOT(OnPaintToolButtonClicked()));
connect(m_Controls.m_EraseToolButton, SIGNAL(clicked()),
this, SLOT(OnEraseToolButtonClicked()));
connect(m_Controls.m_SaveSegmentationPushButton, SIGNAL(clicked()),
this, SLOT(OnSaveSegmentationPushButtonClicked()));
connect(m_Controls.m_SavePredictionsPushButton, SIGNAL(clicked()),
this, SLOT(OnSavePredictionsPushButtonClicked()));
connect(m_Controls.m_UpdatePredictionsPushButton, SIGNAL(clicked()),
this, SLOT(OnUpdatePredictionsPushButtonClicked()));
connect(this, SIGNAL(SignalIncrementProgress()),
this, SLOT(OnSignalIncrementProgress()));
connect(this, SIGNAL(SignalSetProgress(int)),
this, SLOT(OnSignalSetProgress(int)));
connect(this, SIGNAL(SignalResetProgress()),
this, SLOT(OnSignalResetProgress()));
connect(this, SIGNAL(SignalSetProgressMaximum(int)),
this, SLOT(OnSignalSetProgressMaximum(int)));
connect(m_Controls.m_BrushSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(OnBrushSizeSliderValueChanged(int)));
connect(m_Controls.m_ActiveGuidanceCheckbox, SIGNAL(stateChanged(int)),
this, SLOT(OnActiveGuidanceCheckBoxToggled(int)));
// Set start configuration
m_Controls.m_LabelControlsFrame->setVisible(false);
SetInitializeReady(false);
}
void ActiveLearning::ResetLabels()
{
for (int i=0; irowCount(); i++)
{
m_LabelListModel->item(i, 1)->setText(QString::number(i + 1));
}
}
std::vector > ActiveLearning::CalculateFeatures(const mitk::Image::Pointer inputImage)
{
std::vector> result;
// TODO: Get features from preference page
std::vector sigmas = {0.7, 1.6};
for (auto sigma : sigmas)
{
std::stringstream ss;
auto gaussImage = mitk::Image::New();
AccessByItk_n(inputImage, GaussianSmoothing, (sigma, gaussImage));
gaussImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
ss << "GaussianSmoothing (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(gaussImage, ss.str()));
ss.str("");
emit SignalIncrementProgress();
auto gradMagImage = mitk::Image::New();
AccessByItk_n(inputImage, GaussianGradientMagnitude, (sigma, gradMagImage));
gradMagImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
ss << "GaussianGradientMagnitude (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(gradMagImage, ss.str()));
ss.str("");
emit SignalIncrementProgress();
auto logImage = mitk::Image::New();
AccessByItk_n(inputImage, LaplacianOfGaussian, (sigma, logImage));
logImage->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
ss << "LaplacianOfGaussian (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(logImage, ss.str()));
ss.str("");
emit SignalIncrementProgress();
auto structImage1 = mitk::Image::New();
auto structImage2 = mitk::Image::New();
auto structImage3 = mitk::Image::New();
std::vector structImages = {structImage1, structImage2, structImage3};
AccessByItk_n(inputImage, StructureTensorEigenvalues, (sigma, structImages));
structImage1->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
structImage2->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
structImage3->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
ss << "StructureTensorEV1 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(structImage1, ss.str()));
ss.str("");
ss << "StructureTensorEV2 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(structImage2, ss.str()));
ss.str("");
if (inputImage->GetDimension() == 3)
{
ss << "StructureTensorEV3 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(structImage3, ss.str()));
ss.str("");
}
emit SignalIncrementProgress();
auto hessianImage1 = mitk::Image::New();
auto hessianImage2 = mitk::Image::New();
auto hessianImage3 = mitk::Image::New();
std::vector hessianImages = {hessianImage1, hessianImage2, hessianImage3};
AccessByItk_n(inputImage, HessianEigenvalues, (sigma, hessianImages));
hessianImage1->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
hessianImage2->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
hessianImage3->SetClonedTimeGeometry(inputImage->GetTimeGeometry());
ss << "HessianEV1 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(hessianImage1, ss.str()));
ss.str("");
ss << "HessianEV2 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(hessianImage2, ss.str()));
ss.str("");
if (inputImage->GetDimension() == 3)
{
ss << "HessianEV3 (" << std::fixed << std::setprecision(2) << sigma << ")";
result.push_back(std::pair(hessianImage3, ss.str()));
ss.str("");
}
emit SignalIncrementProgress();
}
return result;
}
std::pair> ActiveLearning::CalculatePrediction(const mitk::Image::Pointer annotationImage,
const std::vector &featureImageVector,
const mitk::Image::Pointer referenceImage,
mitk::AbstractClassifier* classifier,
std::shared_ptr predictionMatrix)
{
// Create prediction matrix if necessary
if (predictionMatrix == nullptr)
{
FeatureMatrixType mat = Transform(featureImageVector);
predictionMatrix = std::make_shared(mat);
}
emit SignalIncrementProgress();
// Get training data and train
auto training = GetTrainingData(annotationImage, featureImageVector);
classifier->Train(*training.second, *training.first);
emit SignalIncrementProgress();
// Get result
LabelVectorType segmentation = classifier->Predict(*predictionMatrix);
emit SignalIncrementProgress();
FeatureMatrixType prediction = classifier->GetPointWiseProbabilities();
mitk::Image::Pointer segmentationImage = Transform(segmentation, referenceImage);
std::vector predictionImages = Transform(prediction, referenceImage);
emit SignalIncrementProgress();
std::pair> result = std::make_pair(segmentationImage, predictionImages);
return result;
}
std::pair ActiveLearning::CalculateUncertainty(const std::vector &probabilityImageVector,
const mitk::Image::Pointer referenceImage,
bool guidance)
{
typedef itk::Image ImageType;
std::vector itkImages;
std::vector> iterators;
for (auto image : probabilityImageVector)
{
typename ImageType::Pointer itkImage;
mitk::CastToItkImage(image, itkImage);
itkImages.push_back(itkImage);
itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion());
iterators.push_back(it);
}
auto outputImage = mitk::Image::New();
outputImage->Initialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
typename ImageType::Pointer uncertaintyImage;
mitk::CastToItkImage(outputImage, uncertaintyImage);
itk::ImageRegionIterator uit(uncertaintyImage, uncertaintyImage->GetLargestPossibleRegion());
while (!uit.IsAtEnd())
{
FeaturePixelType value = 0;
for (unsigned int i=0; iInitialize(mitk::MakeScalarPixelType(), *(referenceImage->GetTimeGeometry()->Clone()));
auto regionFilter = mitk::ActiveLearningSuggestRegionFilter>::New();
regionFilter->SetInput(uncertaintyImage);
regionFilter->SetThreshold(0.5);
regionFilter->Update();
mitk::GrabItkImageMemory(regionFilter->GetOutput(), region);
}
mitk::GrabItkImageMemory(uncertaintyImage, outputImage);
std::pair result = std::make_pair(outputImage, region);
return result;
}
std::pair, std::shared_ptr> ActiveLearning::GetTrainingData(const mitk::Image::Pointer annotationImage,
const std::vector &featureImageVector)
{
// Get indices and labels
std::vector> indices;
std::vector labels;
itk::Image::Pointer annotationImageItk;
mitk::CastToItkImage(annotationImage, annotationImageItk);
itk::ImageRegionIteratorWithIndex> it(annotationImageItk, annotationImageItk->GetLargestPossibleRegion());
while (!it.IsAtEnd())
{
if (it.Get() != 0)
{
indices.push_back(it.GetIndex());
labels.push_back(it.Get());
}
++it;
}
FeatureMatrixType trainingData(indices.size(), featureImageVector.size());
LabelVectorType trainingLabels = LabelVectorType::Map(labels.data(), labels.size());
int j = 0;
for (mitk::Image::Pointer feature : featureImageVector)
{
int i = 0;
mitk::ImagePixelReadAccessor access(feature, feature->GetVolumeData());
for (auto index : indices)
{
trainingData(i, j) = access.GetPixelByIndexSafe(index);
i++;
}
j++;
}
auto trainingLabelsPtr = std::make_shared(trainingLabels);
auto trainingDataPtr = std::make_shared(trainingData);
std::pair, std::shared_ptr> result = std::make_pair(trainingLabelsPtr, trainingDataPtr);
return result;
}
void ActiveLearning::UpdateLookupTables()
{
// Create new lookup table from list
// Annotation type is int, but we only use a ushort lookup table
auto lut = vtkSmartPointer::New();
int lowlim = std::numeric_limits::min();
int uplim = std::numeric_limits::max();
lut->SetNumberOfTableValues(uplim - lowlim + 1);
lut->SetTableRange(lowlim, uplim);
for (long i=0; i<(uplim-lowlim+1); ++i)
{
lut->SetTableValue(i, 0.0, 0.0, 0.0, 0.0);
}
for (int j=0; jrowCount(); ++j)
{
int value = m_LabelListModel->item(j, 1)->text().toInt();
const QColor color = m_LabelListModel->item(j, 0)->background().color();
lut->SetTableValue(value, color.redF(), color.greenF(), color.blueF(), 1.0);
}
auto lutMitk = mitk::LookupTable::New();
lutMitk->SetVtkLookupTable(lut);
// Set to annotation image and segmentation image
auto * lut_prop = dynamic_cast(m_AnnotationNode->GetProperty("LookupTable"));
lut_prop->SetLookupTable(lutMitk);
m_AnnotationNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR));
m_AnnotationNode->Modified();
lut_prop = dynamic_cast(m_SegmentationNode->GetProperty("LookupTable"));
lut_prop->SetLookupTable(lutMitk);
m_SegmentationNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR));
m_SegmentationNode->Modified();
}
void ActiveLearning::UpdatePredictionNodes()
{
if (m_LabelListModel->rowCount() == 0) return;
// Build lookup table
vtkSmartPointer lut = vtkSmartPointer::New();
lut->SetTableRange (0, 1);
lut->SetSaturationRange (0, 0);
lut->SetHueRange (0, 0);
lut->SetValueRange (0, 1);
lut->SetAlphaRange (0, 1);
lut->Build();
auto lutMitk = mitk::LookupTable::New();
lutMitk->SetVtkLookupTable(lut);
for (int i=0; irowCount(); ++i)
{
auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt()));
auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property);
if (nodes->Size() == 1)
{
auto node = nodes->GetElement(0);
QString name = "Prediction ";
name += m_LabelListModel->item(i, 2)->text();
node->SetName(name.toStdString());
auto * lut_prop = dynamic_cast(node->GetProperty("LookupTable"));
lut_prop->SetLookupTable(lutMitk);
node->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR));
node->SetColor(QColorToMitkColor(m_LabelListModel->item(i, 0)->background().color()));
node->Modified();
}
if (nodes->Size() > 1) mitkThrow();
}
// Uncertainty image
auto *lut_prop = dynamic_cast(m_UncertaintyNode->GetProperty("LookupTable"));
if (lut_prop != nullptr)
{
lut_prop->SetLookupTable(lutMitk);
m_UncertaintyNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR));
}
vtkSmartPointer lut2 = vtkSmartPointer::New();
lut2->SetRampToLinear();
lut2->SetSaturationRange (0., 0.);
lut2->SetHueRange (0., 0.);
lut2->SetValueRange (0., 1.);
lut2->SetAlphaRange (1., 0.);
lut2->Build();
auto lutMitk2 = mitk::LookupTable::New();
lutMitk2->SetVtkLookupTable(lut2);
lut_prop = dynamic_cast(m_UncertaintyRegionNode->GetProperty("LookupTable"));
if (lut_prop != nullptr)
{
lut_prop->SetLookupTable(lutMitk2);
m_UncertaintyRegionNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_COLOR));
}
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
}
const std::string ActiveLearning::VIEW_ID = "org.mitk.views.activelearning";
/* ==================================================================
* PROTECTED SLOTS
* =============================================================== */
void ActiveLearning::OnSignalIncrementProgress()
{
m_Controls.m_ProgressBar->setValue(m_Controls.m_ProgressBar->value() + 1);
}
void ActiveLearning::OnSignalSetProgress(int value)
{
m_Controls.m_ProgressBar->setValue(value);
}
void ActiveLearning::OnSignalResetProgress()
{
m_Controls.m_ProgressBar->setValue(0);
}
void ActiveLearning::OnSignalSetProgressMaximum(int value)
{
m_Controls.m_ProgressBar->setMaximum(value);
}
void ActiveLearning::OnAddLabelPushButtonClicked()
{
QString labelName = QString("Label ") + QString::number(m_LabelListModel->rowCount() + 1);
QColor labelColor = Qt::GlobalColor(m_LabelListModel->rowCount() % 12 + 7); // We only want Qt default colors 7 to 18
// Create icon
QStandardItem* colorSquare = new QStandardItem;
colorSquare->setBackground(labelColor);
colorSquare->setEditable(false);
QPixmap colorPixmap(20, 20);
colorPixmap.fill(labelColor);
colorSquare->setIcon(QIcon(colorPixmap));
// Key is the highest existing key + 1
int value = 1;
if (m_LabelListModel->rowCount() >= 1)
{
value = m_LabelListModel->item(m_LabelListModel->rowCount() - 1, 1)->text().toInt() + 1;
}
QStandardItem* valueItem = new QStandardItem;
valueItem->setText(QString::number(value));
// Create label item
QStandardItem* label = new QStandardItem(labelName);
// Make list and insert
QList list;
list.append(colorSquare);
list.append(valueItem);
list.append(label);
m_LabelListModel->appendRow(list);
m_Controls.m_LabelTableView->selectRow(m_LabelListModel->rowCount() - 1);
// If this is the first label, we activate the paint button
// We also have to set the data node color for this one, because for 1 values that color seems to define the rendered color
if (m_LabelListModel->rowCount() == 1)
{
OnPaintToolButtonClicked();
}
// Update colors
UpdateLookupTables();
}
void ActiveLearning::OnRemoveLabelPushButtonClicked()
{
// can't remove last label
if (m_LabelListModel->rowCount() <= 1) return;
QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel();
if (selection->hasSelection())
{
unsigned int removeIndex = selection->selectedRows().first().row();
QString removeMessage = QString("Remove label '") + m_LabelListModel->item(removeIndex, 2)->text() + QString("'?");
QMessageBox::StandardButton removeReply;
removeReply = QMessageBox::question(m_Parent,
"Remove Label",
removeMessage,
QMessageBox::Yes | QMessageBox::No);
if (removeReply == QMessageBox::Yes)
{
AnnotationPixelType removeValue = m_LabelListModel->item(removeIndex, 1)->text().toInt();
m_LabelListModel->removeRow(removeIndex);
if (!m_Interactor->IsUsed())
{
ResetLabels();
}
else
{
itk::Image::Pointer imageItk;
mitk::CastToItkImage(m_AnnotationImage, imageItk);
auto it = itk::ImageRegionIterator>(imageItk, imageItk->GetLargestPossibleRegion());
while (!it.IsAtEnd())
{
if (it.Get() == removeValue)
it.Set(0);
++it;
}
mitk::GrabItkImageMemory(imageItk, m_AnnotationImage);
UpdateLookupTables();
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
}
}
}
}
void ActiveLearning::OnPaintToolButtonClicked()
{
m_Controls.m_PaintToolButton->setChecked(true);
QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel();
int row(0);
if (selection->hasSelection())
{
row = selection->selectedRows().first().row();
}
else
{
m_Controls.m_LabelTableView->selectRow(0);
}
m_Interactor->SetPaintingPixelValue(m_LabelListModel->item(row, 1)->text().toInt());
}
void ActiveLearning::OnEraseToolButtonClicked()
{
m_Controls.m_EraseToolButton->setChecked(true);
m_Interactor->SetPaintingPixelValue(0);
}
void ActiveLearning::OnSaveSegmentationPushButtonClicked()
{
auto newNode = mitk::DataNode::New();
newNode->SetName("Segmentation");
newNode->SetBoolProperty("binary", false);
newNode->SetOpacity(1.0);
newNode->SetVisibility(false);
newNode->SetData(m_SegmentationImage->Clone());
this->GetDataStorage()->Add(newNode);
}
void ActiveLearning::OnSavePredictionsPushButtonClicked()
{
if (m_LabelListModel->rowCount() < 1) return;
for (int i=0; irowCount(); ++i)
{
auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt()));
auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property);
if (nodes->Size() == 1)
{
auto sourceNode = nodes->GetElement(0);
mitk::Image::Pointer sourceImage = dynamic_cast(sourceNode->GetData());
auto newNode = mitk::DataNode::New();
QString name = "Prediction ";
name += m_LabelListModel->item(i, 2)->text();
newNode->SetName(name.toStdString());
newNode->SetBoolProperty("binary", false);
newNode->SetOpacity(1.0);
newNode->SetVisibility(false);
newNode->SetProperty("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt()));
newNode->SetData(sourceImage->Clone());
this->GetDataStorage()->Add(newNode);
}
if (nodes->Size() > 1) mitkThrow();
}
}
void ActiveLearning::OnActiveGuidanceCheckBoxToggled(int toggled)
{
m_Guidance = (toggled > 0);
}
void ActiveLearning::OnColorIconDoubleClicked(const QModelIndex& index)
{
// Check if click is really from color icon
if (index.column() != 0)
{
return;
}
else
{
// Color change dialog
QColor setColor = QColorDialog::getColor(m_LabelListModel->itemFromIndex(index)->background().color(), m_Parent, "Select Label Color");
if (setColor.isValid())
{
m_LabelListModel->itemFromIndex(index)->setBackground(setColor);
QPixmap colorPixmap(20, 20);
colorPixmap.fill(setColor);
m_LabelListModel->itemFromIndex(index)->setIcon(QIcon(colorPixmap));
UpdateLookupTables();
UpdatePredictionNodes();
}
}
}
void ActiveLearning::OnLabelListSelectionChanged(const QItemSelection& selected,
const QItemSelection& /*deselected*/)
{
if (selected.empty()) return;
if (m_Controls.m_EraseToolButton->isChecked()) OnPaintToolButtonClicked();
// This assumes that only one item can be selected (single selection table view)
try
{
int labelValue = m_LabelListModel->item(selected.indexes()[0].row(), 1)->text().toInt();
m_Interactor->SetPaintingPixelValue(labelValue);
}
catch (...)
{
m_Interactor->SetPaintingPixelValue(-1);
}
}
void ActiveLearning::OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/)
{
UpdatePredictionNodes();
}
void ActiveLearning::OnInitializationFinished()
{
// Check if all futures are finished
for (auto watcher : m_FeatureCalculationWatchers)
{
if (watcher->isFinished() == false) {return;}
}
// Empty feature vector
m_FeatureImageVector.clear();
// Insert features into feature vector and data storage
for (unsigned int i=0; iresult();
for (unsigned int j=0; jSetData(result[j].first);
node->SetName(result[j].second);
node->SetBoolProperty("helper object", true);
node->SetVisibility(false);
this->GetDataStorage()->Add(node, m_Nodes[i]);
}
}
// Show controls
m_Controls.m_LabelControlsFrame->setVisible(true);
m_Controls.m_InitializePushButton->setHidden(true);
emit SignalResetProgress();
// Delete watchers
for (auto watcher : m_FeatureCalculationWatchers)
{
delete watcher;
}
m_FeatureCalculationWatchers.clear();
}
void ActiveLearning::OnUpdatePredictionsPushButtonClicked()
{
if (m_LabelListModel->rowCount() < 1) return;
m_Controls.m_UpdatePredictionsPushButton->setDisabled(true);
emit SignalSetProgressMaximum(4);
// Clear old predictions
for (int i=0; irowCount(); ++i)
{
auto property = mitk::NodePredicateProperty::New("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt()));
auto nodes = this->GetDataStorage()->GetDerivations(m_Nodes[0], property);
if (nodes->Size() == 1)
{
this->GetDataStorage()->Remove(nodes);
}
if (nodes->Size() > 1) mitkThrow();
}
// Classifier
auto classifier = mitk::VigraRandomForestClassifier::New();
classifier->SetTreeCount(m_NumberOfTrees);
classifier->SetMaximumTreeDepth(m_MaximumTreeDepth);
classifier->SetSamplesPerTree(m_SamplesPerTree);
mitk::Image::Pointer referenceImage = dynamic_cast(m_Nodes[0]->GetData());
QFuture>> future;
future = QtConcurrent::run(this, &ActiveLearning::CalculatePrediction, m_AnnotationImage, m_FeatureImageVector, referenceImage, classifier, m_PredictionMatrix);
m_PredictionCalculationWatcher = new QFutureWatcher>>();
m_PredictionCalculationWatcher->setFuture(future);
connect(m_PredictionCalculationWatcher, SIGNAL(finished()),
this, SLOT(OnPredictionCalculationFinished()));
}
void ActiveLearning::OnPredictionCalculationFinished()
{
auto result = m_PredictionCalculationWatcher->result();
m_SegmentationImage = result.first;
m_SegmentationImage->Modified();
m_SegmentationNode->SetData(m_SegmentationImage);
m_SegmentationNode->Modified();
for (unsigned int i=0; iitem(i, 2)->text();
node->SetName(name.toStdString());
node->SetBoolProperty("binary", false);
node->SetVisibility(false);
node->SetOpacity(0.3);
node->SetColor(QColorToMitkColor(m_LabelListModel->item(i, 0)->background().color()));
node->SetProperty("segmentation_value", mitk::IntProperty::New(m_LabelListModel->item(i, 1)->text().toInt()));
node->SetData(result.second[i]);
this->GetDataStorage()->Add(node, m_Nodes[0]);
}
QFuture> future;
future = QtConcurrent::run(this, &ActiveLearning::CalculateUncertainty, result.second, m_SegmentationImage, m_Guidance);
m_UncertaintyCalculationWatcher = new QFutureWatcher>();
m_UncertaintyCalculationWatcher->setFuture(future);
connect(m_UncertaintyCalculationWatcher, SIGNAL(finished()),
this, SLOT(OnUncertaintyCalculationFinished()));
}
void ActiveLearning::OnUncertaintyCalculationFinished()
{
m_UncertaintyImage = m_UncertaintyCalculationWatcher->result().first;
m_UncertaintyImage->Modified();
m_UncertaintyNode->SetData(m_UncertaintyImage);
m_UncertaintyNode->Modified();
if (m_UncertaintyCalculationWatcher->result().second.IsNotNull())
{
m_UncertaintyRegion = m_UncertaintyCalculationWatcher->result().second;
m_UncertaintyRegion->Modified();
m_UncertaintyRegionNode->SetData(m_UncertaintyRegion);
m_UncertaintyRegionNode->Modified();
}
UpdateLookupTables();
UpdatePredictionNodes();
emit SignalResetProgress();
m_Controls.m_UpdatePredictionsPushButton->setEnabled(true);
mitk::RenderingManager::GetInstance()->RequestUpdateAll();
}
void ActiveLearning::OnBrushSizeSliderValueChanged(int value)
{
QString labelText = "Size ";
labelText += QString::number(value);
m_Controls.m_BrushSizeLabel->setText(labelText);
m_Interactor->SetSize(value);
}
/* ==================================================================
* PROTECTED
* =============================================================== */
void ActiveLearning::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/,
const QList& nodes)
{
if (!SelectionAllImages(nodes))
{
SetInitializeReady(false);
return;
}
if (nodes.length() >= 2)
{
// First selection is the reference (could be any other)
mitk::Image::Pointer referenceImage = dynamic_cast(nodes[0]->GetData());
mitk::BaseGeometry* referenceGeometry = referenceImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps
for (int i=1; i(nodes[i]->GetData());
mitk::BaseGeometry* currentGeometry = currentImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps
if (!mitk::Equal(*currentGeometry, *referenceGeometry, mitk::eps, true))
{
SetInitializeReady(false);
return;
}
}
}
// All nodes have the same geometry, allow init
SetInitializeReady(true);
}
void ActiveLearning::SetFocus()
{
}
/* ==================================================================
* PRIVATE
* =============================================================== */
void ActiveLearning::SetInitializeReady(bool ready)
{
if (ready)
{
// get selection, check again just to be sure
auto nodes = this->GetDataManagerSelection();
if (!SelectionAllImages(nodes)) return;
m_Controls.m_InitializePushButton->setEnabled(true);
if (!m_Active)
{
QString nameList = QString::fromStdString(nodes[0]->GetName());
if (nodes.length() >= 2)
{
for (int i=1; i");
nameList += QString::fromStdString(nodes[i]->GetName());
}
}
m_Controls.m_InitializeLabel->setText(nameList);
}
}
else
{
m_Controls.m_InitializePushButton->setDisabled(true);
if (!m_Active)
{
m_Controls.m_InitializeLabel->setText("Selected images must have matching geometries");
}
}
}