fw4spl
STransferFunction.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2017.
3  * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
4  * published by the Free Software Foundation.
5  * ****** END LICENSE BLOCK ****** */
6 
7 #include "scene2D/adaptor/STransferFunction.hpp"
8 
9 #include <fwCom/Signal.hpp>
10 #include <fwCom/Signal.hxx>
11 #include <fwCom/Slot.hpp>
12 #include <fwCom/Slot.hxx>
13 
14 #include <fwData/Composite.hpp>
15 #include <fwData/String.hpp>
16 
17 #include <fwDataTools/fieldHelper/Image.hpp>
18 #include <fwDataTools/fieldHelper/MedicalImageHelpers.hpp>
19 #include <fwDataTools/helper/Image.hpp>
20 
21 #include <fwRenderQt/data/InitQtPen.hpp>
22 #include <fwRenderQt/data/Viewport.hpp>
23 #include <fwRenderQt/Scene2DGraphicsView.hpp>
24 
25 #include <fwServices/macros.hpp>
26 
27 #include <QColorDialog>
28 #include <QGraphicsItemGroup>
29 #include <QPoint>
30 
31 fwServicesRegisterMacro( ::fwRenderQt::IAdaptor, ::scene2D::adaptor::STransferFunction);
32 
33 namespace scene2D
34 {
35 namespace adaptor
36 {
37 
38 static const ::fwServices::IService::KeyType s_TF_INOUT = "tf";
39 static const ::fwServices::IService::KeyType s_VIEWPORT_INPUT = "viewport";
40 
42  m_layer(nullptr),
43  m_circleWidth(0.),
44  m_circleHeight(0.),
45  m_pointIsCaptured(false),
46  m_capturedCircle(nullptr),
47  m_pointSize(10.f)
48 {
49 }
50 
51 //-----------------------------------------------------------------------------
52 
54 {
55 }
56 
57 //-----------------------------------------------------------------------------
58 
60 {
61  this->configureParams();
62 
63  const ConfigType config = this->getConfigTree().get_child("config.<xmlattr>");
64 
65  // Set the line and circle pens color if the respective attribute are present in the config
66  if (config.count("lineColor"))
67  {
68  ::fwRenderQt::data::InitQtPen::setPenColor(m_linePen, config.get<std::string>("lineColor"));
69  }
70 
71  if (config.count("circleColor"))
72  {
73  ::fwRenderQt::data::InitQtPen::setPenColor(m_circlePen, config.get<std::string>("circleColor"));
74  }
75 
76  if (config.count("pointSize"))
77  {
78  m_pointSize = config.get<float>("pointSize");
79  }
80 }
81 
82 //-----------------------------------------------------------------------------
83 
84 void STransferFunction::buildTFPoints()
85 {
86  // Get the selected tf of the image
87  ::fwData::TransferFunction::sptr selectedTF = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
88  SLM_ASSERT("inout '" + s_TF_INOUT + "' is not defined", selectedTF);
89 
90  // Clear the tf points map
91  m_TFPoints.clear();
92 
93  // Iterate on the selected tf and fill the tf points map with key = value and T = Color(RGBA)
94  ::fwData::TransferFunction::TFValuePairType minMax = selectedTF->getMinMaxTFValues();
95  ::fwData::TransferFunction::TFValueType wlMin = selectedTF->getWLMinMax().first;
96  ::fwData::TransferFunction::TFValueType window = selectedTF->getWindow();
97  ::fwData::TransferFunction::TFValueType width = minMax.second - minMax.first;
98 
99  for(const ::fwData::TransferFunction::TFDataType::value_type& elt : selectedTF->getTFData())
100  {
101  ::fwData::TransferFunction::TFValueType val;
102  val = (elt.first - minMax.first) / width;
103  val = val * window + wlMin;
104  m_TFPoints[val] = elt.second;
105  }
106 }
107 
108 //-----------------------------------------------------------------------------
109 
110 void STransferFunction::buildCircles()
111 {
112  ::fwRenderQt::data::Viewport::sptr viewport = this->getScene2DRender()->getViewport();
113 
114  this->initializeViewSize();
115  this->initializeViewportSize();
116 
117  const double viewportHeight = viewport->getHeight();
118  const double viewportWidth = viewport->getWidth();
119 
120  // Total ratio
121  const Scene2DRatio ratio = this->getRatio();
122 
123  const double viewportWidthRatio = this->getViewportSizeRatio().first;
124 
125  // Initialize the width of the circle
126  m_circleWidth = m_pointSize;
127 
128  // Initialize the height of the circles
129  m_circleHeight = m_pointSize * viewportHeight / viewportWidth;
130  m_circleHeight /= viewportWidthRatio;
131  m_circleHeight *= (m_viewInitialSize.first / m_viewInitialSize.second);
132 
133  // Apply the ratio of the scene 2D in order to keep the same size for the circles if viewport's size or
134  // view's size change:
135  m_circleWidth *= ratio.first;
136  m_circleHeight *= ratio.second;
137 
138  // Remove the circles items from the scene and clear the circles vector
139  for(QGraphicsEllipseItem* circle : m_circles)
140  {
141  this->getScene2DRender()->getScene()->removeItem(circle);
142  delete circle;
143  }
144 
145  m_circles.clear();
146 
147  // Iterate on the tf points map to add circle items to the circles vector
148  for(const ::fwData::TransferFunction::TFDataType::value_type& elt : m_TFPoints)
149  {
150  // Build circle corresponding to the tf point and push it back into the circles vector
151  m_circles.push_back(this->buildCircle(elt.first, elt.second));
152  }
153 }
154 
155 //-----------------------------------------------------------------------------
156 
157 QGraphicsEllipseItem* STransferFunction::buildCircle(::fwData::TransferFunction::TFValueType value,
159 {
160  Point2DType valColor(value, color.a );
161  Point2DType coord = this->mapAdaptorToScene(valColor, m_xAxis, m_yAxis);
162 
163  // Build a circle item, set its color, pen and zValue
164  QGraphicsEllipseItem* circle = new QGraphicsEllipseItem(
165  coord.first - m_circleWidth / 2,
166  coord.second - m_circleHeight / 2,
167  m_circleWidth,
168  m_circleHeight
169  );
170  circle->setBrush(QBrush(QColor(color.r*255, color.g*255, color.b*255)));
171  circle->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
172  circle->setPen(m_circlePen);
173  circle->setZValue( 3 );
174 
175  return circle;
176 }
177 
178 //-----------------------------------------------------------------------------
179 
180 void STransferFunction::buildLinesAndPolygons()
181 {
182  ::fwData::TransferFunction::sptr selectedTF = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
183  SLM_ASSERT("inout '" + s_TF_INOUT + "' is not defined", selectedTF);
184 
185  // Remove line and polygon items from the scene and clear the lines and polygons vector
186  for( QGraphicsItem* linesAndPolygons : m_linesAndPolygons)
187  {
188  this->getScene2DRender()->getScene()->removeItem(linesAndPolygons);
189  delete linesAndPolygons;
190  }
191 
192  m_linesAndPolygons.clear();
193 
194  if (selectedTF->getInterpolationMode() == ::fwData::TransferFunction::LINEAR)
195  {
196  this->buildLinearLinesAndPolygons();
197  }
198  else
199  {
200  this->buildNearestLinesAndPolygons();
201  if (!selectedTF->getIsClamped())
202  {
203  this->buildBounds();
204  }
205  }
206 }
207 
208 //-----------------------------------------------------------------------------
209 
210 void STransferFunction::buildBounds()
211 {
212  ::fwRenderQt::data::Viewport::sptr viewport = this->getScene2DRender()->getViewport();
213 
214  const QGraphicsEllipseItem* beginCircle = m_circles.front();
215  const QGraphicsEllipseItem* endCircle = m_circles.back();
216 
217  double x1 = viewport->getX() - 10;
218  double x2 = beginCircle->rect().x() + beginCircle->pos().x() + m_circleWidth /2;
219  double y = beginCircle->rect().y() + beginCircle->pos().y() + m_circleHeight / 2;
220  // Build the line between the actual and the next TF Point and push it back into the lines and polygons vector
221  QGraphicsLineItem* line = new QGraphicsLineItem(x1, y, x2, y);
222  line->setPen(m_linePen);
223  line->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
224  line->setZValue( 2 );
225 
226  m_linesAndPolygons.push_back(line);
227 
228  // Build the points vector defining the polygon and build the polygon
229  QVector<QPointF> vect;
230  vect.append(QPointF(line->line().x1(), 0));
231  vect.append(QPointF(line->line().x1(), line->line().y1()));
232  vect.append(QPointF(line->line().x2(), line->line().y2()));
233  vect.append(QPointF(line->line().x2(), 0));
234 
235  QGraphicsPolygonItem* poly = new QGraphicsPolygonItem( QPolygonF( vect ) );
236 
237  // Set gradient, opacity and pen to the polygon
238  poly->setBrush( beginCircle->brush() );
239  poly->setOpacity(0.8);
240  poly->setPen(QPen(Qt::NoPen));
241  poly->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
242  poly->setZValue( 1 );
243 
244  // Push the polygon back into the lines and polygons vector
245  m_linesAndPolygons.push_back(poly);
246 
247  x1 = endCircle->rect().x() + endCircle->pos().x() + m_circleWidth /2;
248  x2 = viewport->getX() + viewport->getWidth() + 10;
249  y = endCircle->rect().y() + endCircle->pos().y() + m_circleHeight / 2;
250  // Build the line between the actual and the next TF Point and push it back into the lines and polygons vector
251  QGraphicsLineItem* line2 = new QGraphicsLineItem(x1, y, x2, y);
252  line2->setPen(m_linePen);
253  line2->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
254  line2->setZValue( 2 );
255 
256  m_linesAndPolygons.push_back(line2);
257 
258  // Build the points vector defining the polygon and build the polygon
259  QVector<QPointF> vect2;
260  vect2.append(QPointF(line2->line().x1(), 0));
261  vect2.append(QPointF(line2->line().x1(), line2->line().y1()));
262  vect2.append(QPointF(line2->line().x2(), line2->line().y2()));
263  vect2.append(QPointF(line2->line().x2(), 0));
264 
265  QGraphicsPolygonItem* poly2 = new QGraphicsPolygonItem( QPolygonF( vect2 ) );
266 
267  // Set gradient, opacity and pen to the polygon
268  poly2->setBrush( endCircle->brush() );
269  poly2->setOpacity(0.8);
270  poly2->setPen(QPen(Qt::NoPen));
271  poly2->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
272  poly2->setZValue( 1 );
273 
274  // Push the polygon back into the lines and polygons vector
275  m_linesAndPolygons.push_back(poly2);
276 }
277 
278 //-----------------------------------------------------------------------------
279 
280 void STransferFunction::buildLinearLinesAndPolygons()
281 {
282  SLM_ASSERT("Circles must not be empty", !m_circles.empty());
283 
284  ::fwData::TransferFunction::sptr selectedTF = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
285  SLM_ASSERT("inout '" + s_TF_INOUT + "' is not defined", selectedTF);
286 
287  ::fwRenderQt::data::Viewport::sptr viewport = this->getScene2DRender()->getViewport();
288 
289  QVector<QPointF> vect;
290  QLinearGradient grad;
291 
292  const QGraphicsEllipseItem* firtsCircle = m_circles.front();
293  const QGraphicsEllipseItem* lastCircle = m_circles.back();
294 
295  double xBegin;
296  double xEnd;
297  xBegin = firtsCircle->rect().x() + firtsCircle->pos().x() + m_circleWidth / 2;
298  xEnd = lastCircle->rect().x() + lastCircle->pos().x() + m_circleWidth / 2;
299  if (selectedTF->getIsClamped())
300  {
301  vect.append(QPointF(xBegin, 0));
302  }
303  else
304  {
305  if (xBegin > viewport->getX())
306  {
307  xBegin = viewport->getX()-10;
308  vect.append(QPointF(xBegin, 0));
309  vect.append(QPointF(xBegin, firtsCircle->rect().y() + firtsCircle->pos().y() + m_circleHeight / 2));
310  }
311  else
312  {
313  vect.append(QPointF(xBegin, 0));
314  }
315  if (xEnd < viewport->getX() + viewport->getWidth())
316  {
317  xEnd = viewport->getX() + viewport->getWidth() +10;
318  }
319  }
320 
321  grad.setColorAt(0, firtsCircle->brush().color());
322 
323  grad.setStart( xBegin, 0);
324  grad.setFinalStop( xEnd, 0 );
325 
326  double distanceMax = xEnd - xBegin;
327 
328  // Iterate on the circles vector to add line and polygon items to the lines and polygons vector
329  for ( auto circleIt = m_circles.cbegin(); circleIt != m_circles.cend()-1; ++circleIt)
330  {
331  QPointF p1((*circleIt)->rect().x() + (*circleIt)->pos().x() + m_circleWidth / 2,
332  (*circleIt)->rect().y() + (*circleIt)->pos().y() + m_circleHeight / 2);
333  QPointF p2((*(circleIt + 1))->rect().x() + (*(circleIt + 1))->pos().x() + m_circleWidth / 2,
334  (*(circleIt + 1))->rect().y() + (*(circleIt + 1))->pos().y() + m_circleHeight / 2);
335 
336  vect.append(p1);
337  vect.append(p2);
338 
339  // Build the gradient
340  grad.setColorAt((p1.x() - xBegin)/distanceMax, (*circleIt)->brush().color());
341  }
342 
343  if (!selectedTF->getIsClamped())
344  {
345  if (xEnd == viewport->getX() + viewport->getWidth() + 10)
346  {
347  vect.append(QPointF(xEnd, lastCircle->rect().y() + lastCircle->pos().y() + m_circleHeight / 2));
348  }
349  double lastCircleX = lastCircle->rect().x() + lastCircle->pos().x() + m_circleWidth / 2;
350  grad.setColorAt((lastCircleX-xBegin)/distanceMax, lastCircle->brush().color());
351  }
352  vect.append(QPointF(xEnd, 0));
353  grad.setColorAt(1, lastCircle->brush().color());
354 
355  QGraphicsPolygonItem* poly = new QGraphicsPolygonItem( QPolygonF( vect ) );
356  // Set gradient, opacity and pen to the polygon
357  poly->setBrush( QBrush( grad ) );
358  poly->setOpacity(0.8);
359  poly->setPen(m_linePen);
360  poly->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
361  poly->setZValue( 1 );
362 
363  // Push the polygon back into the lines and polygons vector
364  m_linesAndPolygons.push_back(poly);
365 }
366 
367 //-----------------------------------------------------------------------------
368 
369 void STransferFunction::buildNearestLinesAndPolygons()
370 {
371  // Iterate on the circles vector to add line and polygon items to the lines and polygons vector
372  for ( auto circleIt = m_circles.cbegin(); circleIt != m_circles.cend(); ++circleIt)
373  {
374  QGraphicsEllipseItem* circle = *circleIt;
375  QGraphicsEllipseItem* previousCircle;
376  if (circleIt == m_circles.cbegin())
377  {
378  previousCircle = circle;
379  }
380  else
381  {
382  previousCircle = *(circleIt-1);
383  }
384  QGraphicsEllipseItem* nextCircle;
385  if (circleIt == m_circles.cend()-1)
386  {
387  nextCircle = circle;
388  }
389  else
390  {
391  nextCircle = *(circleIt+1);
392  }
393 
394  double x1 = previousCircle->rect().x() + previousCircle->pos().x() + (circle->pos().x() + circle->rect().x()
395  - (previousCircle->pos().x() +
396  previousCircle->rect().x()))/2 +
397  m_circleWidth /2;
398  double x2 = circle->rect().x() + circle->pos().x() + (nextCircle->pos().x() + nextCircle->rect().x()
399  - (circle->pos().x() + circle->rect().x()))/2 +
400  m_circleWidth /2;
401  double y = circle->rect().y() + circle->pos().y() + m_circleHeight / 2;
402  // Build the line between the actual and the next TF Point and push it back into the lines and polygons vector
403  QGraphicsLineItem* line = new QGraphicsLineItem(x1, y, x2, y);
404  line->setPen(m_linePen);
405  line->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
406  line->setZValue( 2 );
407 
408  m_linesAndPolygons.push_back(line);
409 
410  // Build the points vector defining the polygon and build the polygon
411  QVector<QPointF> vect;
412  vect.append(QPointF(line->line().x1(), 0));
413  vect.append(QPointF(line->line().x1(), line->line().y1()));
414  vect.append(QPointF(line->line().x2(), line->line().y2()));
415  vect.append(QPointF(line->line().x2(), 0));
416 
417  QGraphicsPolygonItem* poly = new QGraphicsPolygonItem( QPolygonF( vect ) );
418 
419  // Set gradient, opacity and pen to the polygon
420  poly->setBrush( circle->brush() );
421  poly->setOpacity(0.8);
422  poly->setPen(QPen(Qt::NoPen));
423  poly->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
424  poly->setZValue( 1 );
425 
426  // Push the polygon back into the lines and polygons vector
427  m_linesAndPolygons.push_back(poly);
428  }
429 }
430 
431 //-----------------------------------------------------------------------------
432 
433 void STransferFunction::buildLayer()
434 {
435  // Add graphics items vectors to the layer
436  for ( unsigned int i = 0; i < m_linesAndPolygons.size(); i++)
437  {
438  m_layer->addToGroup(m_linesAndPolygons.at(i));
439  }
440 
441  for ( unsigned int i = 0; i < m_circles.size(); i++)
442  {
443  m_layer->addToGroup(m_circles.at(i));
444  }
445 
446  // Adjust the layer's position and zValue depending on the associated axis
447  m_layer->setPos(m_xAxis->getOrigin(), m_yAxis->getOrigin());
448  m_layer->setZValue(m_zValue);
449 
450  // Add the layer item to the scene
451  this->getScene2DRender()->getScene()->addItem(m_layer);
452 }
453 
454 //-----------------------------------------------------------------------------
455 
456 void STransferFunction::updateImageTF()
457 {
458  // Get the selected tf of the image
459  ::fwData::TransferFunction::sptr selectedTF = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
460  SLM_ASSERT("inout '" + s_TF_INOUT + "' is not defined", selectedTF);
461 
462  ::fwData::TransferFunction::TFValuePairType minMax = selectedTF->getMinMaxTFValues();
463  ::fwData::TransferFunction::TFValueType window = selectedTF->getWindow();
464  ::fwData::TransferFunction::TFValueType wlMin = selectedTF->getWLMinMax().first;
465  ::fwData::TransferFunction::TFValueType val;
466  selectedTF->clear();
467 
468  // Rebuild the selected tf from the tf points map
469  double width = minMax.second - minMax.first;
470  double min = m_TFPoints.begin()->first;
471  double max = m_TFPoints.rbegin()->first;
472  for(const ::fwData::TransferFunction::TFDataType::value_type& elt : m_TFPoints)
473  {
474  val = (elt.first - wlMin) / window;
475  val = val * width + minMax.first;
476  selectedTF->addTFColor(val, elt.second);
477  }
478 
479  if (window >= 0)
480  {
481  selectedTF->setWLMinMax(::fwData::TransferFunction::TFValuePairType(min, max));
482  }
483  else
484  {
485  selectedTF->setWLMinMax(::fwData::TransferFunction::TFValuePairType(max, min));
486  }
487 
488  auto sig = selectedTF->signal< ::fwData::TransferFunction::PointsModifiedSignalType >(
490  {
491  ::fwCom::Connection::Blocker block(sig->getConnection(m_slotUpdate));
492  sig->asyncEmit();
493  }
494 }
495 
496 //-----------------------------------------------------------------------------
497 
499 {
500  // Initialize the layer and the circle height and width
501  m_layer = new QGraphicsItemGroup();
502 
503  m_linePen.setCosmetic( true );
504  m_linePen.setWidthF( 0 );
505 
506  m_circlePen.setCosmetic( true );
507  m_circlePen.setWidthF( 0 );
508 
509  this->updating();
510 }
511 
512 //-----------------------------------------------------------------------------
513 
515 {
516  // Build the tf map points, the circles vector, the lines and polygons vector, add the items to the layer and add it
517  // to the scene
518  this->buildTFPoints();
519  this->buildCircles();
520  this->buildLinesAndPolygons();
521  this->buildLayer();
522 }
523 
524 //-----------------------------------------------------------------------------
525 
527 {
528  // Clear the items vectors and remove the layer (and all its children) from the scene
529  for (auto circleIt = m_circles.begin(); circleIt != m_circles.end(); ++circleIt )
530  {
531  delete *circleIt;
532  }
533  m_circles.clear();
534 
535  for( auto linesPolyIt = m_linesAndPolygons.begin(); linesPolyIt != m_linesAndPolygons.end(); ++linesPolyIt)
536  {
537  delete *linesPolyIt;
538  }
539  m_linesAndPolygons.clear();
540 
541  this->getScene2DRender()->getScene()->removeItem(m_layer);
542  delete m_layer;
543  m_layer = nullptr;
544 }
545 
546 //-----------------------------------------------------------------------------
547 
549 {
550  SLM_ASSERT("Sizes of circles vector and tf points map are different", m_TFPoints.size() == m_circles.size());
551 
552  QPoint scenePos = QPoint(_event.getCoord().getX(), _event.getCoord().getY());
553 
554  QList<QGraphicsItem*> items = this->getScene2DRender()->getView()->items(scenePos);
555 
556  // Iterate parallely on the circles vector and the tf points map
557  ::fwData::TransferFunction::TFDataType::iterator TFPointIt = m_TFPoints.begin();
558  for(QGraphicsEllipseItem* circle : m_circles)
559  {
560  if ( items.indexOf(circle) >= 0)
561  {
562  // If there is a double click
563  if ( _event.getType() == ::fwRenderQt::data::Event::MouseButtonDoubleClick )
564  {
565  this->doubleClickEvent(circle, TFPointIt->second);
566  return;
567  }
568  // If left button is pressed
569  else if ( _event.getType() == ::fwRenderQt::data::Event::MouseButtonPress
570  && _event.getButton() == ::fwRenderQt::data::Event::LeftButton )
571  {
572 
573  this->leftButtonEvent(circle, _event);
574  return;
575  }
576  else if ( _event.getType() == ::fwRenderQt::data::Event::MouseButtonPress
577  && _event.getButton() == ::fwRenderQt::data::Event::RightButton
578  && m_circles.size() > 2 )
579  {
580  this->rightButtonEvent(TFPointIt->first, _event);
581  return;
582  }
583  }
584  // If a point is captured, this is the good one, and the mouse move
585  if ( m_pointIsCaptured && m_capturedCircle == circle
586  && _event.getType() == ::fwRenderQt::data::Event::MouseMove )
587  {
588  this->mouseMoveEvent(circle, TFPointIt->first, _event);
589  return;
590  }
591  // If a point is captured, this is the good one, and the button is released
592  else if ( m_pointIsCaptured && m_capturedCircle == circle
593  && _event.getType() == ::fwRenderQt::data::Event::MouseButtonRelease )
594  {
595  this->mouseButtonReleaseEvent(circle, _event);
596  return;
597  }
598  ++TFPointIt;
599  }
600 
601  // After iteration, due to return instruction in it, the events caught aren't on circles
602  if ( _event.getType() == ::fwRenderQt::data::Event::MouseButtonDoubleClick &&
603  _event.getButton() == ::fwRenderQt::data::Event::LeftButton )
604  {
605  // If no other event has been catched, a left click means a point creation
606  this->doubleClickEvent(_event);
607  }
608 
609  if( _event.getType() == ::fwRenderQt::data::Event::Resize)
610  {
611  ::fwRenderQt::data::Size oldSize = _event.getOldSize();
612  ::fwRenderQt::data::Size newSize = _event.getSize();
613 
614  if( oldSize.getWidth() == -1.0f )
615  {
616  m_viewInitialSize.first = newSize.getWidth();
617  }
618 
619  if( oldSize.getHeight() == -1.0f )
620  {
621  m_viewInitialSize.second = newSize.getHeight();
622  }
623 
624  this->updating();
625  }
626 }
627 
628 //-----------------------------------------------------------------------------
629 
630 void STransferFunction::doubleClickEvent(QGraphicsEllipseItem* circle, ::fwData::TransferFunction::TFColor& tfColor)
631 {
632  // Open a QColorDialog with the selected circle color and the tf point alpha as default rgba color
633  const QColor shapeColor = ((QAbstractGraphicsShapeItem*)circle)->brush().color();
634  const QColor initialColor( shapeColor.red(), shapeColor.green(), shapeColor.blue(), tfColor.a*255);
635 
636  QColor circleColor = QColorDialog::getColor(initialColor,
637  this->getScene2DRender()->getView(),
638  QString("Choose the point color"),
639  QColorDialog::ShowAlphaChannel);
640 
641  if (circleColor.isValid())
642  {
643  // If the color is valid, modify the selected tf point color
644  tfColor.r = circleColor.redF();
645  tfColor.g = circleColor.greenF();
646  tfColor.b = circleColor.blueF();
647  tfColor.a = circleColor.alphaF();
648 
649  // Build circles, lines and polygons cause they're modified by the new tf point color
650  this->buildCircles();
651  this->buildLinesAndPolygons();
652  this->buildLayer();
653 
654  // Update the selected tf (no need to rescale cause the selected circle position has not changed)
655  this->updateImageTF();
656  }
657 }
658 
659 //-----------------------------------------------------------------------------
660 
661 void STransferFunction::leftButtonEvent(QGraphicsEllipseItem* circle, ::fwRenderQt::data::Event& _event)
662 {
663  m_pointIsCaptured = true;
664 
665  // Store the captured circle in case it's moved
666  m_capturedCircle = circle;
667 
668  //Store the circle coordinates in case it's moved
669  m_oldCoord = this->coordViewToCoordItem( _event.getCoord() );
670 
671  // Set the selected circle pen to yellow to get a visual feedback that the selected circle is selected
672  QPen circlePen(Qt::yellow);
673  circlePen.setCosmetic(true);
674  circle->setPen( circlePen );
675 
676  _event.setAccepted(true);
677 }
678 
679 //-----------------------------------------------------------------------------
680 
681 void STransferFunction::mouseMoveEvent(QGraphicsEllipseItem* circle,
682  ::fwData::TransferFunction::TFValueType tfPoint,
683  ::fwRenderQt::data::Event& _event)
684 {
685  QGraphicsEllipseItem* lastPoint = m_circles.back();
686 
687  std::vector< QGraphicsEllipseItem* >::iterator circleIt;
688  circleIt = std::find(m_circles.begin(), m_circles.end(), circle);
689 
690  // Get the previous circle
691  std::vector< QGraphicsEllipseItem* >::iterator previousCircle = circleIt;
692  if (circle != m_circles.front())
693  {
694  --previousCircle;
695  }
696 
697  // Get the next circle
698  std::vector< QGraphicsEllipseItem* >::iterator nextCircle = circleIt;
699  if (circle != lastPoint)
700  {
701  ++nextCircle;
702  }
703 
704  // Get the actual selected circle coordinates
705  ::fwRenderQt::data::Coord newCoord = this->coordViewToCoordItem( _event.getCoord() );
706 
707  // Calculate the real coordinates of the previous circle
708  const double previousXCircleRealPos = (*previousCircle)->rect().x() + (*previousCircle)->pos().x();
709  const double previousYRealNewPos = (*previousCircle)->rect().y() + (*previousCircle)->pos().y();
710 
711  Point2DType previousValues;
712  Point2DType previousXY(previousXCircleRealPos, previousYRealNewPos );
713  previousValues = this->mapSceneToAdaptor(previousXY, m_xAxis, m_yAxis);
714 
715  // Calculate the real coordinates of the next circle
716  const double nextXCircleRealPos = (*nextCircle)->rect().x() + (*nextCircle)->pos().x();
717  const double nextYRealNewPos = (*nextCircle)->rect().y() + (*nextCircle)->pos().y();
718 
719  Point2DType nextValues;
720  const Point2DType nextXY(nextXCircleRealPos, nextYRealNewPos);
721  nextValues = this->mapSceneToAdaptor(nextXY, m_xAxis, m_yAxis);
722 
723  // Calculate the real coordinates of the selected circle
724  const double circleXRealNewPos = circle->rect().x() + circle->pos().x() + newCoord.getX() - m_oldCoord.getX();
725  const double circleYRealNewPos = circle->rect().y() + circle->pos().y() + newCoord.getY() - m_oldCoord.getY();
726 
727  Point2DType realValues;
728  const Point2DType circleXY( circleXRealNewPos, circleYRealNewPos );
729  realValues = this->mapSceneToAdaptor(circleXY, m_xAxis, m_yAxis);
730 
731  Point2DType oldCoordPair;
732  const Point2DType oldCoordXY( m_oldCoord.getX(), m_oldCoord.getY() );
733  oldCoordPair = this->mapSceneToAdaptor(oldCoordXY, m_xAxis, m_yAxis);
734 
735  Point2DType newCoordPair;
736  const Point2DType newCoordXY( newCoord.getX(), newCoord.getY() );
737  newCoordPair = this->mapSceneToAdaptor(newCoordXY, m_xAxis, m_yAxis);
738 
739  // Check if the mouse isn't out of bounds vertically and horizontally
740  if ( (circle == m_circles.front() || realValues.first > previousValues.first)
741  && (circle == lastPoint || realValues.first < nextValues.first)
742  && (realValues.second - m_circleHeight/2) >= 0
743  && (realValues.second - m_circleHeight/2) <= 1 )
744  {
745  // Move the selected tf point by the difference between the old coordinates and the new ones
746  circle->moveBy( newCoord.getX() - m_oldCoord.getX(), newCoord.getY() - m_oldCoord.getY() );
747 
748  // Set the actual coordinates as the old ones
749  m_oldCoord = newCoord;
750  }
751  else
752  {
753  // Check if the mouse is out of bounds only horizontally
754  if ( ((circle != m_circles.front() && realValues.first < previousValues.first)
755  || (circle != lastPoint && realValues.first > nextValues.first))
756  && (realValues.second - m_circleHeight/2) >= 0
757  && (realValues.second - m_circleHeight/2) <= 1 )
758  {
759  // new abscissa of the moving TF point
760  double x = (newCoordPair.first > oldCoordPair.first) ? (nextValues.first - 1) : (previousValues.first + 1);
761  x = this->mapAdaptorToScene(Point2DType( x, 0 ), m_xAxis, m_yAxis).first;
762 
763  QRectF rect = circle->rect();
764  const double width = rect.width();
765  rect.setX( x );
766  rect.setWidth( width );
767  circle->setRect( rect );
768 
769  circle->setPos( 0, circle->pos().y() + (newCoord.getY() - m_oldCoord.getY()) );
770 
771  m_oldCoord.setX( x );
772  m_oldCoord.setY(newCoord.getY());
773  }
774 
775  // Check if the mouse is out of bounds only vertically
776  if ( (circle == m_circles.front() || realValues.first > previousValues.first)
777  && (circle == lastPoint || realValues.first < nextValues.first)
778  && ((realValues.second - m_circleHeight/2) < 0 || (realValues.second - m_circleHeight/2) > 1) ) // opacity
779  {
780  // If the mouse is vertically out of bounds, the TF point is moved to fit the nearest vertical bound (0 or
781  // 1).
782 
783  double y = 0.; // new ordinate of the moving TF point
784 
785  if( newCoordPair.second > oldCoordPair.second )
786  {
787  // Mouse's move goes toward (and outside) the top of the scene: the ordinate of the circle must be set
788  // to 1,
789  // which is the highest value for opacity
790  y = this->mapAdaptorToScene(Point2DType( 0, 1 ), m_xAxis, m_yAxis).second;
791  }
792 
793  QRectF rect = circle->rect();
794  const double height = rect.height();
795  rect.setY( y - m_circleHeight / 2 );
796  rect.setHeight( height );
797  circle->setRect( rect );
798 
799  circle->setPos( circle->pos().x() + (newCoord.getX() - m_oldCoord.getX()), 0); // position within item's
800  // rect
801 
802  m_oldCoord.setX(newCoord.getX());
803  m_oldCoord.setY( y );
804  }
805  }
806 
807  this->buildLinesAndPolygons();
808  this->buildLayer();
809 
810  // Erase the selected tf point cause it's key is const
811  m_TFPoints.erase(tfPoint);
812 
813  const Point2DType point(this->pointValue(circle), circle->rect().y() + circle->pos().y() + m_circleHeight / 2 );
814  // Create a new tf point with the right value (key) and alpha
815  const Point2DType pair = this->mapSceneToAdaptor(point, m_xAxis, m_yAxis);
816 
817  m_TFPoints[pair.first] = ::fwData::TransferFunction::TFColor(
818  circle->brush().color().redF(),
819  circle->brush().color().greenF(),
820  circle->brush().color().blueF(),
821  pair.second);
822 
823  // Update the image tf
824  this->updateImageTF();
825 }
826 
827 //-----------------------------------------------------------------------------
828 
829 void STransferFunction::mouseButtonReleaseEvent(QGraphicsEllipseItem* circle, ::fwRenderQt::data::Event& _event)
830 {
831  // Reset the circle pen to the selected circle to get a visual feedback that the circle is no more selected
832  circle->setPen(m_circlePen);
833  m_pointIsCaptured = false;
834  _event.setAccepted(true);
835 }
836 
837 //-----------------------------------------------------------------------------
838 
839 void STransferFunction::rightButtonEvent(::fwData::TransferFunction::TFValueType tfPoint,
840  ::fwRenderQt::data::Event& _event)
841 {
842  _event.setAccepted(true);
843 
844  // Erase the selected tf point
845  m_TFPoints.erase(tfPoint);
846 
847  this->updateImageTF();
848  this->updating();
849 }
850 
851 //-----------------------------------------------------------------------------
852 
853 void STransferFunction::doubleClickEvent( ::fwRenderQt::data::Event& _event)
854 {
855  // Get the x and y position in the scene coordinates
856  const double x = this->getScene2DRender()->mapToScene(_event.getCoord()).getX();
857  const double y = this->getScene2DRender()->mapToScene(_event.getCoord()).getY();
858 
859  // Transform the x and y coordinates with axis scaling and type
860  const Point2DType _xy(x, y);
861  const Point2DType values = this->mapSceneToAdaptor(_xy, m_xAxis, m_yAxis);
862 
863  ::fwData::TransferFunction::TFDataType::iterator nextTFPointIt = m_TFPoints.begin();
864  ::fwData::TransferFunction::TFDataType::reverse_iterator lastTFPointIt = m_TFPoints.rbegin();
865 
866  if (values.first < (*nextTFPointIt).first)
867  {
868  ::fwData::TransferFunction::TFColor color((*nextTFPointIt).second.r, (*nextTFPointIt).second.g,
869  (*nextTFPointIt).second.b, values.second);
870  m_TFPoints[values.first] = color;
871 
872  this->updateImageTF();
873  this->updating();
874  }
875  else if (values.first > (*lastTFPointIt).first)
876  {
877  ::fwData::TransferFunction::TFColor color((*lastTFPointIt).second.r, (*lastTFPointIt).second.g,
878  (*lastTFPointIt).second.b, values.second);
879  m_TFPoints[values.first] = color;
880 
881  this->updateImageTF();
882  this->updating();
883  }
884  else
885  {
886  // Iterate on m_TFPoints to find the next and the previous point of our event position
887  while ((*nextTFPointIt).first < values.first)
888  {
889  ++nextTFPointIt;
890  }
891  ::fwData::TransferFunction::TFDataType::iterator prevTFPointIt = nextTFPointIt;
892  --prevTFPointIt;
893 
894  // Check if the new point is not placed on an already existing point
895  if ( (values.first != (*nextTFPointIt).first) && (values.first != (*prevTFPointIt).first) )
896  {
897  // Calculate the relative position of the event between the 2 encompassing points
898  const double coef = (values.first - (*prevTFPointIt).first) /
899  ((*nextTFPointIt).first - (*prevTFPointIt).first);
900 
901  // Calculate the new red, green, blue and alpha by linear interpolation in RGBA
902  const double newRed = coef * ((*nextTFPointIt).second.r - (*prevTFPointIt).second.r) +
903  (*prevTFPointIt).second.r;
904  const double newGreen = coef * ((*nextTFPointIt).second.g - (*prevTFPointIt).second.g) +
905  (*prevTFPointIt).second.g;
906  const double newBlue = coef * ((*nextTFPointIt).second.b - (*prevTFPointIt).second.b) +
907  (*prevTFPointIt).second.b;
908  const double newAlpha = coef * ((*nextTFPointIt).second.a - (*prevTFPointIt).second.a) +
909  (*prevTFPointIt).second.a;
910 
911  // Add a point with the right values to the tf points map
912  m_TFPoints[values.first] = ::fwData::TransferFunction::TFColor(newRed, newGreen, newBlue, newAlpha);
913 
914  this->updateImageTF();
915  this->updating();
916  }
917  }
918 }
919 
920 //-----------------------------------------------------------------------------
921 
922 double STransferFunction::pointValue(QGraphicsEllipseItem* circle)
923 {
924  // Return the x coordinate of the center of a circle in a 0-1 scale
925  return (circle->rect().x() + circle->pos().x() + m_circleWidth / 2);
926 }
927 
928 //-----------------------------------------------------------------------------
929 
930 ::fwRenderQt::data::Coord STransferFunction::coordViewToCoordItem( const ::fwRenderQt::data::Coord& _coord )
931 {
932  ::fwRenderQt::data::Coord scenePoint = this->getScene2DRender()->mapToScene( _coord );
933  return scenePoint;
934 }
935 
936 //------------------------------------------------------------------------------
937 
939 {
940  KeyConnectionsMap connections;
941  connections.push(s_TF_INOUT, ::fwData::TransferFunction::s_MODIFIED_SIG, s_UPDATE_SLOT);
942  connections.push(s_TF_INOUT, ::fwData::TransferFunction::s_POINTS_MODIFIED_SIG, s_UPDATE_SLOT);
943  connections.push(s_TF_INOUT, ::fwData::TransferFunction::s_WINDOWING_MODIFIED_SIG, s_UPDATE_SLOT);
944  connections.push( s_VIEWPORT_INPUT, ::fwRenderQt::data::Viewport::s_MODIFIED_SIG, s_UPDATE_SLOT );
945  return connections;
946 }
947 
948 //------------------------------------------------------------------------------
949 
950 } // namespace adaptor
951 } // namespace scene2D
952 
Root class for all scene2d adaptors.
::fwRenderQt::data::Axis::sptr m_xAxis
The x Axis.
This class is a helper to define the connections of a service and its data.
Definition: IService.hpp:454
std::pair< float, float > Scene2DRatio
<width, height>
Class allowing to block a Connection.
Definition: Connection.hpp:20
FWRENDERQT_API void initializeViewSize()
Initialize the source values used for computing view&#39;s size ratio.
FWRENDERQT_API Point2DType mapAdaptorToScene(const Point2DType &_xy, const ::fwRenderQt::data::Axis::sptr &_xAxis, const ::fwRenderQt::data::Axis::sptr &_yAxis) const
FWRENDERQT_API std::shared_ptr< ::fwRenderQt::SRender > getScene2DRender() const
Get the render that manages the IAdaptor.
SCENE2D_API KeyConnectionsMap getAutoConnections() const override
Returns proposals to connect service slots to associated object signals, this method is used for obj/...
std::pair< double, double > Point2DType
Point2D coordinate <X, Y>
static FWRENDERQT_API void setPenColor(QPen &_pen, std::string _color)
Set a pen a color.
Definition: InitQtPen.cpp:18
SCENE2D_API void starting() override
SCENE2D_API STransferFunction() noexcept
Constructor, add handle events TRANSFERFUNCTION and WINDOWING.
This bundles contains data and services used to display a 2D Qt scene.
virtual SCENE2D_API ~STransferFunction() noexcept
Basic destructor, do nothing.
UpdateSlotType::sptr m_slotUpdate
Slot to call update method.
Definition: IService.hpp:690
FWRENDERQT_API Point2DType mapSceneToAdaptor(const Point2DType &_xy, const ::fwRenderQt::data::Axis::sptr &_xAxis, const ::fwRenderQt::data::Axis::sptr &_yAxis) const
IAdaptor implementation to display a transfer function.
#define SLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:308
FWRENDERQT_API void initializeViewportSize()
Initialize the source values used for computing viewport&#39;s size ratio.
SCENE2D_API void updating() override
::fwRenderQt::data::Axis::sptr m_yAxis
The y Axis.
SCENE2D_API void stopping() override
FWRENDERQT_API ViewportSizeRatio getViewportSizeRatio() const
Return the ratio between viewport&#39;s initial size and its current size.
FWRENDERQT_API void configureParams()
Parse the xml configuration for Axis, z value and opacity.
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_MODIFIED_SIG
Key in m_signals map of signal m_sigModified.
SCENE2D_API void configuring() override
Configure the service before starting. Apply the configuration to service.
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_WINDOWING_MODIFIED_SIG
Type of signal when points are modified.
This class manage events on the scene 2D (mouse event, keyboard event , ...).
Definition: Event.hpp:26
static FWSERVICES_APIconst::fwCom::Slots::SlotKeyType s_UPDATE_SLOT
Slot to call start method.
Definition: IService.hpp:177
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_POINTS_MODIFIED_SIG
Type of signal when points are modified.
SCENE2D_API void processInteraction(::fwRenderQt::data::Event &_event) override
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247