fw4spl
SSliceIndexDicomEditor.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 "ioDicom/SSliceIndexDicomEditor.hpp"
8 
9 #include <fwCom/Signal.hpp>
10 #include <fwCom/Signal.hxx>
11 #include <fwCom/Signals.hpp>
12 #include <fwCom/Slot.hpp>
13 #include <fwCom/Slots.hpp>
14 #include <fwCom/Slots.hxx>
15 
16 #include <fwData/Array.hpp>
17 #include <fwData/Composite.hpp>
18 #include <fwData/Image.hpp>
19 #include <fwData/Integer.hpp>
20 
21 #include <fwDataTools/fieldHelper/Image.hpp>
22 #include <fwDataTools/helper/Array.hpp>
23 #include <fwDataTools/helper/Composite.hpp>
24 
25 #include <fwGui/dialog/MessageDialog.hpp>
26 
27 #include <fwGuiQt/container/QtContainer.hpp>
28 
29 #include <fwMedData/DicomSeries.hpp>
30 #include <fwMedData/ImageSeries.hpp>
31 #include <fwMedData/SeriesDB.hpp>
32 
33 #include <fwMedDataTools/helper/SeriesDB.hpp>
34 
35 #include <fwServices/macros.hpp>
36 #include <fwServices/registry/ActiveWorkers.hpp>
37 #include <fwServices/registry/ObjectService.hpp>
38 
39 #include <fwThread/Timer.hpp>
40 
41 #include <fwTools/System.hpp>
42 
43 #include <boost/asio/placeholders.hpp>
44 #include <boost/filesystem/fstream.hpp>
45 #include <boost/filesystem/operations.hpp>
46 #include <boost/foreach.hpp>
47 
48 #include <QApplication>
49 #include <QComboBox>
50 #include <QHBoxLayout>
51 #include <QMouseEvent>
52 
53 #include <iterator>
54 
55 namespace ioDicom
56 {
57 
59 
60 const ::fwCom::Slots::SlotKeyType SSliceIndexDicomEditor::s_READ_IMAGE_SLOT = "readImage";
61 const ::fwCom::Slots::SlotKeyType SSliceIndexDicomEditor::s_DISPLAY_MESSAGE_SLOT = "displayErrorMessage";
62 
63 //------------------------------------------------------------------------------
64 
66  m_delay(500)
67 {
68  m_slotReadImage = newSlot(s_READ_IMAGE_SLOT, &SSliceIndexDicomEditor::readImage, this);
69  newSlot(s_DISPLAY_MESSAGE_SLOT, &SSliceIndexDicomEditor::displayErrorMessage, this);
70 }
71 //------------------------------------------------------------------------------
72 
74 {
75 }
76 
77 //------------------------------------------------------------------------------
78 
80 {
83 
84  ::fwRuntime::ConfigurationElement::sptr config = m_configuration->findConfigurationElement("config");
85  SLM_ASSERT("The service ::ioDicom::SSliceIndexDicomEditor must have "
86  "a \"config\" element.", config);
87 
88  bool success;
89 
90  // Reader
91  ::boost::tie(success, m_dicomReaderType) = config->getSafeAttributeValue("dicomReader");
92  SLM_ASSERT("It should be a \"dicomReader\" tag in the ::ioDicom::SSliceIndexDicomEditor "
93  "config element.", success);
94 
95  // Reader configuration
96  ::fwRuntime::ConfigurationElement::sptr readerConfig = config->findConfigurationElement("dicomReaderConfig");
97  m_readerConfig =
98  (readerConfig && readerConfig->size() == 1) ? readerConfig->getElements()[0] : nullptr;
99 
100  // Delay
101  std::string delayStr;
102  ::boost::tie(success, delayStr) = config->getSafeAttributeValue("delay");
103  if(success)
104  {
105  m_delay = ::boost::lexical_cast< std::size_t >(delayStr);
106  }
107 }
108 
109 //------------------------------------------------------------------------------
110 
112 {
113  m_delayTimer2 = m_associatedWorker->createTimer();
114 
116  ::fwGuiQt::container::QtContainer::sptr qtContainer = fwGuiQt::container::QtContainer::dynamicCast(getContainer());
117 
118  QHBoxLayout* layout = new QHBoxLayout();
119 
120  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInput< ::fwMedData::DicomSeries >("series");
121  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
122  m_numberOfSlices = dicomSeries->getNumberOfInstances();
123 
124  // Slider
125  m_sliceIndexSlider = new QSlider(Qt::Horizontal);
126  layout->addWidget(m_sliceIndexSlider, 1);
127  m_sliceIndexSlider->setRange(0, static_cast<int>(m_numberOfSlices-1));
128  m_sliceIndexSlider->setValue(static_cast<int>(m_numberOfSlices/2));
129 
130  // Line Edit
131  m_sliceIndexLineEdit = new QLineEdit();
132  layout->addWidget(m_sliceIndexLineEdit, 0);
133  m_sliceIndexLineEdit->setReadOnly(true);
134  m_sliceIndexLineEdit->setMaximumWidth(80);
135 
136  std::stringstream ss;
137  ss << m_sliceIndexSlider->value() << " / " << (m_numberOfSlices-1);
138  m_sliceIndexLineEdit->setText(std::string(ss.str()).c_str());
139 
140  qtContainer->setLayout(layout);
141 
142  // Connect the signals
143  QObject::connect(m_sliceIndexSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSliceIndex(int)));
144 
145  // Create temporary SeriesDB
146  m_tempSeriesDB = ::fwMedData::SeriesDB::New();
147 
148  // Create reader
149  ::fwServices::registry::ServiceFactory::sptr srvFactory = ::fwServices::registry::ServiceFactory::getDefault();
150 
151  ::fwIO::IReader::sptr dicomReader;
152  dicomReader = ::fwIO::IReader::dynamicCast(srvFactory->create(m_dicomReaderType));
153  SLM_ASSERT("Unable to create a reader of type: \"" + m_dicomReaderType + "\" in "
154  "::ioDicom::SSliceIndexDicomEditor.", dicomReader);
155  ::fwServices::OSR::registerService(m_tempSeriesDB, ::fwIO::s_DATA_KEY,
156  ::fwServices::IService::AccessType::INOUT, dicomReader);
157 
158  if(m_readerConfig)
159  {
160  dicomReader->setConfiguration(m_readerConfig);
161  }
162 
163  dicomReader->configure();
164  dicomReader->start();
165 
166  m_dicomReader = dicomReader;
167 
168  // Image Indecies
169  m_axialIndex = ::fwData::Integer::New(0);
170  m_frontalIndex = ::fwData::Integer::New(0);
171  m_sagittalIndex = ::fwData::Integer::New(0);
172 
173  // Load a slice
174  std::chrono::milliseconds duration = std::chrono::milliseconds(m_delay);
175  m_delayTimer2->setFunction( [ = ]()
176  {
177  this->triggerNewSlice();
178  } );
179  m_delayTimer2->setDuration(duration);
180  m_delayTimer2->setOneShot(true);
181 
182  this->triggerNewSlice();
183 
184 }
185 
186 //------------------------------------------------------------------------------
187 
189 {
190  SLM_TRACE_FUNC();
191 
192  // Stop dicom reader
193  if(!m_dicomReader.expired())
194  {
195  m_dicomReader.lock()->stop();
196  ::fwServices::OSR::unregisterService(m_dicomReader.lock());
197  }
198 
199  // Disconnect the signals
200  QObject::disconnect(m_sliceIndexSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSliceIndex(int)));
201 
202  this->destroy();
203 }
204 
205 //------------------------------------------------------------------------------
206 
208 {
209 }
210 
211 //------------------------------------------------------------------------------
212 
213 void SSliceIndexDicomEditor::info(std::ostream& _sstream )
214 {
215  _sstream << "SSliceIndexDicomEditor::info";
216 }
217 
218 //------------------------------------------------------------------------------
219 
220 void SSliceIndexDicomEditor::changeSliceIndex(int value)
221 {
222  // Update text
223  std::stringstream ss;
224  ss << m_sliceIndexSlider->value() << " / " << (m_numberOfSlices-1);
225  m_sliceIndexLineEdit->setText(std::string(ss.str()).c_str());
226 
227  // Get the new slice if there is no change for m_delay milliseconds
228  m_delayTimer2->start();
229 
230 }
231 
232 //------------------------------------------------------------------------------
233 
235 {
236  // DicomSeries
237  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInput< ::fwMedData::DicomSeries >("series");
238  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
239 
240  // Compute slice index
241  size_t selectedSliceIndex = static_cast<size_t>(m_sliceIndexSlider->value()) +
242  dicomSeries->getFirstInstanceNumber();
243  OSLM_TRACE("triggered new slice : " << selectedSliceIndex);
244 
245  SLM_ERROR_IF("There is no instance available for selected slice index.",
246  !dicomSeries->isInstanceAvailable(selectedSliceIndex));
247 
248  if(dicomSeries->isInstanceAvailable(selectedSliceIndex))
249  {
250  m_slotReadImage->asyncRun(selectedSliceIndex);
251  }
252 }
253 
254 //------------------------------------------------------------------------------
255 
256 void SSliceIndexDicomEditor::readImage(std::size_t selectedSliceIndex)
257 {
258  // DicomSeries
259  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInput< ::fwMedData::DicomSeries >("series");
260  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
261 
262  auto isModalitySupported = [](const ::fwMedData::Series::csptr& series )
263  {
264  return series->getModality() == "CT" ||
265  series->getModality() == "MR" ||
266  series->getModality() == "XA";
267  };
268 
269  if( !isModalitySupported(dicomSeries) )
270  {
271  return;
272  }
273 
274  // Clear temporary seriesDB
275  ::fwMedDataTools::helper::SeriesDB sDBTempohelper(m_tempSeriesDB);
276  sDBTempohelper.clear();
277 
278  // Creates unique temporary folder, no need to check if exists before (see ::fwTools::System::getTemporaryFolder)
279  ::boost::filesystem::path path = ::fwTools::System::getTemporaryFolder("dicom");
280  ::boost::filesystem::path tmpPath = path / "tmp";
281 
282  SLM_INFO("Create " + tmpPath.string());
283  ::boost::filesystem::create_directories(tmpPath);
284 
285  const auto& binaries = dicomSeries->getDicomContainer();
286  auto iter = binaries.find(selectedSliceIndex);
287  OSLM_ASSERT("Index '"<<selectedSliceIndex<<"' is not found in DicomSeries", iter != binaries.end());
288 
289  const ::fwMemory::BufferObject::sptr bufferObj = iter->second;
290  const ::fwMemory::BufferObject::Lock lockerDest(bufferObj);
291  const char* buffer = static_cast<char*>(lockerDest.getBuffer());
292  const size_t size = bufferObj->getSize();
293 
294  const ::boost::filesystem::path dest = tmpPath / std::to_string(selectedSliceIndex);
295  ::boost::filesystem::ofstream fs(dest, std::ios::binary|std::ios::trunc);
296  FW_RAISE_IF("Can't open '" << tmpPath << "' for write.", !fs.good());
297 
298  fs.write(buffer, static_cast<long>(size));
299  fs.close();
300 
301  // Read image
302  m_dicomReader.lock()->setFolder(tmpPath);
303  if(!m_dicomReader.expired())
304  {
305  m_dicomReader.lock()->update();
306 
307  if(m_dicomReader.expired() || m_dicomReader.lock()->isStopped())
308  {
309  return;
310  }
311  }
312  else
313  {
314  return;
315  }
316 
317  //Copy image
318  ::fwMedData::ImageSeries::sptr imageSeries;
319 
320  if(m_tempSeriesDB->getContainer().size() > 0)
321  {
322  auto series = *(m_tempSeriesDB->getContainer().begin());
323  if( isModalitySupported(series) )
324  {
325  imageSeries = ::fwMedData::ImageSeries::dynamicCast(series);
326  }
327  }
328 
329  if(imageSeries)
330  {
331  ::fwData::Image::sptr newImage = imageSeries->getImage();
332  ::fwData::Image::SizeType newSize = newImage->getSize();
333 
334  newImage->setField(::fwDataTools::fieldHelper::Image::m_axialSliceIndexId, m_axialIndex);
335  m_frontalIndex->setValue(static_cast< int >(newSize[0]/2));
336  newImage->setField(::fwDataTools::fieldHelper::Image::m_frontalSliceIndexId, m_frontalIndex);
337  m_sagittalIndex->setValue(static_cast< int >(newSize[1]/2));
338  newImage->setField(::fwDataTools::fieldHelper::Image::m_sagittalSliceIndexId, m_sagittalIndex);
339 
340  this->setOutput("image", newImage);
341  }
342 
343  ::boost::system::error_code ec;
344  ::boost::filesystem::remove_all(path, ec);
345  SLM_ERROR_IF("remove_all error for path " + path.string() + ": " + ec.message(), ec.value());
346 }
347 
348 //------------------------------------------------------------------------------
349 
350 void SSliceIndexDicomEditor::displayErrorMessage(const std::string& message) const
351 {
352  SLM_WARN("Error: " + message);
354  messageBox.setTitle("Error");
355  messageBox.setMessage( message );
356  messageBox.setIcon(::fwGui::dialog::IMessageDialog::CRITICAL);
357  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
358  messageBox.show();
359 }
360 
361 //------------------------------------------------------------------------------
362 
363 } // namespace ioDicom
IODICOM_API SSliceIndexDicomEditor() noexcept
Constructor.
#define OSLM_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:310
#define SLM_TRACE_FUNC()
Trace contextual function signature.
Definition: spyLog.hpp:329
virtual FWGUI_API void setMessage(const std::string &msg) override
Set the message.
Defines the service interface managing the editor service for object.
Definition: IEditor.hpp:25
IODICOM_API void info(std::ostream &_sstream) override
Override.
Defines the generic message box for IHM. Use the Delegate design pattern.
FWGUI_API void destroy()
Stops sub-views and toobar services. Destroys view, sub-views and toolbar containers.
IODICOM_API void triggerNewSlice()
Function called when a new slice must be displayed.
IODICOM_API void readImage(std::size_t selectedSliceIndex)
Read the selected image.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
Defines an helper to modify an fwMedData::SeriesDB and create in parallel the message to announce thi...
#define SLM_WARN(message)
Definition: spyLog.hpp:261
IODICOM_API void displayErrorMessage(const std::string &message) const
Display error message.
FWSERVICES_API void setOutput(const ::fwServices::IService::KeyType &key, const ::fwData::Object::sptr &object, size_t index=0)
Register an output object at a given key in the OSR, replacing it if it already exists.
Definition: IService.cpp:80
virtual FWGUI_API void addButton(IMessageDialog::Buttons button) override
Add a button (OK, YES_NO, YES, NO, CANCEL)
FWMEDDATATOOLS_API void clear()
Clear all series in the SeriesDB.
This editor service is used to select a slice index and read the corresponding image on the local com...
virtual FWGUI_API IMessageDialog::Buttons show() override
Show the message box and return the clicked button.
virtual IODICOM_API void configuring() override
Configuring method. This method is used to configure the service.
#define SLM_ERROR_IF(message, cond)
Definition: spyLog.hpp:276
ioDicom contains services used to deal with the DICOM standard.
#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 IODICOM_API void starting() override
Override.
::fwRuntime::ConfigurationElement::sptr m_configuration
Configuration element used to configure service internal state using a generic XML like structure TOD...
Definition: IService.hpp:670
static FWTOOLS_APIconst::boost::filesystem::path getTemporaryFolder(const std::string &subFolderPrefix="") noexcept
Returns a unique per-process temporary folder. The top level temporary folder will be automatically d...
Definition: System.cpp:148
FWGUI_API void create()
Creates view, sub-views and toolbar containers. Manages sub-views and toobar services.
virtual FWGUI_API void setIcon(IMessageDialog::Icons icon) override
Set the icon (CRITICAL, WARNING, INFO or QUESTION)
IODICOM_API void updating() override
Override.
virtual IODICOM_API ~SSliceIndexDicomEditor() noexcept
Destructor.
static FWSERVICES_API ServiceFactory::sptr getDefault()
Return the unique Instance, create it if required at first access.
std::shared_ptr< ::fwThread::Worker > m_associatedWorker
Associated worker.
Definition: IService.hpp:699
#define SLM_INFO(message)
Definition: spyLog.hpp:250
::fwData::Array::SizeType SizeType
Image size type.
virtual FWGUI_API void setTitle(const std::string &title) override
Set the title of the message box.
FWGUI_API void initialize()
Initialize managers.
virtual IODICOM_API void stopping() override
Override.