diff --git a/Modules/Core/include/mitkDisplayActionEventBroadcast.h b/Modules/Core/include/mitkDisplayActionEventBroadcast.h index ff710ac579..a548c87e48 100644 --- a/Modules/Core/include/mitkDisplayActionEventBroadcast.h +++ b/Modules/Core/include/mitkDisplayActionEventBroadcast.h @@ -1,219 +1,218 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDISPLAYACTIONEVENTBROADCAST_H #define MITKDISPLAYACTIONEVENTBROADCAST_H #include "mitkInteractionEventObserver.h" #include namespace mitk { /** * @brief This class serves as an event state machine while simultaneously observing interaction events. * It connects the actions from the event state machine .xml-file with concrete functions of this class. */ class MITKCORE_EXPORT DisplayActionEventBroadcast : public EventStateMachine, public InteractionEventObserver { public: mitkClassMacro(DisplayActionEventBroadcast, EventStateMachine) itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** * By this function this observer is notified about about every 'InteractionEvent'. * The interaction event is passed to the state machine in order to use its infrastructure. * For more information see @see InteractionEventObserver. * * @par interactionEvent The event that was observed and triggered this notification. * @par isHandled Flag that indicates if a 'DataInteractor' has already handled the event. */ virtual void Notify(InteractionEvent* interactionEvent, bool isHandled) override; protected: DisplayActionEventBroadcast(); virtual ~DisplayActionEventBroadcast() override; /** * @brief Connects the action names used in the state machine pattern with functions implemented within this InteractionEventObserver. */ void ConnectActionsAndFunctions() override; /** * @brief This function is executed when a config object is set / changed (via 'SetEventConfig' or 'AddEventConfig' in 'InteractionEventObserver'). * It is used to read out the parameters set in the configuration file and to set the member variables accordingly. */ virtual void ConfigurationChanged() override; /** * @brief Filters the event resp. the sender of the event. * * @par interactionEvent The event whose sender has to be checked * @par data node The data node is ignored in this specific implementation. * * @return True, if the sender of the event is a valid sender and the sending renderer is a 2D-renderer. False, if not. */ virtual bool FilterEvents(InteractionEvent* interactionEvent, DataNode* dataNode) override; ////////////////////////////////////////////////////////////////////////// // Functions to react to interaction events (actions) ////////////////////////////////////////////////////////////////////////// /** * @brief Check if the given interaction event is actually an 'InteractionPositionEvent'. * * @par interactionEvent The interaction event that is checked. * * @return True, if the given event can be dynamically cast to an 'InteractionPositionEvent'. False, if not. */ virtual bool CheckPositionEvent(const InteractionEvent* interactionEvent); virtual bool CheckRotationPossible(const InteractionEvent* interactionEvent); virtual bool CheckSwivelPossible(const InteractionEvent* interactionEvent); virtual void Init(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void Move(StateMachineAction* stateMachineAction , InteractionEvent* interactionEvent); virtual void SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); virtual void Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); private: virtual void UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); bool GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char* propertyName, bool defaultValue); - mitk::DataNode::Pointer GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer* renderer); /** * @brief Reference to the service registration of the observer. * This is needed to unregister the observer on unload. */ us::ServiceRegistration m_ServiceRegistration; /** * @brief Determines if this broadcast class reacts to events that already have been processed by a DataInteractor. * The default value is false. */ bool m_AlwaysReact; /** * @brief Coordinate of the mouse pointer at beginning of an interaction (translated to mm unit). */ mitk::Point2D m_StartCoordinateInMM; /** * @brief Coordinate of the mouse pointer in the last step within an interaction. */ mitk::Point2D m_LastDisplayCoordinate; /** * @brief Coordinate of the mouse pointer in the last step within an interaction (translated to mm unit). */ mitk::Point2D m_LastCoordinateInMM; /** * \brief Current coordinates of the pointer. */ mitk::Point2D m_CurrentDisplayCoordinate; /** * @brief Defines the behavior at the end of a data set. * If set to true, it will restart at end of data set from the beginning. */ bool m_AutoRepeat; /** * @brief Defines how many slices are scrolled per pixel that the mouse pointer was moved. * By default the modifier is 4. This means that when the user moves the cursor by 4 pixels in Y-direction, * the scene is scrolled by one slice. If the user has moved the the cursor by 20 pixels, the scene is * scrolled by 5 slices. * * If the cursor has moved less than m_IndexToSliceModifier pixels, the scene is scrolled by one slice. */ int m_IndexToSliceModifier; /** * @brief Defines the scroll behavior. * Default is up/down movement of pointer performs scrolling */ std::string m_ScrollDirection; /** * @brief Defines how the axis of interaction influences scroll behavior. */ bool m_InvertScrollDirection; /** * @brief Defines the zoom behavior. * Default is up/down movement of pointer performs zooming */ std::string m_ZoomDirection; /** * @brief Defines how the axis of interaction influences zoom behavior. */ bool m_InvertZoomDirection; /** * @brief Factor to adjust zooming speed. */ float m_ZoomFactor; /** * @brief Defines how the axis of interaction influences move behavior. */ bool m_InvertMoveDirection; /** * @brief Defines the level-window behavior. * Default is left/right movement of pointer modifies the level. */ std::string m_LevelDirection; /** * @brief Defines how the axis of interaction influences level-window behavior. */ bool m_InvertLevelWindowDirection; /** * @brief Determines if the angle between crosshair remains fixed when rotating. */ bool m_LinkPlanes; typedef std::vector SNCVector; SNCVector m_RotatableSNCs; SNCVector m_SNCsToBeRotated; Point3D m_LastCursorPosition; Point3D m_CenterOfRotation; Point2D m_ReferenceCursor; Vector3D m_RotationPlaneNormal; Vector3D m_RotationPlaneXVector; Vector3D m_RotationPlaneYVector; Vector3D m_PreviousRotationAxis; ScalarType m_PreviousRotationAngle; }; } // end namespace #endif // MITKDISPLAYACTIONEVENTBROADCAST_H diff --git a/Modules/Core/include/mitkDisplayActionEventFunctions.h b/Modules/Core/include/mitkDisplayActionEventFunctions.h index 00ce9331a8..4cefc1f7ac 100644 --- a/Modules/Core/include/mitkDisplayActionEventFunctions.h +++ b/Modules/Core/include/mitkDisplayActionEventFunctions.h @@ -1,81 +1,83 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDISPLAYACTIONEVENTFUNCTIONS_H #define MITKDISPLAYACTIONEVENTFUNCTIONS_H #include #include "mitkStdFunctionCommand.h" namespace mitk { namespace DisplayActionEventFunctions { /** * @brief Returns a command that reacts on the 'DisplayMoveEvent'. * The command performs a move of the camera controller of the sending renderer by a vector * that was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction MoveSenderCameraAction(); /** * @brief Returns a command that reacts on the 'DisplaySetCrosshairEvent'. * The command performs a slice selection of the slice navigation controller and will set * the cross hair for all render windows. * The new position was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetCrosshairAction(); /** * @brief Returns a command that reacts on the 'DisplayZoomEvent'. * The command performs a zoom of the camera controller of the sending renderer by a zoom factor * that was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ZoomSenderCameraAction(); /** * @brief Returns a command that reacts on the 'DisplayScrollEvent'. * The command performs a slice scrolling of the slice navigation controller of the sending renderer. * The new position was previously determined by the mouse interaction event. */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ScrollSliceStepperAction(); /** - * @brief - * - * + * @brief Returns an 'std::function' that can be used to react on the 'DisplaySetLevelWindowEvent'. + * The function sets the 'levelwindow' property of the topmost visible image that is display by the sending renderer. + * The level and window value for this property were previously determined by the mouse interaction event. + */ + MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetLevelWindowAction(); */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction MoveCameraSynchronizedAction(); /** * @brief * * */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetCrosshairSynchronizedAction(); /** * @brief * * */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ZoomCameraSynchronizedAction(); /** * @brief * * */ MITKCORE_EXPORT StdFunctionCommand::ActionFunction ScrollSliceStepperSynchronizedAction(); } // end namespace DisplayActionEventFunctions } // end namespace mitk #endif // MITKDISPLAYACTIONEVENTFUNCTIONS_H diff --git a/Modules/Core/include/mitkDisplayActionEvents.h b/Modules/Core/include/mitkDisplayActionEvents.h index 0758ac04e4..f56823720c 100644 --- a/Modules/Core/include/mitkDisplayActionEvents.h +++ b/Modules/Core/include/mitkDisplayActionEvents.h @@ -1,158 +1,188 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKDISPLAYACTIONEVENTS_H #define MITKDISPLAYACTIONEVENTS_H #include // mitk core #include "mitkInteractionEvent.h" // itk #include #include #include namespace mitk { class MITKCORE_EXPORT DisplayActionEvent : public itk::AnyEvent { public: typedef DisplayActionEvent Self; typedef itk::AnyEvent Superclass; DisplayActionEvent() : m_InteractionEvent(nullptr) {} DisplayActionEvent(InteractionEvent* interactionEvent) : m_InteractionEvent(interactionEvent) {} virtual ~DisplayActionEvent() {} virtual const char* GetEventName() const override { return "DisplayActionEvent"; } virtual bool CheckEvent(const itk::EventObject* e) const override { return dynamic_cast(e) != nullptr; } virtual itk::EventObject* MakeObject() const override { return new Self(m_InteractionEvent); } InteractionEvent* GetInteractionEvent() const { return m_InteractionEvent; } BaseRenderer* GetSender() const { return m_InteractionEvent != nullptr ? m_InteractionEvent->GetSender() : nullptr; } - DisplayActionEvent(const Self& s) : Superclass(s), m_InteractionEvent(s.m_InteractionEvent) {}; + DisplayActionEvent(const Self& s) : Superclass(s), m_InteractionEvent(s.GetInteractionEvent()) {}; private: InteractionEvent* m_InteractionEvent; void operator=(const Self &); }; class MITKCORE_EXPORT DisplayMoveEvent : public DisplayActionEvent { public: typedef DisplayMoveEvent Self; typedef DisplayActionEvent Superclass; DisplayMoveEvent() : Superclass() {} DisplayMoveEvent(InteractionEvent* interactionEvent, const Vector2D& moveVector) : Superclass(interactionEvent) , m_MoveVector(moveVector) { } virtual ~DisplayMoveEvent() {} virtual const char* GetEventName() const override { return "DisplayMoveEvent"; } virtual bool CheckEvent(const itk::EventObject* e) const override { return dynamic_cast(e) != nullptr; } virtual itk::EventObject* MakeObject() const override { return new Self(GetInteractionEvent(), m_MoveVector); } const Vector2D& GetMoveVector() const { return m_MoveVector; } - DisplayMoveEvent(const Self& s) : Superclass(s), m_MoveVector(s.m_MoveVector) {}; + DisplayMoveEvent(const Self& s) : Superclass(s), m_MoveVector(s.GetMoveVector()) {}; private: Vector2D m_MoveVector; }; class MITKCORE_EXPORT DisplaySetCrosshairEvent : public DisplayActionEvent { public: typedef DisplaySetCrosshairEvent Self; typedef DisplayActionEvent Superclass; DisplaySetCrosshairEvent() : Superclass() {} DisplaySetCrosshairEvent(InteractionEvent* interactionEvent, const Point3D& position) : Superclass(interactionEvent) , m_Position(position) { } virtual ~DisplaySetCrosshairEvent() {} virtual const char* GetEventName() const override { return "DisplaySetCrosshairEvent"; } virtual bool CheckEvent(const itk::EventObject* e) const override { return dynamic_cast(e) != nullptr; } virtual itk::EventObject* MakeObject() const override { return new Self(GetInteractionEvent(), m_Position); } const Point3D& GetPosition() const { return m_Position; } - DisplaySetCrosshairEvent(const Self& s) : Superclass(s), m_Position(s.m_Position) {}; + DisplaySetCrosshairEvent(const Self& s) : Superclass(s), m_Position(s.GetPosition()) {}; private: Point3D m_Position; }; class MITKCORE_EXPORT DisplayZoomEvent : public DisplayActionEvent { public: typedef DisplayZoomEvent Self; typedef DisplayActionEvent Superclass; DisplayZoomEvent() : Superclass() {} DisplayZoomEvent(InteractionEvent* interactionEvent, float zoomFactor, const Point2D& startCoordinate) : Superclass(interactionEvent) , m_ZoomFactor(zoomFactor) , m_StartCoordinate(startCoordinate) { } virtual ~DisplayZoomEvent() {} virtual const char* GetEventName() const override { return "DisplayZoomEvent"; } virtual bool CheckEvent(const itk::EventObject* e) const override { return dynamic_cast(e) != nullptr; } virtual itk::EventObject* MakeObject() const override { return new Self(GetInteractionEvent(), m_ZoomFactor, m_StartCoordinate); } float GetZoomFactor() const { return m_ZoomFactor; } const Point2D& GetStartCoordinate() const { return m_StartCoordinate; } - DisplayZoomEvent(const Self& s) : Superclass(s), m_ZoomFactor(s.m_ZoomFactor), m_StartCoordinate(s.m_StartCoordinate) {}; + DisplayZoomEvent(const Self& s) : Superclass(s), m_ZoomFactor(s.GetZoomFactor()), m_StartCoordinate(s.GetStartCoordinate()) {}; private: float m_ZoomFactor; Point2D m_StartCoordinate; }; class MITKCORE_EXPORT DisplayScrollEvent : public DisplayActionEvent { public: typedef DisplayScrollEvent Self; typedef DisplayActionEvent Superclass; DisplayScrollEvent() : Superclass() {} DisplayScrollEvent(InteractionEvent* interactionEvent, int sliceDelta) : Superclass(interactionEvent) , m_SliceDelta(sliceDelta) { } virtual ~DisplayScrollEvent() {} virtual const char* GetEventName() const override { return "DisplayScrollEvent"; } virtual bool CheckEvent(const itk::EventObject* e) const override { return dynamic_cast(e) != nullptr; } virtual itk::EventObject* MakeObject() const override { return new Self(GetInteractionEvent(), m_SliceDelta); } int GetSliceDelta() const { return m_SliceDelta; } - DisplayScrollEvent(const Self& s) : Superclass(s), m_SliceDelta(s.m_SliceDelta) {}; + DisplayScrollEvent(const Self& s) : Superclass(s), m_SliceDelta(s.GetSliceDelta()) {}; private: int m_SliceDelta; }; + + class MITKCORE_EXPORT DisplaySetLevelWindowEvent : public DisplayActionEvent + { + public: + typedef DisplaySetLevelWindowEvent Self; + typedef DisplayActionEvent Superclass; + + DisplaySetLevelWindowEvent() : Superclass() {} + DisplaySetLevelWindowEvent(InteractionEvent* interactionEvent, mitk::ScalarType level, mitk::ScalarType window) + : Superclass(interactionEvent) + , m_Level(level) + , m_Window(window) + { + } + virtual ~DisplaySetLevelWindowEvent() {} + virtual const char* GetEventName() const override { return "DisplaySetLevelWindowEvent"; } + virtual bool CheckEvent(const itk::EventObject* e) const override + { + return dynamic_cast(e) != nullptr; + } + virtual itk::EventObject* MakeObject() const override { return new Self(GetInteractionEvent(), m_Level, m_Window); } + mitk::ScalarType GetLevel() const { return m_Level; } + mitk::ScalarType GetWindow() const { return m_Window; } + DisplaySetLevelWindowEvent(const Self& s) : Superclass(s), m_Level(s.GetLevel()), m_Window(s.GetWindow()) {}; + + private: + mitk::ScalarType m_Level; + mitk::ScalarType m_Window; + }; + } // end namespace #endif // MITKDISPLAYACTIONEVENTS_H diff --git a/Modules/Core/src/DataManagement/mitkDataStorage.cpp b/Modules/Core/src/DataManagement/mitkDataStorage.cpp index f548fb9ced..0d39755520 100644 --- a/Modules/Core/src/DataManagement/mitkDataStorage.cpp +++ b/Modules/Core/src/DataManagement/mitkDataStorage.cpp @@ -1,518 +1,577 @@ /*=================================================================== 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 "mitkDataStorage.h" #include "itkCommand.h" #include "itkMutexLockHolder.h" #include "mitkDataNode.h" #include "mitkGroupTagProperty.h" #include "mitkImage.h" #include "mitkNodePredicateBase.h" #include "mitkNodePredicateProperty.h" #include "mitkProperties.h" mitk::DataStorage::DataStorage() : itk::Object(), m_BlockNodeModifiedEvents(false) { } mitk::DataStorage::~DataStorage() { ///// we can not call GetAll() in destructor, because it is implemented in a subclass // SetOfObjects::ConstPointer all = this->GetAll(); // for (SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) // this->RemoveListeners(it->Value()); // m_NodeModifiedObserverTags.clear(); // m_NodeDeleteObserverTags.clear(); } void mitk::DataStorage::Add(mitk::DataNode *node, mitk::DataNode *parent) { mitk::DataStorage::SetOfObjects::Pointer parents = mitk::DataStorage::SetOfObjects::New(); if (parent != nullptr) //< Return empty set if parent is null parents->InsertElement(0, parent); this->Add(node, parents); } void mitk::DataStorage::Remove(const mitk::DataStorage::SetOfObjects *nodes) { if (nodes == nullptr) return; for (mitk::DataStorage::SetOfObjects::ConstIterator it = nodes->Begin(); it != nodes->End(); it++) this->Remove(it.Value()); } mitk::DataStorage::SetOfObjects::ConstPointer mitk::DataStorage::GetSubset(const NodePredicateBase *condition) const { mitk::DataStorage::SetOfObjects::ConstPointer result = this->FilterSetOfObjects(this->GetAll(), condition); return result; } +mitk::DataNode::Pointer mitk::DataStorage::GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, const mitk::Point3D worldPosition, const mitk::BaseRenderer* renderer) +{ + if (nodes.IsNull()) + { + return nullptr; + } + + mitk::DataNode::Pointer topLayerNode = nullptr; + int maxLayer = std::numeric_limits::min(); + + for (auto node : *nodes) + { + if (node.IsNull()) + { + continue; + } + + bool isHelperObject = false; + node->GetBoolProperty("helper object", isHelperObject); + if (isHelperObject) + { + continue; + } + + auto data = node->GetData(); + if (nullptr == data) + { + continue; + } + + auto geometry = data->GetGeometry(); + if (nullptr == geometry || !geometry->IsInside(worldPosition)) + { + continue; + } + + int layer = 0; + if (!node->GetIntProperty("layer", layer, renderer)) + { + continue; + } + + if (layer <= maxLayer) + { + continue; + } + + if (!node->IsVisible(renderer)) + { + continue; + } + + topLayerNode = node; + maxLayer = layer; + } + + return topLayerNode; +} + mitk::DataNode *mitk::DataStorage::GetNamedNode(const char *name) const { if (name == nullptr) return nullptr; mitk::StringProperty::Pointer s(mitk::StringProperty::New(name)); mitk::NodePredicateProperty::Pointer p = mitk::NodePredicateProperty::New("name", s); mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetSubset(p); if (rs->Size() >= 1) return rs->GetElement(0); else return nullptr; } mitk::DataNode *mitk::DataStorage::GetNode(const NodePredicateBase *condition) const { if (condition == nullptr) return nullptr; mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetSubset(condition); if (rs->Size() >= 1) return rs->GetElement(0); else return nullptr; } mitk::DataNode *mitk::DataStorage::GetNamedDerivedNode(const char *name, const mitk::DataNode *sourceNode, bool onlyDirectDerivations) const { if (name == nullptr) return nullptr; mitk::StringProperty::Pointer s(mitk::StringProperty::New(name)); mitk::NodePredicateProperty::Pointer p = mitk::NodePredicateProperty::New("name", s); mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetDerivations(sourceNode, p, onlyDirectDerivations); if (rs->Size() >= 1) return rs->GetElement(0); else return nullptr; } void mitk::DataStorage::PrintSelf(std::ostream &os, itk::Indent indent) const { // Superclass::PrintSelf(os, indent); mitk::DataStorage::SetOfObjects::ConstPointer all = this->GetAll(); os << indent << "DataStorage " << this << " is managing " << all->Size() << " objects. List of objects:" << std::endl; for (mitk::DataStorage::SetOfObjects::ConstIterator allIt = all->Begin(); allIt != all->End(); allIt++) { std::string name; allIt.Value()->GetName(name); std::string datatype; if (allIt.Value()->GetData() != nullptr) datatype = allIt.Value()->GetData()->GetNameOfClass(); os << indent << " " << allIt.Value().GetPointer() << "<" << datatype << ">: " << name << std::endl; mitk::DataStorage::SetOfObjects::ConstPointer parents = this->GetSources(allIt.Value()); if (parents->Size() > 0) { os << indent << " Direct sources: "; for (mitk::DataStorage::SetOfObjects::ConstIterator parentIt = parents->Begin(); parentIt != parents->End(); parentIt++) os << parentIt.Value().GetPointer() << ", "; os << std::endl; } mitk::DataStorage::SetOfObjects::ConstPointer derivations = this->GetDerivations(allIt.Value()); if (derivations->Size() > 0) { os << indent << " Direct derivations: "; for (mitk::DataStorage::SetOfObjects::ConstIterator derivationIt = derivations->Begin(); derivationIt != derivations->End(); derivationIt++) os << derivationIt.Value().GetPointer() << ", "; os << std::endl; } } os << std::endl; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::DataStorage::FilterSetOfObjects( const SetOfObjects *set, const NodePredicateBase *condition) const { if (set == nullptr) return nullptr; mitk::DataStorage::SetOfObjects::Pointer result = mitk::DataStorage::SetOfObjects::New(); for (mitk::DataStorage::SetOfObjects::ConstIterator it = set->Begin(); it != set->End(); it++) if (condition == nullptr || condition->CheckNode(it.Value()) == true) // alway copy the set, otherwise the iterator in mitk::DataStorage::Remove() will crash result->InsertElement(result->Size(), it.Value()); return mitk::DataStorage::SetOfObjects::ConstPointer(result); } const mitk::DataNode::GroupTagList mitk::DataStorage::GetGroupTags() const { DataNode::GroupTagList result; SetOfObjects::ConstPointer all = this->GetAll(); if (all.IsNull()) return result; for (mitk::DataStorage::SetOfObjects::ConstIterator nodeIt = all->Begin(); nodeIt != all->End(); nodeIt++) // for each node { mitk::PropertyList *pl = nodeIt.Value()->GetPropertyList(); for (auto propIt = pl->GetMap()->begin(); propIt != pl->GetMap()->end(); ++propIt) if (dynamic_cast(propIt->second.GetPointer()) != nullptr) result.insert(propIt->first); } return result; } void mitk::DataStorage::EmitAddNodeEvent(const mitk::DataNode *node) { AddNodeEvent.Send(node); } void mitk::DataStorage::EmitRemoveNodeEvent(const mitk::DataNode *node) { RemoveNodeEvent.Send(node); } void mitk::DataStorage::OnNodeInteractorChanged(itk::Object *caller, const itk::EventObject &) { const auto *_Node = dynamic_cast(caller); if (_Node) { InteractorChangedNodeEvent.Send(_Node); } } void mitk::DataStorage::OnNodeModifiedOrDeleted(const itk::Object *caller, const itk::EventObject &event) { if (m_BlockNodeModifiedEvents) return; const auto *_Node = dynamic_cast(caller); if (_Node) { const auto *modEvent = dynamic_cast(&event); if (modEvent) ChangedNodeEvent.Send(_Node); else DeleteNodeEvent.Send(_Node); } } void mitk::DataStorage::AddListeners(const mitk::DataNode *_Node) { itk::MutexLockHolder locked(m_MutexOne); // node must not be 0 and must not be yet registered auto *NonConstNode = const_cast(_Node); if (_Node && m_NodeModifiedObserverTags.find(NonConstNode) == m_NodeModifiedObserverTags.end()) { itk::MemberCommand::Pointer nodeModifiedCommand = itk::MemberCommand::New(); nodeModifiedCommand->SetCallbackFunction(this, &mitk::DataStorage::OnNodeModifiedOrDeleted); m_NodeModifiedObserverTags[NonConstNode] = NonConstNode->AddObserver(itk::ModifiedEvent(), nodeModifiedCommand); itk::MemberCommand::Pointer interactorChangedCommand = itk::MemberCommand::New(); interactorChangedCommand->SetCallbackFunction(this, &mitk::DataStorage::OnNodeInteractorChanged); m_NodeInteractorChangedObserverTags[NonConstNode] = NonConstNode->AddObserver(mitk::DataNode::InteractorChangedEvent(), interactorChangedCommand); // add itk delete listener on datastorage itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &mitk::DataStorage::OnNodeModifiedOrDeleted); // add observer m_NodeDeleteObserverTags[NonConstNode] = NonConstNode->AddObserver(itk::DeleteEvent(), deleteCommand); } } void mitk::DataStorage::RemoveListeners(const mitk::DataNode *_Node) { itk::MutexLockHolder locked(m_MutexOne); // node must not be 0 and must be registered auto *NonConstNode = const_cast(_Node); if (_Node && m_NodeModifiedObserverTags.find(NonConstNode) != m_NodeModifiedObserverTags.end()) { // const cast is bad! but sometimes it is necessary. removing an observer does not really // touch the internal state NonConstNode->RemoveObserver(m_NodeModifiedObserverTags.find(NonConstNode)->second); NonConstNode->RemoveObserver(m_NodeDeleteObserverTags.find(NonConstNode)->second); NonConstNode->RemoveObserver(m_NodeInteractorChangedObserverTags.find(NonConstNode)->second); m_NodeModifiedObserverTags.erase(NonConstNode); m_NodeDeleteObserverTags.erase(NonConstNode); m_NodeInteractorChangedObserverTags.erase(NonConstNode); } } mitk::TimeGeometry::Pointer mitk::DataStorage::ComputeBoundingGeometry3D(const SetOfObjects *input, const char *boolPropertyKey, const mitk::BaseRenderer *renderer, const char *boolPropertyKey2) const { if (input == nullptr) throw std::invalid_argument("DataStorage: input is invalid"); BoundingBox::PointsContainer::Pointer pointscontainer = BoundingBox::PointsContainer::New(); BoundingBox::PointIdentifier pointid = 0; Point3D point; Vector3D minSpacing; minSpacing.Fill(itk::NumericTraits::max()); ScalarType stmin, stmax; stmin = itk::NumericTraits::NonpositiveMin(); stmax = itk::NumericTraits::max(); ScalarType minimalIntervallSize = stmax; ScalarType minimalTime = stmax; ScalarType maximalTime = 0; // Needed for check of zero bounding boxes mitk::ScalarType nullpoint[] = {0, 0, 0, 0, 0, 0}; BoundingBox::BoundsArrayType itkBoundsZero(nullpoint); for (SetOfObjects::ConstIterator it = input->Begin(); it != input->End(); ++it) { DataNode::Pointer node = it->Value(); if ((node.IsNotNull()) && (node->GetData() != nullptr) && (node->GetData()->IsEmpty() == false) && node->IsOn(boolPropertyKey, renderer) && node->IsOn(boolPropertyKey2, renderer)) { const TimeGeometry *timeGeometry = node->GetData()->GetUpdatedTimeGeometry(); if (timeGeometry != nullptr) { // bounding box (only if non-zero) BoundingBox::BoundsArrayType itkBounds = timeGeometry->GetBoundingBoxInWorld()->GetBounds(); if (itkBounds == itkBoundsZero) { continue; } unsigned char i; for (i = 0; i < 8; ++i) { point = timeGeometry->GetCornerPointInWorld(i); if (point[0] * point[0] + point[1] * point[1] + point[2] * point[2] < large) pointscontainer->InsertElement(pointid++, point); else { itkGenericOutputMacro(<< "Unrealistically distant corner point encountered. Ignored. Node: " << node); } } try { // time bounds // iterate over all time steps // Attention: Objects with zero bounding box are not respected in time bound calculation for (TimeStepType i = 0; i < timeGeometry->CountTimeSteps(); i++) { // We must not use 'node->GetData()->GetGeometry(i)->GetSpacing()' here, as it returns the spacing // in its original space, which, in case of an image geometry, can have the values in different // order than in world space. For the further calculations, we need to have the spacing values // in world coordinate order (sag-cor-ax). Vector3D spacing; spacing.Fill(1.0); node->GetData()->GetGeometry(i)->IndexToWorld(spacing, spacing); for (int axis = 0; axis < 3; ++ axis) { ScalarType space = std::abs(spacing[axis]); if (space < minSpacing[axis]) { minSpacing[axis] = space; } } const TimeBounds &curTimeBounds = node->GetData()->GetTimeGeometry()->GetTimeBounds(i); // get the minimal time of all objects in the DataStorage if ((curTimeBounds[0] < minimalTime) && (curTimeBounds[0] > stmin)) { minimalTime = curTimeBounds[0]; } // get the maximal time of all objects in the DataStorage if ((curTimeBounds[1] > maximalTime) && (curTimeBounds[1] < stmax)) { maximalTime = curTimeBounds[1]; } // get the minimal TimeBound of all time steps of the current DataNode if (curTimeBounds[1] - curTimeBounds[0] < minimalIntervallSize) { minimalIntervallSize = curTimeBounds[1] - curTimeBounds[0]; } } } catch (itk::ExceptionObject &e) { MITK_ERROR << e << std::endl; } } } } BoundingBox::Pointer result = BoundingBox::New(); result->SetPoints(pointscontainer); result->ComputeBoundingBox(); // compute the number of time steps unsigned int numberOfTimeSteps = 1; if (maximalTime == 0) // make sure that there is at least one time sliced geometry in the data storage { minimalTime = 0; maximalTime = 1; minimalIntervallSize = 1; } numberOfTimeSteps = static_cast((maximalTime - minimalTime) / minimalIntervallSize); TimeGeometry::Pointer timeGeometry = nullptr; if (result->GetPoints()->Size() > 0) { // Initialize a geometry of a single time step Geometry3D::Pointer geometry = Geometry3D::New(); geometry->Initialize(); // correct bounding-box (is now in mm, should be in index-coordinates) // according to spacing BoundingBox::BoundsArrayType bounds = result->GetBounds(); AffineTransform3D::OutputVectorType offset; for (int i = 0; i < 3; ++i) { offset[i] = bounds[i * 2]; bounds[i * 2] = 0.0; bounds[i * 2 + 1] = (bounds[i * 2 + 1] - offset[i]) / minSpacing[i]; } geometry->GetIndexToWorldTransform()->SetOffset(offset); geometry->SetBounds(bounds); geometry->SetSpacing(minSpacing); // Initialize the time sliced geometry timeGeometry = ProportionalTimeGeometry::New(); dynamic_cast(timeGeometry.GetPointer())->Initialize(geometry, numberOfTimeSteps); dynamic_cast(timeGeometry.GetPointer())->SetFirstTimePoint(minimalTime); dynamic_cast(timeGeometry.GetPointer())->SetStepDuration(minimalIntervallSize); } return timeGeometry; } mitk::TimeGeometry::Pointer mitk::DataStorage::ComputeBoundingGeometry3D(const char *boolPropertyKey, const mitk::BaseRenderer *renderer, const char *boolPropertyKey2) const { return this->ComputeBoundingGeometry3D(this->GetAll(), boolPropertyKey, renderer, boolPropertyKey2); } mitk::TimeGeometry::Pointer mitk::DataStorage::ComputeVisibleBoundingGeometry3D(const mitk::BaseRenderer *renderer, const char *boolPropertyKey) { return ComputeBoundingGeometry3D("visible", renderer, boolPropertyKey); } mitk::BoundingBox::Pointer mitk::DataStorage::ComputeBoundingBox(const char *boolPropertyKey, const mitk::BaseRenderer *renderer, const char *boolPropertyKey2) { BoundingBox::PointsContainer::Pointer pointscontainer = BoundingBox::PointsContainer::New(); BoundingBox::PointIdentifier pointid = 0; Point3D point; // Needed for check of zero bounding boxes mitk::ScalarType nullpoint[] = {0, 0, 0, 0, 0, 0}; BoundingBox::BoundsArrayType itkBoundsZero(nullpoint); SetOfObjects::ConstPointer all = this->GetAll(); for (SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); if ((node.IsNotNull()) && (node->GetData() != nullptr) && (node->GetData()->IsEmpty() == false) && node->IsOn(boolPropertyKey, renderer) && node->IsOn(boolPropertyKey2, renderer)) { const TimeGeometry *geometry = node->GetData()->GetUpdatedTimeGeometry(); if (geometry != nullptr) { // bounding box (only if non-zero) BoundingBox::BoundsArrayType itkBounds = geometry->GetBoundingBoxInWorld()->GetBounds(); if (itkBounds == itkBoundsZero) { continue; } unsigned char i; for (i = 0; i < 8; ++i) { point = geometry->GetCornerPointInWorld(i); if (point[0] * point[0] + point[1] * point[1] + point[2] * point[2] < large) pointscontainer->InsertElement(pointid++, point); else { itkGenericOutputMacro(<< "Unrealistically distant corner point encountered. Ignored. Node: " << node); } } } } } BoundingBox::Pointer result = BoundingBox::New(); result->SetPoints(pointscontainer); result->ComputeBoundingBox(); return result; } mitk::TimeBounds mitk::DataStorage::ComputeTimeBounds(const char *boolPropertyKey, const mitk::BaseRenderer *renderer, const char *boolPropertyKey2) { TimeBounds timeBounds; ScalarType stmin, stmax, cur; stmin = itk::NumericTraits::NonpositiveMin(); stmax = itk::NumericTraits::max(); timeBounds[0] = stmax; timeBounds[1] = stmin; SetOfObjects::ConstPointer all = this->GetAll(); for (SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); if ((node.IsNotNull()) && (node->GetData() != nullptr) && (node->GetData()->IsEmpty() == false) && node->IsOn(boolPropertyKey, renderer) && node->IsOn(boolPropertyKey2, renderer)) { const TimeGeometry *geometry = node->GetData()->GetUpdatedTimeGeometry(); if (geometry != nullptr) { const TimeBounds &curTimeBounds = geometry->GetTimeBounds(); cur = curTimeBounds[0]; // is it after -infinity, but before everything else that we found until now? if ((cur > stmin) && (cur < timeBounds[0])) timeBounds[0] = cur; cur = curTimeBounds[1]; // is it before infinity, but after everything else that we found until now? if ((cur < stmax) && (cur > timeBounds[1])) timeBounds[1] = cur; } } } if (!(timeBounds[0] < stmax)) { timeBounds[0] = stmin; timeBounds[1] = stmax; } return timeBounds; } void mitk::DataStorage::BlockNodeModifiedEvents(bool block) { m_BlockNodeModifiedEvents = block; } diff --git a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp index 08a13fbf72..9ee76de4c3 100644 --- a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp @@ -1,651 +1,675 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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 "mitkDisplayActionEventBroadcast.h" // us #include "usGetModuleContext.h" #include "usModuleContext.h" // mitk core module #include "mitkDisplayActionEvents.h" #include "mitkInteractionPositionEvent.h" #include "mitkLine.h" mitk::DisplayActionEventBroadcast::DisplayActionEventBroadcast() : m_AlwaysReact(false) , m_AutoRepeat(false) , m_IndexToSliceModifier(4) , m_InvertScrollDirection(false) , m_InvertZoomDirection(false) , m_ZoomFactor(2) , m_InvertMoveDirection(false) , m_InvertLevelWindowDirection(false) , m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); // register the broadcast class (itself) as an interaction event observer via micro services us::ServiceProperties props; props["name"] = std::string("DisplayActionEventBroadcast"); m_ServiceRegistration = us::GetModuleContext()->RegisterService(this, props); } mitk::DisplayActionEventBroadcast::~DisplayActionEventBroadcast() { m_ServiceRegistration.Unregister(); } void mitk::DisplayActionEventBroadcast::Notify(InteractionEvent* interactionEvent, bool isHandled) { // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { HandleEvent(interactionEvent, nullptr); } } void mitk::DisplayActionEventBroadcast::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("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); 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); } void mitk::DisplayActionEventBroadcast::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // allwaysReact std::string strAlwaysReact = ""; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } else { m_AlwaysReact = false; } } else { m_AlwaysReact = false; } // 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); } } bool mitk::DisplayActionEventBroadcast::FilterEvents(InteractionEvent* interactionEvent, DataNode * /*dataNode*/) { if (nullptr == interactionEvent->GetSender()) { return false; } if (BaseRenderer::Standard3D == interactionEvent->GetSender()->GetMapperID()) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckPositionEvent(const InteractionEvent *interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckRotationPossible(const 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 (nullptr == posEvent) { return false; } BaseRenderer* clickedRenderer = posEvent->GetSender(); const PlaneGeometry* ourViewportGeometry = clickedRenderer->GetCurrentWorldPlaneGeometry(); if (nullptr == 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 = interactionEvent->GetSender()->GetRenderingManager()->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 (nullptr == otherRenderersRenderPlane) { 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 (nullptr == geometryToBeRotated) // 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 // remember where the last cursor position ON THE LINE has been observed m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project(cursorPosition); // find center of rotation by intersection with any of the OTHER lines if (anyOtherGeometry->IntersectionPoint(intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) { return true; } else { return false; } } return false; } bool mitk::DisplayActionEventBroadcast::CheckSwivelPossible(const 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 registeredRenderWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : registeredRenderWindows) { 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); } } } Line3D line; 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::DisplayActionEventBroadcast::Init(StateMachineAction* 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::DisplayActionEventBroadcast::Move(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); BaseRenderer* sender = interactionEvent->GetSender(); Vector2D moveVector = m_LastDisplayCoordinate - positionEvent->GetPointerPositionOnScreen(); if (m_InvertMoveDirection) { moveVector *= -1.0; } moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); // #TODO: put here? // store new display coordinate m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate move event with computed geometry values InvokeEvent(DisplayMoveEvent(interactionEvent, moveVector)); } void mitk::DisplayActionEventBroadcast::SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); Point3D position = positionEvent->GetPositionInWorld(); // propagate set crosshair event with computed geometry values InvokeEvent(DisplaySetCrosshairEvent(interactionEvent, position)); } void mitk::DisplayActionEventBroadcast::Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(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; } // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate zoom event with computed geometry values InvokeEvent(DisplayZoomEvent(interactionEvent, factor, m_StartCoordinateInMM)); } void mitk::DisplayActionEventBroadcast::Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { auto* positionEvent = static_cast(interactionEvent); int sliceDelta = 0; // scroll direction if (m_ScrollDirection == "updown") { sliceDelta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); } else { sliceDelta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); } if (m_InvertScrollDirection) { sliceDelta *= -1; } // set how many pixels the mouse has to be moved to scroll one slice // if the mouse has been moved less than 'm_IndexToSliceModifier', pixels slice ONE slice only if (sliceDelta > 0 && sliceDelta < m_IndexToSliceModifier) { sliceDelta = m_IndexToSliceModifier; } else if (sliceDelta < 0 && sliceDelta > -m_IndexToSliceModifier) { sliceDelta = -m_IndexToSliceModifier; } sliceDelta /= m_IndexToSliceModifier; // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate scroll event with computed geometry values InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { int sliceDelta = 1; if (m_InvertScrollDirection) { sliceDelta = -1; } // propagate scroll event with a single slice delta (increase) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { int sliceDelta = -1; if (m_InvertScrollDirection) { sliceDelta = 1; } // propagate scroll event with a single slice delta (decrease) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta)); } void mitk::DisplayActionEventBroadcast::AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { - // nothing here; no event sent + auto* positionEvent = static_cast(interactionEvent); + + mitk::ScalarType level; + mitk::ScalarType window; + + if (m_LevelDirection == "leftright") + { + level = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; + window = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; + } + else + { + level = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; + window = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; + } + + if (m_InvertLevelWindowDirection) + { + level *= -1; + window *= -1; + } + + level *= static_cast(2); + window *= static_cast(2); + + // store new display coordinates + m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; + m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); + + // propagate set level window event with the level and window delta + InvokeEvent(DisplaySetLevelWindowEvent(interactionEvent, level, window)); } void mitk::DisplayActionEventBroadcast::StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { // nothing here; no event sent } void mitk::DisplayActionEventBroadcast::UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent) { } bool mitk::DisplayActionEventBroadcast::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; } } } - -mitk::DataNode::Pointer mitk::DisplayActionEventBroadcast::GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer* renderer) -{ - // #TODO: see T24173 - return nodes->front(); -} diff --git a/Modules/Core/src/Interactions/mitkDisplayActionEventFunctions.cpp b/Modules/Core/src/Interactions/mitkDisplayActionEventFunctions.cpp index fe6eed8143..3c10673025 100644 --- a/Modules/Core/src/Interactions/mitkDisplayActionEventFunctions.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayActionEventFunctions.cpp @@ -1,285 +1,337 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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 "mitkDisplayActionEventFunctions.h" // mitk core #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkDisplayActionEvents.h" +#include "mitkInteractionPositionEvent.h" +#include "mitkLevelWindow.h" +#include "mitkLevelWindowProperty.h" +#include "mitkNodePredicateDataType.h" ////////////////////////////////////////////////////////////////////////// // STANDARD FUNCTIONS ////////////////////////////////////////////////////////////////////////// mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::MoveSenderCameraAction() { mitk::StdFunctionCommand::ActionFunction actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayMoveEvent().CheckEvent(&displayInteractorEvent)) { const DisplayMoveEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action sendingRenderer->GetCameraController()->MoveBy(displayActionEvent->GetMoveVector()); sendingRenderer->GetRenderingManager()->RequestUpdate(sendingRenderer->GetRenderWindow()); } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::SetCrosshairAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplaySetCrosshairEvent().CheckEvent(&displayInteractorEvent)) { const DisplaySetCrosshairEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action auto allRenderWindows = sendingRenderer->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { if (BaseRenderer::GetInstance(renderWindow)->GetMapperID() == BaseRenderer::Standard2D && renderWindow != sendingRenderer->GetRenderWindow()) { BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController()->SelectSliceByPoint(displayActionEvent->GetPosition()); } } } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::ZoomSenderCameraAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayZoomEvent().CheckEvent(&displayInteractorEvent)) { const DisplayZoomEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action if (1.0 != displayActionEvent->GetZoomFactor()) { sendingRenderer->GetCameraController()->Zoom(displayActionEvent->GetZoomFactor(), displayActionEvent->GetStartCoordinate()); sendingRenderer->GetRenderingManager()->RequestUpdate(sendingRenderer->GetRenderWindow()); } } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::ScrollSliceStepperAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayScrollEvent().CheckEvent(&displayInteractorEvent)) { const DisplayScrollEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action mitk::SliceNavigationController* sliceNavigationController = sendingRenderer->GetSliceNavigationController(); if (nullptr == sliceNavigationController) { return; } if (sliceNavigationController->GetSliceLocked()) { return; } mitk::Stepper* sliceStepper = sliceNavigationController->GetSlice(); if (nullptr == sliceStepper) { return; } // if only a single slice image was loaded, scrolling will affect the time steps if (sliceStepper->GetSteps() <= 1) { sliceStepper = sliceNavigationController->GetTime(); } sliceStepper->MoveSlice(displayActionEvent->GetSliceDelta()); } }; return actionFunction; } +mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::SetLevelWindowAction() +{ + auto actionFunction = [](const itk::EventObject& displayInteractorEvent) + { + if (DisplaySetLevelWindowEvent().CheckEvent(&displayInteractorEvent)) + { + const DisplaySetLevelWindowEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); + const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); + if (nullptr == sendingRenderer) + { + return; + } + + // concrete action + + // get the the topmost visible image of the sending renderer + DataStorage::Pointer storage = sendingRenderer->GetDataStorage(); + DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(NodePredicateDataType::New("Image")); + Point3D worldposition; + const auto* positionEvent = dynamic_cast(displayActionEvent->GetInteractionEvent()); + sendingRenderer->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), worldposition); + DataNode::Pointer node = storage->GetTopLayerNode(allImageNodes, worldposition, sendingRenderer); + if (node.IsNull()) + { + return; + } + + LevelWindow levelWindow = LevelWindow(); + node->GetLevelWindow(levelWindow); + ScalarType level = levelWindow.GetLevel(); + ScalarType window = levelWindow.GetWindow(); + + level += displayActionEvent->GetLevel(); + window += displayActionEvent->GetWindow(); + + levelWindow.SetLevelWindow(level, window); + auto* levelWindowProperty = dynamic_cast(node->GetProperty("levelwindow")); + if (nullptr != levelWindowProperty) + { + levelWindowProperty->SetLevelWindow(levelWindow); + sendingRenderer->GetRenderingManager()->RequestUpdateAll(); + } + } + }; + + return actionFunction; +} + ////////////////////////////////////////////////////////////////////////// // SYNCHRONIZED FUNCTIONS ////////////////////////////////////////////////////////////////////////// mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::MoveCameraSynchronizedAction() { mitk::StdFunctionCommand::ActionFunction actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayMoveEvent().CheckEvent(&displayInteractorEvent)) { const DisplayMoveEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action auto allRenderWindows = sendingRenderer->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { if (BaseRenderer::GetInstance(renderWindow)->GetMapperID() == BaseRenderer::Standard2D) { BaseRenderer* currentRenderer = BaseRenderer::GetInstance(renderWindow); currentRenderer->GetCameraController()->MoveBy(displayActionEvent->GetMoveVector()); currentRenderer->GetRenderingManager()->RequestUpdate(currentRenderer->GetRenderWindow()); } } } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::SetCrosshairSynchronizedAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplaySetCrosshairEvent().CheckEvent(&displayInteractorEvent)) { const DisplaySetCrosshairEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action auto allRenderWindows = sendingRenderer->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { if (BaseRenderer::GetInstance(renderWindow)->GetMapperID() == BaseRenderer::Standard2D) { BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController()->SelectSliceByPoint(displayActionEvent->GetPosition()); } } } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::ZoomCameraSynchronizedAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayZoomEvent().CheckEvent(&displayInteractorEvent)) { const DisplayZoomEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action if (1.0 != displayActionEvent->GetZoomFactor()) { auto allRenderWindows = sendingRenderer->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { if (BaseRenderer::GetInstance(renderWindow)->GetMapperID() == BaseRenderer::Standard2D) { BaseRenderer* currentRenderer = BaseRenderer::GetInstance(renderWindow); currentRenderer->GetCameraController()->Zoom(displayActionEvent->GetZoomFactor(), displayActionEvent->GetStartCoordinate()); currentRenderer->GetRenderingManager()->RequestUpdate(currentRenderer->GetRenderWindow()); } } } } }; return actionFunction; } mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::ScrollSliceStepperSynchronizedAction() { auto actionFunction = [](const itk::EventObject& displayInteractorEvent) { if (DisplayScrollEvent().CheckEvent(&displayInteractorEvent)) { const DisplayScrollEvent* displayActionEvent = dynamic_cast(&displayInteractorEvent); const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } // concrete action auto allRenderWindows = sendingRenderer->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { if (BaseRenderer::GetInstance(renderWindow)->GetMapperID() == BaseRenderer::Standard2D) { mitk::SliceNavigationController* sliceNavigationController = BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController(); if (nullptr == sliceNavigationController) { return; } if (sliceNavigationController->GetSliceLocked()) { return; } mitk::Stepper* sliceStepper = sliceNavigationController->GetSlice(); if (nullptr == sliceStepper) { return; } // if only a single slice image was loaded, scrolling will affect the time steps if (sliceStepper->GetSteps() <= 1) { sliceStepper = sliceNavigationController->GetTime(); } sliceStepper->MoveSlice(displayActionEvent->GetSliceDelta()); } } } }; return actionFunction; } diff --git a/Modules/QtWidgets/src/QmitkCustomMultiWidget.cpp b/Modules/QtWidgets/src/QmitkCustomMultiWidget.cpp index 441e4b95bf..78e6ad4a0b 100644 --- a/Modules/QtWidgets/src/QmitkCustomMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkCustomMultiWidget.cpp @@ -1,564 +1,568 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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 "QmitkCustomMultiWidget.h" #include #include #include #include // mitk core #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qt #include QmitkCustomMultiWidget::QmitkCustomMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, mitk::RenderingManager* renderingManager/* = nullptr*/, mitk::BaseRenderer::RenderingMode::Type renderingMode/* = mitk::BaseRenderer::RenderingMode::Standard*/, const QString& multiWidgetName/* = "custommulti"*/) : QWidget(parent, f) , m_CustomMultiWidgetLayout(nullptr) , m_RenderingManager(renderingManager) , m_RenderingMode(renderingMode) , m_MultiWidgetName(multiWidgetName) , m_PendingCrosshairPositionEvent(false) , m_CrosshairNavigationEnabled(false) , m_DisplayActionEventBroadcast(nullptr) , m_DisplayActionEventHandler(nullptr) , m_DataStorage(nullptr) { // create widget manually // create and set layout InitializeGUI(); InitializeWidget(); InitializeDisplayActionEventHandling(); resize(QSize(364, 477).expandedTo(minimumSizeHint())); } QmitkCustomMultiWidget::~QmitkCustomMultiWidget() { // nothing here } void QmitkCustomMultiWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; // set new data storage for the render window widgets for (const auto& renderWindowWidget : m_RenderWindowWidgets) { renderWindowWidget.second->SetDataStorage(m_DataStorage); } } void QmitkCustomMultiWidget::InitializeRenderWindowWidgets() { // create render window (widgets) initially QString UID = CreateRenderWindowWidget("2015-01-14 - CT"); AddToMultiWidgetLayout(0, 0, UID); /* AddRenderWindowWidget(0, 1, "2015-08-19 - CT"); AddRenderWindowWidget(0, 2, "2016-06-29 - CT"); AddRenderWindowWidget(1, 0, "2015-01-14 - MR"); AddRenderWindowWidget(1, 1, "2015-08-19 - MR"); AddRenderWindowWidget(1, 2, "2016-06-29 - MR"); */ } void QmitkCustomMultiWidget::ResetLayout(int row, int column) { int neededRenderWindowWidgets = (row + 1) * (column + 1); int existingRenderWindowWidgets = m_RenderWindowWidgets.size(); int difference = neededRenderWindowWidgets - existingRenderWindowWidgets; while(0 < difference) { // more render window widgets needed CreateRenderWindowWidget(); --difference; } while(0 > difference) { // less render window widgets needed RemoveRenderWindowWidget(m_RenderWindowWidgets.rbegin()->first); ++difference; } InitializeGUI(); if (0 == difference) { FillLayout(row, column); } } void QmitkCustomMultiWidget::Synchronize(bool synchronized) { auto allObserverTags = m_DisplayActionEventHandler->GetAllObserverTags(); for (auto observerTag : allObserverTags) { m_DisplayActionEventHandler->DisconnectObserver(observerTag); } if (synchronized) { mitk::StdFunctionCommand::ActionFunction actionFunction = mitk::DisplayActionEventFunctions::MoveCameraSynchronizedAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayMoveEvent(nullptr, mitk::Vector2D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::SetCrosshairSynchronizedAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplaySetCrosshairEvent(nullptr, mitk::Point3D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::ZoomCameraSynchronizedAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayZoomEvent(nullptr, 0.0, mitk::Point2D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::ScrollSliceStepperSynchronizedAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayScrollEvent(nullptr, 0), actionFunction); } else { mitk::StdFunctionCommand::ActionFunction actionFunction = mitk::DisplayActionEventFunctions::MoveSenderCameraAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayMoveEvent(nullptr, mitk::Vector2D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::SetCrosshairAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplaySetCrosshairEvent(nullptr, mitk::Point3D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::ZoomSenderCameraAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayZoomEvent(nullptr, 0.0, mitk::Point2D()), actionFunction); actionFunction = mitk::DisplayActionEventFunctions::ScrollSliceStepperAction(); m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplayScrollEvent(nullptr, 0), actionFunction); } + + // use the standard 'set level window' action for both modes + mitk::StdFunctionCommand::ActionFunction actionFunction = mitk::DisplayActionEventFunctions::SetLevelWindowAction(); + m_DisplayActionEventHandler->ConnectDisplayActionEvent(mitk::DisplaySetLevelWindowEvent(nullptr, mitk::ScalarType(), mitk::ScalarType()), actionFunction); } QmitkCustomMultiWidget::RenderWindowWidgetMap QmitkCustomMultiWidget::GetRenderWindowWidgets() const { return m_RenderWindowWidgets; } std::shared_ptr QmitkCustomMultiWidget::GetRenderWindowWidget(const QString& widgetID) const { RenderWindowWidgetMap::const_iterator it = m_RenderWindowWidgets.find(widgetID); if (it == m_RenderWindowWidgets.end()) { return nullptr; } return it->second; } QString QmitkCustomMultiWidget::CreateRenderWindowWidget(const std::string& cornerAnnotation/* = ""*/) { // create the render window widget and connect signals / slots mitk::UIDGenerator generator; std::string renderWindowUID = generator.GetUID(); QString UID = m_MultiWidgetName + "_" + QString::fromStdString(renderWindowUID); std::shared_ptr renderWindowWidget = std::make_shared(this, UID, m_DataStorage); renderWindowWidget->SetCornerAnnotationText(cornerAnnotation); // create connections //connect(renderWindowWidget.get(), SIGNAL(ResetView()), this, SLOT(ResetCrosshair())); //connect(renderWindowWidget.get(), SIGNAL(ChangeCrosshairRotationMode(int)), this, SLOT(SetWidgetPlaneMode(int))); // store the newly created render window widget with the UID m_RenderWindowWidgets.insert(std::make_pair(UID, renderWindowWidget)); return UID; } void QmitkCustomMultiWidget::AddToMultiWidgetLayout(int row, int column, const QString& widgetID) { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { m_CustomMultiWidgetLayout->addWidget(renderWindowWidget.get(), row, column); } } void QmitkCustomMultiWidget::RemoveRenderWindowWidget(const QString& widgetID) { auto iterator = m_RenderWindowWidgets.find(widgetID); if (iterator == m_RenderWindowWidgets.end()) { return; } std::shared_ptr renderWindowWidgetToRemove = iterator->second; disconnect(renderWindowWidgetToRemove.get(), 0, 0, 0); // erase the render window from the map m_RenderWindowWidgets.erase(iterator); // remove from layout // #TODO } QmitkRenderWindow* QmitkCustomMultiWidget::GetRenderWindow(const QString& widgetID) const { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { return renderWindowWidget->GetRenderWindow(); } return nullptr; } std::shared_ptr QmitkCustomMultiWidget::GetActiveRenderWindowWidget() const { //return m_ActiveRenderWindowWidget; return m_RenderWindowWidgets.begin()->second; } std::shared_ptr QmitkCustomMultiWidget::GetFirstRenderWindowWidget() const { if (!m_RenderWindowWidgets.empty()) { return m_RenderWindowWidgets.begin()->second; } else { return nullptr; } } std::shared_ptr QmitkCustomMultiWidget::GetLastRenderWindowWidget() const { if (!m_RenderWindowWidgets.empty()) { return m_RenderWindowWidgets.rbegin()->second; } else { return nullptr; } } unsigned int QmitkCustomMultiWidget::GetNumberOfRenderWindowWidgets() const { return m_RenderWindowWidgets.size(); } void QmitkCustomMultiWidget::RequestUpdate(const QString& widgetID) { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { return renderWindowWidget->RequestUpdate(); } } void QmitkCustomMultiWidget::RequestUpdateAll() { for (const auto& renderWindowWidget : m_RenderWindowWidgets) { renderWindowWidget.second->RequestUpdate(); } } void QmitkCustomMultiWidget::ForceImmediateUpdate(const QString& widgetID) { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { renderWindowWidget->ForceImmediateUpdate(); } } void QmitkCustomMultiWidget::ForceImmediateUpdateAll() { for (const auto& renderWindowWidget : m_RenderWindowWidgets) { renderWindowWidget.second->ForceImmediateUpdate(); } } const mitk::Point3D QmitkCustomMultiWidget::GetCrossPosition(const QString& widgetID) const { /* const mitk::PlaneGeometry *plane1 = mitkWidget1->GetSliceNavigationController()->GetCurrentPlaneGeometry(); const mitk::PlaneGeometry *plane2 = mitkWidget2->GetSliceNavigationController()->GetCurrentPlaneGeometry(); const mitk::PlaneGeometry *plane3 = mitkWidget3->GetSliceNavigationController()->GetCurrentPlaneGeometry(); mitk::Line3D line; if ((plane1 != NULL) && (plane2 != NULL) && (plane1->IntersectionLine(plane2, line))) { mitk::Point3D point; if ((plane3 != NULL) && (plane3->IntersectionPoint(line, point))) { return point; } } // TODO BUG POSITIONTRACKER; mitk::Point3D p; return p; // return m_LastLeftClickPositionSupplier->GetCurrentPoint(); */ return mitk::Point3D(); } ////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS ////////////////////////////////////////////////////////////////////////// void QmitkCustomMultiWidget::ShowLevelWindowWidget(const QString& widgetID, bool show) { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { renderWindowWidget->ShowLevelWindowWidget(show); return; } MITK_ERROR << "Level window widget can not be shown for an unknown widget."; } void QmitkCustomMultiWidget::ShowAllLevelWindowWidgets(bool show) { for (const auto& renderWindowWidget : m_RenderWindowWidgets) { renderWindowWidget.second->ShowLevelWindowWidget(show); } } void QmitkCustomMultiWidget::HandleCrosshairPositionEvent() { /* if (!m_PendingCrosshairPositionEvent) { m_PendingCrosshairPositionEvent = true; QTimer::singleShot(0, this, SLOT(HandleCrosshairPositionEventDelayed())); } */ } void QmitkCustomMultiWidget::HandleCrosshairPositionEventDelayed() { /* m_PendingCrosshairPositionEvent = false; // find image with highest layer mitk::TNodePredicateDataType::Pointer isImageData = mitk::TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->m_DataStorage->GetSubset(isImageData).GetPointer(); mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; mitk::Image::Pointer image; bool isBinary = false; node = this->GetTopLayerNode(nodes); int component = 0; if (node.IsNotNull()) { node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = m_DataStorage->GetSources(node, NULL, true); if (!sourcenodes->empty()) { topSourceNode = this->GetTopLayerNode(sourcenodes); } if (topSourceNode.IsNotNull()) { image = dynamic_cast(topSourceNode->GetData()); topSourceNode->GetIntProperty("Image.Displayed Component", component); } else { image = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } else { image = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } mitk::Point3D crosshairPos = this->GetCrossPosition(); std::string statusText; std::stringstream stream; itk::Index<3> p; mitk::BaseRenderer *baseRenderer = GetRenderWindow()->GetSliceNavigationController()->GetRenderer(); unsigned int timestep = baseRenderer->GetTimeStep(); if (image.IsNotNull() && (image->GetTimeSteps() > timestep)) { image->GetGeometry()->WorldToIndex(crosshairPos, p); stream.precision(2); stream << "Position: <" << std::fixed << crosshairPos[0] << ", " << std::fixed << crosshairPos[1] << ", " << std::fixed << crosshairPos[2] << "> mm"; stream << "; Index: <" << p[0] << ", " << p[1] << ", " << p[2] << "> "; mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, image->GetChannelDescriptor().GetPixelType(), image, image->GetVolumeData(baseRenderer->GetTimeStep()), p, pixelValue, component); if (fabs(pixelValue) > 1000000 || fabs(pixelValue) < 0.01) { stream << "; Time: " << baseRenderer->GetTime() << " ms; Pixelvalue: " << std::scientific << pixelValue << " "; } else { stream << "; Time: " << baseRenderer->GetTime() << " ms; Pixelvalue: " << pixelValue << " "; } } else { stream << "No image information at this position!"; } statusText = stream.str(); mitk::StatusBar::GetInstance()->DisplayGreyValueText(statusText.c_str()); */ } void QmitkCustomMultiWidget::MoveCrossToPosition(const QString& widgetID, const mitk::Point3D& newPosition) { std::shared_ptr renderWindowWidget = GetRenderWindowWidget(widgetID); if (nullptr != renderWindowWidget) { renderWindowWidget->GetSliceNavigationController()->SelectSliceByPoint(newPosition); renderWindowWidget->RequestUpdate(); return; } MITK_ERROR << "Geometry plane can not be shown for an unknown widget."; } void QmitkCustomMultiWidget::ResetCrosshair() { // #TODO: new concept: we do not want to initialize all views; // we do not want to rely on the geometry planes /* if (m_DataStorage.IsNotNull()) { m_RenderingManager->InitializeViewsByBoundingObjects(m_DataStorage); // m_RenderingManager->InitializeViews( m_DataStorage->ComputeVisibleBoundingGeometry3D() ); // reset interactor to normal slicing SetWidgetPlaneMode(PLANE_MODE_SLICING); } */ } ////////////////////////////////////////////////////////////////////////// // MOUSE EVENTS ////////////////////////////////////////////////////////////////////////// void QmitkCustomMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } void QmitkCustomMultiWidget::mousePressEvent(QMouseEvent* e) { } void QmitkCustomMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the overlays as the MultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkCustomMultiWidget::InitializeGUI() { delete m_CustomMultiWidgetLayout; m_CustomMultiWidgetLayout = new QGridLayout(this); m_CustomMultiWidgetLayout->setContentsMargins(0, 0, 0, 0); setLayout(m_CustomMultiWidgetLayout); } void QmitkCustomMultiWidget::InitializeWidget() { // #TODO: some things have to be handled globally (hold for all render window (widgets) // analyse those things and design a controlling mechanism } void QmitkCustomMultiWidget::InitializeDisplayActionEventHandling() { m_DisplayActionEventBroadcast = mitk::DisplayActionEventBroadcast::New(); m_DisplayActionEventBroadcast->LoadStateMachine("DisplayInteraction.xml"); m_DisplayActionEventBroadcast->SetEventConfig("DisplayConfigPACS.xml"); m_DisplayActionEventHandler = std::make_unique(); m_DisplayActionEventHandler->SetObservableBroadcast(m_DisplayActionEventBroadcast); Synchronize(true); } void QmitkCustomMultiWidget::FillLayout(int row, int column) { int r = 0; int c = 0; for (const auto& renderWindowWidget : m_RenderWindowWidgets) { AddToMultiWidgetLayout(r, c, renderWindowWidget.first); c += 1; if (c > column) { // first column c = 0; // next row r += 1; if (r > row) { return; } } } } mitk::DataNode::Pointer QmitkCustomMultiWidget::GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes) { // #TODO: see T24173 return nodes->front(); } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp index a7e99d6c1e..582801ac25 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp @@ -1,229 +1,230 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical Image Computing. 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 "QmitkRenderWindowWidget.h" // vtk #include // qt widgets module #include QmitkRenderWindowWidget::QmitkRenderWindowWidget(QWidget* parent/* = nullptr*/, const QString& UID/* = ""*/, mitk::DataStorage* dataStorage/* = nullptr*/, mitk::BaseRenderer::RenderingMode::Type renderingMode/* = mitk::BaseRenderer::RenderingMode::Standard*/) : QWidget(parent) , m_UID(UID) , m_DataStorage(dataStorage) , m_RenderingMode(renderingMode) , m_RenderWindow(nullptr) , m_LevelWindowWidget(nullptr) { InitializeGUI(); // TODO: signals / slots } QmitkRenderWindowWidget::~QmitkRenderWindowWidget() { // nothing here } void QmitkRenderWindowWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; if (nullptr != m_RenderWindow) { mitk::BaseRenderer::GetInstance(m_RenderWindow->GetRenderWindow())->SetDataStorage(dataStorage); } } mitk::SliceNavigationController* QmitkRenderWindowWidget::GetSliceNavigationController() const { if (nullptr == m_RenderWindow) { return nullptr; } return m_RenderWindow->GetSliceNavigationController(); } void QmitkRenderWindowWidget::RequestUpdate() { m_RenderingManager->RequestUpdate(m_RenderWindow->GetRenderWindow()); } void QmitkRenderWindowWidget::ForceImmediateUpdate() { m_RenderingManager->ForceImmediateUpdate(m_RenderWindow->GetRenderWindow()); } void QmitkRenderWindowWidget::SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower) { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } m_GradientBackgroundColors.first = upper; m_GradientBackgroundColors.second = lower; vtkRenderer->SetBackground(lower[0], lower[1], lower[2]); vtkRenderer->SetBackground2(upper[0], upper[1], upper[2]); ShowGradientBackground(true); } void QmitkRenderWindowWidget::ShowGradientBackground(bool show) { m_RenderWindow->GetRenderer()->GetVtkRenderer()->SetGradientBackground(show); } bool QmitkRenderWindowWidget::IsGradientBackgroundOn() const { return m_RenderWindow->GetRenderer()->GetVtkRenderer()->GetGradientBackground(); } void QmitkRenderWindowWidget::ShowLevelWindowWidget(bool show) { + /* #TODO: level window will currently be managed by the PACS mode m_LevelWindowWidget->disconnect(this); if (show) { m_LevelWindowWidget->SetDataStorage(m_DataStorage); m_LevelWindowWidget->show(); } else { m_LevelWindowWidget->disconnect(this); m_LevelWindowWidget->hide(); } + */ } void QmitkRenderWindowWidget::SetDecorationColor(const mitk::Color& color) { m_DecorationColor = color; m_RectangleProp->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); m_CornerAnnotation->GetTextProperty()->SetColor(color[0], color[1], color[2]); } void QmitkRenderWindowWidget::ShowColoredRectangle(bool show) { m_RectangleProp->SetVisibility(show); } bool QmitkRenderWindowWidget::IsColoredRectangleVisible() const { return m_RectangleProp->GetVisibility() > 0; } void QmitkRenderWindowWidget::ShowCornerAnnotation(bool show) { m_CornerAnnotation->SetVisibility(show); } bool QmitkRenderWindowWidget::IsCornerAnnotationVisible() const { return m_CornerAnnotation->GetVisibility() > 0; } void QmitkRenderWindowWidget::SetCornerAnnotationText(const std::string& cornerAnnotation) { m_CornerAnnotation->SetText(0, cornerAnnotation.c_str()); } std::string QmitkRenderWindowWidget::GetCornerAnnotationText() const { return std::string(m_CornerAnnotation->GetText(0)); } bool QmitkRenderWindowWidget::IsRenderWindowMenuActivated() const { return m_RenderWindow->GetActivateMenuWidgetFlag(); } void QmitkRenderWindowWidget::InitializeGUI() { m_Layout = new QHBoxLayout(this); m_Layout->setMargin(0); setLayout(m_Layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // create render window for this render window widget m_RenderingManager = mitk::RenderingManager::GetInstance(); //m_RenderingManager = mitk::RenderingManager::New(); m_RenderingManager->SetDataStorage(m_DataStorage); m_RenderWindow = new QmitkRenderWindow(this, m_UID, nullptr, m_RenderingManager, m_RenderingMode); m_RenderWindow->SetLayoutIndex(QmitkCustomMultiWidget::SAGITTAL); // TODO: allow to change layout type later m_RenderWindow->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Sagittal); m_RenderWindow->GetSliceNavigationController()->SetRenderingManager(m_RenderingManager); mitk::TimeGeometry::Pointer timeGeometry = m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll()); m_RenderingManager->InitializeViews(timeGeometry); // create level window widget for this render window widget m_LevelWindowWidget = new QmitkLevelWindowWidget(this); - //m_LevelWindowWidget->setObjectName(QString::fromUtf8("levelWindowWidget")); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(m_LevelWindowWidget->sizePolicy().hasHeightForWidth()); m_LevelWindowWidget->setSizePolicy(sizePolicy); m_LevelWindowWidget->setMaximumWidth(50); m_Layout->addWidget(m_RenderWindow); - m_Layout->addWidget(m_LevelWindowWidget); + //m_Layout->addWidget(m_LevelWindowWidget); // set colors and corner annotation InitializeDecorations(); } void QmitkRenderWindowWidget::InitializeDecorations() { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } // initialize background color gradients float black[3] = { 0.0f, 0.0f, 0.0f }; SetGradientBackgroundColors(black, black); // initialize decoration color, rectangle and annotation text float white[3] = { 1.0f, 1.0f, 1.0f }; m_DecorationColor = white; m_RectangleProp = vtkSmartPointer::New(); m_RectangleProp->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); if (0 == vtkRenderer->HasViewProp(m_RectangleProp)) { vtkRenderer->AddViewProp(m_RectangleProp); } m_CornerAnnotation = vtkSmartPointer::New(); m_CornerAnnotation->SetText(0, "Sagittal"); m_CornerAnnotation->SetMaximumFontSize(12); m_CornerAnnotation->GetTextProperty()->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); if (0 == vtkRenderer->HasViewProp(m_CornerAnnotation)) { vtkRenderer->AddViewProp(m_CornerAnnotation); } } \ No newline at end of file