diff --git a/Modules/Core/include/mitkSliceNavigationController.h b/Modules/Core/include/mitkSliceNavigationController.h index 3e47fe2da4..0c6a0395e5 100644 --- a/Modules/Core/include/mitkSliceNavigationController.h +++ b/Modules/Core/include/mitkSliceNavigationController.h @@ -1,461 +1,454 @@ /*============================================================================ 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 MITKSLICENAVIGATIONCONTROLLER_H #define MITKSLICENAVIGATIONCONTROLLER_H #include #include #include #include #include #include #pragma GCC visibility push(default) #include #pragma GCC visibility pop #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->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 use * the PlaneGeometry instances in a SlicedGeometry3D provided * 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 SetInputWorldTimeGeometry(const TimeGeometry* geometry); itkGetConstObjectMacro(InputWorldTimeGeometry, TimeGeometry); /** * \brief Access the created geometry */ itkGetConstObjectMacro(CreatedWorldGeometry, TimeGeometry); itkGetObjectMacro(CreatedWorldGeometry, 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) { auto 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) { auto 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) { auto 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); } template void ConnectGeometryTimeEvent(T* receiver) { auto 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); } 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 Point3D& point); /** \brief Returns the BaseGeometry of the currently selected time step. */ const BaseGeometry* GetCurrentGeometry3D(); /** \brief Returns the currently selected Plane in the current * BaseGeometry (if existent). */ const 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 Sets / 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. + */ + itkSetObjectMacro(Renderer, BaseRenderer); + itkGetMacro(Renderer, BaseRenderer*); /** \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 Point3D &point, const Vector3D &axisVec0, const Vector3D &axisVec1). */ void ReorientSlices(const Point3D& point, const 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 Point3D& point, const Vector3D& axisVec0, const 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; TimeGeometry::ConstPointer m_InputWorldTimeGeometry; TimeGeometry::Pointer m_CreatedWorldGeometry; ViewDirection m_ViewDirection; ViewDirection m_DefaultViewDirection; RenderingManager::Pointer m_RenderingManager; 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 // MITKSLICENAVIGATIONCONTROLLER_H diff --git a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp index 1f37f53bcd..560575d935 100644 --- a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp +++ b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp @@ -1,624 +1,630 @@ /*============================================================================ 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 "mitkBaseRenderer.h" #include "mitkCrosshairPositionEvent.h" #include "mitkOperation.h" #include "mitkOperationActor.h" #include "mitkPlaneGeometry.h" #include "mitkProportionalTimeGeometry.h" #include "mitkArbitraryTimeGeometry.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 "mitkApplyTransformMatrixOperation.h" namespace mitk { SliceNavigationController::SliceNavigationController() : BaseController() , m_InputWorldTimeGeometry(TimeGeometry::ConstPointer()) , m_CreatedWorldGeometry(TimeGeometry::Pointer()) , m_ViewDirection(Axial) , m_DefaultViewDirection(Axial) , m_RenderingManager(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() {} + SliceNavigationController::~SliceNavigationController() + { + // nothing here + } void SliceNavigationController::SetInputWorldTimeGeometry(const TimeGeometry* geometry) { - if (geometry != nullptr) + if (nullptr != geometry) { if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() < eps) { itkWarningMacro("setting an empty bounding-box"); geometry = nullptr; } } + if (m_InputWorldTimeGeometry != geometry) { m_InputWorldTimeGeometry = geometry; this->Modified(); } } - void SliceNavigationController::SetViewDirectionToDefault() { m_ViewDirection = m_DefaultViewDirection; } + 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) { if (m_BlockUpdate) { return; } if (m_InputWorldTimeGeometry.IsNull()) { return; } if(0 == m_InputWorldTimeGeometry->CountTimeSteps()) { return; } 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->IsValidTimeStep(GetTime()->GetPos())) currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(GetTime()->GetPos()); else currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(0); m_CreatedWorldGeometry = 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 = 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::SetGeometry(const itk::EventObject&) + { + // not implemented + } + void SliceNavigationController::SetGeometryTime(const itk::EventObject& geometryTimeEvent) { if (m_CreatedWorldGeometry.IsNull()) { return; } const auto* timeEvent = dynamic_cast(&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( + const auto* slicedWorldGeometry = dynamic_cast( m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()).GetPointer()); - if (slicedWorldGeometry) + if (nullptr == slicedWorldGeometry) { - int bestSlice = -1; - double bestDistance = itk::NumericTraits::max(); + return; + } - int s, slices; - slices = slicedWorldGeometry->GetSlices(); - if (slicedWorldGeometry->GetEvenlySpaced()) - { - PlaneGeometry* plane = slicedWorldGeometry->GetPlaneGeometry(0); + int bestSlice = -1; + double bestDistance = itk::NumericTraits::max(); - const Vector3D& direction = slicedWorldGeometry->GetDirectionVector(); + if (slicedWorldGeometry->GetEvenlySpaced()) + { + PlaneGeometry* plane = slicedWorldGeometry->GetPlaneGeometry(0); - Point3D projectedPoint; - plane->Project(point, projectedPoint); + const Vector3D& direction = slicedWorldGeometry->GetDirectionVector(); - // 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(); + Point3D projectedPoint; + plane->Project(point, projectedPoint); - if (currentDistance < bestDistance) - { - bestDistance = currentDistance; - bestSlice = s; - } - } - } - if (bestSlice >= 0) + // 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) { - this->GetSlice()->SetPos(bestSlice); + bestSlice = static_cast(plane->Distance(point) / slicedWorldGeometry->GetSpacing()[2] + 0.5); } - else + } + else + { + int numberOfSlices = slicedWorldGeometry->GetSlices(); + Point3D projectedPoint; + for (int slice = 0; slice < numberOfSlices; ++slice) { - this->GetSlice()->SetPos(0); + slicedWorldGeometry->GetPlaneGeometry(slice)->Project(point, projectedPoint); + const Vector3D distance = projectedPoint - point; + ScalarType currentDistance = distance.GetSquaredNorm(); + + if (currentDistance < bestDistance) + { + bestDistance = currentDistance; + bestSlice = slice; + } } - this->SendCreatedWorldGeometryUpdate(); - // send crosshair event - SetCrosshairEvent.Send(point); } + + 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 Point3D& point, const Vector3D& axisVec0, const Vector3D& axisVec1) { - if (m_CreatedWorldGeometry) + if (m_CreatedWorldGeometry.IsNull()) { - PlaneOperation op(OpORIENT, point, axisVec0, axisVec1); - m_CreatedWorldGeometry->ExecuteOperation(&op); - - this->SendCreatedWorldGeometryUpdate(); + return; } + + PlaneOperation op(OpORIENT, point, axisVec0, axisVec1); + m_CreatedWorldGeometry->ExecuteOperation(&op); + + this->SendCreatedWorldGeometryUpdate(); } const BaseGeometry* SliceNavigationController::GetCurrentGeometry3D() { - if (m_CreatedWorldGeometry.IsNotNull()) - { - return m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()); - } - else + if (m_CreatedWorldGeometry.IsNull()) { return nullptr; } + + return m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()); } const PlaneGeometry* SliceNavigationController::GetCurrentPlaneGeometry() { const auto* slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); - if (slicedGeometry) - { - const PlaneGeometry* planeGeometry = (slicedGeometry->GetPlaneGeometry(this->GetSlice()->GetPos())); - return planeGeometry; - } - else + if (nullptr == slicedGeometry) { return nullptr; } + + return slicedGeometry->GetPlaneGeometry(this->GetSlice()->GetPos()); } - 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 mitk