fw4spl
SCurvedHistogram.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2018.
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/SCurvedHistogram.hpp"
8 
9 #include <fwData/Float.hpp>
10 #include <fwData/Histogram.hpp>
11 #include <fwData/mt/ObjectReadLock.hpp>
12 #include <fwData/Point.hpp>
13 
14 #include <fwRenderQt/bspline.hpp>
15 #include <fwRenderQt/data/InitQtPen.hpp>
16 #include <fwRenderQt/Scene2DGraphicsView.hpp>
17 
18 #include <fwServices/macros.hpp>
19 
20 #include <QGraphicsRectItem>
21 #include <QGraphicsView>
22 
23 fwServicesRegisterMacro( ::fwRenderQt::IAdaptor, ::scene2D::adaptor::SCurvedHistogram);
24 
25 #define PI 3.14159265
26 
27 namespace scene2D
28 {
29 
30 namespace adaptor
31 {
32 
33 static const ::fwServices::IService::KeyType s_POINT_INOUT = "point";
34 static const ::fwServices::IService::KeyType s_HISTOGRAM_INPUT = "histogram";
35 
36 const float SCurvedHistogram::SCALE = 1.1f; // vertical scaling factor applied at each mouse scroll
37 const float SCurvedHistogram::NB_POINTS_BEZIER = 100.0f;
38 
39 //-----------------------------------------------------------------------------------------------------------------
40 
41 SCurvedHistogram::SCurvedHistogram() noexcept :
42  m_painterPath(nullptr),
43  m_borderWidth(1.75f),
44  m_scale(1.0f),
45  m_layer(nullptr)
46 {
47 }
48 
49 //-----------------------------------------------------------------------------------------------------------------
50 
51 SCurvedHistogram::~SCurvedHistogram() noexcept
52 {
53 }
54 
55 //-----------------------------------------------------------------------------------------------------------------
56 
58 {
59  this->configureParams(); // Looks for 'xAxis', 'yAxis', 'opacity' and 'zValue'
60 
61  const ConfigType config = this->getConfigTree().get_child("config.<xmlattr>");
62 
63  m_innerColor = QPen( Qt::transparent );
64  m_borderColor = QPen( Qt::transparent );
65  m_brush = QBrush( Qt::NoBrush );
66 
67  if (config.count("borderColor"))
68  {
70  m_borderColor, config.get<std::string>("borderColor"), m_opacity );
71  }
72 
73  if (config.count("innerColor"))
74  {
76  m_innerColor, config.get<std::string>("innerColor"), m_opacity );
77  }
78 
79  if (config.count("borderWidth"))
80  {
81  m_borderWidth = config.get<float>("borderWidth");
82  }
83 }
84 
85 //----------------------------------------------------------------------------------------------------------
86 
88 {
89  // Init border style
90  m_borderColor.setCosmetic( true );
91  m_borderColor.setWidthF( static_cast<qreal>(m_borderWidth) );
92  m_borderColor.setStyle( Qt::SolidLine );
93  m_borderColor.setJoinStyle( Qt::RoundJoin );
94  m_borderColor.setCapStyle( Qt::RoundCap );
95 
96  m_brush = QBrush( m_innerColor.color() );
97 
98  this->updating();
99 }
100 
101 //----------------------------------------------------------------------------------------------------------
102 
103 SCurvedHistogram::Points SCurvedHistogram::getControlPoints(const ::fwData::Histogram::csptr& _histogram) const
104 {
105  ::fwData::Histogram::fwHistogramValues histogramValues = _histogram->getValues();
106  const float binsWidth = _histogram->getBinsWidth();
107  const float histogramMin = _histogram->getMinValue();
108 
109  Point p;
110  Points controlPoints;
111  const size_t nbValues = histogramValues.size();
112 
113  // WARNING: we shouldn't add all the points of the histogram into the vector of controlPoints
114  // (testing...)
115  for(size_t i = 0; i < nbValues; ++i)
116  {
117  p.first = static_cast<double>(histogramMin + i * binsWidth);
118  p.second = histogramValues[i];
119 
120  controlPoints.push_back( p );
121  }
122 
123  return controlPoints;
124 }
125 
126 //----------------------------------------------------------------------------------------------------------
127 
128 SCurvedHistogram::Points SCurvedHistogram::getBSplinePoints( const Points& _points ) const
129 {
130  Points bSplinePoints;
131  point_list list; // see bspline.h
132 
133  // Add again the first point with a higher value in order to prevent B-Spline algorithm from removing
134  // the first value.
135  list.add_point(new point(static_cast<float>( _points[0].first), static_cast<float>( _points[0].second * 2) ));
136 
137  // Add all the points
138  for(const auto& pt : _points )
139  {
140  list.add_point(new point(static_cast<float>(pt.first), static_cast<float>(pt.second) ));
141  }
142 
143  // Add again the last point
144  list.add_point(new point(static_cast<float>( _points.back().first),
145  static_cast<float>( _points.back().second / 2 ) ));
146 
147  // Commpute the points of the B-Spline with external code from AHO (to be integrated here later).
148  cat_curve curve( list );
149  curve.m_precision = static_cast<int>(_points.size() * 5);
150  curve.compute();
151 
152  for(int i = 0; i < curve.m_precision; ++i)
153  {
154  bSplinePoints.push_back( Point( curve.m_curve_point[i].x, curve.m_curve_point[i].y ) );
155  }
156 
157  return bSplinePoints;
158 }
159 
160 //----------------------------------------------------------------------------------------------------------
161 
162 SCurvedHistogram::Points SCurvedHistogram::getResampledBSplinePoints(const Points& _bSplinePoints ) const
163 {
164  Points points;
165  Point point = _bSplinePoints.front();
166 
167  double dx, dy;
168  const double maxLength = 2000;
169  double segmentLength = 0;
170 
171  points.push_back( point );
172 
173  for(Points::const_iterator it = _bSplinePoints.begin() + 1; it != _bSplinePoints.end(); ++it)
174  {
175  dx = abs((*it).first - point.first); // theoretically positive
176  dy = abs((*it).second - point.second);
177 
178  segmentLength += sqrt( dx * dx + dy * dy );
179 
180  if(segmentLength > maxLength)
181  {
182  points.push_back( *it );
183  segmentLength = 0;
184  }
185 
186  point = *it;
187  }
188 
189  points.push_back( _bSplinePoints.back() );
190 
191  OSLM_TRACE("B-Spline points size moved from " << _bSplinePoints.size() << " to " << points.size());
192 
193  return points;
194 }
195 
196 //----------------------------------------------------------------------------------------------------------
197 
198 void SCurvedHistogram::computePointToPathLengthMapFromBSplinePoints(Points& _bSplinePoints )
199 {
200  Points::iterator it = _bSplinePoints.begin();
201 
202  if( it != _bSplinePoints.end())
203  {
204  Point p;
205 
206  p = this->mapAdaptorToScene( *it, m_xAxis, m_yAxis );
207  QPointF prevPt = QPointF(p.first, p.second);
208  m_painterPath->lineTo( p.first, p.second );
209  qreal len = m_painterPath->length();
210  ++it;
211 
212  for(; it != _bSplinePoints.end(); ++it)
213  {
214  p = this->mapAdaptorToScene( *it, m_xAxis, m_yAxis );
215 
216  m_painterPath->lineTo( p.first, p.second );
217 
218  // This is way too slow as the complexity is O(N.log(N) )
219  //m_positionsToPathLength[ (int) p.first ] = m_painterPath->length();
220 
221  QPointF pt(p.first, p.second);
222  len += QLineF( prevPt, pt ).length();
223  m_positionsToPathLength[ static_cast<int>( p.first ) ] = len;
224 
225  prevPt = pt;
226  }
227  }
228 }
229 
230 //----------------------------------------------------------------------------------------------------------
231 
233 {
234  this->stopping();
235 
236  const ::fwData::Histogram::csptr histogram = this->getInput< ::fwData::Histogram>(s_HISTOGRAM_INPUT);
237 
238  ::fwData::mt::ObjectReadLock lock(histogram);
239 
240  m_layer = new QGraphicsItemGroup();
241 
242  m_painterPath = new QPainterPath();
243 
244  if (!histogram->getValues().empty())
245  {
246  Points controlPoints = this->getControlPoints( histogram );
247  Points bSplinePoints = this->getBSplinePoints( controlPoints );
248 
249  this->computePointToPathLengthMapFromBSplinePoints( bSplinePoints );
250 
251  // Try to remove unnecessary points of the B-Spline points
252  Points resampledBSplinePoints = this->getResampledBSplinePoints( bSplinePoints );
253  bSplinePoints = resampledBSplinePoints;
254 
255  this->buildBSplineFromPoints( bSplinePoints );
256 
257  // Adjust the layer's position and zValue depending on the associated axis
258  m_layer->setPos(static_cast<qreal>( m_xAxis->getOrigin() ), static_cast<qreal>( m_yAxis->getOrigin() ));
259  m_layer->setZValue(static_cast<qreal>( m_zValue ));
260 
261  // Add to the scene the unique item which gather the whole set of rectangle graphic items:
262  this->getScene2DRender()->getScene()->addItem( m_layer );
263  }
264 }
265 
266 //----------------------------------------------------------------------------------------------------------
267 
268 void SCurvedHistogram::buildBSplineFromPoints(Points& _bSplinePoints )
269 {
270  const ::fwData::Histogram::csptr histogram = this->getInput< ::fwData::Histogram>(s_HISTOGRAM_INPUT);
271 
272  const bool useBorderColor = (m_borderColor.color() != Qt::transparent);
273  const bool useInnerColor = (m_innerColor.color() != Qt::transparent);
274 
275  Point currentPoint = this->mapAdaptorToScene( Point(histogram->getMinValue(), _bSplinePoints[0].second),
276  m_xAxis, m_yAxis );
277  Point previousPoint = currentPoint;
278  Points::iterator it;
279 
280  const QPointF startPoint( currentPoint.first, currentPoint.second / 10 ); // divide by 10 to cut meaningless
281  // values
282  QPainterPath path( QPointF(startPoint.x(), 0.0) );
283  path.lineTo( startPoint );
284 
285  previousPoint.first = startPoint.x();
286  previousPoint.second = startPoint.y();
287 
288  // Build the path with the B-Spline points
289  for(it = _bSplinePoints.begin() + 1; it != _bSplinePoints.end(); ++it)
290  {
291  currentPoint = this->mapAdaptorToScene(*it, m_xAxis, m_yAxis );
292 
293  path.lineTo( currentPoint.first, currentPoint.second );
294  }
295 
296  // Close the path:
297  m_painterPath->lineTo( static_cast<qreal>( histogram->getMaxValue() ), _bSplinePoints.back().second);
298 
299  if( useBorderColor )
300  {
301  path.lineTo( currentPoint.first, 0.0 );
302  this->addBorderItem( path );
303  }
304 
305  if( useInnerColor )
306  {
307  path.lineTo( previousPoint.first, 0.0 );
308  this->addInnerItem( path );
309  }
310 }
311 
312 //----------------------------------------------------------------------------------------------------------
313 
314 void SCurvedHistogram::addInnerItem( const QPainterPath& _path )
315 {
316  QGraphicsPathItem* item = new QGraphicsPathItem( _path );
317  item->setPen( Qt::NoPen );
318  item->setBrush( m_brush );
319  item->setPath( _path );
320  item->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
321  item->setZValue(1);
322 
323  m_layer->addToGroup( item );
324 }
325 
326 //----------------------------------------------------------------------------------------------------------
327 
328 void SCurvedHistogram::addBorderItem( const QPainterPath& _path )
329 {
330  QGraphicsPathItem* item = new QGraphicsPathItem( _path );
331  item->setPen( m_borderColor );
332  item->setBrush( Qt::NoBrush );
333  item->setPath( _path );
334  item->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
335  item->setZValue(2);
336 
337  m_layer->addToGroup( item );
338 }
339 
340 //----------------------------------------------------------------------------------------------------------
341 
342 SCurvedHistogram::Points SCurvedHistogram::quadraticInterpolation(
343  const Point _p0, const Point _p1, const Point _p2 )
344 {
345  Points points;
346  Point p;
347 
348  const double d2 =
349  2 * ((_p2.second - _p1.second) / (_p2.first - _p1.first) - (_p1.second - _p0.second)
350  / (_p1.first - _p0.first))
351  / (_p2.first - _p0.first);
352 
353  const double d1 = (_p2.second - _p1.second) / (_p2.first - _p1.first) - 0.5 * d2 * (_p2.first - _p1.first);
354  const double d0 = _p1.second;
355 
356  points.push_back( _p0 );
357 
358  for(double x = _p0.first; x < _p2.first; x += 0.5)
359  {
360  p.first = x;
361  p.second = 0.5 * d2 * pow(x - _p1.first, 2) + d1 * (x - _p1.first) + d0;
362 
363  points.push_back( p );
364  }
365 
366  return points;
367 }
368 
369 //----------------------------------------------------------------------------------------------------------
370 
371 SCurvedHistogram::Points SCurvedHistogram::cosinusInterpolation(const Point _p0, const Point _p1)
372 {
373  Points points;
374  Point p;
375  double mu2;
376  const double deltaX = _p1.first - _p0.first;
377 
378  for(double mu = 0.0; mu < 1.0; mu += 0.05)
379  {
380  mu2 = (1 - std::cos(mu * PI)) / 2;
381 
382  p.first = _p0.first + mu * deltaX;
383  p.second = _p0.second * (1 - mu2) + _p1.second * mu2;
384 
385  points.push_back( p );
386  }
387 
388  return points;
389 }
390 
391 //----------------------------------------------------------------------------------------------------------
392 
393 SCurvedHistogram::Points SCurvedHistogram::cubicInterpolation(
394  const Point _p0, const Point _p1, const Point _p2, const Point _p3 )
395 {
396  Points points;
397  Point p;
398  double a0, a1, a2, a3, mu2;
399  const double deltaX = _p2.first - _p1.first;
400  for(double mu = 0.0; mu < 1.0; mu += 0.01)
401  {
402  mu2 = mu * mu;
403 
404  /*
405  a0 = _p3.second - _p2.second - _p0.second + _p1.second;
406  a1 = _p0.second - _p1.second - a0;
407  a2 = _p2.second - _p0.second;
408  a3 = _p1.second;
409  */
410 
411  // Smoother curves (Catmull-Rom s_plines)
412  a0 = -0.5*_p0.second + 1.5*_p1.second - 1.5*_p2.second + 0.5*_p3.second;
413  a1 = _p0.second - 2.5*_p1.second + 2*_p2.second - 0.5*_p3.second;
414  a2 = -0.5*_p0.second + 0.5*_p2.second;
415  a3 = _p1.second;
416 
417  p.first = _p1.first + mu * deltaX;
418  p.second = a0 * mu * mu2 +a1 * mu2 + a2 * mu + a3;
419 
420  points.push_back( p );
421  }
422 
423  return points;
424 }
425 
426 //----------------------------------------------------------------------------------------------------------
427 
428 void SCurvedHistogram::updateCurrentPoint(const ::fwRenderQt::data::Event& _event, const ::fwData::Point::sptr& point )
429 {
430  const ::fwData::Histogram::csptr histogram = this->getInput< ::fwData::Histogram>(s_HISTOGRAM_INPUT);
431  const ::fwData::Histogram::fwHistogramValues values = histogram->getValues();
432 
433  const float histogramMinValue = histogram->getMinValue();
434  const float histogramBinsWidth = histogram->getBinsWidth();
435 
436  // Event coordinates in scene
437  const ::fwRenderQt::data::Coord sceneCoord = this->getScene2DRender()->mapToScene( _event.getCoord() );
438 
439  const int histIndex = static_cast<int>( sceneCoord.getX() );
440  const int index = static_cast<const int>(histIndex - histogramMinValue);
441  const int nbValues = static_cast<int>(values.size() * histogramBinsWidth);
442 
443  if(index >= 0 && index < nbValues && m_positionsToPathLength.find( histIndex ) != m_positionsToPathLength.end())
444  {
445  const double key = m_positionsToPathLength[ histIndex ];
446  const double percent = m_painterPath->percentAtLength( key );
447  const QPointF qPoint = m_painterPath->pointAtPercent( percent );
448 
449  point->getCoord()[0] = sceneCoord.getX();
450  point->getCoord()[1] = qPoint.y() * static_cast<double>(m_scale);
451  }
452 }
453 
454 //---------------------------------------------------------------------------------------------------------
455 
456 SCurvedHistogram::Points SCurvedHistogram::linearInterpolation( const Point _p1, const Point _p2 )
457 {
458  Points points;
459  float t = 0.f;
460  Point p;
461  for(int i = 0; i < 100; ++i)
462  {
463  t = i / 100;
464 
465  p.first = _p1.first + ( _p2.first - _p1.first ) * static_cast<double>(t);
466  p.second = _p1.second + ( _p2.second - _p1.second ) * static_cast<double>(t);
467  points.push_back( p );
468  }
469 
470  return points;
471 }
472 
473 //----------------------------------------------------------------------------------------------------------
474 
476 {
477  if (m_layer)
478  {
479  this->getScene2DRender()->getScene()->removeItem(m_layer);
480  delete m_layer;
481  m_layer = nullptr;
482  }
483  m_positionsToPathLength.clear();
484  if (m_painterPath)
485  {
486  delete m_painterPath;
487  m_painterPath = nullptr;
488  }
489 }
490 
491 //----------------------------------------------------------------------------------------------------------
492 
494 {
495  bool updatePointedPos = false;
496 
497  // Vertical scaling
498  if( _event.getType() == ::fwRenderQt::data::Event::MouseWheelUp )
499  {
500  m_scale *= SCALE;
501  m_layer->setTransform(QTransform::fromScale(1, static_cast<qreal>(SCALE) ), true);
502 
503  updatePointedPos = true;
504  }
505  else if( _event.getType() == ::fwRenderQt::data::Event::MouseWheelDown )
506  {
507  m_scale /= SCALE;
508  m_layer->setTransform(QTransform::fromScale(1, 1 / static_cast<qreal>(SCALE) ), true);
509 
510  updatePointedPos = true;
511  }
512  else if( _event.getType() == ::fwRenderQt::data::Event::MouseMove )
513  {
514  updatePointedPos = true;
515  }
516 
517  ::fwData::Point::sptr point = this->getInOut< ::fwData::Point>( s_POINT_INOUT );
518  if( point && updatePointedPos )
519  {
520  this->updateCurrentPoint( _event, point );
521  }
522 }
523 
524 //----------------------------------------------------------------------------------------------------------
525 
527 {
528  KeyConnectionsMap connections;
529  connections.push( "histogram", ::fwData::Histogram::s_MODIFIED_SIG, s_UPDATE_SLOT );
530  return connections;
531 }
532 
533 } // namespace adaptor
534 } // namespace scene2D
Root class for all scene2d adaptors.
static const float SCALE
Ratio used for vertical scaling (default value: 1.1)
::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
static const float NB_POINTS_BEZIER
The number of points between to points of the final Bezier curve to compute.
FWRENDERQT_API Point2DType mapAdaptorToScene(const Point2DType &_xy, const ::fwRenderQt::data::Axis::sptr &_xAxis, const ::fwRenderQt::data::Axis::sptr &_yAxis) const
SCENE2D_API::fwServices::IService::KeyConnectionsMap getAutoConnections() const override
Returns proposals to connect service slots to associated object signals, this method is used for obj/...
FWRENDERQT_API std::shared_ptr< ::fwRenderQt::SRender > getScene2DRender() const
Get the render that manages the IAdaptor.
A helper to lock object on read mode.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
static FWRENDERQT_API void setPenColor(QPen &_pen, std::string _color)
Set a pen a color.
Definition: InitQtPen.cpp:18
SCENE2D_API void processInteraction(::fwRenderQt::data::Event &_event) override
SCENE2D_API void starting() override
Initialize the service activity.
This bundles contains data and services used to display a 2D Qt scene.
SCENE2D_API void configuring() override
Configure the service before starting. Apply the configuration to service.
SCENE2D_API void updating() override
Perform some computations according to object (this service is attached to) attribute values and its ...
::fwRenderQt::data::Axis::sptr m_yAxis
The y Axis.
IAdaptor implementation for histogram data.
float m_opacity
Opacity of the adaptor. Default value set to 1 (opaque).
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 stopping() override
Uninitialize the service activity. The stop() method is always invoked before destroying a service...
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
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247