Medical Imaging Interaction Toolkit  2018.4.99-3e3f1a6e
Medical Imaging Interaction Toolkit
mitkGizmo.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 #include "mitkGizmo.h"
14 #include "mitkGizmoInteractor.h"
15 
16 // MITK includes
17 #include <mitkBaseRenderer.h>
20 #include <mitkRenderingManager.h>
22 
23 // VTK includes
24 #include <vtkAppendPolyData.h>
25 #include <vtkCellArray.h>
26 #include <vtkCharArray.h>
27 #include <vtkConeSource.h>
28 #include <vtkCylinderSource.h>
29 #include <vtkMath.h>
30 #include <vtkPointData.h>
31 #include <vtkPoints.h>
32 #include <vtkPolyDataNormals.h>
33 #include <vtkRenderWindow.h>
34 #include <vtkSphereSource.h>
35 #include <vtkTransformPolyDataFilter.h>
36 #include <vtkTubeFilter.h>
37 
38 // ITK includes
39 #include <itkCommand.h>
40 
41 // MicroServices
42 #include <usGetModuleContext.h>
43 
44 namespace
45 {
46  const char *PROPERTY_KEY_ORIGINAL_OBJECT_OPACITY = "gizmo.originalObjectOpacity";
47 }
48 
49 namespace mitk
50 {
52  class GizmoRemover
53  {
54  public:
55  GizmoRemover() : m_Storage(nullptr), m_GizmoNode(nullptr), m_ManipulatedNode(nullptr), m_StorageObserverTag(0) {}
62  void UpdateStorageObservation(mitk::DataStorage *storage,
63  mitk::DataNode *gizmo_node,
64  mitk::DataNode *manipulated_node)
65  {
66  if (m_Storage != nullptr)
67  {
68  m_Storage->RemoveNodeEvent.RemoveListener(
69  mitk::MessageDelegate1<GizmoRemover, const mitk::DataNode *>(this, &GizmoRemover::OnDataNodeHasBeenRemoved));
70  m_Storage->RemoveObserver(m_StorageObserverTag);
71  }
72 
73  m_Storage = storage;
74  m_GizmoNode = gizmo_node;
75  m_ManipulatedNode = manipulated_node;
76 
77  if (m_Storage != nullptr)
78  {
79  m_Storage->RemoveNodeEvent.AddListener(
80  mitk::MessageDelegate1<GizmoRemover, const mitk::DataNode *>(this, &GizmoRemover::OnDataNodeHasBeenRemoved));
81 
82  itk::SimpleMemberCommand<GizmoRemover>::Pointer command = itk::SimpleMemberCommand<GizmoRemover>::New();
83  command->SetCallbackFunction(this, &mitk::GizmoRemover::OnDataStorageDeleted);
84  m_StorageObserverTag = m_Storage->AddObserver(itk::ModifiedEvent(), command);
85  }
86  }
87 
89  void OnDataStorageDeleted() { m_Storage = nullptr; }
91  void OnDataNodeHasBeenRemoved(const mitk::DataNode *node)
92  {
93  if (node == m_ManipulatedNode)
94  {
95  // m_Storage is still alive because it is the emitter
96  if (m_Storage->Exists(m_GizmoNode))
97  {
98  m_Storage->Remove(m_GizmoNode);
99  // normally, gizmo will be deleted here (unless somebody
100  // still holds a reference to it)
101  }
102  }
103  }
104 
106  ~GizmoRemover()
107  {
108  if (m_Storage)
109  {
110  m_Storage->RemoveNodeEvent.RemoveListener(
111  mitk::MessageDelegate1<GizmoRemover, const mitk::DataNode *>(this, &GizmoRemover::OnDataNodeHasBeenRemoved));
112  m_Storage->RemoveObserver(m_StorageObserverTag);
113  }
114  }
115 
116  private:
117  mitk::DataStorage *m_Storage;
118  mitk::DataNode *m_GizmoNode;
119  mitk::DataNode *m_ManipulatedNode;
120  unsigned long m_StorageObserverTag;
121  };
122 
123 } // namespace MITK
124 
126 {
127  auto typeCondition = TNodePredicateDataType<Gizmo>::New();
128  auto gizmoChildren = storage->GetDerivations(node, typeCondition);
129  return !gizmoChildren->empty();
130 }
131 
133 {
134  if (node == nullptr || storage == nullptr)
135  {
136  return false;
137  }
138 
139  auto typeCondition = TNodePredicateDataType<Gizmo>::New();
140  auto gizmoChildren = storage->GetDerivations(node, typeCondition);
141 
142  for (auto &gizmoChild : *gizmoChildren)
143  {
144  auto *gizmo = dynamic_cast<Gizmo *>(gizmoChild->GetData());
145  if (gizmo)
146  {
147  storage->Remove(gizmoChild);
148  gizmo->m_GizmoRemover->UpdateStorageObservation(nullptr, nullptr, nullptr);
149  }
150  }
151 
152  //--------------------------------------------------------------
153  // Restore original opacity if we changed it
154  //--------------------------------------------------------------
155  float originalOpacity = 1.0;
156  if (node->GetFloatProperty(PROPERTY_KEY_ORIGINAL_OBJECT_OPACITY, originalOpacity))
157  {
158  node->SetOpacity(originalOpacity);
159  node->GetPropertyList()->DeleteProperty(PROPERTY_KEY_ORIGINAL_OBJECT_OPACITY);
160  }
161 
162  return !gizmoChildren->empty();
163 }
164 
166 {
167  assert(node);
168  if (node->GetData() == nullptr || node->GetData()->GetGeometry() == nullptr)
169  {
170  return nullptr;
171  }
172  //--------------------------------------------------------------
173  // Add visual gizmo that follows the node to be manipulated
174  //--------------------------------------------------------------
175 
176  auto gizmo = Gizmo::New();
177  auto gizmoNode = DataNode::New();
178  gizmoNode->SetName("Gizmo");
179  gizmoNode->SetData(gizmo);
180  gizmo->FollowGeometry(node->GetData()->GetGeometry());
181 
182  //--------------------------------------------------------------
183  // Add interaction to the gizmo
184  //--------------------------------------------------------------
185 
187  interactor->LoadStateMachine("Gizmo3DStates.xml", us::GetModuleContext()->GetModule());
188  interactor->SetEventConfig("Gizmo3DConfig.xml", us::ModuleRegistry::GetModule("MitkGizmo"));
189 
190  interactor->SetGizmoNode(gizmoNode);
191  interactor->SetManipulatedObjectNode(node);
192 
193  //--------------------------------------------------------------
194  // Note current opacity for later restore and lower it
195  //--------------------------------------------------------------
196 
197  float currentNodeOpacity = 1.0;
198  if (node->GetOpacity(currentNodeOpacity, nullptr))
199  {
200  if (currentNodeOpacity > 0.5f)
201  {
202  node->SetFloatProperty(PROPERTY_KEY_ORIGINAL_OBJECT_OPACITY, currentNodeOpacity);
203  node->SetOpacity(0.5f);
204  }
205  }
206 
207  if (storage)
208  {
209  storage->Add(gizmoNode, node);
210  gizmo->m_GizmoRemover->UpdateStorageObservation(storage, gizmoNode, node);
211  }
212 
213  return gizmoNode;
214 }
215 
217  : Surface(), m_AllowTranslation(true), m_AllowRotation(true), m_AllowScaling(true), m_GizmoRemover(new GizmoRemover())
218 {
219  m_Center.Fill(0);
220 
221  m_AxisX.Fill(0);
222  m_AxisX[0] = 1;
223  m_AxisY.Fill(0);
224  m_AxisY[1] = 1;
225  m_AxisZ.Fill(0);
226  m_AxisZ[2] = 1;
227 
228  m_Radius.Fill(1);
229 
231 }
232 
234 {
235  if (m_FollowedGeometry.IsNotNull())
236  {
237  m_FollowedGeometry->RemoveObserver(m_FollowerTag);
238  }
239 }
240 
242 {
243  /* bounding box around the unscaled bounding object */
244  ScalarType bounds[6] = {-m_Radius[0] * 1.2,
245  +m_Radius[0] * 1.2,
246  -m_Radius[1] * 1.2,
247  +m_Radius[1] * 1.2,
248  -m_Radius[2] * 1.2,
249  +m_Radius[2] * 1.2};
250  GetGeometry()->SetBounds(bounds);
251  GetTimeGeometry()->Update();
252 
254 }
255 
256 namespace
257 {
258  void AssignScalarValueTo(vtkPolyData *polydata, char value)
259  {
260  vtkSmartPointer<vtkCharArray> pointData = vtkSmartPointer<vtkCharArray>::New();
261 
262  int numberOfPoints = polydata->GetNumberOfPoints();
263  pointData->SetNumberOfComponents(1);
264  pointData->SetNumberOfTuples(numberOfPoints);
265  pointData->FillComponent(0, value);
266  polydata->GetPointData()->SetScalars(pointData);
267  }
268 
269  vtkSmartPointer<vtkPolyData> BuildAxis(const mitk::Point3D &center,
270  const mitk::Vector3D &axis,
271  double halflength,
272  bool drawRing,
273  char vertexValueAxis,
274  char vertexValueRing,
275  char vertexValueScale)
276  {
277  // Define all sizes relative to absolute size (thus that the gizmo will appear
278  // in the same relative size for huge (size >> 1) and tiny (size << 1) objects).
279  // This means that the gizmo will appear very different when a scene contains _both_
280  // huge and tiny objects at the same time, but when the users zooms in on his
281  // object of interest, the gizmo will always have the same relative size.
282  const double shaftRadius = halflength * 0.02;
283  const double arrowHeight = shaftRadius * 6;
284  const int tubeSides = 15;
285 
286  // poly data appender to collect cones and tube that make up the axis
287  vtkSmartPointer<vtkAppendPolyData> axisSource = vtkSmartPointer<vtkAppendPolyData>::New();
288 
289  // build two cones at the end of axis
290  for (double sign = -1.0; sign < 3.0; sign += 2)
291  {
292  vtkSmartPointer<vtkConeSource> cone = vtkConeSource::New();
293  // arrow tips at 110% of radius
294  cone->SetCenter(center[0] + sign * axis[0] * (halflength * 1.1 + arrowHeight * 0.5),
295  center[1] + sign * axis[1] * (halflength * 1.1 + arrowHeight * 0.5),
296  center[2] + sign * axis[2] * (halflength * 1.1 + arrowHeight * 0.5));
297  cone->SetDirection(sign * axis[0], sign * axis[1], sign * axis[2]);
298  cone->SetRadius(shaftRadius * 3);
299  cone->SetHeight(arrowHeight);
300  cone->SetResolution(tubeSides);
301  cone->CappingOn();
302  cone->Update();
303  AssignScalarValueTo(cone->GetOutput(), vertexValueScale);
304  axisSource->AddInputData(cone->GetOutput());
305  }
306 
307  // build the axis itself (as a tube around the line defining the axis)
308  vtkSmartPointer<vtkPolyData> shaftSkeleton = vtkSmartPointer<vtkPolyData>::New();
309  vtkSmartPointer<vtkPoints> shaftPoints = vtkSmartPointer<vtkPoints>::New();
310  shaftPoints->InsertPoint(0, (center - axis * halflength * 1.1).GetDataPointer());
311  shaftPoints->InsertPoint(1, (center + axis * halflength * 1.1).GetDataPointer());
312  shaftSkeleton->SetPoints(shaftPoints);
313 
314  vtkSmartPointer<vtkCellArray> shaftLines = vtkSmartPointer<vtkCellArray>::New();
315  vtkIdType shaftLinePoints[] = {0, 1};
316  shaftLines->InsertNextCell(2, shaftLinePoints);
317  shaftSkeleton->SetLines(shaftLines);
318 
319  vtkSmartPointer<vtkTubeFilter> shaftSource = vtkSmartPointer<vtkTubeFilter>::New();
320  shaftSource->SetInputData(shaftSkeleton);
321  shaftSource->SetNumberOfSides(tubeSides);
322  shaftSource->SetVaryRadiusToVaryRadiusOff();
323  shaftSource->SetRadius(shaftRadius);
324  shaftSource->Update();
325  AssignScalarValueTo(shaftSource->GetOutput(), vertexValueAxis);
326 
327  axisSource->AddInputData(shaftSource->GetOutput());
328  axisSource->Update();
329 
330  vtkSmartPointer<vtkTubeFilter> ringSource; // used after if block, so declare it here
331  if (drawRing)
332  {
333  // build the ring orthogonal to the axis (as another tube)
334  vtkSmartPointer<vtkPolyData> ringSkeleton = vtkSmartPointer<vtkPolyData>::New();
335  vtkSmartPointer<vtkPoints> ringPoints = vtkSmartPointer<vtkPoints>::New();
336  ringPoints->SetDataTypeToDouble(); // just some decision (see cast below)
337  unsigned int numberOfRingPoints = 100;
338  vtkSmartPointer<vtkCellArray> ringLines = vtkSmartPointer<vtkCellArray>::New();
339  ringLines->InsertNextCell(numberOfRingPoints + 1);
340  mitk::Vector3D ringPointer;
341  for (unsigned int segment = 0; segment < numberOfRingPoints; ++segment)
342  {
343  ringPointer[0] = 0;
344  ringPointer[1] = std::cos((double)(segment) / (double)numberOfRingPoints * 2.0 * vtkMath::Pi());
345  ringPointer[2] = std::sin((double)(segment) / (double)numberOfRingPoints * 2.0 * vtkMath::Pi());
346 
347  ringPoints->InsertPoint(segment, (ringPointer * halflength).GetDataPointer());
348 
349  ringLines->InsertCellPoint(segment);
350  }
351  ringLines->InsertCellPoint(0);
352 
353  // transform ring points (copied from vtkConeSource)
354  vtkSmartPointer<vtkTransform> t = vtkSmartPointer<vtkTransform>::New();
355  t->Translate(center.GetDataPointer());
356  double vMag = vtkMath::Norm(axis.GetDataPointer());
357  if (axis[0] < 0.0)
358  {
359  // flip x -> -x to avoid instability
360  t->RotateWXYZ(180.0, (axis[0] - vMag) / 2.0, axis[1] / 2.0, axis[2] / 2.0);
361  t->RotateWXYZ(180.0, 0, 1, 0);
362  }
363  else
364  {
365  t->RotateWXYZ(180.0, (axis[0] + vMag) / 2.0, axis[1] / 2.0, axis[2] / 2.0);
366  }
367 
368  double thisPoint[3];
369  for (unsigned int i = 0; i < numberOfRingPoints; ++i)
370  {
371  ringPoints->GetPoint(i, thisPoint);
372  t->TransformPoint(thisPoint, thisPoint);
373  ringPoints->SetPoint(i, thisPoint);
374  }
375 
376  ringSkeleton->SetPoints(ringPoints);
377  ringSkeleton->SetLines(ringLines);
378 
379  ringSource = vtkSmartPointer<vtkTubeFilter>::New();
380  ringSource->SetInputData(ringSkeleton);
381  ringSource->SetNumberOfSides(tubeSides);
382  ringSource->SetVaryRadiusToVaryRadiusOff();
383  ringSource->SetRadius(shaftRadius);
384  ringSource->Update();
385  AssignScalarValueTo(ringSource->GetOutput(), vertexValueRing);
386  }
387 
388  // assemble axis and ring
389  vtkSmartPointer<vtkAppendPolyData> appenderGlobal = vtkSmartPointer<vtkAppendPolyData>::New();
390  appenderGlobal->AddInputData(axisSource->GetOutput());
391  if (drawRing)
392  {
393  appenderGlobal->AddInputData(ringSource->GetOutput());
394  }
395  appenderGlobal->Update();
396 
397  // make everything shiny by adding normals
398  vtkSmartPointer<vtkPolyDataNormals> normalsSource = vtkSmartPointer<vtkPolyDataNormals>::New();
399  normalsSource->SetInputConnection(appenderGlobal->GetOutputPort());
400  normalsSource->ComputePointNormalsOn();
401  normalsSource->ComputeCellNormalsOff();
402  normalsSource->SplittingOn();
403  normalsSource->Update();
404 
405  vtkSmartPointer<vtkPolyData> result = normalsSource->GetOutput();
406  return result;
407  }
408 
409 } // unnamed namespace
410 
412 {
413  double longestAxis = std::max(m_Radius[0], m_Radius[1]);
414  longestAxis = std::max(longestAxis, m_Radius[2]);
415  return longestAxis;
416 }
417 
418 vtkSmartPointer<vtkPolyData> mitk::Gizmo::BuildGizmo()
419 {
420  double longestAxis = GetLongestRadius();
421 
422  vtkSmartPointer<vtkAppendPolyData> appender = vtkSmartPointer<vtkAppendPolyData>::New();
423  appender->AddInputData(BuildAxis(m_Center,
424  m_AxisX,
425  longestAxis,
426  m_AllowRotation,
427  m_AllowTranslation ? MoveAlongAxisX : NoHandle,
428  m_AllowRotation ? RotateAroundAxisX : NoHandle,
429  m_AllowScaling ? ScaleX : NoHandle));
430  appender->AddInputData(BuildAxis(m_Center,
431  m_AxisY,
432  longestAxis,
433  m_AllowRotation,
434  m_AllowTranslation ? MoveAlongAxisY : NoHandle,
435  m_AllowRotation ? RotateAroundAxisY : NoHandle,
436  m_AllowScaling ? ScaleY : NoHandle));
437  appender->AddInputData(BuildAxis(m_Center,
438  m_AxisZ,
439  longestAxis,
440  m_AllowRotation,
441  m_AllowTranslation ? MoveAlongAxisZ : NoHandle,
442  m_AllowRotation ? RotateAroundAxisZ : NoHandle,
443  m_AllowScaling ? ScaleZ : NoHandle));
444 
445  auto sphereSource = vtkSmartPointer<vtkSphereSource>::New();
446  sphereSource->SetCenter(m_Center[0], m_Center[1], m_Center[2]);
447  sphereSource->SetRadius(longestAxis * 0.06);
448  sphereSource->Update();
449  AssignScalarValueTo(sphereSource->GetOutput(), MoveFreely);
450 
451  appender->AddInputData(sphereSource->GetOutput());
452 
453  appender->Update();
454  return appender->GetOutput();
455 }
456 
458 {
459  auto observer = itk::SimpleMemberCommand<Gizmo>::New();
460  observer->SetCallbackFunction(this, &Gizmo::OnFollowedGeometryModified);
461 
462  if (m_FollowedGeometry.IsNotNull())
463  {
464  m_FollowedGeometry->RemoveObserver(m_FollowerTag);
465  }
466 
467  m_FollowedGeometry = geom;
468  m_FollowerTag = m_FollowedGeometry->AddObserver(itk::ModifiedEvent(), observer);
469 
470  // initial adjustment
472 }
473 
475 {
476  m_Center = m_FollowedGeometry->GetCenter();
477 
478  m_AxisX = m_FollowedGeometry->GetAxisVector(0);
479  m_AxisY = m_FollowedGeometry->GetAxisVector(1);
480  m_AxisZ = m_FollowedGeometry->GetAxisVector(2);
481 
482  m_AxisX.Normalize();
483  m_AxisY.Normalize();
484  m_AxisZ.Normalize();
485 
486  for (int dim = 0; dim < 3; ++dim)
487  {
488  m_Radius[dim] = 0.5 * m_FollowedGeometry->GetExtentInMM(dim);
489  }
490 
492 }
493 
495 {
496 #define CheckHandleType(type) \
497  if (static_cast<int>(value) == static_cast<int>(type)) \
498  return type;
499 
510  return NoHandle;
511 #undef CheckHandleType
512 }
513 
515 {
516  assert(GetVtkPolyData());
517  assert(GetVtkPolyData()->GetPointData());
518  assert(GetVtkPolyData()->GetPointData()->GetScalars());
519  double dataValue = GetVtkPolyData()->GetPointData()->GetScalars()->GetTuple1(id);
520  return GetHandleFromPointDataValue(dataValue);
521 }
522 
524 {
525 #define CheckHandleType(candidateType) \
526  if (type == candidateType) \
527  return std::string(#candidateType);
528 
540  return "InvalidHandleType";
541 #undef CheckHandleType
542 }
Class for storing surfaces (vtkPolyData).
Definition: mitkSurface.h:28
Data management class that handles &#39;was created by&#39; relations.
virtual vtkPolyData * GetVtkPolyData(unsigned int t=0) const
virtual SetOfObjects::ConstPointer GetDerivations(const DataNode *node, const NodePredicateBase *condition=nullptr, bool onlyDirectDerivations=true) const =0
returns a set of derived objects for a given node.
HandleType GetHandleFromPointID(vtkIdType id)
Definition: mitkGizmo.cpp:514
double ScalarType
bool GetFloatProperty(const char *propertyKey, float &floatValue, const mitk::BaseRenderer *renderer=nullptr) const
Convenience access method for float properties (instances of FloatProperty)
static DataNode::Pointer AddGizmoToNode(DataNode *node, DataStorage *storage)
Definition: mitkGizmo.cpp:165
static Module * GetModule(long id)
DataCollection - Class to facilitate loading/accessing structured data.
static Pointer New()
virtual void Add(DataNode *node, const DataStorage::SetOfObjects *parents=nullptr)=0
Adds a DataNode containing a data object to its internal storage.
bool GetOpacity(float &opacity, const mitk::BaseRenderer *renderer, const char *propertyKey="opacity") const
Convenience access method for opacity properties (instances of FloatProperty)
#define CheckHandleType(type)
static bool HasGizmoAttached(mitk::DataNode *node, DataStorage *storage)
Definition: mitkGizmo.cpp:125
bool DeleteProperty(const std::string &propertyKey)
Remove a property from the list/map.
void FollowGeometry(BaseGeometry *geom)
Definition: mitkGizmo.cpp:457
HandleType
Names for the different parts of the gizmo.
Definition: mitkGizmo.h:61
const mitk::TimeGeometry * GetTimeGeometry() const
Return the TimeGeometry of the data as const pointer.
Definition: mitkBaseData.h:61
void SetFloatProperty(const char *propertyKey, float floatValue, const mitk::BaseRenderer *renderer=nullptr)
Convenience method for setting float properties (instances of FloatProperty)
BaseData * GetData() const
Get the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
void SetOpacity(float opacity, const mitk::BaseRenderer *renderer=nullptr, const char *propertyKey="opacity")
Convenience method for setting opacity properties (instances of FloatProperty)
static Pointer New()
virtual void SetVtkPolyData(vtkPolyData *polydata, unsigned int t=0)
static T max(T x, T y)
Definition: svm.cpp:56
static Pointer New()
~Gizmo() override
Definition: mitkGizmo.cpp:233
virtual void Remove(const DataNode *node)=0
Removes node from the DataStorage.
mitk::PropertyList * GetPropertyList(const mitk::BaseRenderer *renderer=nullptr) const
Get the PropertyList of the renderer. If renderer is nullptr, the BaseRenderer-independent PropertyLi...
void UpdateRepresentation()
Updates the representing surface object after changes to center, axes, or radius. ...
Definition: mitkGizmo.cpp:241
double GetLongestRadius() const
Return the longest of the three axes.
Definition: mitkGizmo.cpp:411
void SetBounds(const BoundsArrayType &bounds)
Set the bounding box (in index/unit coordinates)
mitk::Gizmo::HandleType GetHandleFromPointDataValue(double value)
Definition: mitkGizmo.cpp:494
vtkSmartPointer< vtkPolyData > BuildGizmo()
Creates a vtkPolyData representing the parameters defining the gizmo.
Definition: mitkGizmo.cpp:418
void Update()
Updates the geometry.
mitk::BaseGeometry * GetGeometry(int t=0) const
Return the geometry, which is a TimeGeometry, of the data as non-const pointer.
Definition: mitkBaseData.h:138
static ModuleContext * GetModuleContext()
Returns the module context of the calling module.
void OnFollowedGeometryModified()
The ITK callback to receive modified events of the followed geometry.
Definition: mitkGizmo.cpp:474
static std::string HandleTypeToString(HandleType type)
Conversion for any kind of logging/debug/... purposes.
Definition: mitkGizmo.cpp:523
BaseGeometry Describes the geometry of a data object.
Class for nodes of the DataTree.
Definition: mitkDataNode.h:57
static bool RemoveGizmoFromNode(DataNode *node, DataStorage *storage)
Definition: mitkGizmo.cpp:132