fw4spl
ioPacs/src/ioPacs/SSeriesPuller.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/SSeriesPuller.hpp"
8 
9 #include <fwCom/Signal.hpp>
10 #include <fwCom/Signal.hxx>
11 #include <fwCom/Slots.hpp>
12 #include <fwCom/Slots.hxx>
13 
14 #include <fwGui/dialog/MessageDialog.hpp>
15 #include <fwGui/dialog/ProgressDialog.hpp>
16 
17 #include <fwGuiQt/container/QtContainer.hpp>
18 
19 #include <fwMedData/DicomSeries.hpp>
20 
21 #include <fwMedDataTools/helper/SeriesDB.hpp>
22 
23 #include <fwPacsIO/exceptions/Base.hpp>
24 #include <fwPacsIO/helper/Series.hpp>
25 
26 #include <fwServices/macros.hpp>
27 #include <fwServices/registry/ActiveWorkers.hpp>
28 #include <fwServices/registry/ObjectService.hpp>
29 #include <fwServices/registry/ServiceConfig.hpp>
30 
31 #include <fwTools/System.hpp>
32 
33 #include <boost/filesystem/operations.hpp>
34 #include <boost/foreach.hpp>
35 
36 #include <sstream>
37 
38 namespace ioPacs
39 {
40 
41 fwServicesRegisterMacro( ::fwServices::IController, ::ioPacs::SSeriesPuller, ::fwData::Vector );
42 
43 //------------------------------------------------------------------------------
44 
45 const ::fwCom::Slots::SlotKeyType SSeriesPuller::s_READ_SLOT = "readDicom";
46 const ::fwCom::Slots::SlotKeyType SSeriesPuller::s_DISPLAY_SLOT = "displayMessage";
47 
48 const ::fwCom::Signals::SignalKeyType SSeriesPuller::s_PROGRESSED_SIG = "progressed";
49 const ::fwCom::Signals::SignalKeyType SSeriesPuller::s_STARTED_PROGRESS_SIG = "startedProgress";
50 const ::fwCom::Signals::SignalKeyType SSeriesPuller::s_STOPPED_PROGRESS_SIG = "stoppedProgress";
51 
53  m_isPulling(false),
54  m_progressbarId("pullDicomProgressBar"),
55  m_seriesCount(0),
56  m_seriesIndex(0)
57 {
58  // Internal slots
59  m_slotReadLocalSeries = newSlot(s_READ_SLOT, &SSeriesPuller::readLocalSeries, this);
60  m_slotDisplayMessage = newSlot(s_DISPLAY_SLOT, &SSeriesPuller::displayErrorMessage, this);
61 
62  m_slotStoreInstanceCallbackUsingMoveRequests = newSlot(::fwPacsIO::SeriesRetriever::s_PROGRESS_CALLBACK_SLOT,
64  m_slotStoreInstanceCallbackUsingGetRequests = newSlot(::fwPacsIO::SeriesEnquirer::s_PROGRESS_CALLBACK_SLOT,
66 
67  m_sigProgressed = newSignal<ProgressedSignalType>(s_PROGRESSED_SIG);
68  m_sigStartedProgress = newSignal<StartedProgressSignalType>(s_STARTED_PROGRESS_SIG);
69  m_sigStoppedProgress = newSignal<StoppedProgressSignalType>(s_STOPPED_PROGRESS_SIG);
70 
71 }
72 //------------------------------------------------------------------------------
73 
75 {
76 }
77 
78 //------------------------------------------------------------------------------
79 
80 void SSeriesPuller::info(std::ostream& _sstream )
81 {
82  _sstream << "SSeriesPuller::info";
83 }
84 
85 //------------------------------------------------------------------------------
86 
88 {
89  ::fwRuntime::ConfigurationElement::sptr config = m_configuration->findConfigurationElement("config");
90  SLM_ASSERT("The service ::ioPacs::SSeriesPuller must have a \"config\" element.", config);
91 
92  bool success;
93 
94  // Dicom Reader
95  ::boost::tie(success, m_dicomReaderType) = config->getSafeAttributeValue("dicomReader");
96  SLM_ASSERT("It should be a \"dicomReader\" in the ::ioPacs::SSeriesPuller config element.", success);
97 
98  // Dicom Reader Config
99  ::boost::tie(success, m_dicomReaderSrvConfig) = config->getSafeAttributeValue("dicomReaderConfig");
100 }
101 
102 //------------------------------------------------------------------------------
103 
105 {
106  // Create enquirer
107  m_seriesEnquirer = ::fwPacsIO::SeriesEnquirer::New();
108 
109  // Get Destination SeriesDB
110  m_destinationSeriesDB = this->getInOut< ::fwMedData::SeriesDB>("seriesDB");
111  SLM_ASSERT("The 'seriesDB' key doesn't exist.", m_destinationSeriesDB);
112 
113  // Create temporary SeriesDB
114  m_tempSeriesDB = ::fwMedData::SeriesDB::New();
115 
116  // Create reader
117  ::fwServices::registry::ServiceFactory::sptr srvFactory = ::fwServices::registry::ServiceFactory::getDefault();
118  m_dicomReader =
119  ::fwIO::IReader::dynamicCast(srvFactory->create(m_dicomReaderType));
120  SLM_ASSERT("Unable to create a reader of type: \"" + m_dicomReaderType + "\" in ::ioPacs::SSeriesPuller.",
121  m_dicomReader);
122  ::fwServices::OSR::registerService(m_tempSeriesDB, ::fwIO::s_DATA_KEY,
123  ::fwServices::IService::AccessType::INOUT, m_dicomReader);
124  if(!m_dicomReaderSrvConfig.empty())
125  {
126  // Get the config
127  ::fwRuntime::ConfigurationElement::csptr readerConfig =
129  m_dicomReaderSrvConfig, "::fwIO::IReader");
130 
131  SLM_ASSERT("Sorry, there is no service configuration "
133  << " for ::fwIO::IReader", readerConfig);
134 
135  m_dicomReader->setConfiguration( ::fwRuntime::ConfigurationElement::constCast(readerConfig) );
136 
137  }
138 
139  m_dicomReader->configure();
140  m_dicomReader->start();
141 
142  // Worker
143  m_pullSeriesWorker = ::fwThread::Worker::New();
144 
145  // Get pacs configuration
146  m_pacsConfiguration = this->getInput< ::fwPacsIO::data::PacsConfiguration>("pacsConfig");
147  SLM_ASSERT("The pacs configuration object should not be null.", m_pacsConfiguration);
148 }
149 
150 //------------------------------------------------------------------------------
151 
153 {
154  // Stop reader service
155  m_dicomReader->stop();
156  ::fwServices::OSR::unregisterService(m_dicomReader);
157 
158  // Worker
159  m_pullSeriesWorker.reset();
160 }
161 
162 //------------------------------------------------------------------------------
163 
165 {
166  ::fwData::Vector::csptr selectedSeries = this->getInput< ::fwData::Vector >("selectedSeries");
167 
168  if(m_isPulling)
169  {
170  // Display a message to inform the user that the service is already pulling data.
172  messageBox.setTitle("Pulling Series");
173  messageBox.setMessage( "The service is already pulling data. Please wait until the pulling is done "
174  "before sending a new pull request." );
175  messageBox.setIcon(::fwGui::dialog::IMessageDialog::INFO);
176  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
177  messageBox.show();
178  }
179  else if(selectedSeries->empty())
180  {
181  // Display a message to inform the user that there is no series selected.
183  messageBox.setTitle("Pulling Series");
184  messageBox.setMessage( "Unable to pull series, there is no series selected." );
185  messageBox.setIcon(::fwGui::dialog::IMessageDialog::INFO);
186  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
187  messageBox.show();
188  }
189  else
190  {
191  m_pullSeriesWorker->post(std::bind(&::ioPacs::SSeriesPuller::pullSeries, this));
192  }
193 }
194 
195 //------------------------------------------------------------------------------
196 
198 {
199  // Catch any errors
200  try
201  {
202  // Clear map of Dicom series being pulled
203  m_pullingDicomSeriesMap.clear();
204 
205  // Set pulling boolean to true
206  m_isPulling = true;
207 
208  // Reset Counters
209  m_seriesIndex = 0;
210  m_instanceCount = 0;
211 
212  ::fwData::Vector::csptr selectedSeries = this->getInput< ::fwData::Vector >("selectedSeries");
213 
214  // Find which selected series must be pulled
215  DicomSeriesContainerType pullSeriesVector;
216  DicomSeriesContainerType selectedSeriesVector;
217 
218  ::fwData::Vector::ConstIteratorType it = selectedSeries->begin();
219  for(; it != selectedSeries->end(); ++it)
220  {
221  ::fwMedData::DicomSeries::sptr series = ::fwMedData::DicomSeries::dynamicCast(*it);
222 
223  // Check if the series must be pulled
224  if(series &&
225  std::find(m_localSeries.begin(), m_localSeries.end(), series->getInstanceUID()) == m_localSeries.end())
226  {
227  // Add series in the pulling series map
228  m_pullingDicomSeriesMap[series->getInstanceUID()] = series;
229 
230  pullSeriesVector.push_back(series);
231  m_instanceCount += series->getNumberOfInstances();
232  }
233  selectedSeriesVector.push_back(series);
234  }
235 
236  m_seriesCount = pullSeriesVector.size();
237 
238  if(m_seriesCount > 0)
239  {
240  //Notify Progress Dialog
242  }
243 
244  // Pull series
245  if(!pullSeriesVector.empty())
246  {
247  m_seriesEnquirer->initialize(
248  m_pacsConfiguration->getLocalApplicationTitle(),
249  m_pacsConfiguration->getPacsHostName(),
250  m_pacsConfiguration->getPacsApplicationPort(),
251  m_pacsConfiguration->getPacsApplicationTitle(),
252  m_pacsConfiguration->getMoveApplicationTitle(),
254 
255  // Use C-GET Requests
256  if(m_pacsConfiguration->getRetrieveMethod() == ::fwPacsIO::data::PacsConfiguration::GET_RETRIEVE_METHOD)
257  {
258  // Pull Selected Series
259  m_seriesEnquirer->connect();
261  pullSeriesVector));
262  m_seriesEnquirer->disconnect();
263  }
264  // Use C-MOVE Requests
265  else
266  {
267  ::fwPacsIO::SeriesRetriever::sptr seriesRetriever = ::fwPacsIO::SeriesRetriever::New();
268  seriesRetriever->initialize(
269  m_pacsConfiguration->getMoveApplicationTitle(),
270  m_pacsConfiguration->getMoveApplicationPort(),
271  1,
273 
274  // Start Series Retriever
275  ::fwThread::Worker::sptr worker = ::fwThread::Worker::New();
276  worker->post(std::bind(&::fwPacsIO::SeriesRetriever::start, seriesRetriever));
277 
278  // Pull Selected Series
279  m_seriesEnquirer->connect();
281  pullSeriesVector));
282  m_seriesEnquirer->disconnect();
283 
284  worker.reset();
285  }
286 
287  // Notify Progress Dialog
289  }
290 
291  // Read series if there is no error
292  if(m_isPulling)
293  {
294  m_slotReadLocalSeries->asyncRun(selectedSeriesVector);
295  }
296 
297  // Set pulling boolean to false
298  m_isPulling = false;
299 
300  }
301  catch (::fwPacsIO::exceptions::Base& exception)
302  {
303  ::std::stringstream ss;
304  ss << "Unable to connect to the pacs. Please check your configuration: \n"
305  << "Pacs host name: " << m_pacsConfiguration->getPacsHostName() << "\n"
306  << "Pacs application title: " << m_pacsConfiguration->getPacsApplicationTitle() << "\n"
307  << "Pacs port: " << m_pacsConfiguration->getPacsApplicationPort() << "\n";
308  m_slotDisplayMessage->asyncRun(ss.str());
309  SLM_WARN(exception.what());
310  //Notify Progress Dialog
312  m_isPulling = false;
313  }
314 }
315 
316 //------------------------------------------------------------------------------
317 
318 void SSeriesPuller::readLocalSeries(DicomSeriesContainerType selectedSeries)
319 {
320  // Read only series that are not in the SeriesDB
321  InstanceUIDContainerType alreadyLoadedSeries =
323 
324  // Create temporary series helper
326 
327  for(const ::fwMedData::Series::sptr& series: selectedSeries)
328  {
329  const std::string selectedSeriesUID = series->getInstanceUID();
330 
331  // Add the series to the local series vector
332  if(std::find(m_localSeries.begin(), m_localSeries.end(), selectedSeriesUID) == m_localSeries.end())
333  {
334  m_localSeries.push_back(selectedSeriesUID);
335  }
336 
337  // Check if the series is loaded
338  if(std::find(alreadyLoadedSeries.begin(), alreadyLoadedSeries.end(),
339  selectedSeriesUID) == alreadyLoadedSeries.end())
340  {
341  // Clear temporary series
342  tempSDBhelper.clear();
343 
344  ::boost::filesystem::path path = ::fwTools::System::getTemporaryFolder() / "dicom/";
345  m_dicomReader->setFolder(path.string() + selectedSeriesUID + "/");
346  m_dicomReader->update();
347 
348  // Merge series
350  sDBhelper.merge(m_tempSeriesDB);
351  sDBhelper.notify();
352  }
353  }
354 }
355 
356 //------------------------------------------------------------------------------
357 
358 void SSeriesPuller::storeInstanceCallback(const ::std::string& seriesInstanceUID, unsigned int instanceNumber,
359  const ::std::string& filePath)
360 {
361  //Add path in the Dicom series
362  if(!m_pullingDicomSeriesMap[seriesInstanceUID].expired())
363  {
364  ::fwMedData::DicomSeries::sptr series = m_pullingDicomSeriesMap[seriesInstanceUID].lock();
365  series->addDicomPath(instanceNumber, filePath);
366  }
367  else
368  {
369  SLM_WARN("The Dicom Series " + seriesInstanceUID + " has expired.");
370  }
371 
372  //Notify Progress Dialog
373  ::std::stringstream ss;
374  ss << "Downloading file " << instanceNumber << "/" << m_instanceCount;
375  float percentage = static_cast<float>(instanceNumber)/static_cast<float>(m_instanceCount);
376  m_sigProgressed->asyncEmit(m_progressbarId, percentage, ss.str());
377 }
378 
379 //------------------------------------------------------------------------------
380 
381 void SSeriesPuller::displayErrorMessage(const ::std::string& message) const
382 {
383  SLM_WARN("Error: " + message);
385  messageBox.setTitle("Error");
386  messageBox.setMessage( message );
387  messageBox.setIcon(::fwGui::dialog::IMessageDialog::CRITICAL);
388  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
389  messageBox.show();
390 }
391 
392 //------------------------------------------------------------------------------
393 
394 } // namespace ioPacs
std::string m_dicomReaderSrvConfig
Reader Config.
::fwMedData::SeriesDB::sptr m_tempSeriesDB
Temporary SeriesDB.
::fwMedData::SeriesDB::sptr m_destinationSeriesDB
Destination SeriesDB.
DisplayMessageSlotType::sptr m_slotDisplayMessage
Slot to call displayErrorMessage method;.
DicomSeriesMapType m_pullingDicomSeriesMap
Map of Dicom series being pulled.
virtual IOPACS_API void starting() override
Override.
This service is used to pull series from a PACS.
ioPacs contains services use to deal with PACS using DCMTK library.
virtual FWGUI_API void setMessage(const std::string &msg) override
Set the message.
::fwPacsIO::SeriesEnquirer::sptr m_seriesEnquirer
Series enquirer.
IOPACS_API void readLocalSeries(DicomSeriesContainerType selectedSeries)
Read local series.
Defines the generic message box for IHM. Use the Delegate design pattern.
::fwPacsIO::data::PacsConfiguration::csptr m_pacsConfiguration
Pacs Configuration object.
IOPACS_API void displayErrorMessage(const std::string &message) const
Display an error message.
::fwPacsIO::SeriesEnquirer::ProgressCallbackSlotType::sptr m_slotStoreInstanceCallbackUsingGetRequests
Slot to call storeInstanceCallback method using C-GET Requests.
StoppedProgressSignalType::sptr m_sigStoppedProgress
Signal emitted when the bar is stopping.
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
IOPACS_API void updating() override
Override.
static FWPACSIO_API InstanceUIDContainer toSeriesInstanceUIDContainer(OFList< QRResponse * > responses)
Convert DCMTK series to instance uid vector.
IOPACS_API SSeriesPuller() noexcept
Constructor.
This class defines a vector of objects.
::fwIO::IReader::sptr m_dicomReader
Reader.
virtual FWGUI_API void addButton(IMessageDialog::Buttons button) override
Add a button (OK, YES_NO, YES, NO, CANCEL)
virtual IOPACS_API ~SSeriesPuller() noexcept
Destructor.
bool m_isPulling
Is pulling is set to true when we are pulling series.
virtual FWGUI_API IMessageDialog::Buttons show() override
Show the message box and return the clicked button.
This interface defines control service API. Does nothing particularly, can be considered as a default...
Definition: IController.hpp:23
long unsigned int m_seriesCount
Total number of downloaded series.
IOPACS_API void pullSeries()
Pull Series.
ReadDicomSlotType::sptr m_slotReadLocalSeries
Slot to call readLocalSeries method.
#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
FWPACSIO_API bool start()
Start the server.
::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
IOPACS_API void storeInstanceCallback(const std::string &seriesInstanceUID, unsigned int instanceNumber, const std::string &filePath)
Store instance callback.
virtual FWGUI_API void setIcon(IMessageDialog::Icons icon) override
Set the icon (CRITICAL, WARNING, INFO or QUESTION)
std::string m_dicomReaderType
IOPACS Reader.
::fwThread::Worker::sptr m_pullSeriesWorker
Pull Worker.
static const ::fwCom::Signals::SignalKeyType s_PROGRESSED_SIG
Key in m_signals map of signal m_sigProgressed.
InstanceUIDContainerType m_localSeries
Local Series.
FWMEDDATATOOLS_API void notify()
Send the signal of modification.
std::size_t m_instanceCount
Total number of instances that must be downloaded.
static FWSERVICES_API ServiceFactory::sptr getDefault()
Return the unique Instance, create it if required at first access.
ProgressedSignalType::sptr m_sigProgressed
Signal emitted when the bar is progressing.
static FWSERVICES_API ServiceConfig::sptr getDefault()
Return the default global instance of ServiceConfig.
virtual IOPACS_API void stopping() override
Override.
virtual IOPACS_API void configuring() override
Configuring method. This method is used to configure the service.
::fwPacsIO::SeriesRetriever::ProgressCallbackSlotType::sptr m_slotStoreInstanceCallbackUsingMoveRequests
Slot to call storeInstanceCallback method using C-MOVE Requests.
StartedProgressSignalType::sptr m_sigStartedProgress
Signal emitted when the bar is starting.
virtual FWGUI_API void setTitle(const std::string &title) override
Set the title of the message box.
unsigned int m_seriesIndex
Index of the series being downloaded.
FWMEDDATATOOLS_API void merge(::fwMedData::SeriesDB::sptr seriesDBIn)
Merge seriesDBIn all series from seriesDBIn to the SeriesDB.
IOPACS_API void info(std::ostream &_sstream) override
Override.
std::string m_progressbarId
Progress Bar ID.