fw4spl
ioDicomWeb/src/ioDicomWeb/SQueryEditor.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 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 "ioDicomWeb/SQueryEditor.hpp"
8 
9 #include <fwGui/dialog/MessageDialog.hpp>
10 
11 #include <fwGuiQt/container/QtContainer.hpp>
12 
13 #include <fwMedData/DicomSeries.hpp>
14 
15 #include <fwMedDataTools/helper/SeriesDB.hpp>
16 
17 #include <fwNetworkIO/helper/Series.hpp>
18 
19 #include <fwPreferences/helper.hpp>
20 
21 #include <fwServices/macros.hpp>
22 
23 #include <boost/filesystem/operations.hpp>
24 #include <boost/foreach.hpp>
25 
26 #include <dcmtk/dcmnet/scu.h>
27 
28 #include <QGridLayout>
29 #include <QHBoxLayout>
30 #include <QLabel>
31 
32 namespace ioDicomWeb
33 {
34 
35 //------------------------------------------------------------------------------
36 
38 {
39 }
40 //------------------------------------------------------------------------------
41 
43 {
44 }
45 
46 //------------------------------------------------------------------------------
47 
49 {
50  ::fwServices::IService::ConfigType configuration = this->getConfigTree();
51  //Parse server port and hostname
52  if(configuration.count("server"))
53  {
54  const std::string serverInfo = configuration.get("server", "");
55  const std::string::size_type splitPosition = serverInfo.find(':');
56  SLM_ASSERT("Server info not formatted correctly", splitPosition != std::string::npos);
57 
58  const std::string hostnameStr = serverInfo.substr(0, splitPosition);
59  const std::string portStr = serverInfo.substr(splitPosition + 1, serverInfo.size());
60 
61  m_serverHostnameKey = this->getPreferenceKey(hostnameStr);
62  m_serverPortKey = this->getPreferenceKey(portStr);
63  if(m_serverHostnameKey.empty())
64  {
65  m_serverHostname = hostnameStr;
66  }
67  if(m_serverPortKey.empty())
68  {
69  m_serverPort = std::stoi(portStr);
70  }
71  }
72  else
73  {
74  throw ::fwTools::Failed("'server' element not found");
75  }
76 
78 }
79 
80 //------------------------------------------------------------------------------
81 
82 std::string SQueryEditor::getPreferenceKey(const std::string& key) const
83 {
84  std::string keyResult;
85  const size_t first = key.find('%');
86  const size_t last = key.rfind('%');
87  if (first == 0 && last == key.size() - 1)
88  {
89  keyResult = key.substr(1, key.size() - 2);
90  }
91  return keyResult;
92 }
93 
94 //------------------------------------------------------------------------------
95 
97 {
100  ::fwGuiQt::container::QtContainer::sptr qtContainer = fwGuiQt::container::QtContainer::dynamicCast(getContainer());
101 
102  // Main Widget
103  QGridLayout* layout = new QGridLayout();
104 
105  m_patientNameLineEdit = new QLineEdit();
106  m_patientNameQueryButton = new QPushButton("Send");
107  layout->addWidget(new QLabel("Patient name:"), 0, 0);
108  layout->addWidget(m_patientNameLineEdit, 0, 1);
109  layout->addWidget(m_patientNameQueryButton, 0, 2);
110 
111  m_beginStudyDateEdit = new QDateEdit();
112  m_beginStudyDateEdit->setDate(QDate::currentDate());
113  m_beginStudyDateEdit->setDisplayFormat("dd.MM.yyyy");
114  m_endStudyDateEdit = new QDateEdit();
115  m_endStudyDateEdit->setDate(QDate::currentDate());
116  m_endStudyDateEdit->setDisplayFormat("dd.MM.yyyy");
117  m_studyDateQueryButton = new QPushButton("Send");
118  QHBoxLayout* dateLayout = new QHBoxLayout();
119  layout->addWidget(new QLabel("Study date:"), 1, 0);
120  layout->addLayout(dateLayout, 1, 1);
121  layout->addWidget(m_studyDateQueryButton, 1, 2);
122  dateLayout->addWidget(m_beginStudyDateEdit);
123  dateLayout->addWidget(m_endStudyDateEdit);
124 
125  //Set layout
126  qtContainer->setLayout(layout);
127 
128  // Connect the signals
129  QObject::connect(m_patientNameLineEdit, SIGNAL(returnPressed()), this, SLOT(queryPatientName()));
130  QObject::connect(m_patientNameQueryButton, SIGNAL(clicked()), this, SLOT(queryPatientName()));
131  QObject::connect(m_studyDateQueryButton, SIGNAL(clicked()), this, SLOT(queryStudyDate()));
132  QObject::connect(m_beginStudyDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(queryStudyDate()));
133  QObject::connect(m_endStudyDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(queryStudyDate()));
134 }
135 
136 //------------------------------------------------------------------------------
137 
139 {
140  SLM_TRACE_FUNC();
141 
142  // Disconnect the signals
143  QObject::disconnect(m_patientNameLineEdit, SIGNAL(returnPressed()), this, SLOT(queryPatientName()));
144  QObject::disconnect(m_patientNameQueryButton, SIGNAL(clicked()), this, SLOT(queryPatientName()));
145  QObject::disconnect(m_studyDateQueryButton, SIGNAL(clicked()), this, SLOT(queryStudyDate()));
146  QObject::disconnect(m_beginStudyDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(queryStudyDate()));
147  QObject::disconnect(m_endStudyDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(queryStudyDate()));
148 
149  this->destroy();
150 }
151 
152 //------------------------------------------------------------------------------
153 
155 {
156  SLM_TRACE_FUNC();
157 }
158 
159 //------------------------------------------------------------------------------
160 
161 void SQueryEditor::queryPatientName()
162 {
163  if(!m_serverHostnameKey.empty())
164  {
165  const std::string hostname = ::fwPreferences::getPreference(m_serverHostnameKey);
166  if(!hostname.empty())
167  {
168  m_serverHostname = hostname;
169  }
170  }
171  if(!m_serverPortKey.empty())
172  {
173  const std::string port = ::fwPreferences::getPreference(m_serverPortKey);
174  if(!port.empty())
175  {
176  m_serverPort = std::stoi(port);
177  }
178  }
179 
180  try
181  {
182  // Vector of all Series that will be retrieved.
183  ::fwMedData::SeriesDB::ContainerType allSeries;
184 
185  // Find series according to patient's name
186  QJsonObject query;
187  query.insert("PatientName", m_patientNameLineEdit->text().toStdString().c_str());
188 
189  QJsonObject body;
190  body.insert("Level", "Series");
191  body.insert("Query", query);
192  body.insert("Limit", 0);
193 
195  const std::string pacsServer("http://" + m_serverHostname + ":" + std::to_string(m_serverPort));
196 
198  ::fwNetworkIO::http::Request::sptr request = ::fwNetworkIO::http::Request::New(pacsServer + "/tools/find");
199  const QByteArray& seriesAnswer = m_clientQt.post(request, QJsonDocument(body).toJson());
200  QJsonDocument jsonResponse = QJsonDocument::fromJson(seriesAnswer);
201  QJsonArray seriesArray = jsonResponse.array();
202  const int seriesArraySize = seriesArray.count();
203 
204  for(int i = 0; i < seriesArraySize; ++i)
205  {
206 
207  const std::string& seriesUID = seriesArray.at(i).toString().toStdString();
208  const std::string instancesListUrl(pacsServer + "/series/" + seriesUID);
209  const QByteArray& instancesAnswer = m_clientQt.get( ::fwNetworkIO::http::Request::New(instancesListUrl));
210  jsonResponse = QJsonDocument::fromJson(instancesAnswer);
211  const QJsonObject& jsonObj = jsonResponse.object();
212  const QJsonArray& instanceArray = jsonObj["Instances"].toArray();
213 
214  // Retrieve the first instance for the needed information
215  const std::string& instanceUID = instanceArray.at(0).toString().toStdString();
216  const std::string instanceUrl(pacsServer + "/instances/" + instanceUID + "/simplified-tags");
217  const QByteArray& instance = m_clientQt.get( ::fwNetworkIO::http::Request::New(instanceUrl));
218 
219  QJsonObject seriesJson = QJsonDocument::fromJson(instance).object();
220  seriesJson.insert( "NumberOfSeriesRelatedInstances", instanceArray.count() );
221 
222  // Convert response to DicomSeries
223  ::fwMedData::SeriesDB::ContainerType series = ::fwNetworkIO::helper::Series::toFwMedData(seriesJson);
224 
225  allSeries.insert(std::end(allSeries), std::begin(series), std::end(series));
226  this->updateSeriesDB(allSeries);
227  }
228  }
229  catch (::fwNetworkIO::exceptions::Base& exception)
230  {
231  this->displayErrorMessage(exception.what());
232  }
233 
234 }
235 
236 //------------------------------------------------------------------------------
237 
238 void SQueryEditor::queryStudyDate()
239 {
240  if(!m_serverHostnameKey.empty())
241  {
242  const std::string hostname = ::fwPreferences::getPreference(m_serverHostnameKey);
243  if(!hostname.empty())
244  {
245  m_serverHostname = hostname;
246  }
247  }
248  if(!m_serverPortKey.empty())
249  {
250  const std::string port = ::fwPreferences::getPreference(m_serverPortKey);
251  if(!port.empty())
252  {
253  m_serverPort = std::stoi(port);
254  }
255  }
256 
257  try
258  {
259  // Vector of all Series that will be retrieved.
260  ::fwMedData::SeriesDB::ContainerType allSeries;
261 
262  // Find Studies according to their StudyDate
263  QJsonObject query;
264  const std::string& beginDate = m_beginStudyDateEdit->date().toString("yyyyMMdd").toStdString();
265  const std::string& endDate = m_endStudyDateEdit->date().toString("yyyyMMdd").toStdString();
266  const std::string& dateRange = beginDate + "-" + endDate;
267  query.insert("StudyDate", dateRange.c_str());
268 
269  QJsonObject body;
270  body.insert("Level", "Studies");
271  body.insert("Query", query);
272  body.insert("Limit", 0);
273 
275  const std::string pacsServer("http://" + m_serverHostname + ":" + std::to_string(m_serverPort));
276 
278  ::fwNetworkIO::http::Request::sptr request = ::fwNetworkIO::http::Request::New(pacsServer + "/tools/find");
279  QByteArray studiesListAnswer;
280  try
281  {
282  studiesListAnswer = m_clientQt.post(request, QJsonDocument(body).toJson());
283  }
284  catch (::fwNetworkIO::exceptions::HostNotFound& exception)
285  {
286  std::stringstream ss;
287  ss << "Host not found:\n"
288  << " Please check your configuration: \n"
289  << "Pacs host name: " << m_serverHostname << "\n"
290  << "Pacs port: " << m_serverPort << "\n";
291 
292  this->displayErrorMessage(ss.str());
293  SLM_WARN(exception.what());
294  }
295  QJsonDocument jsonResponse = QJsonDocument::fromJson(studiesListAnswer);
296  const QJsonArray& studiesListArray = jsonResponse.array();
297  const int studiesListArraySize = studiesListArray.count();
298 
299  for(int i = 0; i < studiesListArraySize; ++i)
300  {
301  const std::string& studiesUID = studiesListArray.at(i).toString().toStdString();
302  const std::string studiesUrl(pacsServer + "/studies/" + studiesUID);
303  const QByteArray& studiesAnswer = m_clientQt.get( ::fwNetworkIO::http::Request::New(studiesUrl));
304 
305  jsonResponse = QJsonDocument::fromJson(studiesAnswer);
306  const QJsonObject& jsonObj = jsonResponse.object();
307  const QJsonArray& seriesArray = jsonObj["Series"].toArray();
308  const int seriesArraySize = seriesArray.count();
309 
310  for(int i = 0; i < seriesArraySize; ++i)
311  {
312  const std::string& seriesUID = seriesArray.at(i).toString().toStdString();
313  const std::string instancesUrl(pacsServer + "/series/" + seriesUID);
314  const QByteArray& instancesAnswer = m_clientQt.get( ::fwNetworkIO::http::Request::New(instancesUrl));
315  jsonResponse = QJsonDocument::fromJson(instancesAnswer);
316  const QJsonObject& jsonObj = jsonResponse.object();
317  const QJsonArray& instanceArray = jsonObj["Instances"].toArray();
318 
319  // Retrieve the first instance for the needed information
320  const std::string& instanceUID = instanceArray.at(0).toString().toStdString();
321  const std::string instanceUrl(pacsServer + "/instances/" + instanceUID + "/simplified-tags");
322  const QByteArray& instance = m_clientQt.get( ::fwNetworkIO::http::Request::New(instanceUrl));
323 
324  QJsonObject seriesJson = QJsonDocument::fromJson(instance).object();
325  seriesJson.insert( "NumberOfSeriesRelatedInstances", instanceArray.count() );
326 
327  // Convert response to DicomSeries
328  ::fwMedData::SeriesDB::ContainerType series = ::fwNetworkIO::helper::Series::toFwMedData(seriesJson);
329 
330  allSeries.insert(std::end(allSeries), std::begin(series), std::end(series));
331  this->updateSeriesDB(allSeries);
332  }
333  }
334  }
335  catch (::fwNetworkIO::exceptions::Base& exception)
336  {
337  std::stringstream ss;
338  ss << "Unknown error.";
339  this->displayErrorMessage(ss.str());
340  SLM_WARN(exception.what());
341  }
342 }
343 
344 //------------------------------------------------------------------------------
345 
346 void SQueryEditor::updateSeriesDB(::fwMedData::SeriesDB::ContainerType series)
347 {
348  ::fwMedData::SeriesDB::sptr seriesDB = this->getInOut< ::fwMedData::SeriesDB >("seriesDB");
349  ::fwMedDataTools::helper::SeriesDB seriesDBHelper(seriesDB);
350 
351  // Delete old series from the SeriesDB
352  seriesDBHelper.clear();
353 
354  // Push new series in the SeriesDB
355  for(const ::fwMedData::Series::sptr& s: series)
356  {
357  ::fwMedData::DicomSeries::sptr dicomSeries = ::fwMedData::DicomSeries::dynamicCast(s);
358  seriesDBHelper.add(dicomSeries);
359  }
360 
361  // Notify th SeriesDB
362  seriesDBHelper.notify();
363 
364 }
365 
366 //------------------------------------------------------------------------------
367 
368 void SQueryEditor::displayErrorMessage(const std::string& message) const
369 {
371  messageBox.setTitle("Error");
372  messageBox.setMessage( message );
373  messageBox.setIcon(::fwGui::dialog::IMessageDialog::CRITICAL);
374  messageBox.addButton(::fwGui::dialog::IMessageDialog::OK);
375  messageBox.show();
376 }
377 
378 //------------------------------------------------------------------------------
379 
380 } // namespace ioDicomWeb
FWNETWORKIO_API QByteArray post(Request::sptr request, const QByteArray &body)
Performs POST request.
Definition: ClientQt.cpp:124
static FWNETWORKIO_API Request::sptr New(const std::string &url)
Creates a new Request with given url.
Definition: Request.cpp:26
#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 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.
virtual IODICOMWEB_API void starting() override
Creates the widgets & connect the signals.
IODICOMWEB_API void updating() override
Does nothing.
virtual IODICOMWEB_API void stopping() override
Disconnect the signals and destroy the widgets.
virtual IODICOMWEB_API void configuring() override
Gets the configurations.
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
virtual FWGUI_API void addButton(IMessageDialog::Buttons button) override
Add a button (OK, YES_NO, YES, NO, CANCEL)
static FWNETWORKIO_API DicomSeriesContainer toFwMedData(const QJsonObject &answer)
Convert HTTP series response to fwMedData::DicomSeries.
FWNETWORKIO_API QByteArray get(Request::sptr request)
Retrieves data over network.
Definition: ClientQt.cpp:36
virtual FWGUI_API IMessageDialog::Buttons show() override
Show the message box and return the clicked button.
#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 IODICOMWEB_API ~SQueryEditor() noexcept
Destructor.
IODICOMWEB_API SQueryEditor() noexcept
Constructor.
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)
Implements exception for an HTTP host not found errors.
ioDicomWeb contains services use to deal with PACS through HTTP.
virtual FWGUI_API void setTitle(const std::string &title) override
Set the title of the message box.
FWGUI_API void initialize()
Initialize managers.
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247