fw4spl
SVolume.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 "visuVTKVRAdaptor/SVolume.hpp"
8 
9 #include <fwCom/Slot.hxx>
10 #include <fwCom/Slots.hxx>
11 
12 #include <fwData/TransferFunction.hpp>
13 
14 #include <fwDataTools/fieldHelper/MedicalImageHelpers.hpp>
15 
16 #include <fwServices/macros.hpp>
17 
18 #include <fwVtkIO/vtk.hpp>
19 
20 #include <vtkBoundingBox.h>
21 #include <vtkBoxRepresentation.h>
22 #include <vtkBoxWidget2.h>
23 #include <vtkColorTransferFunction.h>
24 #include <vtkCommand.h>
25 #include <vtkImageImport.h>
26 #include <vtkImageResample.h>
27 #include <vtkPiecewiseFunction.h>
28 #include <vtkPlaneCollection.h>
29 #include <vtkRenderer.h>
30 #include <vtkRenderWindow.h>
31 #include <vtkRenderWindowInteractor.h>
32 #include <vtkSmartVolumeMapper.h>
33 #include <vtkTransform.h>
34 #include <vtkVolume.h>
35 #include <vtkVolumeProperty.h>
36 
37 fwServicesRegisterMacro( ::fwRenderVTK::IAdaptor, ::visuVTKVRAdaptor::SVolume);
38 
39 //-----------------------------------------------------------------------------
40 
41 namespace visuVTKVRAdaptor
42 {
43 
44 class TransformCallback : public ::vtkCommand
45 {
46 public:
47 
48  //------------------------------------------------------------------------------
49 
50  static TransformCallback* New(SVolume* adaptor)
51  {
53  cb->m_adaptor = adaptor;
54  return cb;
55  }
56 
57  //------------------------------------------------------------------------------
58 
59  virtual void Execute( ::vtkObject* caller, unsigned long, void* )
60  {
61  m_adaptor->updateCropBoxTransform();
62  m_adaptor->crop();
63  }
64 
65 private:
66  SVolume* m_adaptor;
67 };
68 
69 //------------------------------------------------------------------------------
70 
71 class AbortCallback : public vtkCommand
72 {
73 public:
74 
75  //------------------------------------------------------------------------------
76 
77  static AbortCallback* New()
78  {
79  return new AbortCallback();
80  }
81 
82  //------------------------------------------------------------------------------
83 
84  virtual void Execute( vtkObject* caller, unsigned long eventId, void*)
85  {
86  vtkRenderWindow* win = vtkRenderWindow::SafeDownCast(caller);
87  if ( win )
88  {
89  if( win->GetEventPending() )
90  {
91  win->SetAbortRender(1);
92  }
93  }
94  }
95 };
96 
97 //------------------------------------------------------------------------------
98 
99 class CroppingCallback : public vtkCommand
100 {
101 public:
102 
103  //------------------------------------------------------------------------------
104 
105  static CroppingCallback* New(SVolume* adaptor)
106  {
107  CroppingCallback* callback = new CroppingCallback();
108  callback->m_adaptor = adaptor;
109  return callback;
110  }
111 
112  //------------------------------------------------------------------------------
113 
114  virtual void Execute( vtkObject* caller, unsigned long eventId, void*)
115  {
116  m_adaptor->crop();
117  m_adaptor->updateTransform();
118  }
119 
120 private:
121  SVolume* m_adaptor;
122 };
123 
124 static const ::fwCom::Slots::SlotKeyType s_RESET_BOX_WIDGET_SLOT = "resetBoxWidget";
125 static const ::fwCom::Slots::SlotKeyType s_ACTIVATE_BOX_CLIPPING_SLOT = "activateBoxClipping";
126 static const ::fwCom::Slots::SlotKeyType s_SHOW_SLOT = "show";
127 
128 const ::fwServices::IService::KeyType SVolume::s_IMAGE_INOUT = "image";
129 const ::fwServices::IService::KeyType SVolume::s_TF_INOUT = "tf";
130 
131 //------------------------------------------------------------------------------
132 
133 SVolume::SVolume() noexcept :
136  m_clippingPlanes(nullptr),
137  m_volumeMapper( vtkSmartVolumeMapper::New()),
138  m_volumeProperty(vtkVolumeProperty::New()),
139  m_volume(vtkVolume::New()),
140  m_opacityTransferFunction(vtkPiecewiseFunction::New()),
141  m_colorTransferFunction(vtkColorTransferFunction::New()),
142  m_abortCommand(AbortCallback::New()),
143  m_boxWidget(vtkBoxWidget2::New()),
144  m_croppingCommand(nullptr),
145  m_transformCommand(nullptr),
146  m_croppingBoxDefaultState(true),
147  m_cropBoxTransform(nullptr),
148  m_autoResetCamera(true),
149  m_reductionFactor(1.0),
150  m_blendMode("composite")
151 {
152  m_boxWidget->KeyPressActivationOff();
153  m_boxWidget->SetRotationEnabled(0);
154  vtkBoxRepresentation* repr = vtkBoxRepresentation::New();
155  m_boxWidget->SetRepresentation(repr);
156  repr->Delete();
157 
158  newSlot(s_RESET_BOX_WIDGET_SLOT, &SVolume::resetBoxWidget, this);
159  newSlot(s_ACTIVATE_BOX_CLIPPING_SLOT, &SVolume::activateBoxClipping, this);
160  newSlot(s_SHOW_SLOT, &SVolume::show, this);
161 
162  this->installTFSlots(this);
163 }
164 
165 //------------------------------------------------------------------------------
166 
167 SVolume::~SVolume() noexcept
168 {
169  m_volumeMapper->Delete();
170  m_volumeMapper = nullptr;
171 
172  m_volume->Delete();
173  m_volume = nullptr;
174 
175  m_abortCommand->Delete();
176  m_abortCommand = nullptr;
177 
178  if (m_clippingPlanes)
179  {
180  m_clippingPlanes->Delete();
181  m_clippingPlanes = nullptr;
182  }
183 
184  m_boxWidget->Delete();
185  m_boxWidget = nullptr;
186 
187  m_opacityTransferFunction->Delete();
188  m_opacityTransferFunction = nullptr;
189 
190  m_colorTransferFunction->Delete();
191  m_colorTransferFunction = nullptr;
192 }
193 
194 //------------------------------------------------------------------------------
195 
196 void SVolume::setClippingPlanesId(::fwRenderVTK::SRender::VtkObjectIdType id)
197 {
198  m_clippingPlanesId = id;
199 }
200 
201 //------------------------------------------------------------------------------
202 
203 void SVolume::setVtkClippingPlanes(vtkPlaneCollection* planes)
204 {
205  m_clippingPlanes = planes;
206 }
207 
208 //------------------------------------------------------------------------------
209 
211 {
212  this->configureParams();
213 
214  const ConfigType config = this->getConfigTree().get_child("config.<xmlattr>");
215 
216  this->setClippingPlanesId( config.get<std::string>("clippingplanes", "") );
217 
218  m_autoResetCamera = config.get<std::string>("autoresetcamera", "yes") == "yes";
219 
220  // Show croppingBox
221  m_croppingBoxDefaultState = config.get<std::string>("croppingBox", "yes") == "yes";
222 
223  // Get the boundingBox transformation matrix
224  m_cropBoxTransformID = config.get<std::string>("cropBoxTransform", "");
225 
226  m_reductionFactor = config.get<double>("reductionFactor", 1.);
227 
228  // Blend Mode
229  m_blendMode = config.get<std::string>("blend", "composite");
230 
231 }
232 
233 //------------------------------------------------------------------------------
234 
236 {
237  this->initialize();
238 
239  ::fwData::TransferFunction::sptr tf = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
240  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
241  SLM_ASSERT("Missing image", image);
242 
243  this->setOrCreateTF(tf, image);
244 
245  this->addToRenderer(m_volume);
246 
247  this->getInteractor()->GetRenderWindow()->AddObserver("AbortCheckEvent", m_abortCommand);
248  this->updating(); //TODO: remove me ?
249 
250  this->activateBoxClipping( m_croppingBoxDefaultState );
251 
252  if(!m_cropBoxTransformID.empty())
253  {
254  m_cropBoxTransform = vtkTransform::SafeDownCast( m_renderService.lock()->getVtkObject(m_cropBoxTransformID));
255  }
256 
257  if(m_cropBoxTransform)
258  {
259  m_transformCommand = TransformCallback::New(this);
260  m_cropBoxTransform->AddObserver( ::vtkCommand::ModifiedEvent, m_transformCommand );
261 
262  vtkBoxRepresentation* repr = vtkBoxRepresentation::SafeDownCast( m_boxWidget->GetRepresentation() );
263  repr->SetTransform(m_cropBoxTransform);
264 
265  this->crop();
266  }
267 
268  m_croppingCommand = CroppingCallback::New(this);
269  m_boxWidget->AddObserver(vtkCommand::InteractionEvent, m_croppingCommand);
270 
271  if (!this->getTransformId().empty())
272  {
273  m_volume->SetUserTransform(this->getTransform());
274  }
275 }
276 
277 //------------------------------------------------------------------------------
278 
280 {
281  this->removeTFConnections();
282  this->removeAllPropFromRenderer();
283  this->getInteractor()->GetRenderWindow()->RemoveObserver(m_abortCommand);
284  m_boxWidget->RemoveObserver(m_croppingCommand);
285 
286  m_croppingCommand->Delete();
287  m_croppingCommand = nullptr;
288 
289  if(m_cropBoxTransform)
290  {
291  m_cropBoxTransform->RemoveObserver( m_transformCommand );
292  m_transformCommand->Delete();
293  m_transformCommand = nullptr;
294  }
295 }
296 
297 //------------------------------------------------------------------------------
298 
300 {
301  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
303 
304  if (imageIsValid)
305  {
306  this->buildPipeline();
307  this->updateImage(image);
308  this->updateVolumeTransferFunction(image);
309  this->requestRender();
310  }
311 }
312 
313 //------------------------------------------------------------------------------
314 
315 void SVolume::swapping(const KeyType& key)
316 {
317  if (key == s_TF_INOUT)
318  {
319  ::fwData::TransferFunction::sptr tf = this->getInOut< ::fwData::TransferFunction >(s_TF_INOUT);
320  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
321  SLM_ASSERT("Missing image", image);
322 
323  this->setOrCreateTF(tf, image);
324  this->updating();
325  }
326 }
327 
328 //------------------------------------------------------------------------------
329 
331 {
332  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
333  this->updateVolumeTransferFunction(image);
334  this->requestRender();
335 }
336 
337 //------------------------------------------------------------------------------
338 
339 void SVolume::updateTFWindowing(double /*window*/, double /*level*/)
340 {
341  ::fwData::Image::sptr image = this->getInOut< ::fwData::Image >(s_IMAGE_INOUT);
342  this->updateVolumeTransferFunction(image);
343  this->requestRender();
344 }
345 
346 //------------------------------------------------------------------------------
347 
348 void SVolume::updateImage( ::fwData::Image::sptr image )
349 {
350  this->updateImageInfos(image);
351 
352  vtkImageImport* imageImport = vtkImageImport::New();
353  ::fwVtkIO::configureVTKImageImport( imageImport, image );
354 
355  m_volumeMapper->RemoveAllClippingPlanes();
356  if (m_clippingPlanes)
357  {
358  m_volumeMapper->SetClippingPlanes(m_clippingPlanes);
359  }
360 
361  if ( m_reductionFactor < 1.0 )
362  {
363  vtkImageResample* resample = vtkImageResample::New();
364  resample->SetInputConnection( imageImport->GetOutputPort() );
365  resample->SetAxisMagnificationFactor(0, m_reductionFactor);
366  resample->SetAxisMagnificationFactor(1, m_reductionFactor);
367  resample->SetAxisMagnificationFactor(2, m_reductionFactor);
368  m_volumeMapper->SetInputConnection(resample->GetOutputPort());
369  resample->Delete();
370  }
371  else
372  {
373  m_volumeMapper->SetInputConnection(imageImport->GetOutputPort());
374  }
375 
376  m_boxWidget->GetRepresentation()->SetPlaceFactor(1.0);
377  m_boxWidget->GetRepresentation()->PlaceWidget(m_volumeMapper->GetBounds());
378  m_boxWidget->SetInteractor(this->getInteractor());
379 
380  vtkVolumeMapper::SafeDownCast(m_volumeMapper)->CroppingOn();
381  vtkVolumeMapper::SafeDownCast(m_volumeMapper)->SetCroppingRegionPlanes( m_volumeMapper->GetBounds() );
382 
383  imageImport->Delete();
384 
385  if (m_autoResetCamera)
386  {
387  this->getRenderer()->ResetCamera();
388  }
389 
390  this->setVtkPipelineModified();
391 }
392 
393 //------------------------------------------------------------------------------
394 
395 void SVolume::updateVolumeTransferFunction( ::fwData::Image::sptr image )
396 {
397  ::fwData::TransferFunction::sptr pTF = this->getTransferFunction();
398  SLM_ASSERT("TransferFunction null pointer", pTF);
399 
400  m_colorTransferFunction->RemoveAllPoints();
401  m_opacityTransferFunction->RemoveAllPoints();
402 
403  ::fwData::TransferFunction::TFValueVectorType values = pTF->getScaledValues();
404  ::fwData::TransferFunction::TFValueVectorType::iterator valueIter = values.begin();
405  if(pTF->getInterpolationMode() == ::fwData::TransferFunction::NEAREST)
406  {
407  m_colorTransferFunction->AllowDuplicateScalarsOn();
408  m_opacityTransferFunction->AllowDuplicateScalarsOn();
409 
410  for(const ::fwData::TransferFunction::TFDataType::value_type& tfPoint : pTF->getTFData())
411  {
412  const ::fwData::TransferFunction::TFValueType& value = *valueIter;
413  ::fwData::TransferFunction::TFValueType valuePrevious = *valueIter;
414  ::fwData::TransferFunction::TFValueType valueNext = *valueIter;
415  if(valueIter != values.begin())
416  {
417  valuePrevious = *(valueIter - 1);
418  }
419  if(valueIter != (values.end()-1))
420  {
421  valueNext = *(valueIter + 1);
422  }
423 
424  const ::fwData::TransferFunction::TFColor& color = tfPoint.second;
425 
426  m_colorTransferFunction->AddRGBPoint(valuePrevious + (value - valuePrevious) / 2., color.r, color.g,
427  color.b );
428  m_colorTransferFunction->AddRGBPoint(value + (valueNext - value) / 2., color.r, color.g, color.b );
429 
430  m_opacityTransferFunction->AddPoint(valuePrevious + (value -valuePrevious) / 2., color.a );
431  m_opacityTransferFunction->AddPoint(value + (valueNext - value) / 2., color.a );
432 
433  ++valueIter;
434  }
435  }
436  else
437  {
438  for(const ::fwData::TransferFunction::TFDataType::value_type& tfPoint : pTF->getTFData())
439  {
440  const ::fwData::TransferFunction::TFValueType& value = *(valueIter++);
441  const ::fwData::TransferFunction::TFColor& color = tfPoint.second;
442 
443  m_colorTransferFunction->AddRGBPoint( value, color.r, color.g, color.b );
444  m_opacityTransferFunction->AddPoint( value, color.a );
445  }
446  }
447 
448  m_colorTransferFunction->SetClamping(!pTF->getIsClamped());
449  m_opacityTransferFunction->SetClamping(!pTF->getIsClamped());
450 
451  if(m_blendMode == "average")
452  {
453  //use the TF windowing min and max values to set up the average blend range
454  std::pair< double, double > averageRange = this->getTransferFunction()->getWLMinMax();
455  m_volumeMapper->SetAverageIPScalarRange(averageRange.first, averageRange.second);
456  }
457 
458  this->setVtkPipelineModified();
459 
460 }
461 
462 //------------------------------------------------------------------------------
463 
464 void SVolume::buildPipeline( )
465 {
466  if (!m_clippingPlanesId.empty())
467  {
468  vtkObject* o = this->getVtkObject(m_clippingPlanesId);
469  vtkPlaneCollection* planes = vtkPlaneCollection::SafeDownCast(o);
470  this->setVtkClippingPlanes( planes );
471  }
472 
473  m_volumeProperty->SetScalarOpacity(m_opacityTransferFunction);
474  m_volumeProperty->SetColor(m_colorTransferFunction);
475 
476  m_volumeProperty->ShadeOn();
477  m_volumeProperty->SetInterpolationTypeToLinear();
478 
479  m_volumeProperty->SetAmbient( 0.2 );
480  m_volumeProperty->SetDiffuse( 1.0 );
481  m_volumeProperty->SetSpecular( 1.0 );
482  m_volumeProperty->SetSpecularPower( 10.0 );
483 
484  // set the mapper according to the blendMode
485  if(m_blendMode == "min")
486  {
487  m_volumeMapper->SetBlendModeToMinimumIntensity();
488  }
489  else if(m_blendMode == "max")
490  {
491  m_volumeMapper->SetBlendModeToMaximumIntensity();
492  }
493  else if(m_blendMode == "average")
494  {
495  m_volumeMapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
496  m_volumeMapper->SetBlendModeToAverageIntensity();
497  }
498  else if(m_blendMode == "additive")
499  {
500  m_volumeMapper->SetBlendModeToAdditive();
501 
502  }
503  else if(m_blendMode == "composite")
504  {
505  m_volumeMapper->SetBlendModeToComposite();
506  }
507  else
508  {
509  OSLM_WARN("blend mode '"<< m_blendMode <<"' is unknown. Should be min, max, average, composite or additive.");
510  }
511 
512  m_volume->SetMapper(m_volumeMapper);
513  m_volume->SetProperty(m_volumeProperty);
514 
515  this->setVtkPipelineModified();
516 }
517 
518 //------------------------------------------------------------------------------
519 
521 {
522  m_boxWidget->GetRepresentation()->SetPlaceFactor(1.0);
523  m_boxWidget->GetRepresentation()->PlaceWidget( m_volumeMapper->GetBounds() );
524  vtkVolumeMapper::SafeDownCast( m_volumeMapper )->SetCroppingRegionPlanes( m_volumeMapper->GetBounds() );
525  if (m_autoResetCamera)
526  {
527  this->getRenderer()->ResetCamera();
528  }
529  this->setVtkPipelineModified();
530  this->requestRender();
531 }
532 
533 //------------------------------------------------------------------------------
534 
535 void SVolume::activateBoxClipping( bool activate )
536 {
537  if ( activate )
538  {
539  m_boxWidget->On();
540  }
541  else
542  {
543  m_boxWidget->Off();
544  }
545  this->setVtkPipelineModified();
546  this->requestRender();
547 }
548 
549 //------------------------------------------------------------------------------
550 
552 {
553  KeyConnectionsMap connections;
554  connections.push(s_IMAGE_INOUT, ::fwData::Image::s_MODIFIED_SIG, s_UPDATE_SLOT);
555  connections.push(s_IMAGE_INOUT, ::fwData::Image::s_BUFFER_MODIFIED_SIG, s_UPDATE_SLOT);
556 
557  return connections;
558 }
559 
560 //------------------------------------------------------------------------------
561 
563 {
564  vtkVolumeMapper* mapper = vtkVolumeMapper::SafeDownCast(m_volumeMapper);
565  double* croppingRegionPlanes = m_boxWidget->GetRepresentation()->GetBounds();
566 
567  vtkBoundingBox boundingBoxCrop(croppingRegionPlanes);
568  vtkBoundingBox boundingBoxVolume(mapper->GetBounds());
569 
570  if(boundingBoxCrop.Intersects(boundingBoxVolume))
571  {
572  mapper->SetCroppingRegionPlanes( croppingRegionPlanes );
573  }
574  else
575  {
576  mapper->SetCroppingRegionPlanes(0., 0., 0., 0., 0., 0.);
577  }
578 }
579 
580 //------------------------------------------------------------------------------
581 
583 {
584  if(m_cropBoxTransform)
585  {
586  vtkBoxRepresentation* repr = vtkBoxRepresentation::SafeDownCast( m_boxWidget->GetRepresentation() );
587  if( repr )
588  {
589  m_cropBoxTransform->RemoveObserver(m_transformCommand);
590  repr->GetTransform(m_cropBoxTransform);
591  m_cropBoxTransform->Modified();
592  m_cropBoxTransform->AddObserver(vtkCommand::ModifiedEvent, m_transformCommand);
593  }
594  }
595 }
596 
597 //------------------------------------------------------------------------------
598 
600 {
601  if(m_cropBoxTransform)
602  {
603  vtkBoxRepresentation* repr = vtkBoxRepresentation::SafeDownCast( m_boxWidget->GetRepresentation() );
604  if( repr )
605  {
606  m_boxWidget->RemoveObserver(m_croppingCommand);
607  repr->SetTransform(m_cropBoxTransform);
608  m_boxWidget->AddObserver(vtkCommand::InteractionEvent, m_croppingCommand);
609  }
610  }
611 }
612 
613 //------------------------------------------------------------------------------
614 
615 void SVolume::show(bool isVisible)
616 {
617  m_volume->SetVisibility(isVisible);
618  this->setVtkPipelineModified();
619  this->requestRender();
620 }
621 
622 //------------------------------------------------------------------------------
623 
624 } //namespace visuVTKVRAdaptor
VISUVTKVRADAPTOR_API void starting() override
Initialize the service activity.
Definition: SVolume.cpp:235
VISUVTKVRADAPTOR_API void updateTransform()
Update associated transform adaptor with CropBox transform.
Definition: SVolume.cpp:582
This class is a helper to define the connections of a service and its data.
Definition: IService.hpp:454
virtual VISUVTKVRADAPTOR_API void updateTFPoints() override
Slot: updates the volume transfer function.
Definition: SVolume.cpp:330
VISUVTKVRADAPTOR_API void crop()
Apply the cropping on volume rendering.
Definition: SVolume.cpp:562
This adaptor displays a volume image.
Definition: SVolume.hpp:67
virtual void swapping()
Swap the service from associated object to another object.
Definition: IService.hpp:613
virtual VISUVTKVRADAPTOR_API void updateTFWindowing(double window, double level) override
Slot: updates the volume transfer function.
Definition: SVolume.cpp:339
void resetBoxWidget()
Slot: reset the clipping box widget around the volume.
Definition: SVolume.cpp:520
void activateBoxClipping(bool activate)
Slot: show/hide clipping box.
Definition: SVolume.cpp:535
#define OSLM_WARN(message)
Definition: spyLog.hpp:263
#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
virtual VISUVTKVRADAPTOR_API KeyConnectionsMap getAutoConnections() const override
Returns proposals to connect service slots to associated object signals, this method is used for obj/...
Definition: SVolume.cpp:551
VISUVTKVRADAPTOR_API void stopping() override
Uninitialize the service activity. The stop() method is always invoked before destroying a service...
Definition: SVolume.cpp:279
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.
static FWDATATOOLS_API bool checkImageValidity(::fwData::Image::csptr _pImg)
Check if the image is valid.
VISUVTKVRADAPTOR_API void updateCropBoxTransform()
Update CropBox transform with transform adaptor.
Definition: SVolume.cpp:599
VISUVTKVRADAPTOR_API void updating() override
Perform some computations according to object (this service is attached to) attribute values and its ...
Definition: SVolume.cpp:299
void show(bool isVisible)
Slot: show/hide the volume.
Definition: SVolume.cpp:615
VISUVTKVRADAPTOR_API void configuring() override
Configure the service before starting. Apply the configuration to service.
Definition: SVolume.cpp:210