diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.cpp b/Modules/ContourModel/DataManagement/mitkContourElement.cpp index 02cfc0c4a3..0bfbe91f9c 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourElement.cpp @@ -1,489 +1,541 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include bool mitk::ContourElement::ContourModelVertex::operator==(const ContourModelVertex &other) const { return this->Coordinates == other.Coordinates && this->IsControlPoint == other.IsControlPoint; } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorBegin() const { return this->begin(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorEnd() const { return this->end(); } mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorBegin() { return this->begin(); } mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorEnd() { return this->end(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::begin() const { return this->m_Vertices.begin(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::end() const { return this->m_Vertices.end(); } mitk::ContourElement::VertexIterator mitk::ContourElement::begin() { return this->m_Vertices.begin(); } mitk::ContourElement::VertexIterator mitk::ContourElement::end() { return this->m_Vertices.end(); } mitk::ContourElement::ContourElement(const mitk::ContourElement &other) : itk::LightObject(), m_IsClosed(other.m_IsClosed) { for (const auto &v : other.m_Vertices) { m_Vertices.push_back(new ContourModelVertex(*v)); } } mitk::ContourElement &mitk::ContourElement::operator=(const ContourElement &other) { if (this != &other) { this->Clear(); for (const auto &v : other.m_Vertices) { m_Vertices.push_back(new ContourModelVertex(*v)); } } this->m_IsClosed = other.m_IsClosed; return *this; } mitk::ContourElement::~ContourElement() { this->Clear(); } mitk::ContourElement::VertexSizeType mitk::ContourElement::GetSize() const { return this->m_Vertices.size(); } void mitk::ContourElement::AddVertex(const mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices.push_back(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::AddVertexAtFront(const mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices.push_front(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::InsertVertexAtIndex(const mitk::Point3D &vertex, bool isControlPoint, VertexSizeType index) { - if (this->GetSize() > index) + if (this->GetSize() >= index) { auto _where = this->m_Vertices.begin(); _where += index; this->m_Vertices.insert(_where, new VertexType(vertex, isControlPoint)); } } void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const Point3D &point) { if (this->GetSize() > pointId) { this->m_Vertices[pointId]->Coordinates = point; } } void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const VertexType *vertex) { if (nullptr == vertex) { mitkThrow() << "Cannot set vertex. Passed vertex instance is invalid. Index to set: " << pointId; } if (this->GetSize() > pointId) { this->m_Vertices[pointId]->Coordinates = vertex->Coordinates; this->m_Vertices[pointId]->IsControlPoint = vertex->IsControlPoint; } } mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(VertexSizeType index) { return this->m_Vertices.at(index); } const mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(VertexSizeType index) const { return this->m_Vertices.at(index); } bool mitk::ContourElement::IsEmpty() const { return this->m_Vertices.empty(); } mitk::ContourElement::VertexType *mitk::ContourElement::GetControlVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps, true); } // if eps < 0 return nullptr; } mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps); } // if eps < 0 return nullptr; } mitk::ContourElement::VertexType *mitk::ContourElement::GetNextControlVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps, true, 1); } // if eps < 0 return nullptr; } mitk::ContourElement::VertexType *mitk::ContourElement::GetPreviousControlVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps, true, -1); } // if eps < 0 return nullptr; } mitk::ContourElement::VertexType *mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, double eps, bool isControlPoint, int offset) { VertexListType verticesList; if (isControlPoint) { verticesList = this->GetControlVertices(); } else { verticesList = *this->GetVertexList(); } int vertexIndex = BruteForceGetVertexIndexAt(point, eps, verticesList); if (vertexIndex!=-1) { vertexIndex += offset; if (vertexIndex < 0) { // for negative offset // if the offset exceeds the first vertex, we start from the end of the vertex list backwards vertexIndex = verticesList.size() + offset; } else if (vertexIndex >= (int) verticesList.size()) { // if the offset exceeds the last vertex, we start from the beginning of the vertex list vertexIndex = vertexIndex - verticesList.size(); } return verticesList[vertexIndex]; } return nullptr; } int mitk::ContourElement::BruteForceGetVertexIndexAt(const mitk::Point3D &point, double eps, VertexListType verticesList) { if (eps < 0) { mitkThrow() << "Distance cannot be negative"; } ConstVertexIterator nearestPointIterator; bool nearestPointIsInitialized = false; double nearestPointDistance = std::numeric_limits::max(); ConstVertexIterator it = verticesList.begin(); ConstVertexIterator end = verticesList.end(); while (it != end) { mitk::Point3D currentPoint = (*it)->Coordinates; double distance = currentPoint.EuclideanDistanceTo(point); if (distance < eps) { if (distance < nearestPointDistance) { nearestPointIterator = it; nearestPointIsInitialized = true; nearestPointDistance = distance; } } // if distance > eps it++; } // while if (nearestPointIsInitialized) { return nearestPointIterator - verticesList.begin(); } return -1; } const mitk::ContourElement::VertexListType *mitk::ContourElement::GetVertexList() const { return &(this->m_Vertices); } bool mitk::ContourElement::IsClosed() const { return this->m_IsClosed; } -bool mitk::ContourElement::IsNearContour(const mitk::Point3D &point, float eps) const +bool mitk::ContourElement::IsNearContour(const mitk::Point3D& point, float eps) const +{ + VertexSizeType segmentStartIndex; + VertexSizeType segmentEndIndex; + mitk::Point3D closestContourPoint; + + return GetLineSegmentForPoint(point, eps, segmentStartIndex, segmentEndIndex, closestContourPoint, false); +} + +bool mitk::ContourElement::GetLineSegmentForPoint(const mitk::Point3D& point, + float eps, VertexSizeType& segmentStartIndex, VertexSizeType& segmentEndIndex, mitk::Point3D& closestContourPoint, bool findClosest) const { ConstVertexIterator it1 = this->m_Vertices.begin(); ConstVertexIterator it2 = this->m_Vertices.begin(); it2++; // it2 runs one position ahead ConstVertexIterator end = this->m_Vertices.end(); - int counter = 0; - - for (; it1 != end; it1++, it2++, counter++) + bool closePointFound = false; + double closestDistance = std::numeric_limits::max(); + for (; it1 != end; it1++, it2++) { if (it2 == end) it2 = this->m_Vertices.begin(); mitk::Point3D v1 = (*it1)->Coordinates; mitk::Point3D v2 = (*it2)->Coordinates; const float l2 = v1.SquaredEuclideanDistanceTo(v2); mitk::Vector3D p_v1 = point - v1; mitk::Vector3D v2_v1 = v2 - v1; double tc = (p_v1 * v2_v1) / l2; // take into account we have line segments and not (infinite) lines if (tc < 0.0) + { tc = 0.0; + } if (tc > 1.0) + { tc = 1.0; + } mitk::Point3D crossPoint = v1 + v2_v1 * tc; double distance = point.SquaredEuclideanDistanceTo(crossPoint); - if (distance < eps) + if (distance < eps && distance < closestDistance) { - return true; + closestDistance = distance; + closePointFound = true; + closestContourPoint = crossPoint; + segmentStartIndex = std::distance(this->m_Vertices.begin(), it1); + segmentEndIndex = std::distance(this->m_Vertices.begin(), it2); + if (!findClosest) + { + return true; + } } } - return false; + return closePointFound; } +bool mitk::ContourElement::GetLineSegmentForPoint(const mitk::Point3D &point, + float eps, + mitk::ContourElement::VertexType *previousVertex, + mitk::ContourElement::VertexType *nextVertex) const +{ + VertexSizeType segmentStartIndex; + VertexSizeType segmentEndIndex; + mitk::Point3D closestContourPoint; + + auto result = GetLineSegmentForPoint(point, eps, segmentStartIndex, segmentEndIndex, closestContourPoint, true); + + if (result) + { + ConstVertexIterator it1 = this->m_Vertices.begin() + segmentStartIndex; + ConstVertexIterator it2 = this->m_Vertices.begin() + segmentEndIndex; + if (previousVertex) + { + previousVertex->Coordinates = (*it1)->Coordinates; + previousVertex->IsControlPoint = (*it1)->IsControlPoint; + } + if (nextVertex) + { + nextVertex->Coordinates = (*it2)->Coordinates; + nextVertex->IsControlPoint = (*it2)->IsControlPoint; + } + } + return result; +} + + void mitk::ContourElement::Close() { this->m_IsClosed = true; } void mitk::ContourElement::Open() { this->m_IsClosed = false; } void mitk::ContourElement::SetClosed(bool isClosed) { isClosed ? this->Close() : this->Open(); } mitk::ContourElement::VertexListType mitk::ContourElement::GetControlVertices() const { VertexListType controlVertices; std::copy_if( this->m_Vertices.begin(), this->m_Vertices.end(), std::back_inserter(controlVertices), [](const VertexType *v) { return v->IsControlPoint; }); return controlVertices; } void mitk::ContourElement::Concatenate(const mitk::ContourElement *other, bool check) { if (other->GetSize() > 0) { for (const auto &sourceVertex : other->m_Vertices) { if (check) { auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [sourceVertex](const VertexType *v) { return sourceVertex->Coordinates == v->Coordinates; }); if (finding == this->m_Vertices.end()) { this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } } else { this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } } } } mitk::ContourElement::VertexSizeType mitk::ContourElement::GetIndex(const VertexType *vertex) const { VertexSizeType result = NPOS; auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); if (finding != this->m_Vertices.end()) { result = finding - this->m_Vertices.begin(); } return result; } bool mitk::ContourElement::RemoveVertex(const VertexType *vertex) { auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); return RemoveVertexByIterator(finding); } bool mitk::ContourElement::RemoveVertexAt(VertexSizeType index) { if (index < this->m_Vertices.size()) { auto delIter = this->m_Vertices.begin() + index; return RemoveVertexByIterator(delIter); } return false; } bool mitk::ContourElement::RemoveVertexAt(const mitk::Point3D &point, double eps) { if (eps > 0) { auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [point, eps](const VertexType *v) { return v->Coordinates.EuclideanDistanceTo(point) < eps; }); return RemoveVertexByIterator(finding); } return false; } bool mitk::ContourElement::RemoveVertexByIterator(VertexListType::iterator &iter) { if (iter != this->m_Vertices.end()) { delete *iter; this->m_Vertices.erase(iter); return true; } return false; } void mitk::ContourElement::Clear() { for (auto vertex : m_Vertices) { delete vertex; } this->m_Vertices.clear(); } //---------------------------------------------------------------------- void mitk::ContourElement::RedistributeControlVertices(const VertexType *selected, int period) { int counter = 0; auto _where = this->m_Vertices.begin(); if (selected != nullptr) { auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), selected); if (finding != this->m_Vertices.end()) { _where = finding; } } auto _iter = _where; while (_iter != this->m_Vertices.end()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter++; } _iter = _where; counter = 0; while (_iter != this->m_Vertices.begin()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter--; } } diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.h b/Modules/ContourModel/DataManagement/mitkContourElement.h index 4a5e52864e..3175781eba 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.h +++ b/Modules/ContourModel/DataManagement/mitkContourElement.h @@ -1,290 +1,308 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _mitkContourElement_H_ #define _mitkContourElement_H_ #include "mitkCommon.h" #include #include #include namespace mitk { /** \brief Represents a contour in 3D space. A ContourElement is consisting of linked vertices implicitely defining the contour. They are stored in a double ended queue making it possible to add vertices at front and end of the contour and to iterate in both directions. To mark a vertex as a special one it can be set as a control point. \note This class assumes that it manages its vertices. So if a vertex instance is added to this class the ownership of the vertex is transfered to the ContourElement instance. The ContourElement instance takes care of deleting vertex instances if needed. It is highly not recommend to use this class directly as it is designed as a internal class of ContourModel. Therefore it is adviced to use ContourModel if contour representations are needed in MITK. */ class MITKCONTOURMODEL_EXPORT ContourElement : public itk::LightObject { public: mitkClassMacroItkParent(ContourElement, itk::LightObject); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Represents a single vertex of a contour. */ struct MITKCONTOURMODEL_EXPORT ContourModelVertex { ContourModelVertex(const mitk::Point3D& point, bool active = false) : IsControlPoint(active), Coordinates(point) {}; ContourModelVertex(const ContourModelVertex& other) : IsControlPoint(other.IsControlPoint), Coordinates(other.Coordinates) { }; /** \brief Treat point special. */ bool IsControlPoint; /** \brief Coordinates in 3D space. */ mitk::Point3D Coordinates; bool operator ==(const ContourModelVertex& other) const; }; using VertexType = ContourModelVertex; using VertexListType = std::deque; using VertexIterator = VertexListType::iterator; using ConstVertexIterator = VertexListType::const_iterator; using VertexSizeType = VertexListType::size_type; /**Indicates an invalid index. * It is always the maximum of the unsigned int type.*/ static const VertexSizeType NPOS = -1; /** \brief Return a const iterator a the front. */ ConstVertexIterator ConstIteratorBegin() const; /** \brief Return a const iterator a the end. */ ConstVertexIterator ConstIteratorEnd() const; /** \brief Return an iterator a the front. */ VertexIterator IteratorBegin(); /** \brief Return an iterator a the end. */ VertexIterator IteratorEnd(); /** \brief Return a const iterator a the front. * For easier support of stl functionality. */ ConstVertexIterator begin() const; /** \brief Return a const iterator a the end. * For easier support of stl functionality. */ ConstVertexIterator end() const; /** \brief Return an iterator a the front. * For easier support of stl functionality. */ VertexIterator begin(); /** \brief Return an iterator a the end. * For easier support of stl functionality. */ VertexIterator end(); /** \brief Returns the number of contained vertices. */ VertexSizeType GetSize() const; /** \brief Add a vertex at the end of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. */ void AddVertex(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at the front of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a control point. */ void AddVertexAtFront(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at a given index of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. \param index - the index to be inserted at. */ void InsertVertexAtIndex(const mitk::Point3D &point, bool isControlPoint, VertexSizeType index); /** \brief Set coordinates a given index. \param pointId Index of vertex. \param point Coordinates. */ void SetVertexAt(VertexSizeType pointId, const mitk::Point3D &point); /** \brief Set vertex a given index (by copying the values). \param pointId Index of vertex. \param vertex Vertex. \pre Passed vertex is a valid instance */ void SetVertexAt(VertexSizeType pointId, const VertexType* vertex); /** \brief Returns the vertex a given index \param index \pre index must be valid. */ VertexType* GetVertexAt(VertexSizeType index); const VertexType* GetVertexAt(VertexSizeType index) const; /** \brief Returns the approximate nearest vertex a given position in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ VertexType *GetVertexAt(const mitk::Point3D &point, float eps); /** \brief Returns the next vertex to the approximate nearest vertex of a given position in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ VertexType *GetNextControlVertexAt(const mitk::Point3D &point, float eps); /** \brief Returns the previous vertex to the approximate nearest vertex of a given position in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ VertexType *GetPreviousControlVertexAt(const mitk::Point3D &point, float eps); /** \brief Returns the approximate nearest control vertex a given posoition in 3D space, if the clicked position is within a specific range. \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ VertexType *GetControlVertexAt(const mitk::Point3D &point, float eps); /** \brief Returns the index of the given vertex within the contour. \param vertex - the vertex to be searched. \return index of vertex. Returns ContourElement::NPOS if not found. */ VertexSizeType GetIndex(const VertexType *vertex) const; /** \brief Returns the container of the vertices. */ const VertexListType *GetVertexList() const; /** \brief Returns whether the contour element is empty. */ bool IsEmpty() const; /** \brief Returns if the conour is closed or not. */ bool IsClosed() const; /** \brief Returns whether a given point is near a contour, according to eps. \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ bool IsNearContour(const mitk::Point3D &point, float eps) const; + /** Function that searches for the line segment of the contour that is closest to the passed point + and close enough (distance between point and line segment <= eps). If such an line segment exist, + the starting vertex and closing vertex of the found segment are passed back. + @return True indicates that a line segment was found. False indicates that no segment of the contour + is close enough to the passed point. + @remark previousVertex and nextVertex are only valid if return is true.*/ + bool GetLineSegmentForPoint(const mitk::Point3D &point, + float eps, + mitk::ContourElement::VertexType *previousVertex, + mitk::ContourElement::VertexType *nextVertex) const; + /** Overloaded version that offers additional options when searching for the line segment. + In contrast to the other version it returns the index of the segment start and end as well as the point + on the line segment closest to the passed point. Further one can decide if the function should search for + the first segment that is close enough (see eps) or for the segment that is really the closest (findClosest==true). + @remark segmentStartIndex, segmentEndIndex and closestContourPoint are only valid if return is true.*/ + bool GetLineSegmentForPoint(const mitk::Point3D& point, + float eps, VertexSizeType& segmentStartIndex, VertexSizeType& segmentEndIndex, mitk::Point3D& closestContourPoint, bool findClosest = true) const; + /** \brief Close the contour. Connect first with last element. */ void Close(); /** \brief Open the contour. Disconnect first and last element. */ void Open(); /** \brief Set the contours IsClosed property. \param isClosed - true = closed; false = open; */ void SetClosed(bool isClosed); /** \brief Concatenate the contuor with a another contour. All vertices of the other contour will be cloned and added after last vertex. \param other - the other contour \param check - set it true to avoid adding of vertices that are already in the source contour */ void Concatenate(const mitk::ContourElement *other, bool check); /** \brief Remove the given vertex from the container if exists. \param vertex - the vertex to be removed. */ bool RemoveVertex(const VertexType *vertex); /** \brief Remove a vertex at given index within the container if exists. \param index - the index where the vertex should be removed. */ bool RemoveVertexAt(VertexSizeType index); /** \brief Remove the approximate nearest vertex at given position in 3D space if one exists. \param point - query point in 3D space. \param eps - error bound for search algorithm. */ bool RemoveVertexAt(const mitk::Point3D &point, double eps); /** \brief Clear the storage container. */ void Clear(); /** \brief Returns the approximate nearest vertex a given position in 3D space. With the parameter 'isControlPoint', one can decide if any vertex should be returned, or just control vertices. \param point - query position in 3D space. \param eps - the error bound for search algorithm. It is an open boundary. \param isControlPoint \param offset - a offset to the vertex, e.g. 1 if the next vertex should be returned or -1 for the previous vertex */ VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, double eps, bool isControlPoint = false, int offset = 0); /** \brief Returns the index of the approximate nearest vertex of a given position in 3D space. \param point - query position in 3D space. \param eps - the error bound for search algorithm. It is an open boundary. \param verticesList - the vertex list to search the index in, either only control vertices or all vertices */ int BruteForceGetVertexIndexAt(const mitk::Point3D &point, double eps, VertexListType verticesList); /** Returns a list pointing to all vertices that are indicated to be control points. \remark It is important to note, that the vertex pointers in the returned list directly point to the vertices stored interanlly. So they are still owned by the ContourElement instance that returns the list. If one wants to take over ownership, one has to clone the vertex instances. */ VertexListType GetControlVertices() const; /** \brief Uniformly redistribute control points with a given period (in number of vertices) \param vertex - the vertex around which the redistribution is done. \param period - number of vertices between control points. */ void RedistributeControlVertices(const VertexType *vertex, int period); protected: mitkCloneMacro(Self); ContourElement() = default; ContourElement(const mitk::ContourElement &other); ~ContourElement(); ContourElement& operator = (const ContourElement & other); /** Internal helper function to correctly remove the element indicated by the iterator from the list. After the call the iterator is invalid. Caller of the function must ensure that the iterator is valid!. \result Indicates if the element indicated by the iterator was removed. If iterator points to end it returns false.*/ bool RemoveVertexByIterator(VertexListType::iterator& iter); VertexListType m_Vertices; // double ended queue with vertices bool m_IsClosed = false; }; } // namespace mitk #endif // _mitkContourElement_H_ diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.cpp b/Modules/ContourModel/DataManagement/mitkContourModel.cpp index 518abf4352..1f7c53febc 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourModel.cpp @@ -1,690 +1,729 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include namespace mitk { itkEventMacroDefinition(ContourModelEvent, itk::AnyEvent); itkEventMacroDefinition(ContourModelShiftEvent, ContourModelEvent); itkEventMacroDefinition(ContourModelSizeChangeEvent, ContourModelEvent); itkEventMacroDefinition(ContourModelAddEvent, ContourModelSizeChangeEvent); itkEventMacroDefinition(ContourModelRemoveEvent, ContourModelSizeChangeEvent); itkEventMacroDefinition(ContourModelExpandTimeBoundsEvent, ContourModelEvent); itkEventMacroDefinition(ContourModelClosedEvent, ContourModelEvent); } mitk::ContourModel::ContourModel() : m_UpdateBoundingBox(true) { // set to initial state this->InitializeEmpty(); } mitk::ContourModel::ContourModel(const ContourModel &other) : BaseData(other), m_ContourSeries(other.m_ContourSeries), m_lineInterpolation(other.m_lineInterpolation) { m_SelectedVertex = nullptr; } mitk::ContourModel::~ContourModel() { m_SelectedVertex = nullptr; this->m_ContourSeries.clear(); // TODO check destruction } void mitk::ContourModel::AddVertex(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertex(vertex, false, timestep); } } void mitk::ContourModel::AddVertex(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertex(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::AddVertex(const VertexType &vertex, TimeStepType timestep) { this->AddVertex(vertex.Coordinates, vertex.IsControlPoint, timestep); } void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertexAtFront(vertex, false, timestep); } } void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertexAtFront(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::AddVertexAtFront(const VertexType &vertex, TimeStepType timestep) { this->AddVertexAtFront(vertex.Coordinates, vertex.IsControlPoint, timestep); } bool mitk::ContourModel::SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, point); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } bool mitk::ContourModel::SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep) { if (vertex == nullptr) return false; if (!this->IsEmptyTimeStep(timestep)) { if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, vertex); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } void mitk::ContourModel::InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { - if (index >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(index)) + if (index >= 0 && this->m_ContourSeries[timestep]->GetSize() >= ContourElement::VertexSizeType(index)) { this->m_ContourSeries[timestep]->InsertVertexAtIndex(vertex, isControlPoint, index); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } void mitk::ContourModel::UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) { mitkThrow() << "Cannot update contour. Passed source model is invalid."; } if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) { mitkThrow() << "Cannot update contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; } if (!this->GetTimeGeometry()->IsValidTimeStep(destinationTimeStep)) { MITK_WARN << "Cannot update contour. Contour time geometry does not support passed time step. Invalid time step: " << destinationTimeStep; return; } this->Clear(destinationTimeStep); std::for_each(sourceModel->Begin(sourceTimeStep), sourceModel->End(sourceTimeStep), [this, destinationTimeStep](ContourElement::VertexType* vertex) { this->m_ContourSeries[destinationTimeStep]->AddVertex(vertex->Coordinates, vertex->IsControlPoint); }); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } bool mitk::ContourModel::IsEmpty(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsEmpty(); } return true; } bool mitk::ContourModel::IsEmpty() const { return this->IsEmpty(0); } int mitk::ContourModel::GetNumberOfVertices(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetSize(); } return -1; } const mitk::ContourModel::VertexType *mitk::ContourModel::GetVertexAt(int index, TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep) && this->m_ContourSeries[timestep]->GetSize() > mitk::ContourElement::VertexSizeType(index)) { return this->m_ContourSeries[timestep]->GetVertexAt(index); } return nullptr; } +const mitk::ContourModel::VertexType *mitk::ContourModel::GetVertexAt(mitk::Point3D &point, + float eps, + TimeStepType timestep) const +{ + if (!this->IsEmptyTimeStep(timestep)) + { + return this->m_ContourSeries[timestep]->GetVertexAt(point, eps); + } + return nullptr; +} + + const mitk::ContourModel::VertexType *mitk::ContourModel::GetNextControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetNextControlVertexAt(point, eps); } return nullptr; } const mitk::ContourModel::VertexType *mitk::ContourModel::GetPreviousControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetPreviousControlVertexAt(point, eps); } return nullptr; } int mitk::ContourModel::GetIndex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetIndex(vertex); } return -1; } void mitk::ContourModel::Close(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Close(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::Open(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Open(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::SetClosed(bool isClosed, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->SetClosed(isClosed); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } bool mitk::ContourModel::IsEmptyTimeStep(unsigned int t) const { return (this->m_ContourSeries.size() <= t); } -bool mitk::ContourModel::IsNearContour(Point3D &point, float eps, TimeStepType timestep) +bool mitk::ContourModel::IsNearContour(Point3D &point, float eps, TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsNearContour(point, eps); } return false; } +bool mitk::ContourModel::GetLineSegmentForPoint(const mitk::Point3D& point, + float eps, TimeStepType timestep, + ContourElement::VertexSizeType& segmentStartIndex, + ContourElement::VertexSizeType& segmentEndIndex, + mitk::Point3D& closestContourPoint, + bool findClosest) const +{ + if (!this->IsEmptyTimeStep(timestep)) + { + return this->m_ContourSeries[timestep]->GetLineSegmentForPoint(point, eps, segmentStartIndex, segmentEndIndex, closestContourPoint, findClosest); + } + return false; +} + +bool mitk::ContourModel::GetLineSegmentForPoint(Point3D &point, + float eps, + TimeStepType timestep, + mitk::ContourElement::VertexType *previousVertex, + mitk::ContourElement::VertexType *nextVertex) +{ + if (!this->IsEmptyTimeStep(timestep)) + { + return this->m_ContourSeries[timestep]->GetLineSegmentForPoint(point, eps, previousVertex, nextVertex); + } + return false; +} + void mitk::ContourModel::Concatenate(ContourModel *other, TimeStepType timestep, bool check) { if (!this->IsEmptyTimeStep(timestep)) { if (!this->m_ContourSeries[timestep]->IsClosed()) { this->m_ContourSeries[timestep]->Concatenate(other->m_ContourSeries[timestep], check); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } mitk::ContourModel::VertexIterator mitk::ContourModel::Begin(TimeStepType timestep) const { return this->IteratorBegin(timestep); } mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorBegin(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorBegin(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } mitk::ContourModel::VertexIterator mitk::ContourModel::End(TimeStepType timestep) const { return this->IteratorEnd(timestep); } mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorEnd(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorEnd(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } bool mitk::ContourModel::IsClosed(int timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsClosed(); } return false; } bool mitk::ContourModel::SelectControlVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetControlVertexAt(point, eps); } return this->m_SelectedVertex != nullptr; } bool mitk::ContourModel::SelectVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); } return this->m_SelectedVertex != nullptr; } bool mitk::ContourModel::SelectVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { return (this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(index)); } return false; } bool mitk::ContourModel::SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } bool mitk::ContourModel::SetControlVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(index); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } bool mitk::ContourModel::RemoveVertex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertex(vertex)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } bool mitk::ContourModel::RemoveVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(index)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } bool mitk::ContourModel::RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(point, eps)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } void mitk::ContourModel::ShiftSelectedVertex(Vector3D &translate) { if (this->m_SelectedVertex) { this->ShiftVertex(this->m_SelectedVertex, translate); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::ShiftContour(Vector3D &translate, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { // shift all vertices for (auto vertex : *(this->m_ContourSeries[timestep])) { this->ShiftVertex(vertex, translate); } this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelShiftEvent()); } } void mitk::ContourModel::ShiftVertex(VertexType *vertex, Vector3D &vector) { vertex->Coordinates[0] += vector[0]; vertex->Coordinates[1] += vector[1]; vertex->Coordinates[2] += vector[2]; } void mitk::ContourModel::Clear(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { // clear data at timestep this->m_ContourSeries[timestep]->Clear(); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::Expand(unsigned int timeSteps) { std::size_t oldSize = this->m_ContourSeries.size(); if (static_cast(timeSteps) > oldSize) { Superclass::Expand(timeSteps); // insert contours for each new timestep for (std::size_t i = oldSize; i < static_cast(timeSteps); i++) { m_ContourSeries.push_back(ContourElement::New()); } this->InvokeEvent(ContourModelExpandTimeBoundsEvent()); } } void mitk::ContourModel::SetRequestedRegionToLargestPossibleRegion() { // no support for regions } bool mitk::ContourModel::RequestedRegionIsOutsideOfTheBufferedRegion() { // no support for regions return false; } bool mitk::ContourModel::VerifyRequestedRegion() { // no support for regions return true; } void mitk::ContourModel::SetRequestedRegion(const itk::DataObject * /*data*/) { // no support for regions } void mitk::ContourModel::Clear() { // clear data and set to initial state again this->ClearData(); this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::RedistributeControlVertices(int period, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->RedistributeControlVertices(this->GetSelectedVertex(), period); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } mitk::ContourModel::VertexListType mitk::ContourModel::GetControlVertices(TimeStepType timestep) { VertexListType controlVertices; if (!this->IsEmptyTimeStep(timestep)) { controlVertices = this->m_ContourSeries[timestep]->GetControlVertices(); } return controlVertices; } mitk::ContourModel::VertexListType mitk::ContourModel::GetVertexList(TimeStepType timestep) { VertexListType controlVertices; if (!this->IsEmptyTimeStep(timestep)) { controlVertices = *this->m_ContourSeries[timestep]->GetVertexList(); } return controlVertices; } void mitk::ContourModel::ClearData() { // call the superclass, this releases the data of BaseData Superclass::ClearData(); // clear out the time resolved contours this->m_ContourSeries.clear(); } void mitk::ContourModel::Initialize() { this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::Initialize(const ContourModel &other) { TimeStepType numberOfTimesteps = other.GetTimeGeometry()->CountTimeSteps(); this->InitializeTimeGeometry(numberOfTimesteps); for (TimeStepType currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) { this->m_ContourSeries.push_back(ContourElement::New()); this->SetClosed(other.IsClosed(currentTimestep), currentTimestep); } m_SelectedVertex = nullptr; this->m_lineInterpolation = other.m_lineInterpolation; this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::InitializeEmpty() { // clear data at timesteps this->m_ContourSeries.resize(0); this->m_ContourSeries.push_back(ContourElement::New()); // set number of timesteps to one this->InitializeTimeGeometry(1); m_SelectedVertex = nullptr; this->m_lineInterpolation = ContourModel::LINEAR; } void mitk::ContourModel::UpdateOutputInformation() { if (this->GetSource()) { this->GetSource()->UpdateOutputInformation(); } if (this->m_UpdateBoundingBox) { // update the bounds of the geometry according to the stored vertices ScalarType mitkBounds[6]; // calculate the boundingbox at each timestep typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::PointsContainer PointsContainer; int timesteps = this->GetTimeSteps(); // iterate over the timesteps for (int currenTimeStep = 0; currenTimeStep < timesteps; currenTimeStep++) { if (dynamic_cast(this->GetGeometry(currenTimeStep))) { // do not update bounds for 2D geometries, as they are unfortunately defined with min bounds 0! return; } else { // we have a 3D geometry -> let's update bounds // only update bounds if the contour was modified if (this->GetMTime() > this->GetGeometry(currenTimeStep)->GetBoundingBox()->GetMTime()) { mitkBounds[0] = 0.0; mitkBounds[1] = 0.0; mitkBounds[2] = 0.0; mitkBounds[3] = 0.0; mitkBounds[4] = 0.0; mitkBounds[5] = 0.0; BoundingBoxType::Pointer boundingBox = BoundingBoxType::New(); PointsContainer::Pointer points = PointsContainer::New(); auto it = this->IteratorBegin(currenTimeStep); auto end = this->IteratorEnd(currenTimeStep); // fill the boundingbox with the points while (it != end) { Point3D currentP = (*it)->Coordinates; BoundingBoxType::PointType p; p.CastFrom(currentP); points->InsertElement(points->Size(), p); it++; } // construct the new boundingBox boundingBox->SetPoints(points); boundingBox->ComputeBoundingBox(); BoundingBoxType::BoundsArrayType tmp = boundingBox->GetBounds(); mitkBounds[0] = tmp[0]; mitkBounds[1] = tmp[1]; mitkBounds[2] = tmp[2]; mitkBounds[3] = tmp[3]; mitkBounds[4] = tmp[4]; mitkBounds[5] = tmp[5]; // set boundingBox at current timestep BaseGeometry *geometry3d = this->GetGeometry(currenTimeStep); geometry3d->SetBounds(mitkBounds); } } } this->m_UpdateBoundingBox = false; } GetTimeGeometry()->Update(); } void mitk::ContourModel::ExecuteOperation(Operation * /*operation*/) { // not supported yet } diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.h b/Modules/ContourModel/DataManagement/mitkContourModel.h index f88e13b358..6fb6c7bb7a 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.h +++ b/Modules/ContourModel/DataManagement/mitkContourModel.h @@ -1,467 +1,489 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _MITK_CONTOURMODEL_H_ #define _MITK_CONTOURMODEL_H_ #include "mitkBaseData.h" #include "mitkCommon.h" #include #include namespace mitk { /** \brief ContourModel is a structure of linked vertices defining a contour in 3D space. The vertices are stored in a mitk::ContourElement is stored for each timestep. The contour line segments are implicitly defined by the given linked vertices. By default two control points are are linked by a straight line.It is possible to add vertices at front and end of the contour and to iterate in both directions. Points are specified containing coordinates and additional (data) information, see mitk::ContourElement. For accessing a specific vertex either an index or a position in 3D Space can be used. The vertices are best accessed by using a VertexIterator. Interaction with the contour is thus available without any mitk interactor class using the api of ContourModel. It is possible to shift single vertices also as shifting the whole contour. A contour can be either open like a single curved line segment or closed. A closed contour can for example represent a jordan curve. \section mitkContourModelDisplayOptions Display Options The default mappers for this data structure are mitk::ContourModelGLMapper2D and mitk::ContourModelMapper3D. See these classes for display options which can can be set via properties. */ class MITKCONTOURMODEL_EXPORT ContourModel : public BaseData { public: mitkClassMacro(ContourModel, BaseData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /*+++++++++++++++ typedefs +++++++++++++++++++++++++++++++*/ typedef ContourElement::VertexType VertexType; typedef ContourElement::VertexListType VertexListType; typedef ContourElement::VertexIterator VertexIterator; typedef ContourElement::ConstVertexIterator ConstVertexIterator; typedef std::vector ContourModelSeries; /*+++++++++++++++ END typedefs ++++++++++++++++++++++++++++*/ /** \brief Possible interpolation of the line segments between control points */ enum LineSegmentInterpolation { LINEAR, B_SPLINE }; /*++++++++++++++++ inline methods +++++++++++++++++++++++*/ /** \brief Get the current selected vertex. */ VertexType *GetSelectedVertex() { return this->m_SelectedVertex; } /** \brief Deselect vertex. */ void Deselect() { this->m_SelectedVertex = nullptr; } /** \brief Set selected vertex as control point */ void SetSelectedVertexAsControlPoint(bool isControlPoint = true) { if (this->m_SelectedVertex) { m_SelectedVertex->IsControlPoint = isControlPoint; this->Modified(); } } /** \brief Set the interpolation of the line segments between control points. */ void SetLineSegmentInterpolation(LineSegmentInterpolation interpolation) { this->m_lineInterpolation = interpolation; this->Modified(); } /** \brief Get the interpolation of the line segments between control points. */ LineSegmentInterpolation GetLineSegmentInterpolation() { return this->m_lineInterpolation; } /*++++++++++++++++ END inline methods +++++++++++++++++++++++*/ /** \brief Add a vertex to the contour at given timestep. The vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep. A copy of the passed vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const Point3D& vertex, bool isControlPoint, TimeStepType timestep = 0); /** Clears the contour of destinationTimeStep and copies the contour of the passed source model at the sourceTimeStep. @pre soureModel must point to a valid instance @pre sourceTimePoint must be valid @note Updateing a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep = 0); /** \brief Insert a vertex at given index. */ void InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint = false, TimeStepType timestep = 0); /** \brief Set a coordinates for point at given index. */ bool SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep = 0); /** \brief Set a coordinates and control state for point at given index. */ bool SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep = 0); /** \brief Return if the contour is closed or not. */ bool IsClosed(int timestep = 0) const; /** \brief Concatenate two contours. The starting control point of the other will be added at the end of the contour. \param other \param timestep - the timestep at which the vertex will be add ( default 0) \param check - check for intersections ( default false) */ void Concatenate(ContourModel *other, TimeStepType timestep = 0, bool check = false); /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator Begin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator IteratorBegin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator End(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator IteratorEnd(TimeStepType timestep = 0) const; /** \brief Close the contour. The last control point will be linked with the first point. */ virtual void Close(TimeStepType timestep = 0); /** \brief Set isClosed to false contour. The link between the last control point the first point will be removed. */ virtual void Open(TimeStepType timestep = 0); /** \brief Set closed property to given boolean. false - The link between the last control point the first point will be removed. true - The last control point will be linked with the first point. */ virtual void SetClosed(bool isClosed, TimeStepType timestep = 0); /** \brief Returns the number of vertices at a given timestep. \param timestep - default = 0 */ int GetNumberOfVertices(TimeStepType timestep = 0) const; /** \brief Returns whether the contour model is empty at a given timestep. \param timestep - default = 0 */ virtual bool IsEmpty(TimeStepType timestep) const; /** \brief Returns whether the contour model is empty. */ bool IsEmpty() const override; /** \brief Returns the vertex at the index position within the container. * If the index or timestep is invalid a nullptr will be returned. */ virtual const VertexType *GetVertexAt(int index, TimeStepType timestep = 0) const; + const VertexType *GetVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const; + /** Returns the next control vertex to the approximate nearest vertex of a given position in 3D space * If the timestep is invalid a nullptr will be returned. */ virtual const VertexType *GetNextControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const; /** Returns the previous control vertex to the approximate nearest vertex of a given position in 3D space * If the timestep is invalid a nullptr will be returned. */ virtual const VertexType *GetPreviousControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const; /** \brief Remove a vertex at given timestep within the container. \return index of vertex. -1 if not found. */ int GetIndex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Check if there isn't something at this timestep. */ bool IsEmptyTimeStep(unsigned int t) const override; /** \brief Check if mouse cursor is near the contour. */ - virtual bool IsNearContour(Point3D &point, float eps, TimeStepType timestep); - + bool IsNearContour(Point3D &point, float eps, TimeStepType timestep) const; + + /** Function that searches for the line segment of the contour that is closest to the passed point + and close enough (distance between point and line segment <= eps). If such an line segment exist, + the starting vertex and closing vertex of the found segment are passed back. + @return True indicates that a line segment was found. False indicates that no segment of the contour + is close enough to the passed point. + @remark previousVertex and nextVertex are only valid if return is true.*/ + bool GetLineSegmentForPoint(Point3D &point, + float eps, + TimeStepType timestep, + mitk::ContourElement::VertexType *previousVertex = nullptr, + mitk::ContourElement::VertexType *nextVertex = nullptr); + + /**Overloaded version that returns additional information (start and end vertix of the line + closest to the passed point and the closest point on the contour). + @remark segmentStart, segmentStop and closestContourPoint are only valid if the function returns true. + */ + bool GetLineSegmentForPoint(const mitk::Point3D& point, + float eps, TimeStepType timestep, ContourElement::VertexSizeType& segmentStartIndex, + ContourElement::VertexSizeType& segmentEndIndex, mitk::Point3D& closestContourPoint, + bool findClosest = true) const; /** \brief Mark a vertex at an index in the container as selected. */ bool SelectVertexAt(int index, TimeStepType timestep = 0); /** \brief Mark a vertex at an index in the container as control point. */ bool SetControlVertexAt(int index, TimeStepType timestep = 0); /** \brief Mark a control vertex at a given position in 3D space. \param point - query point in 3D space \param eps - radius for nearest neighbour search (error bound). \param timestep - search at this timestep @return true = vertex found; false = no vertex found */ bool SelectControlVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Mark a vertex at a given position in 3D space. \param point - query point in 3D space \param eps - radius for nearest neighbour search (error bound). \param timestep - search at this timestep @return true = vertex found; false = no vertex found */ bool SelectVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /* \pararm point - query point in 3D space \pararm eps - radius for nearest neighbour search (error bound). \pararm timestep - search at this timestep @return true = vertex found; false = no vertex found */ bool SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Remove a vertex at given index within the container. @return true = the vertex was successfuly removed; false = wrong index. */ bool RemoveVertexAt(int index, TimeStepType timestep = 0); /** \brief Remove a vertex at given timestep within the container. @return true = the vertex was successfuly removed. */ bool RemoveVertex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Remove a vertex at a query position in 3D space. The vertex to be removed will be search by nearest neighbour search. Note that possibly no vertex at this position and eps is stored inside the contour. @return true = the vertex was successfuly removed; false = no vertex found. */ bool RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Shift the currently selected vertex by a translation vector. \param translate - the translation vector. */ void ShiftSelectedVertex(Vector3D &translate); /** \brief Shift the whole contour by a translation vector at given timestep. \param translate - the translation vector. \param timestep - at this timestep the contour will be shifted. */ void ShiftContour(Vector3D &translate, TimeStepType timestep = 0); /** \brief Clear the storage container at given timestep. All control points are removed at timestep. */ virtual void Clear(TimeStepType timestep); /** \brief Initialize all data objects */ void Initialize() override; /** \brief Initialize object with specs of other contour. Note: No data will be copied. */ void Initialize(const ContourModel &other); /** \brief Returns a list pointing to all vertices that are indicated to be control points. */ VertexListType GetControlVertices(TimeStepType timestep); /** \brief Returns the container of the vertices. */ VertexListType GetVertexList(TimeStepType timestep); /*++++++++++++++++++ method inherit from base data +++++++++++++++++++++++++++*/ /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegionToLargestPossibleRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool RequestedRegionIsOutsideOfTheBufferedRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool VerifyRequestedRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegion(const itk::DataObject *data) override; /** \brief Expand the contour model and its TimeGeometry to given number of timesteps. */ void Expand(unsigned int timeSteps) override; /** \brief Update the OutputInformation of a ContourModel object The BoundingBox of the contour will be updated, if necessary. */ void UpdateOutputInformation() override; /** \brief Clear the storage container. The object is set to initial state. All control points are removed and the number of timesteps are set to 1. */ void Clear() override; /** \brief overwrite if the Data can be called by an Interactor (StateMachine). */ void ExecuteOperation(Operation *operation) override; /** \brief Redistributes ontrol vertices with a given period (as number of vertices) \param period - the number of vertices between control points. \param timestep - at this timestep all lines will be rebuilt. */ virtual void RedistributeControlVertices(int period, TimeStepType timestep); protected: mitkCloneMacro(Self); ContourModel(); ContourModel(const ContourModel &other); ~ContourModel() override; // inherit from BaseData. called by Clear() void ClearData() override; // inherit from BaseData. Initial state of a contour with no vertices and a single timestep. void InitializeEmpty() override; // Shift a vertex static void ShiftVertex(VertexType *vertex, Vector3D &vector); // Storage with time resolved support. ContourModelSeries m_ContourSeries; // The currently selected vertex. VertexType *m_SelectedVertex; // The interpolation of the line segment between control points. LineSegmentInterpolation m_lineInterpolation; // only update the bounding geometry if necessary bool m_UpdateBoundingBox; }; itkEventMacroDeclaration(ContourModelEvent, itk::AnyEvent); itkEventMacroDeclaration(ContourModelShiftEvent, ContourModelEvent); itkEventMacroDeclaration(ContourModelSizeChangeEvent, ContourModelEvent); itkEventMacroDeclaration(ContourModelAddEvent, ContourModelSizeChangeEvent); itkEventMacroDeclaration(ContourModelRemoveEvent, ContourModelSizeChangeEvent); itkEventMacroDeclaration(ContourModelExpandTimeBoundsEvent, ContourModelEvent); itkEventMacroDeclaration(ContourModelClosedEvent, ContourModelEvent); } #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp index e608aaec13..1482b9ddcd 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp @@ -1,164 +1,242 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkContourModelInteractor.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include mitk::ContourModelInteractor::ContourModelInteractor() { } void mitk::ContourModelInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); - CONNECT_FUNCTION("movePoints", OnMovePoint); + CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); + CONNECT_FUNCTION("addPoint", OnAddPoint); CONNECT_FUNCTION("finish", OnFinishEditing); } mitk::ContourModelInteractor::~ContourModelInteractor() { } +void mitk::ContourModelInteractor::SetRestrictedArea(mitk::ContourModel* restrictedArea) +{ + m_RestrictedArea = restrictedArea; +} + bool mitk::ContourModelInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); + if (contour == nullptr) + { + MITK_ERROR << "Invalid Contour"; + return false; + } contour->Deselect(); mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timeStep)) + bool isVertexSelected = contour->SelectControlVertexAt(click, ContourModelInteractor::eps, timeStep); + + if (isVertexSelected) + { + auto foundVertex = contour->GetSelectedVertex(); + const auto restrictedVs = m_RestrictedArea->GetVertexList(timeStep); + for (auto restrictedV : restrictedVs) + { + if (restrictedV->Coordinates == foundVertex->Coordinates) + { + isVertexSelected = false; + contour->Deselect(); + break; + } + } + } + + if (isVertexSelected) { - contour->SetSelectedVertexAsControlPoint(); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); - m_lastMousePosition = click; + } + + return isVertexSelected; +} + +void mitk::ContourModelInteractor::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) +{ + const auto* positionEvent = dynamic_cast(interactionEvent); + if (!positionEvent) + return; + + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); - auto *contourGeometry = dynamic_cast(contour->GetGeometry(timeStep)); + auto* contour = dynamic_cast(this->GetDataNode()->GetData()); - if (contourGeometry->IsInside(click)) + Point3D currentPosition = positionEvent->GetPositionInWorld(); + + ContourElement::VertexSizeType segmentStart; + ContourElement::VertexSizeType segmentEnd; + Point3D closestContourPoint; + if (contour->GetLineSegmentForPoint(currentPosition, ContourModelInteractor::eps, timeStep, segmentStart, segmentEnd, closestContourPoint, true)) + { + const auto vertexList = contour->GetVertexList(timeStep); + //check if the segment is NOT within restricted control points. + auto controlStartIt = vertexList.begin(); + auto controlEndIt = vertexList.begin(); + for (auto searchIt = vertexList.begin() + segmentStart; searchIt != vertexList.begin(); searchIt--) { - m_lastMousePosition = click; - return true; + if ((*searchIt)->IsControlPoint) + { + controlStartIt = searchIt; + break; + } + } + for (auto searchIt = vertexList.begin() + segmentEnd; searchIt != vertexList.end(); searchIt++) + { + if ((*searchIt)->IsControlPoint) + { + controlEndIt = searchIt; + break; + } + } + + const auto restrictedVs = m_RestrictedArea->GetVertexList(timeStep); + bool startIsRestricted = false; + bool stopIsRestricted = false; + + for (auto restrictedV : restrictedVs) + { + if (restrictedV->Coordinates == (*controlStartIt)->Coordinates) + { + startIsRestricted = true; + } + if (restrictedV->Coordinates == (*controlEndIt)->Coordinates) + { + stopIsRestricted = true; + } + } + + if (!(startIsRestricted && stopIsRestricted)) + { + //add the point + contour->InsertVertexAtIndex(closestContourPoint, segmentEnd, true, timeStep); + contour->SelectVertexAt(segmentEnd); + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } - else - return false; - } - else - { - return false; } - return true; } void mitk::ContourModelInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *) { auto *contour = dynamic_cast(this->GetDataNode()->GetData()); contour->RemoveVertex(contour->GetSelectedVertex()); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::ContourModelInteractor::IsHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); - mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); + Point3D currentPosition = positionEvent->GetPositionInWorld(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, 1.5, timeStep)) + if (contour->IsNearContour(currentPosition, ContourModelInteractor::eps, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } + //check if it would be valid to add a point. } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } + this->m_lastMousePosition = currentPosition; + return false; } void mitk::ContourModelInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Vector3D translation; mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); translation[0] = currentPosition[0] - this->m_lastMousePosition[0]; translation[1] = currentPosition[1] - this->m_lastMousePosition[1]; translation[2] = currentPosition[2] - this->m_lastMousePosition[2]; contour->ShiftSelectedVertex(translation); this->m_lastMousePosition = positionEvent->GetPositionInWorld(); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::ContourModelInteractor::OnMoveContour(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Vector3D translation; mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); translation[0] = currentPosition[0] - this->m_lastMousePosition[0]; translation[1] = currentPosition[1] - this->m_lastMousePosition[1]; translation[2] = currentPosition[2] - this->m_lastMousePosition[2]; contour->ShiftContour(translation, timeStep); this->m_lastMousePosition = positionEvent->GetPositionInWorld(); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } -void mitk::ContourModelInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::ContourModelInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *) { - auto *contour = dynamic_cast(this->GetDataNode()->GetData()); - contour->Deselect(); - mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelInteractor.h index 0ea308590f..6c2e779f20 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelInteractor.h @@ -1,63 +1,68 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkContourModelInteractor_h_Included #define mitkContourModelInteractor_h_Included #include "mitkCommon.h" #include "mitkDataInteractor.h" #include #include #include namespace mitk { /** \brief \sa Interactor \ingroup Interaction \ingroup ToolManagerEtAl */ class MITKSEGMENTATION_EXPORT ContourModelInteractor : public DataInteractor { public: mitkClassMacro(ContourModelInteractor, DataInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** * Here actions strings from the loaded state machine pattern are mapped to functions of * the DataInteractor. These functions are called when an action from the state machine pattern is executed. */ void ConnectActionsAndFunctions() override; + void SetRestrictedArea(mitk::ContourModel* restrictedArea); + protected: ContourModelInteractor(); ~ContourModelInteractor() override; virtual bool OnCheckPointClick(const InteractionEvent *interactionEvent); virtual bool IsHovering(const InteractionEvent *interactionEvent); + virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); virtual void OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnMoveContour(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent); + const float eps = 3.0; mitk::Point3D m_lastMousePosition; + mitk::ContourModel::Pointer m_RestrictedArea; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp index 27e02d9986..1f25e5d026 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -1,426 +1,333 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkContourModelLiveWireInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include "mitkIOUtil.h" mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor() : ContourModelInteractor() { m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetUseCostFunction(true); m_NextActiveVertexDown.Fill(0); m_NextActiveVertexUp.Fill(0); } mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() { } void mitk::ContourModelLiveWireInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); + CONNECT_FUNCTION("addPoint", OnAddPoint) CONNECT_FUNCTION("finish", OnFinishEditing); } bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { - const auto *positionEvent = dynamic_cast(interactionEvent); - - if (!positionEvent) - return false; - - const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); - - auto *contour = dynamic_cast(this->GetDataNode()->GetData()); - if (contour == nullptr) - { - MITK_ERROR << "Invalid Contour"; - return false; - } - - contour->Deselect(); - - // Check distance to any vertex. - // Transition YES if click close to a vertex - mitk::Point3D click = positionEvent->GetPositionInWorld(); - - bool isVertexSelected = false; - // Check, if clicked position is close to control vertex and if so, select closest control vertex. - isVertexSelected = contour->SelectControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - - // If the position is not close to control vertex. but hovering the contour line, we check, if it is close to non-control vertex. - // The closest vertex will be set as a control vertex. - if (isVertexSelected == false) - isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - - // If the position is not close to control or non-control vertex. but hovering the contour line, we create a vertex at the position. - if (isVertexSelected == false) - { - bool isHover = false; - if (this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()) == false) - { - MITK_WARN << "Unknown property contour.hovering"; - } - if (isHover) - { - contour->AddVertex(click, timeStep); - isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - } - } + auto isVertexSelected = Superclass::OnCheckPointClick(interactionEvent); if (isVertexSelected) { - contour->SetSelectedVertexAsControlPoint(true); + auto* contour = dynamic_cast(this->GetDataNode()->GetData()); + const auto* positionEvent = + dynamic_cast(interactionEvent); + mitk::Point3D click = positionEvent->GetPositionInWorld(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); + auto controlVertices = contour->GetControlVertices(timeStep); - const mitk::ContourModel::VertexType *nextPoint = contour->GetNextControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - const mitk::ContourModel::VertexType *previousPoint = contour->GetPreviousControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + const mitk::ContourModel::VertexType* nextPoint = contour->GetNextControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + const mitk::ContourModel::VertexType* previousPoint = contour->GetPreviousControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); this->SplitContourFromSelectedVertex(contour, nextPoint, previousPoint, timeStep); m_NextActiveVertexUp = nextPoint->Coordinates; m_NextActiveVertexDown = previousPoint->Coordinates; // clear previous void positions this->m_LiveWireFilter->ClearRepulsivePoints(); + // all points in lower and upper part should be marked as repulsive points to not be changed this->SetRepulsivePoints(previousPoint, m_ContourLeft, timeStep); this->SetRepulsivePoints(nextPoint, m_ContourRight, timeStep); // clear container with void points between neighboring control points m_ContourBeingModified.clear(); - - // finally, return true to pass this condition - return true; - } - else - { - // do not pass condition - return false; - } - - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - return true; -} - -void mitk::ContourModelLiveWireInteractor::SetEditingContourModelNode(mitk::DataNode *_arg) -{ - if (this->m_EditingContourNode != _arg) - { - this->m_EditingContourNode = _arg; } + return isVertexSelected; } void mitk::ContourModelLiveWireInteractor::SetWorkingImage(mitk::Image *_arg) { if (this->m_WorkingSlice != _arg) { this->m_WorkingSlice = _arg; this->m_LiveWireFilter->SetInput(this->m_WorkingSlice); } } +void mitk::ContourModelLiveWireInteractor::OnAddPoint(StateMachineAction* sm, InteractionEvent* interactionEvent) +{ + Superclass::OnAddPoint(sm, interactionEvent); +} + void mitk::ContourModelLiveWireInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour!"; return; } if (contour->GetSelectedVertex()) { mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); newContour->Concatenate(m_ContourLeft, timeStep); // recompute contour between neighbored two active control points this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(this->m_NextActiveVertexUp); this->m_LiveWireFilter->Update(); mitk::ContourModel *liveWireContour = this->m_LiveWireFilter->GetOutput(); assert(liveWireContour); if (liveWireContour->IsEmpty(timeStep)) return; liveWireContour->RemoveVertexAt(0, timeStep); liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timeStep) - 1, timeStep); // insert new live wire computed points newContour->Concatenate(liveWireContour, timeStep); // insert right side of original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); // instead of leaving a single point, delete all points if (newContour->GetNumberOfVertices(timeStep) <= 2) { newContour->Clear(timeStep); } this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } void mitk::ContourModelLiveWireInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "invalid contour"; return; } + std::cout << currentPosition << std::endl; + mitk::ContourModel::Pointer editingContour = mitk::ContourModel::New(); editingContour->Expand(contour->GetTimeSteps()); editingContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // recompute left live wire, i.e. the contour between previous active vertex and selected vertex this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(currentPosition); // remove void positions between previous active vertex and next active vertex. if (!m_ContourBeingModified.empty()) { std::vector>::const_iterator iter = m_ContourBeingModified.begin(); for (; iter != m_ContourBeingModified.end(); iter++) { this->m_LiveWireFilter->RemoveRepulsivePoint((*iter)); } } // update to get the left livewire. Remember that the points in the rest of the contour are already // set as void positions in the filter this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer leftLiveWire = this->m_LiveWireFilter->GetOutput(); assert(leftLiveWire); if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(leftLiveWire, timeStep); // the new index of the selected vertex unsigned int selectedVertexIndex = this->m_ContourLeft->GetNumberOfVertices(timeStep) + leftLiveWire->GetNumberOfVertices(timeStep) - 1; // at this point the container has to be empty m_ContourBeingModified.clear(); // add points from left live wire contour auto iter = leftLiveWire->IteratorBegin(timeStep); for (; iter != leftLiveWire->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); // add indices m_ContourBeingModified.push_back(idx); } // recompute right live wire, i.e. the contour between selected vertex and next active vertex this->m_LiveWireFilter->SetStartPoint(currentPosition); this->m_LiveWireFilter->SetEndPoint(m_NextActiveVertexUp); // update filter with all contour points set as void but the right live wire portion to be calculated now this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer rightLiveWire = this->m_LiveWireFilter->GetOutput(); assert(rightLiveWire); - // reject strange paths - if (abs(rightLiveWire->GetNumberOfVertices(timeStep) - leftLiveWire->GetNumberOfVertices(timeStep)) > 50) - { - return; - } - if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timeStep); if (!rightLiveWire->IsEmpty(timeStep)) rightLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(rightLiveWire, timeStep); - m_EditingContourNode->SetData(editingContour); - mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // concatenate left original contour newContour->Concatenate(this->m_ContourLeft, timeStep); newContour->Concatenate(editingContour, timeStep, true); // set last inserted vertex as selected newContour->SelectVertexAt(selectedVertexIndex, timeStep); // set as control point newContour->SetSelectedVertexAsControlPoint(true); // concatenate right original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } -bool mitk::ContourModelLiveWireInteractor::IsHovering(const InteractionEvent *interactionEvent) -{ - const auto *positionEvent = dynamic_cast(interactionEvent); - if (!positionEvent) - return false; - - const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); - - auto *contour = dynamic_cast(this->GetDataNode()->GetData()); - - mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); - - bool isHover = false; - this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, mitk::ContourModelLiveWireInteractor::eps, timeStep)) - { - if (isHover == false) - { - this->GetDataNode()->SetBoolProperty("contour.hovering", true); - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - } - return true; - } - else - { - if (isHover == true) - { - this->GetDataNode()->SetBoolProperty("contour.hovering", false); - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - } - } - return false; -} void mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, const mitk::ContourModel::VertexType *nextPoint, const mitk::ContourModel::VertexType *previousPoint, int timeStep) { m_ContourLeft = mitk::ContourModel::New(); m_ContourRight = mitk::ContourModel::New(); auto it = srcContour->IteratorBegin(); // part between nextPoint and end of Countour bool upperPart = false; // part between start of countour and previousPoint bool lowerPart = true; // edge cases when point right before first control vertex is selected or first control vertex is selected if (nextPoint == (*it) || srcContour->GetSelectedVertex() == (*it)) { upperPart = true; lowerPart = false; m_ContourLeft->AddVertex(previousPoint->Coordinates, previousPoint->IsControlPoint, timeStep); } // if first control vertex is selected, move to next point before adding vertices to m_ContourRight // otherwise, second line appears when moving the vertex if (srcContour->GetSelectedVertex() == (*it)) { while (*it != nextPoint) { it++; } } for (; it != srcContour->IteratorEnd(timeStep); it++) { // everything in lower part should be added to m_CountoutLeft if (lowerPart) { m_ContourLeft->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } // start of "restricted area" where no vertex should be added to m_CountoutLeft or m_CountoutRight if (*it == previousPoint) { lowerPart = false; upperPart = false; } // start of upperPart if (*it == nextPoint) { upperPart = true; } // everything in upper part should be added to m_CountoutRight if (upperPart) { m_ContourRight->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } } } void mitk::ContourModelLiveWireInteractor::SetRepulsivePoints(const mitk::ContourModel::VertexType *pointToExclude, mitk::ContourModel *contour, int timeStep) { auto it = contour->IteratorBegin(); for (; it != contour->IteratorEnd(timeStep); it++) { if (*it != pointToExclude) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*it)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); } } } -void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *) { - const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); - - auto *editingContour = dynamic_cast(this->m_EditingContourNode->GetData()); - - editingContour->Clear(timeStep); - mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h index 6352775048..e9ba614f3b 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h @@ -1,95 +1,91 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkContourModelLiveWireInteractor_h_Included #define mitkContourModelLiveWireInteractor_h_Included #include "mitkCommon.h" #include "mitkContourModelInteractor.h" #include #include namespace mitk { /** \brief \sa Interactor \sa ContourModelInteractor \ingroup Interaction \warning Make sure the working image is properly set, otherwise the algorithm for computing livewire contour segments will not work! */ class MITKSEGMENTATION_EXPORT ContourModelLiveWireInteractor : public ContourModelInteractor { public: mitkClassMacro(ContourModelLiveWireInteractor, ContourModelInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - virtual void SetEditingContourModelNode(mitk::DataNode *_arg); - virtual void SetWorkingImage(mitk::Image *_arg); void ConnectActionsAndFunctions() override; protected: ContourModelLiveWireInteractor(); ~ContourModelLiveWireInteractor() override; /// \brief Select/ add and select vertex to modify contour and prepare for modification of contour. bool OnCheckPointClick(const InteractionEvent *interactionEvent) override; - /// \brief Check if mouse is hovering over contour - bool IsHovering(const InteractionEvent *interactionEvent) override; /// \brief Update contour when point is moved. void OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) override; + /// \brief Add a new control point. + void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) override; /// \brief Delete selected vertex and recompute contour. void OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Finish modification of contour. void OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Split contour into a part before the selected vertex and after the selected vertex void SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, const mitk::ContourModel::VertexType *nextPoint, const mitk::ContourModel::VertexType *previousPoint, int timestep); /// \brief Set repulsive points which should not be changed during editing of the contour. void SetRepulsivePoints(const mitk::ContourModel::VertexType *nextPoint, mitk::ContourModel *contour, int timestep); - const float eps = 3.0; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; mitk::Image::Pointer m_WorkingSlice; mitk::Point3D m_NextActiveVertexDown; mitk::Point3D m_NextActiveVertexUp; mitk::ContourModel::VertexIterator m_NextActiveVertexDownIter; mitk::ContourModel::VertexIterator m_NextActiveVertexUpIter; std::vector> m_ContourBeingModified; - mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModel::Pointer m_ContourLeft; mitk::ContourModel::Pointer m_ContourRight; }; } // namespace mitk #endif // mitkContourModelLiveWireInteractor_h_Included diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp new file mode 100644 index 0000000000..f25e261373 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp @@ -0,0 +1,471 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkEditableContourTool.h" +#include + + +mitk::EditableContourTool::EditableContourTool() : FeedbackContourTool("EditableContourTool"), m_AutoConfirm(true), m_AddMode(true) +{} + +mitk::EditableContourTool::~EditableContourTool() +{ + this->ReleaseHelperObjects(); + this->ReleaseInteractors(); +} + + +void mitk::EditableContourTool::ConnectActionsAndFunctions() +{ + CONNECT_FUNCTION("InitObject", OnInitContour); + CONNECT_FUNCTION("AddPoint", OnAddPoint); + CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); + CONNECT_FUNCTION("Drawing", OnDrawing); + CONNECT_FUNCTION("EndDrawing", OnEndDrawing); + CONNECT_FUNCTION("FinishContour", OnFinish); + CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); +} + +void mitk::EditableContourTool::Activated() +{ + Superclass::Activated(); + this->ResetToStartState(); + this->EnableContourInteraction(true); +} + +void mitk::EditableContourTool::Deactivated() +{ + this->ClearSegmentation(); + Superclass::Deactivated(); +} + +void mitk::EditableContourTool::ConfirmSegmentation(bool resetStatMachine) +{ + auto referenceImage = this->GetReferenceData(); + auto workingImage = this->GetWorkingData(); + + if (nullptr != referenceImage && nullptr != workingImage) + { + std::vector sliceInfos; + + const auto currentTimePoint = + mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + + auto contour = this->GetContour(); + if (nullptr == contour || contour->IsEmpty()) + return; + + auto workingSlice = + this->GetAffectedImageSliceAs2DImage(m_PlaneGeometry, workingImage, workingImageTimeStep)->Clone(); + sliceInfos.emplace_back(workingSlice, m_PlaneGeometry, workingImageTimeStep); + + auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); + int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); + if (!m_AddMode) + { + activePixelValue = 0; + } + + ContourModelUtils::FillContourInSlice(projectedContour, workingSlice, workingImage, activePixelValue); + + this->WriteBackSegmentationResults(sliceInfos); + } + + this->ReleaseHelperObjects(); + this->ReleaseInteractors(); + if (resetStatMachine) this->ResetToStartState(); +} + +void mitk::EditableContourTool::ClearSegmentation() +{ + this->ReleaseHelperObjects(); + this->ReleaseInteractors(); + this->ResetToStartState(); +} + +bool mitk::EditableContourTool::IsEditingContour() const +{ + return (nullptr != GetContour()) && !this->IsDrawingContour(); +}; + + +bool mitk::EditableContourTool::IsDrawingContour() const +{ + return m_PreviewContour.IsNotNull(); +}; + +bool mitk::EditableContourTool::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, + mitk::BaseData *data) +{ + bool isPositionEventInsideImageRegion = + nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); + + if (!isPositionEventInsideImageRegion) + MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; + + return isPositionEventInsideImageRegion; +} + +mitk::Point3D mitk::EditableContourTool::PrepareInitContour(const Point3D& clickedPoint) +{ //default implementation does nothing + return clickedPoint; +} + +void mitk::EditableContourTool::OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + return; + + auto workingDataNode = this->GetWorkingDataNode(); + + if (nullptr != this->GetContour()) + { + this->ConfirmSegmentation(false); + } + + if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) + { + this->ResetToStartState(); + return; + } + + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + + auto contour = this->CreateNewContour(); + m_ContourNode = mitk::DataNode::New(); + m_ContourNode->SetData(contour); + m_ContourNode->SetName("working contour node"); + m_ContourNode->SetProperty("layer", IntProperty::New(100)); + m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); + m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); + m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); + m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); + + m_PreviewContour = this->CreateNewContour(); + m_PreviewContourNode = mitk::DataNode::New(); + m_PreviewContourNode->SetData(m_PreviewContour); + m_PreviewContourNode->SetName("active preview node"); + m_PreviewContourNode->SetProperty("layer", IntProperty::New(101)); + m_PreviewContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); + m_PreviewContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + m_PreviewContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); + m_PreviewContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); + + + m_ClosureContour = this->CreateNewContour(); + m_ClosureContourNode = mitk::DataNode::New(); + m_ClosureContourNode->SetData(m_ClosureContour); + m_ClosureContourNode->SetName("active closure node"); + m_ClosureContourNode->SetProperty("layer", IntProperty::New(101)); + m_ClosureContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); + m_ClosureContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + m_ClosureContourNode->AddProperty("contour.color", ColorProperty::New(0.0f, 1.0f, 0.1f), nullptr, true); + m_ClosureContourNode->AddProperty("contour.width", mitk::FloatProperty::New(2.0f), nullptr, true); + + m_CurrentRestrictedArea = this->CreateNewContour(); + + auto dataStorage = this->GetToolManager()->GetDataStorage(); + dataStorage->Add(m_ContourNode, workingDataNode); + dataStorage->Add(m_PreviewContourNode, workingDataNode); + dataStorage->Add(m_ClosureContourNode, workingDataNode); + + m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); + + auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); + m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); + m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); + m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); + + // Remember PlaneGeometry to determine if events were triggered in the same plane + m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); + + // Map click to pixel coordinates + auto click = positionEvent->GetPositionInWorld(); + + click = this->PrepareInitContour(click); + + this->InitializePreviewContour(click); + // Set initial start point + contour->AddVertex(click, true); + m_PreviewContour->AddVertex(click, false); + m_ClosureContour->AddVertex(click); + + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::EditableContourTool::FinalizePreviewContour(const Point3D& /*clickedPoint*/) +{ // Remove duplicate first vertex, it's already contained in m_ContourNode + m_PreviewContour->RemoveVertexAt(0); + + m_PreviewContour->SetControlVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); +} + +void mitk::EditableContourTool::InitializePreviewContour(const Point3D& clickedPoint) +{ //default implementation only clears the preview and sets the start point + m_PreviewContour = this->CreateNewContour(); + m_PreviewContour->AddVertex(clickedPoint); + m_PreviewContourNode->SetData(m_PreviewContour); +} + +void mitk::EditableContourTool::UpdatePreviewContour(const Point3D& clickedPoint) +{ //default implementation draws just a simple line to position + if (m_PreviewContour->GetNumberOfVertices() > 2) + { + auto contour = this->GetContour(); + this->InitializePreviewContour(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); + } + + if (m_PreviewContour->GetNumberOfVertices() == 2) + { + m_PreviewContour->RemoveVertexAt(m_PreviewContour->GetNumberOfVertices()-1); + } + + m_PreviewContour->AddVertex(clickedPoint); +} + +void mitk::EditableContourTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + + if (nullptr == positionEvent) + return; + + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + + this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); + + // Merge contours + auto contour = this->GetContour(); + contour->Concatenate(m_PreviewContour); + + this->InitializePreviewContour(positionEvent->GetPositionInWorld()); + + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::EditableContourTool::OnDrawing(StateMachineAction*, InteractionEvent* interactionEvent) +{ + auto* positionEvent = dynamic_cast(interactionEvent); + if (!positionEvent) + return; + + m_PreviewContourNode->SetVisibility(false); + + auto contour = this->GetContour(); + contour->AddVertex(positionEvent->GetPositionInWorld(), false); + UpdateClosureContour(positionEvent->GetPositionInWorld()); + + assert(positionEvent->GetSender()->GetRenderWindow()); + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::EditableContourTool::OnEndDrawing(StateMachineAction*, InteractionEvent* interactionEvent) +{ + auto* positionEvent = dynamic_cast(interactionEvent); + if (!positionEvent) + return; + + auto contour = this->GetContour(); + auto controlVs = contour->GetControlVertices(0); + if (!controlVs.empty()) + { //add the last control point (after that the draw part start) + m_CurrentRestrictedArea->AddVertex(controlVs.back()->Coordinates); + } + m_PreviewContourNode->SetVisibility(true); + contour->SetControlVertexAt(contour->GetNumberOfVertices() - 1); + + //add the just created/set last control point (with it the draw part ends) + m_CurrentRestrictedArea->AddVertex(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); + + this->InitializePreviewContour(positionEvent->GetPositionInWorld()); + + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::EditableContourTool::OnMouseMoved(StateMachineAction*, InteractionEvent* interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + + if (nullptr == positionEvent) + return; + + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + + this->UpdatePreviewContour(positionEvent->GetPositionInWorld()); + this->UpdateClosureContour(positionEvent->GetPositionInWorld()); + + RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::EditableContourTool::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + + if (nullptr == positionEvent) + return; + + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + + this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); + + this->FinishTool(); + + // Merge contours + auto contour = this->GetContour(); + contour->Concatenate(m_PreviewContour); + + auto numberOfTimesteps = static_cast(contour->GetTimeSteps()); + + for (int i = 0; i <= numberOfTimesteps; ++i) + contour->Close(i); + + this->ReleaseHelperObjects(false); + + if (m_AutoConfirm) + { + this->ConfirmSegmentation(); + } +} + +void mitk::EditableContourTool::ReleaseHelperObjects(bool includeWorkingContour) +{ + this->RemoveHelperObjectsFromDataStorage(includeWorkingContour); + + if (includeWorkingContour) + { + m_ContourNode = nullptr; + m_CurrentRestrictedArea = nullptr; + } + + m_PreviewContourNode = nullptr; + m_PreviewContour = nullptr; + + m_ClosureContourNode = nullptr; + m_ClosureContour = nullptr; +} + +void mitk::EditableContourTool::RemoveHelperObjectsFromDataStorage(bool includeWorkingContour) +{ + auto dataStorage = this->GetToolManager()->GetDataStorage(); + + if (nullptr == dataStorage) + return; + + if (includeWorkingContour) + { + if (m_ContourNode.IsNotNull()) + dataStorage->Remove(m_ContourNode); + } + + if (m_PreviewContourNode.IsNotNull()) + dataStorage->Remove(m_PreviewContourNode); + + if (m_ClosureContourNode.IsNotNull()) + dataStorage->Remove(m_ClosureContourNode); + + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +} + +mitk::ContourModel::Pointer mitk::EditableContourTool::CreateNewContour() const +{ + auto workingData = this->GetWorkingData(); + if (nullptr == workingData) + { + this->InteractiveSegmentationBugMessage( + "Cannot create new contour. No valid working data is set. Application is in invalid state."); + mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; + } + + auto contour = ContourModel::New(); + + // generate a time geometry that is always visible as the working contour should always be. + auto contourTimeGeometry = ProportionalTimeGeometry::New(); + contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); + contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); + contour->SetTimeGeometry(contourTimeGeometry); + + return contour; +} + +void mitk::EditableContourTool::UpdateClosureContour(mitk::Point3D endpoint) +{ + if (m_ClosureContour->GetNumberOfVertices() > 2) + { + m_ClosureContour = this->CreateNewContour(); + m_ClosureContourNode->SetData(m_ClosureContour); + } + + if (m_ClosureContour->GetNumberOfVertices() == 0) + { + auto contour = this->GetContour(); + m_ClosureContour->AddVertex(contour->GetVertexAt(0)->Coordinates); + m_ClosureContour->Update(); + } + + if (m_ClosureContour->GetNumberOfVertices() == 2) + { + m_ClosureContour->RemoveVertexAt(0); + } + + m_ClosureContour->AddVertexAtFront(endpoint); +} + +void mitk::EditableContourTool::EnableContourInteraction(bool on) +{ + if (m_ContourInteractor.IsNotNull()) + { + m_ContourInteractor->EnableInteraction(on); + } +} + +void mitk::EditableContourTool::ReleaseInteractors() +{ + this->EnableContourInteraction(false); + m_ContourInteractor = nullptr; +} + +mitk::ContourModel* mitk::EditableContourTool::GetContour() +{ + if (this->m_ContourNode.IsNotNull()) + { + return dynamic_cast(this->m_ContourNode->GetData()); + } + return nullptr; +}; + +const mitk::ContourModel* mitk::EditableContourTool::GetContour() const +{ + if (this->m_ContourNode.IsNotNull()) + { + return dynamic_cast(this->m_ContourNode->GetData()); + } + return nullptr; +}; \ No newline at end of file diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.h b/Modules/Segmentation/Interactions/mitkEditableContourTool.h new file mode 100644 index 0000000000..8be7c24ac9 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.h @@ -0,0 +1,127 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkEditbaleContourTool_h_Included +#define mitkEditbaleContourTool_h_Included + +#include + +namespace mitk +{ + /** + * Base class for lasso like tools that allow to draw closed contours with multiple ancor points. + * The segments between the ancor points may be freehand contour segments or computed segments + * (e.g. straight lines or live wire). Derive from the class to implement the computation of the non-freehand + * segments. + * @sa LassoTool LivewWireTool2D + */ + class MITKSEGMENTATION_EXPORT EditableContourTool : public FeedbackContourTool + { + public: + mitkClassMacro(EditableContourTool, FeedbackContourTool); + + /// \brief Convert current contour to binary segmentations. + virtual void ConfirmSegmentation(bool resetStatMachine = true); + + /// \brief Delete all current contours. + virtual void ClearSegmentation(); + + itkBooleanMacro(AutoConfirm); + itkSetMacro(AutoConfirm, bool); + itkGetMacro(AutoConfirm, bool); + + itkBooleanMacro(AddMode); + itkSetMacro(AddMode, bool); + itkGetMacro(AddMode, bool); + + /* Indicated if a contour is drawn, but not confirmed yet. This the tool is in interactor mode + to allow users to edit the contour. This state can be reached if AutoConfirm is false, after the finalizing double + click before the contour is confirmed.*/ + bool IsEditingContour() const; + /* Indicate if a contour is currently drawn by the user (state between the initializing double click and + the finalizing double click).*/ + bool IsDrawingContour() const; + + protected: + EditableContourTool(); + ~EditableContourTool() override; + + void ConnectActionsAndFunctions() override; + + void Activated() override; + void Deactivated() override; + + virtual Point3D PrepareInitContour(const Point3D& clickedPoint); + virtual void FinalizePreviewContour(const Point3D& clickedPoint); + virtual void InitializePreviewContour(const Point3D& clickedPoint); + virtual void UpdatePreviewContour(const Point3D& clickedPoint); + + /// \brief Initialize tool. + virtual void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Add a control point and finish current segment. + virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Draw a contour according to the mouse movement when mouse button is pressed and mouse is moved. + virtual void OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent); + + virtual void OnEndDrawing(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Computation of the preview contour. + virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Finish EditableContour tool. + virtual void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Finish contour interaction. + virtual void FinishTool() = 0; + + void EnableContourInteraction(bool on); + + void ReleaseInteractors(); + + virtual void ReleaseHelperObjects(bool includeWorkingContour = true); + + virtual void RemoveHelperObjectsFromDataStorage(bool includeWorkingContour = true); + + ContourModel::Pointer CreateNewContour() const; + + virtual void UpdateClosureContour(mitk::Point3D endpoint); + + bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); + + mitk::ContourModel* GetContour(); + const mitk::ContourModel* GetContour() const; + mitk::DataNode::Pointer m_ContourNode; + + mitk::ContourModel::Pointer m_PreviewContour; + mitk::DataNode::Pointer m_PreviewContourNode; + + mitk::ContourModel::Pointer m_ClosureContour; + mitk::DataNode::Pointer m_ClosureContourNode; + + mitk::ContourModel::Pointer m_CurrentRestrictedArea; + + /** Slice of the reference data the tool is currently actively working on to + define contours.*/ + mitk::Image::Pointer m_ReferenceDataSlice; + + PlaneGeometry::ConstPointer m_PlaneGeometry; + + mitk::DataInteractor::Pointer m_ContourInteractor; + + bool m_AutoConfirm; + bool m_AddMode; + }; +} + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLassoTool.cpp b/Modules/Segmentation/Interactions/mitkLassoTool.cpp new file mode 100644 index 0000000000..84adb3ee05 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkLassoTool.cpp @@ -0,0 +1,70 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +//#include +#include +#include + +#include +#include + +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LassoTool, "Lasso tool"); +} + +mitk::LassoTool::LassoTool() : EditableContourTool() +{ +} + +mitk::LassoTool::~LassoTool() +{} + +void mitk::LassoTool::ConnectActionsAndFunctions() +{ + mitk::EditableContourTool::ConnectActionsAndFunctions(); + CONNECT_FUNCTION("MovePoint", OnMouseMoved); +} + +const char **mitk::LassoTool::GetXPM() const +{ + return nullptr; +} + +us::ModuleResource mitk::LassoTool::GetIconResource() const +{ + return us::GetModuleContext()->GetModule()->GetResource("NewAdd_48x48.png"); +} + +us::ModuleResource mitk::LassoTool::GetCursorIconResource() const +{ + return us::GetModuleContext()->GetModule()->GetResource("NewAdd_Cursor_32x32.png"); +} + +const char *mitk::LassoTool::GetName() const +{ + return "Lasso"; +} + +void mitk::LassoTool::FinishTool() +{ + auto contourInteractor = mitk::ContourModelInteractor::New(); + contourInteractor->SetDataNode(m_ContourNode); + contourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); + contourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); + contourInteractor->SetRestrictedArea(this->m_CurrentRestrictedArea); + + this->m_ContourInteractor = contourInteractor; + m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); +} diff --git a/Modules/Segmentation/Interactions/mitkLassoTool.h b/Modules/Segmentation/Interactions/mitkLassoTool.h new file mode 100644 index 0000000000..d86b1d3e92 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkLassoTool.h @@ -0,0 +1,63 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkLassoTool_h +#define mitkLassoTool_h + +#include "mitkEditableContourTool.h" +#include "mitkContourTool.h" +#include "mitkContourModelInteractor.h" + +namespace mitk +{ + /** + \brief A 2D segmentation tool to draw polygon structures. + + The contour between the last point and the current mouse position is + computed by forming a straight line. + + The tool always assumes that unconfirmed contours are always defined for the + current time point. So the time step in which the contours will be stored as + segmentations will be determined when the contours got confirmed. Then they + will be transfered to the slices of the currently selected time step. + + \sa SegTool2D + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + */ + class MITKSEGMENTATION_EXPORT LassoTool : public EditableContourTool + { + public: + mitkClassMacro(LassoTool, SegTool2D); + itkFactorylessNewMacro(Self); + + us::ModuleResource GetCursorIconResource() const override; + us::ModuleResource GetIconResource() const override; + const char *GetName() const override; + const char **GetXPM() const override; + + protected: + LassoTool(); + ~LassoTool() override; + + void ConnectActionsAndFunctions() override; + void FinishTool() override; + + private: + mitk::ContourModelInteractor::Pointer m_ContourInteractor; + }; +} + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index e39f44b64e..861f2bd557 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,740 +1,261 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() - : SegTool2D("LiveWireTool"), m_SnapClosureContour(false), m_CreateAndUseDynamicCosts(false) + : EditableContourTool(), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { - this->ClearSegmentation(); -} - -void mitk::LiveWireTool2D::RemoveHelperObjects() -{ - auto dataStorage = this->GetToolManager()->GetDataStorage(); - - if (nullptr == dataStorage) - return; - - for (const auto &editingContour : m_EditingContours) - dataStorage->Remove(editingContour.first); - - for (const auto &workingContour : m_WorkingContours) - dataStorage->Remove(workingContour.first); - - if (m_EditingContourNode.IsNotNull()) - dataStorage->Remove(m_EditingContourNode); - - if (m_LiveWireContourNode.IsNotNull()) - dataStorage->Remove(m_LiveWireContourNode); - - if (m_ClosureContourNode.IsNotNull()) - dataStorage->Remove(m_ClosureContourNode); - - if (m_ContourNode.IsNotNull()) - dataStorage->Remove(m_ContourNode); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::LiveWireTool2D::ReleaseHelperObjects() -{ - this->RemoveHelperObjects(); - - m_EditingContours.clear(); - m_WorkingContours.clear(); - - m_EditingContourNode = nullptr; - m_EditingContour = nullptr; - - m_LiveWireContourNode = nullptr; - m_LiveWireContour = nullptr; - - m_ClosureContourNode = nullptr; - m_ClosureContour = nullptr; - - m_ContourNode = nullptr; - m_Contour = nullptr; -} - -void mitk::LiveWireTool2D::ReleaseInteractors() -{ - this->EnableContourLiveWireInteraction(false); - m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { - CONNECT_FUNCTION("InitObject", OnInitLiveWire); - CONNECT_FUNCTION("AddPoint", OnAddPoint); - CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); + mitk::EditableContourTool::ConnectActionsAndFunctions(); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); - CONNECT_FUNCTION("FinishContour", OnFinish); - CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); - CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } -void mitk::LiveWireTool2D::Activated() -{ - Superclass::Activated(); - this->ResetToStartState(); - this->EnableContourLiveWireInteraction(true); -} - -void mitk::LiveWireTool2D::Deactivated() -{ - this->ConfirmSegmentation(); - Superclass::Deactivated(); -} - void mitk::LiveWireTool2D::UpdateLiveWireContour() { - if (m_Contour.IsNotNull()) + auto contour = this->GetContour(); + if (nullptr != contour) { - auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); - m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); - m_LiveWireContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms - // to 1 ms and the filter also resets its outputs. - m_LiveWireContourNode->SetData(this->m_LiveWireContour); - - if (m_SnapClosureContour) - { - m_ClosureContour = this->m_LiveWireFilterClosure->GetOutput(); - } - - m_ClosureContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms - // to 1 ms and the filter also resets its outputs. - m_ClosureContourNode->SetData(this->m_ClosureContour); + auto timeGeometry = contour->GetTimeGeometry()->Clone(); + m_PreviewContour = this->m_LiveWireFilter->GetOutput(); + + // needed because the results of the filter are always from 0 ms + // to 1 ms and the filter also resets its outputs. + // generate a time geometry that is always visible as the working contour should always be. + auto contourTimeGeometry = ProportionalTimeGeometry::New(); + contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); + contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); + m_PreviewContour->SetTimeGeometry(contourTimeGeometry); + m_PreviewContourNode->SetData(this->m_PreviewContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); - if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_LiveWireContourNode.IsNull()) + if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_PreviewContourNode.IsNull()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); - m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); - - m_LiveWireFilterClosure->Update(); - this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; -void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) -{ - for (const auto &interactor : m_LiveWireInteractors) - interactor->EnableInteraction(on); -} - -void mitk::LiveWireTool2D::ConfirmSegmentation() -{ - auto referenceImage = this->GetReferenceData(); - auto workingImage = this->GetWorkingData(); - - if (nullptr != referenceImage && nullptr != workingImage) - { - std::vector sliceInfos; - sliceInfos.reserve(m_WorkingContours.size()); - - const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); - - for (const auto &workingContour : m_WorkingContours) - { - auto contour = dynamic_cast(workingContour.first->GetData()); - - if (nullptr == contour || contour->IsEmpty()) - continue; - - auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; - - auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); - if (finding == sliceInfos.end()) - { - auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); - sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); - finding = std::prev(sliceInfos.end()); - } - - //cast const away is OK in this case, because these are all slices created and manipulated - //localy in this function call. And we want to keep the high constness of SliceInformation for - //public interfaces. - auto workingSlice = const_cast(finding->slice.GetPointer()); - - auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); - int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); - - ContourModelUtils::FillContourInSlice( - projectedContour, workingSlice, workingImage, activePixelValue); - } - - this->WriteBackSegmentationResults(sliceInfos); - } - - this->ClearSegmentation(); -} - -void mitk::LiveWireTool2D::ClearSegmentation() -{ - this->ReleaseHelperObjects(); - this->ReleaseInteractors(); - this->ResetToStartState(); -} - -void mitk::LiveWireTool2D::SetSnapClosureContour(bool snap) -{ - m_SnapClosureContour = snap; - if (!m_LiveWireContour || !m_Contour) - { - return; - } - - if (m_LiveWireContour->GetNumberOfVertices() > 0) - { - UpdateClosureContour(m_LiveWireContour->GetVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1)->Coordinates); - } - else if (m_Contour->GetNumberOfVertices() > 0) - { - UpdateClosureContour(m_Contour->GetVertexAt(m_Contour->GetNumberOfVertices() - 1)->Coordinates); - } - - this->UpdateLiveWireContour(); -} - -bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, - mitk::BaseData *data) -{ - bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); - - if (!isPositionEventInsideImageRegion) - MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; - - return isPositionEventInsideImageRegion; -} - -mitk::ContourModel::Pointer mitk::LiveWireTool2D::CreateNewContour() const -{ - auto workingData = this->GetWorkingData(); - if (nullptr == workingData) - { - this->InteractiveSegmentationBugMessage("Cannot create new contour. No valid working data is set. Application is in invalid state."); - mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; - } - - auto contour = ContourModel::New(); - - //generate a time geometry that is always visible as the working contour should always be. - auto contourTimeGeometry = ProportionalTimeGeometry::New(); - contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); - contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); - contour->SetTimeGeometry(contourTimeGeometry); - - return contour; -} - -void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) +mitk::Point3D mitk::LiveWireTool2D::PrepareInitContour(const mitk::Point3D& clickedPoint) { - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return; - - auto workingDataNode = this->GetWorkingDataNode(); - - if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) - { - this->ResetToStartState(); - return; - } - - m_LastEventSender = positionEvent->GetSender(); - m_LastEventSlice = m_LastEventSender->GetSlice(); - - m_Contour = this->CreateNewContour(); - m_ContourNode = mitk::DataNode::New(); - m_ContourNode->SetData(m_Contour); - m_ContourNode->SetName("working contour node"); - m_ContourNode->SetProperty("layer", IntProperty::New(100)); - m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); - m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); - m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); - m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); - - m_LiveWireContour = this->CreateNewContour(); - m_LiveWireContourNode = mitk::DataNode::New(); - m_LiveWireContourNode->SetData(m_LiveWireContour); - m_LiveWireContourNode->SetName("active livewire node"); - m_LiveWireContourNode->SetProperty("layer", IntProperty::New(101)); - m_LiveWireContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); - m_LiveWireContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_LiveWireContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); - m_LiveWireContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); - - m_ClosureContour = this->CreateNewContour(); - m_ClosureContourNode = mitk::DataNode::New(); - m_ClosureContourNode->SetData(m_ClosureContour); - m_ClosureContourNode->SetName("active closure node"); - m_ClosureContourNode->SetProperty("layer", IntProperty::New(101)); - m_ClosureContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); - m_ClosureContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_ClosureContourNode->AddProperty("contour.color", ColorProperty::New(0.0f, 1.0f, 0.1f), nullptr, true); - m_ClosureContourNode->AddProperty("contour.width", mitk::FloatProperty::New(2.0f), nullptr, true); - - m_EditingContour = this->CreateNewContour(); - m_EditingContourNode = mitk::DataNode::New(); - m_EditingContourNode->SetData(m_EditingContour); - m_EditingContourNode->SetName("editing node"); - m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); - m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); - m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); - m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); - m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); - - auto dataStorage = this->GetToolManager()->GetDataStorage(); - dataStorage->Add(m_ContourNode, workingDataNode); - dataStorage->Add(m_LiveWireContourNode, workingDataNode); - dataStorage->Add(m_ClosureContourNode, workingDataNode); - dataStorage->Add(m_EditingContourNode, workingDataNode); - // Set current slice as input for ImageToLiveWireContourFilter - m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); - - auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); - m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); - m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); - m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); - m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetUseCostFunction(true); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); - m_LiveWireFilterClosure = ImageLiveWireContourModelFilter::New(); - m_LiveWireFilterClosure->SetUseCostFunction(true); - m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); - - // Map click to pixel coordinates - auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; - m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); + m_ReferenceDataSlice->GetGeometry()->WorldToIndex(clickedPoint, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); - click[0] = indexWithHighestGradient[0]; - click[1] = indexWithHighestGradient[1]; - click[2] = indexWithHighestGradient[2]; - m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); - - // Set initial start point - m_Contour->AddVertex(click, true); - m_LiveWireFilter->SetStartPoint(click); - //m_LiveWireFilterClosure->SetStartPoint(click); - m_LiveWireFilterClosure->SetEndPoint(click); - if (!m_SnapClosureContour) - { - m_ClosureContour->AddVertex(click); - } - - // Remember PlaneGeometry to determine if events were triggered in the same plane - m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); + Point3D adaptedClick; + m_ReferenceDataSlice->GetGeometry()->IndexToWorld(indexWithHighestGradient, adaptedClick); m_CreateAndUseDynamicCosts = true; - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); + return adaptedClick; } -void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::LiveWireTool2D::FinalizePreviewContour(const Point3D& clickedPoint) { - // Complete LiveWire interaction for the last segment. Add current LiveWire contour to - // the finished contour and reset to start a new segment and computation. - - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return; - - if (m_PlaneGeometry.IsNotNull()) - { - // Check if the point is in the correct slice - if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) - return; - } - // Add repulsive points to avoid getting the same path again - std::for_each(m_LiveWireContour->IteratorBegin(), m_LiveWireContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { + std::for_each(m_PreviewContour->IteratorBegin(), m_PreviewContour->IteratorEnd(), [this](ContourElement::VertexType* vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); - this->m_LiveWireFilterClosure->AddRepulsivePoint(idx); }); - // Remove duplicate first vertex, it's already contained in m_Contour - m_LiveWireContour->RemoveVertexAt(0); - - // Set last point as control point - m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); - - // Merge contours - m_Contour->Concatenate(m_LiveWireContour); + EditableContourTool::FinalizePreviewContour(clickedPoint); +} - // Clear the LiveWire contour and reset the corresponding DataNode - m_LiveWireContour->Clear(); +void mitk::LiveWireTool2D::InitializePreviewContour(const Point3D& clickedPoint) +{ + m_PreviewContour->Clear(); // Set new start point - m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); - m_LiveWireFilterClosure->SetStartPoint(positionEvent->GetPositionInWorld()); + m_LiveWireFilter->SetStartPoint(clickedPoint); if (m_CreateAndUseDynamicCosts) { + auto contour = this->GetContour(); // Use dynamic cost map for next update - m_LiveWireFilter->CreateDynamicCostMap(m_Contour); + m_LiveWireFilter->CreateDynamicCostMap(contour); m_LiveWireFilter->SetUseDynamicCostMap(true); - - m_LiveWireFilterClosure->CreateDynamicCostMap(m_Contour); - m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } - - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } -void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // Compute LiveWire segment from last control point to current mouse position - - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return; - - if (m_PlaneGeometry.IsNotNull()) - { - // Check if the point is in the correct slice - if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) - return; - } - - m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); +void mitk::LiveWireTool2D::UpdatePreviewContour(const Point3D& clickedPoint) +{ // Compute LiveWire segment from last control point to current mouse position + m_LiveWireFilter->SetEndPoint(clickedPoint); m_LiveWireFilter->Update(); - UpdateClosureContour(positionEvent->GetPositionInWorld()); - this->UpdateLiveWireContour(); - - RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); -} - -void mitk::LiveWireTool2D::UpdateClosureContour(mitk::Point3D endpoint) -{ - if (m_SnapClosureContour) - { - m_LiveWireFilterClosure->SetStartPoint(endpoint); - m_LiveWireFilterClosure->Update(); - } - else - { - if (m_ClosureContour->GetNumberOfVertices() > 2) - { - m_ClosureContour = this->CreateNewContour(); - } - - if (m_ClosureContour->GetNumberOfVertices() == 0) - { - m_ClosureContour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates); - m_ClosureContour->Update(); - } - - if (m_ClosureContour->GetNumberOfVertices() == 2) - { - m_ClosureContour->RemoveVertexAt(0); - } - - m_ClosureContour->AddVertexAtFront(endpoint); - } } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); - m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); - m_LiveWireFilterClosure->SetUseDynamicCostMap(true); -} - -bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) -{ - // Check double click on first control point to finish the LiveWire tool - - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return false; - - mitk::Point3D click = positionEvent->GetPositionInWorld(); - mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; - - return first.EuclideanDistanceTo(click) < 4.5; -} - -void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // Finish LiveWire tool interaction - - m_Contour->Concatenate(m_ClosureContour); - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return; - - if (m_PlaneGeometry.IsNotNull()) - { - // Check if the point is in the correct slice - if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) - return; - } - - //m_Contour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates, false); - // Remove last control point added by double click, if double click was performed on first point - //if (OnCheckPoint(interactionEvent)) - // m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); - - // remove green connection between mouse position and start point - m_ClosureContour->Clear(); - - // Save contour and corresponding plane geometry to list - this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); - this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); - - m_LiveWireFilter->SetUseDynamicCostMap(false); - m_LiveWireFilterClosure->SetUseDynamicCostMap(false); - - this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { - auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); - - for (int i = 0; i <= numberOfTimesteps; ++i) - m_Contour->Close(i); - - this->GetToolManager()->GetDataStorage()->Remove(m_LiveWireContourNode); - - m_LiveWireContourNode = nullptr; - m_LiveWireContour = nullptr; + m_LiveWireFilter->SetUseDynamicCostMap(false); m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); - m_ContourInteractor->SetEditingContourModelNode(this->m_EditingContourNode); + m_ContourInteractor->SetRestrictedArea(this->m_CurrentRestrictedArea); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); - - this->m_LiveWireInteractors.push_back(m_ContourInteractor); -} - -void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // If last point of current contour will be removed go to start state and remove nodes - if (m_Contour->GetNumberOfVertices() <= 1) - { - auto dataStorage = this->GetToolManager()->GetDataStorage(); - - dataStorage->Remove(m_LiveWireContourNode); - dataStorage->Remove(m_ContourNode); - dataStorage->Remove(m_EditingContourNode); - - m_LiveWireContour = this->CreateNewContour(); - m_LiveWireContourNode->SetData(m_LiveWireContour); - - m_Contour = this->CreateNewContour(); - m_ContourNode->SetData(m_Contour); - - this->ResetToStartState(); - } - else // Remove last segment from contour and reset LiveWire contour - { - m_LiveWireContour = this->CreateNewContour(); - m_LiveWireContourNode->SetData(m_LiveWireContour); - - auto newContour = this->CreateNewContour(); - - auto begin = m_Contour->IteratorBegin(); - - // Iterate from last point to next active point - auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); - - // Go at least one down - if (newLast != begin) - --newLast; - - // Search next active control point - while (newLast != begin && !((*newLast)->IsControlPoint)) - --newLast; - - // Set position of start point for LiveWire filter to coordinates of the new last point - m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); - //m_LiveWireFilterClosure->SetStartPoint((*newLast)->Coordinates); - - auto it = m_Contour->IteratorBegin(); - - // Fll new Contour - while (it <= newLast) - { - newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); - ++it; - } - - newContour->SetClosed(m_Contour->IsClosed()); - - m_ContourNode->SetData(newContour); - m_Contour = newContour; - - mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); - } } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h index 755e9eedc2..a598e8c3bb 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,148 +1,90 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLiveWireTool2D_h #define mitkLiveWireTool2D_h -#include +#include #include namespace mitk { /** \brief A 2D segmentation tool based on a LiveWire approach. The contour between the last point and the current mouse position is computed by searching the shortest path according to specific features of the image. The contour thus tends to snap to the boundary of objects. The tool always assumes that unconfirmed contours are always defined for the current time point. So the time step in which the contours will be stored as segmentations will be determined when the contours got confirmed. Then they will be transfered to the slices of the currently selected time step. Changing the time point/time step while tool is active will updated the working slice the live wire filter. So the behavior of the active live wire contour is always WYSIWYG (What you see is what you get). \sa SegTool2D \sa ImageLiveWireContourModelFilter \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT LiveWireTool2D : public SegTool2D + class MITKSEGMENTATION_EXPORT LiveWireTool2D : public EditableContourTool { public: - mitkClassMacro(LiveWireTool2D, SegTool2D); + mitkClassMacro(LiveWireTool2D, EditableContourTool); itkFactorylessNewMacro(Self); us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; const char **GetXPM() const override; - /// \brief Convert all current contours to binary segmentations. - void ConfirmSegmentation(); - - /// \brief Delete all current contours. - void ClearSegmentation(); - - void SetSnapClosureContour(bool snap); - protected: LiveWireTool2D(); ~LiveWireTool2D() override; void ConnectActionsAndFunctions() override; - void Activated() override; - void Deactivated() override; + void UpdateLiveWireContour(); void OnTimePointChanged() override; - private: - /// \brief Initialize tool. - void OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent); - - /// \brief Add a control point and finish current segment. - void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); + mitk::Point3D PrepareInitContour(const mitk::Point3D& clickedPoint) override; + virtual void FinalizePreviewContour(const Point3D& clickedPoint) override; + virtual void InitializePreviewContour(const Point3D& clickedPoint) override; + virtual void UpdatePreviewContour(const Point3D& clickedPoint) override; - /// \brief Actual LiveWire computation. - void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); - - /// \brief Check double click on first control point to finish the LiveWire tool. - bool OnCheckPoint(const InteractionEvent *interactionEvent); - - /// \brief Finish LiveWire tool. - void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); - - /// \brief Close the contour. - void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); + private: /// \brief Don't use dynamic cost map for LiveWire calculation. void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. - void FinishTool(); - - void EnableContourLiveWireInteraction(bool on); - - bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); - - void ReleaseInteractors(); - - void ReleaseHelperObjects(); - - void RemoveHelperObjects(); + void FinishTool() override; template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); - ContourModel::Pointer CreateNewContour() const; - - void UpdateClosureContour(mitk::Point3D endpoint); - - mitk::ContourModel::Pointer m_Contour; - mitk::DataNode::Pointer m_ContourNode; - - mitk::ContourModel::Pointer m_LiveWireContour; - mitk::DataNode::Pointer m_LiveWireContourNode; - - mitk::ContourModel::Pointer m_ClosureContour; - mitk::DataNode::Pointer m_ClosureContourNode; - bool m_SnapClosureContour; - - mitk::ContourModel::Pointer m_EditingContour; - mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModelLiveWireInteractor::Pointer m_ContourInteractor; - /** Slice of the reference data the tool is currently actively working on to - define contours.*/ - mitk::Image::Pointer m_ReferenceDataSlice; - mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; - mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilterClosure; bool m_CreateAndUseDynamicCosts; - - std::vector> m_WorkingContours; - std::vector> m_EditingContours; - std::vector m_LiveWireInteractors; - - PlaneGeometry::ConstPointer m_PlaneGeometry; }; } #endif diff --git a/Modules/Segmentation/Resources/Interactions/ContourModelModificationConfig.xml b/Modules/Segmentation/Resources/Interactions/ContourModelModificationConfig.xml index da40ab89b3..fd6317e057 100644 --- a/Modules/Segmentation/Resources/Interactions/ContourModelModificationConfig.xml +++ b/Modules/Segmentation/Resources/Interactions/ContourModelModificationConfig.xml @@ -1,15 +1,22 @@ + + + + + + + diff --git a/Modules/Segmentation/Resources/Interactions/ContourModelModificationInteractor.xml b/Modules/Segmentation/Resources/Interactions/ContourModelModificationInteractor.xml index 32a7574791..4963143426 100644 --- a/Modules/Segmentation/Resources/Interactions/ContourModelModificationInteractor.xml +++ b/Modules/Segmentation/Resources/Interactions/ContourModelModificationInteractor.xml @@ -1,22 +1,28 @@ + + + + + + diff --git a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml b/Modules/Segmentation/Resources/Interactions/EditableContourTool.xml similarity index 55% rename from Modules/Segmentation/Resources/Interactions/LiveWireTool.xml rename to Modules/Segmentation/Resources/Interactions/EditableContourTool.xml index 27d02bc15d..c9ba8da33f 100644 --- a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml +++ b/Modules/Segmentation/Resources/Interactions/EditableContourTool.xml @@ -1,30 +1,42 @@ - + - + - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/NewAdd_48x48.png b/Modules/Segmentation/Resources/NewAdd_48x48.png new file mode 100644 index 0000000000..3336c07b3c Binary files /dev/null and b/Modules/Segmentation/Resources/NewAdd_48x48.png differ diff --git a/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png b/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png new file mode 100644 index 0000000000..a22b98f416 Binary files /dev/null and b/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png differ diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index c35539f7fe..2cdf1bcdf5 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,108 +1,112 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationHelper.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkSegWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp + Interactions/mitkEditableContourTool.cpp Interactions/mitkLiveWireTool2D.cpp + Interactions/mitkLassoTool.cpp Interactions/mitkContourTool.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Interactions/mitkProcessExecutor.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png AI_48x48.png AI_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png + NewAdd_48x48.png + NewAdd_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml - Interactions/LiveWireTool.xml + Interactions/EditableContourTool.xml Interactions/PickingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.cpp new file mode 100644 index 0000000000..f0cb874239 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.cpp @@ -0,0 +1,100 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkEditableContourToolGUIBase.h" + +QmitkEditableContourToolGUIBase::QmitkEditableContourToolGUIBase() : QmitkToolGUI() +{ + m_Controls.setupUi(this); + m_Controls.m_Information->hide(); + m_Controls.m_AutoCheck->setChecked(true); + m_Controls.m_ConfirmButton->hide(); + m_Controls.m_AddMode->setChecked(true); + m_Controls.m_SubstractMode->hide(); + m_Controls.m_AddMode->hide(); + + m_Controls.m_ClearButton->hide(); + + connect(m_Controls.m_ConfirmButton, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); + connect(m_Controls.m_ClearButton, SIGNAL(clicked()), this, SLOT(OnClearSegmentation())); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); + connect(m_Controls.m_InformationCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnShowInformation(bool))); + connect(m_Controls.m_AutoCheck, SIGNAL(toggled(bool)), this, SLOT(OnAutoConfirm(bool))); + connect(m_Controls.m_AddMode, SIGNAL(toggled(bool)), this, SLOT(OnAddModeToogled(bool))); +} + +QmitkEditableContourToolGUIBase::~QmitkEditableContourToolGUIBase() +{ +} + +void QmitkEditableContourToolGUIBase::OnNewToolAssociated(mitk::Tool *tool) +{ + m_NewTool = dynamic_cast(tool); + if (m_NewTool.IsNull()) + { + mitkThrow() << "Tool is in an invalid state. QmitkEditableContourToolGUIBase needs tools based on EditableContourTool."; + } + + const auto autoConfirm = m_NewTool->GetAutoConfirm(); + m_Controls.m_AutoCheck->setChecked(autoConfirm); + const auto addMode = m_NewTool->GetAddMode(); + m_Controls.m_AddMode->setChecked(addMode); + this->OnAutoConfirm(autoConfirm); +} + +void QmitkEditableContourToolGUIBase::OnConfirmSegmentation() +{ + if (m_NewTool.IsNotNull()) + { + m_NewTool->ConfirmSegmentation(); + } +} + +void QmitkEditableContourToolGUIBase::OnClearSegmentation() +{ + if (m_NewTool.IsNotNull()) + m_NewTool->ClearSegmentation(); +} + +void QmitkEditableContourToolGUIBase::OnShowInformation(bool on) +{ + m_Controls.m_Information->setVisible(on); +} + +void QmitkEditableContourToolGUIBase::OnAutoConfirm(bool on) +{ + m_Controls.m_ConfirmButton->setVisible(!on); + m_Controls.m_ClearButton->setVisible(!on); + m_Controls.m_AddMode->setVisible(!on); + if (on) + { + m_Controls.m_AddMode->setChecked(true); + } + m_Controls.m_SubstractMode->setVisible(!on); + + if (m_NewTool.IsNotNull()) + { + if (on && m_NewTool->IsEditingContour()) + { + this->OnConfirmSegmentation(); + } + m_NewTool->SetAutoConfirm(on); + } +} + +void QmitkEditableContourToolGUIBase::OnAddModeToogled(bool on) +{ + if (m_NewTool.IsNotNull()) + { + m_NewTool->SetAddMode(on); + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h new file mode 100644 index 0000000000..8343107799 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIBase.h @@ -0,0 +1,59 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkEditableContourToolGUIBase_h_Included +#define QmitkEditableContourToolGUIBase_h_Included + +#include "QmitkToolGUI.h" +#include "mitkEditableContourTool.h" +#include "ui_QmitkEditableContourToolGUIControls.h" +#include + +class QmitkEditableContourToolGUIBaseControls; + +/** +\ingroup org_mitk_gui_qt_interactivesegmentation_internal +\brief GUI for mitk::EditableContourTool based classes. +\sa mitk::LassoTool +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkEditableContourToolGUIBase : public QmitkToolGUI +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkEditableContourToolGUIBase, QmitkToolGUI); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots : + + void OnNewToolAssociated(mitk::Tool *); + + void OnConfirmSegmentation(); + + void OnClearSegmentation(); + + void OnAutoConfirm(bool on); + void OnAddModeToogled(bool on); + + void OnShowInformation(bool on); + +protected: + QmitkEditableContourToolGUIBase(); + ~QmitkEditableContourToolGUIBase() override; + + Ui::QmitkEditableContourToolGUIControls m_Controls; + + mitk::EditableContourTool::Pointer m_NewTool; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIControls.ui similarity index 63% rename from Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui rename to Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIControls.ui index c093e3b61d..4cb2af9f25 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkEditableContourToolGUIControls.ui @@ -1,131 +1,159 @@ - QmitkLiveWireTool2DGUIControls - + QmitkEditableContourToolGUIControls + 0 0 421 - 355 + 429 0 0 0 0 QmitkLiveWireTool2DGUIControls Confirm all previous contour. 0 0 0 0 + + + + Auto confirm contour + + + true + + + + + + + The contour will be added as segmentation for the active label. + + + Add mode + + + true + + + + + + + If confirmed the contour will be added as background, thus it will be substracted from the active label. + + + Substract mode + + + Confirm Segmentation Clear Segmentation Qt::Vertical QSizePolicy::Fixed 20 15 - - - - Snap closing contour - - - Show usage information 0 150 12 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:12pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The tool is started by a double-click near the object of interest.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">You can fix preview segments by adding anchor points with a single left mouse button click. Pressing &quot;Delete&quot; will remove the last segment. To close a contour and finish current segmentation perform a double click on the first anchor point.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">You can fix preview segments by adding ancor points with a single left mouse button click. When you keep the mouse button pressed, you can manually draw contours (freehand contours) by moving the mouse. To close a contour and finish current segmentation perform a double click.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If &quot;Auto confirm&quot; is active and the segmentation is finished, the preview will be directly added as segmentation. From there you are free to start a new contour (double click).</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">After finishing a segmentation the contour will be still editable. You can move, insert and delete its anchor points.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If &quot;Auto confirm&quot; is not active, the contour will be still editable after finishing the segmentation. You can move, insert (CTRL+click on the contour segment) and delete (press DEL while ancor is selected) its ancor points. To confirm the segmentation, push the &quot;Confirm Segmentation&quot; button. Please be aware that freehand contour parts are treated differently: You cannot add an ancor point there, also you are not allowed to select/move/delete an ancor point that is at the beginning or end of a freehand contour part. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Push the &quot;Confirm Segmentation&quot; button or deactivate the Live Wire Tool to fill all contours in the working image. Push the &quot;Clear Segmentation&quot; button to delete all unconfirmed contours.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Push the &quot;Clear Segmentation&quot; button to delete all unconfirmed contours.</span></p></body></html> diff --git a/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.cpp new file mode 100644 index 0000000000..5f468b83eb --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.cpp @@ -0,0 +1,24 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkLassoToolGUI.h" + + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkLassoToolGUI, "") + +QmitkLassoToolGUI::QmitkLassoToolGUI() : QmitkEditableContourToolGUIBase() +{ +} + +QmitkLassoToolGUI::~QmitkLassoToolGUI() +{ +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.h new file mode 100644 index 0000000000..71a92bf7d7 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkLassoToolGUI.h @@ -0,0 +1,38 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkLassoToolGUI_h_Included +#define QmitkLassoToolGUI_h_Included + +#include "QmitkEditableContourToolGUIBase.h" +#include + +/** +\ingroup org_mitk_gui_qt_interactivesegmentation_internal +\brief GUI for mitk::NewAddTool. +\sa mitk::LassoTool +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkLassoToolGUI : public QmitkEditableContourToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkLassoToolGUI, QmitkEditableContourToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected: + QmitkLassoToolGUI(); + ~QmitkLassoToolGUI() override; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp index 19887dc125..4466e7530a 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp @@ -1,66 +1,23 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkLiveWireTool2DGUI.h" -#include "mitkBaseRenderer.h" -#include "mitkStepper.h" -#include -#include -#include -#include -#include - MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkLiveWireTool2DGUI, "") -QmitkLiveWireTool2DGUI::QmitkLiveWireTool2DGUI() : QmitkToolGUI() +QmitkLiveWireTool2DGUI::QmitkLiveWireTool2DGUI() : QmitkEditableContourToolGUIBase() { - m_Controls.setupUi(this); - m_Controls.m_Information->hide(); - - connect(m_Controls.m_ConfirmButton, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); - connect(m_Controls.m_ClearButton, SIGNAL(clicked()), this, SLOT(OnClearSegmentation())); - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); - connect(m_Controls.m_SnapClosureCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnSnapClosureContour(bool))); - connect(m_Controls.m_InformationCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnShowInformation(bool))); } QmitkLiveWireTool2DGUI::~QmitkLiveWireTool2DGUI() { } - -void QmitkLiveWireTool2DGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - m_LiveWireTool = dynamic_cast(tool); -} - -void QmitkLiveWireTool2DGUI::OnConfirmSegmentation() -{ - if (m_LiveWireTool.IsNotNull()) - m_LiveWireTool->ConfirmSegmentation(); -} - -void QmitkLiveWireTool2DGUI::OnClearSegmentation() -{ - if (m_LiveWireTool.IsNotNull()) - m_LiveWireTool->ClearSegmentation(); -} - -void QmitkLiveWireTool2DGUI::OnSnapClosureContour(bool snap) -{ - m_LiveWireTool->SetSnapClosureContour(snap); -} - -void QmitkLiveWireTool2DGUI::OnShowInformation(bool on) -{ - m_Controls.m_Information->setVisible(on); -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h index 74cb48476f..8d23694707 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h @@ -1,66 +1,38 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkLiveWireTool2DGUI_h_Included #define QmitkLiveWireTool2DGUI_h_Included -#include "QmitkToolGUI.h" -#include "mitkLiveWireTool2D.h" -#include "ui_QmitkLiveWireTool2DGUIControls.h" +#include "QmitkEditableContourToolGUIBase.h" #include -class QSlider; -class QLabel; -class QFrame; -class QPushButton; -#include - -#include "QmitkStepperAdapter.h" - -class QmitkLiveWireTool2DGUIControls; - /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::LiveWireTool. \sa mitk::LiveWireTool2D */ -class MITKSEGMENTATIONUI_EXPORT QmitkLiveWireTool2DGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkLiveWireTool2DGUI : public QmitkEditableContourToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkLiveWireTool2DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - protected slots : - - void OnNewToolAssociated(mitk::Tool *); - - void OnConfirmSegmentation(); - - void OnClearSegmentation(); - - void OnSnapClosureContour(bool snap); - - void OnShowInformation(bool on); - protected: QmitkLiveWireTool2DGUI(); ~QmitkLiveWireTool2DGUI() override; - - Ui::QmitkLiveWireTool2DGUIControls m_Controls; - - mitk::LiveWireTool2D::Pointer m_LiveWireTool; }; #endif diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index e1bf1397ec..466772d08a 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,79 +1,83 @@ set( CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp +Qmitk/QmitkEditableContourToolGUIBase.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp +Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h +Qmitk/QmitkEditableContourToolGUIBase.h Qmitk/QmitkLiveWireTool2DGUI.h +Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui -Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui +Qmitk/QmitkEditableContourToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index 82dcb5a5b7..8fff9e1c8c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,405 +1,415 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the segmentation view",2.00} Some of the features described below are closed source additions to the open source toolkit MITK and are not available in every application. \tableofcontents \section org_mitk_views_segmentationoverview Overview Segmentation is the act of partitioning an image into subsets by either manual or automated delineation to create i.e. a distinction between foreground and background. A multilabel segmentation can contain more than one label and more than one layer. This allows you to create different labels for different regions of interest encapsulated in one single image. The difference between labels and layers is that labels on one layer cannot overlap but labels on different layers can. The MITK segmentation plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of two views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Segmentation post-processing
In this documentation, the features and usage of the segmentation view will be described. For an introduction to the segmentation utilities please be referred to the respective documentation pages. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation plugin overview", 16.00} \section org_mitk_views_segmentationpreferences Preferences The segmentation plugin offers a number of preferences which can be set via the MITK Workbench application preferences: \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Slim view: Allows to show or hide the tool button description of the segmentation view
  • 2D display: Specify whether the segmentation is drawn as outline or as a transparent overlay
  • Show only selected nodes: Enable if only the selected segmentation and the reference image should be visible
  • Smoothed surface creation: Set certain smoothing parameters for surface creation
  • Default label set preset: Start a new segmentation with this preset instead of a default label
  • Label creation: Assign default names and colors to new labels or ask users for them
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtechnicalissues Technical issues The segmentation plugin makes a number of assumptions:
  • Images must be 2D, 3D, or 3D+t.
  • Images must be single-values, i.e. CT, MRI or "normal" ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations are handled as multilabel images of the same extent as the original image.
\section org_mitk_views_segmentationdataselection Data selection & creating new segmentations To select a reference image for the segmentation, click on the Selected image selection widget and choose a suitable image from the selection available in the data manager. Once an image is selected, a new segmentation can be created on this reference image by clicking the button to the right of the Selected segmentation selection widget. A new multilabel segmentation with an initial label is automatically generated. The new segmentation will be added to the data manager as sub-node of the reference image. This item is then automatically selected in the data selection, which allows to start editing the new segmentation right away. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection",12} Alternatively to creating a new segmentation, an existing one can be edited as well. If a reference image is selected for which a segmentation already exists in the data manager, the auto selection mode will automatically select a fitting segmentation. Clicking on the segmentation selection widget a drop down list will open, containing all suitable segmentations for the selected reference dataset available in the data manager. \section org_mitk_views_segmentationlayers Segmentation layers For each multilabel segmentation different layers can be added or deleted. The layers can be used independently and layers can be switched using the left and right arrows. A layer is a set of labels that occupy a non-overlapping anatomical space. The best way to describe them is by a real use case: Imagine you are working on a radiotherapy planning application. In the first layer of your segmentation session, you would like to trace the contours of the liver and neighboring organs. You can accommodate all these segmentations in separate labels because they all occupy different anatomical regions and do not overlap. Now say you would like to segment the arteries and veins inside the liver. If you don't trace them in a different layer, you will overwrite the previous ones. You may also need a third layer for segmenting the different irrigation territories in the liver and a fourth layer to contain the lesion you would like to treat. \imageMacro{"QmitkSegmentation_LayerSelection.png","Layer selection",12} \section org_mitk_views_segmentationlabels Segmentation labels For each layer, one or more labels can be added. Pressing the double arrow on the right, all created labels are shown in the 'Label Table'. The following label properties are available:
  • Name:
  • the name of the label. Can be a predefined one or any other.
  • Locked:
  • whether the label is locked or editable. A locked label cannot be overwritten by another.
  • Color:
  • the color of the label.
  • Visible:
  • whether the label is currently visible or hidden.
\imageMacro{"QmitkSegmentation_LabelTable.png","The 'Label Table' shows all labels in the current segmentation session",12} The 'New Label' button can be used to add a new label. This will automatically add a new label with a distinct name and color to the list of available labels.\n In the current implementation of the plugin, the maximum number of labels is restricted to 255. If you need more, you will have to create a new segmentation session. \subsection org_mitk_views_segmentationlabelsuggestions Label name and color suggestions When renaming labels or creating new labels with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. The list of predefined label names and colors for the auto-completion feature can be either extented or replaced by a custom list of label name and color suggestions. This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. \subsection org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all labels in all layers like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label for a certain value already exists, its properties are overridden by the preset. If a label for a certain value does not yet exist, an empty label with the label properties of the preset is created. The actual segmentations of labels are unaffected as label set presets only store label properties. \subsubsection org_mitk_views_segmentationdefaultlabelpresets Applying label set presets by default If you work on a repetetive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "New label 1" label. \subsection org_mitk_views_segmentationlabelsearch Searching for a label It may happen that many labels (e.g. > 200) are present in a segmentation session and therefore manual searching can be time-consuming. The 'Label Search' edit box allows for quickly finding a label by providing assistance for label name completion. If the label is found, it will become the active one after pressing 'enter'. To start editing a label needs to be activated by clicking on the corresponding row in the 'Label Table'. Only one label can be active at the time. Then the segmentation tools in the toolbox can be used for mask generation. \subsection org_mitk_views_multilabelsegmentationoperationsonlabels Operations on labels Depending on the selection in the 'Label Table', several actions are offered: \subsubsection org_mitk_views_segmentationoperationssingleselection Operations with single label selection Right-clicking on any label opens a pop-up menu that offers the following actions to be performed on the selected label:
  • Rename...
  • : change the name and / or color of the selected label.
  • Remove...
  • : delete the selected label.
  • Erase...
  • : only clear the contents of the selected label.
  • Merge...
  • : merge two labels by selecting a second label.
  • Random color
  • : assign a random color to the label.
  • View only
  • : make all labels except the current selected label invisible.
  • View/Hide all
  • : make all labels visible / invisible
  • Lock/Unlock all
  • : lock or unlock all labels.
  • Create surface
  • : generate a surface out of the selected label.
  • Create mask
  • : generate a mask out of the selected label. A mask is a binary image with "1" inside and "0" outside.
  • Create cropped mask
  • : generate a binary mask out of the selected label. Crop changes the extent of the resulting image to the extent of the label.
\imageMacro{"QmitkSegmentation_OperationsSingleSelection.png","Context menu for single label selection",12} \subsubsection org_mitk_views_segmentationoperationsmultiselection Operations with multiple label selection Shift-clickink on multiple labels allows to select more than one label. If more than one label is selected, different options will appear in the menu:
  • Merge selection on current label
  • : transfer the contents of the selected labels in the 'Label Table' into the current one.
  • Remove selected labels
  • : delete the selected labels.
  • Erase selected labels
  • : only clear the contents of the selected labels.
\imageMacro{"QmitkSegmentation_OperationsMultiSelection.png","Context menu for multiple label selection",12} \section org_mitk_views_segmentationtooloverview Segmentation tool overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting / adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as any of the manual segmentation tools are active - otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationaddsubtracttools Add and subtract tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and subtract tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). +\subsection org_mitk_views_segmentationlassotool Lasso tool +\imageMacro{QmitkSegmentation_Lasso.png,"Lasso tool",7.70} + +The tool is a more advanced version of the add/substract tool. It offers you the following features: +
    +
  1. Generating a polygon segmentation (click left mouse button to set ancor point) +
  2. Freehand contouring (like the add tool; press left mouse button while moving the mouse) +
  3. Move ancor points (select an ancor point, press left mouse button while moving the mouse) +
  4. Add new ancor points (press CTRL while click left mouse to add an ancor to the contour) +
  5. Delete an ancor point (press Del while ancor point is selected) +
  6. Segmentation can be added to the label (Add mode) or substracted (Substract mode) +
+To start a segmentation double left click where the first ancor point should be. To end the segmentation double left click where the last ancor point should be. +Please note that: +
    +
  • feature 3-6 are only available, if auto confirm is *not* activated +
  • feature 3-5 is not available for freehand contour segments +
+ \subsection org_mitk_views_segmentationpaintwipetools Paint and wipe tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and wipe tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationregiongrowingtool Region growing tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region growing tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This allows to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up / down is different from left / right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool Fill tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill tool",3.81} Left-click inside a segmentation with holes to completely fill all holes. Left-click inside a hole to fill only this specific hole. \subsection org_mitk_views_segmentationerasetool Erase tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmentations (left-click on specific segmentation) or to clear a whole slice at once (left-click outside a segmentation). \subsection org_mitk_views_segmentationlivewiretool Live wire tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live wire tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00} -
    -
  • (1) To start the tool you have to double-click near the edge of the object you want to segment. The initial anchor point will snap to the edge within a 3x3 region. -
  • (2) Move the mouse. You don't have trace the edge of the object. The contour will automatically snap to it. -
  • (3) To fix a segment you can set anchor points by single left mouse button click. -
  • (4) Go on with moving the mouse and setting anchor points. -
  • (5) To close the contour double-click on the initial anchor point. -
  • (6) After closing, the contour can be edited by moving, inserting and deleting anchor points. -
- -The contour will be transferred to its binary image representation by deactivating the tool. +The tool handling is the same like the Lasso tool (see for more info), except it generates live wire contours instead of straight lines. \subsection org_mitk_views_segmentationinterpolation 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. Note: Interpolation is currently disabled for segmentations containing more than one label. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D interpolation usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D interpolation usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since undo / redo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentation3dsegmentation 3D segmentation tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentation3dthresholdtool 3D Threshold tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentation3dulthresholdTool 3D upper / lower threshold tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D upper / lower threshold tool",10.00} \subsection org_mitk_views_segmentation3dotsutool 3D Otsu tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentation3drgtool 3D Region growing tool The 3D Region Growing tool works similar to the 2D pendant. At the beginning you have to place a seedpoint and define a threshold interval. If you press Run Segmentation a preview is calculated. By moving the Adapt region growing slider you can interactively adapt the segmentation result. \imageMacro{QmitkSegmentation_3DRGTool.png,"3D Region growing tool",10.00} \subsection org_mitk_views_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "islands" within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the nnUNet. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
Remark: The tool assumes that you have a Python3 environment with nnUNet (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. -# For ensemble predictions, you will get the option to select parameters irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded and shown next to "Required Modalities". Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc Miscellaneous: -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# The "Advanced" > "Available Models" lists the available pre-trained tasks for download. Make sure you have internet connection. Then, choose a Task from the dropdown and click the Download button. The pre-trained models for the selected Task will be downloaded and placed to the RESULTS_FOLDER directory automatically. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \section org_mitk_views_segmentationpostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3dtimages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationtechnicaldetail Technical information for developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions. */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Lasso.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Lasso.png new file mode 100644 index 0000000000..3336c07b3c Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Lasso.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 98faec74c8..62b582b93c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,989 +1,989 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { OnLooseLabelSetConnection(); // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls->workingNodeSelector->SetNodePredicate(segPredicate); if (m_SelectionMode) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); // Remove observer if one was registered auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); if (finding != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_SelectionMode) { // hide all segmentation nodes to later show only the selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); this->OnEstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags.insert(std::pair(m_WorkingNode, m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { QmitkNewSegmentationDialog dialog(m_Parent); dialog.SetName(QString::fromStdString(newLabel->GetName())); dialog.SetColor(newLabel->GetColor()); if (QDialog::Rejected == dialog.exec()) return; auto name = dialog.GetName(); if (!name.isEmpty()) newLabel->SetName(name.toStdString()); newLabel->SetColor(dialog.GetColor()); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnLayersChanged() { this->OnEstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); } void QmitkSegmentationView::OnShowLabelTable(bool value) { m_Controls->labelSetWidget->setVisible(value); } void QmitkSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); - QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire'"); + QString segTools2D = tr("Add Subtract Lasso Fill Erase Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); m_Controls->toolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); m_Controls->toolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->layersWidget, &QmitkLayersWidget::LayersChanged, this, &QmitkSegmentationView::OnLayersChanged); connect(m_Controls->labelsWidget, &QmitkLabelsWidget::ShowLabelTable, this, &QmitkSegmentationView::OnShowLabelTable); // *------------------------ // * LABELSETWIDGET // *------------------------ connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::LabelSetWidgetReset, this, &QmitkSegmentationView::OnLabelSetWidgetReset); m_Controls->labelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls->labelSetWidget->hide(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } // tell the interpolation about tool manager, data storage and render window part if (nullptr != m_RenderWindowPart) { QList controllers; controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->labelsWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = prefs->Get("label set preset", ""); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { this->ApplyDisplayOptions(const_cast(node)); } } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is a multi label segmentation // its outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline node->GetData()->Modified(); } else if (nullptr != node->GetData()) { // node is a legacy binary segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", mitk::BoolProperty::New(m_DrawOutline)); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } m_Controls->layersWidget->UpdateGUI(); m_Controls->labelsWidget->UpdateGUI(); this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); m_Controls->layersWidget->setEnabled(false); m_Controls->labelsWidget->setEnabled(false); m_Controls->labelSetWidget->setEnabled(false); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->toolSelectionBox2D->setEnabled(false); m_Controls->toolSelectionBox3D->setEnabled(false); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); mitk::DataNode* referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry* workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->layersWidget->setEnabled(true); m_Controls->labelsWidget->setEnabled(true); m_Controls->labelSetWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr != labelSetImage) { int numberOfLabels = labelSetImage->GetNumberOfLabels(labelSetImage->GetActiveLayer()); if (2 == numberOfLabels) // fix for T27319: exterior is label 0, first label is label 1 { m_Controls->slicesInterpolator->setEnabled(true); } else { m_Controls->interpolatorWarningLabel->show(); m_Controls->interpolatorWarningLabel->setText("Interpolation only works for single label segmentations."); } } return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->show(); m_Controls->selectionWarningLabel->setText("" + text + ""); } }