diff --git a/Modules/Core/include/mitkSliceNavigationController.h b/Modules/Core/include/mitkSliceNavigationController.h index 9b262c68e0..3b4194ba36 100644 --- a/Modules/Core/include/mitkSliceNavigationController.h +++ b/Modules/Core/include/mitkSliceNavigationController.h @@ -1,472 +1,467 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F -#define SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F +#ifndef MITKSLICENAVIGATIONCONTROLLER_H +#define MITKSLICENAVIGATIONCONTROLLER_H #include "mitkBaseController.h" #include "mitkMessage.h" #include "mitkRenderingManager.h" #include "mitkTimeGeometry.h" #include #pragma GCC visibility push(default) #include #pragma GCC visibility pop #include "mitkDataStorage.h" #include "mitkRestorePlanePositionOperation.h" #include #include namespace mitk { #define mitkTimeGeometryEventMacro(classname, super) \ class MITKCORE_EXPORT classname : public super \ { \ public: \ typedef classname Self; \ typedef super Superclass; \ classname(TimeGeometry *aTimeGeometry, unsigned int aPos) : Superclass(aTimeGeometry, aPos) {} \ virtual ~classname() {} \ virtual const char *GetEventName() const { return #classname; } \ virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ virtual ::itk::EventObject *MakeObject() const { return new Self(GetTimeGeometry(), GetPos()); } \ private: \ void operator=(const Self &); \ } class PlaneGeometry; class BaseGeometry; class BaseRenderer; /** * \brief Controls the selection of the slice the associated BaseRenderer * will display * * A SliceNavigationController takes a BaseGeometry or a TimeGeometry as input world geometry * (TODO what are the exact requirements?) and generates a TimeGeometry * as output. The TimeGeometry holds a number of SlicedGeometry3Ds and * these in turn hold a series of PlaneGeometries. One of these PlaneGeometries is * selected as world geometry for the BaseRenderers associated to 2D views. * * The SliceNavigationController holds has Steppers (one for the slice, a * second for the time step), which control the selection of a single * PlaneGeometry from the TimeGeometry. SliceNavigationController generates * ITK events to tell observers, like a BaseRenderer, when the selected slice * or timestep changes. * * Example: * \code * // Initialization * sliceCtrl = mitk::SliceNavigationController::New(); * * // Tell the navigator the geometry to be sliced (with geometry a * // BaseGeometry::ConstPointer) - * sliceCtrl->SetInputWorldGeometry3D(geometry.GetPointer()); + * sliceCtrl->SetInputWorldTimeGeometry(geometry.GetPointer()); * * // Tell the navigator in which direction it shall slice the data * sliceCtrl->SetViewDirection(mitk::SliceNavigationController::Axial); * * // Connect one or more BaseRenderer to this navigator, i.e.: events sent * // by the navigator when stepping through the slices (e.g. by * // sliceCtrl->GetSlice()->Next()) will be received by the BaseRenderer * // (in this example only slice-changes, see also ConnectGeometryTimeEvent * // and ConnectGeometryEvents.) * sliceCtrl->ConnectGeometrySliceEvent(renderer.GetPointer()); * * //create a world geometry and send the information to the connected renderer(s) * sliceCtrl->Update(); * \endcode * * * You can connect visible navigators to a SliceNavigationController, e.g., a * QmitkSliderNavigator (for Qt): * * \code * // Create the visible navigator (a slider with a spin-box) * QmitkSliderNavigator* navigator = * new QmitkSliderNavigator(parent, "slidernavigator"); * * // Connect the navigator to the slice-stepper of the * // SliceNavigationController. For initialization (position, mininal and * // maximal values) the values of the SliceNavigationController are used. * // Thus, accessing methods of a navigator is normally not necessary, since * // everything can be set via the (Qt-independent) SliceNavigationController. * // The QmitkStepperAdapter converts the Qt-signals to Qt-independent * // itk-events. * new QmitkStepperAdapter(navigator, sliceCtrl->GetSlice(), "navigatoradaptor"); * \endcode * * If you do not want that all renderwindows are updated when a new slice is * selected, you can use a specific RenderingManager, which updates only those * renderwindows that should be updated. This is sometimes useful when a 3D view * does not need to be updated when the slices in some 2D views are changed. * QmitkSliderNavigator (for Qt): * * \code * // create a specific RenderingManager * mitk::RenderingManager::Pointer myManager = mitk::RenderingManager::New(); * * // tell the RenderingManager to update only renderwindow1 and renderwindow2 * myManager->AddRenderWindow(renderwindow1); * myManager->AddRenderWindow(renderwindow2); * * // tell the SliceNavigationController of renderwindow1 and renderwindow2 * // to use the specific RenderingManager instead of the global one * renderwindow1->GetSliceNavigationController()->SetRenderingManager(myManager); * renderwindow2->GetSliceNavigationController()->SetRenderingManager(myManager); * \endcode * * \todo implement for non-evenly-timed geometry! * \ingroup NavigationControl */ class MITKCORE_EXPORT SliceNavigationController : public BaseController { public: mitkClassMacro(SliceNavigationController, BaseController); // itkFactorylessNewMacro(Self) // mitkNewMacro1Param(Self, const char *); itkNewMacro(Self); // itkCloneMacro(Self) /** - * \brief Possible view directions, \a Original will uses + * \brief Possible view directions, \a Original will use * the PlaneGeometry instances in a SlicedGeometry3D provided - * as input world geometry (by SetInputWorldGeometry3D). + * as input world geometry (by SetInputWorldTimeGeometry). */ enum ViewDirection { Axial, Sagittal, Coronal, Original }; /** * \brief Set the input world geometry3D out of which the * geometries for slicing will be created. * * Any previous previous set input geometry (3D or Time) will * be ignored in future. */ - void SetInputWorldGeometry3D(const mitk::BaseGeometry *geometry); - itkGetConstObjectMacro(InputWorldGeometry3D, mitk::BaseGeometry); - void SetInputWorldTimeGeometry(const mitk::TimeGeometry *geometry); itkGetConstObjectMacro(InputWorldTimeGeometry, mitk::TimeGeometry); /** * \brief Access the created geometry */ itkGetConstObjectMacro(CreatedWorldGeometry, mitk::TimeGeometry); + itkGetObjectMacro(CreatedWorldGeometry, mitk::TimeGeometry); /** * \brief Set the desired view directions * * \sa ViewDirection * \sa Update(ViewDirection viewDirection, bool top = true, * bool frontside = true, bool rotated = false) */ itkSetEnumMacro(ViewDirection, ViewDirection); itkGetEnumMacro(ViewDirection, ViewDirection); /** * \brief Set the default view direction * * This is used to re-initialize the view direction of the SNC to the * default value with SetViewDirectionToDefault() * * \sa ViewDirection * \sa Update(ViewDirection viewDirection, bool top = true, * bool frontside = true, bool rotated = false) */ itkSetEnumMacro(DefaultViewDirection, ViewDirection); itkGetEnumMacro(DefaultViewDirection, ViewDirection); const char *GetViewDirectionAsString() const; virtual void SetViewDirectionToDefault(); /** * \brief Do the actual creation and send it to the connected * observers (renderers) * */ virtual void Update(); /** * \brief Extended version of Update, additionally allowing to * specify the direction/orientation of the created geometry. * */ virtual void Update(ViewDirection viewDirection, bool top = true, bool frontside = true, bool rotated = false); /** * \brief Send the created geometry to the connected * observers (renderers) * * Called by Update(). */ virtual void SendCreatedWorldGeometry(); /** * \brief Tell observers to re-read the currently selected 2D geometry * */ virtual void SendCreatedWorldGeometryUpdate(); /** * \brief Send the currently selected slice to the connected * observers (renderers) * * Called by Update(). */ virtual void SendSlice(); /** * \brief Send the currently selected time to the connected * observers (renderers) * * Called by Update(). */ virtual void SendTime(); class MITKCORE_EXPORT TimeGeometryEvent : public itk::AnyEvent { public: typedef TimeGeometryEvent Self; typedef itk::AnyEvent Superclass; TimeGeometryEvent(TimeGeometry *aTimeGeometry, unsigned int aPos) : m_TimeGeometry(aTimeGeometry), m_Pos(aPos) {} ~TimeGeometryEvent() override {} const char *GetEventName() const override { return "TimeGeometryEvent"; } bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } ::itk::EventObject *MakeObject() const override { return new Self(m_TimeGeometry, m_Pos); } TimeGeometry *GetTimeGeometry() const { return m_TimeGeometry; } unsigned int GetPos() const { return m_Pos; } private: TimeGeometry::Pointer m_TimeGeometry; unsigned int m_Pos; // TimeGeometryEvent(const Self&); void operator=(const Self &); // just hide }; mitkTimeGeometryEventMacro(GeometrySendEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometryUpdateEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometryTimeEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometrySliceEvent, TimeGeometryEvent); template void ConnectGeometrySendEvent(T *receiver) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometry); unsigned long tag = AddObserver(GeometrySendEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); } template void ConnectGeometryUpdateEvent(T *receiver) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::UpdateGeometry); unsigned long tag = AddObserver(GeometryUpdateEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); } template void ConnectGeometrySliceEvent(T *receiver, bool connectSendEvent = true) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometrySlice); unsigned long tag = AddObserver(GeometrySliceEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); if (connectSendEvent) ConnectGeometrySendEvent(receiver); } template void ConnectGeometryTimeEvent(T *receiver, bool connectSendEvent = true) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometryTime); unsigned long tag = AddObserver(GeometryTimeEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); if (connectSendEvent) ConnectGeometrySendEvent(receiver); } template void ConnectGeometryEvents(T *receiver) { // connect sendEvent only once ConnectGeometrySliceEvent(receiver, false); ConnectGeometryTimeEvent(receiver); } // use a templated method to get the right offset when casting to void* template void Disconnect(T *receiver) { auto i = m_ReceiverToObserverTagsMap.find(static_cast(receiver)); if (i == m_ReceiverToObserverTagsMap.end()) return; const std::list &tags = i->second; for (auto tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { RemoveObserver(*tagIter); } m_ReceiverToObserverTagsMap.erase(i); } Message1 SetCrosshairEvent; /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface * \warning not implemented */ virtual void SetGeometry(const itk::EventObject &geometrySliceEvent); /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface */ virtual void SetGeometrySlice(const itk::EventObject &geometrySliceEvent); /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface */ virtual void SetGeometryTime(const itk::EventObject &geometryTimeEvent); /** \brief Positions the SNC according to the specified point */ void SelectSliceByPoint(const mitk::Point3D &point); - /** \brief Returns the TimeGeometry created by the SNC. */ - mitk::TimeGeometry *GetCreatedWorldGeometry(); - /** \brief Returns the BaseGeometry of the currently selected time step. */ const mitk::BaseGeometry *GetCurrentGeometry3D(); /** \brief Returns the currently selected Plane in the current * BaseGeometry (if existent). */ const mitk::PlaneGeometry *GetCurrentPlaneGeometry(); /** \brief Sets the BaseRenderer associated with this SNC (if any). While * the BaseRenderer is not directly used by SNC, this is a convenience * method to enable BaseRenderer access via the SNC. */ void SetRenderer(BaseRenderer *renderer); /** \brief Gets the BaseRenderer associated with this SNC (if any). While * the BaseRenderer is not directly used by SNC, this is a convenience * method to enable BaseRenderer access via the SNC. Returns nullptr if no * BaseRenderer has been specified*/ BaseRenderer *GetRenderer() const; /** \brief Re-orients the slice stack. All slices will be oriented to the given normal vector. The given point (world coordinates) defines the selected slice. Careful: The resulting axis vectors are not clearly defined this way. If you want to define them clearly, use ReorientSlices (const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1). */ void ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &normal); /** \brief Re-orients the slice stack so that all planes are oriented according to the * given axis vectors. The given Point eventually defines selected slice. */ void ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1); void ExecuteOperation(Operation *operation) override; /** * \brief Feature option to lock planes during mouse interaction. * This option flag disables the mouse event which causes the center * cross to move near by. */ itkSetMacro(SliceLocked, bool); itkGetMacro(SliceLocked, bool); itkBooleanMacro(SliceLocked); /** * \brief Feature option to lock slice rotation. * * This option flag disables separately the rotation of a slice which is * implemented in mitkSliceRotator. */ itkSetMacro(SliceRotationLocked, bool); itkGetMacro(SliceRotationLocked, bool); itkBooleanMacro(SliceRotationLocked); /** * \brief Adjusts the numerical range of the slice stepper according to * the current geometry orientation of this SNC's SlicedGeometry. */ void AdjustSliceStepperRange(); /** \brief Convenience method that returns the time step currently selected by the controller.*/ TimeStepType GetSelectedTimeStep() const; /** \brief Convenience method that returns the time point that corresponds to the selected * time step. The conversion is done using the time geometry of the SliceNavigationController. * If the time geometry is not yet set, this function will always return 0.0.*/ TimePointType GetSelectedTimePoint() const; protected: + SliceNavigationController(); ~SliceNavigationController() override; - mitk::BaseGeometry::ConstPointer m_InputWorldGeometry3D; - mitk::TimeGeometry::ConstPointer m_InputWorldTimeGeometry; + mitk::TimeGeometry::ConstPointer m_InputWorldTimeGeometry; mitk::TimeGeometry::Pointer m_CreatedWorldGeometry; ViewDirection m_ViewDirection; ViewDirection m_DefaultViewDirection; mitk::RenderingManager::Pointer m_RenderingManager; mitk::BaseRenderer *m_Renderer; itkSetMacro(Top, bool); itkGetMacro(Top, bool); itkBooleanMacro(Top); itkSetMacro(FrontSide, bool); itkGetMacro(FrontSide, bool); itkBooleanMacro(FrontSide); itkSetMacro(Rotated, bool); itkGetMacro(Rotated, bool); itkBooleanMacro(Rotated); bool m_Top; bool m_FrontSide; bool m_Rotated; bool m_BlockUpdate; bool m_SliceLocked; bool m_SliceRotationLocked; unsigned int m_OldPos; typedef std::map> ObserverTagsMapType; ObserverTagsMapType m_ReceiverToObserverTagsMap; }; } // namespace mitk -#endif /* SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F */ +#endif // MITKSLICENAVIGATIONCONTROLLER_H diff --git a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp index 65ac9ae5ad..08c3b5454a 100644 --- a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp +++ b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp @@ -1,656 +1,638 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSliceNavigationController.h" #include "mitkAction.h" #include "mitkBaseRenderer.h" #include "mitkCrosshairPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkOperation.h" #include "mitkOperationActor.h" #include "mitkPlaneGeometry.h" #include "mitkProportionalTimeGeometry.h" #include "mitkArbitraryTimeGeometry.h" #include "mitkRenderingManager.h" #include "mitkSlicedGeometry3D.h" #include "mitkVtkPropRenderer.h" #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkInteractionConst.h" #include "mitkNodePredicateDataType.h" #include "mitkOperationEvent.h" #include "mitkPixelTypeMultiplex.h" #include "mitkPlaneOperation.h" #include "mitkPointOperation.h" #include "mitkStatusBar.h" #include "mitkUndoController.h" #include "mitkApplyTransformMatrixOperation.h" #include "mitkMemoryUtilities.h" #include namespace mitk { SliceNavigationController::SliceNavigationController() : BaseController(), - m_InputWorldGeometry3D( mitk::BaseGeometry::ConstPointer() ), - m_InputWorldTimeGeometry( mitk::TimeGeometry::ConstPointer() ), - m_CreatedWorldGeometry( mitk::TimeGeometry::Pointer() ), + m_InputWorldTimeGeometry(mitk::TimeGeometry::ConstPointer()), + m_CreatedWorldGeometry(mitk::TimeGeometry::Pointer()), m_ViewDirection(Axial), m_DefaultViewDirection(Axial), m_RenderingManager( mitk::RenderingManager::Pointer() ), m_Renderer( nullptr ), m_Top(false), m_FrontSide(false), m_Rotated(false), m_BlockUpdate(false), m_SliceLocked(false), m_SliceRotationLocked(false), m_OldPos(0) { typedef itk::SimpleMemberCommand SNCCommandType; SNCCommandType::Pointer sliceStepperChangedCommand, timeStepperChangedCommand; sliceStepperChangedCommand = SNCCommandType::New(); timeStepperChangedCommand = SNCCommandType::New(); sliceStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendSlice); timeStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendTime); m_Slice->AddObserver(itk::ModifiedEvent(), sliceStepperChangedCommand); m_Time->AddObserver(itk::ModifiedEvent(), timeStepperChangedCommand); m_Slice->SetUnitName("mm"); m_Time->SetUnitName("ms"); m_Top = false; m_FrontSide = false; m_Rotated = false; } SliceNavigationController::~SliceNavigationController() {} - void SliceNavigationController::SetInputWorldGeometry3D(const BaseGeometry *geometry) - { - if ( geometry != nullptr ) - { - if (geometry->GetBoundingBox()->GetDiagonalLength2() < eps) - { - itkWarningMacro("setting an empty bounding-box"); - geometry = nullptr; - } - } - if (m_InputWorldGeometry3D != geometry) - { - m_InputWorldGeometry3D = geometry; - m_InputWorldTimeGeometry = mitk::TimeGeometry::ConstPointer(); - this->Modified(); - } - } void SliceNavigationController::SetInputWorldTimeGeometry(const TimeGeometry *geometry) { if ( geometry != nullptr ) { if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() < eps) { itkWarningMacro("setting an empty bounding-box"); geometry = nullptr; } } if (m_InputWorldTimeGeometry != geometry) { m_InputWorldTimeGeometry = geometry; - m_InputWorldGeometry3D = mitk::BaseGeometry::ConstPointer(); this->Modified(); } } void SliceNavigationController::SetViewDirectionToDefault() { m_ViewDirection = m_DefaultViewDirection; } const char *SliceNavigationController::GetViewDirectionAsString() const { const char *viewDirectionString; switch (m_ViewDirection) { case SliceNavigationController::Axial: viewDirectionString = "Axial"; break; case SliceNavigationController::Sagittal: viewDirectionString = "Sagittal"; break; case SliceNavigationController::Coronal: viewDirectionString = "Coronal"; break; case SliceNavigationController::Original: viewDirectionString = "Original"; break; default: viewDirectionString = "No View Direction Available"; break; } return viewDirectionString; } void SliceNavigationController::Update() { if (!m_BlockUpdate) { if (m_ViewDirection == Sagittal) { this->Update(Sagittal, true, true, false); } else if (m_ViewDirection == Coronal) { this->Update(Coronal, false, true, false); } else if (m_ViewDirection == Axial) { this->Update(Axial, false, false, true); } else { this->Update(m_ViewDirection); } } } void SliceNavigationController::Update(SliceNavigationController::ViewDirection viewDirection, bool top, bool frontside, bool rotated) { - TimeGeometry::ConstPointer worldTimeGeometry = m_InputWorldTimeGeometry; - - if (m_BlockUpdate || (m_InputWorldTimeGeometry.IsNull() && m_InputWorldGeometry3D.IsNull()) || - ((worldTimeGeometry.IsNotNull()) && (worldTimeGeometry->CountTimeSteps() == 0))) + if (m_BlockUpdate) { return; } - m_BlockUpdate = true; + if (m_InputWorldTimeGeometry.IsNull()) + { + return; + } - if (m_InputWorldTimeGeometry.IsNotNull() && m_LastUpdateTime < m_InputWorldTimeGeometry->GetMTime()) + if(0 == m_InputWorldTimeGeometry->CountTimeSteps()) { - Modified(); + return; } - if (m_InputWorldGeometry3D.IsNotNull() && m_LastUpdateTime < m_InputWorldGeometry3D->GetMTime()) + + m_BlockUpdate = true; + + if (m_LastUpdateTime < m_InputWorldTimeGeometry->GetMTime()) { Modified(); } + TimeGeometry::ConstPointer worldTimeGeometry = m_InputWorldTimeGeometry; + this->SetViewDirection(viewDirection); this->SetTop(top); this->SetFrontSide(frontside); this->SetRotated(rotated); if (m_LastUpdateTime < GetMTime()) { m_LastUpdateTime = GetMTime(); // initialize the viewplane SlicedGeometry3D::Pointer slicedWorldGeometry = SlicedGeometry3D::Pointer(); BaseGeometry::ConstPointer currentGeometry = BaseGeometry::ConstPointer(); - if (m_InputWorldTimeGeometry.IsNotNull()) - if (m_InputWorldTimeGeometry->IsValidTimeStep(GetTime()->GetPos())) - currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(GetTime()->GetPos()); - else - currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(0); + if (m_InputWorldTimeGeometry->IsValidTimeStep(GetTime()->GetPos())) + currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(GetTime()->GetPos()); else - currentGeometry = m_InputWorldGeometry3D; + currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(0); m_CreatedWorldGeometry = mitk::TimeGeometry::Pointer(); switch (viewDirection) { case Original: if (worldTimeGeometry.IsNotNull()) { m_CreatedWorldGeometry = worldTimeGeometry->Clone(); worldTimeGeometry = m_CreatedWorldGeometry.GetPointer(); slicedWorldGeometry = dynamic_cast( m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()).GetPointer()); if (slicedWorldGeometry.IsNotNull()) { break; } } else { const auto *worldSlicedGeometry = dynamic_cast(currentGeometry.GetPointer()); if ( worldSlicedGeometry != nullptr ) { slicedWorldGeometry = static_cast(currentGeometry->Clone().GetPointer()); break; } } slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::None, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Axial: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Axial, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Coronal: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Coronal, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Sagittal: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Sagittal, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; default: itkExceptionMacro("unknown ViewDirection"); } m_Slice->SetPos(0); m_Slice->SetSteps((int)slicedWorldGeometry->GetSlices()); if ( worldTimeGeometry.IsNull() ) { auto createdTimeGeometry = ProportionalTimeGeometry::New(); createdTimeGeometry->Initialize( slicedWorldGeometry, 1 ); m_CreatedWorldGeometry = createdTimeGeometry; m_Time->SetSteps(0); m_Time->SetPos(0); m_Time->InvalidateRange(); } else { m_BlockUpdate = true; m_Time->SetSteps(worldTimeGeometry->CountTimeSteps()); m_Time->SetPos(0); const TimeBounds &timeBounds = worldTimeGeometry->GetTimeBounds(); m_Time->SetRange(timeBounds[0], timeBounds[1]); m_BlockUpdate = false; const auto currentTemporalPosition = this->GetTime()->GetPos(); assert( worldTimeGeometry->GetGeometryForTimeStep( currentTemporalPosition ).IsNotNull() ); if ( dynamic_cast( worldTimeGeometry.GetPointer() ) != nullptr ) { const TimePointType minimumTimePoint = worldTimeGeometry->TimeStepToTimePoint( currentTemporalPosition ); const TimePointType stepDuration = worldTimeGeometry->TimeStepToTimePoint( currentTemporalPosition + 1 ) - minimumTimePoint; auto createdTimeGeometry = ProportionalTimeGeometry::New(); createdTimeGeometry->Initialize( slicedWorldGeometry, worldTimeGeometry->CountTimeSteps() ); createdTimeGeometry->SetFirstTimePoint( minimumTimePoint ); createdTimeGeometry->SetStepDuration( stepDuration ); m_CreatedWorldGeometry = createdTimeGeometry; } else { auto createdTimeGeometry = mitk::ArbitraryTimeGeometry::New(); const TimeStepType numberOfTimeSteps = worldTimeGeometry->CountTimeSteps(); createdTimeGeometry->ReserveSpaceForGeometries( numberOfTimeSteps ); for ( TimeStepType i = 0; i < numberOfTimeSteps; ++i ) { const BaseGeometry::Pointer clonedGeometry = slicedWorldGeometry->Clone().GetPointer(); const auto bounds = worldTimeGeometry->GetTimeBounds( i ); createdTimeGeometry->AppendNewTimeStep( clonedGeometry, bounds[0], bounds[1]); } createdTimeGeometry->Update(); m_CreatedWorldGeometry = createdTimeGeometry; } } } // unblock update; we may do this now, because if m_BlockUpdate was already // true before this method was entered, then we will never come here. m_BlockUpdate = false; // Send the geometry. Do this even if nothing was changed, because maybe // Update() was only called to re-send the old geometry and time/slice data. this->SendCreatedWorldGeometry(); this->SendSlice(); this->SendTime(); // Adjust the stepper range of slice stepper according to geometry this->AdjustSliceStepperRange(); } void SliceNavigationController::SendCreatedWorldGeometry() { // Send the geometry. Do this even if nothing was changed, because maybe // Update() was only called to re-send the old geometry. if (!m_BlockUpdate) { this->InvokeEvent(GeometrySendEvent(m_CreatedWorldGeometry, 0)); } } void SliceNavigationController::SendCreatedWorldGeometryUpdate() { if (!m_BlockUpdate) { this->InvokeEvent(GeometryUpdateEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); } } void SliceNavigationController::SendSlice() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometrySliceEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SendTime() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometryTimeEvent(m_CreatedWorldGeometry, m_Time->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SetGeometry(const itk::EventObject &) {} void SliceNavigationController::SetGeometryTime(const itk::EventObject &geometryTimeEvent) { if (m_CreatedWorldGeometry.IsNull()) { return; } const auto *timeEvent = dynamic_cast< const SliceNavigationController::GeometryTimeEvent * >(&geometryTimeEvent); assert( timeEvent != nullptr ); TimeGeometry *timeGeometry = timeEvent->GetTimeGeometry(); assert( timeGeometry != nullptr ); auto timeStep = (int)timeEvent->GetPos(); ScalarType timeInMS; timeInMS = timeGeometry->TimeStepToTimePoint(timeStep); timeStep = m_CreatedWorldGeometry->TimePointToTimeStep(timeInMS); this->GetTime()->SetPos(timeStep); } void SliceNavigationController::SetGeometrySlice(const itk::EventObject &geometrySliceEvent) { const auto *sliceEvent = dynamic_cast(&geometrySliceEvent); assert(sliceEvent!=nullptr); this->GetSlice()->SetPos(sliceEvent->GetPos()); } void SliceNavigationController::SelectSliceByPoint(const Point3D &point) { if (m_CreatedWorldGeometry.IsNull()) { return; } //@todo add time to PositionEvent and use here!! SlicedGeometry3D *slicedWorldGeometry = dynamic_cast( m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()).GetPointer()); if (slicedWorldGeometry) { int bestSlice = -1; double bestDistance = itk::NumericTraits::max(); int s, slices; slices = slicedWorldGeometry->GetSlices(); if (slicedWorldGeometry->GetEvenlySpaced()) { mitk::PlaneGeometry *plane = slicedWorldGeometry->GetPlaneGeometry(0); const Vector3D &direction = slicedWorldGeometry->GetDirectionVector(); Point3D projectedPoint; plane->Project(point, projectedPoint); // Check whether the point is somewhere within the slice stack volume; // otherwise, the default slice (0) will be selected if (direction[0] * (point[0] - projectedPoint[0]) + direction[1] * (point[1] - projectedPoint[1]) + direction[2] * (point[2] - projectedPoint[2]) >= 0) { bestSlice = (int)(plane->Distance(point) / slicedWorldGeometry->GetSpacing()[2] + 0.5); } } else { Point3D projectedPoint; for (s = 0; s < slices; ++s) { slicedWorldGeometry->GetPlaneGeometry(s)->Project(point, projectedPoint); const Vector3D distance = projectedPoint - point; ScalarType currentDistance = distance.GetSquaredNorm(); if (currentDistance < bestDistance) { bestDistance = currentDistance; bestSlice = s; } } } if (bestSlice >= 0) { this->GetSlice()->SetPos(bestSlice); } else { this->GetSlice()->SetPos(0); } this->SendCreatedWorldGeometryUpdate(); // send crosshair event SetCrosshairEvent.Send(point); } } void SliceNavigationController::ReorientSlices(const Point3D &point, const Vector3D &normal) { if (m_CreatedWorldGeometry.IsNull()) { return; } PlaneOperation op(OpORIENT, point, normal); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } void SliceNavigationController::ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1) { if (m_CreatedWorldGeometry) { PlaneOperation op(OpORIENT, point, axisVec0, axisVec1); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } } - mitk::TimeGeometry *SliceNavigationController::GetCreatedWorldGeometry() { return m_CreatedWorldGeometry; } const mitk::BaseGeometry *SliceNavigationController::GetCurrentGeometry3D() { if (m_CreatedWorldGeometry.IsNotNull()) { return m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()); } else { return nullptr; } } const mitk::PlaneGeometry *SliceNavigationController::GetCurrentPlaneGeometry() { const auto *slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); if (slicedGeometry) { const mitk::PlaneGeometry *planeGeometry = (slicedGeometry->GetPlaneGeometry(this->GetSlice()->GetPos())); return planeGeometry; } else { return nullptr; } } void SliceNavigationController::SetRenderer(BaseRenderer *renderer) { m_Renderer = renderer; } BaseRenderer *SliceNavigationController::GetRenderer() const { return m_Renderer; } void SliceNavigationController::AdjustSliceStepperRange() { const auto *slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); const Vector3D &direction = slicedGeometry->GetDirectionVector(); int c = 0; int i, k = 0; for (i = 0; i < 3; ++i) { if (fabs(direction[i]) < 0.000000001) { ++c; } else { k = i; } } if (c == 2) { ScalarType min = slicedGeometry->GetOrigin()[k]; ScalarType max = min + slicedGeometry->GetExtentInMM(k); m_Slice->SetRange(min, max); } else { m_Slice->InvalidateRange(); } } void SliceNavigationController::ExecuteOperation(Operation *operation) { // switch on type // - select best slice for a given point // - rotate created world geometry according to Operation->SomeInfo() if (!operation || m_CreatedWorldGeometry.IsNull()) { return; } switch (operation->GetOperationType()) { case OpMOVE: // should be a point operation { if (!m_SliceLocked) // do not move the cross position { // select a slice auto *po = dynamic_cast(operation); if (po && po->GetIndex() == -1) { this->SelectSliceByPoint(po->GetPoint()); } else if (po && po->GetIndex() != -1) // undo case because index != -1, index holds the old position of this slice { this->GetSlice()->SetPos(po->GetIndex()); } } break; } case OpRESTOREPLANEPOSITION: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } case OpAPPLYTRANSFORMMATRIX: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } default: { // do nothing break; } } } TimeStepType SliceNavigationController::GetSelectedTimeStep() const { return this->GetTime()->GetPos(); } TimePointType SliceNavigationController::GetSelectedTimePoint() const { auto timeStep = this->GetSelectedTimeStep(); if (m_CreatedWorldGeometry.IsNull()) { return 0.0; } if (!m_CreatedWorldGeometry->IsValidTimeStep(timeStep)) { mitkThrow() << "SliceNavigationController is in an invalid state. It has a time step" << "selected that is not covered by its time geometry. Selected time step: " << timeStep << "; TimeGeometry steps count: " << m_CreatedWorldGeometry->CountTimeSteps(); } return m_CreatedWorldGeometry->TimeStepToTimePoint(timeStep); } } // namespace diff --git a/Modules/Core/test/mitkSliceNavigationControllerTest.cpp b/Modules/Core/test/mitkSliceNavigationControllerTest.cpp index 232eeb6a33..c9ba5b24a0 100644 --- a/Modules/Core/test/mitkSliceNavigationControllerTest.cpp +++ b/Modules/Core/test/mitkSliceNavigationControllerTest.cpp @@ -1,205 +1,198 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include // T22254 class mitkSliceNavigationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSliceNavigationControllerTestSuite); CPPUNIT_TEST(validateAxialViewDirection); CPPUNIT_TEST(validateCoronalViewDirection); CPPUNIT_TEST(validateSagittalViewDirection); CPPUNIT_TEST(GetSelectedTimePoint); CPPUNIT_TEST_SUITE_END(); mitk::Geometry3D::Pointer m_Geometry3D; mitk::ArbitraryTimeGeometry::Pointer m_TimeGeometry; public: void setUp() override { mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 20.0, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 50.0, 0.0); mitk::Vector3D spacing; mitk::FillVector3D(spacing, 1.0, 1.0, 2.0); auto planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->InitializeStandardPlane(firstAxisVector, secondAxisVector, &spacing); planeGeometry->SetOrigin(origin); unsigned int numberOfSlices = 100U; auto slicedGeometry3D = mitk::SlicedGeometry3D::New(); slicedGeometry3D->InitializeEvenlySpaced(planeGeometry, numberOfSlices); m_Geometry3D = mitk::Geometry3D::New(); m_Geometry3D->SetBounds(slicedGeometry3D->GetBounds()); m_Geometry3D->SetIndexToWorldTransform(slicedGeometry3D->GetIndexToWorldTransform()); m_TimeGeometry = mitk::ArbitraryTimeGeometry::New(); m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 0.5, 10.); m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 10., 30.); m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 30., 50.); m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 50., 60.); m_TimeGeometry->Update(); } void tearDown() override { } void validateAxialViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); - sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); + sliceNavigationController->SetInputWorldTimeGeometry(m_TimeGeometry); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Axial); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 70.0, 229.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, -50.0, 0.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 0.0, 0.0, -200.0); std::cout << "Axial view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } void validateCoronalViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); - sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); + sliceNavigationController->SetInputWorldTimeGeometry(m_TimeGeometry); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Coronal); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 69.5, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 0.0, 200.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 0.0, -50.0, 0.0); std::cout << "Coronal view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } void validateSagittalViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); - sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); + sliceNavigationController->SetInputWorldTimeGeometry(m_TimeGeometry); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Sagittal); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.5, 20.0, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 0.0, 50.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 0.0, 200.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 100.0, 0.0, 0.0); std::cout << "Sagittal view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } void GetSelectedTimePoint() { auto sliceNavigationController = mitk::SliceNavigationController::New(); CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 0.); sliceNavigationController->SetInputWorldTimeGeometry(m_TimeGeometry); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Sagittal); sliceNavigationController->Update(); CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimeStep() == 0); CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 0.5); sliceNavigationController->GetTime()->SetPos(2); CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimeStep() == 2); CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 30.0); auto sliceNavigationController2 = mitk::SliceNavigationController::New(); - - sliceNavigationController2->SetInputWorldGeometry3D(m_Geometry3D); - sliceNavigationController2->SetViewDirection(mitk::SliceNavigationController::Sagittal); - sliceNavigationController2->Update(); - - CPPUNIT_ASSERT(sliceNavigationController2->GetSelectedTimeStep() == 0); - CPPUNIT_ASSERT(sliceNavigationController2->GetSelectedTimePoint() == 0.0); } private: bool validateGeometry(mitk::BaseGeometry::ConstPointer geometry, const mitk::Point3D &origin, const mitk::Vector3D &firstAxisVector, const mitk::Vector3D &secondAxisVector, const mitk::Vector3D &thirdAxisVector) { bool result = true; std::cout << " Origin" << std::endl; if (!mitk::Equal(geometry->GetOrigin(), origin, mitk::eps, true)) result = false; std::cout << " First axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(0), firstAxisVector, mitk::eps, true)) result = false; std::cout << " Second axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(1), secondAxisVector, mitk::eps, true)) result = false; std::cout << " Third axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(2), thirdAxisVector, mitk::eps, true)) result = false; return result; } }; MITK_TEST_SUITE_REGISTRATION(mitkSliceNavigationController) diff --git a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp index c22961b79f..b0423f5a91 100644 --- a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp +++ b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp @@ -1,612 +1,563 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageNavigatorView.h" #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageNavigatorView::VIEW_ID = "org.mitk.views.imagenavigator"; QmitkImageNavigatorView::QmitkImageNavigatorView() : m_AxialStepper(nullptr) , m_SagittalStepper(nullptr) , m_CoronalStepper(nullptr) , m_TimeStepper(nullptr) , m_Parent(nullptr) , m_IRenderWindowPart(nullptr) { } QmitkImageNavigatorView::~QmitkImageNavigatorView() { } void QmitkImageNavigatorView::CreateQtPartControl(QWidget *parent) { // create GUI widgets m_Parent = parent; m_Controls.setupUi(parent); connect(m_Controls.m_XWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); connect(m_Controls.m_YWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); connect(m_Controls.m_ZWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); m_Parent->setEnabled(false); mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); this->RenderWindowPartActivated(renderPart); } void QmitkImageNavigatorView::SetFocus () { m_Controls.m_XWorldCoordinateSpinBox->setFocus(); } void QmitkImageNavigatorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (this->m_IRenderWindowPart != renderWindowPart) { this->m_IRenderWindowPart = renderWindowPart; this->m_Parent->setEnabled(true); QmitkRenderWindow* renderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); if (renderWindow) { if (m_AxialStepper) m_AxialStepper->deleteLater(); m_AxialStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorAxial, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorAxialFromSimpleExample"); m_Controls.m_SliceNavigatorAxial->setEnabled(true); m_Controls.m_AxialLabel->setEnabled(true); m_Controls.m_ZWorldCoordinateSpinBox->setEnabled(true); connect(m_AxialStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); connect(m_AxialStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorAxial->setEnabled(false); m_Controls.m_AxialLabel->setEnabled(false); m_Controls.m_ZWorldCoordinateSpinBox->setEnabled(false); } renderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); if (renderWindow) { if (m_SagittalStepper) m_SagittalStepper->deleteLater(); m_SagittalStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorSagittal, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorSagittalFromSimpleExample"); m_Controls.m_SliceNavigatorSagittal->setEnabled(true); m_Controls.m_SagittalLabel->setEnabled(true); m_Controls.m_YWorldCoordinateSpinBox->setEnabled(true); connect(m_SagittalStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); connect(m_SagittalStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorSagittal->setEnabled(false); m_Controls.m_SagittalLabel->setEnabled(false); m_Controls.m_YWorldCoordinateSpinBox->setEnabled(false); } renderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); if (renderWindow) { if (m_CoronalStepper) m_CoronalStepper->deleteLater(); m_CoronalStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorCoronal, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorCoronalFromSimpleExample"); m_Controls.m_SliceNavigatorCoronal->setEnabled(true); m_Controls.m_CoronalLabel->setEnabled(true); m_Controls.m_XWorldCoordinateSpinBox->setEnabled(true); connect(m_CoronalStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); connect(m_CoronalStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorCoronal->setEnabled(false); m_Controls.m_CoronalLabel->setEnabled(false); m_Controls.m_XWorldCoordinateSpinBox->setEnabled(false); } mitk::SliceNavigationController* timeController = renderWindowPart->GetTimeNavigationController(); if (timeController) { if (m_TimeStepper) m_TimeStepper->deleteLater(); m_TimeStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorTime, timeController->GetTime(), "sliceNavigatorTimeFromSimpleExample"); m_Controls.m_SliceNavigatorTime->setEnabled(true); m_Controls.m_TimeLabel->setEnabled(true); connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorTime->setEnabled(false); m_Controls.m_TimeLabel->setEnabled(false); } this->OnRefetch(); this->UpdateStatusBar(); } } void QmitkImageNavigatorView::UpdateStatusBar() { if (m_IRenderWindowPart != nullptr) { mitk::Point3D position = m_IRenderWindowPart->GetSelectedPosition(); mitk::BaseRenderer::Pointer baseRenderer = mitk::BaseRenderer::GetInstance(m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetVtkRenderWindow()); auto globalCurrentTimePoint = baseRenderer->GetTime(); mitk::TNodePredicateDataType::Pointer isImageData = mitk::TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = GetDataStorage()->GetSubset(isImageData).GetPointer(); if (nodes.IsNotNull()) { mitk::Image::Pointer image3D; mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; int component = 0; node = mitk::FindTopmostVisibleNode(nodes, position, globalCurrentTimePoint, baseRenderer); if (node.IsNotNull()) { bool isBinary(false); node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = GetDataStorage()->GetSources(node, nullptr, true); if (!sourcenodes->empty()) { topSourceNode = mitk::FindTopmostVisibleNode(sourcenodes, position, 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 pixel value from the image and build up status bar text auto statusBar = mitk::StatusBar::GetInstance(); if (image3D.IsNotNull() && statusBar != nullptr) { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(position, p); auto pixelType = image3D->GetChannelDescriptor().GetPixelType().GetPixelType(); if (pixelType == itk::IOPixelEnum::RGB || pixelType == itk::IOPixelEnum::RGBA) { std::string pixelValue = "Pixel RGB(A) value: "; pixelValue.append(ConvertCompositePixelValueToString(image3D, p)); statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue.c_str()); } else if (pixelType == itk::IOPixelEnum::DIFFUSIONTENSOR3D || pixelType == itk::IOPixelEnum::SYMMETRICSECONDRANKTENSOR) { std::string pixelValue = "See ODF Details view. "; statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue.c_str()); } else { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(position, p); mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, image3D->GetVolumeData(image3D->GetTimeGeometry()->TimePointToTimeStep(globalCurrentTimePoint)), p, pixelValue, component); statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue); } } else { statusBar->DisplayImageInfoInvalid(); } } } } void QmitkImageNavigatorView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_IRenderWindowPart = nullptr; m_Parent->setEnabled(false); } int QmitkImageNavigatorView::GetSizeFlags(bool width) { if(!width) { return berry::Constants::MIN | berry::Constants::MAX | berry::Constants::FILL; } else { return 0; } } int QmitkImageNavigatorView::ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) { if(width==false) { return 200; } else { return preferredResult; } } int QmitkImageNavigatorView::GetClosestAxisIndex(mitk::Vector3D normal) { // cos(theta) = normal . axis // cos(theta) = (a, b, c) . (d, e, f) // cos(theta) = (a, b, c) . (1, 0, 0) = a // cos(theta) = (a, b, c) . (0, 1, 0) = b // cos(theta) = (a, b, c) . (0, 0, 1) = c double absCosThetaWithAxis[3]; for (int i = 0; i < 3; i++) { absCosThetaWithAxis[i] = fabs(normal[i]); } int largestIndex = 0; double largestValue = absCosThetaWithAxis[0]; for (int i = 1; i < 3; i++) { if (absCosThetaWithAxis[i] > largestValue) { largestValue = absCosThetaWithAxis[i]; largestIndex = i; } } return largestIndex; } void QmitkImageNavigatorView::SetBorderColors() { if (m_IRenderWindowPart) { QString decoColor; QmitkRenderWindow* renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("axial"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("sagittal"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("coronal"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } } } QString QmitkImageNavigatorView::GetDecorationColorOfGeometry(QmitkRenderWindow* renderWindow) { QColor color; float rgb[3] = {1.0f, 1.0f, 1.0f}; float rgbMax = 255.0f; mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow())->GetCurrentWorldPlaneGeometryNode()->GetColor(rgb); color.setRed(static_cast(rgb[0]*rgbMax + 0.5)); color.setGreen(static_cast(rgb[1]*rgbMax + 0.5)); color.setBlue(static_cast(rgb[2]*rgbMax + 0.5)); QString colorAsString = QString(color.name()); return colorAsString; } void QmitkImageNavigatorView::SetBorderColor(int axis, QString colorAsStyleSheetString) { if (axis == 0) { this->SetBorderColor(m_Controls.m_XWorldCoordinateSpinBox, colorAsStyleSheetString); } else if (axis == 1) { this->SetBorderColor(m_Controls.m_YWorldCoordinateSpinBox, colorAsStyleSheetString); } else if (axis == 2) { this->SetBorderColor(m_Controls.m_ZWorldCoordinateSpinBox, colorAsStyleSheetString); } } void QmitkImageNavigatorView::SetBorderColor(QDoubleSpinBox *spinBox, QString colorAsStyleSheetString) { assert(spinBox); spinBox->setStyleSheet(QString("border: 2px solid ") + colorAsStyleSheetString + ";"); } -void QmitkImageNavigatorView::SetStepSizes() -{ - this->SetStepSize(0); - this->SetStepSize(1); - this->SetStepSize(2); -} - -void QmitkImageNavigatorView::SetStepSize(int axis) -{ - if (m_IRenderWindowPart) - { - mitk::BaseGeometry::ConstPointer geometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldGeometry3D(); - - if (geometry.IsNotNull()) - { - mitk::Point3D crossPositionInIndexCoordinates; - mitk::Point3D crossPositionInIndexCoordinatesPlus1; - mitk::Point3D crossPositionInMillimetresPlus1; - mitk::Vector3D transformedAxisDirection; - - mitk::Point3D crossPositionInMillimetres = m_IRenderWindowPart->GetSelectedPosition(); - geometry->WorldToIndex(crossPositionInMillimetres, crossPositionInIndexCoordinates); - - crossPositionInIndexCoordinatesPlus1 = crossPositionInIndexCoordinates; - crossPositionInIndexCoordinatesPlus1[axis] += 1; - - geometry->IndexToWorld(crossPositionInIndexCoordinatesPlus1, crossPositionInMillimetresPlus1); - - transformedAxisDirection = crossPositionInMillimetresPlus1 - crossPositionInMillimetres; - - int closestAxisInMillimetreSpace = this->GetClosestAxisIndex(transformedAxisDirection); - double stepSize = transformedAxisDirection.GetNorm(); - this->SetStepSize(closestAxisInMillimetreSpace, stepSize); - } - } -} - -void QmitkImageNavigatorView::SetStepSize(int axis, double stepSize) -{ - if (axis == 0) - { - m_Controls.m_XWorldCoordinateSpinBox->setSingleStep(stepSize); - } - else if (axis == 1) - { - m_Controls.m_YWorldCoordinateSpinBox->setSingleStep(stepSize); - } - else if (axis == 2) - { - m_Controls.m_ZWorldCoordinateSpinBox->setSingleStep(stepSize); - } -} - void QmitkImageNavigatorView::OnMillimetreCoordinateValueChanged() { if (m_IRenderWindowPart) { mitk::TimeGeometry::ConstPointer geometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldTimeGeometry(); if (geometry.IsNotNull()) { mitk::Point3D positionInWorldCoordinates; positionInWorldCoordinates[0] = m_Controls.m_XWorldCoordinateSpinBox->value(); positionInWorldCoordinates[1] = m_Controls.m_YWorldCoordinateSpinBox->value(); positionInWorldCoordinates[2] = m_Controls.m_ZWorldCoordinateSpinBox->value(); m_IRenderWindowPart->SetSelectedPosition(positionInWorldCoordinates); } } } void QmitkImageNavigatorView::OnRefetch() { - if (m_IRenderWindowPart) + if (nullptr == m_IRenderWindowPart) { - mitk::BaseGeometry::ConstPointer geometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldGeometry3D(); - mitk::TimeGeometry::ConstPointer timeGeometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldTimeGeometry(); + return; + } - if (geometry.IsNull() && timeGeometry.IsNotNull()) - { - mitk::TimeStepType timeStep = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetTime()->GetPos(); - geometry = timeGeometry->GetGeometryForTimeStep(timeStep); - SetVisibilityOfTimeSlider(timeGeometry->CountTimeSteps()); - } + mitk::TimeGeometry::ConstPointer timeGeometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldTimeGeometry(); - if (geometry.IsNotNull()) - { - mitk::BoundingBox::BoundsArrayType bounds = geometry->GetBounds(); + if (timeGeometry.IsNull()) + { + return; + } + + SetVisibilityOfTimeSlider(timeGeometry->CountTimeSteps()); + mitk::TimeStepType timeStep = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetTime()->GetPos(); + mitk::BaseGeometry::Pointer geometry = timeGeometry->GetGeometryForTimeStep(timeStep); + if (geometry.IsNotNull()) + { + mitk::BoundingBox::BoundsArrayType bounds = geometry->GetBounds(); - mitk::Point3D cornerPoint1InIndexCoordinates; - cornerPoint1InIndexCoordinates[0] = bounds[0]; - cornerPoint1InIndexCoordinates[1] = bounds[2]; - cornerPoint1InIndexCoordinates[2] = bounds[4]; + mitk::Point3D cornerPoint1InIndexCoordinates; + cornerPoint1InIndexCoordinates[0] = bounds[0]; + cornerPoint1InIndexCoordinates[1] = bounds[2]; + cornerPoint1InIndexCoordinates[2] = bounds[4]; - mitk::Point3D cornerPoint2InIndexCoordinates; - cornerPoint2InIndexCoordinates[0] = bounds[1]; - cornerPoint2InIndexCoordinates[1] = bounds[3]; - cornerPoint2InIndexCoordinates[2] = bounds[5]; + mitk::Point3D cornerPoint2InIndexCoordinates; + cornerPoint2InIndexCoordinates[0] = bounds[1]; + cornerPoint2InIndexCoordinates[1] = bounds[3]; + cornerPoint2InIndexCoordinates[2] = bounds[5]; - if (!geometry->GetImageGeometry()) - { - cornerPoint1InIndexCoordinates[0] += 0.5; - cornerPoint1InIndexCoordinates[1] += 0.5; - cornerPoint1InIndexCoordinates[2] += 0.5; - cornerPoint2InIndexCoordinates[0] -= 0.5; - cornerPoint2InIndexCoordinates[1] -= 0.5; - cornerPoint2InIndexCoordinates[2] -= 0.5; - } + if (!geometry->GetImageGeometry()) + { + cornerPoint1InIndexCoordinates[0] += 0.5; + cornerPoint1InIndexCoordinates[1] += 0.5; + cornerPoint1InIndexCoordinates[2] += 0.5; + cornerPoint2InIndexCoordinates[0] -= 0.5; + cornerPoint2InIndexCoordinates[1] -= 0.5; + cornerPoint2InIndexCoordinates[2] -= 0.5; + } + + mitk::Point3D crossPositionInWorldCoordinates = m_IRenderWindowPart->GetSelectedPosition(); - mitk::Point3D crossPositionInWorldCoordinates = m_IRenderWindowPart->GetSelectedPosition(); + mitk::Point3D cornerPoint1InWorldCoordinates; + mitk::Point3D cornerPoint2InWorldCoordinates; - mitk::Point3D cornerPoint1InWorldCoordinates; - mitk::Point3D cornerPoint2InWorldCoordinates; + geometry->IndexToWorld(cornerPoint1InIndexCoordinates, cornerPoint1InWorldCoordinates); + geometry->IndexToWorld(cornerPoint2InIndexCoordinates, cornerPoint2InWorldCoordinates); - geometry->IndexToWorld(cornerPoint1InIndexCoordinates, cornerPoint1InWorldCoordinates); - geometry->IndexToWorld(cornerPoint2InIndexCoordinates, cornerPoint2InWorldCoordinates); + m_Controls.m_XWorldCoordinateSpinBox->blockSignals(true); + m_Controls.m_YWorldCoordinateSpinBox->blockSignals(true); + m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(true); - m_Controls.m_XWorldCoordinateSpinBox->blockSignals(true); - m_Controls.m_YWorldCoordinateSpinBox->blockSignals(true); - m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(true); + m_Controls.m_XWorldCoordinateSpinBox->setMinimum( + std::min(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); + m_Controls.m_YWorldCoordinateSpinBox->setMinimum( + std::min(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); + m_Controls.m_ZWorldCoordinateSpinBox->setMinimum( + std::min(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); + m_Controls.m_XWorldCoordinateSpinBox->setMaximum( + std::max(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); + m_Controls.m_YWorldCoordinateSpinBox->setMaximum( + std::max(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); + m_Controls.m_ZWorldCoordinateSpinBox->setMaximum( + std::max(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); - m_Controls.m_XWorldCoordinateSpinBox->setMinimum(std::min(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); - m_Controls.m_YWorldCoordinateSpinBox->setMinimum(std::min(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); - m_Controls.m_ZWorldCoordinateSpinBox->setMinimum(std::min(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); - m_Controls.m_XWorldCoordinateSpinBox->setMaximum(std::max(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); - m_Controls.m_YWorldCoordinateSpinBox->setMaximum(std::max(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); - m_Controls.m_ZWorldCoordinateSpinBox->setMaximum(std::max(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); + m_Controls.m_XWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[0]); + m_Controls.m_YWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[1]); + m_Controls.m_ZWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[2]); - m_Controls.m_XWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[0]); - m_Controls.m_YWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[1]); - m_Controls.m_ZWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[2]); + m_Controls.m_XWorldCoordinateSpinBox->blockSignals(false); + m_Controls.m_YWorldCoordinateSpinBox->blockSignals(false); + m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(false); - m_Controls.m_XWorldCoordinateSpinBox->blockSignals(false); - m_Controls.m_YWorldCoordinateSpinBox->blockSignals(false); - m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(false); + /// Calculating 'inverse direction' property. - /// Calculating 'inverse direction' property. + mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); + matrix.GetVnlMatrix().normalize_columns(); + mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); - mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); - matrix.GetVnlMatrix().normalize_columns(); - mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); + for (int worldAxis = 0; worldAxis < 3; ++worldAxis) + { + QmitkRenderWindow *renderWindow = worldAxis == 0 ? m_IRenderWindowPart->GetQmitkRenderWindow("sagittal") : + worldAxis == 1 ? m_IRenderWindowPart->GetQmitkRenderWindow("coronal") : + m_IRenderWindowPart->GetQmitkRenderWindow("axial"); - for (int worldAxis = 0; worldAxis < 3; ++worldAxis) + if (renderWindow) { - QmitkRenderWindow* renderWindow = - worldAxis == 0 ? m_IRenderWindowPart->GetQmitkRenderWindow("sagittal") : - worldAxis == 1 ? m_IRenderWindowPart->GetQmitkRenderWindow("coronal") : - m_IRenderWindowPart->GetQmitkRenderWindow("axial"); + const mitk::BaseGeometry *rendererGeometry = renderWindow->GetRenderer()->GetCurrentWorldGeometry(); - if (renderWindow) + /// Because of some problems with the current way of event signalling, + /// 'Modified' events are sent out from the stepper while the renderer + /// does not have a geometry yet. Therefore, we do a nullptr check here. + /// See bug T22122. This check can be resolved after T22122 got fixed. + if (rendererGeometry) { - const mitk::BaseGeometry* rendererGeometry = renderWindow->GetRenderer()->GetCurrentWorldGeometry(); - - /// Because of some problems with the current way of event signalling, - /// 'Modified' events are sent out from the stepper while the renderer - /// does not have a geometry yet. Therefore, we do a nullptr check here. - /// See bug T22122. This check can be resolved after T22122 got fixed. - if (rendererGeometry) - { - int dominantAxis = itk::Function::Max3( - inverseMatrix[0][worldAxis], - inverseMatrix[1][worldAxis], - inverseMatrix[2][worldAxis]); - - bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][worldAxis] < 0; - bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[worldAxis] < 0; - - /// `referenceGeometryAxisInverted` tells if the direction of the corresponding axis - /// of the reference geometry is flipped compared to the 'world direction' or not. - /// - /// `rendererZAxisInverted` tells if direction of the renderer geometry z axis is - /// flipped compared to the 'world direction' or not. This is the same as the indexing - /// direction in the slice navigation controller and matches the 'top' property when - /// initialising the renderer planes. (If 'top' was true then the direction is - /// inverted.) - /// - /// The world direction can be +1 ('up') that means right, anterior or superior, or - /// it can be -1 ('down') that means left, posterior or inferior, respectively. - /// - /// If these two do not match, we have to invert the index between the slice navigation - /// controller and the slider navigator widget, so that the user can see and control - /// the index according to the reference geometry, rather than the slice navigation - /// controller. The index in the slice navigation controller depends on in which way - /// the reference geometry has been resliced for the renderer, and it does not necessarily - /// match neither the world direction, nor the direction of the corresponding axis of - /// the reference geometry. Hence, it is a merely internal information that should not - /// be exposed to the GUI. - /// - /// So that one can navigate in the same world direction by dragging the slider - /// right, regardless of the direction of the corresponding axis of the reference - /// geometry, we invert the direction of the controls if the reference geometry axis - /// is inverted but the direction is not ('inversDirection' is false) or the other - /// way around. - - bool inverseDirection = referenceGeometryAxisInverted != rendererZAxisInverted; - - QmitkSliderNavigatorWidget* navigatorWidget = - worldAxis == 0 ? m_Controls.m_SliceNavigatorSagittal : - worldAxis == 1 ? m_Controls.m_SliceNavigatorCoronal : - m_Controls.m_SliceNavigatorAxial; - - navigatorWidget->SetInverseDirection(inverseDirection); - - // This should be a preference (see T22254) - // bool invertedControls = referenceGeometryAxisInverted != inverseDirection; - // navigatorWidget->SetInvertedControls(invertedControls); - } + int dominantAxis = + itk::Function::Max3(inverseMatrix[0][worldAxis], inverseMatrix[1][worldAxis], inverseMatrix[2][worldAxis]); + + bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][worldAxis] < 0; + bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[worldAxis] < 0; + + /// `referenceGeometryAxisInverted` tells if the direction of the corresponding axis + /// of the reference geometry is flipped compared to the 'world direction' or not. + /// + /// `rendererZAxisInverted` tells if direction of the renderer geometry z axis is + /// flipped compared to the 'world direction' or not. This is the same as the indexing + /// direction in the slice navigation controller and matches the 'top' property when + /// initialising the renderer planes. (If 'top' was true then the direction is + /// inverted.) + /// + /// The world direction can be +1 ('up') that means right, anterior or superior, or + /// it can be -1 ('down') that means left, posterior or inferior, respectively. + /// + /// If these two do not match, we have to invert the index between the slice navigation + /// controller and the slider navigator widget, so that the user can see and control + /// the index according to the reference geometry, rather than the slice navigation + /// controller. The index in the slice navigation controller depends on in which way + /// the reference geometry has been resliced for the renderer, and it does not necessarily + /// match neither the world direction, nor the direction of the corresponding axis of + /// the reference geometry. Hence, it is a merely internal information that should not + /// be exposed to the GUI. + /// + /// So that one can navigate in the same world direction by dragging the slider + /// right, regardless of the direction of the corresponding axis of the reference + /// geometry, we invert the direction of the controls if the reference geometry axis + /// is inverted but the direction is not ('inversDirection' is false) or the other + /// way around. + + bool inverseDirection = referenceGeometryAxisInverted != rendererZAxisInverted; + + QmitkSliderNavigatorWidget* navigatorWidget = + worldAxis == 0 ? m_Controls.m_SliceNavigatorSagittal : + worldAxis == 1 ? m_Controls.m_SliceNavigatorCoronal : + m_Controls.m_SliceNavigatorAxial; + + navigatorWidget->SetInverseDirection(inverseDirection); + + // This should be a preference (see T22254) + // bool invertedControls = referenceGeometryAxisInverted != inverseDirection; + // navigatorWidget->SetInvertedControls(invertedControls); } } } - - this->SetBorderColors(); - } + + this->SetBorderColors(); } void QmitkImageNavigatorView::SetVisibilityOfTimeSlider(std::size_t timeSteps) { m_Controls.m_SliceNavigatorTime->setVisible(timeSteps > 1); m_Controls.m_TimeLabel->setVisible(timeSteps > 1); } diff --git a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h index a9d687bf67..110ef123c4 100644 --- a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h +++ b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h @@ -1,100 +1,95 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef _QMITKIMAGENAVIGATORVIEW_H_INCLUDED -#define _QMITKIMAGENAVIGATORVIEW_H_INCLUDED +#ifndef QMITKIMAGENAVIGATORVIEW_H +#define QMITKIMAGENAVIGATORVIEW_H #include #include #include #include "ui_QmitkImageNavigatorViewControls.h" - class QmitkStepperAdapter; /*! * \ingroup org_mitk_gui_qt_imagenavigator_internal * * \class QmitkImageNavigatorView * * \brief Provides a means to scan quickly through a dataset via Axial, * Coronal and Sagittal sliders, displaying millimetre location and stepper position. * * For images, the stepper position corresponds to a voxel index. For other datasets * such as a surface, it corresponds to a sub-division of the bounding box. * * \sa QmitkAbstractView */ class QmitkImageNavigatorView : public QmitkAbstractView, public mitk::IRenderWindowPartListener, public berry::ISizeProvider { - // this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: static const std::string VIEW_ID; QmitkImageNavigatorView(); ~QmitkImageNavigatorView() override; void CreateQtPartControl(QWidget *parent) override; int GetSizeFlags(bool width) override; int ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) override; protected slots: void OnMillimetreCoordinateValueChanged(); void OnRefetch(); void UpdateStatusBar(); protected: void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) override; void SetBorderColors(); void SetBorderColor(QDoubleSpinBox *spinBox, QString colorAsStyleSheetString); void SetBorderColor(int axis, QString colorAsStyleSheetString); - void SetStepSizes(); - void SetStepSize(int axis); - void SetStepSize(int axis, double stepSize); int GetClosestAxisIndex(mitk::Vector3D normal); void SetVisibilityOfTimeSlider(std::size_t timeSteps); Ui::QmitkImageNavigatorViewControls m_Controls; QmitkStepperAdapter* m_AxialStepper; QmitkStepperAdapter* m_SagittalStepper; QmitkStepperAdapter* m_CoronalStepper; QmitkStepperAdapter* m_TimeStepper; QWidget* m_Parent; mitk::IRenderWindowPart* m_IRenderWindowPart; /** * @brief GetDecorationColorOfGeometry helper method to get the color of a helper geometry node. * @param renderWindow The renderwindow of the geometry * @return the color for decoration in QString format (#RRGGBB). */ QString GetDecorationColorOfGeometry(QmitkRenderWindow *renderWindow); }; -#endif // _QMITKIMAGENAVIGATORVIEW_H_INCLUDED +#endif // QMITKIMAGENAVIGATORVIEW_H