fw4spl
ioPacs/src/ioPacs/SSliceIndexDicomPullerEditor.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 "ioPacs/SSliceIndexDicomPullerEditor.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 <fwPacsIO/exceptions/Base.hpp>
36 
37 #include <fwServices/macros.hpp>
38 #include <fwServices/registry/ActiveWorkers.hpp>
39 #include <fwServices/registry/ObjectService.hpp>
40 
41 #include <fwThread/Timer.hpp>
42 
43 #include <fwTools/System.hpp>
44 
45 #include <boost/asio/placeholders.hpp>
46 #include <boost/filesystem/fstream.hpp>
47 #include <boost/filesystem/operations.hpp>
48 #include <boost/foreach.hpp>
49 
50 #include <QApplication>
51 #include <QComboBox>
52 #include <QHBoxLayout>
53 #include <QMouseEvent>
54 
55 #include <iterator>
56 
57 namespace ioPacs
58 {
59 
62 
63 const ::fwCom::Slots::SlotKeyType SSliceIndexDicomPullerEditor::s_READ_IMAGE_SLOT = "readImage";
64 const ::fwCom::Slots::SlotKeyType SSliceIndexDicomPullerEditor::s_DISPLAY_MESSAGE_SLOT = "displayErrorMessage";
65 
66 //------------------------------------------------------------------------------
67 
69  m_delay(500)
70 {
72  ::fwCom::HasSlots::m_slots(s_READ_IMAGE_SLOT, m_slotReadImage);
73 
75  ::fwCom::HasSlots::m_slots(s_DISPLAY_MESSAGE_SLOT, m_slotDisplayMessage);
76 
77  ::fwCom::HasSlots::m_slots.setWorker( m_associatedWorker );
78 }
79 //------------------------------------------------------------------------------
80 
82 {
83 }
84 
85 //------------------------------------------------------------------------------
86 
87 void SSliceIndexDicomPullerEditor::info(std::ostream& _sstream )
88 {
89  _sstream << "SSliceIndexDicomPullerEditor::info";
90 }
91 
92 //------------------------------------------------------------------------------
93 
95 {
97 
98  ::fwRuntime::ConfigurationElement::sptr config = m_configuration->findConfigurationElement("config");
99  SLM_ASSERT("The service ::ioPacs::SPacsConfigurationInitializer must have "
100  "a \"config\" element.", config);
101 
102  bool success;
103 
104  // Reader
105  ::boost::tie(success, m_dicomReaderType) = config->getSafeAttributeValue("dicomReader");
106  SLM_ASSERT("It should be a \"dicomReader\" tag in the ::ioPacs::SSliceIndexDicomPullerEditor "
107  "config element.", success);
108 
109  // Reader configuration
110  ::fwRuntime::ConfigurationElement::sptr readerConfig = config->findConfigurationElement("dicomReaderConfig");
112  (readerConfig && readerConfig->size() == 1) ? readerConfig->getElements()[0] : nullptr;
113 
114  // Delay
115  std::string delayStr;
116  ::boost::tie(success, delayStr) = config->getSafeAttributeValue("delay");
117  if(success)
118  {
119  m_delay = ::boost::lexical_cast< unsigned int >(delayStr);
120  }
121 }
122 
123 //------------------------------------------------------------------------------
124 
126 {
127  m_delayTimer2 = m_associatedWorker->createTimer();
128 
129  // Get pacs configuration
130  m_pacsConfiguration = this->getInput< ::fwPacsIO::data::PacsConfiguration>("pacsConfig");
131 
133  ::fwGuiQt::container::QtContainer::sptr qtContainer = fwGuiQt::container::QtContainer::dynamicCast(getContainer());
134 
135  QHBoxLayout* layout = new QHBoxLayout();
136 
137  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInOut< ::fwMedData::DicomSeries >("series");
138  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
139  m_numberOfSlices = dicomSeries->getNumberOfInstances();
140 
141  // Slider
142  m_sliceIndexSlider = new QSlider(Qt::Horizontal);
143  layout->addWidget(m_sliceIndexSlider, 1);
144  m_sliceIndexSlider->setRange(0, static_cast<unsigned int>(m_numberOfSlices-1));
145  m_sliceIndexSlider->setValue(static_cast<unsigned int>(m_numberOfSlices/2));
146 
147  // Line Edit
148  m_sliceIndexLineEdit = new QLineEdit();
149  layout->addWidget(m_sliceIndexLineEdit, 0);
150  m_sliceIndexLineEdit->setReadOnly(true);
151  m_sliceIndexLineEdit->setMaximumWidth(80);
152 
153  std::stringstream ss;
154  ss << m_sliceIndexSlider->value() << " / " << (m_numberOfSlices-1);
155  m_sliceIndexLineEdit->setText(std::string(ss.str()).c_str());
156 
157  qtContainer->setLayout(layout);
158 
159  // Connect the signals
160  QObject::connect(m_sliceIndexSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSliceIndex(int)));
161 
162  // Create temporary SeriesDB
163  m_tempSeriesDB = ::fwMedData::SeriesDB::New();
164 
165  // Create reader
166  ::fwServices::registry::ServiceFactory::sptr srvFactory = ::fwServices::registry::ServiceFactory::getDefault();
167 
168  ::fwIO::IReader::sptr dicomReader;
169  dicomReader = ::fwIO::IReader::dynamicCast(srvFactory->create(m_dicomReaderType));
170  SLM_ASSERT("Unable to create a reader of type: \"" + m_dicomReaderType + "\" in "
171  "::ioPacs::SSliceIndexDicomPullerEditor.", dicomReader);
172  ::fwServices::OSR::registerService(m_tempSeriesDB, ::fwIO::s_DATA_KEY,
173  ::fwServices::IService::AccessType::INOUT, dicomReader);
174  if(m_readerConfig)
175  {
176  dicomReader->setConfiguration(m_readerConfig);
177  }
178 
179  dicomReader->configure();
180  dicomReader->start();
181 
182  m_dicomReader = dicomReader;
183 
184  // Image Indecies
185  m_axialIndex = ::fwData::Integer::New(0);
186  m_frontalIndex = ::fwData::Integer::New(0);
187  m_sagittalIndex = ::fwData::Integer::New(0);
188 
189  // Worker
190  m_pullSeriesWorker = ::fwThread::Worker::New();
191 
192  // Create enquirer
193  m_seriesEnquirer = ::fwPacsIO::SeriesEnquirer::New();
194 
195  // Load a slice
196  std::chrono::milliseconds duration = std::chrono::milliseconds(m_delay);
197  m_delayTimer2->setFunction( [ = ]()
198  {
199  this->triggerNewSlice();
200  } );
201  m_delayTimer2->setDuration(duration);
202  m_delayTimer2->setOneShot(true);
203 
204  this->triggerNewSlice();
205 }
206 
207 //------------------------------------------------------------------------------
208 
210 {
211  // Worker
212  m_pullSeriesWorker->stop();
213  m_pullSeriesWorker.reset();
214 
215  // Stop dicom reader
216  if(!m_dicomReader.expired())
217  {
218  m_dicomReader.lock()->stop();
219  ::fwServices::OSR::unregisterService(m_dicomReader.lock());
220  }
221 
222  // Disconnect the signals
223  QObject::disconnect(m_sliceIndexSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSliceIndex(int)));
224 
225  this->destroy();
226 }
227 
228 //------------------------------------------------------------------------------
229 
231 {
232 }
233 
234 //------------------------------------------------------------------------------
235 
236 void SSliceIndexDicomPullerEditor::changeSliceIndex(int value)
237 {
238  // Update text
239  std::stringstream ss;
240  ss << m_sliceIndexSlider->value() << " / " << (m_numberOfSlices-1);
241  m_sliceIndexLineEdit->setText(std::string(ss.str()).c_str());
242 
243  // Get the new slice if there is no change for m_delay milliseconds
244  m_delayTimer2->start();
245 
246 }
247 
248 //------------------------------------------------------------------------------
249 
251 {
252  // DicomSeries
253  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInOut< ::fwMedData::DicomSeries >("series");
254  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
255 
256  // Compute slice index
257  std::size_t selectedSliceIndex = m_sliceIndexSlider->value() + dicomSeries->getFirstInstanceNumber();
258  OSLM_TRACE("triggered new slice : " << selectedSliceIndex);
259  if(!dicomSeries->isInstanceAvailable(selectedSliceIndex))
260  {
262  {
264  }
265  else
266  {
267  SLM_ERROR("There is no instance available for selected slice index.");
268  }
269  }
270  else
271  {
272  //m_slotReadImage->asyncRun(selectedSliceIndex);
273  this->readImage(selectedSliceIndex);
274  }
275 }
276 
277 //------------------------------------------------------------------------------
278 
279 void SSliceIndexDicomPullerEditor::readImage(std::size_t selectedSliceIndex)
280 {
281  // DicomSeries
282  ::fwMedData::DicomSeries::csptr dicomSeries = this->getInOut< ::fwMedData::DicomSeries >("series");
283  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
284  if( dicomSeries->getModality() != "CT" && dicomSeries->getModality() != "MR" && dicomSeries->getModality() != "XA")
285  {
286  return;
287  }
288 
289  // Clear temporary seriesDB
291  sDBTempohelper.clear();
292 
293  // Creates unique temporary folder, no need to check if exists before (see ::fwTools::System::getTemporaryFolder)
294  ::boost::filesystem::path path = ::fwTools::System::getTemporaryFolder("dicom");
295  ::boost::filesystem::path tmpPath = path / "tmp";
296 
297  SLM_INFO("Create " + tmpPath.string());
298  ::boost::filesystem::create_directories(tmpPath);
299 
300  const auto& binaries = dicomSeries->getDicomContainer();
301  auto iter = binaries.find(selectedSliceIndex);
302  OSLM_ASSERT("Index '"<<selectedSliceIndex<<"' is not found in DicomSeries", iter != binaries.end());
303 
304  const ::fwMemory::BufferObject::sptr bufferObj = iter->second;
305  const ::fwMemory::BufferObject::Lock lockerDest(bufferObj);
306  const char* buffer = static_cast<char*>(lockerDest.getBuffer());
307  const size_t size = bufferObj->getSize();
308 
309  ::boost::filesystem::path dest = tmpPath / std::to_string(selectedSliceIndex);
310  ::boost::filesystem::ofstream fs(dest, std::ios::binary|std::ios::trunc);
311  FW_RAISE_IF("Can't open '" << tmpPath << "' for write.", !fs.good());
312 
313  fs.write(buffer, size);
314  fs.close();
315 
316  // Read image
317 
318  m_dicomReader.lock()->setFolder(tmpPath);
319  if(!m_dicomReader.expired())
320  {
321  m_dicomReader.lock()->update();
322 
323  if(m_dicomReader.expired() || m_dicomReader.lock()->isStopped())
324  {
325  return;
326  }
327  }
328  else
329  {
330  return;
331  }
332 
333  //Copy image
334  ::fwMedData::ImageSeries::sptr imageSeries;
335 
336  if(m_tempSeriesDB->getContainer().size() > 0)
337  {
338  imageSeries = ::fwMedData::ImageSeries::dynamicCast(*(m_tempSeriesDB->getContainer().begin()));
339  }
340 
341  if(imageSeries)
342  {
343  ::fwData::Image::sptr newImage = imageSeries->getImage();
344  ::fwData::Image::SizeType newSize = newImage->getSize();
345 
347  m_frontalIndex->setValue(static_cast<int>(newSize[0]/2));
349  m_sagittalIndex->setValue(static_cast<int>(newSize[1]/2));
351 
352  this->setOutput("image", newImage);
353  }
354 
355  ::boost::system::error_code ec;
356  ::boost::filesystem::remove_all(path, ec);
357  SLM_ERROR_IF("remove_all error for path " + path.string() + ": " + ec.message(), ec.value());
358 }
359 
360 //------------------------------------------------------------------------------
361 
363 {
364  SLM_ASSERT("Pacs not configured.", m_pacsConfiguration);
365 
366  if( m_pacsConfiguration )
367  {
368  // Catch any errors
369  try
370  {
371  // DicomSeries
372  ::fwMedData::DicomSeries::sptr dicomSeries = this->getInOut< ::fwMedData::DicomSeries >("series");
373  SLM_ASSERT("DicomSeries should not be null !", dicomSeries);
374 
375  // Get selected slice
376  std::size_t selectedSliceIndex = m_sliceIndexSlider->value() + dicomSeries->getFirstInstanceNumber();
377 
378  m_seriesEnquirer->initialize(m_pacsConfiguration->getLocalApplicationTitle(),
379  m_pacsConfiguration->getPacsHostName(),
380  m_pacsConfiguration->getPacsApplicationPort(),
381  m_pacsConfiguration->getPacsApplicationTitle(),
382  m_pacsConfiguration->getMoveApplicationTitle());
383 
384  m_seriesEnquirer->connect();
385  std::string seriesInstanceUID = dicomSeries->getInstanceUID();
386  std::string sopInstanceUID =
387  m_seriesEnquirer->findSOPInstanceUID(seriesInstanceUID, static_cast<unsigned int>(selectedSliceIndex));
388 
389  // Check if an instance with the selected Instance Number is found on the PACS
390  if(!sopInstanceUID.empty())
391  {
392  // Pull Selected Series using C-GET Requests
393  m_seriesEnquirer->pullInstanceUsingGetRetrieveMethod(seriesInstanceUID, sopInstanceUID);
394 
395  // Add path and trigger reading
396  ::boost::filesystem::path path = ::fwTools::System::getTemporaryFolder() / "dicom/";
397  ::boost::filesystem::path filePath = path.string() + seriesInstanceUID + "/" + sopInstanceUID;
398  dicomSeries->addDicomPath(selectedSliceIndex, filePath);
399  //m_slotReadImage->asyncRun(selectedSliceIndex);
400  this->readImage(selectedSliceIndex);
401  }
402  else
403  {
404  std::stringstream ss;
405  ss << "The selected series does not have an instance matching the selected instance number (" <<
406  selectedSliceIndex << ").";
407  m_slotDisplayMessage->asyncRun(ss.str());
408  }
409 
410  // Close connection
411  m_seriesEnquirer->disconnect();
412 
413  }
414  catch (::fwPacsIO::exceptions::Base& exception)
415  {
416  std::stringstream ss;
417  ss << "Unable to connect to the pacs. Please check your configuration: \n"
418  << "Pacs host name: " << m_pacsConfiguration->getPacsHostName() << "\n"
419  << "Pacs application title: " << m_pacsConfiguration->getPacsApplicationTitle() << "\n"
420  << "Pacs port: " << m_pacsConfiguration->getPacsApplicationPort() << "\n";
421  m_slotDisplayMessage->asyncRun(ss.str());
422  SLM_WARN(exception.what());
423  }
424 
425  }
426  else
427  {
428  SLM_ERROR("Pacs pull aborted : no pacs configuration found.");
429  }
430 }
431 
432 //------------------------------------------------------------------------------
433 
434 void SSliceIndexDicomPullerEditor::displayErrorMessage(const std::string& message) const
435 {
436  SLM_WARN("Error: " + message);
438  messageBox.setTitle("Error");
439  messageBox.setMessage( message );
440  messageBox.setIcon(::fwGui::dialog::IMessageDialog::CRITICAL);
441  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
442  messageBox.show();
443 }
444 
445 } // namespace ioPacs
std::shared_ptr< ::fwData::Integer > m_frontalIndex
Frontal slice index.
#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
ioPacs contains services use to deal with PACS using DCMTK library.
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
Defines the generic message box for IHM. Use the Delegate design pattern.
IOPACS_API void displayErrorMessage(const std::string &message) const
Displays a dialog box with the error message.
FWGUI_API void destroy()
Stops sub-views and toobar services. Destroys view, sub-views and toolbar containers.
virtual IOPACS_API void configuring() override
Configuring method. This method is used to configure the service.
std::shared_ptr< ::fwData::Integer > m_sagittalIndex
Sagittal slice index.
DisplayMessageSlotType::sptr m_slotDisplayMessage
Slot to call displayErrorMessage method;.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
::fwPacsIO::SeriesEnquirer::sptr m_seriesEnquirer
Series enquirer.
IOPACS_API void info(std::ostream &_sstream) override
Override.
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
#define SLM_ERROR(message)
Definition: spyLog.hpp:272
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.
virtual FWGUI_API IMessageDialog::Buttons show() override
Show the message box and return the clicked button.
#define SLM_ERROR_IF(message, cond)
Definition: spyLog.hpp:276
#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
::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)
This editor service is used to select a slice index and pull the image from the pacs if it is not ava...
IOPACS_API void triggerNewSlice()
Function called when a new slice must be displayed.
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
std::shared_ptr< fwData::Integer > m_axialIndex
Axial slice index.
std::shared_ptr< ::fwThread::Timer > m_delayTimer2
Timer used to generate the new slice selection delay.
::fwPacsIO::data::PacsConfiguration::csptr m_pacsConfiguration
Pacs Configuration object.
IOPACS_API void readImage(std::size_t selectedSliceIndex)
Read the selected image.
virtual IOPACS_API ~SSliceIndexDicomPullerEditor() noexcept
Destructor.
::fwData::Array::SizeType SizeType
Image size type.
virtual FWGUI_API void setTitle(const std::string &title) override
Set the title of the message box.
std::shared_ptr< ::fwMedData::SeriesDB > m_tempSeriesDB
Temporary SeriesDB.
ReadImageSlotType::sptr m_slotReadImage
Slot to call readLocalSeries method.
IOPACS_API void pullInstance()
Pull the selected slice from the pacs.
FWGUI_API void initialize()
Initialize managers.
std::shared_ptr< ::fwRuntime::ConfigurationElement > m_readerConfig
Optional configuration to set to reader implementation.