diff --git a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp index cc0d69209b..bcd3dcaf22 100644 --- a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp @@ -1,951 +1,951 @@ /*=================================================================== 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 "mitkDisplayInteractor.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkInteractionPositionEvent.h" #include "mitkPropertyList.h" #include #include #include // level window #include "mitkLevelWindow.h" #include "mitkLevelWindowProperty.h" #include "mitkLine.h" #include "mitkNodePredicateDataType.h" #include "mitkStandaloneDataStorage.h" #include "vtkRenderWindowInteractor.h" // Rotation #include "mitkInteractionConst.h" #include "rotate_cursor.xpm" #include #include #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkStatusBar.h" #include void mitk::DisplayInteractor::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { HandleEvent(interactionEvent, nullptr); } } mitk::DisplayInteractor::DisplayInteractor() : m_IndexToSliceModifier(4) , m_AutoRepeat(false) , m_InvertScrollDirection(false) , m_InvertZoomDirection(false) , m_InvertMoveDirection(false) , m_InvertLevelWindowDirection(false) , m_AlwaysReact(false) , m_ZoomFactor(2) , m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); } mitk::DisplayInteractor::~DisplayInteractor() { // nothing here } void mitk::DisplayInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); CONNECT_FUNCTION("IncreaseTimeStep", IncreaseTimeStep); CONNECT_FUNCTION("DecreaseTimeStep", DecreaseTimeStep); } bool mitk::DisplayInteractor::CheckPositionEvent(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return false; } return true; } bool mitk::DisplayInteractor::CheckRotationPossible(const mitk::InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO interesection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const auto *posEvent = dynamic_cast(interactionEvent); if (posEvent == nullptr) return false; BaseRenderer *clickedRenderer = posEvent->GetSender(); const PlaneGeometry *ourViewportGeometry = (clickedRenderer->GetCurrentWorldPlaneGeometry()); if (!ourViewportGeometry) return false; Point3D cursorPosition = posEvent->GetPositionInWorld(); const auto spacing = ourViewportGeometry->GetSpacing(); const PlaneGeometry *geometryToBeRotated = nullptr; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = nullptr; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const double threshholdDistancePixels = 12.0; auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; const PlaneGeometry *otherRenderersRenderPlane = snc->GetCurrentPlaneGeometry(); if (otherRenderersRenderPlane == nullptr) continue; // ignore, we don't see a plane // check if there is an intersection Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed if (!ourViewportGeometry->IntersectionLine(otherRenderersRenderPlane, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line const double distanceFromIntersectionLine = intersectionLine.Distance(cursorPosition) / spacing[snc->GetDefaultViewDirection()]; // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just // need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used if (m_LinkPlanes) { m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (geometryToBeRotated == nullptr) // first one close to the cursor { geometryToBeRotated = otherRenderersRenderPlane; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < mitk::eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we DO have enough information for rotation m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project( cursorPosition); // remember where the last cursor position ON THE LINE has been observed if (anyOtherGeometry->IntersectionPoint( intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) // find center of rotation by intersection with any of the OTHER lines { return true; } else { return false; } } return false; } bool mitk::DisplayInteractor::CheckSwivelPossible(const mitk::InteractionEvent *interactionEvent) { const ScalarType ThresholdDistancePixels = 6.0; // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const auto *posEvent = dynamic_cast(interactionEvent); BaseRenderer *renderer = interactionEvent->GetSender(); if (!posEvent || !renderer) return false; const Point3D &cursor = posEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry *clickedGeometry(nullptr); const PlaneGeometry *otherGeometry1(nullptr); const PlaneGeometry *otherGeometry2(nullptr); auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; // unsigned int slice = (*iter)->GetSlice()->GetPos(); // unsigned int time = (*iter)->GetTime()->GetPos(); const PlaneGeometry *planeGeometry = snc->GetCurrentPlaneGeometry(); if (!planeGeometry) continue; if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = planeGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (otherGeometry1 == nullptr) { otherGeometry1 = planeGeometry; } else { otherGeometry2 = planeGeometry; } if (m_LinkPlanes) { // If planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } mitk::Line3D line; mitk::Point3D point; if ((clickedGeometry != nullptr) && (otherGeometry1 != nullptr) && (otherGeometry2 != nullptr) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(cursor) < ThresholdDistancePixels) { return false; } else { m_ReferenceCursor = posEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = {1.0, 0.0, 0.0}; ScalarType yVector[] = {0.0, 1.0, 0.0}; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayInteractor::Init(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayInteractor::Move(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer *sender = interactionEvent->GetSender(); auto *positionEvent = static_cast(interactionEvent); float invertModifier = -1.0; if (m_InvertMoveDirection) { invertModifier = 1.0; } // perform translation Vector2D moveVector = (positionEvent->GetPointerPositionOnScreen() - m_LastDisplayCoordinate) * invertModifier; moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); sender->GetCameraController()->MoveBy(moveVector); RenderingManager::GetInstance()->RequestUpdate(sender->GetRenderWindow()); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::SetCrosshair(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { auto* positionEvent = static_cast(interactionEvent); Point3D pos = positionEvent->GetPositionInWorld(); const BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard2D && renWin != sender->GetRenderWindow()) { BaseRenderer::GetInstance(renWin)->GetSliceNavigationController()->SelectSliceByPoint(pos); } } } -void mitk::DisplayInteractor::IncreaseTimeStep(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::DisplayInteractor::IncreaseTimeStep(StateMachineAction *, InteractionEvent *) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Next(); } -void mitk::DisplayInteractor::DecreaseTimeStep(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::DisplayInteractor::DecreaseTimeStep(StateMachineAction *, InteractionEvent *) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Previous(); } void mitk::DisplayInteractor::Zoom(StateMachineAction *, InteractionEvent *interactionEvent) { float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } auto* positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); if (factor != 1.0) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); sender->GetCameraController()->Zoom(factor, m_StartCoordinateInMM); RenderingManager::GetInstance()->RequestUpdate(sender->GetRenderWindow()); } } void mitk::DisplayInteractor::Scroll(StateMachineAction *, InteractionEvent *interactionEvent) { auto* positionEvent = static_cast(interactionEvent); mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (sliceNaviController) { int delta = 0; // Scrolling direction if (m_ScrollDirection == "updown") { delta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); } else { delta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); } if (m_InvertScrollDirection) { delta *= -1; } // Set how many pixels the mouse has to be moved to scroll one slice // if we moved less than 'm_IndexToSliceModifier' pixels slice ONE slice only if (delta > 0 && delta < m_IndexToSliceModifier) { delta = m_IndexToSliceModifier; } else if (delta < 0 && delta > -m_IndexToSliceModifier) { delta = -m_IndexToSliceModifier; } delta /= m_IndexToSliceModifier; int newPos = sliceNaviController->GetSlice()->GetPos() + delta; // if auto repeat is on, start at first slice if you reach the last slice and vice versa int maxSlices = sliceNaviController->GetSlice()->GetSteps(); if (m_AutoRepeat) { while (newPos < 0) { newPos += maxSlices; } while (newPos >= maxSlices) { newPos -= maxSlices; } } else { // if the new slice is below 0 we still show slice 0 // due to the stepper using unsigned int we have to do this ourselves if (newPos < 1) { newPos = 0; } } m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // set the new position sliceNaviController->GetSlice()->SetPos(newPos); } } void mitk::DisplayInteractor::ScrollOneDown(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Next(); } } void mitk::DisplayInteractor::ScrollOneUp(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Previous(); } } void mitk::DisplayInteractor::AdjustLevelWindow(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // search for active image mitk::DataStorage::Pointer storage = sender->GetDataStorage(); mitk::DataNode::Pointer node = nullptr; mitk::DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(mitk::NodePredicateDataType::New("Image")); for (unsigned int i = 0; i < allImageNodes->size(); ++i) { bool isActiveImage = false; bool propFound = allImageNodes->at(i)->GetBoolProperty("imageForLevelWindow", isActiveImage); if (propFound && isActiveImage) { node = allImageNodes->at(i); continue; } } if (node.IsNull()) { node = storage->GetNode(mitk::NodePredicateDataType::New("Image")); } if (node.IsNull()) { return; } mitk::LevelWindow lv = mitk::LevelWindow(); node->GetLevelWindow(lv); ScalarType level = lv.GetLevel(); ScalarType window = lv.GetWindow(); int levelIndex = 0; int windowIndex = 1; if (m_LevelDirection != "leftright") { levelIndex = 1; windowIndex = 0; } int directionModifier = 1; if (m_InvertLevelWindowDirection) { directionModifier = -1; } // calculate adjustments from mouse movements level += (m_CurrentDisplayCoordinate[levelIndex] - m_LastDisplayCoordinate[levelIndex]) * static_cast(2) * directionModifier; window += (m_CurrentDisplayCoordinate[windowIndex] - m_LastDisplayCoordinate[windowIndex]) * static_cast(2) * directionModifier; lv.SetLevelWindow(level, window); dynamic_cast(node->GetProperty("levelwindow"))->SetLevelWindow(lv); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::StartRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->SetMouseCursor(rotate_cursor_xpm, 0, 0); } void mitk::DisplayInteractor::EndRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->ResetMouseCursor(); } void mitk::DisplayInteractor::Rotate(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto *posEvent = dynamic_cast(event); if (posEvent == nullptr) return; Point3D cursor = posEvent->GetPositionInWorld(); Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; Vector3D toCursor = cursor - m_CenterOfRotation; // cross product: | A x B | = |A| * |B| * sin(angle) Vector3D axisOfRotation; vnl_vector_fixed vnlDirection = vnl_cross_3d(toCursor.GetVnlVector(), toProjected.GetVnlVector()); axisOfRotation.SetVnlVector(vnlDirection); // scalar product: A * B = |A| * |B| * cos(angle) // tan = sin / cos ScalarType angle = -atan2((double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected)); angle *= 180.0 / vnl_math::pi; m_LastCursorPosition = cursor; // create RotationOperation and apply to all SNCs that should be rotated RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); // iterate the OTHER slice navigation controllers: these are filled in DoDecideBetweenRotationAndSliceSelection for (auto iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&rotationOperation); (*iter)->SendCreatedWorldGeometryUpdate(); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::Swivel(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto *posEvent = dynamic_cast(event); if (!posEvent) return; // Determine relative mouse movement projected onto world space Point2D cursor = posEvent->GetPointerPositionOnScreen(); Vector2D relativeCursor = cursor - m_ReferenceCursor; Vector3D relativeCursorAxis = m_RotationPlaneXVector * relativeCursor[0] + m_RotationPlaneYVector * relativeCursor[1]; // Determine rotation axis (perpendicular to rotation plane and cursor // movement) Vector3D rotationAxis = itk::CrossProduct(m_RotationPlaneNormal, relativeCursorAxis); ScalarType rotationAngle = relativeCursor.GetNorm() / 2.0; // Restore the initial plane pose by undoing the previous rotation // operation RotationOperation op(OpROTATE, m_CenterOfRotation, m_PreviousRotationAxis, -m_PreviousRotationAngle); SNCVector::iterator iter; for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&op); (*iter)->SendCreatedWorldGeometryUpdate(); } } // Apply new rotation operation to all relevant SNCs RotationOperation op2(OpROTATE, m_CenterOfRotation, rotationAxis, rotationAngle); for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { // Retrieve the TimeGeometry of this SliceNavigationController TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; // Execute the new rotation timeGeometry->ExecuteOperation(&op2); // Notify listeners (*iter)->SendCreatedWorldGeometryUpdate(); } } m_PreviousRotationAxis = rotationAxis; m_PreviousRotationAngle = rotationAngle; RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::DisplayInteractor::UpdateStatusbar(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto* posEvent = dynamic_cast(event); if (nullptr == posEvent) { return; } const mitk::BaseRenderer::Pointer baseRenderer = posEvent->GetSender(); TNodePredicateDataType::Pointer isImageData = TNodePredicateDataType::New(); auto globalCurrentTimePoint = baseRenderer->GetTime(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = baseRenderer->GetDataStorage()->GetSubset(isImageData).GetPointer(); if (nodes.IsNull()) { return; } // posEvent->GetPositionInWorld() would return the world position at the // time of initiating the interaction. However, we need to update the // status bar with the position after changing slice. Therefore, we // translate the same display position with the renderer again to // get the new world position. Point3D worldposition; baseRenderer->DisplayToWorld(posEvent->GetPointerPositionOnScreen(), worldposition); mitk::Image::Pointer image3D; mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; int component = 0; node = FindTopmostVisibleNode(nodes, worldposition, globalCurrentTimePoint, baseRenderer); if (node.IsNull()) { return; } bool isBinary(false); node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = baseRenderer->GetDataStorage()->GetSources(node, nullptr, true); if (!sourcenodes->empty()) { topSourceNode = mitk::FindTopmostVisibleNode(sourcenodes, worldposition, globalCurrentTimePoint, baseRenderer); } if (topSourceNode.IsNotNull()) { image3D = dynamic_cast(topSourceNode->GetData()); topSourceNode->GetIntProperty("Image.Displayed Component", component); } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } // get the position and gray value from the image and build up status bar text auto statusBar = StatusBar::GetInstance(); if (image3D.IsNotNull() && statusBar != nullptr) { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(worldposition, p); auto pixelType = image3D->GetChannelDescriptor().GetPixelType().GetPixelType(); if (pixelType == itk::ImageIOBase::RGB || pixelType == itk::ImageIOBase::RGBA) { std::string pixelValue = "Pixel RGB(A) value: "; pixelValue.append(ConvertCompositePixelValueToString(image3D, p)); statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue.c_str()); } else if (pixelType == itk::ImageIOBase::DIFFUSIONTENSOR3D || pixelType == itk::ImageIOBase::SYMMETRICSECONDRANKTENSOR) { std::string pixelValue = "See ODF Details view. "; statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue.c_str()); } else { mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, image3D->GetVolumeData(image3D->GetTimeGeometry()->TimePointToTimeStep(globalCurrentTimePoint)), p, pixelValue, component); statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue); } } else { statusBar->DisplayImageInfoInvalid(); } } void mitk::DisplayInteractor::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // auto repeat std::string strAutoRepeat = ""; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } else { m_AutoRepeat = false; } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } else { m_IndexToSliceModifier = 4; } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") m_LinkPlanes = true; else m_LinkPlanes = false; } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } // allwaysReact std::string strAlwaysReact = ""; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } else { m_AlwaysReact = false; } } else { m_AlwaysReact = false; } } bool mitk::DisplayInteractor::FilterEvents(InteractionEvent *interactionEvent, DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } bool mitk::DisplayInteractor::GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } } diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 1d2df7ad7b..7fefadf5fc 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,953 +1,953 @@ /*=================================================================== 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 "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" 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_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) 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); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (abstractTransformGeometry != nullptr) return; // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // 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 RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { auto *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()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { auto *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); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); return (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints()); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { const 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::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const 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(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { const mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; const DataNode::Pointer node = this->GetDataNode(); const BaseData::Pointer data = node->GetData(); /* * 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 necessary because of a macOS 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; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } auto *planarFigure = dynamic_cast(data.GetPointer()); // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (abstractTransformGeometry != nullptr) return; // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // 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 const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { const mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); auto *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { planarFigureGeometry = planeGeometry; planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // 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 PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { const mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); 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); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { const mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); 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->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } -void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); auto *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (abstractTransformGeometry != nullptr) return false; mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); if (isHovering) { return true; } else { return false; } return false; } bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (abstractTransformGeometry != nullptr) return false; int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { return true; } else { return false; } } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { const mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (abstractTransformGeometry != nullptr) return; const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } 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 auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); m_LastPointWasValid = IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); return m_LastPointWasValid; } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto *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()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { const auto *posEvent = dynamic_cast(interactionEvent); if (posEvent == nullptr) return false; const mitk::Point3D worldPoint3D = posEvent->GetPositionInWorld(); const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::PlaneGeometry *planarFigurePlaneGeometry = dynamic_cast(planarFigure->GetGeometry(0)); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (abstractTransformGeometry != nullptr) return false; const double planeThickness = planarFigurePlaneGeometry->GetExtentInMM(2); if (planarFigurePlaneGeometry->Distance(worldPoint3D) > 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 PlaneGeometry *planarFigureGeometry, Point2D &point2D) { const 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::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, 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 const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // 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 (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, 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; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { 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 std::distance(polyLine.begin(), it); } 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 PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { 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; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // 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); const BaseRenderer *renderer = positionEvent->GetSender(); assert(renderer); // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); const mitk::AbstractTransformGeometry *abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); if (abstractTransformGeometry != nullptr) return false; Point2D point2D; // 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. const Point2D 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; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // 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->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const 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() { const 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; } }