diff --git a/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp b/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp index da351f5580..99ce9d6302 100644 --- a/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp +++ b/Modules/PlanarFigure/IO/mitkPlanarFigureReader.cpp @@ -1,429 +1,434 @@ /*=================================================================== 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 "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() { 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 { // 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 ); + // 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::AffineGeometryFrame3D::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::AffineGeometryFrame3D::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::Geometry3D::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->SetGeometry2D(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->QueryFloatAttribute("x", &x) == TIXML_WRONG_TYPE) return; // TODO: can we do a better error handling? if (vertElement->QueryFloatAttribute("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 } 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->QueryFloatAttribute("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->QueryFloatAttribute("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->QueryFloatAttribute("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->QueryFloatAttribute("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->QueryFloatAttribute("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->QueryFloatAttribute("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->QueryFloatAttribute( 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/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp index c012720609..be5a45deee 100644 --- a/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,952 +1,956 @@ /*=================================================================== 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. ===================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkPlanarCircle.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" //how precise must the user pick the point //default value mitk::PlanarFigureInteractor::PlanarFigureInteractor() : DataInteractor() , m_Precision( 6.5 ) , m_MinimumPointDistance( 25.0 ) , m_IsHovering( false ) , m_LastPointWasValid( false ) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_FUNCTION( "finalize_figure", FinalizeFigure); CONNECT_FUNCTION( "hide_preview_point", HidePreviewPoint ) CONNECT_FUNCTION( "set_preview_point_position", SetPreviewPointPosition ) CONNECT_FUNCTION( "move_current_point", MoveCurrentPoint); CONNECT_FUNCTION( "deselect_point", DeselectPoint); CONNECT_FUNCTION( "add_new_point", AddPoint); CONNECT_FUNCTION( "add_initial_point", AddInitialPoint); CONNECT_FUNCTION( "remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION( "request_context_menu", RequestContextMenu); CONNECT_FUNCTION( "select_figure", SelectFigure ); CONNECT_FUNCTION( "select_point", SelectPoint ); CONNECT_FUNCTION( "end_interaction", EndInteraction ); CONNECT_FUNCTION( "start_hovering", StartHovering ) CONNECT_FUNCTION( "end_hovering", EndHovering ); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced( const InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); - return planarFigure->IsPlaced(); + + bool isFigureFinished = false; + planarFigure->GetPropertyList()->GetBoolProperty( "initiallyplaced", isFigureFinished ); + + return planarFigure->IsPlaced() && isFigureFinished; } bool mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; bool isEditable = true; GetDataNode()->GetBoolProperty( "planarfigure.iseditable", isEditable ); mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D; if ( !this->TransformPositionEventToPoint2D( positionEvent, planarFigureGeometry, point2D ) || !isEditable ) { return false; } // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty( "planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction ); // hide the control points if necessary //interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty( "planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction ); //interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint( point2D ); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene interactionEvent->GetSender()->GetRenderingManager()->RequestUpdateAll(); return true; } bool mitk::PlanarFigureInteractor::FinalizeFigure( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty( "initiallyplaced", mitk::BoolProperty::New( true ) ); GetDataNode()->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); GetDataNode()->Modified(); planarFigure->InvokeEvent( EndPlacementPlanarFigureEvent() ); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); interactionEvent->GetSender()->GetRenderingManager()->RequestUpdateAll(); return false; } bool mitk::PlanarFigureInteractor::EndInteraction( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); GetDataNode()->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); planarFigure->Modified(); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); interactionEvent->GetSender()->GetRenderingManager()->RequestUpdateAll(); return false; } bool mitk::PlanarFigureInteractor::EndHovering( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent( EndHoverPlanarFigureEvent() ); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty( "planarfigure.ishovering", false ); interactionEvent->GetSender()->GetRenderingManager()->RequestUpdateAll(); return false; } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished( const InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); return ( planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints() ); } bool mitk::PlanarFigureInteractor::CheckFigureFinished( const InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); return ( planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints() ); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable( const InteractionEvent* interactionEvent ) { bool isExtendable = false; GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); bool wasSelected = planarFigure->DeselectControlPoint(); if ( wasSelected ) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); GetDataNode()->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); // GetDataNode()->SetBoolProperty( "planarfigure.ishovering", false ); GetDataNode()->Modified(); } return true; } bool mitk::PlanarFigureInteractor::AddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; bool selected = false; bool isEditable = true; GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty( "planarfigure.iseditable", isEditable ); if ( !selected || !isEditable ) { return false; } mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); // If the planarFigure already has reached the maximum number if ( planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints() ) { return false; } // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D, projectedPoint; if ( !this->TransformPositionEventToPoint2D( positionEvent, planarFigureGeometry, point2D ) ) { return false; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are neccessary because of a mac os x specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty( "initiallyplaced", isFigureFinished ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); if ( dynamic_cast( planarFigure ) && isFigureFinished) { nextIndex = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry(), projectedPoint ); } // Add point as new control point renderer->GetDisplayGeometry()->DisplayToWorld( projectedPoint, projectedPoint ); if ( planarFigure->IsPreviewControlPointVisible() ) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint( point2D, nextIndex ); if ( planarFigure->IsPreviewControlPointVisible() ) { planarFigure->SelectControlPoint( nextIndex ); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); //this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); return true; } bool mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent( StartPlacementPlanarFigureEvent() ); // Use Geometry2D of the renderer clicked on for this PlanarFigure mitk::PlaneGeometry *planeGeometry = const_cast< mitk::PlaneGeometry * >( dynamic_cast< const mitk::PlaneGeometry * >( renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry() ) ); if ( planeGeometry != NULL ) { planarFigureGeometry = planeGeometry; planarFigure->SetGeometry2D( planeGeometry ); } else { return false; } // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D; if ( !this->TransformPositionEventToPoint2D( positionEvent, planarFigureGeometry, point2D ) ) { return false; } // Place PlanarFigure at this point planarFigure->PlaceFigure( point2D ); // Re-evaluate features planarFigure->EvaluateFeatures(); //this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the Geometry2D of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty( "PlanarFigureInitializedWindow", true, renderer ); // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); return true; } bool mitk::PlanarFigureInteractor::StartHovering( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); if ( !m_IsHovering ) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent( StartHoverPlanarFigureEvent() ); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty( "planarfigure.ishovering", true ); renderer->GetRenderingManager()->RequestUpdateAll(); } return true; } bool mitk::PlanarFigureInteractor::SetPreviewPointPosition( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = mitk::PlanarFigureInteractor::IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry(), pointProjectedOntoLine ); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty( "planarfigure.iseditable", isEditable ); if ( selected && isExtendable && isEditable ) { renderer->GetDisplayGeometry()->DisplayToWorld( pointProjectedOntoLine, pointProjectedOntoLine ); planarFigure->SetPreviewControlPoint( pointProjectedOntoLine ); } renderer->GetRenderingManager()->RequestUpdateAll(); return false; } bool mitk::PlanarFigureInteractor::HidePreviewPoint( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); planarFigure->ResetPreviewContolPoint(); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); renderer->GetRenderingManager()->RequestUpdateAll(); return false; } bool mitk::PlanarFigureInteractor::CheckFigureHovering( const InteractionEvent* interactionEvent ) { const mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry(), pointProjectedOntoLine ); bool isHovering = (previousControlPoint != -1); if ( isHovering ) { return true; } else { return false; } return false; } bool mitk::PlanarFigureInteractor::CheckControlPointHovering( const InteractionEvent* interactionEvent ) { const mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry() ); if ( pointIndex >= 0 ) { return true; } else { return false; } } bool mitk::PlanarFigureInteractor::CheckSelection( const InteractionEvent* interactionEvent ) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } bool mitk::PlanarFigureInteractor::SelectFigure( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); planarFigure->InvokeEvent( SelectPlanarFigureEvent() ); return false; } bool mitk::PlanarFigureInteractor::SelectPoint( StateMachineAction*, InteractionEvent* interactionEvent ) { mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); const Geometry2D *projectionPlane = renderer->GetCurrentWorldGeometry2D(); int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry() ); if ( pointIndex >= 0 ) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint( pointIndex ); } else { planarFigure->DeselectControlPoint(); } return false; } bool mitk::PlanarFigureInteractor::CheckPointValidity( const InteractionEvent* interactionEvent ) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) // Extract display position const mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); if ( positionEvent == NULL ) return false; mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); m_LastPointWasValid = IsMousePositionAcceptableAsNewControlPoint( positionEvent, planarFigure ); return m_LastPointWasValid; } bool mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint( selectedControlPoint ); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); renderer->GetRenderingManager()->RequestUpdateAll(); HandleEvent( mitk::InternalEvent::New( renderer, this, "Dummy-Event" ), GetDataNode() ); return true; } bool mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction*, InteractionEvent* interactionEvent) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if ( !selected ) { planarFigure->InvokeEvent( SelectPlanarFigureEvent() ); } planarFigure->InvokeEvent( ContextMenuPlanarFigureEvent() ); return true; } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect( const InteractionEvent* interactionEvent ) { mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); // Invoke tmpEvent to notify listeners that interaction with this PF starts now planarFigure->InvokeEvent( StartInteractionPlanarFigureEvent() ); // Reset the PlanarFigure if required return planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry( const InteractionEvent* interactionEvent ) { const mitk::InteractionPositionEvent* posEvent = dynamic_cast(interactionEvent); if ( posEvent == NULL ) return false; mitk::Point3D worldPoint3D = posEvent->GetPositionInWorld(); mitk::PlanarFigure *planarFigure = dynamic_cast( GetDataNode()->GetData() ); mitk::Geometry2D *planarFigureGeometry2D = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); double planeThickness = planarFigureGeometry2D->GetExtentInMM( 2 ); double distanceToFigure = planarFigureGeometry2D->Distance( worldPoint3D ); if ( distanceToFigure > planeThickness ) { // don't react, when interaction is too far away return false; } return true; } void mitk::PlanarFigureInteractor::SetPrecision( mitk::ScalarType precision ) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance( ScalarType minimumDistance ) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D( const InteractionPositionEvent *positionEvent, const Geometry2D *planarFigureGeometry, Point2D &point2D ) { mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if ( planarFigureGeometry->Distance( worldPoint3D ) > 0.1 ) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map( worldPoint3D, point2D ); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay( const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::Geometry2D *objectGeometry, const mitk::Geometry2D *rendererGeometry, const mitk::DisplayGeometry *displayGeometry ) const { mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map( point2D, point3D ); double planeThickness = objectGeometry->GetExtentInMM( 2 ); // TODO: proper handling of distance tolerance if ( displayGeometry->Distance( point3D ) < planeThickness / 3.0 ) { // Project 3D world point onto display geometry rendererGeometry->Map( point3D, displayPoint ); displayGeometry->WorldToDisplay( displayPoint, displayPoint ); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine( const mitk::Point2D& point, const mitk::Point2D& startPoint, const mitk::Point2D& endPoint, mitk::Point2D& projectedPoint ) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors double l1 = n1 * (point - startPoint); double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((crossPoint.SquaredEuclideanDistanceTo(point) < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || endPoint.SquaredEuclideanDistanceTo(point) < 20.0 || startPoint.SquaredEuclideanDistanceTo(point) < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure( const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const Geometry2D *planarFigureGeometry, const Geometry2D *rendererGeometry, const DisplayGeometry *displayGeometry, Point2D& pointProjectedOntoLine ) const { mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for ( unsigned short loop=0; loopGetPolyLinesSize(); ++loop ) { const VertexContainerType polyLine = planarFigure->GetPolyLine( loop ); bool firstPoint( true ); for ( VertexContainerType::const_iterator it = polyLine.begin(); it != polyLine.end(); ++it ) { // Get plane coordinates of this point of polyline (if possible) if ( !this->TransformObjectToDisplay( it->Point, polyLinePoint, planarFigureGeometry, rendererGeometry, displayGeometry ) ) { break; // Poly line invalid (not on current 2D plane) --> skip it } if ( firstPoint ) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if ( this->IsPointNearLine( displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine ) ) { // Point is close enough to line segment --> Return index of the segment return it->Index; } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if ( planarFigure->IsClosed() && this->IsPointNearLine( displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine ) ) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker( const InteractionPositionEvent* positionEvent, const PlanarFigure *planarFigure, const Geometry2D *planarFigureGeometry, const Geometry2D *rendererGeometry, const DisplayGeometry *displayGeometry ) const { mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for ( int i=0; iTransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, displayGeometry ) ) { // TODO: variable size of markers if ( displayPosition.SquaredEuclideanDistanceTo( displayControlPoint ) < 20.0 ) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities( const PlanarFigure *planarFigure ) { MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for ( unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i ) { MITK_INFO << "* " << planarFigure->GetFeatureName( i ) << ": " << planarFigure->GetQuantity( i ) << " " << planarFigure->GetFeatureUnit( i ); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent* positionEvent, const PlanarFigure* planarFigure ) { assert(positionEvent && planarFigure); BaseRenderer* renderer = positionEvent->GetSender(); assert(renderer); // Get the timestep to support 3D+t int timeStep( renderer->GetTimeStep( planarFigure ) ); bool tooClose(false); const Geometry2D *renderingPlane = renderer->GetCurrentWorldGeometry2D(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< mitk::Geometry2D * >( planarFigure->GetGeometry( timeStep ) ); Point2D point2D, correctedPoint; // Get the point2D from the positionEvent if ( !this->TransformPositionEventToPoint2D( positionEvent, planarFigureGeometry, point2D ) ) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. correctedPoint = const_cast( planarFigure )->ApplyControlPointConstraints( 0, point2D ); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map( correctedPoint, newPoint3D ); mitk::Point2D newDisplayPosition; renderingPlane->Map( newPoint3D, newDisplayPosition ); renderer->GetDisplayGeometry()->WorldToDisplay( newDisplayPosition, newDisplayPosition ); for( int i=0; i < (int)planarFigure->GetNumberOfControlPoints(); i++ ) { if ( i != planarFigure->GetSelectedControlPoint() ) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map( planarFigure->GetControlPoint( i ), previousPoint3D ); if ( renderer->GetDisplayGeometry()->Distance( previousPoint3D ) < 0.1 ) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderingPlane->Map( previousPoint3D, previousDisplayPosition ); renderer->GetDisplayGeometry()->WorldToDisplay( previousDisplayPosition, previousDisplayPosition ); //Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. double a = newDisplayPosition[0] - previousDisplayPosition[0]; double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance ); } if ( tooClose ) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType) 6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType) 25.0; } }