GCC Code Coverage Report


./
File: modules/io/dicomweb/series_puller.cpp
Date: 2025-01-21 16:21:04
Lines:
0/155
0.0%
Functions:
0/9
0.0%
Branches:
0/390
0.0%

Line Branch Exec Source
1 /************************************************************************
2 *
3 * Copyright (C) 2018-2023 IRCAD France
4 * Copyright (C) 2018-2019 IHU Strasbourg
5 *
6 * This file is part of Sight.
7 *
8 * Sight is free software: you can redistribute it and/or modify it under
9 * the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Sight is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
20 *
21 ***********************************************************************/
22
23 #include "series_puller.hpp"
24
25 #include <core/com/signal.hxx>
26 #include <core/com/slots.hxx>
27 #include <core/tools/system.hpp>
28
29 #include <data/dicom_series.hpp>
30
31 #include <io/http/exceptions/base.hpp>
32 #include <io/http/helper/series.hpp>
33 #include <io/http/request.hpp>
34
35 #include <service/extension/config.hpp>
36 #include <service/op.hpp>
37
38 #include <ui/__/dialog/message.hpp>
39 #include <ui/__/dialog/progress.hpp>
40 #include <ui/__/preferences.hpp>
41
42 #include <filesystem>
43
44 namespace sight::module::io::dicomweb
45 {
46
47 //------------------------------------------------------------------------------
48
49 series_puller::series_puller() noexcept =
50 default;
51
52 //------------------------------------------------------------------------------
53
54 series_puller::~series_puller() noexcept =
55 default;
56
57 //------------------------------------------------------------------------------
58
59 void series_puller::configuring()
60 {
61 const auto& config = this->get_config();
62
63 m_dicom_reader_type = config.get<std::string>("config.<xmlattr>.dicomReader", m_dicom_reader_type);
64 m_dicom_reader_srv_config = config.get<std::string>("config.<xmlattr>.readerConfig", m_dicom_reader_srv_config);
65
66 //Parse server port and hostname
67 if(config.count("server") != 0U)
68 {
69 const std::string server_info = config.get("server", "");
70 const std::string::size_type split_position = server_info.find(':');
71 SIGHT_ASSERT("Server info not formatted correctly", split_position != std::string::npos);
72
73 m_server_hostname_key = server_info.substr(0, split_position);
74 m_server_port_key = server_info.substr(split_position + 1, server_info.size());
75 }
76 else
77 {
78 throw core::tools::failed("'server' element not found");
79 }
80 }
81
82 //------------------------------------------------------------------------------
83
84 void series_puller::starting()
85 {
86 // Create temporary series_set
87 m_tmp_series_set = std::make_shared<data::series_set>();
88
89 // Create reader
90 m_dicom_reader = service::add<sight::io::service::reader>(m_dicom_reader_type);
91 SIGHT_ASSERT(
92 "Unable to create a reader of type: \"" + m_dicom_reader_type + "\" in module::io::dicomweb::series_puller.",
93 m_dicom_reader
94 );
95 m_dicom_reader->set_inout(m_tmp_series_set, sight::io::service::DATA_KEY);
96
97 if(!m_dicom_reader_srv_config.empty())
98 {
99 // Get the config
100 const auto reader_config = service::extension::config::get_default()->get_service_config(
101 m_dicom_reader_srv_config,
102 "sight::io::service::reader"
103 );
104
105 SIGHT_ASSERT(
106 "Sorry, there is no service configuration "
107 << m_dicom_reader_srv_config
108 << " for sight::io::service::reader",
109 !reader_config.empty()
110 );
111
112 m_dicom_reader->set_config(reader_config);
113 }
114
115 m_dicom_reader->configure();
116 m_dicom_reader->start();
117 }
118
119 //------------------------------------------------------------------------------
120
121 void series_puller::stopping()
122 {
123 // Stop reader service
124 m_dicom_reader->stop();
125 service::remove(m_dicom_reader);
126 }
127
128 //------------------------------------------------------------------------------
129
130 void series_puller::updating()
131 {
132 try
133 {
134 ui::preferences preferences;
135 m_server_port = preferences.delimited_get(m_server_port_key, m_server_port);
136 m_server_hostname = preferences.delimited_get(m_server_hostname_key, m_server_hostname);
137 }
138 catch(...)
139 {
140 // Do nothing
141 }
142
143 if(m_is_pulling)
144 {
145 // Display a message to inform the user that the service is already pulling data.
146 sight::ui::dialog::message message_box;
147 message_box.set_title("Pulling Series");
148 message_box.set_message(
149 "The service is already pulling data. Please wait until the pulling is done "
150 "before sending a new pull request."
151 );
152 message_box.set_icon(ui::dialog::message::info);
153 message_box.add_button(ui::dialog::message::ok);
154 message_box.show();
155 }
156 else
157 {
158 const auto selected_series = m_selected_series.lock();
159 if(selected_series->empty())
160 {
161 // Display a message to inform the user that there is no series selected.
162 sight::ui::dialog::message message_box;
163 message_box.set_title("Pulling Series");
164 message_box.set_message("Unable to pull series, there is no series selected. ");
165 message_box.set_icon(ui::dialog::message::info);
166 message_box.add_button(ui::dialog::message::ok);
167 message_box.show();
168 }
169 else
170 {
171 this->pull_series();
172 }
173 }
174 }
175
176 //------------------------------------------------------------------------------
177
178 void series_puller::pull_series()
179 {
180 // Catch any errors
181 try
182 {
183 // Clear map of Dicom series being pulled
184 m_pulling_dicom_series_map.clear();
185
186 // Set pulling boolean to true
187 m_is_pulling = true;
188
189 // Reset Counters
190 m_series_index = 0;
191 m_instance_count = 0;
192
193 const auto selected_series = m_selected_series.lock();
194
195 // Find which selected series must be pulled
196 dicom_series_container_t pull_series_vector;
197 dicom_series_container_t selected_series_vector;
198
199 auto it = selected_series->cbegin();
200 for( ; it != selected_series->cend() ; ++it)
201 {
202 data::dicom_series::sptr series = std::dynamic_pointer_cast<data::dicom_series>(*it);
203
204 // Check if the series must be pulled
205 if(series
206 && std::find(
207 m_local_series.begin(),
208 m_local_series.end(),
209 series->get_series_instance_uid()
210 ) == m_local_series.end())
211 {
212 // Add series in the pulling series map
213 m_pulling_dicom_series_map[series->get_series_instance_uid()] = series;
214
215 pull_series_vector.push_back(series);
216 m_instance_count += series->num_instances();
217 }
218
219 selected_series_vector.push_back(series);
220 }
221
222 // Pull series
223 if(!pull_series_vector.empty())
224 {
225 /// GET
226 const instance_uid_container_t& series_instances_ui_ds =
227 sight::io::http::helper::series::to_series_instance_uid_container(pull_series_vector);
228 for(const std::string& series_instances_uid : series_instances_ui_ds)
229 {
230 // Find Series according to SeriesInstanceUID
231 QJsonObject query;
232 query.insert("SeriesInstanceUID", series_instances_uid.c_str());
233
234 QJsonObject body;
235 body.insert("Level", "Series");
236 body.insert("Query", query);
237 body.insert("Limit", 0);
238
239 /// Url PACS
240 const std::string pacs_server("http://" + m_server_hostname + ":" + std::to_string(m_server_port));
241
242 /// Orthanc "/tools/find" route. POST a JSON to get all Series corresponding to the SeriesInstanceUID.
243 sight::io::http::request::sptr request = sight::io::http::request::New(
244 pacs_server + "/tools/find"
245 );
246 QByteArray series_answer;
247 try
248 {
249 series_answer = m_client_qt.post(request, QJsonDocument(body).toJson());
250 }
251 catch(sight::io::http::exceptions::host_not_found& exception)
252 {
253 std::stringstream ss;
254 ss << "Host not found:\n"
255 << " Please check your configuration: \n"
256 << "Pacs host name: " << m_server_hostname << "\n"
257 << "Pacs port: " << m_server_port << "\n";
258
259 sight::module::io::dicomweb::series_puller::display_error_message(ss.str());
260 SIGHT_WARN(exception.what());
261 }
262
263 QJsonDocument json_response = QJsonDocument::fromJson(series_answer);
264 const QJsonArray& series_array = json_response.array();
265
266 const int series_array_size = series_array.count();
267 for(int i = 0 ; i < series_array_size ; ++i)
268 {
269 const std::string& series_uid = series_array.at(i).toString().toStdString();
270
271 /// GET all Instances by Series.
272 const std::string& instances_url(std::string(pacs_server) + "/series/" + series_uid);
273 const QByteArray& instances_answer =
274 m_client_qt.get(sight::io::http::request::New(instances_url));
275 json_response = QJsonDocument::fromJson(instances_answer);
276 const QJsonObject& json_obj = json_response.object();
277 const QJsonArray& instances_array = json_obj["Instances"].toArray();
278
279 const int instances_array_size = instances_array.count();
280 for(int j = 0 ; j < instances_array_size ; ++j)
281 {
282 const std::string& instance_uid = instances_array.at(j).toString().toStdString();
283
284 /// GET DICOM Instance file.
285 const std::string instance_url(pacs_server + "/instances/" + instance_uid + "/file");
286
287 try
288 {
289 m_path = m_client_qt.get_file(sight::io::http::request::New(instance_url));
290 }
291 catch(sight::io::http::exceptions::content_not_found& exception)
292 {
293 std::stringstream ss;
294 ss << "Content not found: \n"
295 << "Unable download the DICOM instance. \n";
296
297 sight::module::io::dicomweb::series_puller::display_error_message(ss.str());
298 SIGHT_WARN(exception.what());
299 }
300
301 // Create dicom folder
302 std::filesystem::path instance_path = m_path.parent_path() / series_instances_uid;
303 QDir().mkpath(instance_path.string().c_str());
304 // Move dicom file to the created dicom folder
305 instance_path /= m_path.filename();
306 QFile::rename(m_path.string().c_str(), instance_path.string().c_str());
307 m_path = m_path.parent_path() / series_instances_uid;
308 }
309 }
310 }
311 }
312
313 // Read series if there is no error
314 if(m_is_pulling)
315 {
316 this->read_local_series(selected_series_vector);
317 }
318
319 // Set pulling boolean to false
320 m_is_pulling = false;
321 }
322 catch(sight::io::http::exceptions::base& exception)
323 {
324 std::stringstream ss;
325 ss << "Unknown error.";
326 sight::module::io::dicomweb::series_puller::display_error_message(ss.str());
327 SIGHT_WARN(exception.what());
328 m_is_pulling = false;
329 }
330 }
331
332 //------------------------------------------------------------------------------
333
334 void series_puller::read_local_series(dicom_series_container_t _selected_series)
335 {
336 const auto dest_series_set = m_series_set.lock();
337
338 const auto scoped_emitter = dest_series_set->scoped_emit();
339
340 // Read only series that are not in the series_set
341 const instance_uid_container_t& already_loaded_series =
342 sight::io::http::helper::series::to_series_instance_uid_container(dest_series_set->get_content());
343
344 for(const auto& series : _selected_series)
345 {
346 const std::string& selected_series_uid = series->get_series_instance_uid();
347
348 // Add the series to the local series vector
349 if(std::find(m_local_series.begin(), m_local_series.end(), selected_series_uid) == m_local_series.end())
350 {
351 m_local_series.push_back(selected_series_uid);
352 }
353
354 // Check if the series is loaded
355 if(std::find(
356 already_loaded_series.cbegin(),
357 already_loaded_series.cend(),
358 selected_series_uid
359 ) == already_loaded_series.cend())
360 {
361 // Clear temporary series
362 m_tmp_series_set->clear();
363
364 m_dicom_reader->set_folder(m_path);
365 m_dicom_reader->update();
366
367 // Merge series
368 std::copy(m_tmp_series_set->cbegin(), m_tmp_series_set->cend(), sight::data::inserter(*dest_series_set));
369 }
370 }
371 }
372
373 //------------------------------------------------------------------------------
374
375 void series_puller::display_error_message(const std::string& _message)
376 {
377 SIGHT_WARN("Error: " + _message);
378 sight::ui::dialog::message message_box;
379 message_box.set_title("Error");
380 message_box.set_message(_message);
381 message_box.set_icon(ui::dialog::message::critical);
382 message_box.add_button(ui::dialog::message::ok);
383 message_box.show();
384 }
385
386 //------------------------------------------------------------------------------
387
388 } // namespace sight::module::io::dicomweb
389