diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.cpp b/Modules/ContourModel/DataManagement/mitkContourElement.cpp index bd906acb7b..be73ced97b 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourElement.cpp @@ -1,419 +1,489 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include -bool mitk::ContourElement::ContourModelVertex::operator ==(const ContourModelVertex& other) const +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) + for (const auto &v : other.m_Vertices) { m_Vertices.push_back(new ContourModelVertex(*v)); } } -mitk::ContourElement& mitk::ContourElement::operator = (const ContourElement& other) +mitk::ContourElement &mitk::ContourElement::operator=(const ContourElement &other) { if (this != &other) { this->Clear(); - for (const auto& v : other.m_Vertices) + 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 (index >= 0 && 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 (pointId >= 0 && 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 (pointId >= 0 && 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 +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::BruteForceGetVertexAt(const mitk::Point3D &point, double eps) +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) { - std::deque> nearestlist; + // 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(); + } - ConstVertexIterator it = this->m_Vertices.begin(); + int vertexIndex = BruteForceGetVertexIndexAt(point, eps, verticesList); - ConstVertexIterator end = this->m_Vertices.end(); + if (vertexIndex!=-1) + { + vertexIndex += offset; - while (it != end) + if (vertexIndex < 0) { - mitk::Point3D currentPoint = (*it)->Coordinates; + // 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(); + } - double distance = currentPoint.EuclideanDistanceTo(point); - if (distance < eps) - { - // if list is emtpy, add point to list - if (nearestlist.size() < 1) - { - nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); - } - // found an approximate point - check if current is closer then first in nearestlist - else if (distance < nearestlist.front().first) - { - // found even closer vertex - nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); - } - } // if distance > eps + return verticesList[vertexIndex]; + } + return nullptr; +} - it++; - } // while - if (nearestlist.size() > 0) +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) { - /*++++++++++++++++++++ return the nearest active point if one was found++++++++++++++++++*/ - auto it = nearestlist.begin(); - auto end = nearestlist.end(); - while (it != end) + if (distance < nearestPointDistance) { - if ((*it).second->IsControlPoint) - { - return (*it).second; - } - it++; + nearestPointIterator = it; + nearestPointIsInitialized = true; + nearestPointDistance = distance; } - /*---------------------------------------------------------------------------------------*/ + } // if distance > eps - // return closest point - return nearestlist.front().second; - } + it++; + } // while + + if (nearestPointIsInitialized) + { + return nearestPointIterator - verticesList.begin(); } - return nullptr; + 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; } 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; }); + 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) + 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; }); + 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 +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 >= 0 && 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; }); + 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) +bool mitk::ContourElement::RemoveVertexByIterator(VertexListType::iterator &iter) { if (iter != this->m_Vertices.end()) { - delete* iter; + 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 42be258cc3..9a9f17186e 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.h +++ b/Modules/ContourModel/DataManagement/mitkContourElement.h @@ -1,260 +1,289 @@ /*============================================================================ 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 posoition in 3D space + /** \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; /** \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 posoition in 3D space + /** \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. + \param eps - the error bound for search algorithm. It is an open boundary. + \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 */ - VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, double eps); + 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 d6e04cb150..02350613d6 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourModel.cpp @@ -1,628 +1,679 @@ /*============================================================================ 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 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::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; } 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 d234d9cefb..f42452d056 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.h +++ b/Modules/ContourModel/DataManagement/mitkContourModel.h @@ -1,437 +1,467 @@ /*============================================================================ 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; + /** 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); + /** \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; }; itkEventMacro(ContourModelEvent, itk::AnyEvent); itkEventMacro(ContourModelShiftEvent, ContourModelEvent); itkEventMacro(ContourModelSizeChangeEvent, ContourModelEvent); itkEventMacro(ContourModelAddEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelRemoveEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelExpandTimeBoundsEvent, ContourModelEvent); itkEventMacro(ContourModelClosedEvent, ContourModelEvent); } #endif diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h index f392465b02..5d0aa957bc 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h @@ -1,231 +1,235 @@ /*============================================================================ 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 __itkShortestPathImageFilter_h #define __itkShortestPathImageFilter_h #include "itkImageToImageFilter.h" #include "itkShortestPathCostFunction.h" #include "itkShortestPathNode.h" #include #include // ------- INFORMATION ---------- /// SET FUNCTIONS // void SetInput( ItkImage ) // Compulsory // void SetStartIndex (const IndexType & StartIndex); // Compulsory // void SetEndIndex(const IndexType & EndIndex); // Compulsory // void SetFullNeighborsMode(bool) // Optional (default=false), if false N4, if true N26 // void SetActivateTimeOut(bool) // Optional (default=false), for debug issues: after 30s algorithms terminates. You can // have a look at the VectorOrderImage to see how far it came // void SetMakeOutputImage(bool) // Optional (default=true), Generate an outputimage of the path. You can also get the // path directoy with GetVectorPath() // void SetCalcAllDistances(bool) // Optional (default=false), Calculate Distances over the whole image. CAREFUL, // algorithm time extends a lot. Necessary for GetDistanceImage // void SetStoreVectorOrder(bool) // Optional (default=false), Stores in which order the pixels were checked. Necessary // for GetVectorOrderImage // void AddEndIndex(const IndexType & EndIndex) //Optional. By calling this function you can add several endpoints! The // algorithm will look for several shortest Pathes. From Start to all Endpoints. // /// GET FUNCTIONS // std::vector< itk::Index<3> > GetVectorPath(); // returns the shortest path as vector // std::vector< std::vector< itk::Index<3> > GetMultipleVectorPathe(); // returns a vector of shortest Pathes (which are // vectors of points) // GetDistanceImage // Returns the distance image // GetVectorOrderIMage // Returns the Vector Order image // // EXAMPLE USE // pleae see qmitkmitralvalvesegmentation4dtee bundle namespace itk { template class ShortestPathImageFilter : public ImageToImageFilter { public: // Standard Typedefs typedef ShortestPathImageFilter Self; typedef ImageToImageFilter Superclass; typedef SmartPointer Pointer; typedef SmartPointer ConstPointer; // Typdefs for metric typedef ShortestPathCostFunction CostFunctionType; typedef typename CostFunctionType::Pointer CostFunctionTypePointer; // More typdefs for convenience typedef TInputImageType InputImageType; typedef typename TInputImageType::Pointer InputImagePointer; typedef typename TInputImageType::PixelType InputImagePixelType; typedef typename TInputImageType::SizeType InputImageSizeType; typedef typename TInputImageType::IndexType IndexType; typedef typename itk::ImageRegionIteratorWithIndex InputImageIteratorType; typedef TOutputImageType OutputImageType; typedef typename TOutputImageType::Pointer OutputImagePointer; typedef typename TOutputImageType::PixelType OutputImagePixelType; typedef typename TOutputImageType::IndexType OutputImageIndexType; typedef ImageRegionIteratorWithIndex OutputImageIteratorType; typedef itk::ShapedNeighborhoodIterator itkShapedNeighborhoodIteratorType; // New Macro for smartpointer instantiation itkFactorylessNewMacro(Self); itkCloneMacro(Self); - // Run-time type information - itkTypeMacro(ShortestPathImageFilter, ImageToImageFilter); + // Run-time type information + itkTypeMacro(ShortestPathImageFilter, ImageToImageFilter); - // Display - void PrintSelf(std::ostream &os, Indent indent) const override; + // Display + void PrintSelf(std::ostream &os, Indent indent) const override; // Compare function for A_STAR struct CompareNodeStar { bool operator()(ShortestPathNode *a, ShortestPathNode *b) { return (a->distAndEst > b->distAndEst); } }; // \brief Set Starpoint for ShortestPath Calculation void SetStartIndex(const IndexType &StartIndex); // \brief Adds Endpoint for multiple ShortestPath Calculation void AddEndIndex(const IndexType &index); // \brief Set Endpoint for ShortestPath Calculation void SetEndIndex(const IndexType &EndIndex); // \brief Set FullNeighborsMode. false = no diagonal neighbors, in 2D this means N4 Neigborhood. true = would be N8 // in 2D itkSetMacro(FullNeighborsMode, bool); itkGetMacro(FullNeighborsMode, bool); // \brief Set Graph_fullNeighbors. false = no diagonal neighbors, in 2D this means N4 Neigborhood. true = would be // N8 in 2D itkSetMacro(Graph_fullNeighbors, bool); - // \brief (default=true), Produce output image, which shows the shortest path. But you can also get the shortest - // Path directly as vector with the function GetVectorPath - itkSetMacro(MakeOutputImage, bool); + // \brief (default=true), Produce output image, which shows the shortest path. But you can also get the shortest + // Path directly as vector with the function GetVectorPath + itkSetMacro(MakeOutputImage, bool); itkGetMacro(MakeOutputImage, bool); // \brief (default=false), Store an Vector of Order, so you can call getVectorOrderImage after update itkSetMacro(StoreVectorOrder, bool); itkGetMacro(StoreVectorOrder, bool); // \brief (default=false), // Calculate all Distances to all pixels, so you can call getDistanceImage after update // (warning algo will take a long time) itkSetMacro(CalcAllDistances, bool); itkGetMacro(CalcAllDistances, bool); // \brief (default=false), for debug issues: after 30s algorithms terminates. You can have a look at the // VectorOrderImage to see how far it came itkSetMacro(ActivateTimeOut, bool); itkGetMacro(ActivateTimeOut, bool); // \brief returns shortest Path as vector std::vector GetVectorPath(); // \brief returns Multiple shortest Paths. You can call this function, when u performed a multiple shortest path // search (one start, several ends) std::vector> GetMultipleVectorPaths(); // \brief returns the vector order image. It shows in which order the pixels were checked. good for debugging. Be // sure to have m_StoreVectorOrder=true OutputImagePointer GetVectorOrderImage(); // \brief returns the distance image. It shows the distances from the startpoint to all other pixels. Be sure to // have m_CalcAllDistances=true OutputImagePointer GetDistanceImage(); // \brief Fill m_VectorPath void MakeShortestPathVector(); // \brief cleans up the filter void CleanUp(); itkSetObjectMacro(CostFunction, CostFunctionType); // itkSetObjectMacro = set function that uses pointer as parameter itkGetObjectMacro(CostFunction, CostFunctionType); + void SetUseCostFunction(bool doUseCostFunction) { m_useCostFunction = doUseCostFunction; }; + bool GetUseCostFunction() { return m_useCostFunction; }; + protected: std::vector m_endPoints; // if you fill this vector, the algo will not rest until all endPoints have been reached std::vector m_endPointsClosed; ShortestPathNode *m_Nodes; // main list that contains all nodes NodeNumType m_Graph_NumberOfNodes; NodeNumType m_Graph_StartNode; NodeNumType m_Graph_EndNode; bool m_Graph_fullNeighbors; + bool m_useCostFunction; std::vector m_Graph_DiscoveredNodeList; ShortestPathImageFilter(Self &); // intentionally not implemented void operator=(const Self &); // intentionally not implemented const static int BACKGROUND = 0; const static int FOREGROUND = 255; bool m_FullNeighborsMode; bool m_MakeOutputImage; bool m_StoreVectorOrder; // Store an Vector of Order, so you can call getVectorOrderImage after update bool m_CalcAllDistances; // Calculate all Distances, so you can call getDistanceImage after update (warning algo // will take a long time) bool multipleEndPoints; bool m_ActivateTimeOut; // if true, then i search max. 30 secs. then abort bool m_Initialized; CostFunctionTypePointer m_CostFunction; IndexType m_StartIndex, m_EndIndex; std::vector m_VectorPath; std::vector> m_MultipleVectorPaths; std::vector m_VectorOrder; ShortestPathImageFilter(); ~ShortestPathImageFilter() override; // \brief Create all the outputs void MakeOutputs(); // \brief Generate Data void GenerateData() override; // \brief gets the estimate costs from pixel a to target. double getEstimatedCostsToTarget(const IndexType &a); typename InputImageType::Pointer m_magnitudeImage; // \brief Convert a indexnumber of a node in m_Nodes to image coordinates typename TInputImageType::IndexType NodeToCoord(NodeNumType); // \brief Convert image coordinate to a indexnumber of a node in m_Nodes unsigned int CoordToNode(IndexType); // \brief Returns the neighbors of a node std::vector GetNeighbors(NodeNumType nodeNum, bool FullNeighbors); // \brief Check if coords are in bounds of image bool CoordIsInBounds(IndexType); // \brief Initializes the graph void InitGraph(); // \brief Start ShortestPathSearch void StartShortestPathSearch(); }; } // end of namespace itk #include "itkShortestPathImageFilter.txx" #endif diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx index 73fee98147..1e4a4c2756 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx @@ -1,890 +1,897 @@ /*============================================================================ 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 __itkShortestPathImageFilter_txx #define __itkShortestPathImageFilter_txx #include "itkShortestPathImageFilter.h" #include "mitkMemoryUtilities.h" #include #include #include #include namespace itk { // Constructor (initialize standard values) template ShortestPathImageFilter::ShortestPathImageFilter() : m_Nodes(nullptr), m_Graph_NumberOfNodes(0), m_Graph_fullNeighbors(false), + m_useCostFunction(true), m_FullNeighborsMode(false), m_MakeOutputImage(true), m_StoreVectorOrder(false), m_CalcAllDistances(false), multipleEndPoints(false), m_ActivateTimeOut(false), m_Initialized(false) { m_endPoints.clear(); m_endPointsClosed.clear(); if (m_MakeOutputImage) { this->SetNumberOfRequiredOutputs(1); this->SetNthOutput(0, OutputImageType::New()); } } template ShortestPathImageFilter::~ShortestPathImageFilter() { delete[] m_Nodes; } template inline typename ShortestPathImageFilter::IndexType ShortestPathImageFilter::NodeToCoord(NodeNumType node) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; IndexType coord; if (dim == 2) { coord[1] = node / size[0]; coord[0] = node % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1])) { coord[0] = 0; coord[1] = 0; } } if (dim == 3) { coord[2] = node / (size[0] * size[1]); coord[1] = (node % (size[0] * size[1])) / size[0]; coord[0] = (node % (size[0] * size[1])) % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1]) || ((unsigned long)coord[2] >= size[2])) { coord[0] = 0; coord[1] = 0; coord[2] = 0; } } return coord; } template inline typename itk::NodeNumType ShortestPathImageFilter::CoordToNode( IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; NodeNumType node = 0; if (dim == 2) { node = (coord[1] * size[0]) + coord[0]; } if (dim == 3) { node = (coord[2] * size[0] * size[1]) + (coord[1] * size[0]) + coord[0]; } if ((m_Graph_NumberOfNodes > 0) && (node >= m_Graph_NumberOfNodes)) { /*MITK_INFO << "WARNING! Coordinates outside image!"; MITK_INFO << "Coords = " << coord ; MITK_INFO << "ImageDim = " << dim ; MITK_INFO << "RequestedRegionSize = " << size ;*/ node = 0; } return node; } template inline bool ShortestPathImageFilter::CoordIsInBounds(IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; if (dim == 2) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1])) { return true; } } if (dim == 3) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1]) && (coord[2] >= 0) && ((unsigned long)coord[2] < size[2])) { return true; } } return false; } template inline std::vector ShortestPathImageFilter::GetNeighbors( unsigned int nodeNum, bool FullNeighbors) { // returns a vector of nodepointers.. these nodes are the neighbors int dim = InputImageType::ImageDimension; IndexType Coord = NodeToCoord(nodeNum); IndexType NeighborCoord; std::vector nodeList; int neighborDistance = 1; // if i increase that, i might not hit the endnote // maybe use itkNeighborhoodIterator here, might be faster if (dim == 2) { // N4 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N8 NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } if (dim == 3) { // N6 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N26 // Middle Slice NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice(Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } return nodeList; } template void ShortestPathImageFilter::SetStartIndex( const typename TInputImageType::IndexType &StartIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_StartIndex[i] = StartIndex[i]; } m_Graph_StartNode = CoordToNode(m_StartIndex); // MITK_INFO << "StartIndex = " << StartIndex; // MITK_INFO << "StartNode = " << m_Graph_StartNode; m_Initialized = false; } template void ShortestPathImageFilter::SetEndIndex( const typename TInputImageType::IndexType &EndIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_EndIndex[i] = EndIndex[i]; } m_Graph_EndNode = CoordToNode(m_EndIndex); // MITK_INFO << "EndNode = " << m_Graph_EndNode; } template void ShortestPathImageFilter::AddEndIndex( const typename TInputImageType::IndexType &index) { // ONLY FOR MULTIPLE END POINTS SEARCH IndexType newEndIndex; for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { newEndIndex[i] = index[i]; } m_endPoints.push_back(newEndIndex); SetEndIndex(m_endPoints[0]); multipleEndPoints = true; } template inline double ShortestPathImageFilter::getEstimatedCostsToTarget( const typename TInputImageType::IndexType &a) { // Returns the minimal possible costs for a path from "a" to targetnode. itk::Vector v; v[0] = m_EndIndex[0] - a[0]; v[1] = m_EndIndex[1] - a[1]; v[2] = m_EndIndex[2] - a[2]; return m_CostFunction->GetMinCost() * v.GetNorm(); } template void ShortestPathImageFilter::InitGraph() { if (!m_Initialized) { // Clean up previous stuff CleanUp(); // Calc Number of nodes auto imageDimensions = TInputImageType::ImageDimension; const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); m_Graph_NumberOfNodes = 1; for (NodeNumType i = 0; i < imageDimensions; ++i) m_Graph_NumberOfNodes = m_Graph_NumberOfNodes * size[i]; // Initialize mainNodeList with that number m_Nodes = new ShortestPathNode[m_Graph_NumberOfNodes]; // Initialize each node in nodelist for (NodeNumType i = 0; i < m_Graph_NumberOfNodes; i++) { m_Nodes[i].distAndEst = -1; m_Nodes[i].distance = -1; m_Nodes[i].prevNode = -1; m_Nodes[i].mainListIndex = i; m_Nodes[i].closed = false; } m_Initialized = true; } // In the beginning, the Startnode needs a distance of 0 m_Nodes[m_Graph_StartNode].distance = 0; m_Nodes[m_Graph_StartNode].distAndEst = 0; // initalize cost function m_CostFunction->Initialize(); } template void ShortestPathImageFilter::StartShortestPathSearch() { // Setup Timer clock_t startAll = clock(); clock_t stopAll = clock(); // init variables double durationAll = 0; bool timeout = false; NodeNumType mainNodeListIndex = 0; DistanceType curNodeDistance = 0; NodeNumType numberOfNodesChecked = 0; // Create Multimap (tree structure for fast searching) std::multimap myMap; std::pair::iterator, std::multimap::iterator> ret; std::multimap::iterator it; // At first, only startNote is discovered. myMap.insert( std::pair(m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode])); // While there are discovered Nodes, pick the one with lowest distance, // update its neighbors and eventually delete it from the discovered Nodes list. while (!myMap.empty()) { numberOfNodesChecked++; /*if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) { MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() << "\n"; }*/ // Get element with lowest score mainNodeListIndex = myMap.begin()->second->mainListIndex; curNodeDistance = myMap.begin()->second->distance; myMap.begin()->second->closed = true; // close it // Debug: // MITK_INFO << "INFO: size " << myMap.size(); /* for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ // Kicks out element with lowest score myMap.erase(myMap.begin()); // if wanted, store vector order if (m_StoreVectorOrder) { m_VectorOrder.push_back(mainNodeListIndex); } // Check neighbors std::vector neighborNodes = GetNeighbors(mainNodeListIndex, m_Graph_fullNeighbors); for (NodeNumType i = 0; i < neighborNodes.size(); i++) { if (neighborNodes[i]->closed) continue; // this nodes is already closed, go to next neighbor IndexType coordCurNode = NodeToCoord(mainNodeListIndex); IndexType coordNeighborNode = NodeToCoord(neighborNodes[i]->mainListIndex); // calculate the new Distance to the current neighbor double newDistance = curNodeDistance + (m_CostFunction->GetCost(coordCurNode, coordNeighborNode)); // if it is shorter than any yet known path to this neighbor, than the current path is better. Save that! if ((newDistance < neighborNodes[i]->distance) || (neighborNodes[i]->distance == -1)) { // if that neighbornode is not in discoverednodeList yet, Push it there and update if (neighborNodes[i]->distance == -1) { neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); /* MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ } // or if is already in discoverednodelist, update else { /* it = myMap.find(neighborNodes[i]->distAndEst); if (it == myMap.end() ) { MITK_INFO << "Nothing!"; // look further for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; } } } */ // 1st : find and delete old element bool found = false; ret = myMap.equal_range(neighborNodes[i]->distAndEst); if ((ret.first == ret.second)) { /*+++++++++++++ no exact match +++++++++++++*/ // MITK_INFO << "No exact match!"; // if this happens, you are screwed /* MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; if (ret.first != myMap.end() ) { it = ret.first; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; ++it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; --it; --it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; } // look if that ID is found in the map for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; } } */ } else { for (it = ret.first; it != ret.second; ++it) { if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) { found = true; myMap.erase(it); /* MITK_INFO << "INFO: size " << myMap.size(); MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ break; } } } if (!found) { // MITK_INFO << "Could not find it! :("; continue; } // 2nd: update and insert new element neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; // myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); // MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << // m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; // MITK_INFO << "INFO: size " << myMap.size(); /*for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; }*/ } } } // finished with checking all neighbors. // Check Timeout, if activated if (m_ActivateTimeOut) { stopAll = clock(); durationAll = (double)(stopAll - startAll) / CLOCKS_PER_SEC; if (durationAll >= 30) { // MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; timeout = true; } } // Check end criteria: // For multiple points if (multipleEndPoints) { // super slow, make it faster for (unsigned int i = 0; i < m_endPoints.size(); i++) { if (CoordToNode(m_endPoints[i]) == mainNodeListIndex) { m_endPointsClosed.push_back(NodeToCoord(mainNodeListIndex)); m_endPoints.erase(m_endPoints.begin() + i); if (m_endPoints.empty()) { // Finished! break return; } if (m_Graph_EndNode == mainNodeListIndex) { // set new end SetEndIndex(m_endPoints[0]); } } } } // if single end point, then end, if this one is reached or timeout happened. else if ((mainNodeListIndex == m_Graph_EndNode || timeout) && !m_CalcAllDistances) { /*if (m_StoreVectorOrder) MITK_INFO << "Number of Nodes checked: " << m_VectorOrder.size() ;*/ return; } } } template void ShortestPathImageFilter::MakeOutputs() { // MITK_INFO << "Make Output"; if (m_MakeOutputImage) { OutputImagePointer output0 = this->GetOutput(0); output0->SetRegions(this->GetInput()->GetLargestPossibleRegion()); output0->Allocate(); OutputImageIteratorType shortestPathImageIt(output0, output0->GetRequestedRegion()); // Create ShortestPathImage (Output 0) for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) { // First intialize with background color shortestPathImageIt.Set(BACKGROUND); } if (!multipleEndPoints) // Only one path was calculated { for (unsigned int i = 0; i < m_VectorPath.size(); i++) { shortestPathImageIt.SetIndex(m_VectorPath[i]); shortestPathImageIt.Set(FOREGROUND); } } else // multiple pathes has been calculated, draw all { for (unsigned int i = 0; i < m_MultipleVectorPaths.size(); i++) { for (unsigned int j = 0; j < m_MultipleVectorPaths[i].size(); j++) { shortestPathImageIt.SetIndex(m_MultipleVectorPaths[i][j]); shortestPathImageIt.Set(FOREGROUND); } } } } } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetVectorOrderImage() { // Create Vector Order Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); OutputImageIteratorType vectorOrderImageIt(image, image->GetRequestedRegion()); // MITK_INFO << "GetVectorOrderImage"; for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) { // First intialize with background color vectorOrderImageIt.Value() = BACKGROUND; } for (int i = 0; i < m_VectorOrder.size(); i++) { vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i])); vectorOrderImageIt.Set(BACKGROUND + i); } return image; } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetDistanceImage() { // Create Distance Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); ; OutputImageIteratorType distanceImageIt(image, image->GetRequestedRegion()); // Create Distance Image (Output 1) NodeNumType myNodeNum; for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) { IndexType index = distanceImageIt.GetIndex(); myNodeNum = CoordToNode(index); double newVal = m_Nodes[myNodeNum].distance; distanceImageIt.Set(newVal); } } template std::vector::IndexType> ShortestPathImageFilter::GetVectorPath() { return m_VectorPath; } template std::vector::IndexType>> ShortestPathImageFilter::GetMultipleVectorPaths() { return m_MultipleVectorPaths; } template void ShortestPathImageFilter::MakeShortestPathVector() { // MITK_INFO << "Make ShortestPath Vec"; + if (m_useCostFunction == false) + { + m_VectorPath.push_back(NodeToCoord(m_Graph_StartNode)); + m_VectorPath.push_back(NodeToCoord(m_Graph_EndNode)); + return; + } // single end point if (!multipleEndPoints) { // fill m_VectorPath with the Shortest Path m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = m_Graph_EndNode; while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); } // Multiple end end points and pathes else { for (unsigned int i = 0; i < m_endPointsClosed.size(); i++) { m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = CoordToNode(m_endPointsClosed[i]); while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); // push_back m_MultipleVectorPaths.push_back(m_VectorPath); } } } template void ShortestPathImageFilter::CleanUp() { m_VectorOrder.clear(); m_VectorPath.clear(); // TODO: if multiple Path, clear all multiple Paths if (m_Nodes) delete[] m_Nodes; } template void ShortestPathImageFilter::GenerateData() { // Build Graph InitGraph(); // Calc Shortest Parth StartShortestPathSearch(); // Fill Shortest Path MakeShortestPathVector(); // Make Outputs MakeOutputs(); } template void ShortestPathImageFilter::PrintSelf(std::ostream &os, Indent indent) const { Superclass::PrintSelf(os, indent); } } /* end namespace itk */ #endif // __itkShortestPathImageFilter_txx diff --git a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h index b3c2f482d5..e8780e6a56 100644 --- a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h +++ b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h @@ -1,156 +1,158 @@ /*============================================================================ 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 _mitkImageLiveWireContourModelFilter_h__ #define _mitkImageLiveWireContourModelFilter_h__ #include "mitkCommon.h" #include "mitkContourModel.h" #include "mitkContourModelSource.h" #include #include #include #include #include #include namespace mitk { /** \brief Calculates a LiveWire contour between two points in an image. For defining costs between two pixels specific features are extraced from the image and tranformed into a single cost value. \sa ShortestPathCostFunctionLiveWire The filter is able to create dynamic cost tranfer map and thus use on the fly training. \note On the fly training will only be used for next update. The computation uses the last calculated segment to map cost according to features in the area of the segment. Caution: time support currently not available. Filter will always work on the first timestep in its current implementation. \ingroup ContourModelFilters \ingroup Process */ class MITKSEGMENTATION_EXPORT ImageLiveWireContourModelFilter : public ContourModelSource { public: mitkClassMacro(ImageLiveWireContourModelFilter, ContourModelSource); itkFactorylessNewMacro(Self); itkCloneMacro(Self); typedef ContourModel OutputType; typedef OutputType::Pointer OutputTypePointer; typedef mitk::Image InputType; typedef itk::Image InternalImageType; typedef itk::ShortestPathImageFilter ShortestPathImageFilterType; typedef itk::ShortestPathCostFunctionLiveWire CostFunctionType; typedef std::vector> ShortestPathType; /** \brief start point in world coordinates*/ itkSetMacro(StartPoint, mitk::Point3D); itkGetMacro(StartPoint, mitk::Point3D); /** \brief end point in woorld coordinates*/ itkSetMacro(EndPoint, mitk::Point3D); itkGetMacro(EndPoint, mitk::Point3D); /** \brief Create dynamic cost tranfer map - use on the fly training. \note On the fly training will be used for next update only. The computation uses the last calculated segment to map cost according to features in the area of the segment. */ itkSetMacro(UseDynamicCostMap, bool); itkGetMacro(UseDynamicCostMap, bool); /** \brief Clear all repulsive points used in the cost function */ void ClearRepulsivePoints(); /** \brief Set a vector with repulsive points to use in the cost function */ void SetRepulsivePoints(const ShortestPathType &points); /** \brief Add a single repulsive point to the cost function */ void AddRepulsivePoint(const itk::Index<2> &idx); /** \brief Remove a single repulsive point from the cost function */ void RemoveRepulsivePoint(const itk::Index<2> &idx); virtual void SetInput(const InputType *input); using Superclass::SetInput; virtual void SetInput(unsigned int idx, const InputType *input); const InputType *GetInput(void); const InputType *GetInput(unsigned int idx); virtual OutputType *GetOutput(); virtual void DumpMaskImage(); /** \brief Create dynamic cost tranfer map - on the fly training*/ bool CreateDynamicCostMap(mitk::ContourModel *path = nullptr); + void SetUseCostFunction(bool doUseCostFunction) { m_ShortestPathFilter->SetUseCostFunction(doUseCostFunction); }; + protected: ImageLiveWireContourModelFilter(); ~ImageLiveWireContourModelFilter() override; void GenerateOutputInformation() override{}; void GenerateData() override; void UpdateLiveWire(); /** \brief start point in worldcoordinates*/ mitk::Point3D m_StartPoint; /** \brief end point in woorldcoordinates*/ mitk::Point3D m_EndPoint; /** \brief Start point in index*/ mitk::Point3D m_StartPointInIndex; /** \brief End point in index*/ mitk::Point3D m_EndPointInIndex; /** \brief The cost function to compute costs between two pixels*/ CostFunctionType::Pointer m_CostFunction; /** \brief Shortest path filter according to cost function m_CostFunction*/ ShortestPathImageFilterType::Pointer m_ShortestPathFilter; /** \brief Flag to use a dynmic cost map or not*/ bool m_UseDynamicCostMap; unsigned int m_TimeStep; template void ItkPreProcessImage(const itk::Image *inputImage); template void CreateDynamicCostMapByITK(const itk::Image *inputImage, mitk::ContourModel *path = nullptr); InternalImageType::Pointer m_InternalImage; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp index e2e0e95b68..27e02d9986 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -1,485 +1,426 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkContourModelLiveWireInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include "mitkIOUtil.h" mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor() : ContourModelInteractor() { m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); - + m_LiveWireFilter->SetUseCostFunction(true); m_NextActiveVertexDown.Fill(0); m_NextActiveVertexUp.Fill(0); } mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() { } void mitk::ContourModelLiveWireInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); CONNECT_FUNCTION("finish", OnFinishEditing); } bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour"; return false; } contour->Deselect(); // Check distance to any vertex. // Transition YES if click close to a vertex mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timeStep)) - { - contour->SetSelectedVertexAsControlPoint(false); - m_ContourLeft = mitk::ContourModel::New(); - // get coordinates of next active vertex downwards from selected vertex - int downIndex = this->SplitContourFromSelectedVertex(contour, m_ContourLeft, false, timeStep); - - m_NextActiveVertexDownIter = contour->IteratorBegin() + downIndex; - m_NextActiveVertexDown = (*m_NextActiveVertexDownIter)->Coordinates; - - m_ContourRight = mitk::ContourModel::New(); - - // get coordinates of next active vertex upwards from selected vertex - int upIndex = this->SplitContourFromSelectedVertex(contour, m_ContourRight, true, timeStep); - - m_NextActiveVertexUpIter = contour->IteratorBegin() + upIndex; - m_NextActiveVertexUp = (*m_NextActiveVertexUpIter)->Coordinates; + bool isVertexSelected = false; + // Check, if clicked position is close to control vertex and if so, select closest control vertex. + isVertexSelected = contour->SelectControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - // clear previous void positions - this->m_LiveWireFilter->ClearRepulsivePoints(); + // If the position is not close to control vertex. but hovering the contour line, we check, if it is close to non-control vertex. + // The closest vertex will be set as a control vertex. + if (isVertexSelected == false) + isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - // set the current contour as void positions in the cost map - // start with down side - auto iter = contour->IteratorBegin(timeStep); - for (; iter != m_NextActiveVertexDownIter; iter++) + // If the position is not close to control or non-control vertex. but hovering the contour line, we create a vertex at the position. + if (isVertexSelected == false) + { + bool isHover = false; + if (this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()) == false) { - itk::Index<2> idx; - this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); - this->m_LiveWireFilter->AddRepulsivePoint(idx); + MITK_WARN << "Unknown property contour.hovering"; } - - // continue with upper side - iter = m_NextActiveVertexUpIter + 1; - for (; iter != contour->IteratorEnd(timeStep); iter++) + if (isHover) { - itk::Index<2> idx; - this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); - this->m_LiveWireFilter->AddRepulsivePoint(idx); + contour->AddVertex(click, timeStep); + isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); } + } + + if (isVertexSelected) + { + contour->SetSelectedVertexAsControlPoint(true); + auto controlVertices = contour->GetControlVertices(timeStep); + const mitk::ContourModel::VertexType *nextPoint = contour->GetNextControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + const mitk::ContourModel::VertexType *previousPoint = contour->GetPreviousControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + this->SplitContourFromSelectedVertex(contour, nextPoint, previousPoint, timeStep); + m_NextActiveVertexUp = nextPoint->Coordinates; + m_NextActiveVertexDown = previousPoint->Coordinates; + + // clear previous void positions + this->m_LiveWireFilter->ClearRepulsivePoints(); + // all points in lower and upper part should be marked as repulsive points to not be changed + this->SetRepulsivePoints(previousPoint, m_ContourLeft, timeStep); + this->SetRepulsivePoints(nextPoint, m_ContourRight, timeStep); // clear container with void points between neighboring control points m_ContourBeingModified.clear(); - // let us have the selected point as a control point - contour->SetSelectedVertexAsControlPoint(true); - // finally, return true to pass this condition return true; } else { // do not pass condition return false; } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); return true; } void mitk::ContourModelLiveWireInteractor::SetEditingContourModelNode(mitk::DataNode *_arg) { if (this->m_EditingContourNode != _arg) { this->m_EditingContourNode = _arg; } } void mitk::ContourModelLiveWireInteractor::SetWorkingImage(mitk::Image *_arg) { if (this->m_WorkingSlice != _arg) { this->m_WorkingSlice = _arg; this->m_LiveWireFilter->SetInput(this->m_WorkingSlice); } } void mitk::ContourModelLiveWireInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour!"; return; } if (contour->GetSelectedVertex()) { mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); newContour->Concatenate(m_ContourLeft, timeStep); // recompute contour between neighbored two active control points this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(this->m_NextActiveVertexUp); - // this->m_LiveWireFilter->ClearRepulsivePoints(); this->m_LiveWireFilter->Update(); mitk::ContourModel *liveWireContour = this->m_LiveWireFilter->GetOutput(); assert(liveWireContour); if (liveWireContour->IsEmpty(timeStep)) return; liveWireContour->RemoveVertexAt(0, timeStep); liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timeStep) - 1, timeStep); // insert new live wire computed points newContour->Concatenate(liveWireContour, timeStep); // insert right side of original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); // instead of leaving a single point, delete all points if (newContour->GetNumberOfVertices(timeStep) <= 2) { newContour->Clear(timeStep); } this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } void mitk::ContourModelLiveWireInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "invalid contour"; return; } mitk::ContourModel::Pointer editingContour = mitk::ContourModel::New(); editingContour->Expand(contour->GetTimeSteps()); editingContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // recompute left live wire, i.e. the contour between previous active vertex and selected vertex this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(currentPosition); // remove void positions between previous active vertex and next active vertex. if (!m_ContourBeingModified.empty()) { std::vector>::const_iterator iter = m_ContourBeingModified.begin(); for (; iter != m_ContourBeingModified.end(); iter++) { this->m_LiveWireFilter->RemoveRepulsivePoint((*iter)); } } // update to get the left livewire. Remember that the points in the rest of the contour are already // set as void positions in the filter this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer leftLiveWire = this->m_LiveWireFilter->GetOutput(); assert(leftLiveWire); if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(leftLiveWire, timeStep); // the new index of the selected vertex unsigned int selectedVertexIndex = this->m_ContourLeft->GetNumberOfVertices(timeStep) + leftLiveWire->GetNumberOfVertices(timeStep) - 1; // at this point the container has to be empty m_ContourBeingModified.clear(); // add points from left live wire contour auto iter = leftLiveWire->IteratorBegin(timeStep); for (; iter != leftLiveWire->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); // add indices m_ContourBeingModified.push_back(idx); } // recompute right live wire, i.e. the contour between selected vertex and next active vertex this->m_LiveWireFilter->SetStartPoint(currentPosition); this->m_LiveWireFilter->SetEndPoint(m_NextActiveVertexUp); // update filter with all contour points set as void but the right live wire portion to be calculated now this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer rightLiveWire = this->m_LiveWireFilter->GetOutput(); assert(rightLiveWire); // reject strange paths if (abs(rightLiveWire->GetNumberOfVertices(timeStep) - leftLiveWire->GetNumberOfVertices(timeStep)) > 50) { return; } if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timeStep); if (!rightLiveWire->IsEmpty(timeStep)) rightLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(rightLiveWire, timeStep); m_EditingContourNode->SetData(editingContour); mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // concatenate left original contour newContour->Concatenate(this->m_ContourLeft, timeStep); newContour->Concatenate(editingContour, timeStep, true); // set last inserted vertex as selected newContour->SelectVertexAt(selectedVertexIndex, timeStep); // set as control point newContour->SetSelectedVertexAsControlPoint(true); // concatenate right original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } bool mitk::ContourModelLiveWireInteractor::IsHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, 1.5, timeStep)) + if (contour->IsNearContour(currentPosition, mitk::ContourModelLiveWireInteractor::eps, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } + return true; } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } return false; } -int mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, - mitk::ContourModel *destContour, - bool fromSelectedUpwards, - int timestep) +void mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, + const mitk::ContourModel::VertexType *nextPoint, + const mitk::ContourModel::VertexType *previousPoint, + int timeStep) { - auto end = srcContour->IteratorEnd(); - auto begin = srcContour->IteratorBegin(); + m_ContourLeft = mitk::ContourModel::New(); + m_ContourRight = mitk::ContourModel::New(); - // search next active control point to left and rigth and set as start and end point for filter - auto itSelected = begin; + auto it = srcContour->IteratorBegin(); + // part between nextPoint and end of Countour + bool upperPart = false; + // part between start of countour and previousPoint + bool lowerPart = true; - // move iterator to position - while ((*itSelected) != srcContour->GetSelectedVertex()) + // edge cases when point right before first control vertex is selected or first control vertex is selected + if (nextPoint == (*it) || srcContour->GetSelectedVertex() == (*it)) { - itSelected++; + upperPart = true; + lowerPart = false; + m_ContourLeft->AddVertex(previousPoint->Coordinates, previousPoint->IsControlPoint, timeStep); } - - // CASE search upwards for next control point - if (fromSelectedUpwards) + // if first control vertex is selected, move to next point before adding vertices to m_ContourRight + // otherwise, second line appears when moving the vertex + if (srcContour->GetSelectedVertex() == (*it)) { - auto itUp = itSelected; - - if (itUp != end) + while (*it != nextPoint) { - itUp++; // step once up otherwise the loop breaks immediately + it++; } + } - while (itUp != end && !((*itUp)->IsControlPoint)) + for (; it != srcContour->IteratorEnd(timeStep); it++) + { + // everything in lower part should be added to m_CountoutLeft + if (lowerPart) { - itUp++; + m_ContourLeft->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } - - auto it = itUp; - - if (itSelected != begin) + // start of "restricted area" where no vertex should be added to m_CountoutLeft or m_CountoutRight + if (*it == previousPoint) { - // copy the rest of the original contour - while (it != end) - { - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } + lowerPart = false; + upperPart = false; } - // else do not copy the contour - - // return the offset of iterator at one before next-vertex-upwards - if (itUp != begin) + // start of upperPart + if (*it == nextPoint) { - return std::distance(begin, itUp) - 1; + upperPart = true; } - else + // everything in upper part should be added to m_CountoutRight + if (upperPart) { - return std::distance(begin, itUp); + m_ContourRight->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } } - else // CASE search downwards for next control point - { - auto itDown = itSelected; - auto it = srcContour->IteratorBegin(); +} - if (itSelected != begin) - { - if (itDown != begin) - { - itDown--; // step once down otherwise the the loop breaks immediately - } - - while (itDown != begin && !((*itDown)->IsControlPoint)) - { - itDown--; - } - - if (it != end) // if not empty - { - // always add the first vertex - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - // copy from begin to itDown - while (it <= itDown) - { - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - } - else - { - // if selected vertex is the first element search from end of contour downwards - itDown = end; - itDown--; - while (!((*itDown)->IsControlPoint) && itDown != begin) - { - itDown--; - } - - // move one forward as we don't want the first control point - it++; - // move iterator to second control point - while ((it != end) && !((*it)->IsControlPoint)) - { - it++; - } - // copy from begin to itDown - while (it <= itDown) - { - // copy the contour from second control point to itDown - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - } - /* - //add vertex at itDown - it's not considered during while loop - if( it != begin && it != end) - { - //destContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); - } - */ - // return the offset of iterator at one after next-vertex-downwards - if (itDown != end) - { - return std::distance(begin, itDown); // + 1;//index of next vertex - } - else +void mitk::ContourModelLiveWireInteractor::SetRepulsivePoints(const mitk::ContourModel::VertexType *pointToExclude, + mitk::ContourModel *contour, + int timeStep) +{ + auto it = contour->IteratorBegin(); + for (; it != contour->IteratorEnd(timeStep); it++) + { + if (*it != pointToExclude) { - return std::distance(begin, itDown) - 1; + itk::Index<2> idx; + this->m_WorkingSlice->GetGeometry()->WorldToIndex((*it)->Coordinates, idx); + this->m_LiveWireFilter->AddRepulsivePoint(idx); } } } void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *editingContour = dynamic_cast(this->m_EditingContourNode->GetData()); editingContour->Clear(timeStep); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h index 5d437fa213..6352775048 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h @@ -1,84 +1,95 @@ /*============================================================================ 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; - int SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, - mitk::ContourModel *destContour, - bool fromSelectedUpwards, - int timestep); - + /// \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/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index 49c56b305e..e8d13314c3 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,620 +1,694 @@ /*============================================================================ 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_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::RemoveHelperObjects() { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_LiveWireContourNode.IsNotNull()) dataStorage->Remove(m_LiveWireContourNode); + if (m_ClosureContourNode.IsNotNull()) + dataStorage->Remove(m_ClosureContourNode); + if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::LiveWireTool2D::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; + m_ClosureContourNode = nullptr; + m_ClosureContour = nullptr; + m_ContourNode = nullptr; m_Contour = nullptr; } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourLiveWireInteraction(false); m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { - CONNECT_CONDITION("CheckContourClosed", OnCheckPoint); - 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 to 1 ms and the filter also resets its outputs. + m_LiveWireContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms + // to 1 ms and the filter also resets its outputs. m_LiveWireContourNode->SetData(this->m_LiveWireContour); + + 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()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); + m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); + + m_LiveWireFilterClosure->Update(); + this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::ConfirmSegmentation() { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); if (nullptr == contour || contour->IsEmpty()) continue; auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); if (finding == sliceInfos.end()) { auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); finding = std::prev(sliceInfos.end()); } //cast const away is OK in this case, because these are all slices created and manipulated //localy in this function call. And we want to keep the high constness of SliceInformation for //public interfaces. auto workingSlice = const_cast(finding->slice.GetPointer()); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); ContourModelUtils::FillContourInSlice( projectedContour, workingSlice, workingImage, activePixelValue); } this->WriteBackSegmentationResults(sliceInfos); } this->ClearSegmentation(); } void mitk::LiveWireTool2D::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, mitk::BaseData *data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } mitk::ContourModel::Pointer mitk::LiveWireTool2D::CreateNewContour() const { auto workingData = this->GetWorkingData(); if (nullptr == workingData) { this->InteractiveSegmentationBugMessage("Cannot create new contour. No valid working data is set. Application is in invalid state."); mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; } auto contour = ContourModel::New(); //generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); contour->SetTimeGeometry(contourTimeGeometry); return contour; } void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) { 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(false); + 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); // 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) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); + this->m_LiveWireFilterClosure->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour m_LiveWireContour->RemoveVertexAt(0); // Set last point as control point m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); // Merge contours m_Contour->Concatenate(m_LiveWireContour); // Clear the LiveWire contour and reset the corresponding DataNode m_LiveWireContour->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; + } + + //if (m_ClosureContour->IsEmpty()) + //{ + // m_ClosureContour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates); + // m_ClosureContour->AddVertex(positionEvent->GetPositionInWorld()); + //} + //else + //{ + // m_ClosureContour->SetVertexAt(1, positionEvent->GetPositionInWorld()); + //} + m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); + m_LiveWireFilterClosure->SetStartPoint(positionEvent->GetPositionInWorld()); + m_LiveWireFilterClosure->Update(); + this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); + m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); + m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; mitk::Point3D click = positionEvent->GetPositionInWorld(); mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction + m_Contour->Concatenate(m_ClosureContour); auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; - // Remove last control point added by double click - m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + + //m_Contour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates, false); + // Remove last control point added by double click, if double click was performed on first point + //if (OnCheckPoint(interactionEvent)) + // m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); + + // remove green connection between mouse position and start point + m_ClosureContour->Clear(); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); m_LiveWireFilter->SetUseDynamicCostMap(false); + m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); this->GetToolManager()->GetDataStorage()->Remove(m_LiveWireContourNode); m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_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_ContourNode); dataStorage->Remove(m_EditingContourNode); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_Contour = this->CreateNewContour(); m_ContourNode->SetData(m_Contour); this->ResetToStartState(); } else // Remove last segment from contour and reset LiveWire contour { m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); auto newContour = this->CreateNewContour(); auto begin = m_Contour->IteratorBegin(); // Iterate from last point to next active point auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); // Go at least one down if (newLast != begin) --newLast; // Search next active control point while (newLast != begin && !((*newLast)->IsControlPoint)) --newLast; // Set position of start point for LiveWire filter to coordinates of the new last point m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); + //m_LiveWireFilterClosure->SetStartPoint((*newLast)->Coordinates); auto it = m_Contour->IteratorBegin(); // Fll new Contour while (it <= newLast) { newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); ++it; } newContour->SetClosed(m_Contour->IsClosed()); m_ContourNode->SetData(newContour); m_Contour = newContour; mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h index dff07f7a0b..33e85d700a 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,139 +1,143 @@ /*============================================================================ 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 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 { 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(); protected: LiveWireTool2D(); ~LiveWireTool2D() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; void UpdateLiveWireContour(); void OnTimePointChanged() override; private: /// \brief Initialize tool. void OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Actual LiveWire computation. void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Check double click on first control point to finish the LiveWire tool. bool OnCheckPoint(const InteractionEvent *interactionEvent); /// \brief Finish LiveWire tool. void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Close the contour. void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Don't use dynamic cost map for LiveWire calculation. void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. void FinishTool(); void EnableContourLiveWireInteraction(bool on); bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); void ReleaseInteractors(); void ReleaseHelperObjects(); void RemoveHelperObjects(); template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); ContourModel::Pointer CreateNewContour() const; 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; + mitk::ContourModel::Pointer m_EditingContour; mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModelLiveWireInteractor::Pointer m_ContourInteractor; /** Slice of the reference data the tool is currently actively working on to define contours.*/ mitk::Image::Pointer m_ReferenceDataSlice; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; + mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilterClosure; bool m_CreateAndUseDynamicCosts; std::vector> m_WorkingContours; std::vector> m_EditingContours; std::vector m_LiveWireInteractors; PlaneGeometry::ConstPointer m_PlaneGeometry; }; } #endif diff --git a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml index e64b4c810b..27d02bc15d 100644 --- a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml +++ b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml @@ -1,34 +1,30 @@ - - - -