diff --git a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h index d9a7126c09..2c84ced023 100644 --- a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h @@ -1,62 +1,64 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkActiveLearningInteractor_h #define mitkActiveLearningInteractor_h #include #include #include +#include #include namespace mitk { class MITKCLACTIVELEARNING_EXPORT ActiveLearningInteractor : public DataInteractor { public: typedef unsigned short AnnotationPixelType; mitkClassMacro(ActiveLearningInteractor, DataInteractor) itkFactorylessNewMacro(Self) void SetPaintingPixelValue(AnnotationPixelType value){m_PaintingPixelValue = static_cast(value);} 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); + int m_Size; itk::Index<3> m_LastPixelIndex; AnnotationPixelType m_PaintingPixelValue; bool m_Used; }; } #endif diff --git a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp index dcb78dd492..db205205f2 100644 --- a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp +++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp @@ -1,217 +1,637 @@ /*=================================================================== 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 + +#define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) // 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 std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, mitk::BaseGeometry* geometry) +static mitk::Point2D upperLeft(mitk::Point2D p) +{ + p[0] -= 0.5; + p[1] += 0.5; + return p; +} + +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 ScalarProduct(const mitk::Point3D p1, const mitk::Point3D p2) +{ + return p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2]; +} + +static std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, mitk::BaseGeometry* geometry, unsigned int size) { if (geometry == nullptr) mitkThrow(); + MITK_INFO << "Interpolating between " << startIndex << " and " << endIndex; + 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; - } + // Distance between end points + double dist = EuclideanDistance(startPoint, endPoint); - 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]])) + MITK_INFO << "Distance between points is " << dist; + + // Define region between startIndex and endIndex, padded by (size - 1) + int regionBounds[6]; + for (int i=0; i<3; ++i) { - argm[2] = argm[1]; - argm[1] = 2; + regionBounds[2*i] = std::min(startIndex[i], endIndex[i]) - (size - 1); + regionBounds[2*i+1] = std::max(startIndex[i], endIndex[i]) + (size - 1); } - if (abs(indexDelta[2]) > abs(indexDelta[argm[0]])) + + MITK_INFO << "Region size is " << regionBounds[0] << ", " << regionBounds[1] << ", " << regionBounds[2] << ", " << regionBounds[3] << ", " << regionBounds[4] << ", " << regionBounds[5]; + + // 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 spacingInIndexSystem[3]; + double minSpacing = -1.; + for (int i=0; i<3; ++i) { - argm[1] = argm[0]; - argm[0] = 2; + 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; } - 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) + MITK_INFO << "Spacings are " << spacingInIndexSystem[0] << ", " << spacingInIndexSystem[1] << ", " << spacingInIndexSystem[2]; + + // 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) { - 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); + 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); + } + } + } } + MITK_INFO << "Found " << resultIndices.size() << " indices"; + return resultIndices; + +// 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_Size(1), m_PaintingPixelValue(0) { } mitk::ActiveLearningInteractor::~ActiveLearningInteractor() { } void mitk::ActiveLearningInteractor::ConnectActionsAndFunctions() { CONNECT_FUNCTION("paint", Paint) - CONNECT_FUNCTION("paint_interpolate", PaintInterpolate) + CONNECT_FUNCTION("paint_interpolate", PaintInterpolate) } void mitk::ActiveLearningInteractor::DataNodeChanged() { this->ResetToStartState(); } void mitk::ActiveLearningInteractor::Paint(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event) { if (m_PaintingPixelValue == -1) return; 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); + geometry->WorldToIndex(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, image->GetVolumeData(timeStep)); writeAccessor.SetPixelByIndexSafe(index, m_PaintingPixelValue); image->Modified(); this->GetDataNode()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_LastPixelIndex = index; m_Used = true; } } catch (...) { return; } } void mitk::ActiveLearningInteractor::PaintInterpolate(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event) { if (m_PaintingPixelValue == -1) return; try { auto renderer = event->GetSender(); auto image = GetImage(this->GetDataNode()); auto timeStep = renderer->GetTimeStep(); auto geometry = GetGeometry(image, timeStep); + auto planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); auto positionEvent = dynamic_cast(event); auto position = positionEvent->GetPositionInWorld(); +// mitk::Point3D index; +// geometry->WorldToIndex(position, index); 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); +// // Extract current slice +// vtkSmartPointer reslice = vtkSmartPointer::New(); +// //set to false to extract a slice +// reslice->SetOverwriteMode(false); +// reslice->Modified(); +// mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); +// extractor->SetInput(image); +// extractor->SetTimeStep(timeStep); +// extractor->SetWorldGeometry(planeGeometry); +// extractor->SetVtkOutputRequest(false); +// extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); +// extractor->Modified(); +// extractor->Update(); +// auto workingSlice = extractor->GetOutput(); + +// // Draw a contour in Square according to selected brush size +// int radius = m_Size / 2; +// double fradius = static_cast(m_Size) / 2.0; + +// ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); + +// // estimate center point of the brush ( relative to the pixel the mouse points on ) +// // -- left upper corner for even sizes, +// // -- midpoint for uneven sizes +// mitk::Point2D centerCorrection; +// centerCorrection.Fill(0); + +// // even --> correction of [+0.5, +0.5] +// bool evenSize = ((m_Size % 2) == 0); +// if( evenSize ) +// { +// centerCorrection[0] += 0.5; +// centerCorrection[1] += 0.5; +// } + +// // we will compute the control points for the upper left quarter part of a circle contour +// std::vector< mitk::Point2D > quarterCycleUpperRight; +// std::vector< mitk::Point2D > quarterCycleLowerRight; +// std::vector< mitk::Point2D > quarterCycleLowerLeft; +// std::vector< mitk::Point2D > quarterCycleUpperLeft; + + +// mitk::Point2D curPoint; +// bool curPointIsInside = true; +// curPoint[0] = 0; +// curPoint[1] = radius; +// quarterCycleUpperRight.push_back(upperLeft(curPoint)); + +// // to estimate if a pixel is inside the circle, we need to compare against the 'outer radius' +// // i.e. the distance from the midpoint [0,0] to the border of the pixel [0,radius] +// //const float outer_radius = static_cast(radius) + 0.5; + +// while (curPoint[1] > 0) +// { +// // Move right until pixel is outside circle +// float curPointX_squared = 0.0f; +// float curPointY_squared = (curPoint[1] - centerCorrection[1] ) * (curPoint[1] - centerCorrection[1] ); +// while( curPointIsInside ) +// { +// // increment posX and chec +// curPoint[0]++; +// curPointX_squared = (curPoint[0] - centerCorrection[0] ) * (curPoint[0] - centerCorrection[0] ); +// const float len = sqrt( curPointX_squared + curPointY_squared); +// if ( len > fradius ) +// { +// // found first Pixel in this horizontal line, that is outside the circle +// curPointIsInside = false; +// } +// } +// quarterCycleUpperRight.push_back( upperLeft(curPoint) ); + +// // Move down until pixel is inside circle +// while( !curPointIsInside ) +// { +// // increment posX and chec +// curPoint[1]--; +// curPointY_squared = (curPoint[1] - centerCorrection[1] ) * (curPoint[1] - centerCorrection[1] ); +// const float len = sqrt( curPointX_squared + curPointY_squared); +// if ( len <= fradius ) +// { +// // found first Pixel in this horizontal line, that is outside the circle +// curPointIsInside = true; +// quarterCycleUpperRight.push_back( upperLeft(curPoint) ); +// } + +// // Quarter cycle is full, when curPoint y position is 0 +// if (curPoint[1] <= 0) +// break; +// } + +// } + +// // QuarterCycle is full! Now copy quarter cycle to other quarters. + +// if( !evenSize ) +// { +// std::vector< mitk::Point2D >::const_iterator it = quarterCycleUpperRight.begin(); +// while( it != quarterCycleUpperRight.end() ) +// { +// mitk::Point2D p; +// p = *it; + +// // the contour points in the lower right corner have same position but with negative y values +// p[1] *= -1; +// quarterCycleLowerRight.push_back(p); + +// // the contour points in the lower left corner have same position +// // but with both x,y negative +// p[0] *= -1; +// quarterCycleLowerLeft.push_back(p); + +// // the contour points in the upper left corner have same position +// // but with x negative +// p[1] *= -1; +// quarterCycleUpperLeft.push_back(p); + +// it++; +// } +// } +// else +// { +// std::vector< mitk::Point2D >::const_iterator it = quarterCycleUpperRight.begin(); +// while( it != quarterCycleUpperRight.end() ) +// { +// mitk::Point2D p,q; +// p = *it; + +// q = p; +// // the contour points in the lower right corner have same position but with negative y values +// q[1] *= -1; +// // correct for moved offset if size even = the midpoint is not the midpoint of the current pixel +// // but its upper rigt corner +// q[1] += 1; +// quarterCycleLowerRight.push_back(q); + +// q = p; +// // the contour points in the lower left corner have same position +// // but with both x,y negative +// q[1] = -1.0f * q[1] + 1; +// q[0] = -1.0f * q[0] + 1; +// quarterCycleLowerLeft.push_back(q); + +// // the contour points in the upper left corner have same position +// // but with x negative +// q = p; +// q[0] *= -1; +// q[0] += 1; +// quarterCycleUpperLeft.push_back(q); + +// it++; +// } +// } + +// // fill contour with points in right ordering, starting with the upperRight block +// mitk::Point3D tempPoint; +// for (unsigned int i=0; iAddVertex( tempPoint ); +// } +// // the lower right has to be parsed in reverse order +// for (int i=quarterCycleLowerRight.size()-1; i>=0; i--) +// { +// tempPoint[0] = quarterCycleLowerRight[i][0]; +// tempPoint[1] = quarterCycleLowerRight[i][1]; +// tempPoint[2] = 0; +// contourInImageIndexCoordinates->AddVertex( tempPoint ); +// } +// for (unsigned int i=0; iAddVertex( tempPoint ); +// } +// // the upper left also has to be parsed in reverse order +// for (int i=quarterCycleUpperLeft.size()-1; i>=0; i--) +// { +// tempPoint[0] = quarterCycleUpperLeft[i][0]; +// tempPoint[1] = quarterCycleUpperLeft[i][1]; +// tempPoint[2] = 0; +// contourInImageIndexCoordinates->AddVertex( tempPoint ); +// } + +// // round to nearest voxel center (abort if this hasn't changed) +// if ( m_Size % 2 == 0 ) // even +// { +// index[0] = ROUND(index[0]); +// index[1] = ROUND(index[1]); +// } +// else // odd +// { +// index[0] = ROUND(index[0]); +// index[1] = ROUND(index[1]); +// } + +// ContourModel::Pointer contour = ContourModel::New(); +// contour->Expand(timeStep + 1); +// contour->SetClosed(true, timeStep); + +// ContourModel::VertexIterator it = contourInImageIndexCoordinates->Begin(); +// ContourModel::VertexIterator end = contourInImageIndexCoordinates->End(); + +// while(it != end) +// { +// mitk::Point3D point = (*it)->Coordinates; +// point[0] += index[0]; +// point[1] += index[1]; + +// contour->AddVertex(point, timeStep); +// it++; +// } + +// double dist = index.EuclideanDistanceTo(m_LastPixelIndex); + +// // draw it the old way +// FeedbackContourTool::FillContourInSlice(contour, timeStep, workingSlice, m_PaintingPixelValue); + +// // if points are >= radius away draw rectangle to fill empty holes +// // in between the 2 points +// if (dist > radius) +// { +// const mitk::Point3D& currentPos = index; +// mitk::Point3D direction; +// mitk::Point3D vertex; +// mitk::Point3D normal; + +// direction[0] = index[0] - m_LastPixelIndex[0]; +// direction[1] = index[1] - m_LastPixelIndex[1]; +// direction[2] = index[2] - m_LastPixelIndex[2]; + +// direction[0] = direction.GetVnlVector().normalize()[0]; +// direction[1] = direction.GetVnlVector().normalize()[1]; +// direction[2] = direction.GetVnlVector().normalize()[2]; + +// // 90 degrees rotation of direction +// normal[0] = -1.0 * direction[1]; +// normal[1] = direction[0]; + +// contour->Clear(); + +// // upper left corner +// vertex[0] = m_LastPixelIndex[0] + (normal[0] * radius); +// vertex[1] = m_LastPixelIndex[1] + (normal[1] * radius); + +// contour->AddVertex(vertex); + +// // upper right corner +// vertex[0] = currentPos[0] + (normal[0] * radius); +// vertex[1] = currentPos[1] + (normal[1] * radius); + +// contour->AddVertex(vertex); + +// // lower right corner +// vertex[0] = currentPos[0] - (normal[0] * radius); +// vertex[1] = currentPos[1] - (normal[1] * radius); + +// contour->AddVertex(vertex); + +// // lower left corner +// vertex[0] = m_LastPixelIndex[0] - (normal[0] * radius); +// vertex[1] = m_LastPixelIndex[1] - (normal[1] * radius); + +// contour->AddVertex(vertex); + +// FeedbackContourTool::FillContourInSlice(contour, timeStep, workingSlice, m_PaintingPixelValue); +// } + +// m_LastPixelIndex = index; +// m_Used = true; + +// RenderingManager::GetInstance()->RequestUpdateAll(); + + // 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, image->GetVolumeData(timeStep)); + + // Paint all points between current and last pixel + auto indices = InterpolateIndices(m_LastPixelIndex, index, geometry, m_Size); + for (auto i : indices) + { + if (geometry->IsIndexInside(i)) + { + writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue); + } + } - // 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, 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(); - image->Modified(); - this->GetDataNode()->Modified(); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_LastPixelIndex = index; - m_Used = true; - } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + m_LastPixelIndex = index; + m_Used = true; + } } catch (...) { return; } } diff --git a/Modules/Classification/CLUtilities/files.cmake b/Modules/Classification/CLUtilities/files.cmake index cfcdfcfe53..990b650b01 100644 --- a/Modules/Classification/CLUtilities/files.cmake +++ b/Modules/Classification/CLUtilities/files.cmake @@ -1,24 +1,25 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES Algorithms/itkLabelSampler.cpp Algorithms/itkSmoothedClassProbabilites.cpp Features/itkNeighborhoodFunctorImageFilter.cpp Features/itkLineHistogramBasedMassImageFilter.cpp + Features/itkStructureTensorImageFilter.hxx GlobalImageFeatures/mitkGIFCooccurenceMatrix.cpp GlobalImageFeatures/mitkGIFGrayLevelRunLength.cpp GlobalImageFeatures/mitkGIFFirstOrderStatistics.cpp GlobalImageFeatures/mitkGIFVolumetricStatistics.cpp #GlobalImageFeatures/itkEnhancedScalarImageToRunLengthFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedScalarImageToRunLengthMatrixFilter.hxx #GlobalImageFeatures/itkEnhancedHistogramToRunLengthFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedHistogramToTextureFeaturesFilter.hxx #GlobalImageFeatures/itkEnhancedScalarImageToTextureFeaturesFilter.hxx mitkCLUtil.cpp ) set( TOOL_FILES ) diff --git a/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h b/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h index 191605b35e..30a5c14d54 100644 --- a/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h +++ b/Modules/Classification/CLUtilities/include/itkStructureTensorImageFilter.h @@ -1,144 +1,144 @@ /*========================================================================= * * Copyright Insight Software Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ // // Created by Jean-Marie Mirebeau on 05/03/2014. // // #ifndef itkStructureTensorImageFilter_h #define itkStructureTensorImageFilter_h #include "itkCastImageFilter.h" #include "itkGradientRecursiveGaussianImageFilter.h" #include "itkAddImageFilter.h" #include "itkVectorIndexSelectionCastImageFilter.h" #include "itkGradientImageFilter.h" #include "itkSymmetricSecondRankTensor.h" namespace itk { /** * \class StructureTensorImageFilter * * \brief Computes the structure tensor. * * Implementation of the structure tensor, defined by * * \f[K_\rho (\nabla u_\sigma \otimes \nabla u_\sigma),\f] * * where \f$K_\rho\f$ denotes the gaussian kernel of standard deviation \f$\rho\f$, * and \f$u_\sigma := K_\sigma * u\f$. * * \ingroup AnisotropicDiffusionLBR */ template< typename TImage, typename TTensorImage = Image< SymmetricSecondRankTensor< typename TImage::PixelType,TImage::ImageDimension >, TImage::ImageDimension > > class StructureTensorImageFilter: public ImageToImageFilter< TImage, TTensorImage > { public: typedef StructureTensorImageFilter Self; typedef ImageToImageFilter< TImage, TImage> Superclass; typedef SmartPointer Pointer; typedef SmartPointer ConstPointer; /// Method for creation through the object factory. itkNewMacro(Self); /// Run-time type information (and related methods). itkTypeMacro(StructureTensorImageFilter, Superclass); typedef TImage ImageType; typedef typename ImageType::PixelType PixelType; static const unsigned int Dimension = ImageType::ImageDimension; typedef TTensorImage TensorImageType; typedef typename TensorImageType::PixelType TensorType; typedef typename TensorType::ComponentType ScalarType; typedef Image ScalarImageType; ///Parameter \f$\sigma\f$ of the structure tensor definition. itkSetMacro(NoiseScale, ScalarType); ///Parameter \f$\rho\f$ of the structure tensor definition. itkSetMacro(FeatureScale, ScalarType); ///Rescales all structure tensors by a common factor, so that the maximum trace is 1. itkSetMacro(RescaleForUnitMaximumTrace, bool); itkGetConstMacro(NoiseScale, ScalarType); itkGetConstMacro(FeatureScale, ScalarType); itkGetConstMacro(RescaleForUnitMaximumTrace, bool); itkGetConstMacro(PostRescaling, ScalarType); /// Global rescaling constant used. protected: virtual void GenerateData() ITK_OVERRIDE; ScalarType m_FeatureScale; ScalarType m_NoiseScale; bool m_RescaleForUnitMaximumTrace; ScalarType m_PostRescaling; bool m_UseGradientRecursiveGaussianImageFilter; struct DispatchBase {}; template< bool > struct Dispatch: public DispatchBase {}; void IntermediateFilter( const Dispatch< true > & ); void IntermediateFilter( const Dispatch< false > & ); typename TensorImageType::Pointer m_IntermediateResult; typedef CovariantVector CovariantVectorType; typedef Image CovariantImageType; struct OuterFunctor { TensorType operator()(const CovariantVectorType & u) const { TensorType m; for( unsigned int i = 0; i < Dimension; ++i ) { for( unsigned int j = i; j < Dimension; ++j) { m(i,j) = u[i]*u[j]; } } return m; } }; struct TraceFunctor { ScalarType operator()(const TensorType & t) const { return t.GetTrace(); } }; struct ScaleFunctor { ScalarType scaling; TensorType operator()(const TensorType & t) const { return t*scaling; } }; StructureTensorImageFilter(); }; } // end namespace itk #ifndef ITK_MANUAL_INSTANTIATION -#include "itkStructureTensorImageFilter.hxx" +#include "../src/Features/itkStructureTensorImageFilter.hxx" #endif #endif diff --git a/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx b/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx index 0132a52c49..eb17a8715b 100644 --- a/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx +++ b/Modules/Classification/CLUtilities/src/Features/itkStructureTensorImageFilter.hxx @@ -1,164 +1,170 @@ /*========================================================================= * * Copyright Insight Software Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ // // Created by Jean-Marie Mirebeau on 21/11/2014. // // #ifndef itkStructureTensorImageFilter_hxx #define itkStructureTensorImageFilter_hxx #include "itkStructureTensorImageFilter.h" +#include + +#include namespace itk { template< typename TImage, typename TTensorImage > StructureTensorImageFilter< TImage, TTensorImage > ::StructureTensorImageFilter(): m_FeatureScale( 2 ), m_NoiseScale( 1 ), m_RescaleForUnitMaximumTrace( false ), m_UseGradientRecursiveGaussianImageFilter( true ) { } template< typename TImage, typename TTensorImage > void StructureTensorImageFilter< TImage, TTensorImage > ::IntermediateFilter( const Dispatch< true > & ) { + MITK_INFO << "Intermediate filter true"; typedef GradientRecursiveGaussianImageFilter< TImage, Self::CovariantImageType > GradientFilterType; typename GradientFilterType::Pointer gradientFilter = GradientFilterType::New(); gradientFilter->SetInput(this->GetInput()); gradientFilter->SetSigma(this->m_NoiseScale); typedef UnaryFunctorImageFilter< Self::CovariantImageType, Self::TensorImageType, Self::OuterFunctor > OuterFilterType; typename OuterFilterType::Pointer outerFilter = OuterFilterType::New(); outerFilter->SetInput(gradientFilter->GetOutput()); outerFilter->Update(); this->m_IntermediateResult = outerFilter->GetOutput(); } template< typename TImage, typename TTensorImage > void StructureTensorImageFilter< TImage, TTensorImage > ::IntermediateFilter( const Dispatch< false > & ) { + MITK_INFO << "Intermediate filter false"; typename Self::ImageType::ConstPointer input = this->GetInput(); typename Self::TensorImageType::Pointer output = Self::TensorImageType::New(); output->CopyInformation(input); output->SetRegions(input->GetRequestedRegion()); output->Allocate(); output->FillBuffer(Self::TensorType(0.)); for( unsigned int index = 0; index < Self::PixelType::Dimension; ++index ) { typedef VectorIndexSelectionCastImageFilter< Self::ImageType, Self::ScalarImageType > SelectionFilterType; typename SelectionFilterType::Pointer selectionFilter = SelectionFilterType::New(); selectionFilter->SetIndex(index); selectionFilter->SetInput(input); typedef RecursiveGaussianImageFilter< Self::ScalarImageType > GaussianFilterType; typedef GradientImageFilter< Self::ScalarImageType, Self::ScalarType, Self::ScalarType, Self::CovariantImageType > GradientFilterType; typedef GradientRecursiveGaussianImageFilter< Self::ScalarImageType, Self::CovariantImageType > GradientGaussianFilterType; typename GaussianFilterType::Pointer gaussianFilter = GaussianFilterType::New(); typename GradientFilterType::Pointer gradientFilter = GradientFilterType::New(); typename GradientGaussianFilterType::Pointer gradientGaussianFilter = GradientGaussianFilterType::New(); gaussianFilter->SetSigma(this->m_NoiseScale); gradientGaussianFilter->SetSigma(this->m_NoiseScale); typedef UnaryFunctorImageFilter< Self::CovariantImageType, Self::TensorImageType, Self::OuterFunctor > OuterFilterType; typename OuterFilterType::Pointer outerFilter = OuterFilterType::New(); if( this->m_UseGradientRecursiveGaussianImageFilter ) { gradientGaussianFilter->SetInput(selectionFilter->GetOutput()); outerFilter->SetInput(gradientGaussianFilter->GetOutput()); } else { gaussianFilter->SetInput(selectionFilter->GetOutput()); gradientFilter->SetInput(gaussianFilter->GetOutput()); outerFilter->SetInput(gradientFilter->GetOutput()); } typedef AddImageFilter< Self::TensorImageType > AddFilterType; typename AddFilterType::Pointer addFilter = AddFilterType::New(); addFilter->InPlaceOn(); addFilter->SetInput1(output); addFilter->SetInput2(outerFilter->GetOutput()); addFilter->Update(); output = addFilter->GetOutput(); this->UpdateProgress(index/float( Self::PixelType::Dimension+1 )); } this->m_IntermediateResult = output; } template< typename TImage, typename TTensorImage > void StructureTensorImageFilter< TImage, TTensorImage > ::GenerateData() { + MITK_INFO << "Trying to generate data"; this->IntermediateFilter( Dispatch< std::numeric_limits< PixelType >::is_specialized >() ); typedef RecursiveGaussianImageFilter GaussianFilterType; typename GaussianFilterType::Pointer gaussianFilter = GaussianFilterType::New(); gaussianFilter->SetInput( m_IntermediateResult ); gaussianFilter->SetSigma( m_FeatureScale ); if( !m_RescaleForUnitMaximumTrace ) { m_PostRescaling = 1.; gaussianFilter->Update(); this->GraftOutput(gaussianFilter->GetOutput()); return; } // *** Rescaling for normalization of largest trace *** typedef UnaryFunctorImageFilter TraceFilterType; typename TraceFilterType::Pointer traceFilter = TraceFilterType::New(); traceFilter->SetInput(gaussianFilter->GetOutput()); typedef MinimumMaximumImageCalculator MaximumCalculatorType; typename MaximumCalculatorType::Pointer maximumCalculator = MaximumCalculatorType::New(); maximumCalculator->SetImage(traceFilter->GetOutput()); typedef UnaryFunctorImageFilter ScaleFilterType; typename ScaleFilterType::Pointer scaleFilter = ScaleFilterType::New(); scaleFilter->SetInput(gaussianFilter->GetOutput()); traceFilter->Update(); maximumCalculator->ComputeMaximum(); m_PostRescaling = 1./maximumCalculator->GetMaximum(); scaleFilter->GetFunctor().scaling = m_PostRescaling; scaleFilter->Update(); this->GraftOutput(scaleFilter->GetOutput()); } } // end namespace itk #endif diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp index f3bb421031..f76d0724d3 100644 --- a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp @@ -1,1177 +1,1229 @@ /*=================================================================== 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 // ITK/VTK #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]); - } -} +//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> TensorImageType; +// auto filter = itk::StructureTensorImageFilter::New(); +// filter->SetInput(inputImage); +// filter->SetNoiseScale(sigma); +// filter->SetFeatureScale(sigma); +// try +// { +// filter->Update(); +// } +// catch (...) +// { +// mitkThrow(); +// } + +// 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; // 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); emit SignalSetProgressMaximum(m_Nodes.length() * 6 + 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); // ======================================= // PREDICTION NODE // ======================================= // m_PredictionNode = mitk::DataNode::New(); // m_PredictionNode->SetName("Predictions"); //// m_PredictionNode->SetColor(1., 1. ,1.); //// m_PredictionNode->SetBoolProperty("binary", false); // m_PredictionNode->SetProperty("opacity", mitk::FloatProperty::New(0.0f)); //// m_PredictionNode->SetBoolProperty("helper object", true); // this->GetDataStorage()->Add(m_PredictionNode, m_Nodes[0]); // ======================================= // 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)); // m_PredictionNode->SetBoolProperty("helper object", true); 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)); // m_AnnotationNode->SetBoolProperty("helper object", true); this->GetDataStorage()->Add(m_AnnotationNode, 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); } 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_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))); // 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)); - 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(""); - } +// 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)); // 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::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, 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(); } 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::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()) 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); } 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]); } UpdateLookupTables(); UpdatePredictionNodes(); emit SignalResetProgress(); m_Controls.m_UpdatePredictionsPushButton->setEnabled(true); } /* ================================================================== * 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"); } } }