diff --git a/CMakeExternals/MITKData.cmake b/CMakeExternals/MITKData.cmake index bee95e81b3..ca7fee4d86 100644 --- a/CMakeExternals/MITKData.cmake +++ b/CMakeExternals/MITKData.cmake @@ -1,36 +1,36 @@ #----------------------------------------------------------------------------- # MITK Data #----------------------------------------------------------------------------- # Sanity checks if(DEFINED MITK_DATA_DIR AND NOT EXISTS ${MITK_DATA_DIR}) message(FATAL_ERROR "MITK_DATA_DIR variable is defined but corresponds to non-existing directory") endif() set(proj MITK-Data) set(proj_DEPENDENCIES) set(MITK-Data_DEPENDS ${proj}) if(BUILD_TESTING) - set(revision_tag 3be15a2d) + set(revision_tag 81f5a871) # ^^^^^^^^ these are just to check correct length of hash part ExternalProject_Add(${proj} URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/MITK-Data_${revision_tag}.tar.gz UPDATE_COMMAND "" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" DEPENDS ${proj_DEPENDENCIES} ) set(MITK_DATA_DIR ${ep_source_dir}/${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif(BUILD_TESTING) diff --git a/Core/Code/Interactions/mitkEventRecorder.cpp b/Core/Code/Interactions/mitkEventRecorder.cpp index f14294a3fb..b260013615 100644 --- a/Core/Code/Interactions/mitkEventRecorder.cpp +++ b/Core/Code/Interactions/mitkEventRecorder.cpp @@ -1,163 +1,171 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkEventRecorder.h" #include "mitkEventFactory.h" #include "mitkInteractionEvent.h" #include "mitkInteractionEventConst.h" #include "mitkBaseRenderer.h" static void WriteEventXMLHeader(std::ofstream& stream) { stream << mitk::InteractionEventConst::xmlHead() << "\n"; } static void WriteEventXMLConfig(std::ofstream& stream) { // stream << " <" << mitk::InteractionEventConst::xmlTagConfigRoot() << ">\n"; //write renderer config //for all registered 2D renderers write name and viewdirection. mitk::BaseRenderer::BaseRendererMapType::iterator rendererIterator = mitk::BaseRenderer::baseRendererMap.begin(); mitk::BaseRenderer::BaseRendererMapType::iterator end = mitk::BaseRenderer::baseRendererMap.end(); for(; rendererIterator != end; rendererIterator++) { std::string rendererName = (*rendererIterator).second->GetName(); + mitk::SliceNavigationController::ViewDirection viewDirection = (*rendererIterator).second->GetSliceNavigationController()->GetDefaultViewDirection(); mitk::BaseRenderer::MapperSlotId mapperID = (*rendererIterator).second->GetMapperID(); - // - stream << " <" << mitk::InteractionEventConst::xmlTagRenderer() << " " << mitk::InteractionEventConst::xmlEventPropertyRendererName() << "=\"" << rendererName << "\" " << mitk::InteractionEventConst::xmlEventPropertyViewDirection() << "=\"" << viewDirection << "\" " << mitk::InteractionEventConst::xmlEventPropertyMapperID() << "=\"" << mapperID << "\"/>\n"; + // + stream << " <" << mitk::InteractionEventConst::xmlTagRenderer() << " " + << mitk::InteractionEventConst::xmlEventPropertyRendererName() << "=\""<< rendererName << "\" " + << mitk::InteractionEventConst::xmlEventPropertyViewDirection() << "=\""<< viewDirection << "\" " + << mitk::InteractionEventConst::xmlEventPropertyMapperID() << "=\"" << mapperID << "\" " + << mitk::InteractionEventConst::xmlRenderSizeX() << "=\"" << (*rendererIterator).second->GetSize()[0] << "\" " + << mitk::InteractionEventConst::xmlRenderSizeY() << "=\"" << (*rendererIterator).second->GetSize()[1] << "\" " + << mitk::InteractionEventConst::xmlRenderSizeZ() << "=\"" << (*rendererIterator).second->GetSize()[2] << "\" " + << "/>\n"; } // stream << " \n"; } static void WriteEventXMLEventsOpen(std::ofstream& stream) { stream << " <" << mitk::InteractionEventConst::xmlTagEvents() << ">\n"; } static void WriteEventXMLEventsClose(std::ofstream& stream) { stream << " \n"; } static void WriteEventXMLInteractionsOpen(std::ofstream& stream) { stream << "<" << mitk::InteractionEventConst::xmlTagInteractions() << ">\n"; } static void WriteEventXMLInteractionsClose(std::ofstream& stream) { stream << ""; } static void WriteEventXMLClose(std::ofstream& stream) { WriteEventXMLEventsClose(stream); WriteEventXMLInteractionsClose(stream); } mitk::EventRecorder::EventRecorder() : m_Active(false) { } mitk::EventRecorder::~EventRecorder() { if (m_FileStream.is_open()) { m_FileStream.flush(); m_FileStream.close(); } } void mitk::EventRecorder::Notify(mitk::InteractionEvent *interactionEvent, bool /*isHandled*/) { if (m_FileStream.is_open()) m_FileStream << EventFactory::EventToXML(interactionEvent) << "\n"; } void mitk::EventRecorder::SetEventIgnoreList(std::vector list) { m_IgnoreList = list; } void mitk::EventRecorder::StartRecording() { if (m_FileName == "") { MITK_ERROR << "EventRecorder::StartRecording - Filename needs to be set first."; return; } if (m_FileStream.is_open()) { MITK_ERROR << "EventRecorder::StartRecording - Still recording. Stop recording before starting it again."; return; } m_FileStream.open(m_FileName.c_str(), std::ofstream::out ); if ( !m_FileStream.good() ) { MITK_ERROR << "File " << m_FileName << " could not be opened!"; m_FileStream.close(); return; } m_Active = true; //write head and config // // // // // // ... // // WriteEventXMLHeader(m_FileStream); WriteEventXMLInteractionsOpen(m_FileStream); WriteEventXMLConfig(m_FileStream); WriteEventXMLEventsOpen(m_FileStream); } void mitk::EventRecorder::StopRecording() { if (m_FileStream.is_open()) { //write end tag // // WriteEventXMLClose(m_FileStream); m_FileStream.flush(); m_FileStream.close(); m_Active =false; } } diff --git a/Core/Code/Interactions/mitkInteractionEventConst.cpp b/Core/Code/Interactions/mitkInteractionEventConst.cpp index a61354c688..7615c30aaf 100644 --- a/Core/Code/Interactions/mitkInteractionEventConst.cpp +++ b/Core/Code/Interactions/mitkInteractionEventConst.cpp @@ -1,165 +1,183 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkInteractionEventConst.h" namespace mitk { const std::string InteractionEventConst::xmlHead() { static const std::string xmlHead = ""; return xmlHead; } const std::string InteractionEventConst::xmlTagConfigRoot() { static const std::string xmlTagConfigRoot = "config"; return xmlTagConfigRoot; } const std::string InteractionEventConst::xmlTagEvents() { static const std::string xmlTagEvents = "events"; return xmlTagEvents; } const std::string InteractionEventConst::xmlTagInteractions() { static const std::string xmlTagInteractions = "interactions"; return xmlTagInteractions; } const std::string InteractionEventConst::xmlTagRenderer() { static const std::string xmlTagRenderer = "renderer"; return xmlTagRenderer; } const std::string InteractionEventConst::xmlTagParam() { static const std::string xmlTagParam = "param"; return xmlTagParam; } const std::string InteractionEventConst::xmlTagEventVariant() { static const std::string xmlTagEventVariant = "event_variant"; return xmlTagEventVariant; } const std::string InteractionEventConst::xmlTagAttribute() { static const std::string xmlTagAttribute = "attribute"; return xmlTagAttribute; } const std::string InteractionEventConst::xmlParameterName() { static const std::string xmlParameterName = "name"; return xmlParameterName; } const std::string InteractionEventConst::xmlParameterValue() { static const std::string xmlParameterValue = "value"; return xmlParameterValue; } const std::string InteractionEventConst::xmlParameterEventVariant() { static const std::string xmlParameterEventVariant = "event_variant"; return xmlParameterEventVariant; } const std::string InteractionEventConst::xmlParameterEventClass() { static const std::string xmlParameterEventClass = "class"; return xmlParameterEventClass; } const std::string InteractionEventConst::xmlEventPropertyModifier() { static const std::string xmlEventPropertyModifier = "Modifiers"; return xmlEventPropertyModifier; } const std::string InteractionEventConst::xmlEventPropertyEventButton() { static const std::string xmlEventPropertyEventButton = "EventButton"; return xmlEventPropertyEventButton; } const std::string InteractionEventConst::xmlEventPropertyButtonState() { static const std::string xmlEventPropertyButtonState = "ButtonState"; return xmlEventPropertyButtonState; } const std::string InteractionEventConst::xmlEventPropertyPositionInWorld() { static const std::string xmlEventPropertyPosition = "PositionInWorld"; return xmlEventPropertyPosition; } const std::string InteractionEventConst::xmlEventPropertyPositionOnScreen() { static const std::string xmlEventPropertyPosition = "PositionOnScreen"; return xmlEventPropertyPosition; } const std::string InteractionEventConst::xmlEventPropertyKey() { static const std::string xmlEventPropertyKey = "Key"; return xmlEventPropertyKey; } const std::string InteractionEventConst::xmlEventPropertyScrollDirection() { static const std::string xmlEventPropertyScrollDirection = "ScrollDirection"; return xmlEventPropertyScrollDirection; } const std::string InteractionEventConst::xmlEventPropertyWheelDelta() { static const std::string xmlEventPropertyWheelDelta = "WheelDelta"; return xmlEventPropertyWheelDelta; } const std::string InteractionEventConst::xmlEventPropertySignalName() { static const std::string xmlEventPropertySignalName = "SignalName"; return xmlEventPropertySignalName; } const std::string InteractionEventConst::xmlEventPropertyRendererName() { static const std::string xmlEventPropertyRendererName = "RendererName"; return xmlEventPropertyRendererName; } const std::string InteractionEventConst::xmlEventPropertyViewDirection() { static const std::string xmlEventPropertyViewDirection = "ViewDirection"; return xmlEventPropertyViewDirection; } const std::string InteractionEventConst::xmlEventPropertyMapperID() { static const std::string xmlEventPropertyMapperID = "MapperID"; return xmlEventPropertyMapperID; } +const std::string mitk::InteractionEventConst::xmlRenderSizeX() +{ + static const std::string xmlSize = "RenderSizeX"; + return xmlSize; +} + +const std::string mitk::InteractionEventConst::xmlRenderSizeY() +{ + static const std::string xmlSize = "RenderSizeY"; + return xmlSize; +} + +const std::string mitk::InteractionEventConst::xmlRenderSizeZ() +{ + static const std::string xmlSize = "RenderSizeZ"; + return xmlSize; +} + } diff --git a/Core/Code/Interactions/mitkInteractionEventConst.h b/Core/Code/Interactions/mitkInteractionEventConst.h index 02589cac20..137aaf0552 100644 --- a/Core/Code/Interactions/mitkInteractionEventConst.h +++ b/Core/Code/Interactions/mitkInteractionEventConst.h @@ -1,64 +1,68 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKINTERACTEVENTCONST_H #define MITKINTERACTEVENTCONST_H #include #include namespace mitk { /** * @brief Constants to describe Mouse Events and special Key Events. */ struct MITK_CORE_EXPORT InteractionEventConst { static const std::string xmlHead(); // = ""; // XML Tags static const std::string xmlTagConfigRoot(); // = "config"; static const std::string xmlTagEvents(); // = "events"; static const std::string xmlTagInteractions(); // = "interactions"; static const std::string xmlTagParam(); // = "param"; static const std::string xmlTagEventVariant(); // = "event_variant"; static const std::string xmlTagAttribute(); // = "attribute"; static const std::string xmlTagRenderer(); // = "renderer"; // XML Param static const std::string xmlParameterName(); // = "name"; static const std::string xmlParameterValue(); // = "value"; static const std::string xmlParameterEventVariant(); // = "event_variant"; static const std::string xmlParameterEventClass(); // = "class"; // Event Description static const std::string xmlEventPropertyModifier(); // = "Modifiers"; static const std::string xmlEventPropertyEventButton(); // = "EventButton"; static const std::string xmlEventPropertyButtonState(); // = "ButtonState"; static const std::string xmlEventPropertyPositionInWorld(); // = "PositionInWorld"; static const std::string xmlEventPropertyPositionOnScreen(); // = "PositionOnScreen"; static const std::string xmlEventPropertyKey(); // = "Key"; static const std::string xmlEventPropertyScrollDirection(); // = "ScrollDirection"; static const std::string xmlEventPropertyWheelDelta(); // = "WheelDelta"; static const std::string xmlEventPropertySignalName(); // = "SignalName"; static const std::string xmlEventPropertyRendererName(); // = "RendererName"; static const std::string xmlEventPropertyViewDirection(); // = "ViewDirection"; static const std::string xmlEventPropertyMapperID(); // = "MapperID"; + + static const std::string xmlRenderSizeX(); // = "RenderSizeX"; + static const std::string xmlRenderSizeY(); // = "RenderSizeY"; + static const std::string xmlRenderSizeZ(); // = "RenderSizeZ"; }; } //namespace mitk #endif //ifndef MITKINTERACTEVENTCONST_H diff --git a/Core/Code/TestingHelper/mitkInteractionTestHelper.cpp b/Core/Code/TestingHelper/mitkInteractionTestHelper.cpp index 1e8759440f..d2c65d5136 100644 --- a/Core/Code/TestingHelper/mitkInteractionTestHelper.cpp +++ b/Core/Code/TestingHelper/mitkInteractionTestHelper.cpp @@ -1,220 +1,242 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //MITK #include #include #include #include #include //us #include #include mitk::InteractionTestHelper::InteractionTestHelper(const std::string &interactionXmlFilePath) : m_InteractionFilePath(interactionXmlFilePath) { this->Initialize(interactionXmlFilePath); } void mitk::InteractionTestHelper::Initialize(const std::string &interactionXmlFilePath) { //TiXmlDocument document(interactionXmlPath.c_str()); TiXmlDocument document(interactionXmlFilePath); bool loadOkay = document.LoadFile(); if (loadOkay) { // Global interaction must(!) be initialized if(! mitk::GlobalInteraction::GetInstance()->IsInitialized()) mitk::GlobalInteraction::GetInstance()->Initialize("global"); //get RenderingManager instance mitk::RenderingManager* rm = mitk::RenderingManager::GetInstance(); //create data storage m_DataStorage = mitk::StandaloneDataStorage::New(); //for each renderer found create a render window and configure for( TiXmlElement* element = document.FirstChildElement(mitk::InteractionEventConst::xmlTagInteractions())->FirstChildElement(mitk::InteractionEventConst::xmlTagConfigRoot())->FirstChildElement(mitk::InteractionEventConst::xmlTagRenderer()); element != NULL; element = element->NextSiblingElement(mitk::InteractionEventConst::xmlTagRenderer()) ) { //get name of renderer const char* rendererName = element->Attribute(mitk::InteractionEventConst::xmlEventPropertyRendererName().c_str()); //get view direction mitk::SliceNavigationController::ViewDirection viewDirection = mitk::SliceNavigationController::Axial; if(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection()) != NULL) { int viewDirectionNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection())->c_str()); viewDirection = static_cast(viewDirectionNum); } //get mapper slot id mitk::BaseRenderer::MapperSlotId mapperID = mitk::BaseRenderer::Standard2D; if(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID()) != NULL) { int mapperIDNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID())->c_str()); mapperID = static_cast(mapperIDNum); } + + // Get Size of Render Windows + int size[3]; + size[0]=size[1]=size[2]=0; + if(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX()) != NULL) + { + size[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX())->c_str()); + } + if(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY()) != NULL) + { + size[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY())->c_str()); + } + if(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ()) != NULL) + { + size[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ())->c_str()); + } + //create renderWindow, renderer and dispatcher mitk::RenderWindow::Pointer rw = mitk::RenderWindow::New(NULL, rendererName, rm); //VtkRenderWindow is created within constructor if NULL + if (size[0] != 0 && size[1] != 0) + rw->SetSize(size[0],size[1]); + //set storage of renderer rw->GetRenderer()->SetDataStorage(m_DataStorage); //set view direction to axial rw->GetSliceNavigationController()->SetDefaultViewDirection( viewDirection ); //set renderer to render 2D rw->GetRenderer()->SetMapperID(mapperID); //connect SliceNavigationControllers to timestep changed event of TimeNavigationController rw->GetSliceNavigationController()->ConnectGeometryTimeEvent(rm->GetTimeNavigationController(), false); rm->GetTimeNavigationController()->ConnectGeometryTimeEvent(rw->GetSliceNavigationController(), false); //add to list of kown render windows m_RenderWindowList.push_back(rw); } //TODO: check the following lines taken from QmitkStdMultiWidget and adapt them to be executed in our code here. // mitkWidget1->GetSliceNavigationController() // ->ConnectGeometrySendEvent(mitk::BaseRenderer::GetInstance(mitkWidget4->GetRenderWindow())); //########### register display interactor to handle scroll events ################## //use MouseModeSwitcher to ensure that the statemachine of DisplayInteractor is loaded correctly m_MouseModeSwitcher = mitk::MouseModeSwitcher::New(); } else { mitkThrow() << "Can not load interaction xml file <" << m_InteractionFilePath << ">"; } } mitk::InteractionTestHelper::~InteractionTestHelper() { mitk::RenderingManager* rm = mitk::RenderingManager::GetInstance(); //unregister renderers InteractionTestHelper::RenderWindowListType::iterator it = m_RenderWindowList.begin(); InteractionTestHelper::RenderWindowListType::iterator end = m_RenderWindowList.end(); for(; it != end; it++) { rm->GetTimeNavigationController()->Disconnect((*it)->GetSliceNavigationController()); (*it)->GetSliceNavigationController()->Disconnect(rm->GetTimeNavigationController()); mitk::BaseRenderer::RemoveInstance((*it)->GetVtkRenderWindow()); } rm->RemoveAllObservers(); } mitk::DataStorage::Pointer mitk::InteractionTestHelper::GetDataStorage() { return m_DataStorage; } void mitk::InteractionTestHelper::AddNodeToStorage(mitk::DataNode::Pointer node) { this->m_DataStorage->Add(node); mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); } void mitk::InteractionTestHelper::PlaybackInteraction() { //load events if not loaded yet if(m_Events.empty()) this->LoadInteraction(); //playback all events in queue for (unsigned long i=0; i < m_Events.size(); ++i) { //let dispatcher of sending renderer process the event m_Events.at(i)->GetSender()->GetDispatcher()->ProcessEvent(m_Events.at(i)); } } void mitk::InteractionTestHelper::LoadInteraction() { //load interaction pattern from xml file std::ifstream xmlStream(m_InteractionFilePath.c_str()); mitk::XML2EventParser parser(xmlStream); m_Events = parser.GetInteractions(); xmlStream.close(); + // Avoid VTK warning: Trying to delete object with non-zero reference count. + parser.SetReferenceCount(0); } void mitk::InteractionTestHelper::SetTimeStep(int newTimeStep) { bool timeStepIsvalid = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetCreatedWorldGeometry()->IsValidTimeStep(newTimeStep); if(timeStepIsvalid) { mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(newTimeStep); } } mitk::RenderWindow* mitk::InteractionTestHelper::GetRenderWindowByName(const std::string &name) { InteractionTestHelper::RenderWindowListType::iterator it = m_RenderWindowList.begin(); InteractionTestHelper::RenderWindowListType::iterator end = m_RenderWindowList.end(); for(; it != end; it++) { if( name.compare( (*it)->GetRenderer()->GetName() ) == 0) return (*it).GetPointer(); } return NULL; } mitk::RenderWindow* mitk::InteractionTestHelper::GetRenderWindowByDefaultViewDirection(mitk::SliceNavigationController::ViewDirection viewDirection) { InteractionTestHelper::RenderWindowListType::iterator it = m_RenderWindowList.begin(); InteractionTestHelper::RenderWindowListType::iterator end = m_RenderWindowList.end(); for(; it != end; it++) { if( viewDirection == (*it)->GetSliceNavigationController()->GetDefaultViewDirection() ) return (*it).GetPointer(); } return NULL; } mitk::RenderWindow* mitk::InteractionTestHelper::GetRenderWindow(unsigned int index) { if( index < m_RenderWindowList.size() ) { return m_RenderWindowList.at(index).GetPointer(); } else { return NULL; } } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.cpp index 338f9b7023..f559a6ffd7 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.cpp @@ -1,171 +1,184 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarAngle.h" #include "mitkPlaneGeometry.h" mitk::PlanarAngle::PlanarAngle() : FEATURE_ID_ANGLE( this->AddFeature( "Angle", "deg" ) ) { // Start with two control points this->ResetNumberOfControlPoints( 2 ); this->SetNumberOfPolyLines(1); this->SetNumberOfHelperPolyLines(1); m_HelperPolyLinesToBePainted->InsertElement( 0, false ); } mitk::PlanarAngle::~PlanarAngle() { } void mitk::PlanarAngle::GeneratePolyLine() { this->ClearPolyLines(); for ( unsigned int i=0; iGetNumberOfControlPoints(); i++ ) this->AppendPointToPolyLine(0, this->GetControlPoint(i)); } void mitk::PlanarAngle::GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) { // Generate helper-poly-line for angle if ( this->GetNumberOfControlPoints() < 3) { m_HelperPolyLinesToBePainted->SetElement(0, false); return; //We do not need to draw an angle as there are no two arms yet } this->ClearHelperPolyLines(); const Point2D centerPoint = this->GetControlPoint( 1 ); const Point2D boundaryPointOne = this->GetControlPoint( 0 ); const Point2D boundaryPointTwo = this->GetControlPoint( 2 ); double radius = centerPoint.EuclideanDistanceTo( boundaryPointOne ); if ( radius > centerPoint.EuclideanDistanceTo( boundaryPointTwo ) ) { radius = centerPoint.EuclideanDistanceTo( boundaryPointTwo ); } //Fixed size radius depending on screen size for the angle double nonScalingRadius = displayHeight * mmPerDisplayUnit * 0.05; if (nonScalingRadius > radius) { m_HelperPolyLinesToBePainted->SetElement(0, false); return; //if the arc has a radius that is longer than the shortest arm it should not be painted } m_HelperPolyLinesToBePainted->SetElement(0, true); radius = nonScalingRadius; double angle = this->GetQuantity( FEATURE_ID_ANGLE ); //Determine from which arm the angle should be drawn Vector2D v0 = boundaryPointOne - centerPoint; Vector2D v1 = boundaryPointTwo - centerPoint; Vector2D v2; v2[0] = 1.0; v2[1] = 0.0; v0[0] = v0[0] * cos( 0.001 ) - v0[1] * sin( 0.001 ); //rotate one arm a bit v0[1] = v0[0] * sin( 0.001 ) + v0[1] * cos( 0.001 ); v0.Normalize(); v1.Normalize(); double testAngle = acos( v0 * v1 ); //if the rotated arm is closer to the other arm than before it is the one from which we start drawing //else we start drawing from the other arm (we want to draw in the mathematically positive direction) if( angle > testAngle ) { v1[0] = v0[0] * cos( -0.001 ) - v0[1] * sin( -0.001 ); v1[1] = v0[0] * sin( -0.001 ) + v0[1] * cos( -0.001 ); //We determine if the arm is mathematically forward or backward //assuming we rotate between -pi and pi if ( acos( v0 * v2 ) > acos ( v1 * v2 )) { testAngle = acos( v1 * v2 ); } else { testAngle = -acos( v1 * v2 ); } } else { v0[0] = v1[0] * cos( -0.001 ) - v1[1] * sin( -0.001 ); v0[1] = v1[0] * sin( -0.001 ) + v1[1] * cos( -0.001 ); //We determine if the arm is mathematically forward or backward //assuming we rotate between -pi and pi if ( acos( v0 * v2 ) < acos ( v1 * v2 )) { testAngle = acos( v1 * v2 ); } else { testAngle = -acos( v1 * v2 ); } } // Generate poly-line with 16 segments for ( int t = 0; t < 16; ++t ) { double alpha = (double) t * angle / 15.0 + testAngle; Point2D polyLinePoint; polyLinePoint[0] = centerPoint[0] + radius * cos( alpha ); polyLinePoint[1] = centerPoint[1] + radius * sin( alpha ); this->AppendPointToHelperPolyLine(0, polyLinePoint); } } void mitk::PlanarAngle::EvaluateFeaturesInternal() { if ( this->GetNumberOfControlPoints() < 3 ) { // Angle not yet complete. return; } // Calculate angle between lines const Point2D &p0 = this->GetControlPoint( 0 ); const Point2D &p1 = this->GetControlPoint( 1 ); const Point2D &p2 = this->GetControlPoint( 2 ); Vector2D v0 = p1 - p0; Vector2D v1 = p1 - p2; v0.Normalize(); v1.Normalize(); double angle = acos( v0 * v1 ); this->SetQuantity( FEATURE_ID_ANGLE, angle ); } void mitk::PlanarAngle::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } + +bool mitk::PlanarAngle::Equals(const PlanarFigure &other) const +{ + const mitk::PlanarAngle* otherAngle = dynamic_cast(&other); + if ( otherAngle ) + { + return Superclass::Equals(other); + } + else + { + return false; + } +} diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.h b/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.h index 9ed5435fed..ab2a69c49c 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarAngle.h @@ -1,88 +1,90 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_ANGLE_H_ #define _MITK_PLANAR_ANGLE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure to display an angle * through three control points */ class MitkPlanarFigure_EXPORT PlanarAngle : public PlanarFigure { public: mitkClassMacro( PlanarAngle, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) public: // Feature identifiers const unsigned int FEATURE_ID_ANGLE; /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); /** \brief Angle has 3 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 3; } /** \brief Angle has 3 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 3; } + virtual bool Equals(const mitk::PlanarFigure& other) const; + protected: PlanarAngle(); virtual ~PlanarAngle(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; private: }; } // namespace mitk #endif //_MITK_PLANAR_ANGLE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.cpp index 2f8c2d0892..6185f0e685 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.cpp @@ -1,117 +1,132 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarArrow.h" #include "mitkPlaneGeometry.h" mitk::PlanarArrow::PlanarArrow() : FEATURE_ID_LENGTH( this->AddFeature( "Length", "mm" ) ) { // Directed arrow has two control points this->ResetNumberOfControlPoints( 2 ); m_ArrowTipScaleFactor = -1.0; this->SetNumberOfPolyLines( 1 ); this->SetNumberOfHelperPolyLines( 2 ); // Create helper polyline object (for drawing the orthogonal orientation line) m_HelperPolyLinesToBePainted->InsertElement( 0, false ); m_HelperPolyLinesToBePainted->InsertElement( 1, false ); } mitk::PlanarArrow::~PlanarArrow() { } void mitk::PlanarArrow::GeneratePolyLine() { this->ClearPolyLines(); this->AppendPointToPolyLine(0, this->GetControlPoint(0)); this->AppendPointToPolyLine(0, this->GetControlPoint(1)); } void mitk::PlanarArrow::GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) { // Generate helper polyline (orientation line orthogonal to first line) // if the third control point is currently being set if ( this->GetNumberOfControlPoints() != 2 ) { m_HelperPolyLinesToBePainted->SetElement( 0, false ); m_HelperPolyLinesToBePainted->SetElement( 1, false ); return; } this->ClearHelperPolyLines(); m_HelperPolyLinesToBePainted->SetElement( 0, true ); m_HelperPolyLinesToBePainted->SetElement( 1, true ); //Fixed size depending on screen size for the angle float scaleFactor = 0.015; if ( m_ArrowTipScaleFactor > 0.0 ) { scaleFactor = m_ArrowTipScaleFactor; } double nonScalingLength = displayHeight * mmPerDisplayUnit * scaleFactor; // Calculate arrow peak const Point2D p1 = this->GetControlPoint( 0 ); const Point2D p2 = this->GetControlPoint( 1 ); Vector2D n1 = p1 - p2; n1.Normalize(); double degrees = 100.0; Vector2D temp; temp[0] = n1[0] * cos(degrees) - n1[1] * sin(degrees); temp[1] = n1[0] * sin(degrees) + n1[1] * cos(degrees); Vector2D temp2; temp2[0] = n1[0] * cos(-degrees) - n1[1] * sin(-degrees); temp2[1] = n1[0] * sin(-degrees) + n1[1] * cos(-degrees); this->AppendPointToHelperPolyLine(0, p1); this->AppendPointToHelperPolyLine(0, Point2D(p1 - temp * nonScalingLength)); this->AppendPointToHelperPolyLine(1, p1); this->AppendPointToHelperPolyLine(1, Point2D(p1 - temp2 * nonScalingLength)); } void mitk::PlanarArrow::EvaluateFeaturesInternal() { // Calculate line length const Point3D &p0 = this->GetWorldControlPoint( 0 ); const Point3D &p1 = this->GetWorldControlPoint( 1 ); double length = p0.EuclideanDistanceTo( p1 ); this->SetQuantity( FEATURE_ID_LENGTH, length ); } - void mitk::PlanarArrow::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } void mitk::PlanarArrow::SetArrowTipScaleFactor( float scale ) { m_ArrowTipScaleFactor = scale; } + +bool mitk::PlanarArrow::Equals(mitk::PlanarFigure& other) +{ + mitk::PlanarArrow* otherArrow = dynamic_cast(&other); + if ( otherArrow ) + { + if ( std::abs(this->m_ArrowTipScaleFactor - otherArrow->m_ArrowTipScaleFactor) > mitk::eps) + return false; + + return Superclass::Equals(other); + } + else + { + return false; + } +} diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.h b/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.h index c0a517da91..9375c529f7 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarArrow.h @@ -1,97 +1,99 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_ARROW_H_ #define _MITK_PLANAR_ARROW_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing an arrow * through two control points */ class MitkPlanarFigure_EXPORT PlanarArrow : public PlanarFigure { public: mitkClassMacro( PlanarArrow, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); /** \brief Line has 2 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 2; } /** \brief Line has 2 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 2; } void SetArrowTipScaleFactor( float scale ); + virtual bool Equals(mitk::PlanarFigure& other); + protected: PlanarArrow(); virtual ~PlanarArrow(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; // Feature identifiers const unsigned int FEATURE_ID_LENGTH; // ScaleFactor defining size of helper-lines in relation to display size float m_ArrowTipScaleFactor; private: }; } // namespace mitk #endif //_MITK_PLANAR_ARROW_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.cpp index fe6e689199..cab3d12c23 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.cpp @@ -1,120 +1,136 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarBezierCurve.h" #include #include mitk::PlanarBezierCurve::PlanarBezierCurve() : FEATURE_ID_LENGTH(Superclass::AddFeature("Length", "mm")), m_NumberOfSegments(100) { this->ResetNumberOfControlPoints(2); this->SetNumberOfPolyLines(1); this->SetNumberOfHelperPolyLines(1); } mitk::PlanarBezierCurve::~PlanarBezierCurve() { } void mitk::PlanarBezierCurve::EvaluateFeaturesInternal() { double length = 0.0; for (unsigned int i = 0; i < m_NumberOfSegments; ++i) length += static_cast(m_PolyLines[0][i]).EuclideanDistanceTo(static_cast(m_PolyLines[0][i + 1])); this->SetQuantity(FEATURE_ID_LENGTH, length); } unsigned int mitk::PlanarBezierCurve::GetNumberOfSegments() const { return m_NumberOfSegments; } void mitk::PlanarBezierCurve::SetNumberOfSegments(unsigned int numSegments) { m_NumberOfSegments = std::max(1U, numSegments); if (this->IsPlaced()) { this->GeneratePolyLine(); this->Modified(); } } void mitk::PlanarBezierCurve::GenerateHelperPolyLine(double, unsigned int) { this->ClearHelperPolyLines(); unsigned int numHelperPolyLinePoints = m_ControlPoints.size(); for (unsigned int i = 0; i < numHelperPolyLinePoints; ++i) this->AppendPointToHelperPolyLine(0, m_ControlPoints[i]); } void mitk::PlanarBezierCurve::GeneratePolyLine() { this->ClearPolyLines(); const unsigned int numPolyLinePoints = m_NumberOfSegments + 1; for (unsigned int i = 0; i < numPolyLinePoints; ++i) this->AppendPointToPolyLine(0, this->ComputeDeCasteljauPoint(i / static_cast(m_NumberOfSegments))); } mitk::Point2D mitk::PlanarBezierCurve::ComputeDeCasteljauPoint(mitk::ScalarType t) { unsigned int n = m_ControlPoints.size() - 1; if (m_DeCasteljauPoints.size() != n) m_DeCasteljauPoints.resize(n); for (unsigned int i = 0; i < n; ++i) { m_DeCasteljauPoints[i][0] = (1 - t) * m_ControlPoints[i][0] + t * m_ControlPoints[i + 1][0]; m_DeCasteljauPoints[i][1] = (1 - t) * m_ControlPoints[i][1] + t * m_ControlPoints[i + 1][1]; } for (--n; n > 0; --n) { for (unsigned int i = 0; i < n; ++i) { m_DeCasteljauPoints[i][0] = (1 - t) * m_DeCasteljauPoints[i][0] + t * m_DeCasteljauPoints[i + 1][0]; m_DeCasteljauPoints[i][1] = (1 - t) * m_DeCasteljauPoints[i][1] + t * m_DeCasteljauPoints[i + 1][1]; } } return m_DeCasteljauPoints[0]; } unsigned int mitk::PlanarBezierCurve::GetMaximumNumberOfControlPoints() const { return std::numeric_limits::max(); } unsigned int mitk::PlanarBezierCurve::GetMinimumNumberOfControlPoints() const { return 2; } bool mitk::PlanarBezierCurve::IsHelperToBePainted(unsigned int index) { return index == 0 && m_ControlPoints.size() > 2; } +bool mitk::PlanarBezierCurve::Equals(const PlanarFigure &other) const +{ + const mitk::PlanarBezierCurve* otherBezierCurve = dynamic_cast(&other); + if ( otherBezierCurve ) + { + if( this->m_NumberOfSegments != otherBezierCurve->m_NumberOfSegments ) + return false; + if( this->m_DeCasteljauPoints != otherBezierCurve->m_DeCasteljauPoints ) + return false; + return Superclass::Equals(other); + } + else + { + return false; + } +} diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.h b/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.h index dd45e0f207..905c6fc1fe 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarBezierCurve.h @@ -1,60 +1,62 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkPlanarBezierCurve_h #define mitkPlanarBezierCurve_h #include #include namespace mitk { class MitkPlanarFigure_EXPORT PlanarBezierCurve : public PlanarFigure { public: mitkClassMacro(PlanarBezierCurve, PlanarFigure) itkFactorylessNewMacro(Self) itkCloneMacro(Self) unsigned int GetNumberOfSegments() const; void SetNumberOfSegments(unsigned int numSegments); virtual unsigned int GetMaximumNumberOfControlPoints() const; virtual unsigned int GetMinimumNumberOfControlPoints() const; virtual bool IsHelperToBePainted(unsigned int index); const unsigned int FEATURE_ID_LENGTH; + virtual bool Equals(const mitk::PlanarFigure& other)const; + protected: PlanarBezierCurve(); virtual ~PlanarBezierCurve(); mitkCloneMacro(Self) virtual void EvaluateFeaturesInternal(); virtual void GenerateHelperPolyLine(double, unsigned int); virtual void GeneratePolyLine(); private: Point2D ComputeDeCasteljauPoint(ScalarType t); std::vector m_DeCasteljauPoints; unsigned int m_NumberOfSegments; }; } #endif diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.cpp index 43b80d29e5..d6e248be95 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.cpp @@ -1,175 +1,188 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarCircle.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" mitk::PlanarCircle::PlanarCircle() : FEATURE_ID_RADIUS( this->AddFeature( "Radius", "mm" ) ), FEATURE_ID_DIAMETER( this->AddFeature( "Diameter", "mm" ) ), FEATURE_ID_AREA( this->AddFeature( "Area", "mm2" ) ), m_MinRadius(0), m_MaxRadius(100), m_MinMaxRadiusContraintsActive(false) { // Circle has two control points this->ResetNumberOfControlPoints( 2 ); this->SetNumberOfPolyLines( 1 ); this->SetProperty( "closed", mitk::BoolProperty::New(true) ); } mitk::PlanarCircle::~PlanarCircle() { } bool mitk::PlanarCircle::SetControlPoint( unsigned int index, const Point2D &point, bool /*createIfDoesNotExist*/ ) { // moving center point if(index == 0) { const Point2D ¢erPoint = GetControlPoint( 0 ); Point2D boundaryPoint = GetControlPoint( 1 ); vnl_vector vec = (point.GetVnlVector() - centerPoint.GetVnlVector()); boundaryPoint[0] += vec[0]; boundaryPoint[1] += vec[1]; PlanarFigure::SetControlPoint( 0, point ); PlanarFigure::SetControlPoint( 1, boundaryPoint ); return true; } else if ( index == 1 ) { PlanarFigure::SetControlPoint( index, point ); return true; } return false; } mitk::Point2D mitk::PlanarCircle::ApplyControlPointConstraints(unsigned int index, const Point2D &point) { if ( this->GetPlaneGeometry() == NULL ) { return point; } Point2D indexPoint; this->GetPlaneGeometry()->WorldToIndex( point, indexPoint ); BoundingBox::BoundsArrayType bounds = this->GetPlaneGeometry()->GetBounds(); if ( indexPoint[0] < bounds[0] ) { indexPoint[0] = bounds[0]; } if ( indexPoint[0] > bounds[1] ) { indexPoint[0] = bounds[1]; } if ( indexPoint[1] < bounds[2] ) { indexPoint[1] = bounds[2]; } if ( indexPoint[1] > bounds[3] ) { indexPoint[1] = bounds[3]; } Point2D constrainedPoint; this->GetPlaneGeometry()->IndexToWorld( indexPoint, constrainedPoint ); if(m_MinMaxRadiusContraintsActive) { if( index != 0) { const Point2D ¢erPoint = this->GetControlPoint(0); double euclideanDinstanceFromCenterToPoint1 = centerPoint.EuclideanDistanceTo(point); Vector2D vectorProjectedPoint; vectorProjectedPoint = point - centerPoint; vectorProjectedPoint.Normalize(); if( euclideanDinstanceFromCenterToPoint1 > m_MaxRadius ) { vectorProjectedPoint *= m_MaxRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } else if( euclideanDinstanceFromCenterToPoint1 < m_MinRadius ) { vectorProjectedPoint *= m_MinRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } } } return constrainedPoint; } void mitk::PlanarCircle::GeneratePolyLine() { // TODO: start circle at specified boundary point... // clear the PolyLine-Contrainer, it will be reconstructed soon enough... this->ClearPolyLines(); const Point2D ¢erPoint = GetControlPoint( 0 ); const Point2D &boundaryPoint = GetControlPoint( 1 ); double radius = centerPoint.EuclideanDistanceTo( boundaryPoint ); // Generate poly-line with 64 segments for ( int t = 0; t < 64; ++t ) { double alpha = (double) t * vnl_math::pi / 32.0; // construct the new polyline point ... Point2D polyLinePoint; polyLinePoint[0] = centerPoint[0] + radius * cos( alpha ); polyLinePoint[1] = centerPoint[1] + radius * sin( alpha ); // ... and append it to the PolyLine. // No extending supported here, so we can set the index of the PolyLineElement to '0' this->AppendPointToPolyLine(0, polyLinePoint); } } void mitk::PlanarCircle::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A circle does not require a helper object } void mitk::PlanarCircle::EvaluateFeaturesInternal() { // Calculate circle radius and area const Point3D &p0 = this->GetWorldControlPoint( 0 ); const Point3D &p1 = this->GetWorldControlPoint( 1 ); double radius = p0.EuclideanDistanceTo( p1 ); double area = vnl_math::pi * radius * radius; this->SetQuantity( FEATURE_ID_RADIUS, radius ); this->SetQuantity( FEATURE_ID_DIAMETER, 2*radius ); this->SetQuantity( FEATURE_ID_AREA, area ); } void mitk::PlanarCircle::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } bool mitk::PlanarCircle::SetCurrentControlPoint( const Point2D& point ) { if ( m_SelectedControlPoint < 0 ) { m_SelectedControlPoint = 1; } return this->SetControlPoint( m_SelectedControlPoint, point, false); } + + bool mitk::PlanarCircle::Equals(const PlanarFigure &other) const + { + const mitk::PlanarCircle* otherCircle = dynamic_cast(&other); + if ( otherCircle ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.h b/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.h index 2839b76f90..8e40da1975 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarCircle.h @@ -1,138 +1,139 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_CIRCLE_H_ #define _MITK_PLANAR_CIRCLE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a circle * through two control points */ class MitkPlanarFigure_EXPORT PlanarCircle : public PlanarFigure { public: mitkClassMacro( PlanarCircle, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); bool SetControlPoint( unsigned int index, const Point2D &point, bool createIfDoesNotExist = false ); /** \brief Circle has 2 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 2; } /** \brief Circle has 2 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 2; } /** \brief Sets the minimum radius */ void SetMinimumRadius( double radius ) { m_MinRadius = radius; } /** \brief Gets the minimum radius */ double GetMinimumRadius() { return m_MinRadius; } /** \brief Sets the maximum radius */ void SetMaximumRadius( double radius ) { m_MaxRadius = radius; } /** \brief Gets the minimum radius */ double GetMaximumRadius() { return m_MaxRadius; } void ActivateMinMaxRadiusContstraints( bool active ) { m_MinMaxRadiusContraintsActive = active; } virtual bool SetCurrentControlPoint( const Point2D& point ); + virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarCircle(); virtual ~PlanarCircle(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Spatially constrain control points of second (orthogonal) line */ virtual Point2D ApplyControlPointConstraints( unsigned int index, const Point2D& point ); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; // Feature identifiers const unsigned int FEATURE_ID_RADIUS; const unsigned int FEATURE_ID_DIAMETER; const unsigned int FEATURE_ID_AREA; //Member variables: double m_MinRadius; double m_MaxRadius; bool m_MinMaxRadiusContraintsActive; private: }; } // namespace mitk #endif //_MITK_PLANAR_CIRCLE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarCross.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarCross.cpp index be15c91a58..7ed28a6cc3 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarCross.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarCross.cpp @@ -1,344 +1,356 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarCross.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" mitk::PlanarCross::PlanarCross() : FEATURE_ID_LONGESTDIAMETER( this->AddFeature( "Longest Axis", "mm" ) ), FEATURE_ID_SHORTAXISDIAMETER( this->AddFeature( "Short Axis", "mm" ) ) { // Cross has two control points at the beginning this->ResetNumberOfControlPoints( 2 ); // Create property for SingleLineMode (default: false) this->SetProperty( "SingleLineMode", mitk::BoolProperty::New( false ) ); // Create helper polyline object (for drawing the orthogonal orientation line) this->SetNumberOfHelperPolyLines( 1 ); m_HelperPolyLinesToBePainted->InsertElement( 0, false ); } mitk::PlanarCross::~PlanarCross() { } void mitk::PlanarCross::SetSingleLineMode( bool singleLineMode ) { this->SetProperty( "SingleLineMode", mitk::BoolProperty::New( singleLineMode ) ); this->Modified(); } bool mitk::PlanarCross::GetSingleLineMode() const { mitk::BoolProperty* singleLineMode = dynamic_cast< mitk::BoolProperty* >( this->GetProperty( "SingleLineMode" ).GetPointer() ); if ( singleLineMode != NULL ) { return singleLineMode->GetValue(); } return false; } bool mitk::PlanarCross::ResetOnPointSelect() { if ( this->GetSingleLineMode() ) { // In single line mode --> nothing to reset return false; } switch ( m_SelectedControlPoint ) { default: // Nothing selected --> nothing to reset return false; case 0: { // Control point 0 selected: exchange points 0 and 1 Point2D tmpPoint = this->GetControlPoint( 0 ); this->SetControlPoint( 0, this->GetControlPoint( 1 ) ); this->SetControlPoint( 1, tmpPoint ); // FALLS THROUGH! } case 1: { // Control point 0 or 1 selected: reset number of control points to two this->ResetNumberOfControlPoints( 2 ); this->SelectControlPoint( 1 ); return true; } case 2: { // Control point 2 selected: replace point 0 with point 3 and point 1 with point 2 this->SetControlPoint( 0, this->GetControlPoint( 3 ) ); this->SetControlPoint( 1, this->GetControlPoint( 2 ) ); // Adjust selected control point, reset number of control points to two this->ResetNumberOfControlPoints( 2 ); this->SelectControlPoint( 1 ); return true; } case 3: { // Control point 3 selected: replace point 0 with point 2 and point 1 with point 3 this->SetControlPoint( 0, this->GetControlPoint( 2 ) ); this->SetControlPoint( 1, this->GetControlPoint( 3 ) ); // Adjust selected control point, reset number of control points to two this->ResetNumberOfControlPoints( 2 ); this->SelectControlPoint( 1 ); return true; } } } unsigned int mitk::PlanarCross::GetNumberOfFeatures() const { if ( this->GetSingleLineMode() || (this->GetNumberOfControlPoints() < 4) ) { return 1; } else { return 2; } } mitk::Point2D mitk::PlanarCross::ApplyControlPointConstraints( unsigned int index, const Point2D& point ) { // Apply spatial constraints from superclass and from this class until the resulting constrained // point converges. Although not an optimal implementation, this iterative approach // helps to respect both constraints from the superclass and from this class. Without this, // situations may occur where control points are constrained by the superclass, but again // moved out of the superclass bounds by the subclass, or vice versa. unsigned int count = 0; // ensures stop of approach if point does not converge in reasonable time Point2D confinedPoint = point; Point2D superclassConfinedPoint; do { superclassConfinedPoint = Superclass::ApplyControlPointConstraints( index, confinedPoint ); confinedPoint = this->InternalApplyControlPointConstraints( index, superclassConfinedPoint ); ++count; } while ( (confinedPoint.EuclideanDistanceTo( superclassConfinedPoint ) > mitk::eps) && (count < 32) ); return confinedPoint; } mitk::Point2D mitk::PlanarCross::InternalApplyControlPointConstraints( unsigned int index, const Point2D& point ) { // Apply constraints depending on current interaction state switch ( index ) { case 2: { // Check if 3rd control point is outside of the range (2D area) defined by the first // line (via the first two control points); if it is outside, clip it to the bounds const Point2D p1 = this->GetControlPoint( 0 ); const Point2D p2 = this->GetControlPoint( 1 ); Vector2D n1 = p2 - p1; n1.Normalize(); Vector2D v1 = point - p1; double dotProduct = n1 * v1; Point2D crossPoint = p1 + n1 * dotProduct;; Vector2D crossVector = point - crossPoint; if ( dotProduct < 0.0 ) { // Out-of-bounds on the left: clip point to left boundary return (p1 + crossVector); } else if ( dotProduct > p2.EuclideanDistanceTo( p1 ) ) { // Out-of-bounds on the right: clip point to right boundary return (p2 + crossVector); } else { // Pass back original point return point; } } case 3: { // Constrain 4th control point so that with the 3rd control point it forms // a line orthogonal to the first line (constraint 1); the 4th control point // must lie on the opposite side of the line defined by the first two control // points than the 3rd control point (constraint 2) const Point2D p1 = this->GetControlPoint( 0 ); const Point2D p2 = this->GetControlPoint( 1 ); const Point2D p3 = this->GetControlPoint( 2 ); // Calculate distance of original point from orthogonal line the corrected // point should lie on to project the point onto this line Vector2D n1 = p2 - p1; n1.Normalize(); Vector2D v1 = point - p3; double dotProduct1 = n1 * v1; Point2D pointOnLine = point - n1 * dotProduct1; // Project new point onto line [p1, p2] Vector2D v2 = pointOnLine - p1; double dotProduct2 = n1 * v2; Point2D crossingPoint = p1 + n1 * dotProduct2; // Determine whether the projected point on the line, or the crossing point should be // used (according to the second constraint in the comment above) if ( (pointOnLine.SquaredEuclideanDistanceTo( p3 ) > crossingPoint.SquaredEuclideanDistanceTo( p3 )) && (pointOnLine.SquaredEuclideanDistanceTo( p3 ) > pointOnLine.SquaredEuclideanDistanceTo( crossingPoint )) ) { return pointOnLine; } else { return crossingPoint; } } default: return point; } } void mitk::PlanarCross::GeneratePolyLine() { this->SetNumberOfPolyLines(1); this->ClearPolyLines(); if (this->GetNumberOfControlPoints() > 2) this->SetNumberOfPolyLines( 2 ); for (unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i) { if (i < 2) this->AppendPointToPolyLine(0, this->GetControlPoint(i)); if (i > 1) this->AppendPointToPolyLine(1, this->GetControlPoint(i)); } } void mitk::PlanarCross::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // Generate helper polyline (orientation line orthogonal to first line) // if the third control point is currently being set if ( this->GetNumberOfControlPoints() != 3 ) { m_HelperPolyLinesToBePainted->SetElement( 0, false ); return; } m_HelperPolyLinesToBePainted->SetElement( 0, true ); this->ClearHelperPolyLines(); // Calculate cross point of first line (p1 to p2) and orthogonal line through // the third control point (p3) const Point2D p1 = this->GetControlPoint( 0 ); const Point2D p2 = this->GetControlPoint( 1 ); const Point2D p3 = this->GetControlPoint( 2 ); Vector2D n1 = p2 - p1; n1.Normalize(); Vector2D v1 = p3 - p1; Point2D crossPoint = p1 + n1 * (n1 * v1); Vector2D v2 = crossPoint - p3; if ( v2.GetNorm() < 1.0 ) { // If third point is on the first line, draw orthogonal "infinite" line // through cross point on line Vector2D v0; v0[0] = n1[1]; v0[1] = -n1[0]; this->AppendPointToHelperPolyLine(0, Point2D(p3 - v0 * 10000.0)); this->AppendPointToHelperPolyLine(0, Point2D(p3 + v0 * 10000.0)); } else { // Else, draw orthogonal line starting from third point and crossing the // first line, open-ended only on the other side this->AppendPointToHelperPolyLine(0, p3); this->AppendPointToHelperPolyLine(0, Point2D(p3 + v2 * 10000.0)); } } void mitk::PlanarCross::EvaluateFeaturesInternal() { // Calculate length of first line const Point3D &p0 = this->GetWorldControlPoint( 0 ); const Point3D &p1 = this->GetWorldControlPoint( 1 ); double l1 = p0.EuclideanDistanceTo( p1 ); // Calculate length of second line double l2 = 0.0; if ( !this->GetSingleLineMode() && (this->GetNumberOfControlPoints() > 3) ) { const Point3D &p2 = this->GetWorldControlPoint( 2 ); const Point3D &p3 = this->GetWorldControlPoint( 3 ); l2 = p2.EuclideanDistanceTo( p3 ); } double longestDiameter; double shortAxisDiameter; if ( l1 > l2 ) { longestDiameter = l1; shortAxisDiameter = l2; } else { longestDiameter = l2; shortAxisDiameter = l1; } this->SetQuantity( FEATURE_ID_LONGESTDIAMETER, longestDiameter ); this->SetQuantity( FEATURE_ID_SHORTAXISDIAMETER, shortAxisDiameter ); } - void mitk::PlanarCross::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } + + bool mitk::PlanarCross::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarCross* otherCross = dynamic_cast(&other); + if ( otherCross ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarCross.h b/Modules/PlanarFigure/DataManagement/mitkPlanarCross.h index 217ed8c5ac..83a667e4df 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarCross.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarCross.h @@ -1,132 +1,133 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_CROSS_H_ #define _MITK_PLANAR_CROSS_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure modeling a cross with two orthogonal lines * on a plane. * * The cross consists of two two orthogonal lines, which are defined by four control points * lying on a plane. The two control points of the first line are freely placable within * the bounds of the underlying 2D geometry, while the two control points of the second line * are ensured to meet the following constraints: * * 1.) The lines must be orthogonal to each other * 2.) The second line must lie within the 2D area defined by the first line * 3.) The two lines must intersect (at least with their boundaries) * * When placing the second line interactively, a graphical helper polyline is provided to the * user to indicate the position and orthogonal orientation of the line if it would be placed * at the current mouse position. * * When modifying one of the lines by interactively moving its control points, the respective * other line is deleted and the user is prompted to draw it again. * * The class provide a special mode for drawing single lines (SingleLineModeOn/Off); in this * case, interaction stops after the first line has been placed. * * The class provides the lengths of both lines via the "feature" interface, ordered by size. * * \sa PlanarFigureMapper2D */ class MitkPlanarFigure_EXPORT PlanarCross : public PlanarFigure { public: mitkClassMacro( PlanarCross, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Indicates whether the PlanarFigure shall represent only a single line instead of an * orthogonal cross. */ void SetSingleLineMode( bool singleLineMode ); /** \brief Indicates whether the PlanarFigure shall represent only a single line instead of an * orthogonal cross. */ bool GetSingleLineMode() const; /** \brief Indicates whether the PlanarFigure shall represent only a single line instead of an * orthogonal cross. */ itkBooleanMacro( SingleLineMode ); // No need to reimplement; calls SetSingleLineMode() /** \brief PlanarCross has either two or four control points, depending on the operation mode. */ unsigned int GetMinimumNumberOfControlPoints() const { return this->GetSingleLineMode() ? 2 : 4; } /** \brief PlanarCross has either two or four control points, depending on the operation mode. */ unsigned int GetMaximumNumberOfControlPoints() const { return this->GetSingleLineMode() ? 2 : 4; } /** \brief The cross shall be reset to a single line when a control point is selected. */ virtual bool ResetOnPointSelect(); /** \brief Returns the number of features available for this PlanarCross (1 or 2). */ virtual unsigned int GetNumberOfFeatures() const; + virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarCross(); virtual ~PlanarCross(); mitkCloneMacro(Self); /** \brief Spatially constrain control points of second (orthogonal) line */ virtual Point2D ApplyControlPointConstraints( unsigned int index, const Point2D& point ); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; // Feature identifiers const unsigned int FEATURE_ID_LONGESTDIAMETER; const unsigned int FEATURE_ID_SHORTAXISDIAMETER; private: /** Internal method for applying spatial constraints. */ virtual Point2D InternalApplyControlPointConstraints( unsigned int index, const Point2D& point ); }; } // namespace mitk #endif //_MITK_PLANAR_CROSS_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.cpp index 68dfab03db..5068f00549 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.cpp @@ -1,251 +1,270 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarDoubleEllipse.h" #include #include mitk::PlanarDoubleEllipse::PlanarDoubleEllipse() : FEATURE_ID_MAJOR_AXIS(Superclass::AddFeature("Major Axis", "mm")), FEATURE_ID_MINOR_AXIS(Superclass::AddFeature("Minor Axis", "mm")), FEATURE_ID_THICKNESS(Superclass::AddFeature("Thickness", "mm")), m_NumberOfSegments(64), m_ConstrainCircle(true), m_ConstrainThickness(true) { this->ResetNumberOfControlPoints(4); this->SetNumberOfPolyLines(2); this->SetProperty("closed", mitk::BoolProperty::New(true)); } mitk::PlanarDoubleEllipse::~PlanarDoubleEllipse() { } mitk::Point2D mitk::PlanarDoubleEllipse::ApplyControlPointConstraints(unsigned int index, const Point2D& point) { if (index == 2 && !m_ConstrainCircle) { Point2D centerPoint = this->GetControlPoint(0); Vector2D outerMajorVector = this->GetControlPoint(1) - centerPoint; Vector2D minorDirection; minorDirection[0] = outerMajorVector[1]; minorDirection[1] = -outerMajorVector[0]; minorDirection.Normalize(); double outerMajorRadius = outerMajorVector.GetNorm(); double innerMajorRadius = (this->GetControlPoint(3) - centerPoint).GetNorm(); ScalarType radius = std::max(outerMajorRadius - innerMajorRadius, std::min(centerPoint.EuclideanDistanceTo(point), outerMajorRadius)); return centerPoint + minorDirection * radius; } else if (index == 3 && !m_ConstrainThickness) { Point2D centerPoint = this->GetControlPoint(0); Vector2D outerMajorVector = this->GetControlPoint(1) - centerPoint; double outerMajorRadius = outerMajorVector.GetNorm(); double outerMinorRadius = (this->GetControlPoint(2) - centerPoint).GetNorm(); ScalarType radius = std::max(outerMajorRadius - outerMinorRadius, std::min(centerPoint.EuclideanDistanceTo(point), outerMajorRadius)); outerMajorVector.Normalize(); return centerPoint - outerMajorVector * radius; } return point; } void mitk::PlanarDoubleEllipse::EvaluateFeaturesInternal() { Point2D centerPoint = this->GetControlPoint(0); ScalarType outerMajorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(1)); this->SetQuantity(FEATURE_ID_MAJOR_AXIS, 2 * outerMajorRadius); this->SetQuantity(FEATURE_ID_MINOR_AXIS, 2 * centerPoint.EuclideanDistanceTo(this->GetControlPoint(2))); this->SetQuantity(FEATURE_ID_THICKNESS, outerMajorRadius - centerPoint.EuclideanDistanceTo(this->GetControlPoint(3))); } void mitk::PlanarDoubleEllipse::GenerateHelperPolyLine(double, unsigned int) { } void mitk::PlanarDoubleEllipse::GeneratePolyLine() { this->ClearPolyLines(); Point2D centerPoint = this->GetControlPoint(0); Point2D outerMajorPoint = this->GetControlPoint(1); Vector2D direction = outerMajorPoint - centerPoint; direction.Normalize(); const ScalarType deltaAngle = vnl_math::pi / (m_NumberOfSegments / 2); int start = 0; int end = m_NumberOfSegments; if (direction[1] < 0.0) { direction[0] = -direction[0]; end = m_NumberOfSegments / 2; start = -end; } vnl_matrix_fixed rotation; rotation[1][0] = std::sin(std::acos(direction[0])); rotation[0][0] = direction[0]; rotation[1][1] = direction[0]; rotation[0][1] = -rotation[1][0]; ScalarType outerMajorRadius = centerPoint.EuclideanDistanceTo(outerMajorPoint); ScalarType outerMinorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(2)); ScalarType innerMajorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(3)); ScalarType innerMinorRadius = innerMajorRadius - (outerMajorRadius - outerMinorRadius); ScalarType angle; ScalarType cosAngle; ScalarType sinAngle; vnl_vector_fixed vector; Point2D point; for (int i = start; i < end; ++i) { angle = i * deltaAngle; cosAngle = std::cos(angle); sinAngle = std::sin(angle); vector[0] = outerMajorRadius * cosAngle; vector[1] = outerMinorRadius * sinAngle; vector = rotation * vector; point[0] = centerPoint[0] + vector[0]; point[1] = centerPoint[1] + vector[1]; this->AppendPointToPolyLine(0, point); vector[0] = innerMajorRadius * cosAngle; vector[1] = innerMinorRadius * sinAngle; vector = rotation * vector; point[0] = centerPoint[0] + vector[0]; point[1] = centerPoint[1] + vector[1]; this->AppendPointToPolyLine(1, point); } } unsigned int mitk::PlanarDoubleEllipse::GetNumberOfSegments() const { return m_NumberOfSegments; } void mitk::PlanarDoubleEllipse::SetNumberOfSegments(unsigned int numSegments) { m_NumberOfSegments = std::max(4U, numSegments); if (this->IsPlaced()) { this->GeneratePolyLine(); this->Modified(); } } unsigned int mitk::PlanarDoubleEllipse::GetMaximumNumberOfControlPoints() const { return 4; } unsigned int mitk::PlanarDoubleEllipse::GetMinimumNumberOfControlPoints() const { return 4; } bool mitk::PlanarDoubleEllipse::SetControlPoint(unsigned int index, const Point2D& point, bool createIfDoesNotExist) { switch (index) { case 0: { Point2D centerPoint = this->GetControlPoint(0); Vector2D vector = point - centerPoint; Superclass::SetControlPoint(0, point, createIfDoesNotExist); Superclass::SetControlPoint(1, this->GetControlPoint(1) + vector, createIfDoesNotExist); Superclass::SetControlPoint(2, this->GetControlPoint(2) + vector, createIfDoesNotExist); Superclass::SetControlPoint(3, this->GetControlPoint(3) + vector, createIfDoesNotExist); break; } case 1: { Vector2D vector = point - this->GetControlPoint(1); Superclass::SetControlPoint(1, point, createIfDoesNotExist); Point2D centerPoint = this->GetControlPoint(0); Vector2D outerMajorVector = point - centerPoint; Vector2D outerMinorVector; outerMinorVector[0] = outerMajorVector[1]; outerMinorVector[1] = -outerMajorVector[0]; if (!m_ConstrainCircle) { outerMinorVector.Normalize(); outerMinorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(2)); } Superclass::SetControlPoint(2, centerPoint + outerMinorVector, createIfDoesNotExist); Vector2D innerMajorVector = outerMajorVector; if (!m_ConstrainThickness) { innerMajorVector.Normalize(); innerMajorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(3) - vector); } Superclass::SetControlPoint(3, centerPoint - innerMajorVector, createIfDoesNotExist); break; } case 2: { m_ConstrainCircle = false; Superclass::SetControlPoint(2, point, createIfDoesNotExist); break; } case 3: { m_ConstrainThickness = false; Superclass::SetControlPoint(3, point, createIfDoesNotExist); break; } default: return false; } return true; } + + bool mitk::PlanarDoubleEllipse::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarDoubleEllipse* otherDoubleEllipse = dynamic_cast(&other); + if ( otherDoubleEllipse ) + { + if( this->m_ConstrainCircle != otherDoubleEllipse->m_ConstrainCircle) + return false; + if( this->m_ConstrainThickness != otherDoubleEllipse->m_ConstrainThickness) + return false; + if( this->m_NumberOfSegments != otherDoubleEllipse->m_NumberOfSegments) + return false; + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.h b/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.h index ff67568439..7f42148beb 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarDoubleEllipse.h @@ -1,61 +1,63 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkPlanarDoubleEllipse_h #define mitkPlanarDoubleEllipse_h #include #include namespace mitk { class MitkPlanarFigure_EXPORT PlanarDoubleEllipse : public PlanarFigure { public: mitkClassMacro(PlanarDoubleEllipse, PlanarFigure); itkFactorylessNewMacro(Self) itkCloneMacro(Self) unsigned int GetNumberOfSegments() const; void SetNumberOfSegments(unsigned int numSegments); virtual unsigned int GetMaximumNumberOfControlPoints() const; virtual unsigned int GetMinimumNumberOfControlPoints() const; virtual bool SetControlPoint(unsigned int index, const Point2D& point, bool createIfDoesNotExist = true); const unsigned int FEATURE_ID_MAJOR_AXIS; const unsigned int FEATURE_ID_MINOR_AXIS; const unsigned int FEATURE_ID_THICKNESS; + virtual bool Equals(const mitk::PlanarFigure& other) const; + protected: PlanarDoubleEllipse(); virtual ~PlanarDoubleEllipse(); mitkCloneMacro(Self) virtual mitk::Point2D ApplyControlPointConstraints(unsigned int index, const Point2D& point); virtual void EvaluateFeaturesInternal(); virtual void GenerateHelperPolyLine(double, unsigned int); virtual void GeneratePolyLine(); private: unsigned int m_NumberOfSegments; bool m_ConstrainCircle; bool m_ConstrainThickness; }; } #endif diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.cpp index 49ddb0f02d..443e89b7f7 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.cpp @@ -1,280 +1,296 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include "mitkPlanarEllipse.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" #include mitk::PlanarEllipse::PlanarEllipse() : FEATURE_ID_MAJOR_AXIS(Superclass::AddFeature("Major Axis", "mm")), FEATURE_ID_MINOR_AXIS(Superclass::AddFeature("Minor Axis", "mm")), m_MinRadius(0), m_MaxRadius(100), m_MinMaxRadiusContraintsActive(false), m_TreatAsCircle(true) { // Ellipse has three control points this->ResetNumberOfControlPoints( 4 ); this->SetNumberOfPolyLines( 2 ); this->SetProperty( "closed", mitk::BoolProperty::New(true) ); } mitk::PlanarEllipse::~PlanarEllipse() { } bool mitk::PlanarEllipse::SetControlPoint( unsigned int index, const Point2D &point, bool createIfDoesNotExist ) { if(index == 0) // moving center point and control points accordingly { const Point2D ¢erPoint = GetControlPoint( 0 ); Point2D boundaryPoint1 = GetControlPoint( 1 ); Point2D boundaryPoint2 = GetControlPoint( 2 ); Point2D boundaryPoint3 = GetControlPoint( 3 ); vnl_vector vec = (point.GetVnlVector() - centerPoint.GetVnlVector()); boundaryPoint1[0] += vec[0]; boundaryPoint1[1] += vec[1]; boundaryPoint2[0] += vec[0]; boundaryPoint2[1] += vec[1]; boundaryPoint3[0] += vec[0]; boundaryPoint3[1] += vec[1]; PlanarFigure::SetControlPoint( 0, point, createIfDoesNotExist ); PlanarFigure::SetControlPoint( 1, boundaryPoint1, createIfDoesNotExist ); PlanarFigure::SetControlPoint( 2, boundaryPoint2, createIfDoesNotExist ); PlanarFigure::SetControlPoint( 3, boundaryPoint3, createIfDoesNotExist ); return true; } else if (index < 3) { PlanarFigure::SetControlPoint( index, point, createIfDoesNotExist ); int otherIndex = index+1; if (otherIndex > 2) otherIndex = 1; const Point2D ¢erPoint = GetControlPoint( 0 ); Point2D otherPoint = GetControlPoint( otherIndex ); Point2D point3 = GetControlPoint( 3 ); Vector2D vec1 = point - centerPoint; Vector2D vec2; if (index == 1 && m_TreatAsCircle ) { float x = vec1[0]; vec2[0] = vec1[1]; vec2[1] = x; if (index==1) vec2[0] *= -1; else vec2[1] *= -1; otherPoint = centerPoint+vec2; PlanarFigure::SetControlPoint( otherIndex, otherPoint, createIfDoesNotExist ); float r = centerPoint.EuclideanDistanceTo(otherPoint); // adjust additional third control point Point2D p3 = this->GetControlPoint(3); Vector2D vec3; vec3[0] = p3[0]-centerPoint[0]; vec3[1] = p3[1]-centerPoint[1]; if (vec3[0]!=0 || vec3[1]!=0) { vec3.Normalize(); vec3 *= r; } else { vec3[0] = r; vec3[1] = 0; } point3 = centerPoint + vec3; PlanarFigure::SetControlPoint( 3, point3, createIfDoesNotExist ); } else if ( vec1.GetNorm() > 0 ) { float r = centerPoint.EuclideanDistanceTo(otherPoint); float x = vec1[0]; vec2[0] = vec1[1]; vec2[1] = x; if (index==1) vec2[0] *= -1; else vec2[1] *= -1; vec2.Normalize(); vec2 *= r; if ( vec2.GetNorm() > 0 ) { otherPoint = centerPoint+vec2; PlanarFigure::SetControlPoint( otherIndex, otherPoint, createIfDoesNotExist ); } // adjust third control point Vector2D vec3 = point3 - centerPoint; vec3.Normalize(); double r1 = centerPoint.EuclideanDistanceTo( GetControlPoint( 1 ) ); double r2 = centerPoint.EuclideanDistanceTo( GetControlPoint( 2 ) ); Point2D newPoint = centerPoint + vec3*std::max(r1, r2); PlanarFigure::SetControlPoint( 3, newPoint, createIfDoesNotExist ); m_TreatAsCircle = false; } return true; } else if (index == 3) { Point2D centerPoint = GetControlPoint( 0 ); Vector2D vec3 = point - centerPoint; vec3.Normalize(); double r1 = centerPoint.EuclideanDistanceTo( GetControlPoint( 1 ) ); double r2 = centerPoint.EuclideanDistanceTo( GetControlPoint( 2 ) ); Point2D newPoint = centerPoint + vec3*std::max(r1, r2); PlanarFigure::SetControlPoint( index, newPoint, createIfDoesNotExist ); m_TreatAsCircle = false; return true; } return false; } void mitk::PlanarEllipse::PlaceFigure( const mitk::Point2D &point ) { PlanarFigure::PlaceFigure( point ); m_SelectedControlPoint = 1; } mitk::Point2D mitk::PlanarEllipse::ApplyControlPointConstraints(unsigned int index, const Point2D &point) { return point; Point2D indexPoint; this->GetPlaneGeometry()->WorldToIndex( point, indexPoint ); BoundingBox::BoundsArrayType bounds = this->GetPlaneGeometry()->GetBounds(); if ( indexPoint[0] < bounds[0] ) { indexPoint[0] = bounds[0]; } if ( indexPoint[0] > bounds[1] ) { indexPoint[0] = bounds[1]; } if ( indexPoint[1] < bounds[2] ) { indexPoint[1] = bounds[2]; } if ( indexPoint[1] > bounds[3] ) { indexPoint[1] = bounds[3]; } Point2D constrainedPoint; this->GetPlaneGeometry()->IndexToWorld( indexPoint, constrainedPoint ); if(m_MinMaxRadiusContraintsActive) { if( index != 0) { const Point2D ¢erPoint = this->GetControlPoint(0); double euclideanDinstanceFromCenterToPoint1 = centerPoint.EuclideanDistanceTo(point); Vector2D vectorProjectedPoint; vectorProjectedPoint = point - centerPoint; vectorProjectedPoint.Normalize(); if( euclideanDinstanceFromCenterToPoint1 > m_MaxRadius ) { vectorProjectedPoint *= m_MaxRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } else if( euclideanDinstanceFromCenterToPoint1 < m_MinRadius ) { vectorProjectedPoint *= m_MinRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } } } return constrainedPoint; } void mitk::PlanarEllipse::GeneratePolyLine() { // clear the PolyLine-Contrainer, it will be reconstructed soon enough... this->ClearPolyLines(); const Point2D ¢erPoint = GetControlPoint( 0 ); const Point2D &boundaryPoint1 = GetControlPoint( 1 ); const Point2D &boundaryPoint2 = GetControlPoint( 2 ); Vector2D dir = boundaryPoint1 - centerPoint; dir.Normalize(); vnl_matrix_fixed rot; // differentiate between clockwise and counterclockwise rotation int start = 0; int end = 64; if (dir[1]<0) { dir[0] = -dir[0]; start = -32; end = 32; } // construct rotation matrix to align ellipse with control point vector rot[0][0] = dir[0]; rot[1][1] = rot[0][0]; rot[1][0] = sin(acos(rot[0][0])); rot[0][1] = -rot[1][0]; double radius1 = centerPoint.EuclideanDistanceTo( boundaryPoint1 ); double radius2 = centerPoint.EuclideanDistanceTo( boundaryPoint2 ); // Generate poly-line with 64 segments for ( int t = start; t < end; ++t ) { double alpha = (double) t * vnl_math::pi / 32.0; // construct the new polyline point ... vnl_vector_fixed< float, 2 > vec; vec[0] = radius1 * cos( alpha ); vec[1] = radius2 * sin( alpha ); vec = rot*vec; Point2D polyLinePoint; polyLinePoint[0] = centerPoint[0] + vec[0]; polyLinePoint[1] = centerPoint[1] + vec[1]; // ... and append it to the PolyLine. // No extending supported here, so we can set the index of the PolyLineElement to '0' this->AppendPointToPolyLine(0, polyLinePoint); } this->AppendPointToPolyLine(1, centerPoint); this->AppendPointToPolyLine(1, this->GetControlPoint(3)); } void mitk::PlanarEllipse::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A circle does not require a helper object } void mitk::PlanarEllipse::EvaluateFeaturesInternal() { Point2D centerPoint = this->GetControlPoint(0); this->SetQuantity(FEATURE_ID_MAJOR_AXIS, 2 * centerPoint.EuclideanDistanceTo(this->GetControlPoint(1))); this->SetQuantity(FEATURE_ID_MINOR_AXIS, 2 * centerPoint.EuclideanDistanceTo(this->GetControlPoint(2))); } void mitk::PlanarEllipse::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } + + bool mitk::PlanarEllipse::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarEllipse* otherEllipse = dynamic_cast(&other); + if ( otherEllipse ) + { + if(this->m_TreatAsCircle != otherEllipse->m_TreatAsCircle) + return false; + + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.h b/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.h index ea900bf835..5654e737cb 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarEllipse.h @@ -1,140 +1,142 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_ELLIPSE_H_ #define _MITK_PLANAR_ELLIPSE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a circle * through two control points */ class MitkPlanarFigure_EXPORT PlanarEllipse : public PlanarFigure { public: mitkClassMacro( PlanarEllipse, PlanarFigure ) itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ virtual void PlaceFigure( const Point2D &point ); bool SetControlPoint( unsigned int index, const Point2D &point, bool createIfDoesNotExist = true ); /** \brief Ellipse has 3 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 4; } /** \brief Ellipse has 3 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 4; } /** \brief Sets the minimum radius */ void SetMinimumRadius( double radius ) { m_MinRadius = radius; } /** \brief Gets the minimum radius */ double GetMinimumRadius() { return m_MinRadius; } /** \brief Sets the maximum radius */ void SetMaximumRadius( double radius ) { m_MaxRadius = radius; } /** \brief Gets the minimum radius */ double GetMaximumRadius() { return m_MaxRadius; } void ActivateMinMaxRadiusContstraints( bool active ) { m_MinMaxRadiusContraintsActive = active; } /** \brief Treat ellipse as circle (equal radii) */ void SetTreatAsCircle( bool active ) { m_TreatAsCircle = active; } + virtual bool Equals(const mitk::PlanarFigure& other) const; + const unsigned int FEATURE_ID_MAJOR_AXIS; const unsigned int FEATURE_ID_MINOR_AXIS; protected: PlanarEllipse(); virtual ~PlanarEllipse(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Spatially constrain control points of second (orthogonal) line */ virtual Point2D ApplyControlPointConstraints( unsigned int index, const Point2D& point ); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; //Member variables: double m_MinRadius; double m_MaxRadius; bool m_MinMaxRadiusContraintsActive; bool m_TreatAsCircle; private: }; } // namespace mitk #endif //_MITK_PLANAR_ELLIPSE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp index 2b4fdbbbb0..5d69c365d6 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp @@ -1,780 +1,895 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarFigure.h" #include "mitkPlaneGeometry.h" #include #include #include #ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #elif __clang__ # pragma clang diagnostic ignored "-Wdeprecated-declarations" #elif _MSC_VER # pragma warning (push) # pragma warning (disable: 4996) #endif mitk::PlanarFigure::PolyLineElement::PolyLineElement(Point2D point, int index) : Point(point), Index(index) { } mitk::PlanarFigure::PolyLineElement::PolyLineElement(const Point2D& point) : Point(point), Index(-1) { } mitk::PlanarFigure::PolyLineElement::PolyLineElement(const PolyLineElement &other) : Point(other.Point), Index(other.Index) { } mitk::PlanarFigure::PolyLineElement& mitk::PlanarFigure::PolyLineElement::operator=(const PolyLineElement &other) { if (this != &other) { Point = other.Point; Index = other.Index; } return *this; } mitk::PlanarFigure::PolyLineElement::operator mitk::Point2D&() { return Point; } mitk::PlanarFigure::PolyLineElement::operator const mitk::Point2D&() const { return Point; } mitk::PlanarFigure::PlanarFigure() : m_SelectedControlPoint( -1 ), m_PreviewControlPointVisible( false ), m_FigurePlaced( false ), m_PlaneGeometry( NULL ), m_PolyLineUpToDate(false), m_HelperLinesUpToDate(false), m_FeaturesUpToDate(false), m_FeaturesMTime( 0 ) { m_HelperPolyLinesToBePainted = BoolContainerType::New(); m_DisplaySize.first = 0.0; m_DisplaySize.second = 0; this->SetProperty( "closed", mitk::BoolProperty::New( false ) ); // Currently only single-time-step geometries are supported this->InitializeTimeGeometry( 1 ); } mitk::PlanarFigure::~PlanarFigure() { } mitk::PlanarFigure::PlanarFigure(const Self& other) : BaseData(other), m_ControlPoints(other.m_ControlPoints), m_NumberOfControlPoints(other.m_NumberOfControlPoints), m_SelectedControlPoint(other.m_SelectedControlPoint), m_PolyLines(other.m_PolyLines), m_HelperPolyLines(other.m_HelperPolyLines), m_HelperPolyLinesToBePainted(other.m_HelperPolyLinesToBePainted->Clone()), m_PreviewControlPoint(other.m_PreviewControlPoint), m_PreviewControlPointVisible(other.m_PreviewControlPointVisible), m_FigurePlaced(other.m_FigurePlaced), m_PlaneGeometry(other.m_PlaneGeometry), // do not clone since SetPlaneGeometry() doesn't clone either m_PolyLineUpToDate(other.m_PolyLineUpToDate), m_HelperLinesUpToDate(other.m_HelperLinesUpToDate), m_FeaturesUpToDate(other.m_FeaturesUpToDate), m_Features(other.m_Features), m_FeaturesMTime(other.m_FeaturesMTime), m_DisplaySize(other.m_DisplaySize) { } void mitk::PlanarFigure::SetPlaneGeometry( mitk::PlaneGeometry *geometry ) { this->SetGeometry( geometry ); m_PlaneGeometry = dynamic_cast(GetGeometry(0));//geometry; } const mitk::PlaneGeometry *mitk::PlanarFigure::GetPlaneGeometry() const { return m_PlaneGeometry; } bool mitk::PlanarFigure::IsClosed() const { mitk::BoolProperty* closed = dynamic_cast< mitk::BoolProperty* >( this->GetProperty( "closed" ).GetPointer() ); if ( closed != NULL ) { return closed->GetValue(); } return false; } void mitk::PlanarFigure::PlaceFigure( const mitk::Point2D& point ) { for ( unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i ) { m_ControlPoints.push_back( this->ApplyControlPointConstraints( i, point ) ); } m_FigurePlaced = true; m_SelectedControlPoint = 1; } bool mitk::PlanarFigure::AddControlPoint( const mitk::Point2D& point, int position ) { // if we already have the maximum number of control points, do nothing if ( m_NumberOfControlPoints < this->GetMaximumNumberOfControlPoints() ) { // if position has not been defined or position would be the last control point, just append the new one // we also append a new point if we click onto the line between the first two control-points if the second control-point is selected // -> special case for PlanarCross if ( position == -1 || position > (int)m_NumberOfControlPoints-1 || (position == 1 && m_SelectedControlPoint == 2) ) { if ( m_ControlPoints.size() > this->GetMaximumNumberOfControlPoints()-1 ) { // get rid of deprecated control points in the list. This is necessary // as ::ResetNumberOfControlPoints() only sets the member, does not resize the list! m_ControlPoints.resize( this->GetNumberOfControlPoints() ); } m_ControlPoints.push_back( this->ApplyControlPointConstraints( m_NumberOfControlPoints, point ) ); m_SelectedControlPoint = m_NumberOfControlPoints; } else { // insert the point at the given position and set it as selected point ControlPointListType::iterator iter = m_ControlPoints.begin() + position; m_ControlPoints.insert( iter, this->ApplyControlPointConstraints( position, point ) ); for( unsigned int i = 0; i < m_ControlPoints.size(); ++i ) { if( point == m_ControlPoints.at(i) ) { m_SelectedControlPoint = i; } } } // polylines & helperpolylines need to be repainted m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; // one control point more ++m_NumberOfControlPoints; return true; } else { return false; } } bool mitk::PlanarFigure::SetControlPoint( unsigned int index, const Point2D& point, bool createIfDoesNotExist ) { bool controlPointSetCorrectly = false; if (createIfDoesNotExist) { if ( m_NumberOfControlPoints <= index ) { m_ControlPoints.push_back( this->ApplyControlPointConstraints( index, point ) ); m_NumberOfControlPoints++; } else { m_ControlPoints.at( index ) = this->ApplyControlPointConstraints( index, point ); } controlPointSetCorrectly = true; } else if ( index < m_NumberOfControlPoints ) { m_ControlPoints.at( index ) = this->ApplyControlPointConstraints( index, point ); controlPointSetCorrectly = true; } else { return false; } if ( controlPointSetCorrectly ) { m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; } return controlPointSetCorrectly; } bool mitk::PlanarFigure::SetCurrentControlPoint( const Point2D& point ) { if ( (m_SelectedControlPoint < 0) || (m_SelectedControlPoint >= (int)m_NumberOfControlPoints) ) { return false; } return this->SetControlPoint(m_SelectedControlPoint, point, false); } unsigned int mitk::PlanarFigure::GetNumberOfControlPoints() const { return m_NumberOfControlPoints; } bool mitk::PlanarFigure::SelectControlPoint( unsigned int index ) { if ( index < this->GetNumberOfControlPoints() ) { m_SelectedControlPoint = index; return true; } else { return false; } } bool mitk::PlanarFigure::DeselectControlPoint() { bool wasSelected = ( m_SelectedControlPoint != -1); m_SelectedControlPoint = -1; return wasSelected; } void mitk::PlanarFigure::SetPreviewControlPoint( const Point2D& point ) { m_PreviewControlPoint = point; m_PreviewControlPointVisible = true; } void mitk::PlanarFigure::ResetPreviewContolPoint() { m_PreviewControlPointVisible = false; } mitk::Point2D mitk::PlanarFigure::GetPreviewControlPoint() { return m_PreviewControlPoint; } bool mitk::PlanarFigure::IsPreviewControlPointVisible() { return m_PreviewControlPointVisible; } mitk::Point2D mitk::PlanarFigure::GetControlPoint( unsigned int index ) const { if ( index < m_NumberOfControlPoints ) { return m_ControlPoints.at( index ); } itkExceptionMacro( << "GetControlPoint(): Invalid index!" ); } mitk::Point3D mitk::PlanarFigure::GetWorldControlPoint( unsigned int index ) const { Point3D point3D; if ( (m_PlaneGeometry != NULL) && (index < m_NumberOfControlPoints) ) { m_PlaneGeometry->Map( m_ControlPoints.at( index ), point3D ); return point3D; } itkExceptionMacro( << "GetWorldControlPoint(): Invalid index!" ); } const mitk::PlanarFigure::PolyLineType mitk::PlanarFigure::GetPolyLine(unsigned int index) { mitk::PlanarFigure::PolyLineType polyLine; if ( index > m_PolyLines.size() || !m_PolyLineUpToDate ) { this->GeneratePolyLine(); m_PolyLineUpToDate = true; } return m_PolyLines.at( index );; } const mitk::PlanarFigure::PolyLineType mitk::PlanarFigure::GetPolyLine(unsigned int index) const { return m_PolyLines.at( index ); } void mitk::PlanarFigure::ClearPolyLines() { for ( std::vector::size_type i=0; iGenerateHelperPolyLine(mmPerDisplayUnit, displayHeight); m_HelperLinesUpToDate = true; // store these parameters to be able to check next time if somebody zoomed in or out m_DisplaySize.first = mmPerDisplayUnit; m_DisplaySize.second = displayHeight; } helperPolyLine = m_HelperPolyLines.at(index); } return helperPolyLine; } void mitk::PlanarFigure::ClearHelperPolyLines() { for ( std::vector::size_type i=0; iGeneratePolyLine(); } this->EvaluateFeaturesInternal(); m_FeaturesUpToDate = true; } } void mitk::PlanarFigure::UpdateOutputInformation() { // Bounds are NOT calculated here, since the PlaneGeometry defines a fixed // frame (= bounds) for the planar figure. Superclass::UpdateOutputInformation(); this->GetTimeGeometry()->Update(); } void mitk::PlanarFigure::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::PlanarFigure::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::PlanarFigure::VerifyRequestedRegion() { return true; } void mitk::PlanarFigure::SetRequestedRegion(const itk::DataObject * /*data*/ ) { } void mitk::PlanarFigure::ResetNumberOfControlPoints( int numberOfControlPoints ) { // DO NOT resize the list here, will cause crash!! m_NumberOfControlPoints = numberOfControlPoints; } mitk::Point2D mitk::PlanarFigure::ApplyControlPointConstraints( unsigned int /*index*/, const Point2D& point ) { if ( m_PlaneGeometry == NULL ) { return point; } Point2D indexPoint; m_PlaneGeometry->WorldToIndex( point, indexPoint ); BoundingBox::BoundsArrayType bounds = m_PlaneGeometry->GetBounds(); if ( indexPoint[0] < bounds[0] ) { indexPoint[0] = bounds[0]; } if ( indexPoint[0] > bounds[1] ) { indexPoint[0] = bounds[1]; } if ( indexPoint[1] < bounds[2] ) { indexPoint[1] = bounds[2]; } if ( indexPoint[1] > bounds[3] ) { indexPoint[1] = bounds[3]; } Point2D constrainedPoint; m_PlaneGeometry->IndexToWorld( indexPoint, constrainedPoint ); return constrainedPoint; } unsigned int mitk::PlanarFigure::AddFeature( const char *featureName, const char *unitName ) { unsigned int index = m_Features.size(); Feature newFeature( featureName, unitName ); m_Features.push_back( newFeature ); return index; } void mitk::PlanarFigure::SetFeatureName( unsigned int index, const char *featureName ) { if ( index < m_Features.size() ) { m_Features[index].Name = featureName; } } void mitk::PlanarFigure::SetFeatureUnit( unsigned int index, const char *unitName ) { if ( index < m_Features.size() ) { m_Features[index].Unit = unitName; } } void mitk::PlanarFigure::SetQuantity( unsigned int index, double quantity ) { if ( index < m_Features.size() ) { m_Features[index].Quantity = quantity; } } void mitk::PlanarFigure::ActivateFeature( unsigned int index ) { if ( index < m_Features.size() ) { m_Features[index].Active = true; } } void mitk::PlanarFigure::DeactivateFeature( unsigned int index ) { if ( index < m_Features.size() ) { m_Features[index].Active = false; } } void mitk::PlanarFigure::InitializeTimeGeometry( unsigned int timeSteps ) { mitk::PlaneGeometry::Pointer geometry2D = mitk::PlaneGeometry::New(); geometry2D->Initialize(); // The geometry is propagated automatically to all time steps, // if EvenlyTimed is true... ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(geometry2D, timeSteps); SetTimeGeometry(timeGeometry); } void mitk::PlanarFigure::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); os << indent << this->GetNameOfClass() << ":\n"; if (this->IsClosed()) os << indent << "This figure is closed\n"; else os << indent << "This figure is not closed\n"; os << indent << "Minimum number of control points: " << this->GetMinimumNumberOfControlPoints() << std::endl; os << indent << "Maximum number of control points: " << this->GetMaximumNumberOfControlPoints() << std::endl; os << indent << "Current number of control points: " << this->GetNumberOfControlPoints() << std::endl; os << indent << "Control points:" << std::endl; for ( unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i ) { //os << indent.GetNextIndent() << i << ": " << m_ControlPoints->ElementAt( i ) << std::endl; os << indent.GetNextIndent() << i << ": " << m_ControlPoints.at( i ) << std::endl; } os << indent << "Geometry:\n"; this->GetPlaneGeometry()->Print(os, indent.GetNextIndent()); } unsigned short mitk::PlanarFigure::GetPolyLinesSize() { if ( !m_PolyLineUpToDate ) { this->GeneratePolyLine(); m_PolyLineUpToDate = true; } return m_PolyLines.size(); } unsigned short mitk::PlanarFigure::GetHelperPolyLinesSize() { return m_HelperPolyLines.size(); } bool mitk::PlanarFigure::IsHelperToBePainted(unsigned int index) { return m_HelperPolyLinesToBePainted->GetElement( index ); } bool mitk::PlanarFigure::ResetOnPointSelect() { return false; } void mitk::PlanarFigure::RemoveControlPoint( unsigned int index ) { if ( index > m_ControlPoints.size() ) return; if ( (m_ControlPoints.size() -1) < this->GetMinimumNumberOfControlPoints() ) return; ControlPointListType::iterator iter; iter = m_ControlPoints.begin() + index; m_ControlPoints.erase( iter ); m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; --m_NumberOfControlPoints; } void mitk::PlanarFigure::RemoveLastControlPoint() { RemoveControlPoint( m_ControlPoints.size()-1 ); } void mitk::PlanarFigure::DeepCopy(Self::Pointer oldFigure) { //DeepCopy only same types of planar figures //Notice to get typeid polymorph you have to use the *operator if(typeid(*oldFigure) != typeid(*this)) { itkExceptionMacro( << "DeepCopy(): Inconsistent type of source (" << typeid(*oldFigure).name() << ") and destination figure (" << typeid(*this).name() << ")!" ); return; } m_ControlPoints.clear(); this->ClearPolyLines(); this->ClearHelperPolyLines(); // clone base data members SetPropertyList(oldFigure->GetPropertyList()->Clone()); /// deep copy members m_FigurePlaced = oldFigure->m_FigurePlaced; m_SelectedControlPoint = oldFigure->m_SelectedControlPoint; m_FeaturesMTime = oldFigure->m_FeaturesMTime; m_Features = oldFigure->m_Features; m_NumberOfControlPoints = oldFigure->m_NumberOfControlPoints; //copy geometry 2D of planar figure PlaneGeometry::Pointer affineGeometry = oldFigure->m_PlaneGeometry->Clone(); SetPlaneGeometry(affineGeometry.GetPointer()); for(unsigned long index=0; index < oldFigure->GetNumberOfControlPoints(); index++) { m_ControlPoints.push_back( oldFigure->GetControlPoint( index )); } //After setting the control points we can generate the polylines this->GeneratePolyLine(); } void mitk::PlanarFigure::SetNumberOfPolyLines( unsigned int numberOfPolyLines ) { m_PolyLines.resize(numberOfPolyLines); } void mitk::PlanarFigure::SetNumberOfHelperPolyLines( unsigned int numberOfHerlperPolyLines ) { m_HelperPolyLines.resize(numberOfHerlperPolyLines); } void mitk::PlanarFigure::AppendPointToPolyLine( unsigned int index, PolyLineElement element ) { if ( index < m_PolyLines.size() ) { if(element.Index == -1) element.Index = m_PolyLines[index].size(); m_PolyLines[index].push_back(element); m_PolyLineUpToDate = false; } else { MITK_ERROR << "Tried to add point to PolyLine " << index+1 << ", although only " << m_PolyLines.size() << " exists"; } } void mitk::PlanarFigure::AppendPointToHelperPolyLine( unsigned int index, PolyLineElement element ) { if ( index < m_HelperPolyLines.size() ) { if(element.Index == -1) element.Index = m_HelperPolyLines[index].size(); m_HelperPolyLines[index].push_back(element); m_HelperLinesUpToDate = false; } else { MITK_ERROR << "Tried to add point to HelperPolyLine " << index+1 << ", although only " << m_HelperPolyLines.size() << " exists"; } } #ifdef __GNUC__ # pragma GCC diagnostic error "-Wdeprecated-declarations" #elif __clang__ # pragma clang diagnostic error "-Wdeprecated-declarations" #elif _MSC_VER # pragma warning (pop) #endif + +bool mitk::PlanarFigure::Equals(const mitk::PlanarFigure& other) const +{ + //check geometries + if ( this->GetPlaneGeometry() && other.GetPlaneGeometry() ) + { + if( !Equal(*(this->GetPlaneGeometry()), *(other.GetPlaneGeometry()), mitk::eps, true)) + { + return false; + } + } + else + { + MITK_ERROR << "Geometry is not equal"; + return false; + } + + //check isPlaced member + if ( this->m_FigurePlaced != other.m_FigurePlaced) + { + MITK_ERROR << "Is_Placed is not equal"; + return false; + } + + //check closed property + if (this->IsClosed() != other.IsClosed()) + { + MITK_ERROR << "Is_closed is not equal"; + return false; + } + + //check poly lines + if (this->m_PolyLines.size() != other.m_PolyLines.size()) + { + return false; + } + else + { + std::vector::const_iterator itThis = this->m_PolyLines.begin(); + std::vector::const_iterator itEnd = this->m_PolyLines.end(); + std::vector::const_iterator itOther = other.m_PolyLines.begin(); + + while( itThis != itEnd ) + { + if(itThis->size() != itOther->size()) + return false; + else + { + PolyLineType::const_iterator itLineThis = itThis->begin(); + PolyLineType::const_iterator itLineEnd = itThis->end(); + PolyLineType::const_iterator itLineOther = itOther->begin(); + + while(itLineThis != itLineEnd) + { + Point2D p1 = *itLineThis; + Point2D p2 = *itLineOther; + ScalarType delta = fabs(p1[0]-p2[0])+fabs(p1[1]-p2[1]); + if(delta > .001) + { + MITK_ERROR << "Poly line is not equal"; + MITK_ERROR << p1 << "/" << p2; + return false; + } + + ++itLineThis; + ++itLineOther; + } + } + ++itThis; + ++itOther; + } + } + + //check features + if (this->GetNumberOfFeatures() != other.GetNumberOfFeatures()) + { + MITK_ERROR << "Number of Features is Different"; + return false; + } + else + { + std::vector::const_iterator itThis = m_Features.begin(); + std::vector::const_iterator itEnd = m_Features.end(); + std::vector::const_iterator itOther = other.m_Features.begin(); + + while(itThis != itEnd) + { + if(( itThis->Quantity - itOther->Quantity) > .001 ) + { + MITK_ERROR << "Quantity is Different" << itThis->Quantity << "/" << itOther->Quantity; + return false; + } + if( itThis->Unit.compare(itOther->Unit) != 0 ) + { + MITK_ERROR << "Unit is Different" << itThis->Unit << "/" << itOther->Unit; + return false; + } + if( itThis->Name.compare(itOther->Name) != 0 ) + { + MITK_ERROR << "Name of Measure is Different " << itThis->Name << "/ " << itOther->Name;; + return false; + } + + ++itThis; + ++itOther; + } + } + + return true; +} + +bool mitk::Equal( const mitk::PlanarFigure& leftHandSide, const mitk::PlanarFigure& rightHandSide, ScalarType eps, bool verbose ) +{ + return leftHandSide.Equals(rightHandSide); +} diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h index 210646a320..b781f8d13d 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h @@ -1,434 +1,441 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_FIGURE_H_ #define _MITK_PLANAR_FIGURE_H_ #include #include "mitkBaseData.h" #include "mitkCommon.h" #include namespace mitk { class PlaneGeometry; /** * \brief Base-class for geometric planar (2D) figures, such as * lines, circles, rectangles, polygons, etc. * * \warning Currently does not support time-resolved data handling * * Behavior and appearance of PlanarFigures are controlled by various properties; for a detailed * list of appearance properties see mitk::PlanarFigureMapper2D * * The following properties control general PlanarFigure behavior: * *
    *
  • "selected": true if the planar figure is selected *
  • "planarfigure.ishovering": true if the mouse "hovers" over the planar figure *
  • "planarfigure.iseditable": true if the planar figure can be edited (otherwise, * it can only be picked/selected, but its control points cannot be edited); default is true *
  • "planarfigure.isextendable": true if new control points can be inserted into the list of control points; * default is false *
* * * TODO: Implement local 2D transform (including center of rotation...) * */ class MitkPlanarFigure_EXPORT PlanarFigure : public BaseData { public: mitkClassMacro( PlanarFigure, BaseData ) itkCloneMacro( Self ) /** \brief Treat as Point2D by implicitly using conversion operators. * * \deprecatedSince{2014_06} "struct PolyLineElement {...};" will be changed to "typedef Point2D PolyLineElement;". */ struct MitkPlanarFigure_EXPORT PolyLineElement { DEPRECATED(PolyLineElement(Point2D point, int index)); PolyLineElement(const Point2D& point); PolyLineElement(const PolyLineElement &other); PolyLineElement& operator=(const PolyLineElement &other); operator Point2D&(); operator const Point2D&() const; DEPRECATED(Point2D Point); DEPRECATED(int Index); }; typedef itk::VectorContainer< unsigned long, bool> BoolContainerType; typedef std::deque< Point2D > ControlPointListType; typedef std::vector< PolyLineElement > PolyLineType; /** \brief Sets the 2D geometry on which this figure will be placed. * * In most cases, this is a Geometry already owned by another object, e.g. * describing the slice of the image on which measurements will be * performed. */ virtual void SetPlaneGeometry( mitk::PlaneGeometry *geometry ); /** * \deprecatedSince{2014_06} Please use SetPlaneGeometry */ DEPRECATED(void SetGeometry2D(PlaneGeometry* geo)){SetPlaneGeometry(geo);}; /** \brief Returns (previously set) 2D geometry of this figure. */ virtual const PlaneGeometry *GetPlaneGeometry() const; /** * \deprecatedSince{2014_06} Please use GetPlaneGeometry */ DEPRECATED(const PlaneGeometry* GetGeometry2D()){return GetPlaneGeometry();}; /** \brief True if the planar figure is closed. * * Default is false. The "closed" boolean property must be set in sub-classes. */ virtual bool IsClosed() const; /** \brief True if the planar figure has been placed (and can be * displayed/interacted with). */ virtual bool IsPlaced() const { return m_FigurePlaced; }; /** \brief Place figure at the given point (in 2D index coordinates) onto * the given 2D geometry. * * By default, the first two control points of the figure are set to the * passed point. Further points can be set via AddControlPoint(), if the * current number of control points is below the maximum number of control * points. * * Can be re-implemented in sub-classes as needed. */ virtual void PlaceFigure( const Point2D& point ); /** * \brief Adds / inserts new control-points * * This method adds a new control-point with the coordinates defined by point at the given index. * If 'index' == -1 or index is greater than the number of control-points the new point is appended * to the back of the list of control points. * If a control-point already exists for 'index', an additional point is inserted at that position. * It is not possible to add more points if the maximum number of control-points (GetMaximumNumberOfControlPoints()) * has been reached. */ virtual bool AddControlPoint( const Point2D& point, int index = -1 ); virtual bool SetControlPoint( unsigned int index, const Point2D& point, bool createIfDoesNotExist = false); virtual bool SetCurrentControlPoint( const Point2D& point ); /** \brief Returns the current number of 2D control points defining this figure. */ unsigned int GetNumberOfControlPoints() const; /** \brief Returns the minimum number of control points needed to represent * this figure. * * Must be implemented in sub-classes. */ virtual unsigned int GetMinimumNumberOfControlPoints() const = 0; /** \brief Returns the maximum number of control points allowed for * this figure (e.g. 3 for triangles). * * Must be implemented in sub-classes. */ virtual unsigned int GetMaximumNumberOfControlPoints() const = 0; /** \brief Selects currently active control points. */ virtual bool SelectControlPoint( unsigned int index ); /** \brief Deselect control point; no control point active. */ virtual bool DeselectControlPoint(); /** \brief Return currently selected control point. */ virtual int GetSelectedControlPoint() const { return m_SelectedControlPoint; } /** \brief Returns specified control point in 2D world coordinates. */ Point2D GetControlPoint( unsigned int index ) const; /** \brief Returns specified control point in world coordinates. */ Point3D GetWorldControlPoint( unsigned int index ) const; /** \brief Returns the polyline representing the planar figure * (for rendering, measurements, etc.). */ const PolyLineType GetPolyLine(unsigned int index); /** \brief Returns the polyline representing the planar figure * (for rendering, measurments, etc.). */ const PolyLineType GetPolyLine(unsigned int index) const; /** \brief Returns the polyline that should be drawn the same size at every scale * (for text, angles, etc.). */ const PolyLineType GetHelperPolyLine( unsigned int index, double mmPerDisplayUnit, unsigned int displayHeight ); /** \brief Sets the position of the PreviewControlPoint. Automatically sets it visible.*/ void SetPreviewControlPoint( const Point2D& point ); /** \brief Marks the PreviewControlPoint as invisible.*/ void ResetPreviewContolPoint(); /** \brief Returns whether or not the PreviewControlPoint is visible.*/ bool IsPreviewControlPointVisible(); /** \brief Returns the coordinates of the PreviewControlPoint. */ Point2D GetPreviewControlPoint(); /** \brief Returns the number of features available for this PlanarFigure * (such as, radius, area, ...). */ virtual unsigned int GetNumberOfFeatures() const; /** \brief Returns the name (identifier) of the specified features. */ const char *GetFeatureName( unsigned int index ) const; /** \brief Returns the physical unit of the specified features. */ const char *GetFeatureUnit( unsigned int index ) const; /** Returns quantity of the specified feature (e.g., length, radius, * area, ... ) */ double GetQuantity( unsigned int index ) const; /** \brief Returns true if the feature with the specified index exists and * is active (an inactive feature may e.g. be the area of a non-closed * polygon. */ bool IsFeatureActive( unsigned int index ) const; /** \brief Returns true if the feature with the specified index exists and is set visible */ bool IsFeatureVisible( unsigned int index ) const; /** \brief Defines if the feature with the specified index will be shown as an * overlay in the RenderWindow */ void SetFeatureVisible( unsigned int index, bool visible ); /** \brief Calculates quantities of all features of this planar figure. */ virtual void EvaluateFeatures(); /** \brief Intherited from parent */ virtual void UpdateOutputInformation(); /** \brief Intherited from parent */ virtual void SetRequestedRegionToLargestPossibleRegion(); /** \brief Intherited from parent */ virtual bool RequestedRegionIsOutsideOfTheBufferedRegion(); /** \brief Intherited from parent */ virtual bool VerifyRequestedRegion(); /** \brief Intherited from parent */ virtual void SetRequestedRegion( const itk::DataObject *data); /** \brief Returns the current number of polylines */ virtual unsigned short GetPolyLinesSize(); /** \brief Returns the current number of helperpolylines */ virtual unsigned short GetHelperPolyLinesSize(); /** \brief Returns whether a helper polyline should be painted or not */ virtual bool IsHelperToBePainted(unsigned int index); /** \brief Returns true if the planar figure is reset to "add points" mode * when a point is selected. * * Default return value is false. Subclasses can overwrite this method and * execute any reset / initialization statements required. */ virtual bool ResetOnPointSelect(); /** \brief removes the point with the given index from the list of controlpoints. */ virtual void RemoveControlPoint( unsigned int index ); /** \brief Removes last control point */ virtual void RemoveLastControlPoint(); /** \brief Copies contents and state of a figre provided as parameter to the current object. * * Requires a matching type of both figures. * * \note Deprecated, use Clone() instead. */ DEPRECATED(void DeepCopy(Self::Pointer oldFigure)); /** \brief Allow sub-classes to apply constraints on control points. * * Sub-classes can define spatial constraints to certain control points by * overwriting this method and returning a constrained point. By default, * the points are constrained by the image bounds. */ virtual Point2D ApplyControlPointConstraints( unsigned int /*index*/, const Point2D& point ); + /** + * \brief Compare two PlanarFigure objects + * Note: all subclasses have to implement the method on their own. + */ + virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarFigure(); virtual ~PlanarFigure(); PlanarFigure(const Self& other); /** \brief Set the initial number of control points of the planar figure */ void ResetNumberOfControlPoints( int numberOfControlPoints ); /** Adds feature (e.g., circumference, radius, angle, ...) to feature vector * of a planar figure object and returns integer ID for the feature element. * Should be called in sub-class constructors. */ virtual unsigned int AddFeature( const char *featureName, const char *unitName ); /** Sets the name of the specified feature. INTERNAL METHOD. */ void SetFeatureName( unsigned int index, const char *featureName ); /** Sets the physical unit of the specified feature. INTERNAL METHOD. */ void SetFeatureUnit( unsigned int index, const char *unitName ); /** Sets quantity of the specified feature. INTERNAL METHOD. */ void SetQuantity( unsigned int index, double quantity ); /** Sets the specified feature as active. INTERAL METHOD. */ void ActivateFeature( unsigned int index ); /** Sets the specified feature as active. INTERAL METHOD. */ void DeactivateFeature( unsigned int index ); /** \brief Generates the poly-line representation of the planar figure. * Must be implemented in sub-classes. */ virtual void GeneratePolyLine() = 0; /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom. * Must be implemented in sub-classes. */ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) = 0; /** \brief Calculates quantities of all features of this planar figure. * Must be implemented in sub-classes. */ virtual void EvaluateFeaturesInternal() = 0; /** \brief Initializes the TimeGeometry describing the (time-resolved) * geometry of this figure. Note that each time step holds one PlaneGeometry. */ virtual void InitializeTimeGeometry( unsigned int timeSteps = 1 ); /** \brief defines the number of PolyLines that will be available */ void SetNumberOfPolyLines( unsigned int numberOfPolyLines ); /** \brief Append a point to the PolyLine # index */ void AppendPointToPolyLine( unsigned int index, PolyLineElement element ); /** \brief clears the list of PolyLines. Call before re-calculating a new Polyline. */ void ClearPolyLines(); /** \brief defines the number of HelperPolyLines that will be available */ void SetNumberOfHelperPolyLines( unsigned int numberOfHelperPolyLines ); /** \brief Append a point to the HelperPolyLine # index */ void AppendPointToHelperPolyLine( unsigned int index, PolyLineElement element ); /** \brief clears the list of HelperPolyLines. Call before re-calculating a new HelperPolyline. */ void ClearHelperPolyLines(); virtual void PrintSelf( std::ostream& os, itk::Indent indent ) const; ControlPointListType m_ControlPoints; unsigned int m_NumberOfControlPoints; // Currently selected control point; -1 means no point selected int m_SelectedControlPoint; std::vector m_PolyLines; std::vector m_HelperPolyLines; BoolContainerType::Pointer m_HelperPolyLinesToBePainted; // this point is used to store the coordiantes an additional 'ControlPoint' that is rendered // when the mouse cursor is above the figure (and not a control-point) and when the // property 'planarfigure.isextendable' is set to true Point2D m_PreviewControlPoint; bool m_PreviewControlPointVisible; bool m_FigurePlaced; private: // not implemented to prevent PlanarFigure::New() calls which would create an itk::Object. static Pointer New(); struct Feature { Feature( const char *name, const char *unit ) : Name( name ), Unit( unit ), Quantity( 0.0 ), Active( true ), Visible( true ) { } std::string Name; std::string Unit; double Quantity; bool Active; bool Visible; }; virtual itk::LightObject::Pointer InternalClone() const = 0; PlaneGeometry *m_PlaneGeometry; bool m_PolyLineUpToDate; bool m_HelperLinesUpToDate; bool m_FeaturesUpToDate; // Vector of features available for this geometric figure typedef std::vector< Feature > FeatureVectorType; FeatureVectorType m_Features; unsigned long m_FeaturesMTime; // this pair is used to store the mmInDisplayUnits (m_DisplaySize.first) and the displayHeight (m_DisplaySize.second) // that the helperPolyLines have been calculated for. // It's used to determine whether or not GetHelperPolyLine() needs to recalculate the HelperPolyLines. std::pair m_DisplaySize; }; +MITK_CORE_EXPORT bool Equal( const mitk::PlanarFigure& leftHandSide, const mitk::PlanarFigure& rightHandSide, ScalarType eps, bool verbose ); + } // namespace mitk #endif //_MITK_PLANAR_FIGURE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.cpp index ddd3d8c262..ca7c048a98 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.cpp @@ -1,79 +1,92 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarFourPointAngle.h" #include "mitkPlaneGeometry.h" mitk::PlanarFourPointAngle::PlanarFourPointAngle() : FEATURE_ID_ANGLE( this->AddFeature( "Angle", "deg" ) ) { // Four point angle has two control points this->ResetNumberOfControlPoints( 2 ); this->SetNumberOfPolyLines( 2 ); } mitk::PlanarFourPointAngle::~PlanarFourPointAngle() { } void mitk::PlanarFourPointAngle::GeneratePolyLine() { this->ClearPolyLines(); for (unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i) this->AppendPointToPolyLine(i / 2, this->GetControlPoint(i)); } void mitk::PlanarFourPointAngle::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // Generate helper-poly-line for an four point angle // Need to discuss a sensible implementation } void mitk::PlanarFourPointAngle::EvaluateFeaturesInternal() { if ( this->GetNumberOfControlPoints() < 4 ) { // Angle not yet complete. return; } // Calculate angle between lines const Point2D &p0 = this->GetControlPoint( 0 ); const Point2D &p1 = this->GetControlPoint( 1 ); const Point2D &p2 = this->GetControlPoint( 2 ); const Point2D &p3 = this->GetControlPoint( 3 ); Vector2D v0 = p1 - p0; Vector2D v1 = p3 - p2; v0.Normalize(); v1.Normalize(); double angle = acos( v0 * v1 ); this->SetQuantity( FEATURE_ID_ANGLE, angle ); } void mitk::PlanarFourPointAngle::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } + + bool mitk::PlanarFourPointAngle::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarFourPointAngle* otherFourPtAngle = dynamic_cast(&other); + if ( otherFourPtAngle ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.h b/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.h index b485beb721..44189715de 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFourPointAngle.h @@ -1,93 +1,94 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_FOURPOINTANGLE_H_ #define _MITK_PLANAR_FOURPOINTANGLE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a four point * angle, which is defined by two non-intersecting lines in 2D. Each of those lines * is defined by two control points. */ class MitkPlanarFigure_EXPORT PlanarFourPointAngle : public PlanarFigure { public: mitkClassMacro( PlanarFourPointAngle, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) public: // Feature identifiers const unsigned int FEATURE_ID_ANGLE; /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); /** \brief Four point angle has 4 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 4; } /** \brief Four point angle has 4 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 4; } + virtual bool Equals(const mitk::PlanarFigure& other) const ; protected: PlanarFourPointAngle(); virtual ~PlanarFourPointAngle(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; private: }; } // namespace mitk #endif //_MITK_PLANAR_FOURPOINTANGLE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarLine.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarLine.cpp index a534089a6a..ffee0a38bd 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarLine.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarLine.cpp @@ -1,65 +1,78 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarLine.h" #include "mitkPlaneGeometry.h" mitk::PlanarLine::PlanarLine() : FEATURE_ID_LENGTH( this->AddFeature( "Length", "mm" ) ) { // Line has two control points this->ResetNumberOfControlPoints( 2 ); this->SetNumberOfPolyLines( 1 ); } mitk::PlanarLine::~PlanarLine() { } void mitk::PlanarLine::GeneratePolyLine() { this->ClearPolyLines(); this->AppendPointToPolyLine(0, this->GetControlPoint(0)); this->AppendPointToPolyLine(0, this->GetControlPoint(1)); } void mitk::PlanarLine::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A line does not require a helper object } void mitk::PlanarLine::EvaluateFeaturesInternal() { // Calculate line length const Point3D &p0 = this->GetWorldControlPoint( 0 ); const Point3D &p1 = this->GetWorldControlPoint( 1 ); double length = p0.EuclideanDistanceTo( p1 ); this->SetQuantity( FEATURE_ID_LENGTH, length ); } void mitk::PlanarLine::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); } + + bool mitk::PlanarLine::Equals(const PlanarFigure &other) const + { + const mitk::PlanarLine* otherLine = dynamic_cast(&other); + if ( otherLine ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarLine.h b/Modules/PlanarFigure/DataManagement/mitkPlanarLine.h index 09732fcef3..cdfd7bfd55 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarLine.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarLine.h @@ -1,93 +1,94 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_LINE_H_ #define _MITK_PLANAR_LINE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a line * through two control points */ class MitkPlanarFigure_EXPORT PlanarLine : public PlanarFigure { public: mitkClassMacro( PlanarLine, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); /** \brief Line has 2 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 2; } /** \brief Line has 2 control points per definition. */ unsigned int GetMaximumNumberOfControlPoints() const { return 2; } + virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarLine(); virtual ~PlanarLine(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; // Feature identifiers const unsigned int FEATURE_ID_LENGTH; private: }; } // namespace mitk #endif //_MITK_PLANAR_LINE_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.cpp index 669d952f3f..e47a71eebd 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.cpp @@ -1,274 +1,287 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarPolygon.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" // stl related includes #include mitk::PlanarPolygon::PlanarPolygon() : FEATURE_ID_CIRCUMFERENCE( this->AddFeature( "Circumference", "mm" ) ), FEATURE_ID_AREA( this->AddFeature( "Area", "mm2" ) ) { // Polygon has at least two control points this->ResetNumberOfControlPoints( 2 ); this->SetNumberOfPolyLines( 1 ); // Polygon is closed by default this->SetProperty( "closed", mitk::BoolProperty::New( true ) ); this->SetProperty( "subdivision", mitk::BoolProperty::New( false ) ); } mitk::PlanarPolygon::~PlanarPolygon() { } void mitk::PlanarPolygon::SetClosed( bool closed ) { this->SetProperty( "closed", mitk::BoolProperty::New( closed ) ); if ( !closed ) { // For non-closed polygons: use "Length" as feature name; disable area this->SetFeatureName( FEATURE_ID_CIRCUMFERENCE, "Length" ); this->DeactivateFeature( FEATURE_ID_AREA ); } else { // For closed polygons: use "Circumference" as feature name; enable area this->SetFeatureName( FEATURE_ID_CIRCUMFERENCE, "Circumference" ); this->ActivateFeature( FEATURE_ID_AREA ); } this->Modified(); } void mitk::PlanarPolygon::GeneratePolyLine() { this->ClearPolyLines(); for (ControlPointListType::size_type i = 0; i < m_ControlPoints.size(); ++i) this->AppendPointToPolyLine(0, this->GetControlPoint(i)); } void mitk::PlanarPolygon::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A polygon does not require helper objects } void mitk::PlanarPolygon::EvaluateFeaturesInternal() { // Calculate circumference double circumference = 0.0; unsigned int i,j; PolyLineType polyLine = m_PolyLines[0]; if(polyLine.empty()) return; for ( i = 0; i <(polyLine.size()-1); ++i ) { circumference += static_cast(polyLine[i]).EuclideanDistanceTo( static_cast(polyLine[i + 1]) ); } if ( this->IsClosed() ) { circumference += static_cast(polyLine[i]).EuclideanDistanceTo( static_cast(polyLine.front()) ); } this->SetQuantity( FEATURE_ID_CIRCUMFERENCE, circumference ); // Calculate polygon area (if closed) double area = 0.0; bool intersection = false; if ( this->IsClosed() && (this->GetPlaneGeometry() != NULL) ) { // does PlanarPolygon overlap/intersect itself? unsigned int numberOfPoints = polyLine.size(); if( numberOfPoints >= 4) { for ( i = 0; i < (numberOfPoints - 1); ++i ) { // line 1 Point2D p0 = polyLine[i]; Point2D p1 = polyLine[i + 1]; // check for intersection with all other lines for (j = i+1; j < (numberOfPoints - 1); ++j ) { Point2D p2 = polyLine[j]; Point2D p3 = polyLine[j + 1]; intersection = CheckForLineIntersection(p0,p1,p2,p3); if (intersection) break; } if (intersection) break; // only because the inner loop might have changed "intersection" // last line from p_x to p_0 Point2D p2 = polyLine.front(); Point2D p3 = polyLine.back(); intersection = CheckForLineIntersection(p0,p1,p2,p3); if (intersection) break; } } // calculate area for ( i = 0; i < polyLine.size(); ++i ) { Point2D p0 = polyLine[i]; Point2D p1 = polyLine[ (i + 1) % polyLine.size() ]; area += p0[0] * p1[1] - p1[0] * p0[1]; } area /= 2.0; } // set area if appropiate (i.e. closed and not intersected) if(this->IsClosed() && !intersection) { SetQuantity( FEATURE_ID_AREA, fabs( area ) ); this->ActivateFeature( FEATURE_ID_AREA ); } else { SetQuantity( FEATURE_ID_AREA, 0 ); this->DeactivateFeature( FEATURE_ID_AREA ); } } void mitk::PlanarPolygon::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); if (this->IsClosed()) os << indent << "Polygon is closed\n"; else os << indent << "Polygon is not closed\n"; } // based on // http://flassari.is/2008/11/line-line-intersection-in-cplusplus/ bool mitk::PlanarPolygon::CheckForLineIntersection( const mitk::Point2D& p1, const mitk::Point2D& p2, const mitk::Point2D& p3, const mitk::Point2D& p4, Point2D& intersection ) const { // do not check for intersections with control points if(p1 == p2 || p1 == p3 || p1 == p4 || p2 == p3 || p2 == p4 || p3 == p4) return false; // Store the values for fast access and easy // equations-to-code conversion double x1 = p1[0], x2 = p2[0], x3 = p3[0], x4 = p4[0]; double y1 = p1[1], y2 = p2[1], y3 = p3[1], y4 = p4[1]; double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); // If d is zero, there is no intersection //if (d < mitk::eps) return false; if (d == 0) return false; // Get the x and y double pre = (x1*y2 - y1*x2); double post = (x3*y4 - y3*x4); double x = ( pre * (x3 - x4) - (x1 - x2) * post ) / d; double y = ( pre * (y3 - y4) - (y1 - y2) * post ) / d; double tolerance = 0.001; // Check if the x coordinates are within both lines, including tolerance if ( x < ( std::min(x1, x2) - tolerance ) || x > ( std::max(x1, x2) + tolerance ) || x < ( std::min(x3, x4) - tolerance ) || x > ( std::max(x3, x4) + tolerance ) ) { return false; } // Check if the y coordinates are within both lines, including tolerance if ( y < ( std::min(y1, y2) - tolerance ) || y > ( std::max(y1, y2) + tolerance ) || y < ( std::min(y3, y4) - tolerance ) || y > ( std::max(y3, y4) + tolerance ) ) { return false; } // point of intersection Point2D ret; ret[0] = x; ret[1] = y; intersection = ret; return true; } bool mitk::PlanarPolygon::CheckForLineIntersection( const mitk::Point2D& p1, const mitk::Point2D& p2, const mitk::Point2D& p3, const mitk::Point2D& p4 ) const { mitk::Point2D intersection; return mitk::PlanarPolygon::CheckForLineIntersection( p1, p2, p3, p4, intersection ); } std::vector mitk::PlanarPolygon::CheckForLineIntersection( const mitk::Point2D& p1, const mitk::Point2D& p2 ) const { std::vector intersectionList; ControlPointListType polyLinePoints; PolyLineType tempList = m_PolyLines[0]; PolyLineType::iterator iter; for( iter = tempList.begin(); iter != tempList.end(); ++iter ) { polyLinePoints.push_back(*iter); } for ( ControlPointListType::size_type i=0; iIsClosed() ) { mitk::Point2D intersection, lastControlPoint, firstControlPoint; lastControlPoint = polyLinePoints.back(); firstControlPoint = polyLinePoints.front(); if ( mitk::PlanarPolygon::CheckForLineIntersection( lastControlPoint, firstControlPoint, p1, p2, intersection ) ) { intersectionList.push_back( intersection ); } } return intersectionList; } + + bool mitk::PlanarPolygon::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarPolygon* otherPolygon = dynamic_cast(&other); + if ( otherPolygon ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.h b/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.h index fa5c0c19b4..e37b70934b 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarPolygon.h @@ -1,103 +1,104 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_POLYGON_H_ #define _MITK_PLANAR_POLYGON_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a polygon * with two or more control points */ class MitkPlanarFigure_EXPORT PlanarPolygon : public PlanarFigure { public: mitkClassMacro( PlanarPolygon, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Set whether the polygon should be closed between first and last control point or not. */ virtual void SetClosed( bool closed ); itkBooleanMacro( Closed ); // Calls SetClosed(); no need to re-implement /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); /** \brief Polygon has 3 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 3; } /** \brief Polygon maximum number of control points is principally not limited. */ unsigned int GetMaximumNumberOfControlPoints() const { return 1000; } std::vector CheckForLineIntersection( const Point2D& p1, const Point2D& p2 ) const; + virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarPolygon(); virtual ~PlanarPolygon(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); bool CheckForLineIntersection(const mitk::Point2D& p1, const mitk::Point2D& p2, const mitk::Point2D& p3, const mitk::Point2D& p4, Point2D& intersection) const ; bool CheckForLineIntersection( const mitk::Point2D& p1, const mitk::Point2D& p2, const mitk::Point2D& p3, const mitk::Point2D& p4 ) const; virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; const unsigned int FEATURE_ID_CIRCUMFERENCE; const unsigned int FEATURE_ID_AREA; private: }; } // namespace mitk #endif //_MITK_PLANAR_POLYGON_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.cpp index cc7169ece3..d4fdeb80d6 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.cpp @@ -1,146 +1,159 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkProperties.h" #include "mitkPlanarRectangle.h" #include "mitkPlaneGeometry.h" mitk::PlanarRectangle::PlanarRectangle() : FEATURE_ID_CIRCUMFERENCE( this->AddFeature( "Circumference", "mm" ) ), FEATURE_ID_AREA( this->AddFeature( "Area", "mm2" ) ) { // Rectangle has four control points this->ResetNumberOfControlPoints( 4 ); this->SetProperty( "closed", mitk::BoolProperty::New(true) ); this->SetNumberOfPolyLines( 1 ); } mitk::PlanarRectangle::~PlanarRectangle() { } bool mitk::PlanarRectangle::SetControlPoint( unsigned int index, const Point2D &point, bool createIfDoesNotExist ) { // heres the deal with the rectangle: // when a point is moved all corresponding corner points are moved with him // e.g. if the lower right point (index=3) is moved the upper right point (index=1) // is moved in the same x direction // and the lower left point (index=2) is moved in the same y direction // the upper left point (index=0) is left untouched bool set = PlanarFigure::SetControlPoint( index, point, createIfDoesNotExist ); if(set) { // can be made better ... unsigned int horizontalCorrespondingPointIndex = 1; unsigned int verticalCorrespondingPointIndex = 3; if(index == 1) { horizontalCorrespondingPointIndex = 0; verticalCorrespondingPointIndex = 2; } else if(index == 2) { horizontalCorrespondingPointIndex = 3; verticalCorrespondingPointIndex = 1; } else if(index == 3) { horizontalCorrespondingPointIndex = 2; verticalCorrespondingPointIndex = 0; } Point2D verticalCorrespondingPoint = GetControlPoint( verticalCorrespondingPointIndex ); verticalCorrespondingPoint[0] = point[0]; PlanarFigure::SetControlPoint( verticalCorrespondingPointIndex, verticalCorrespondingPoint ); Point2D horizontalCorrespondingPoint = GetControlPoint( horizontalCorrespondingPointIndex ); horizontalCorrespondingPoint[1] = point[1]; PlanarFigure::SetControlPoint( horizontalCorrespondingPointIndex, horizontalCorrespondingPoint ); } return set; } void mitk::PlanarRectangle::PlaceFigure( const mitk::Point2D &point ) { PlanarFigure::PlaceFigure( point ); m_SelectedControlPoint = 3; } void mitk::PlanarRectangle::GeneratePolyLine() { this->ClearPolyLines(); for (unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i) this->AppendPointToPolyLine(0, this->GetControlPoint(i)); } void mitk::PlanarRectangle::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A polygon does not require helper objects } void mitk::PlanarRectangle::EvaluateFeaturesInternal() { // Calculate circumference double circumference = 0.0; unsigned int i; for ( i = 0; i < this->GetNumberOfControlPoints(); ++i ) { circumference += this->GetWorldControlPoint( i ).EuclideanDistanceTo( this->GetWorldControlPoint( (i + 1) % this->GetNumberOfControlPoints() ) ); } this->SetQuantity( FEATURE_ID_CIRCUMFERENCE, circumference ); // Calculate rectangle area (well, done a bit clumsy...) double area = 0.0; if ( this->GetPlaneGeometry() != NULL ) { for ( i = 0; i < this->GetNumberOfControlPoints(); ++i ) { Point2D p0 = this->GetControlPoint( i ); Point2D p1 = this->GetControlPoint( (i + 1) % this->GetNumberOfControlPoints() ); area += p0[0] * p1[1] - p1[0] * p0[1]; } area /= 2.0; } this->SetQuantity( FEATURE_ID_AREA, fabs(area) ); } void mitk::PlanarRectangle::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); os << indent << "Number of control points: " << this->GetNumberOfControlPoints() << std::endl; os << indent << "Control points:" << std::endl; for ( unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i ) { os << indent << indent << i << ": " <(&other); + if ( otherRectangle ) + { + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.h b/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.h index 2943a626c5..b4890c5ce8 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarRectangle.h @@ -1,93 +1,94 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_RECTANGLE_H_ #define _MITK_PLANAR_RECTANGLE_H_ #include "mitkPlanarPolygon.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a polygon * with two or more control points */ class MitkPlanarFigure_EXPORT PlanarRectangle : public PlanarFigure { public: mitkClassMacro( PlanarRectangle, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ //virtual void Initialize(); virtual void PlaceFigure( const Point2D &point ); /** \brief Polygon has 2 control points per definition. */ virtual unsigned int GetMinimumNumberOfControlPoints() const { return 4; } /** \brief Polygon maximum number of control points is principally not limited. */ virtual unsigned int GetMaximumNumberOfControlPoints() const { return 4; } virtual bool SetControlPoint( unsigned int index, const Point2D &point, bool createIfDoesNotExist = false); protected: PlanarRectangle(); virtual ~PlanarRectangle(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight); /** \brief Calculates feature quantities of the planar figure. */ virtual void EvaluateFeaturesInternal(); virtual void PrintSelf( std::ostream &os, itk::Indent indent ) const; const unsigned int FEATURE_ID_CIRCUMFERENCE; const unsigned int FEATURE_ID_AREA; + virtual bool Equals(const mitk::PlanarFigure& other) const; private: }; } // namespace mitk #endif //_MITK_PLANAR_POLYGON_H_ diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.cpp index db3ac6657b..2b63f2dffd 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.cpp @@ -1,132 +1,149 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarSubdivisionPolygon.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" // stl related includes #include mitk::PlanarSubdivisionPolygon::PlanarSubdivisionPolygon(): m_TensionParameter(0.0625), m_SubdivisionRounds(5) { // Polygon is subdivision (in contrast to parent class PlanarPolygon this->SetProperty( "closed", mitk::BoolProperty::New( true ) ); this->SetProperty( "subdivision", mitk::BoolProperty::New( true ) ); // Other properties are inherited / already initialized by parent class PlanarPolygon } mitk::PlanarSubdivisionPolygon::~PlanarSubdivisionPolygon() { } void mitk::PlanarSubdivisionPolygon::GeneratePolyLine() { this->ClearPolyLines(); ControlPointListType subdivisionPoints; ControlPointListType newSubdivisionPoints; subdivisionPoints.clear(); subdivisionPoints = m_ControlPoints; if( m_ControlPoints.size() >= GetMinimumNumberOfControlPoints() ) { for( unsigned int i=0; i < GetSubdivisionRounds(); i++ ) { // Indices unsigned int index, indexPrev, indexNext, indexNextNext; unsigned int numberOfPoints = subdivisionPoints.size(); Point2D newPoint; // Keep cycling our array indices forward until they wrap around at the end for ( index = 0; index < numberOfPoints; ++index ) { // Create new subdivision point according to formula // p_new = (0.5 + tension) * (p_here + p_next) - tension * (p_prev + p_nextnext) indexPrev = (numberOfPoints + index - 1) % numberOfPoints; indexNext = (index + 1) % numberOfPoints; indexNextNext = (index + 2) % numberOfPoints; newPoint[0] = (0.5 + GetTensionParameter()) * (double)( subdivisionPoints[index][0] + subdivisionPoints[indexNext][0] ) - GetTensionParameter() * (double)( subdivisionPoints[indexPrev][0] + subdivisionPoints[indexNextNext][0]); newPoint[1] = (0.5 + GetTensionParameter()) * (double)( subdivisionPoints[index][1] + subdivisionPoints[indexNext][1] ) - GetTensionParameter() * (double)( subdivisionPoints[indexPrev][1] + subdivisionPoints[indexNextNext][1]); newSubdivisionPoints.push_back( newPoint ); } ControlPointListType mergedSubdivisionPoints; ControlPointListType::iterator it, itNew; for ( it = subdivisionPoints.begin() , itNew = newSubdivisionPoints.begin(); it != subdivisionPoints.end(); ++it, ++itNew ) { mergedSubdivisionPoints.push_back( *it ); mergedSubdivisionPoints.push_back( *itNew ); } subdivisionPoints = mergedSubdivisionPoints; newSubdivisionPoints.clear(); } } bool isInitiallyPlaced = this->GetProperty("initiallyplaced"); unsigned int i; ControlPointListType::iterator it; for ( it = subdivisionPoints.begin(), i = 0; it != subdivisionPoints.end(); ++it, ++i ) { // Determine the index of the control point FOLLOWING this poly-line element // (this is needed by PlanarFigureInteractor to insert new points at the correct position, // namely BEFORE the next control point) unsigned int nextIndex; if ( i == 0 ) { // For the FIRST polyline point, use the index of the LAST control point // (it will used to check if the mouse is near the very last polyline element) nextIndex = m_ControlPoints.size() - 1; } else { // For all other polyline points, use the index of the control point succeeding it // (for polyline points lying on control points, the index of the previous control point // is used) nextIndex = (((i - 1) >> this->GetSubdivisionRounds()) + 1) % m_ControlPoints.size(); if(!isInitiallyPlaced && nextIndex > m_ControlPoints.size()-2) { PolyLineElement elem( m_ControlPoints[m_ControlPoints.size()-1], nextIndex ); this->AppendPointToPolyLine( 0, elem ); break; } } PolyLineElement elem( *it, nextIndex ); this->AppendPointToPolyLine( 0, elem ); } subdivisionPoints.clear(); } + + bool mitk::PlanarSubdivisionPolygon::Equals(const mitk::PlanarFigure& other) const + { + const mitk::PlanarSubdivisionPolygon* otherSubDivPoly = dynamic_cast(&other); + if ( otherSubDivPoly ) + { + if ( this->m_SubdivisionRounds != otherSubDivPoly->m_SubdivisionRounds) + return false; + if ( std::abs(this->m_TensionParameter - otherSubDivPoly->m_TensionParameter) > mitk::eps) + return false; + return Superclass::Equals(other); + } + else + { + return false; + } + } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.h b/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.h index 9d086cb88c..8f17c66805 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarSubdivisionPolygon.h @@ -1,107 +1,109 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PLANAR_SUBDIVISION_POLYGON_H_ #define _MITK_PLANAR_SUBDIVISION_POLYGON_H_ #include "mitkPlanarFigure.h" #include #include "mitkPlanarPolygon.h" namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a polygon * with two or more control points */ class MitkPlanarFigure_EXPORT PlanarSubdivisionPolygon : public PlanarPolygon { public: mitkClassMacro( PlanarSubdivisionPolygon, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Subdivision Polygon has 3 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const { return 3; } /** \brief Polygon maximum number of control points is principally not limited. */ unsigned int GetMaximumNumberOfControlPoints() const { return 1000; } /** \brief How many times should we generate a round of subdivisions? */ unsigned int GetSubdivisionRounds() const { return m_SubdivisionRounds; } void SetSubdivisionRounds( int subdivisionRounds ) { m_SubdivisionRounds = subdivisionRounds; } /** \brief Parameter w_tension defines the tension. * the higher w_tension, the lower the "tension" on points. * Rule: 0 < w_tension < 0.1 * 0.0625 (1 / 16) seems to be a good value. */ float GetTensionParameter() const { return m_TensionParameter; } void SetTensionParameter(float tensionParameter ) { m_TensionParameter = tensionParameter; } std::vector CheckForLineIntersection( const Point2D& p1, const Point2D& p2 ) const; void IncreaseSubdivisions(); void DecreaseSubdivisions(); + virtual bool Equals(const mitk::PlanarFigure& other) const; + protected: PlanarSubdivisionPolygon(); virtual ~PlanarSubdivisionPolygon(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine(); float m_TensionParameter; int m_SubdivisionRounds; private: }; } // namespace mitk #endif //_MITK_PLANAR_SUBDIVISION_POLYGON_H_ diff --git a/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp b/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp index 8937d098e9..431e4cbbc2 100644 --- a/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp +++ b/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp @@ -1,469 +1,479 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPlanarFigureReader.h" #include "mitkPlanarAngle.h" #include "mitkPlanarCircle.h" #include "mitkPlanarLine.h" #include "mitkPlanarArrow.h" #include "mitkPlanarCross.h" #include "mitkPlanarFourPointAngle.h" #include "mitkPlanarPolygon.h" #include "mitkPlanarSubdivisionPolygon.h" #include "mitkPlanarRectangle.h" #include "mitkPlaneGeometry.h" #include "mitkPlanarEllipse.h" #include "mitkPlanarDoubleEllipse.h" #include "mitkPlanarBezierCurve.h" #include "mitkBasePropertySerializer.h" #include #include mitk::PlanarFigureReader::PlanarFigureReader() : PlanarFigureSource(), FileReader(), m_FileName(""), m_FilePrefix(""), m_FilePattern(""), m_Success(false) { this->SetNumberOfRequiredOutputs(1); this->SetNumberOfIndexedOutputs(1); this->SetNthOutput(0, this->MakeOutput(0)); m_CanReadFromMemory = true; //this->Modified(); //this->GetOutput()->Modified(); //this->GetOutput()->ReleaseData(); } mitk::PlanarFigureReader::~PlanarFigureReader() {} void mitk::PlanarFigureReader::GenerateData() { const std::string& locale = "C"; const std::string& currLocale = setlocale( LC_ALL, NULL ); if ( locale.compare(currLocale)!=0 ) { try { setlocale(LC_ALL, locale.c_str()); } catch(...) { MITK_INFO << "Could not set locale " << locale; } } m_Success = false; this->SetNumberOfIndexedOutputs(0); // reset all outputs, we add new ones depending on the file content TiXmlDocument document; if(m_ReadFromMemory) { if(m_MemoryBuffer == NULL || m_MemorySize == 0) { //check itkWarningMacro( << "Sorry, memory buffer has not been set!" ); return; } if(m_MemoryBuffer[ m_MemorySize - 1 ] == '\0') { document.Parse(m_MemoryBuffer); } else { char * tmpArray = new char[(int)m_MemorySize+1]; tmpArray[m_MemorySize] = '\0'; memcpy(tmpArray,m_MemoryBuffer,m_MemorySize); document.Parse(m_MemoryBuffer); delete [] tmpArray; } } else { if (m_FileName.empty()) { itkWarningMacro( << "Sorry, filename has not been set!" ); return; } if (this->CanReadFile( m_FileName.c_str()) == false) { itkWarningMacro( << "Sorry, can't read file " << m_FileName << "!" ); return; } if (!document.LoadFile(m_FileName)) { MITK_ERROR << "Could not open/read/parse " << m_FileName << ". TinyXML reports: '" << document.ErrorDesc() << "'. " << "The error occurred in row " << document.ErrorRow() << ", column " << document.ErrorCol() << "."; return; } } int fileVersion = 1; TiXmlElement* versionObject = document.FirstChildElement("Version"); if (versionObject != NULL) { if ( versionObject->QueryIntAttribute( "FileVersion", &fileVersion ) != TIXML_SUCCESS ) { MITK_WARN << m_FileName << " does not contain version information! Trying version 1 format." << std::endl; } } else { MITK_WARN << m_FileName << " does not contain version information! Trying version 1 format." << std::endl; } if (fileVersion != 1) // add file version selection and version specific file parsing here, if newer file versions are created { MITK_WARN << "File version > 1 is not supported by this reader."; return; } /* file version 1 reader code */ for( TiXmlElement* pfElement = document.FirstChildElement("PlanarFigure"); pfElement != NULL; pfElement = pfElement->NextSiblingElement("PlanarFigure") ) { if (pfElement == NULL) continue; std::string type = pfElement->Attribute("type"); mitk::PlanarFigure::Pointer planarFigure = NULL; if (type == "PlanarAngle") { planarFigure = mitk::PlanarAngle::New(); } else if (type == "PlanarCircle") { planarFigure = mitk::PlanarCircle::New(); } else if (type == "PlanarEllipse") { planarFigure = mitk::PlanarEllipse::New(); } else if (type == "PlanarCross") { planarFigure = mitk::PlanarCross::New(); } else if (type == "PlanarFourPointAngle") { planarFigure = mitk::PlanarFourPointAngle::New(); } else if (type == "PlanarLine") { planarFigure = mitk::PlanarLine::New(); } else if (type == "PlanarPolygon") { planarFigure = mitk::PlanarPolygon::New(); } else if (type == "PlanarSubdivisionPolygon") { planarFigure = mitk::PlanarSubdivisionPolygon::New(); } else if (type == "PlanarRectangle") { planarFigure = mitk::PlanarRectangle::New(); } else if (type == "PlanarArrow") { planarFigure = mitk::PlanarArrow::New(); } else if (type == "PlanarDoubleEllipse") { planarFigure = mitk::PlanarDoubleEllipse::New(); } else if (type == "PlanarBezierCurve") { planarFigure = mitk::PlanarBezierCurve::New(); } else { // unknown type MITK_WARN << "encountered unknown planar figure type '" << type << "'. Skipping this element."; continue; } // Read properties of the planar figure for( TiXmlElement* propertyElement = pfElement->FirstChildElement("property"); propertyElement != NULL; propertyElement = propertyElement->NextSiblingElement("property") ) { const char* keya = propertyElement->Attribute("key"); std::string key( keya ? keya : ""); const char* typea = propertyElement->Attribute("type"); std::string type( typea ? typea : ""); // hand propertyElement to specific reader std::stringstream propertyDeserializerClassName; propertyDeserializerClassName << type << "Serializer"; std::list readers = itk::ObjectFactoryBase::CreateAllInstance(propertyDeserializerClassName.str().c_str()); if (readers.size() < 1) { MITK_ERROR << "No property reader found for " << type; } if (readers.size() > 1) { MITK_WARN << "Multiple property readers found for " << type << ". Using arbitrary first one."; } for ( std::list::iterator iter = readers.begin(); iter != readers.end(); ++iter ) { if (BasePropertySerializer* reader = dynamic_cast( iter->GetPointer() ) ) { BaseProperty::Pointer property = reader->Deserialize( propertyElement->FirstChildElement() ); if (property.IsNotNull()) { planarFigure->GetPropertyList()->ReplaceProperty(key, property); } else { MITK_ERROR << "There were errors while loading property '" << key << "' of type " << type << ". Your data may be corrupted"; } break; } } } // If we load a planarFigure, it has definitely been placed correctly. // If we do not set this property here, we cannot load old planarFigures // without messing up the interaction (PF-Interactor needs this property. planarFigure->GetPropertyList()->SetBoolProperty( "initiallyplaced", true ); + // Which features (length or circumference etc) a figure has is decided by whether it is closed or not + // the function SetClosed has to be called in case of PlanarPolygons to ensure they hold the correct feature + PlanarPolygon* planarPolygon = dynamic_cast (planarFigure.GetPointer()); + if (planarPolygon != NULL) + { + bool isClosed = false; + planarFigure->GetPropertyList()->GetBoolProperty( "closed", isClosed); + planarPolygon->SetClosed(isClosed); + } + // Read geometry of containing plane TiXmlElement* geoElement = pfElement->FirstChildElement("Geometry"); if (geoElement != NULL) { try { // Create plane geometry mitk::PlaneGeometry::Pointer planeGeo = mitk::PlaneGeometry::New(); // Extract and set plane transform parameters DoubleList transformList = this->GetDoubleAttributeListFromXMLNode( geoElement->FirstChildElement( "transformParam" ), "param", 12 ); typedef mitk::BaseGeometry::TransformType TransformType; TransformType::ParametersType parameters; parameters.SetSize( 12 ); unsigned int i; DoubleList::iterator it; for ( it = transformList.begin(), i = 0; it != transformList.end(); ++it, ++i ) { parameters.SetElement( i, *it ); } typedef mitk::BaseGeometry::TransformType TransformType; TransformType::Pointer affineGeometry = TransformType::New(); affineGeometry->SetParameters( parameters ); planeGeo->SetIndexToWorldTransform( affineGeometry ); // Extract and set plane bounds DoubleList boundsList = this->GetDoubleAttributeListFromXMLNode( geoElement->FirstChildElement( "boundsParam" ), "bound", 6 ); typedef mitk::BaseGeometry::BoundsArrayType BoundsArrayType; BoundsArrayType bounds; for ( it = boundsList.begin(), i = 0; it != boundsList.end(); ++it, ++i ) { bounds[i] = *it; } planeGeo->SetBounds( bounds ); // Extract and set spacing and origin Vector3D spacing = this->GetVectorFromXMLNode(geoElement->FirstChildElement("Spacing")); planeGeo->SetSpacing( spacing ); Point3D origin = this->GetPointFromXMLNode(geoElement->FirstChildElement("Origin")); planeGeo->SetOrigin( origin ); planarFigure->SetPlaneGeometry(planeGeo); } catch (...) { } } TiXmlElement* cpElement = pfElement->FirstChildElement("ControlPoints"); bool first = true; if (cpElement != NULL) for( TiXmlElement* vertElement = cpElement->FirstChildElement("Vertex"); vertElement != NULL; vertElement = vertElement->NextSiblingElement("Vertex")) { if (vertElement == NULL) continue; int id = 0; mitk::Point2D::ValueType x = 0.0; mitk::Point2D::ValueType y = 0.0; if (vertElement->QueryIntAttribute("id", &id) == TIXML_WRONG_TYPE) return; // TODO: can we do a better error handling? if (vertElement->QueryDoubleAttribute("x", &x) == TIXML_WRONG_TYPE) return; // TODO: can we do a better error handling? if (vertElement->QueryDoubleAttribute("y", &y) == TIXML_WRONG_TYPE) return; // TODO: can we do a better error handling? Point2D p; p.SetElement(0, x); p.SetElement(1, y); if (first == true) // needed to set m_FigurePlaced to true { planarFigure->PlaceFigure(p); first = false; } planarFigure->SetControlPoint(id, p, true); } // Calculate feature quantities of this PlanarFigure planarFigure->EvaluateFeatures(); // Make sure that no control point is currently selected planarFigure->DeselectControlPoint(); // \TODO: what about m_FigurePlaced and m_SelectedControlPoint ?? this->SetNthOutput( this->GetNumberOfOutputs(), planarFigure ); // add planarFigure as new output of this filter } try { setlocale(LC_ALL, currLocale.c_str()); } catch(...) { MITK_INFO << "Could not reset locale " << currLocale; } m_Success = true; } mitk::Point3D mitk::PlanarFigureReader::GetPointFromXMLNode(TiXmlElement* e) { if (e == NULL) throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling? mitk::Point3D point; mitk::ScalarType p(-1.0); if (e->QueryDoubleAttribute("x", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? point.SetElement(0, p); if (e->QueryDoubleAttribute("y", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? point.SetElement(1, p); if (e->QueryDoubleAttribute("z", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? point.SetElement(2, p); return point; } mitk::Vector3D mitk::PlanarFigureReader::GetVectorFromXMLNode(TiXmlElement* e) { if (e == NULL) throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling? mitk::Vector3D vector; mitk::ScalarType p(-1.0); if (e->QueryDoubleAttribute("x", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? vector.SetElement(0, p); if (e->QueryDoubleAttribute("y", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? vector.SetElement(1, p); if (e->QueryDoubleAttribute("z", &p) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? vector.SetElement(2, p); return vector; } mitk::PlanarFigureReader::DoubleList mitk::PlanarFigureReader::GetDoubleAttributeListFromXMLNode(TiXmlElement* e, const char *attributeNameBase, unsigned int count) { DoubleList list; if (e == NULL) throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling? for ( unsigned int i = 0; i < count; ++i ) { mitk::ScalarType p(-1.0); std::stringstream attributeName; attributeName << attributeNameBase << i; if (e->QueryDoubleAttribute( attributeName.str().c_str(), &p ) == TIXML_WRONG_TYPE) throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling? list.push_back( p ); } return list; } void mitk::PlanarFigureReader::GenerateOutputInformation() { } int mitk::PlanarFigureReader::CanReadFile ( const char *name ) { if (std::string(name).empty()) return false; return (itksys::SystemTools::LowerCase(itksys::SystemTools::GetFilenameLastExtension(name)) == ".pf"); //assume, we can read all .pf files //TiXmlDocument document(name); //if (document.LoadFile() == false) // return false; //return (document.FirstChildElement("PlanarFigure") != NULL); } bool mitk::PlanarFigureReader::CanReadFile(const std::string filename, const std::string, const std::string) { if (filename.empty()) return false; return (itksys::SystemTools::LowerCase(itksys::SystemTools::GetFilenameLastExtension(filename)) == ".pf"); //assume, we can read all .pf files //TiXmlDocument document(filename); //if (document.LoadFile() == false) // return false; //return (document.FirstChildElement("PlanarFigure") != NULL); } void mitk::PlanarFigureReader::ResizeOutputs( const unsigned int& num ) { unsigned int prevNum = this->GetNumberOfOutputs(); this->SetNumberOfIndexedOutputs( num ); for ( unsigned int i = prevNum; i < num; ++i ) { this->SetNthOutput( i, this->MakeOutput( i ).GetPointer() ); } } diff --git a/Modules/PlanarFigure/Testing/files.cmake b/Modules/PlanarFigure/Testing/files.cmake index 3e8d679a26..5c683c9c48 100644 --- a/Modules/PlanarFigure/Testing/files.cmake +++ b/Modules/PlanarFigure/Testing/files.cmake @@ -1,12 +1,19 @@ set(MODULE_TESTS mitkPlanarCrossTest.cpp mitkPlanarPolygonTest.cpp mitkPlanarSubdivisionPolygonTest.cpp mitkPlanarFigureIOTest.cpp mitkPlanarFigureObjectFactoryTest.cpp mitkPlanarArrowTest.cpp ) set(MODULE_CUSTOM_TESTS mitkViewportRenderingTest.cpp ) + +if(MITK_ENABLE_RENDERING_TESTING) #since mitkInteractionTestHelper is currently creating a vtkRenderWindow +set(MODULE_TESTS + ${MODULE_TESTS} + mitkPlanarFigureInteractionTest.cpp +) +endif() diff --git a/Modules/PlanarFigure/Testing/mitkPlanarFigureInteractionTest.cpp b/Modules/PlanarFigure/Testing/mitkPlanarFigureInteractionTest.cpp new file mode 100644 index 0000000000..8812bbddbf --- /dev/null +++ b/Modules/PlanarFigure/Testing/mitkPlanarFigureInteractionTest.cpp @@ -0,0 +1,189 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkTestingMacros.h" +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usModuleRegistry.h" + +class mitkPlanarFigureInteractionTestSuite : public mitk::TestFixture +{ + + CPPUNIT_TEST_SUITE(mitkPlanarFigureInteractionTestSuite); + MITK_TEST(AngleInteractionCreate); + MITK_TEST(BezierCurveInteractionCreate); + MITK_TEST(CircleInteractionCreate); + MITK_TEST(DoubleEllipseInteractionCreate); + MITK_TEST(PlanarFourPointAngleInteractionCreate); + MITK_TEST(PlanarLineInteractionCreate); + MITK_TEST(PlanarPolygonInteractionCreate); + MITK_TEST(NonClosedPlanarPolygonInteractionCreate); + MITK_TEST(RectangleInteractionCreate); + MITK_TEST(PlanarSubdivisionInteractionCreate); + + CPPUNIT_TEST_SUITE_END(); + + +public: + + void setUp() + { + } + + void tearDown() + { + } + + void RunTest(mitk::PlanarFigure::Pointer figure, std::string interactionXmlPath, std::string referenceFigurePath) + { + mitk::DataNode::Pointer node; + mitk::PlanarFigureInteractor::Pointer figureInteractor; + + //Create DataNode as a container for our PlanarFigure + node = mitk::DataNode::New(); + node->SetData(figure); + + mitk::InteractionTestHelper interactionTestHelper(GetTestDataFilePath(interactionXmlPath)); + + + //Load a bounding image + mitk::Image::Pointer testImage = mitk::IOUtil::LoadImage(GetTestDataFilePath("InteractionTestData/InputData/scaledSingleSlice.nrrd")); + + mitk::DataNode::Pointer dn = mitk::DataNode::New(); + dn->SetData(testImage); + interactionTestHelper.AddNodeToStorage(dn); + interactionTestHelper.GetDataStorage()->Add(node, dn); + + + node->SetName("PLANAR FIGURE"); + // set as selected + node->SetSelected(true); + node->AddProperty("selected", mitk::BoolProperty::New(true)); + + //Load state machine + figureInteractor = mitk::PlanarFigureInteractor::New(); + us::Module* planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); + figureInteractor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule ); + figureInteractor->SetEventConfig( "PlanarFigureConfig.xml", planarFigureModule ); + figureInteractor->SetDataNode( node ); + + + //Start Interaction + interactionTestHelper.PlaybackInteraction(); + + //Load reference PlanarFigure + mitk::PlanarFigureReader::Pointer reader = mitk::PlanarFigureReader::New(); + reader->SetFileName(GetTestDataFilePath(referenceFigurePath)); + reader->Update(); + mitk::PlanarFigure::Pointer reference = reader->GetOutput(0); + + //Compare figures + MITK_ASSERT_EQUAL(figure, reference, "Compare figure with reference"); + } + + void AngleInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarAngle::New(); + RunTest(figure, "InteractionTestData/Interactions/Angle.xml", "InteractionTestData/ReferenceData/Angle.pf"); + } + + void BezierCurveInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarBezierCurve::New(); + RunTest(figure, "InteractionTestData/Interactions/Bezier.xml", "InteractionTestData/ReferenceData/Bezier.pf"); + } + + void CircleInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarCircle::New(); + RunTest(figure, "InteractionTestData/Interactions/Circle.xml", "InteractionTestData/ReferenceData/Circle.pf"); + } + + void DoubleEllipseInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarDoubleEllipse::New(); + RunTest(figure, "InteractionTestData/Interactions/DoubleEllipse.xml", "InteractionTestData/ReferenceData/DoubleEllipse.pf"); + } + + void PlanarSubdivisionInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarSubdivisionPolygon::New(); + RunTest(figure, "InteractionTestData/Interactions/SubDivision.xml", "InteractionTestData/ReferenceData/SubDivision.pf"); + } + + void PlanarFourPointAngleInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarFourPointAngle::New(); + RunTest(figure, "InteractionTestData/Interactions/Planar4PointAngle.xml", "InteractionTestData/ReferenceData/Planar4PointAngle.pf"); + } + + void PlanarLineInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarLine::New(); + RunTest(figure, "InteractionTestData/Interactions/Line.xml", "InteractionTestData/ReferenceData/Line.pf"); + } + + void PlanarPolygonInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarPolygon::New(); + RunTest(figure, "InteractionTestData/Interactions/Polygon.xml", "InteractionTestData/ReferenceData/Polygon.pf"); + } + + void NonClosedPlanarPolygonInteractionCreate() + { + mitk::PlanarPolygon::Pointer figure; + figure = mitk::PlanarPolygon::New(); + figure->ClosedOff(); + RunTest(figure.GetPointer(), "InteractionTestData/Interactions/Path.xml", "InteractionTestData/ReferenceData/Path.pf"); + } + + void RectangleInteractionCreate() + { + mitk::PlanarFigure::Pointer figure; + figure = mitk::PlanarRectangle::New(); + RunTest(figure, "InteractionTestData/Interactions/Rectangle.xml", "InteractionTestData/ReferenceData/Rectangle.pf"); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkPlanarFigureInteraction)