fw4spl
WindowLevel.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 "uiImageQt/WindowLevel.hpp"
8 
9 #include <fwCom/Signal.hpp>
10 #include <fwCom/Signal.hxx>
11 #include <fwCom/Signals.hpp>
12 
13 #include <fwCore/base.hpp>
14 
15 #include <fwData/Composite.hpp>
16 #include <fwData/Image.hpp>
17 #include <fwData/TransferFunction.hpp>
18 
19 #include <fwDataTools/fieldHelper/Image.hpp>
20 #include <fwDataTools/fieldHelper/MedicalImageHelpers.hpp>
21 #include <fwDataTools/helper/Composite.hpp>
22 
23 #include <fwGuiQt/container/QtContainer.hpp>
24 #include <fwGuiQt/widget/QRangeSlider.hpp>
25 
26 #include <fwRuntime/operations.hpp>
27 
28 #include <fwServices/macros.hpp>
29 
30 #include <boost/math/special_functions/fpclassify.hpp>
31 
32 #include <QApplication>
33 #include <QComboBox>
34 #include <QDoubleValidator>
35 #include <QGridLayout>
36 #include <QLabel>
37 #include <QLineEdit>
38 #include <QMenu>
39 #include <QSignalMapper>
40 #include <QToolButton>
41 #include <QWidget>
42 
43 #include <functional>
44 
45 namespace uiImageQt
46 {
47 
48 fwServicesRegisterMacro( ::fwGui::editor::IEditor, ::uiImageQt::WindowLevel, ::fwData::Image );
49 
50 static const ::fwServices::IService::KeyType s_IMAGE_INOUT = "image";
51 static const ::fwServices::IService::KeyType s_TF_INOUT = "tf";
52 
53 //------------------------------------------------------------------------------
54 
56  m_widgetDynamicRangeMin(-1024),
57  m_widgetDynamicRangeWidth(4000),
58  m_autoWindowing(false),
59  m_enableSquareTF(true)
60 {
61  this->installTFSlots(this);
62 }
63 
64 //------------------------------------------------------------------------------
65 
67 {
68 }
69 
70 //------------------------------------------------------------------------------
71 
73 {
74  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
75  SLM_ASSERT("inout '" + s_IMAGE_INOUT + "' is not defined.", image);
76 
77  this->create();
78  ::fwGuiQt::container::QtContainer::sptr qtContainer = ::fwGuiQt::container::QtContainer::dynamicCast(
79  this->getContainer() );
80 
81  QGridLayout* layout = new QGridLayout();
82 
83  m_valueTextMin = new QLineEdit();
84  QDoubleValidator* minValidator = new QDoubleValidator(m_valueTextMin);
85  m_valueTextMin->setValidator(minValidator);
86 
87  m_valueTextMax = new QLineEdit();
88  QDoubleValidator* maxValidator = new QDoubleValidator(m_valueTextMax);
89  m_valueTextMax->setValidator(maxValidator);
90 
91  m_rangeSlider = new ::fwGuiQt::widget::QRangeSlider();
92 
93  m_toggleTFButton = new QToolButton();
94  QIcon ico;
95  std::string squareIcon(::fwRuntime::getBundleResourceFilePath("uiImageQt", "square.png").string());
96  std::string rampIcon(::fwRuntime::getBundleResourceFilePath("uiImageQt", "ramp.png").string());
97  ico.addPixmap(QPixmap(QString::fromStdString(squareIcon)), QIcon::Normal, QIcon::On);
98  ico.addPixmap(QPixmap(QString::fromStdString(rampIcon)), QIcon::Normal, QIcon::Off);
99  m_toggleTFButton->setIcon(ico);
100  m_toggleTFButton->setCheckable(true);
101  m_toggleTFButton->setVisible(m_enableSquareTF);
102 
103  m_toggleAutoButton = new QToolButton();
104  QIcon icon;
105  std::string windo(::fwRuntime::getBundleResourceFilePath("uiImageQt", "windowing.svg").string());
106  icon.addFile(QString::fromStdString(windo), QSize(), QIcon::Normal, QIcon::On);
107  std::string nowindo(::fwRuntime::getBundleResourceFilePath("uiImageQt", "nowindowing.svg").string());
108  icon.addFile(QString::fromStdString(nowindo), QSize(), QIcon::Normal, QIcon::Off);
109  m_toggleAutoButton->setIcon(icon);
110  m_toggleAutoButton->setToolTip("Automatic Windowing");
111  m_toggleAutoButton->setCheckable(true);
112  m_toggleAutoButton->setChecked(m_autoWindowing);
113 
114  m_dynamicRangeSelection = new QToolButton();
115  m_dynamicRangeSelection->setPopupMode(QToolButton::InstantPopup);
116 
117  m_dynamicRangeMenu = new QMenu(m_dynamicRangeSelection);
118  QAction* action1 = m_dynamicRangeMenu->addAction( "-1024; 1023" );
119  QAction* action2 = m_dynamicRangeMenu->addAction( "-100; 300" );
120  QAction* action3 = m_dynamicRangeMenu->addAction( "Fit W/L" );
121  QAction* action4 = m_dynamicRangeMenu->addAction( "Fit Data" ); // TODO
122  //QAction *action5 = m_dynamicRangeMenu->addAction( "Custom ..." ); // TODO
123  m_dynamicRangeSelection->setMenu(m_dynamicRangeMenu);
124 
125  action1->setData(QVariant(1));
126  action2->setData(QVariant(2));
127  action3->setData(QVariant(3));
128  action4->setData(QVariant(4));
129  //action5->setData(QVariant(5));
130 
131  layout->addWidget( m_rangeSlider, 0, 0, 1, -1 );
132  layout->addWidget( m_valueTextMin, 1, 0 );
133  layout->addWidget( m_toggleTFButton, 1, 1 );
134  layout->addWidget( m_toggleAutoButton, 1, 2 );
135  layout->addWidget( m_dynamicRangeSelection, 1, 3 );
136  layout->addWidget( m_valueTextMax, 1, 4 );
137 
138  qtContainer->setLayout( layout );
139 
140  m_dynamicRangeSignalMapper = new QSignalMapper(this);
141 
142  QObject::connect(m_valueTextMin, SIGNAL(editingFinished()), this, SLOT(onTextEditingFinished()));
143  QObject::connect(m_valueTextMax, SIGNAL(editingFinished()), this, SLOT(onTextEditingFinished()));
144  QObject::connect(m_rangeSlider, SIGNAL(sliderRangeEdited(double,double)), this,
145  SLOT(onWindowLevelWidgetChanged(double,double)));
146  QObject::connect(m_toggleTFButton, SIGNAL(toggled(bool)), this, SLOT(onToggleTF(bool)));
147  QObject::connect(m_toggleAutoButton, SIGNAL(toggled(bool)), this, SLOT(onToggleAutoWL(bool)));
148  QObject::connect(m_dynamicRangeSelection, SIGNAL(triggered(QAction*)), this,
149  SLOT(onDynamicRangeSelectionChanged(QAction*)));
150 
151  ::fwData::TransferFunction::sptr tf = this->getInOut < ::fwData::TransferFunction >(s_TF_INOUT);
152  this->setOrCreateTF(tf, image);
153 
154  this->updating();
155 }
156 
157 //------------------------------------------------------------------------------
158 
160 {
161  this->removeTFConnections();
162  QObject::disconnect(m_dynamicRangeSelection, SIGNAL(triggered(QAction*)), this,
163  SLOT(onDynamicRangeSelectionChanged(QAction*)));
164  QObject::disconnect(m_toggleTFButton, SIGNAL(toggled(bool)), this, SLOT(onToggleTF(bool)));
165  QObject::disconnect(m_rangeSlider, SIGNAL(sliderRangeEdited(double,double)), this,
166  SLOT(onWindowLevelWidgetChanged(double,double)));
167  QObject::disconnect(m_valueTextMin, SIGNAL(editingFinished()), this,
168  SLOT(onTextEditingFinished()));
169  QObject::disconnect(m_valueTextMax, SIGNAL(editingFinished()), this,
170  SLOT(onTextEditingFinished()));
171 
172  this->destroy();
173 }
174 
175 //------------------------------------------------------------------------------
176 
178 {
179  this->initialize();
180 
181  const ConfigType srvConfig = this->getConfigTree();
182 
183  if (srvConfig.count("config.<xmlattr>"))
184  {
185  const ConfigType config = srvConfig.get_child("config.<xmlattr>");
186 
187  const std::string autoWindowing = config.get("autoWindowing", "no");
188  SLM_ASSERT("Bad value for 'autoWindowing' attribute. It must be 'yes' or 'no'!",
189  autoWindowing == "yes" || autoWindowing == "no");
190  m_autoWindowing = (autoWindowing == "yes");
191 
192  const std::string enableSquareTF = config.get("enableSquareTF", "yes");
193  SLM_ASSERT("Bad value for 'enableSquareTF' attribute. It must be 'yes' or 'no'!",
194  enableSquareTF == "yes" || enableSquareTF == "no");
195  m_enableSquareTF = (enableSquareTF == "yes");
196  }
197 }
198 
199 //------------------------------------------------------------------------------
200 
202 {
203  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
204  SLM_ASSERT("inout '" + s_IMAGE_INOUT + "' is not defined.", image);
205 
207  this->setEnabled(imageIsValid);
208 
209  if(imageIsValid)
210  {
211  this->updateImageInfos(image);
212 
213  if(m_autoWindowing)
214  {
215  double min, max;
217  this->updateImageWindowLevel(min, max);
218  }
219 
220  ::fwData::TransferFunction::sptr pTF = this->getTransferFunction();
221  SLM_ASSERT("TransferFunction null pointer", pTF);
222  ::fwData::TransferFunction::TFValuePairType minMax = pTF->getWLMinMax();
223  this->onImageWindowLevelChanged( minMax.first, minMax.second );
224  }
225 }
226 
227 //------------------------------------------------------------------------------
228 
229 void WindowLevel::swapping(const KeyType& key)
230 {
231  if (key == s_TF_INOUT)
232  {
233  ::fwData::TransferFunction::sptr tf = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
234 
235  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
236  SLM_ASSERT("Missing image", image);
237 
238  this->setOrCreateTF(tf, image);
239 
240  this->updating();
241  }
242 }
243 
244 //------------------------------------------------------------------------------
245 
247 {
248  this->updating();
249 }
250 
251 //------------------------------------------------------------------------------
252 
253 void WindowLevel::updateTFWindowing(double /*window*/, double /*level*/)
254 {
255  ::fwData::TransferFunction::sptr pTF = this->getTransferFunction();
256  SLM_ASSERT("TransferFunction null pointer", pTF);
257  ::fwData::TransferFunction::TFValuePairType minMax = pTF->getWLMinMax();
258  this->onImageWindowLevelChanged( minMax.first, minMax.second );
259 }
260 
261 //------------------------------------------------------------------------------
262 
263 void WindowLevel::info( std::ostream& _sstream )
264 {
265  _sstream << "Window level editor";
266 }
267 
268 //------------------------------------------------------------------------------
269 
270 WindowLevel::WindowLevelMinMaxType WindowLevel::getImageWindowMinMax()
271 {
272  ::fwData::TransferFunction::sptr pTF = this->getTransferFunction();
273  SLM_ASSERT("TransferFunction null pointer", pTF);
274 
275  return pTF->getWLMinMax();
276 }
277 
278 //------------------------------------------------------------------------------
279 void WindowLevel::updateWidgetMinMax(double _imageMin, double _imageMax)
280 {
281  double rangeMin = this->fromWindowLevel(_imageMin);
282  double rangeMax = this->fromWindowLevel(_imageMax);
283 
284  m_rangeSlider->setPos(rangeMin, rangeMax);
285 }
286 
287 //------------------------------------------------------------------------------
288 
289 double WindowLevel::fromWindowLevel(double val)
290 {
291  double valMin = m_widgetDynamicRangeMin;
292  double valMax = valMin + m_widgetDynamicRangeWidth;
293 
294  valMin = std::min(val, valMin);
295  valMax = std::max(val, valMax);
296 
297  this->setWidgetDynamicRange(valMin, valMax);
298 
299  double res = (val - m_widgetDynamicRangeMin) / m_widgetDynamicRangeWidth;
300  return res;
301 }
302 
303 //------------------------------------------------------------------------------
304 
305 double WindowLevel::toWindowLevel(double _val)
306 {
307  return m_widgetDynamicRangeMin + m_widgetDynamicRangeWidth * _val;
308 }
309 
310 //------------------------------------------------------------------------------
311 
312 void WindowLevel::updateImageWindowLevel(double _imageMin, double _imageMax)
313 {
314  ::fwData::TransferFunction::sptr tf = this->getTransferFunction();
315 
316  this->getTransferFunction()->setWLMinMax( ::fwData::TransferFunction::TFValuePairType(_imageMin,
317  _imageMax) );
320  {
321  ::fwCom::Connection::Blocker block(sig->getConnection(m_slotUpdateTFWindowing));
322  sig->asyncEmit( tf->getWindow(), tf->getLevel());
323  }
324 }
325 
326 //------------------------------------------------------------------------------
327 
328 void WindowLevel::onWindowLevelWidgetChanged(double _min, double _max)
329 {
330  double imageMin = this->toWindowLevel(_min);
331  double imageMax = this->toWindowLevel(_max);
332  this->updateImageWindowLevel(imageMin, imageMax);
333  this->updateTextWindowLevel(imageMin, imageMax);
334 }
335 
336 //------------------------------------------------------------------------------
337 
338 void WindowLevel::onDynamicRangeSelectionChanged(QAction* action)
339 {
340  WindowLevelMinMaxType wl = this->getImageWindowMinMax();
341  double min = m_widgetDynamicRangeMin;
342  double max = m_widgetDynamicRangeWidth + min;
343  int index = action->data().toInt();
344 
345  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
346  SLM_ASSERT("inout '" + s_IMAGE_INOUT + "' is not defined.", image);
347 
348  switch (index)
349  {
350  case 0:
351  break;
352  case 1: // -1024; 1023
353  min = -1024;
354  max = 1023;
355  break;
356  case 2: // -100; 300
357  min = -100;
358  max = 300;
359  break;
360  case 3: // Fit Window/Level
361  min = std::min(wl.first, wl.second);
362  max = std::max(wl.first, wl.second);
363  break;
364  case 4: // Fit Image Range
366  break;
367  case 5: // Custom : TODO
368  break;
369  default:
370  SLM_ASSERT("Unknown range selector index", 0);
371  }
372 
373  this->setWidgetDynamicRange(min, max);
374  this->updateWidgetMinMax(wl.first, wl.second);
375 }
376 
377 //------------------------------------------------------------------------------
378 
379 void WindowLevel::onImageWindowLevelChanged(double _imageMin, double _imageMax)
380 {
381  this->updateWidgetMinMax( _imageMin, _imageMax );
382  this->updateTextWindowLevel( _imageMin, _imageMax );
383 }
384 
385 //------------------------------------------------------------------------------
386 
387 void WindowLevel::updateTextWindowLevel(double _imageMin, double _imageMax)
388 {
389  m_valueTextMin->setText(QString("%1").arg(_imageMin));
390  m_valueTextMax->setText(QString("%1").arg(_imageMax));
391 }
392 
393 //------------------------------------------------------------------------------
394 
395 void WindowLevel::onToggleTF(bool squareTF)
396 {
397  ::fwData::TransferFunction::sptr currentTF = this->getTransferFunction();
398  ::fwData::TransferFunction::sptr newTF;
399 
400  if( squareTF )
401  {
402  newTF = ::fwData::TransferFunction::New();
403  ::fwData::TransferFunction::TFColor color(1., 1., 1., 1.);
404  newTF->setName("SquareTF");
405  newTF->addTFColor(0.0, color);
406  newTF->addTFColor(1.0, color);
407  newTF->setIsClamped(true);
408  }
409  else
410  {
411  if( m_previousTF )
412  {
413  newTF = m_previousTF;
414  }
415  else
416  {
417  newTF = ::fwData::TransferFunction::createDefaultTF();
418  }
419  }
420 
421  newTF->setWindow( currentTF->getWindow() );
422  newTF->setLevel( currentTF->getLevel() );
423 
424  m_previousTF = ::fwData::Object::copy(currentTF);
425 
426  currentTF->deepCopy(newTF);
427 
428  // Send signal
429  auto sig = currentTF->signal< ::fwData::TransferFunction::PointsModifiedSignalType >(
431  {
432  ::fwCom::Connection::Blocker block(sig->getConnection(m_slotUpdateTFPoints));
433  sig->asyncEmit();
434  }
435 }
436 
437 //------------------------------------------------------------------------------
438 
439 void WindowLevel::onToggleAutoWL(bool autoWL)
440 {
441  m_autoWindowing = autoWL;
442 
443  if (m_autoWindowing)
444  {
445  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
446  SLM_ASSERT("inout '" + s_IMAGE_INOUT + "' is not defined.", image);
447  double min, max;
449  this->updateImageWindowLevel(min, max);
450  this->onImageWindowLevelChanged(min, max);
451  }
452 }
453 
454 //------------------------------------------------------------------------------
455 
456 void WindowLevel::onTextEditingFinished()
457 {
458  double min, max;
459  if(this->getWidgetDoubleValue(m_valueTextMin, min) && this->getWidgetDoubleValue(m_valueTextMax, max))
460  {
461  this->updateWidgetMinMax( min, max );
462  this->updateImageWindowLevel(min, max);
463  }
464 }
465 
466 //------------------------------------------------------------------------------
467 
468 bool WindowLevel::getWidgetDoubleValue(QLineEdit* widget, double& val)
469 {
470  bool ok = false;
471  val = widget->text().toDouble(&ok);
472 
473  QPalette palette;
474  if (!ok)
475  {
476  palette.setBrush(QPalette::Base, QBrush(Qt::red));
477  }
478  else
479  {
480  palette.setBrush(QPalette::Base, QApplication::palette().brush(QPalette::Base));
481  }
482  widget->setPalette(palette);
483  return ok;
484 }
485 
486 //------------------------------------------------------------------------------
487 
488 void WindowLevel::setWidgetDynamicRange(double min, double max)
489 {
490  if(fabs(max - min) < 1.e-05)
491  {
492  max = min + 1.e-05;
493  }
494  m_widgetDynamicRangeMin = min;
495  m_widgetDynamicRangeWidth = max - min;
496 
497  m_dynamicRangeSelection->setText(QString("%1, %2 ").arg(min).arg(max));
498 }
499 
500 //------------------------------------------------------------------------------
501 
503 {
504  KeyConnectionsMap connections;
505  connections.push( s_IMAGE_INOUT, ::fwData::Image::s_MODIFIED_SIG, s_UPDATE_SLOT );
506  connections.push( s_IMAGE_INOUT, ::fwData::Image::s_BUFFER_MODIFIED_SIG, s_UPDATE_SLOT );
507 
508  return connections;
509 }
510 
511 //------------------------------------------------------------------------------
512 
513 }
This class is a helper to define the connections of a service and its data.
Definition: IService.hpp:454
FWDATATOOLS_API::fwData::TransferFunction::sptr getTransferFunction() const
Get the current transfer function.
virtual UIIMAGEQT_API void updateTFPoints() override
Slot: Updates the slider position.
FWGUI_API void setEnabled(bool isEnabled)
SLOT: enable/disable the container.
FWDATATOOLS_API void installTFSlots(::fwCom::HasSlots *hasslots)
Install the slots to managed TF modifications.
virtual void configuring() override
Parse the xml configuration.
virtual void updating() override
Update editor information from the image.
Class allowing to block a Connection.
Definition: Connection.hpp:20
virtual UIIMAGEQT_API ~WindowLevel() noexcept
Destructor. Do nothing.
Definition: WindowLevel.cpp:66
Defines the service interface managing the editor service for object.
Definition: IEditor.hpp:25
FWGUI_API void destroy()
Stops sub-views and toobar services. Destroys view, sub-views and toolbar containers.
virtual UIIMAGEQT_API void info(std::ostream &_sstream) override
Overrides.
virtual void starting() override
Install the layout.
Definition: WindowLevel.cpp:72
virtual void swapping()
Swap the service from associated object to another object.
Definition: IService.hpp:613
UpdateTFWindowingSlotType::sptr m_slotUpdateTFWindowing
Slot called when transfer function windowing is modified.
The namespace uiImageQt contains several editors on image written with Qt. This namespace is included...
Definition: ImageInfo.hpp:23
virtual void stopping() override
Destroy the layout.
FWDATATOOLS_API void updateImageInfos(::fwData::Image::sptr image)
Update the image information (slice index, min/max,...)
#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
static FWDATA_API::fwData::Object::sptr copy(const ::fwData::Object::csptr &source)
return a copy of the source. if source is a null pointer, return a null pointer.
FWGUI_API void create()
Creates view, sub-views and toolbar containers. Manages sub-views and toobar services.
WindowLevel service allows to change the min / max value of windowing.
Definition: WindowLevel.hpp:74
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_BUFFER_MODIFIED_SIG
Type of signal when image&#39;s buffer is added.
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_MODIFIED_SIG
Key in m_signals map of signal m_sigModified.
FWDATATOOLS_API void removeTFConnections()
Remove the TF connections.
static FWDATATOOLS_API bool checkImageValidity(::fwData::Image::csptr _pImg)
Check if the image is valid.
FWRUNTIME_API::boost::filesystem::path getBundleResourceFilePath(const std::string &bundleIdentifier, const ::boost::filesystem::path &path) noexcept
Retrieve a filesystem valid path for a path relative to the bundle having the specified identifier...
Definition: operations.cpp:106
UIIMAGEQT_API WindowLevel() noexcept
Constructor. Do nothing.
Definition: WindowLevel.cpp:55
This class defines an image.
UpdateTFPointsSlotType::sptr m_slotUpdateTFPoints
Slot called when transfer function points are modified.
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_WINDOWING_MODIFIED_SIG
Type of signal when points are modified.
FWDATATOOLS_API void setOrCreateTF(const ::fwData::TransferFunction::sptr &_tf, const fwData::Image::sptr &_image)
Sets the transfer function, creates one if _tf is null (.
static void getMinMax(const ::fwData::Image::csptr _img, MINMAXTYPE &_min, MINMAXTYPE &_max)
Return minimum and maximum values contained in image. If image min or max value is out of MINMAXTYPE ...
virtual UIIMAGEQT_API KeyConnectionsMap getAutoConnections() const override
Returns proposals to connect service slots to associated object signals, this method is used for obj/...
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.
FWGUI_API void initialize()
Initialize managers.
virtual UIIMAGEQT_API void updateTFWindowing(double window, double level) override
Slot: Updates the slider position.
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247