Medical Imaging Interaction Toolkit  2018.4.99-1bab67a2
Medical Imaging Interaction Toolkit
mitkPaintbrushTool.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 "mitkPaintbrushTool.h"
14 
15 #include "ipSegmentation.h"
17 #include "mitkBaseRenderer.h"
18 #include "mitkImageDataItem.h"
20 #include "mitkToolManager.h"
21 
22 #include "mitkContourModelUtils.h"
23 #include "mitkLabelSetImage.h"
25 
27 
28 mitk::PaintbrushTool::PaintbrushTool(int paintingPixelValue)
29  : FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"),
30  m_PaintingPixelValue(paintingPixelValue),
31  m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28)
32 {
34  m_MasterContour->Initialize();
35  m_CurrentPlane = nullptr;
36 
38  m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1)));
39  m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true));
40 }
41 
43 {
44 }
45 
47 {
48  CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed);
50  CONNECT_FUNCTION("MouseMove", OnMouseMoved);
52  CONNECT_FUNCTION("InvertLogic", OnInvertLogic);
53 }
54 
56 {
57  Superclass::Activated();
58 
63 }
64 
66 {
70  m_WorkingSlice = nullptr;
71  m_CurrentPlane = nullptr;
74 
75  Superclass::Deactivated();
76 }
77 
79 {
80  m_Size = value;
81 }
82 
84 {
85  p[0] -= 0.5;
86  p[1] += 0.5;
87  return p;
88 }
89 
91 {
92  // MITK_INFO<<"Update...";
93  // examine stateEvent and create a contour that matches the pixel mask that we are going to draw
94  // mitk::InteractionPositionEvent* positionEvent = dynamic_cast<mitk::InteractionPositionEvent*>( interactionEvent );
95  // const PositionEvent* positionEvent = dynamic_cast<const PositionEvent*>(stateEvent->GetEvent());
96  if (!positionEvent)
97  return;
98 
99  // Get Spacing of current Slice
100  // mitk::Vector3D vSpacing = m_WorkingSlice->GetSlicedGeometry()->GetPlaneGeometry(0)->GetSpacing();
101 
102  //
103  // Draw a contour in Square according to selected brush size
104  //
105  int radius = (m_Size) / 2;
106  float fradius = static_cast<float>(m_Size) / 2.0f;
107 
108  ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New();
109 
110  // estimate center point of the brush ( relative to the pixel the mouse points on )
111  // -- left upper corner for even sizes,
112  // -- midpoint for uneven sizes
113  mitk::Point2D centerCorrection;
114  centerCorrection.Fill(0);
115 
116  // even --> correction of [+0.5, +0.5]
117  bool evenSize = ((m_Size % 2) == 0);
118  if (evenSize)
119  {
120  centerCorrection[0] += 0.5;
121  centerCorrection[1] += 0.5;
122  }
123 
124  // we will compute the control points for the upper left quarter part of a circle contour
125  std::vector<mitk::Point2D> quarterCycleUpperRight;
126  std::vector<mitk::Point2D> quarterCycleLowerRight;
127  std::vector<mitk::Point2D> quarterCycleLowerLeft;
128  std::vector<mitk::Point2D> quarterCycleUpperLeft;
129 
130  mitk::Point2D curPoint;
131  bool curPointIsInside = true;
132  curPoint[0] = 0;
133  curPoint[1] = radius;
134  quarterCycleUpperRight.push_back(upperLeft(curPoint));
135 
136  // to estimate if a pixel is inside the circle, we need to compare against the 'outer radius'
137  // i.e. the distance from the midpoint [0,0] to the border of the pixel [0,radius]
138  // const float outer_radius = static_cast<float>(radius) + 0.5;
139 
140  while (curPoint[1] > 0)
141  {
142  // Move right until pixel is outside circle
143  float curPointX_squared = 0.0f;
144  float curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]);
145  while (curPointIsInside)
146  {
147  // increment posX and chec
148  curPoint[0]++;
149  curPointX_squared = (curPoint[0] - centerCorrection[0]) * (curPoint[0] - centerCorrection[0]);
150  const float len = sqrt(curPointX_squared + curPointY_squared);
151  if (len > fradius)
152  {
153  // found first Pixel in this horizontal line, that is outside the circle
154  curPointIsInside = false;
155  }
156  }
157  quarterCycleUpperRight.push_back(upperLeft(curPoint));
158 
159  // Move down until pixel is inside circle
160  while (!curPointIsInside)
161  {
162  // increment posX and chec
163  curPoint[1]--;
164  curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]);
165  const float len = sqrt(curPointX_squared + curPointY_squared);
166  if (len <= fradius)
167  {
168  // found first Pixel in this horizontal line, that is outside the circle
169  curPointIsInside = true;
170  quarterCycleUpperRight.push_back(upperLeft(curPoint));
171  }
172 
173  // Quarter cycle is full, when curPoint y position is 0
174  if (curPoint[1] <= 0)
175  break;
176  }
177  }
178 
179  // QuarterCycle is full! Now copy quarter cycle to other quarters.
180 
181  if (!evenSize)
182  {
183  std::vector<mitk::Point2D>::const_iterator it = quarterCycleUpperRight.begin();
184  while (it != quarterCycleUpperRight.end())
185  {
186  mitk::Point2D p;
187  p = *it;
188 
189  // the contour points in the lower right corner have same position but with negative y values
190  p[1] *= -1;
191  quarterCycleLowerRight.push_back(p);
192 
193  // the contour points in the lower left corner have same position
194  // but with both x,y negative
195  p[0] *= -1;
196  quarterCycleLowerLeft.push_back(p);
197 
198  // the contour points in the upper left corner have same position
199  // but with x negative
200  p[1] *= -1;
201  quarterCycleUpperLeft.push_back(p);
202 
203  it++;
204  }
205  }
206  else
207  {
208  std::vector<mitk::Point2D>::const_iterator it = quarterCycleUpperRight.begin();
209  while (it != quarterCycleUpperRight.end())
210  {
211  mitk::Point2D p, q;
212  p = *it;
213 
214  q = p;
215  // the contour points in the lower right corner have same position but with negative y values
216  q[1] *= -1;
217  // correct for moved offset if size even = the midpoint is not the midpoint of the current pixel
218  // but its upper rigt corner
219  q[1] += 1;
220  quarterCycleLowerRight.push_back(q);
221 
222  q = p;
223  // the contour points in the lower left corner have same position
224  // but with both x,y negative
225  q[1] = -1.0f * q[1] + 1;
226  q[0] = -1.0f * q[0] + 1;
227  quarterCycleLowerLeft.push_back(q);
228 
229  // the contour points in the upper left corner have same position
230  // but with x negative
231  q = p;
232  q[0] *= -1;
233  q[0] += 1;
234  quarterCycleUpperLeft.push_back(q);
235 
236  it++;
237  }
238  }
239 
240  // fill contour with poins in right ordering, starting with the upperRight block
241  mitk::Point3D tempPoint;
242  for (unsigned int i = 0; i < quarterCycleUpperRight.size(); i++)
243  {
244  tempPoint[0] = quarterCycleUpperRight[i][0];
245  tempPoint[1] = quarterCycleUpperRight[i][1];
246  tempPoint[2] = 0;
247  contourInImageIndexCoordinates->AddVertex(tempPoint);
248  }
249  // the lower right has to be parsed in reverse order
250  for (int i = quarterCycleLowerRight.size() - 1; i >= 0; i--)
251  {
252  tempPoint[0] = quarterCycleLowerRight[i][0];
253  tempPoint[1] = quarterCycleLowerRight[i][1];
254  tempPoint[2] = 0;
255  contourInImageIndexCoordinates->AddVertex(tempPoint);
256  }
257  for (unsigned int i = 0; i < quarterCycleLowerLeft.size(); i++)
258  {
259  tempPoint[0] = quarterCycleLowerLeft[i][0];
260  tempPoint[1] = quarterCycleLowerLeft[i][1];
261  tempPoint[2] = 0;
262  contourInImageIndexCoordinates->AddVertex(tempPoint);
263  }
264  // the upper left also has to be parsed in reverse order
265  for (int i = quarterCycleUpperLeft.size() - 1; i >= 0; i--)
266  {
267  tempPoint[0] = quarterCycleUpperLeft[i][0];
268  tempPoint[1] = quarterCycleUpperLeft[i][1];
269  tempPoint[2] = 0;
270  contourInImageIndexCoordinates->AddVertex(tempPoint);
271  }
272 
273  m_MasterContour = contourInImageIndexCoordinates;
274 }
275 
280 {
281  if (m_WorkingSlice.IsNull())
282  return;
283 
284  auto *positionEvent = dynamic_cast<mitk::InteractionPositionEvent *>(interactionEvent);
285  if (!positionEvent)
286  return;
287 
288  m_WorkingSlice->GetGeometry()->WorldToIndex(positionEvent->GetPositionInWorld(), m_LastPosition);
289 
290  // create new working node
291  // a fresh node is needed to only display the actual drawing process for
292  // the undo function
295  m_WorkingSlice = nullptr;
296  m_CurrentPlane = nullptr;
297 
299  m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1)));
300  m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true));
301 
302  this->m_WorkingNode->SetVisibility(true);
303 
304  m_LastEventSender = positionEvent->GetSender();
306 
307  m_MasterContour->SetClosed(true);
308  this->MouseMoved(interactionEvent, true);
309 }
310 
312 {
313  MouseMoved(interactionEvent, false);
314 }
315 
317 {
318  MouseMoved(interactionEvent, true);
319 }
320 
324 void mitk::PaintbrushTool::MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed)
325 {
326  auto *positionEvent = dynamic_cast<mitk::InteractionPositionEvent *>(interactionEvent);
327 
328  CheckIfCurrentSliceHasChanged(positionEvent);
329 
330  if (m_LastContourSize != m_Size)
331  {
332  UpdateContour(positionEvent);
334  }
335 
336  Point3D worldCoordinates = positionEvent->GetPositionInWorld();
337  Point3D indexCoordinates;
338 
339  m_WorkingSlice->GetGeometry()->WorldToIndex(worldCoordinates, indexCoordinates);
340 
341  // round to nearest voxel center (abort if this hasn't changed)
342  if (m_Size % 2 == 0) // even
343  {
344  indexCoordinates[0] = std::round(indexCoordinates[0]);
345  indexCoordinates[1] = std::round(indexCoordinates[1]);
346  }
347  else // odd
348  {
349  indexCoordinates[0] = std::round(indexCoordinates[0]);
350  indexCoordinates[1] = std::round(indexCoordinates[1]);
351  }
352 
353  static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me
354  if (fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps ||
355  fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed)
356  {
357  lastPos = indexCoordinates;
358  }
359  else
360  {
361  return;
362  }
363 
364  int t = positionEvent->GetSender()->GetTimeStep();
365 
366  auto contour = ContourModel::New();
367  contour->SetClosed(true);
368 
369  auto it = m_MasterContour->Begin();
370  auto end = m_MasterContour->End();
371 
372  while (it != end)
373  {
374  auto point = (*it)->Coordinates;
375  point[0] += indexCoordinates[0];
376  point[1] += indexCoordinates[1];
377 
378  contour->AddVertex(point);
379  ++it;
380  }
381 
382  if (leftMouseButtonPressed)
383  {
384  const double dist = indexCoordinates.EuclideanDistanceTo(m_LastPosition);
385  const double radius = static_cast<double>(m_Size) / 2.0;
386 
387  DataNode *workingNode(m_ToolManager->GetWorkingData(0));
388  Image::Pointer image = dynamic_cast<Image *>(workingNode->GetData());
389  auto *labelImage = dynamic_cast<LabelSetImage *>(image.GetPointer());
390 
391  int activeColor = 1;
392  if (labelImage)
393  {
394  activeColor = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue();
395  }
396 
397  // m_PaintingPixelValue only decides whether to paint or erase
399  contour, m_WorkingSlice, image, m_PaintingPixelValue * activeColor);
400 
401  m_WorkingNode->SetData(m_WorkingSlice);
402  m_WorkingNode->Modified();
403 
404  // if points are >= radius away draw rectangle to fill empty holes
405  // in between the 2 points
406  if (dist > radius)
407  {
408  const mitk::Point3D &currentPos = indexCoordinates;
409  mitk::Point3D direction;
410  mitk::Point3D vertex;
411  mitk::Point3D normal;
412 
413  direction[0] = indexCoordinates[0] - m_LastPosition[0];
414  direction[1] = indexCoordinates[1] - m_LastPosition[1];
415  direction[2] = indexCoordinates[2] - m_LastPosition[2];
416 
417  direction[0] = direction.GetVnlVector().normalize()[0];
418  direction[1] = direction.GetVnlVector().normalize()[1];
419  direction[2] = direction.GetVnlVector().normalize()[2];
420 
421  // 90 degrees rotation of direction
422  normal[0] = -1.0 * direction[1];
423  normal[1] = direction[0];
424 
425  contour->Clear();
426 
427  // upper left corner
428  vertex[0] = m_LastPosition[0] + (normal[0] * radius);
429  vertex[1] = m_LastPosition[1] + (normal[1] * radius);
430 
431  contour->AddVertex(vertex);
432 
433  // upper right corner
434  vertex[0] = currentPos[0] + (normal[0] * radius);
435  vertex[1] = currentPos[1] + (normal[1] * radius);
436 
437  contour->AddVertex(vertex);
438 
439  // lower right corner
440  vertex[0] = currentPos[0] - (normal[0] * radius);
441  vertex[1] = currentPos[1] - (normal[1] * radius);
442 
443  contour->AddVertex(vertex);
444 
445  // lower left corner
446  vertex[0] = m_LastPosition[0] - (normal[0] * radius);
447  vertex[1] = m_LastPosition[1] - (normal[1] * radius);
448 
449  contour->AddVertex(vertex);
450 
452  m_WorkingNode->SetData(m_WorkingSlice);
453  m_WorkingNode->Modified();
454  }
455  }
456  else
457  {
458  // switched from different renderwindow
459  // no activate hover highlighting. Otherwise undo / redo wont work
460  this->m_WorkingNode->SetVisibility(false);
461  }
462 
463  m_LastPosition = indexCoordinates;
464 
465  // visualize contour
467  displayContour->Clear();
468 
471 
472  // copy transformed contour into display contour
473  it = tmp->Begin();
474  end = tmp->End();
475 
476  while (it != end)
477  {
478  Point3D point = (*it)->Coordinates;
479 
480  displayContour->AddVertex(point, t);
481  it++;
482  }
483 
484  m_FeedbackContourNode->GetData()->Modified();
485 
486  assert(positionEvent->GetSender()->GetRenderWindow());
487 
488  RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow());
489 }
490 
492 {
493  // When mouse is released write segmentationresult back into image
494  auto *positionEvent = dynamic_cast<mitk::InteractionPositionEvent *>(interactionEvent);
495  if (!positionEvent)
496  return;
497 
498  this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone());
499 
500  // deactivate visibility of helper node
501  m_WorkingNode->SetVisibility(false);
502 
503  RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow());
504 }
505 
510 {
511  // Inversion only for 0 and 1 as painting values
512  if (m_PaintingPixelValue == 1)
513  {
516  }
517  else if (m_PaintingPixelValue == 0)
518  {
521  }
523 }
524 
526 {
527  const PlaneGeometry *planeGeometry((event->GetSender()->GetCurrentWorldPlaneGeometry()));
528  const auto *abstractTransformGeometry(
529  dynamic_cast<const AbstractTransformGeometry *>(event->GetSender()->GetCurrentWorldPlaneGeometry()));
530  DataNode *workingNode(m_ToolManager->GetWorkingData(0));
531 
532  if (!workingNode)
533  return;
534 
535  Image::Pointer image = dynamic_cast<Image *>(workingNode->GetData());
536 
537  if (!image || !planeGeometry || abstractTransformGeometry)
538  return;
539 
540  if (m_CurrentPlane.IsNull() || m_WorkingSlice.IsNull())
541  {
542  m_CurrentPlane = planeGeometry;
544  m_WorkingNode->ReplaceProperty("color", workingNode->GetProperty("color"));
545  m_WorkingNode->SetData(m_WorkingSlice);
546  }
547  else
548  {
549  bool isSameSlice(false);
550  isSameSlice = mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(),
551  m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix());
552  isSameSlice = mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(),
553  m_CurrentPlane->GetIndexToWorldTransform()->GetOffset());
554  if (!isSameSlice)
555  {
557  m_CurrentPlane = nullptr;
558  m_WorkingSlice = nullptr;
559  m_WorkingNode = nullptr;
560  m_CurrentPlane = planeGeometry;
562 
564  m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1)));
565  m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true));
566 
567  m_WorkingNode->SetData(m_WorkingSlice);
568 
569  // So that the paintbrush contour vanished in the previous render window
571  }
572  }
573 
575  {
576  m_WorkingNode->SetProperty("outline binary", mitk::BoolProperty::New(true));
577  m_WorkingNode->SetProperty("color", workingNode->GetProperty("color"));
578  m_WorkingNode->SetProperty("name", mitk::StringProperty::New("Paintbrush_Node"));
579  m_WorkingNode->SetProperty("helper object", mitk::BoolProperty::New(true));
580  m_WorkingNode->SetProperty("opacity", mitk::FloatProperty::New(0.8));
581  m_WorkingNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false));
582  m_WorkingNode->SetVisibility(
584 
586  }
587 }
588 
590 {
591  // Here we simply set the current working slice to null. The next time the mouse is moved
592  // within a renderwindow a new slice will be extracted from the new working data
593  m_WorkingSlice = nullptr;
594 }
void Deactivated() override
Called when the tool gets deactivated.
unsigned int m_LastEventSlice
DataNode::Pointer m_WorkingNode
Super class for all position events.
virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *)
mitk::Point3D m_LastPosition
virtual void OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *)
bool MatrixEqualElementWise(const vnl_matrix_fixed< TCoordRep, NRows, NCols > &matrix1, const vnl_matrix_fixed< TCoordRep, NRows, NCols > &matrix2, mitk::ScalarType epsilon=mitk::eps)
Check for element-wise matrix equality with a user defined accuracy.
Definition: mitkMatrix.h:140
void ConnectActionsAndFunctions() override
static BaseRenderer * GetInstance(vtkRenderWindow *renWin)
Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component=0)
Extract the slice of an image that the user just scribbles on. The given component denotes the vector...
static vtkRenderWindow * GetRenderWindowByName(const std::string &name)
virtual unsigned int GetSlice() const
DataStorage * GetDataStorage()
void UpdateContour(const InteractionPositionEvent *)
virtual void OnMousePressed(StateMachineAction *, InteractionEvent *)
Message WorkingDataChanged
static void FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, mitk::Image::Pointer workingImage, int paintingPixelValue=1)
Fill a contour in a 2D slice with a specified pixel value at time step 0.
static Pointer New()
void SetFeedbackContourColor(float r, float g, float b)
Provide values from 0.0 (black) to 1.0 (full color)
void Send(T t)
Definition: mitkMessage.h:602
virtual const PlaneGeometry * GetCurrentWorldPlaneGeometry()
Get the current 2D-worldgeometry (m_CurrentWorldPlaneGeometry) used for 2D-rendering.
BaseRenderer * m_LastEventSender
virtual void Add(DataNode *node, const DataStorage::SetOfObjects *parents=nullptr)=0
Adds a DataNode containing a data object to its internal storage.
BaseRenderer * GetSender() const
The LevelWindow class Class to store level/window values.
static Pointer New()
ContourModel::Pointer m_MasterContour
static Pointer New()
static RenderingManager * GetInstance()
Represents an action, that is executed after a certain event (in statemachine-mechanism) TODO: implem...
Message1< int > SizeChanged
Image class for storing images.
Definition: mitkImage.h:72
void WriteBackSegmentationResult(const InteractionPositionEvent *, Image *)
mitk::Label * GetActiveLabel(unsigned int layer=0)
Returns the active label of a specific layer.
Image::Pointer m_WorkingSlice
virtual void MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed)
mitk::Image::Pointer image
void Activated() override
Called when the tool gets activated.
ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, ContourModel *contourIn2D, bool correctionForIpSegmentation=false)
Projects a slice index coordinates of a contour back into world coordinates.
static Pointer New()
virtual void Remove(const DataNode *node)=0
Removes node from the DataStorage.
void RequestUpdate(vtkRenderWindow *renderWindow)
Base class for tools that use a contour for feedback.
void CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event)
MITKNEWMODULE_EXPORT bool Equal(mitk::ExampleDataStructure *leftHandSide, mitk::ExampleDataStructure *rightHandSide, mitk::ScalarType eps, bool verbose)
Returns true if the example data structures are considered equal.
mitk::Point2D upperLeft(mitk::Point2D p)
LabelSetImage class for handling labels and layers in a segmentation session.
ToolManager * m_ToolManager
Definition: mitkTool.h:228
MITKCORE_EXPORT const ScalarType eps
virtual bool Exists(const DataNode *node) const =0
Checks if a node exists in the DataStorage.
static Pointer New()
PaintbrushTool(int paintingPixelValue=1)
Describes a two-dimensional, rectangular plane.
#define CONNECT_FUNCTION(a, f)
PlaneGeometry::ConstPointer m_CurrentPlane
virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *)
static Pointer New()
void RequestUpdateAll(RequestType type=REQUEST_UPDATE_ALL)
Class for nodes of the DataTree.
Definition: mitkDataNode.h:57
DataVectorType GetWorkingData()
mitk::AffineTransform3D * GetIndexToWorldTransform()
Get the transformation used to convert from index to world coordinates.
virtual void OnInvertLogic(StateMachineAction *, InteractionEvent *)