diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.cpp b/Modules/ContourModel/DataManagement/mitkContourElement.cpp index 02cfc0c4a3..04873042b8 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourElement.cpp @@ -1,489 +1,552 @@ /*============================================================================ 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) { 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 { 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++) { 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) { return true; } } return false; } +bool mitk::ContourElement::GetLineSegmentForPoint(const mitk::Point3D &point, + float eps, + mitk::ContourElement::VertexType *previousVertex, + mitk::ContourElement::VertexType *nextVertex) 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(); + + 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 && distance < closestDistance) + { + closestDistance = distance; + if (previousVertex) + { + auto prod = p_v1 * v2_v1; + previousVertex->Coordinates = (*it1)->Coordinates; + previousVertex->IsControlPoint = (*it1)->IsControlPoint; + } + if (nextVertex) + { + nextVertex->Coordinates = (*it2)->Coordinates; + nextVertex->IsControlPoint = (*it2)->IsControlPoint; + } + } + } + + return closePointFound; +} + + 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..3ad6028602 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.h +++ b/Modules/ContourModel/DataManagement/mitkContourElement.h @@ -1,290 +1,295 @@ /*============================================================================ 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; + bool GetLineSegmentForPoint(const mitk::Point3D &point, + float eps, + mitk::ContourElement::VertexType *previousVertex, + mitk::ContourElement::VertexType *nextVertex) 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..68ed4433bd 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourModel.cpp @@ -1,690 +1,715 @@ /*============================================================================ 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)) { 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) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsNearContour(point, eps); } 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..eecf76f103 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.h +++ b/Modules/ContourModel/DataManagement/mitkContourModel.h @@ -1,467 +1,474 @@ /*============================================================================ 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 GetLineSegmentForPoint(Point3D &point, + float eps, + TimeStepType timestep, + mitk::ContourElement::VertexType *previousVertex = nullptr, + mitk::ContourElement::VertexType *nextVertex = nullptr); /** \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..c7dfd11a84 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp @@ -1,164 +1,184 @@ /*============================================================================ 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("finish", OnFinishEditing); } mitk::ContourModelInteractor::~ContourModelInteractor() { } 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()); contour->Deselect(); mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timeStep)) + bool isVertexSelected = contour->SelectVertexAt(click, 1.5, timeStep); + + if (!isVertexSelected) + { + bool isHover = false; + if (this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()) == false) + { + MITK_WARN << "Unknown property contour.hovering"; + } + if (isHover) + { + auto lastVertex = *(contour->GetVertexList(timeStep).end() - 1); + mitk::ContourElement::VertexType *previousVertex = + &mitk::ContourElement::VertexType(lastVertex->Coordinates, lastVertex->IsControlPoint); + contour->GetLineSegmentForPoint(click, mitk::ContourModelInteractor::eps, timeStep, previousVertex); + auto previousVertexInList = + contour->GetVertexAt(previousVertex->Coordinates, mitk::ContourModelInteractor::eps, timeStep); + auto index = contour->GetIndex(previousVertexInList, timeStep); + contour->InsertVertexAtIndex(click, index + 1, true, timeStep); + isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelInteractor::eps, timeStep); + } + } + + if (isVertexSelected) { contour->SetSelectedVertexAsControlPoint(); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); m_lastMousePosition = click; auto *contourGeometry = dynamic_cast(contour->GetGeometry(timeStep)); if (contourGeometry->IsInside(click)) { m_lastMousePosition = click; return true; } 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()); } 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(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); if (contour->IsNearContour(currentPosition, 1.5, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } 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) { - 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..1f577e16ba 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelInteractor.h @@ -1,63 +1,64 @@ /*============================================================================ 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; protected: ContourModelInteractor(); ~ContourModelInteractor() override; virtual bool OnCheckPointClick(const InteractionEvent *interactionEvent); virtual bool IsHovering(const 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; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h index 6352775048..6878c05b26 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h @@ -1,95 +1,94 @@ /*============================================================================ 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 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..480852dc63 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp @@ -0,0 +1,325 @@ +/*============================================================================ + +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(const char *type) : FeedbackContourTool(type) {} + +mitk::EditableContourTool::~EditableContourTool() +{ +} + + +void mitk::EditableContourTool::ConnectActionsAndFunctions() +{ + CONNECT_FUNCTION("InitObject", OnInitContour); + CONNECT_FUNCTION("AddPoint", OnAddPoint); + CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); + CONNECT_FUNCTION("FinishContour", OnFinish); + CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); + CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); +} + +void mitk::EditableContourTool::Activated() +{ + Superclass::Activated(); + this->ResetToStartState(); + this->EnableContourInteraction(true); +} + +void mitk::EditableContourTool::Deactivated() +{ + this->ConfirmSegmentation(); + Superclass::Deactivated(); +} + +void mitk::EditableContourTool::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::EditableContourTool::ClearSegmentation() +{ + this->ReleaseHelperObjects(); + this->ReleaseInteractors(); + this->ResetToStartState(); +} + +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; +} + +void mitk::EditableContourTool::OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) +{ + 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_PreviewContour = this->CreateNewContour(); + m_PreviewContourNode = mitk::DataNode::New(); + m_PreviewContourNode->SetData(m_PreviewContour); + m_PreviewContourNode->SetName("active livewire 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_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_PreviewContourNode, workingDataNode); + dataStorage->Add(m_ClosureContourNode, workingDataNode); + dataStorage->Add(m_EditingContourNode, 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(); +} + +bool mitk::EditableContourTool::OnCheckPoint(const InteractionEvent *interactionEvent) +{ + return false; +} + +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; + } + + // 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())); +} + +void mitk::EditableContourTool::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) {} + +void mitk::EditableContourTool::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) {} + +void mitk::EditableContourTool::FinishTool() +{ + auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); + + for (int i = 0; i <= numberOfTimesteps; ++i) + m_Contour->Close(i); + + this->GetToolManager()->GetDataStorage()->Remove(m_PreviewContourNode); + + m_PreviewContourNode = nullptr; + m_PreviewContour = nullptr; +} + +void mitk::EditableContourTool::ReleaseHelperObjects() +{ + this->RemoveHelperObjects(); + + m_EditingContours.clear(); + m_WorkingContours.clear(); + + m_EditingContourNode = nullptr; + m_EditingContour = nullptr; + + m_PreviewContourNode = nullptr; + m_PreviewContour = nullptr; + + m_ClosureContourNode = nullptr; + m_ClosureContour = nullptr; + + m_ContourNode = nullptr; + m_Contour = nullptr; +} + +void mitk::EditableContourTool::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_PreviewContourNode.IsNotNull()) + dataStorage->Remove(m_PreviewContourNode); + + if (m_ClosureContourNode.IsNotNull()) + dataStorage->Remove(m_ClosureContourNode); + + if (m_ContourNode.IsNotNull()) + dataStorage->Remove(m_ContourNode); + + 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(); + } + + 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); +} diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.h b/Modules/Segmentation/Interactions/mitkEditableContourTool.h new file mode 100644 index 0000000000..d21ab7bec6 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.h @@ -0,0 +1,102 @@ +/*============================================================================ + +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 +{ + + class MITKSEGMENTATION_EXPORT EditableContourTool : public FeedbackContourTool + { + public: + mitkClassMacro(EditableContourTool, FeedbackContourTool); + + /// \brief Convert all current contours to binary segmentations. + virtual void ConfirmSegmentation(); + + /// \brief Delete all current contours. + virtual void ClearSegmentation(); + + protected: + EditableContourTool(const char *type); + ~EditableContourTool() override; + + void ConnectActionsAndFunctions() override; + + void Activated() override; + void Deactivated() override; + + /// \brief Initialize tool. + virtual void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Add a control point and finish current segment. + virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) = 0; + + /// \brief Actual LiveWire computation. + virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) = 0; + + /// \brief Check double click on first control point to finish the LiveWire tool. + virtual bool OnCheckPoint(const InteractionEvent *interactionEvent); + + /// \brief Finish LiveWire tool. + virtual void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Close the contour. + virtual void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Don't use dynamic cost map for LiveWire calculation. + virtual void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); + + /// \brief Finish contour interaction. + virtual void FinishTool(); + + virtual void EnableContourInteraction(bool on) = 0; + + virtual void ReleaseInteractors() = 0; + + virtual void ReleaseHelperObjects(); + + virtual void RemoveHelperObjects(); + + ContourModel::Pointer CreateNewContour() const; + + virtual void UpdateClosureContour(mitk::Point3D endpoint); + + bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); + + mitk::ContourModel::Pointer m_Contour; + 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_EditingContour; + mitk::DataNode::Pointer m_EditingContourNode; + + /** Slice of the reference data the tool is currently actively working on to + define contours.*/ + mitk::Image::Pointer m_ReferenceDataSlice; + + std::vector> m_WorkingContours; + std::vector> m_EditingContours; + + PlaneGeometry::ConstPointer m_PlaneGeometry; + }; +} + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index e39f44b64e..3c3c6855f7 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,740 +1,486 @@ /*============================================================================ 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("LiveWireTool"), m_SnapClosureContour(false), 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() +void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { - 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; + mitk::EditableContourTool::ConnectActionsAndFunctions(); + CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); } void mitk::LiveWireTool2D::ReleaseInteractors() { - this->EnableContourLiveWireInteraction(false); + this->EnableContourInteraction(false); m_LiveWireInteractors.clear(); } -void mitk::LiveWireTool2D::ConnectActionsAndFunctions() -{ - CONNECT_FUNCTION("InitObject", OnInitLiveWire); - CONNECT_FUNCTION("AddPoint", OnAddPoint); - CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); - 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 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 + m_PreviewContour = this->m_LiveWireFilter->GetOutput(); + m_PreviewContour->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); + m_PreviewContourNode->SetData(this->m_PreviewContour); 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); } } 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) +void mitk::LiveWireTool2D::EnableContourInteraction(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) + if (!m_PreviewContour || !m_Contour) { return; } - if (m_LiveWireContour->GetNumberOfVertices() > 0) + if (m_PreviewContour->GetNumberOfVertices() > 0) { - UpdateClosureContour(m_LiveWireContour->GetVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1)->Coordinates); + UpdateClosureContour(m_PreviewContour->GetVertexAt(m_PreviewContour->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) +void mitk::LiveWireTool2D::OnInitContour(StateMachineAction *s, InteractionEvent *interactionEvent) { - 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); + mitk::EditableContourTool::OnInitContour(s, interactionEvent); - return contour; -} - -void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) -{ 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); // 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(); - m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // 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); + m_PreviewContour->RemoveVertexAt(0); // Set last point as control point - m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); + m_PreviewContour->SetControlVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); // Merge contours - m_Contour->Concatenate(m_LiveWireContour); + m_Contour->Concatenate(m_PreviewContour); // Clear the LiveWire contour and reset the corresponding DataNode - m_LiveWireContour->Clear(); + m_PreviewContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilterClosure->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_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()); 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); + mitk::EditableContourTool::UpdateClosureContour(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) +void mitk::LiveWireTool2D::OnFinish(StateMachineAction *s, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction - - m_Contour->Concatenate(m_ClosureContour); - auto positionEvent = dynamic_cast(interactionEvent); - - if (nullptr == positionEvent) - return; - - if (m_PlaneGeometry.IsNotNull()) + if (!OnCheckPoint(interactionEvent)) { - // Check if the point is in the correct slice - if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) - return; + m_Contour->Concatenate(m_ClosureContour); } - //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())); + mitk::EditableContourTool::OnFinish(s, interactionEvent); 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; + mitk::EditableContourTool::FinishTool(); 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_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_PreviewContourNode); dataStorage->Remove(m_ContourNode); dataStorage->Remove(m_EditingContourNode); - m_LiveWireContour = this->CreateNewContour(); - m_LiveWireContourNode->SetData(m_LiveWireContour); + m_PreviewContour = this->CreateNewContour(); + m_PreviewContourNode->SetData(m_PreviewContour); 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); + m_PreviewContour = this->CreateNewContour(); + m_PreviewContourNode->SetData(m_PreviewContour); 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..118d1f4d7c 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,148 +1,115 @@ /*============================================================================ 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); 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); + void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Add a control point and finish current segment. - void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); + void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Actual LiveWire computation. - void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); + void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Check double click on first control point to finish the LiveWire tool. - bool OnCheckPoint(const InteractionEvent *interactionEvent); + bool OnCheckPoint(const InteractionEvent *interactionEvent) override; /// \brief Finish LiveWire tool. - void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); + void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Close the contour. - void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); + void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Don't use dynamic cost map for LiveWire calculation. - void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); + void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Finish contour interaction. - void FinishTool(); - - void EnableContourLiveWireInteraction(bool on); - - bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); - - void ReleaseInteractors(); + void FinishTool() override; - void ReleaseHelperObjects(); + void EnableContourInteraction(bool on) override; - void RemoveHelperObjects(); + void ReleaseInteractors() override; template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); - ContourModel::Pointer CreateNewContour() const; + void UpdateClosureContour(mitk::Point3D endpoint) override; - 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/Interactions/mitkNewAddTool2D.cpp b/Modules/Segmentation/Interactions/mitkNewAddTool2D.cpp new file mode 100644 index 0000000000..8eb211047e --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkNewAddTool2D.cpp @@ -0,0 +1,170 @@ +/*============================================================================ + +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, NewAddTool2D, "New Add tool"); +} + +mitk::NewAddTool2D::NewAddTool2D() : EditableContourTool("LiveWireTool") +{ +} + +mitk::NewAddTool2D::~NewAddTool2D() +{} + +void mitk::NewAddTool2D::ConnectActionsAndFunctions() +{ + mitk::EditableContourTool::ConnectActionsAndFunctions(); + CONNECT_FUNCTION("MovePoint", OnMouseMoved); +} + +void mitk::NewAddTool2D::ReleaseInteractors() +{ + this->EnableContourInteraction(false); + m_ContourInteractors.clear(); +} + +const char **mitk::NewAddTool2D::GetXPM() const +{ + return nullptr; +} + +us::ModuleResource mitk::NewAddTool2D::GetIconResource() const +{ + return us::GetModuleContext()->GetModule()->GetResource("NewAdd_48x48.png"); +} + +us::ModuleResource mitk::NewAddTool2D::GetCursorIconResource() const +{ + return us::GetModuleContext()->GetModule()->GetResource("NewAdd_Cursor_32x32.png"); +} + +const char *mitk::NewAddTool2D::GetName() const +{ + return "New Add"; +} + +void mitk::NewAddTool2D::EnableContourInteraction(bool on) +{ + for (const auto &interactor : m_ContourInteractors) + interactor->EnableInteraction(on); +} + +void mitk::NewAddTool2D::OnInitContour(StateMachineAction *s, InteractionEvent *interactionEvent) +{ + mitk::EditableContourTool::OnInitContour(s, interactionEvent); + + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + return; + + auto workingDataNode = this->GetWorkingDataNode(); + + if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) + { + this->ResetToStartState(); + return; + } + + // Map click to pixel coordinates + auto click = positionEvent->GetPositionInWorld(); + + // Set initial start point + m_Contour->AddVertex(click, true); + m_PreviewContour->AddVertex(click, false); + m_ClosureContour->AddVertex(click); + + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::NewAddTool2D::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; + } + + m_Contour->AddVertex(positionEvent->GetPositionInWorld(), true); + + std::for_each(m_PreviewContour->IteratorBegin(), m_PreviewContour->IteratorEnd(), [this](ContourElement::VertexType* vertex) + { + m_PreviewContour->RemoveVertex(vertex); + }); + + m_PreviewContour->AddVertex(positionEvent->GetPositionInWorld()); + m_PreviewContour->Update(); + + mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::NewAddTool2D::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; + } + + if (m_PreviewContour->GetNumberOfVertices() > 1) + { + m_PreviewContour->RemoveVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); + } + m_PreviewContour->AddVertex(positionEvent->GetPositionInWorld()); + m_PreviewContour->Update(); + + this->UpdateClosureContour(positionEvent->GetPositionInWorld()); + + RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); +} + +void mitk::NewAddTool2D::OnFinish(StateMachineAction *s, InteractionEvent *interactionEvent) +{ + mitk::EditableContourTool::OnFinish(s, interactionEvent); + this->FinishTool(); +} + +void mitk::NewAddTool2D::FinishTool() +{ + mitk::EditableContourTool::FinishTool(); + + m_ContourInteractor = mitk::ContourModelInteractor::New(); + m_ContourInteractor->SetDataNode(m_ContourNode); + m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); + m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); + + m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); + + this->m_ContourInteractors.push_back(m_ContourInteractor); +} diff --git a/Modules/Segmentation/Interactions/mitkNewAddTool2D.h b/Modules/Segmentation/Interactions/mitkNewAddTool2D.h new file mode 100644 index 0000000000..b020a52ed0 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkNewAddTool2D.h @@ -0,0 +1,87 @@ +/*============================================================================ + +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 mitkNewAddTool2D_h +#define mitkNewAddTool2D_h + +#include "mitkEditableContourTool.h" +#include "mitkContourTool.h" +#include "mitkContourModelInteractor.h" + +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 NewAddTool2D : public EditableContourTool + { + public: + mitkClassMacro(NewAddTool2D, SegTool2D); + itkFactorylessNewMacro(Self); + + us::ModuleResource GetCursorIconResource() const override; + us::ModuleResource GetIconResource() const override; + const char *GetName() const override; + const char **GetXPM() const override; + + protected: + NewAddTool2D(); + ~NewAddTool2D() override; + + void ConnectActionsAndFunctions() override; + + private: + /// \brief Initialize tool. + void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) override; + + /// \brief Add a control point and finish current segment. + void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) override; + + /// \brief Actual contour computation. + void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) override; + + /// \brief Finish contour tool. + void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) override; + + /// \brief Finish contour interaction. + void FinishTool() override; + + void EnableContourInteraction(bool on) override; + + void ReleaseInteractors() override; + + mitk::ContourModelInteractor::Pointer m_ContourInteractor; + std::vector m_ContourInteractors; + }; +} + +#endif diff --git a/Modules/Segmentation/Resources/NewAdd_48x48.png b/Modules/Segmentation/Resources/NewAdd_48x48.png new file mode 100644 index 0000000000..3f8e04127d 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..16fad2620d 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..16559f0cb0 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/mitkNewAddTool2D.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/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/QmitkNewAddTool2DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkNewAddTool2DGUI.cpp new file mode 100644 index 0000000000..cd7c867d15 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkNewAddTool2DGUI.cpp @@ -0,0 +1,60 @@ +/*============================================================================ + +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 "QmitkNewAddTool2DGUI.h" + +#include "mitkBaseRenderer.h" +#include "mitkStepper.h" +#include +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkNewAddTool2DGUI, "") + +QmitkNewAddTool2DGUI::QmitkNewAddTool2DGUI() : QmitkToolGUI() +{ + 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_InformationCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnShowInformation(bool))); +} + +QmitkNewAddTool2DGUI::~QmitkNewAddTool2DGUI() +{ +} + +void QmitkNewAddTool2DGUI::OnNewToolAssociated(mitk::Tool *tool) +{ + m_NewAddTool = dynamic_cast(tool); +} + +void QmitkNewAddTool2DGUI::OnConfirmSegmentation() +{ + if (m_NewAddTool.IsNotNull()) + m_NewAddTool->ConfirmSegmentation(); +} + +void QmitkNewAddTool2DGUI::OnClearSegmentation() +{ + if (m_NewAddTool.IsNotNull()) + m_NewAddTool->ClearSegmentation(); +} + +void QmitkNewAddTool2DGUI::OnShowInformation(bool on) +{ + m_Controls.m_Information->setVisible(on); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkNewAddTool2DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkNewAddTool2DGUI.h new file mode 100644 index 0000000000..6bb92d855b --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkNewAddTool2DGUI.h @@ -0,0 +1,64 @@ +/*============================================================================ + +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 QmitkNewAddTool2DGUI_h_Included +#define QmitkNewAddTool2DGUI_h_Included + +#include "QmitkToolGUI.h" +#include "mitkNewAddTool2D.h" +#include "ui_QmitkNewAddTool2DGUIControls.h" +#include + +class QSlider; +class QLabel; +class QFrame; +class QPushButton; +#include + +#include "QmitkStepperAdapter.h" + +class QmitkNewAddTool2DGUIControls; + +/** +\ingroup org_mitk_gui_qt_interactivesegmentation_internal +\brief GUI for mitk::NewAddTool. +\sa mitk::NewAddTool2D +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkNewAddTool2DGUI : public QmitkToolGUI +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkNewAddTool2DGUI, QmitkToolGUI); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + protected slots : + + void OnNewToolAssociated(mitk::Tool *); + + void OnConfirmSegmentation(); + + void OnClearSegmentation(); + + void OnShowInformation(bool on); + +protected: + QmitkNewAddTool2DGUI(); + ~QmitkNewAddTool2DGUI() override; + + Ui::QmitkNewAddTool2DGUIControls m_Controls; + + mitk::NewAddTool2D::Pointer m_NewAddTool; +}; + +#endif diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index 818830625e..a6da927cee 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,77 +1,79 @@ 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/QmitkLiveWireTool2DGUI.cpp +Qmitk/QmitkNewAddTool2DGUI.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/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.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/QmitkLiveWireTool2DGUI.h +Qmitk/QmitkNewAddTool2DGUI.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/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitknnUNetFolderParser.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 ) set(QRC_FILES resources/SegmentationUI.qrc ) 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..b2cde4fcce 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 Fill Erase Paint Wipe 'Region Growing' 'Live Wire' 'New Add'"); 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 + ""); } }