Codebase list orthanc-dicomweb / fd1245b
New upstream version 1.0+dfsg jodogne-guest 4 years ago
26 changed file(s) with 10764 addition(s) and 902 deletion(s). Raw diff Collapse all Expand all
00 repo: d5f45924411123cfd02d035fd50b8e37536eadef
1 node: ce4e5f0769c81584a8851cff3d1b0fd56283574f
2 branch: OrthancDicomWeb-0.6
1 node: 1e052095bb1d3e3ac5d0c3a5253699867d784579
2 branch: OrthancDicomWeb-1.0
33 latesttag: null
4 latesttagdistance: 250
5 changessincelatesttag: 264
4 latesttagdistance: 323
5 changessincelatesttag: 339
2020
2121 project(OrthancDicomWeb)
2222
23 set(ORTHANC_DICOM_WEB_VERSION "0.6")
23 set(ORTHANC_DICOM_WEB_VERSION "1.0")
2424
2525 if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
2626 set(ORTHANC_FRAMEWORK_VERSION "mainline")
2727 set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
2828 else()
29 set(ORTHANC_FRAMEWORK_VERSION "1.5.5")
29 set(ORTHANC_FRAMEWORK_VERSION "1.5.7")
3030 set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
3131 endif()
3232
4141 # Advanced parameters to fine-tune linking against system libraries
4242 set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)")
4343 set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
44 set(ORTHANC_SDK_VERSION "1.5.4" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", or \"framework\")")
44 set(ORTHANC_SDK_VERSION "1.5.7" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", \"1.5.7\", or \"framework\")")
45
46
47 set(BUILD_BOOTSTRAP_VUE OFF CACHE BOOL "Compile Bootstrap-Vue from sources")
48 set(BUILD_BABEL_POLYFILL OFF CACHE BOOL "Retrieve babel-polyfill from npm")
4549
4650
4751
5963 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
6064 include_directories(${ORTHANC_ROOT})
6165
62
6366 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
67 include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake)
6468
6569
6670 if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
6771 if (ORTHANC_SDK_VERSION STREQUAL "1.5.4")
6872 include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.4)
73 elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.7")
74 include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.7)
6975 elseif (ORTHANC_SDK_VERSION STREQUAL "framework")
7076 include_directories(${ORTHANC_ROOT}/Plugins/Include)
7177 else()
109115 endif()
110116
111117
118
119 if (STANDALONE_BUILD)
120 add_definitions(-DORTHANC_STANDALONE=1)
121 set(ADDITIONAL_RESOURCES
122 ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/Plugin/OrthancExplorer.js
123 WEB_APPLICATION ${CMAKE_SOURCE_DIR}/WebApplication/
124 )
125 else()
126 add_definitions(-DORTHANC_STANDALONE=0)
127 endif()
128
129 EmbedResources(
130 --no-upcase-check
131 ${ADDITIONAL_RESOURCES}
132 JAVASCRIPT_LIBS ${JAVASCRIPT_LIBS_DIR}
133 )
134
135
112136 include_directories(${ORTHANC_ROOT}/Core) # To access "OrthancException.h"
113137
114138 add_definitions(
115139 -DHAS_ORTHANC_EXCEPTION=1
116140 -DORTHANC_ENABLE_LOGGING_PLUGIN=1
141 -DDICOMWEB_CLIENT_PATH="${CMAKE_SOURCE_DIR}/WebApplication/"
117142 )
118143
119144 set(CORE_SOURCES
00 Pending changes in the mainline
11 ===============================
2
3
4 Version 1.0 (2019-06-26)
5 ========================
6
7 => Recommended SDK version: 1.5.7 <=
8 => Minimum SDK version: 1.5.4 <=
9
10 * Web user interface to QIDO-RS, WADO-RS and STOW-RS client
11 * First implementation of WADO-RS "Retrieve Rendered Transaction"
12 * WADO-RS and STOW-RS client now create Orthanc jobs
13 * Support "Transfer-Encoding: chunked" to reduce memory consumption in STOW-RS
14 (provided the SDK version is above 1.5.7)
15 * New URI: /dicom-web/servers/.../qido
16 * New URI: /dicom-web/servers/.../delete
17 * Handling of the HTTP header "Forwarded" for WADO-RS
18 * Full refactoring of multipart parsing
219
320
421 Version 0.6 (2019-02-27)
2020
2121 #include "Configuration.h"
2222
23 #include "DicomWebServers.h"
24
25 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
26 #include <Core/Toolbox.h>
27
2328 #include <fstream>
2429 #include <json/reader.h>
2530 #include <boost/regex.hpp>
2631 #include <boost/lexical_cast.hpp>
27
28 #include "DicomWebServers.h"
29
30 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
31 #include <Core/Toolbox.h>
32 #include <boost/algorithm/string/predicate.hpp>
33
3234
3335 namespace OrthancPlugins
3436 {
8486 }
8587
8688
87 static const boost::regex MULTIPART_HEADERS_ENDING("(.*?\r\n)\r\n(.*)");
88 static const boost::regex MULTIPART_HEADERS_LINE(".*?\r\n");
89
90 static void ParseMultipartHeaders(bool& hasLength /* out */,
91 size_t& length /* out */,
92 std::string& contentType /* out */,
93 const char* startHeaders,
94 const char* endHeaders)
95 {
96 hasLength = false;
97 contentType = "application/octet-stream";
98
99 // Loop over the HTTP headers of this multipart item
100 boost::cregex_token_iterator it(startHeaders, endHeaders, MULTIPART_HEADERS_LINE, 0);
101 boost::cregex_token_iterator iteratorEnd;
102
103 for (; it != iteratorEnd; ++it)
104 {
105 const std::string line(*it);
106 size_t colon = line.find(':');
107 size_t eol = line.find('\r');
108
109 if (colon != std::string::npos &&
110 eol != std::string::npos &&
111 colon < eol &&
112 eol + 2 == line.length())
113 {
114 std::string key = Orthanc::Toolbox::StripSpaces(line.substr(0, colon));
115 Orthanc::Toolbox::ToLowerCase(key);
116
117 const std::string value = Orthanc::Toolbox::StripSpaces(line.substr(colon + 1, eol - colon - 1));
118
119 if (key == "content-length")
120 {
121 try
122 {
123 int tmp = boost::lexical_cast<int>(value);
124 if (tmp >= 0)
125 {
126 hasLength = true;
127 length = tmp;
128 }
129 }
130 catch (boost::bad_lexical_cast&)
131 {
132 LogWarning("Unable to parse the Content-Length of a multipart item");
133 }
134 }
135 else if (key == "content-type")
136 {
137 contentType = value;
138 }
139 }
140 }
141 }
142
143
144 static const char* ParseMultipartItem(std::vector<MultipartItem>& result,
145 const char* start,
146 const char* end,
147 const boost::regex& nextSeparator)
148 {
149 // Just before "start", it is guaranteed that "--[BOUNDARY]\r\n" is present
150
151 boost::cmatch what;
152 if (!boost::regex_match(start, end, what, MULTIPART_HEADERS_ENDING, boost::match_perl))
153 {
154 // Cannot find the HTTP headers of this multipart item
155 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
156 }
157
158 // Some aliases for more clarity
159 assert(what[1].first == start);
160 const char* startHeaders = what[1].first;
161 const char* endHeaders = what[1].second;
162 const char* startBody = what[2].first;
163
164 bool hasLength;
165 size_t length;
166 std::string contentType;
167 ParseMultipartHeaders(hasLength, length, contentType, startHeaders, endHeaders);
168
169 boost::cmatch separator;
170
171 if (hasLength)
172 {
173 if (!boost::regex_match(startBody + length, end, separator, nextSeparator, boost::match_perl) ||
174 startBody + length != separator[1].first)
175 {
176 // Cannot find the separator after skipping the "Content-Length" bytes
177 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
178 }
179 }
180 else
181 {
182 if (!boost::regex_match(startBody, end, separator, nextSeparator, boost::match_perl))
183 {
184 // No more occurrence of the boundary separator
185 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
186 }
187 }
188
189 MultipartItem item;
190 item.data_ = startBody;
191 item.size_ = separator[1].first - startBody;
192 item.contentType_ = contentType;
193 result.push_back(item);
194
195 return separator[1].second; // Return the end of the separator
196 }
197
198
199 void ParseMultipartBody(std::vector<MultipartItem>& result,
200 const char* body,
201 const uint64_t bodySize,
202 const std::string& boundary)
203 {
204 // Reference:
205 // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
206
207 result.clear();
208
209 // Look for the first boundary separator in the body (note the "?"
210 // to request non-greedy search)
211 const boost::regex firstSeparator1("--" + boundary + "(--|\r\n).*");
212 const boost::regex firstSeparator2(".*?\r\n--" + boundary + "(--|\r\n).*");
213
214 // Look for the next boundary separator in the body (note the "?"
215 // to request non-greedy search)
216 const boost::regex nextSeparator(".*?(\r\n--" + boundary + ").*");
217
218 const char* end = body + bodySize;
219
220 boost::cmatch what;
221 if (boost::regex_match(body, end, what, firstSeparator1, boost::match_perl | boost::match_single_line) ||
222 boost::regex_match(body, end, what, firstSeparator2, boost::match_perl | boost::match_single_line))
223 {
224 const char* current = what[1].first;
225
226 while (current != NULL &&
227 current + 2 < end)
228 {
229 if (current[0] != '\r' ||
230 current[1] != '\n')
231 {
232 // We reached a separator with a trailing "--", which
233 // means that reading the multipart body is done
234 break;
235 }
236 else
237 {
238 current = ParseMultipartItem(result, current + 2, end, nextSeparator);
239 }
240 }
241 }
242 }
243
89 void ParseAssociativeArray(std::map<std::string, std::string>& target,
90 const Json::Value& value)
91 {
92 if (value.type() != Json::objectValue)
93 {
94 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
95 "The JSON object is not a JSON associative array as expected");
96 }
97
98 Json::Value::Members names = value.getMemberNames();
99
100 for (size_t i = 0; i < names.size(); i++)
101 {
102 if (value[names[i]].type() != Json::stringValue)
103 {
104 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
105 "Value \"" + names[i] + "\" in the associative array "
106 "is not a string as expected");
107 }
108 else
109 {
110 target[names[i]] = value[names[i]].asString();
111 }
112 }
113 }
114
244115
245116 void ParseAssociativeArray(std::map<std::string, std::string>& target,
246117 const Json::Value& value,
252123 "This is not a JSON object");
253124 }
254125
255 if (!value.isMember(key))
256 {
257 return;
258 }
259
260 const Json::Value& tmp = value[key];
261
262 if (tmp.type() != Json::objectValue)
263 {
264 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
265 "The field \"" + key + "\" of a JSON object is "
266 "not a JSON associative array as expected");
267 }
268
269 Json::Value::Members names = tmp.getMemberNames();
270
271 for (size_t i = 0; i < names.size(); i++)
272 {
273 if (tmp[names[i]].type() != Json::stringValue)
274 {
275 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
276 "Some value in the associative array \"" + key +
277 "\" is not a string as expected");
278 }
279 else
280 {
281 target[names[i]] = tmp[names[i]].asString();
282 }
126 if (value.isMember(key))
127 {
128 ParseAssociativeArray(target, value[key]);
129 }
130 else
131 {
132 target.clear();
283133 }
284134 }
285135
297147 else
298148 {
299149 return false;
150 }
151 }
152
153
154 void ParseJsonBody(Json::Value& target,
155 const OrthancPluginHttpRequest* request)
156 {
157 Json::Reader reader;
158 if (!reader.parse(reinterpret_cast<const char*>(request->body),
159 reinterpret_cast<const char*>(request->body) + request->bodySize, target))
160 {
161 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
162 "A JSON file was expected");
163 }
164 }
165
166
167 std::string RemoveMultipleSlashes(const std::string& source)
168 {
169 std::string target;
170 target.reserve(source.size());
171
172 size_t prefix = 0;
173
174 if (boost::starts_with(source, "https://"))
175 {
176 prefix = 8;
177 }
178 else if (boost::starts_with(source, "http://"))
179 {
180 prefix = 7;
181 }
182
183 for (size_t i = 0; i < prefix; i++)
184 {
185 target.push_back(source[i]);
186 }
187
188 bool isLastSlash = false;
189
190 for (size_t i = prefix; i < source.size(); i++)
191 {
192 if (source[i] == '/')
193 {
194 if (!isLastSlash)
195 {
196 target.push_back('/');
197 isLastSlash = true;
198 }
199 }
200 else
201 {
202 target.push_back(source[i]);
203 isLastSlash = false;
204 }
205 }
206
207 return target;
208 }
209
210
211 bool LookupStringValue(std::string& target,
212 const Json::Value& json,
213 const std::string& key)
214 {
215 if (json.type() != Json::objectValue)
216 {
217 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
218 }
219 else if (!json.isMember(key))
220 {
221 return false;
222 }
223 else if (json[key].type() != Json::stringValue)
224 {
225 throw Orthanc::OrthancException(
226 Orthanc::ErrorCode_BadFileFormat,
227 "The field \"" + key + "\" in a JSON object should be a string");
228 }
229 else
230 {
231 target = json[key].asString();
232 return true;
233 }
234 }
235
236
237 bool LookupIntegerValue(int& target,
238 const Json::Value& json,
239 const std::string& key)
240 {
241 if (json.type() != Json::objectValue)
242 {
243 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
244 }
245 else if (!json.isMember(key))
246 {
247 return false;
248 }
249 else if (json[key].type() != Json::intValue &&
250 json[key].type() != Json::uintValue)
251 {
252 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
253 }
254 else
255 {
256 target = json[key].asInt();
257 return true;
258 }
259 }
260
261
262 bool LookupBooleanValue(bool& target,
263 const Json::Value& json,
264 const std::string& key)
265 {
266 if (json.type() != Json::objectValue)
267 {
268 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
269 }
270 else if (!json.isMember(key))
271 {
272 return false;
273 }
274 else if (json[key].type() != Json::booleanValue)
275 {
276 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
277 }
278 else
279 {
280 target = json[key].asBool();
281 return true;
300282 }
301283 }
302284
351333 }
352334
353335
354 std::string GetRoot()
336 std::string GetDicomWebRoot()
355337 {
356338 assert(configuration_.get() != NULL);
357339 std::string root = configuration_->GetStringValue("Root", "/dicom-web/");
371353 return root;
372354 }
373355
356
357 std::string GetOrthancApiRoot()
358 {
359 std::string root = OrthancPlugins::Configuration::GetDicomWebRoot();
360 std::vector<std::string> tokens;
361 Orthanc::Toolbox::TokenizeString(tokens, root, '/');
362
363 int depth = 0;
364 for (size_t i = 0; i < tokens.size(); i++)
365 {
366 if (tokens[i].empty() ||
367 tokens[i] == ".")
368 {
369 // Don't change the depth
370 }
371 else if (tokens[i] == "..")
372 {
373 depth--;
374 }
375 else
376 {
377 depth++;
378 }
379 }
380
381 std::string orthancRoot = "./";
382 for (int i = 0; i < depth; i++)
383 {
384 orthancRoot += "../";
385 }
386
387 return orthancRoot;
388 }
389
374390
375391 std::string GetWadoRoot()
376392 {
394410 }
395411
396412
397 std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
413 static bool IsHttpsProto(const std::string& proto,
414 bool defaultValue)
415 {
416 if (proto == "http")
417 {
418 return false;
419 }
420 else if (proto == "https")
421 {
422 return true;
423 }
424 else
425 {
426 return defaultValue;
427 }
428 }
429
430
431 static bool LookupHttpHeader2(std::string& value,
432 const OrthancPlugins::HttpClient::HttpHeaders& headers,
433 const std::string& name)
434 {
435 for (OrthancPlugins::HttpClient::HttpHeaders::const_iterator
436 it = headers.begin(); it != headers.end(); ++it)
437 {
438 if (boost::iequals(it->first, name))
439 {
440 value = it->second;
441 return false;
442 }
443 }
444
445 return false;
446 }
447
448
449 std::string GetBaseUrl(const OrthancPlugins::HttpClient::HttpHeaders& headers)
398450 {
399451 assert(configuration_.get() != NULL);
400452 std::string host = configuration_->GetStringValue("Host", "");
401 bool ssl = configuration_->GetBooleanValue("Ssl", false);
402
453 bool https = configuration_->GetBooleanValue("Ssl", false);
454
455 std::string forwarded;
403456 if (host.empty() &&
404 !LookupHttpHeader(host, request, "host"))
457 LookupHttpHeader2(forwarded, headers, "forwarded"))
458 {
459 // There is a "Forwarded" HTTP header in the query
460 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
461
462 std::vector<std::string> forwarders;
463 Orthanc::Toolbox::TokenizeString(forwarders, forwarded, ',');
464
465 // Only consider the first forwarder, if any
466 if (!forwarders.empty())
467 {
468 std::vector<std::string> tokens;
469 Orthanc::Toolbox::TokenizeString(tokens, forwarders[0], ';');
470
471 for (size_t j = 0; j < tokens.size(); j++)
472 {
473 std::vector<std::string> args;
474 Orthanc::Toolbox::TokenizeString(args, tokens[j], '=');
475
476 if (args.size() == 2)
477 {
478 std::string key = Orthanc::Toolbox::StripSpaces(args[0]);
479 std::string value = Orthanc::Toolbox::StripSpaces(args[1]);
480
481 Orthanc::Toolbox::ToLowerCase(key);
482 if (key == "host")
483 {
484 host = value;
485 }
486 else if (key == "proto")
487 {
488 https = IsHttpsProto(value, https);
489 }
490 }
491 }
492 }
493 }
494
495 if (host.empty() &&
496 !LookupHttpHeader2(host, headers, "host"))
405497 {
406498 // Should never happen: The "host" header should always be present
407499 // in HTTP requests. Provide a default value anyway.
408500 host = "localhost:8042";
409501 }
410502
411 return (ssl ? "https://" : "http://") + host + GetRoot();
503 return (https ? "https://" : "http://") + host + GetDicomWebRoot();
504 }
505
506
507 std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
508 {
509 OrthancPlugins::HttpClient::HttpHeaders headers;
510
511 std::string value;
512 if (LookupHttpHeader(value, request, "forwarded"))
513 {
514 headers["Forwarded"] = value;
515 }
516
517 if (LookupHttpHeader(value, request, "host"))
518 {
519 headers["Host"] = value;
520 }
521
522 return GetBaseUrl(headers);
412523 }
413524
414525
437548 {
438549 return defaultEncoding_;
439550 }
551
552
553 static bool IsXmlExpected(const std::string& acceptHeader)
554 {
555 std::string accept;
556 Orthanc::Toolbox::ToLowerCase(accept, acceptHeader);
557
558 if (accept == "application/dicom+json" ||
559 accept == "application/json" ||
560 accept == "*/*")
561 {
562 return false;
563 }
564 else if (accept == "application/dicom+xml" ||
565 accept == "application/xml" ||
566 accept == "text/xml")
567 {
568 return true;
569 }
570 else
571 {
572 OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
573 ", will return DICOM+JSON");
574 return false;
575 }
576 }
577
578
579 bool IsXmlExpected(const std::map<std::string, std::string>& headers)
580 {
581 std::map<std::string, std::string>::const_iterator found = headers.find("accept");
582
583 if (found == headers.end())
584 {
585 return false; // By default, return DICOM+JSON
586 }
587 else
588 {
589 return IsXmlExpected(found->second);
590 }
591 }
592
593
594 bool IsXmlExpected(const OrthancPluginHttpRequest* request)
595 {
596 std::string accept;
597
598 if (OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
599 {
600 return IsXmlExpected(accept);
601 }
602 else
603 {
604 return false; // By default, return DICOM+JSON
605 }
606 }
440607 }
441608 }
4444 static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
4545 static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
4646
47 struct MultipartItem
48 {
49 const char* data_;
50 size_t size_;
51 std::string contentType_;
52 };
53
5447 bool LookupHttpHeader(std::string& value,
5548 const OrthancPluginHttpRequest* request,
5649 const std::string& header);
5952 std::map<std::string, std::string>& attributes,
6053 const std::string& header);
6154
62 void ParseMultipartBody(std::vector<MultipartItem>& result,
63 const char* body,
64 const uint64_t bodySize,
65 const std::string& boundary);
66
6755 void ParseAssociativeArray(std::map<std::string, std::string>& target,
6856 const Json::Value& value,
6957 const std::string& key);
7058
59 void ParseAssociativeArray(std::map<std::string, std::string>& target,
60 const Json::Value& value);
61
7162 bool ParseTag(Orthanc::DicomTag& target,
7263 const std::string& name);
64
65 void ParseJsonBody(Json::Value& target,
66 const OrthancPluginHttpRequest* request);
67
68 std::string RemoveMultipleSlashes(const std::string& source);
69
70 bool LookupStringValue(std::string& target,
71 const Json::Value& json,
72 const std::string& key);
73
74 bool LookupIntegerValue(int& target,
75 const Json::Value& json,
76 const std::string& key);
77
78 bool LookupBooleanValue(bool& target,
79 const Json::Value& json,
80 const std::string& key);
7381
7482 namespace Configuration
7583 {
8492 unsigned int GetUnsignedIntegerValue(const std::string& key,
8593 unsigned int defaultValue);
8694
87 std::string GetRoot();
95 std::string GetDicomWebRoot();
96
97 std::string GetOrthancApiRoot();
8898
8999 std::string GetWadoRoot();
90100
101 std::string GetBaseUrl(const std::map<std::string, std::string>& headers);
102
103 // TODO => REMOVE
91104 std::string GetBaseUrl(const OrthancPluginHttpRequest* request);
92105
93106 std::string GetWadoUrl(const std::string& wadoBase,
96109 const std::string& sopInstanceUid);
97110
98111 Orthanc::Encoding GetDefaultEncoding();
112
113 bool IsXmlExpected(const std::map<std::string, std::string>& headers);
114
115 // TODO => REMOVE
116 bool IsXmlExpected(const OrthancPluginHttpRequest* request);
99117 }
100118 }
2727 #include <set>
2828 #include <boost/lexical_cast.hpp>
2929
30 #include <Core/HttpServer/MultipartStreamReader.h>
3031 #include <Core/ChunkedBuffer.h>
3132 #include <Core/Toolbox.h>
33 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
34
35
36 #include <boost/thread.hpp>
37 #include <boost/algorithm/string/predicate.hpp>
38
39
40
41
42 class SingleFunctionJob : public OrthancPlugins::OrthancJob
43 {
44 public:
45 class JobContext : public boost::noncopyable
46 {
47 private:
48 SingleFunctionJob& that_;
49
50 public:
51 explicit JobContext(SingleFunctionJob& that) :
52 that_(that)
53 {
54 }
55
56 void SetContent(const std::string& key,
57 const std::string& value)
58 {
59 that_.SetContent(key, value);
60 }
61
62 void SetProgress(unsigned int position,
63 unsigned int maxPosition)
64 {
65 boost::mutex::scoped_lock lock(that_.mutex_);
66
67 if (maxPosition == 0 ||
68 position > maxPosition)
69 {
70 that_.UpdateProgress(1);
71 }
72 else
73 {
74 that_.UpdateProgress(static_cast<float>(position) / static_cast<float>(maxPosition));
75 }
76 }
77 };
78
79
80 class IFunction : public boost::noncopyable
81 {
82 public:
83 virtual ~IFunction()
84 {
85 }
86
87 virtual void Execute(JobContext& context) = 0;
88 };
89
90
91 class IFunctionFactory : public boost::noncopyable
92 {
93 public:
94 virtual ~IFunctionFactory()
95 {
96 }
97
98 // Called when the job is paused or canceled. WARNING:
99 // "CancelFunction()" will be invoked while "Execute()" is
100 // running. Mutex is probably necessary.
101 virtual void CancelFunction() = 0;
102
103 virtual void PauseFunction() = 0;
104
105 virtual IFunction* CreateFunction() = 0;
106 };
107
108
109 protected:
110 void SetFactory(IFunctionFactory& factory)
111 {
112 boost::mutex::scoped_lock lock(mutex_);
113
114 if (factory_ != NULL)
115 {
116 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
117 }
118 else
119 {
120 factory_ = &factory;
121 }
122 }
123
124
125 private:
126 enum FunctionResult
127 {
128 FunctionResult_Running,
129 FunctionResult_Done,
130 FunctionResult_Failure
131 };
132
133 boost::mutex mutex_;
134 FunctionResult functionResult_; // Can only be modified by the "Worker()" function
135 std::auto_ptr<boost::thread> worker_;
136 Json::Value content_;
137 IFunctionFactory* factory_;
138 bool stopping_;
139
140 void JoinWorker()
141 {
142 assert(factory_ != NULL);
143
144 if (worker_.get() != NULL)
145 {
146 if (worker_->joinable())
147 {
148 worker_->join();
149 }
150
151 worker_.reset();
152 }
153 }
154
155 void StartWorker()
156 {
157 assert(factory_ != NULL);
158
159 if (worker_.get() == NULL)
160 {
161 stopping_ = false;
162 worker_.reset(new boost::thread(Worker, this, factory_));
163 }
164 }
165
166 void SetContent(const std::string& key,
167 const std::string& value)
168 {
169 boost::mutex::scoped_lock lock(mutex_);
170 content_[key] = value;
171 UpdateContent(content_);
172 }
173
174 static void Worker(SingleFunctionJob* job,
175 IFunctionFactory* factory)
176 {
177 assert(job != NULL && factory != NULL);
178
179 JobContext context(*job);
180
181 try
182 {
183 std::auto_ptr<IFunction> function(factory->CreateFunction());
184 function->Execute(context);
185
186 {
187 boost::mutex::scoped_lock lock(job->mutex_);
188 job->functionResult_ = FunctionResult_Done;
189 }
190 }
191 catch (Orthanc::OrthancException& e)
192 {
193 LOG(ERROR) << "Error in a job: " << e.What();
194
195 {
196 boost::mutex::scoped_lock lock(job->mutex_);
197
198 job->functionResult_ = FunctionResult_Failure;
199
200 if (!job->stopping_)
201 {
202 // Don't report exceptions that are a consequence of stopping the function
203 job->content_["FunctionErrorCode"] = e.GetErrorCode();
204 job->content_["FunctionErrorDescription"] = e.What();
205 if (e.HasDetails())
206 {
207 job->content_["FunctionErrorDetails"] = e.GetDetails();
208 }
209 job->UpdateContent(job->content_);
210 }
211 }
212 }
213 }
214
215 public:
216 explicit SingleFunctionJob(const std::string& jobName) :
217 OrthancJob(jobName),
218 functionResult_(FunctionResult_Running),
219 content_(Json::objectValue),
220 factory_(NULL),
221 stopping_(false)
222 {
223 }
224
225 virtual ~SingleFunctionJob()
226 {
227 if (worker_.get() != NULL)
228 {
229 LOG(ERROR) << "Classes deriving from SingleFunctionJob must "
230 << "explicitly call Finalize() in their destructor";
231 Finalize();
232 }
233 }
234
235 void Finalize()
236 {
237 try
238 {
239 Stop(OrthancPluginJobStopReason_Canceled);
240 }
241 catch (Orthanc::OrthancException&)
242 {
243 }
244 }
245
246 virtual OrthancPluginJobStepStatus Step()
247 {
248 if (factory_ == NULL)
249 {
250 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
251 }
252
253 FunctionResult result;
254
255 {
256 boost::mutex::scoped_lock lock(mutex_);
257 result = functionResult_;
258 }
259
260 switch (result)
261 {
262 case FunctionResult_Running:
263 StartWorker();
264 boost::this_thread::sleep(boost::posix_time::milliseconds(500));
265 return OrthancPluginJobStepStatus_Continue;
266
267 case FunctionResult_Done:
268 JoinWorker();
269 return OrthancPluginJobStepStatus_Success;
270
271 case FunctionResult_Failure:
272 JoinWorker();
273 return OrthancPluginJobStepStatus_Failure;
274
275 default:
276 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
277 }
278 }
279
280 virtual void Stop(OrthancPluginJobStopReason reason)
281 {
282 if (factory_ == NULL)
283 {
284 return;
285 }
286 else if (reason == OrthancPluginJobStopReason_Paused ||
287 reason == OrthancPluginJobStopReason_Canceled)
288 {
289 stopping_ = true;
290
291 if (reason == OrthancPluginJobStopReason_Paused)
292 {
293 factory_->PauseFunction();
294 }
295 else
296 {
297 factory_->CancelFunction();
298 }
299
300 JoinWorker();
301
302 // Be ready for the next possible call to "Step()" that will resume the function
303 functionResult_ = FunctionResult_Running;
304 }
305 }
306
307 virtual void Reset()
308 {
309 boost::mutex::scoped_lock lock(mutex_);
310
311 assert(worker_.get() == NULL);
312 functionResult_ = FunctionResult_Running;
313 content_ = Json::objectValue;
314 ClearContent();
315 }
316 };
317
318
319
320
321 static const std::string MULTIPART_RELATED = "multipart/related";
322
323
324
325 static void SubmitJob(OrthancPluginRestOutput* output,
326 OrthancPlugins::OrthancJob* job,
327 const Json::Value& body,
328 bool defaultSynchronous)
329 {
330 std::auto_ptr<OrthancPlugins::OrthancJob> protection(job);
331
332 bool synchronous;
333
334 bool b;
335 if (OrthancPlugins::LookupBooleanValue(b, body, "Synchronous"))
336 {
337 synchronous = b;
338 }
339 else if (OrthancPlugins::LookupBooleanValue(b, body, "Asynchronous"))
340 {
341 synchronous = !b;
342 }
343 else
344 {
345 synchronous = defaultSynchronous;
346 }
347
348 int priority;
349 if (!OrthancPlugins::LookupIntegerValue(priority, body, "Priority"))
350 {
351 priority = 0;
352 }
353
354 Json::Value answer;
355
356 if (synchronous)
357 {
358 OrthancPlugins::OrthancJob::SubmitAndWait(answer, protection.release(), priority);
359 }
360 else
361 {
362 std::string jobId = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
363
364 answer = Json::objectValue;
365 answer["ID"] = jobId;
366 answer["Path"] = OrthancPlugins::RemoveMultipleSlashes
367 ("../" + OrthancPlugins::Configuration::GetOrthancApiRoot() + "/jobs/" + jobId);
368 }
369
370 std::string s = answer.toStyledString();
371 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(),
372 output, s.c_str(), s.size(), "application/json");
373 }
32374
33375
34376 static void AddInstance(std::list<std::string>& target,
35377 const Json::Value& instance)
36378 {
37 if (instance.type() != Json::objectValue ||
38 !instance.isMember("ID") ||
39 instance["ID"].type() != Json::stringValue)
379 std::string id;
380 if (OrthancPlugins::LookupStringValue(id, instance, "ID"))
381 {
382 target.push_back(id);
383 }
384 else
40385 {
41386 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
42 }
43 else
44 {
45 target.push_back(instance["ID"].asString());
46387 }
47388 }
48389
102443
103444
104445
446 static void CheckStowAnswer(const Json::Value& response,
447 const std::string& serverName,
448 size_t instancesCount)
449 {
450 if (response.type() != Json::objectValue ||
451 !response.isMember("00081199"))
452 {
453 throw Orthanc::OrthancException(
454 Orthanc::ErrorCode_NetworkProtocol,
455 "Unable to parse STOW-RS JSON response from DICOMweb server " + serverName);
456 }
457
458 size_t size;
459 if (!GetSequenceSize(size, response, "00081199", true, serverName) ||
460 size != instancesCount)
461 {
462 throw Orthanc::OrthancException(
463 Orthanc::ErrorCode_NetworkProtocol,
464 "The STOW-RS server was only able to receive " +
465 boost::lexical_cast<std::string>(size) + " instances out of " +
466 boost::lexical_cast<std::string>(instancesCount));
467 }
468
469 if (GetSequenceSize(size, response, "00081198", false, serverName) &&
470 size != 0)
471 {
472 throw Orthanc::OrthancException(
473 Orthanc::ErrorCode_NetworkProtocol,
474 "The response from the STOW-RS server contains " +
475 boost::lexical_cast<std::string>(size) +
476 " items in its Failed SOP Sequence (0008,1198) tag");
477 }
478
479 if (GetSequenceSize(size, response, "0008119A", false, serverName) &&
480 size != 0)
481 {
482 throw Orthanc::OrthancException(
483 Orthanc::ErrorCode_NetworkProtocol,
484 "The response from the STOW-RS server contains " +
485 boost::lexical_cast<std::string>(size) +
486 " items in its Other Failures Sequence (0008,119A) tag");
487 }
488 }
489
490
105491 static void ParseStowRequest(std::list<std::string>& instances /* out */,
106492 std::map<std::string, std::string>& httpHeaders /* out */,
107 std::map<std::string, std::string>& queryArguments /* out */,
108 const OrthancPluginHttpRequest* request /* in */)
493 const Json::Value& body /* in */)
109494 {
110495 static const char* RESOURCES = "Resources";
111496 static const char* HTTP_HEADERS = "HttpHeaders";
112 static const char* QUERY_ARGUMENTS = "Arguments";
113
114 Json::Value body;
115 Json::Reader reader;
116 if (!reader.parse(request->body, request->body + request->bodySize, body) ||
117 body.type() != Json::objectValue ||
497
498 if (body.type() != Json::objectValue ||
118499 !body.isMember(RESOURCES) ||
119500 body[RESOURCES].type() != Json::arrayValue)
120501 {
125506 "\" containing an array of resources to be sent");
126507 }
127508
128 OrthancPlugins::ParseAssociativeArray(queryArguments, body, QUERY_ARGUMENTS);
129509 OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
130510
131 Json::Value& resources = body[RESOURCES];
511 const Json::Value& resources = body[RESOURCES];
132512
133513 // Extract information about all the child instances
134514 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
176556 }
177557
178558
179 static void SendStowChunks(const Orthanc::WebServiceParameters& server,
180 const std::map<std::string, std::string>& httpHeaders,
181 const std::map<std::string, std::string>& queryArguments,
182 const std::string& boundary,
183 Orthanc::ChunkedBuffer& chunks,
184 size_t& countInstances,
185 bool force)
186 {
187 unsigned int maxInstances = OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxInstances", 10);
188 size_t maxSize = static_cast<size_t>(OrthancPlugins::Configuration::GetUnsignedIntegerValue("StowMaxSize", 10)) * 1024 * 1024;
189
190 if ((force && countInstances > 0) ||
191 (maxInstances != 0 && countInstances >= maxInstances) ||
192 (maxSize != 0 && chunks.GetNumBytes() >= maxSize))
193 {
194 chunks.AddChunk("\r\n--" + boundary + "--\r\n");
195
196 std::string body;
197 chunks.Flatten(body);
198
199 OrthancPlugins::MemoryBuffer answerBody;
200 std::map<std::string, std::string> answerHeaders;
201
202 std::string uri;
203 OrthancPlugins::UriEncode(uri, "studies", queryArguments);
204
205 OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Post,
206 httpHeaders, uri, body);
207
208 Json::Value response;
209 Json::Reader reader;
210 bool success = reader.parse(reinterpret_cast<const char*>((*answerBody)->data),
211 reinterpret_cast<const char*>((*answerBody)->data) + (*answerBody)->size, response);
212 answerBody.Clear();
213
214 if (!success ||
215 response.type() != Json::objectValue ||
216 !response.isMember("00081199"))
217 {
218 throw Orthanc::OrthancException(
219 Orthanc::ErrorCode_NetworkProtocol,
220 "Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
221 }
222
223 size_t size;
224 if (!GetSequenceSize(size, response, "00081199", true, server.GetUrl()) ||
225 size != countInstances)
226 {
227 throw Orthanc::OrthancException(
228 Orthanc::ErrorCode_NetworkProtocol,
229 "The STOW-RS server was only able to receive " +
230 boost::lexical_cast<std::string>(size) + " instances out of " +
231 boost::lexical_cast<std::string>(countInstances));
232 }
233
234 if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
235 size != 0)
236 {
237 throw Orthanc::OrthancException(
238 Orthanc::ErrorCode_NetworkProtocol,
239 "The response from the STOW-RS server contains " +
240 boost::lexical_cast<std::string>(size) +
241 " items in its Failed SOP Sequence (0008,1198) tag");
242 }
243
244 if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
245 size != 0)
246 {
247 throw Orthanc::OrthancException(
248 Orthanc::ErrorCode_NetworkProtocol,
249 "The response from the STOW-RS server contains " +
250 boost::lexical_cast<std::string>(size) +
251 " items in its Other Failures Sequence (0008,119A) tag");
252 }
253
254 countInstances = 0;
255 }
256 }
559 class StowClientJob :
560 public SingleFunctionJob,
561 private SingleFunctionJob::IFunctionFactory
562 {
563 private:
564 enum Action
565 {
566 Action_None,
567 Action_Pause,
568 Action_Cancel
569 };
570
571 boost::mutex mutex_;
572 std::string serverName_;
573 std::vector<std::string> instances_;
574 OrthancPlugins::HttpClient::HttpHeaders headers_;
575 std::string boundary_;
576 size_t position_;
577 Action action_;
578 size_t networkSize_;
579 bool debug_;
580
581 bool ReadNextInstance(std::string& dicom,
582 JobContext& context)
583 {
584 boost::mutex::scoped_lock lock(mutex_);
585
586 if (action_ != Action_None)
587 {
588 return false;
589 }
590
591 while (position_ < instances_.size())
592 {
593 context.SetProgress(position_, instances_.size());
594
595 size_t i = position_++;
596
597 if (debug_)
598 {
599 boost::this_thread::sleep(boost::posix_time::milliseconds(100));
600 }
601
602 if (OrthancPlugins::RestApiGetString(dicom, "/instances/" + instances_[i] + "/file", false))
603 {
604 networkSize_ += dicom.size();
605 context.SetContent("NetworkSizeMB", boost::lexical_cast<std::string>
606 (networkSize_ / static_cast<uint64_t>(1024 * 1024)));
607
608 return true;
609 }
610 }
611
612 return false;
613 }
614
615
616 class RequestBody : public OrthancPlugins::HttpClient::IRequestBody
617 {
618 private:
619 StowClientJob& that_;
620 JobContext& context_;
621 std::string boundary_;
622 bool done_;
623
624 public:
625 RequestBody(StowClientJob& that,
626 JobContext& context) :
627 that_(that),
628 context_(context),
629 boundary_(that.boundary_),
630 done_(false)
631 {
632 }
633
634 virtual bool ReadNextChunk(std::string& chunk)
635 {
636 if (done_)
637 {
638 context_.SetProgress(1, 1);
639 return false;
640 }
641 else
642 {
643 std::string dicom;
644
645 if (that_.ReadNextInstance(dicom, context_))
646 {
647 chunk = ("--" + boundary_ + "\r\n" +
648 "Content-Type: application/dicom\r\n" +
649 "Content-Length: " + boost::lexical_cast<std::string>(dicom.size()) +
650 "\r\n\r\n" + dicom + "\r\n");
651 }
652 else
653 {
654 done_ = true;
655 chunk = ("--" + boundary_ + "--");
656 }
657
658 //boost::this_thread::sleep(boost::posix_time::seconds(1));
659
660 return true;
661 }
662 }
663 };
664
665
666 class F : public IFunction
667 {
668 private:
669 StowClientJob& that_;
670
671 public:
672 explicit F(StowClientJob& that) :
673 that_(that)
674 {
675 }
676
677 virtual void Execute(JobContext& context)
678 {
679 std::string serverName;
680 size_t startPosition;
681
682 // The lifetime of "body" should be larger than "client"
683 std::auto_ptr<RequestBody> body;
684 std::auto_ptr<OrthancPlugins::HttpClient> client;
685
686 {
687 boost::mutex::scoped_lock lock(that_.mutex_);
688 context.SetContent("InstancesCount", boost::lexical_cast<std::string>(that_.instances_.size()));
689 serverName = that_.serverName_;
690
691 startPosition = that_.position_;
692 body.reset(new RequestBody(that_, context));
693
694 client.reset(new OrthancPlugins::HttpClient);
695 OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(*client, serverName, "/studies");
696 client->SetMethod(OrthancPluginHttpMethod_Post);
697 client->AddHeaders(that_.headers_);
698 }
699
700 OrthancPlugins::HttpClient::HttpHeaders answerHeaders;
701 Json::Value answerBody;
702
703 client->SetBody(*body);
704
705 try
706 {
707 client->Execute(answerHeaders, answerBody);
708 }
709 catch (Orthanc::OrthancException&)
710 {
711 if (client->GetHttpStatus() == 411)
712 {
713 /**
714 * "Length required" error. This might indicate an older
715 * version of Orthanc (<= 1.5.6) that does not support
716 * chunked transfers.
717 **/
718 LOG(ERROR) << "The remote DICOMweb server \"" << serverName << "\" does not support chunked transfers, "
719 << "set configuration option \"ChunkedTransfers\" to \"0\" in the configuration";
720 }
721
722 throw;
723 }
724
725 {
726 boost::mutex::scoped_lock lock(that_.mutex_);
727 size_t endPosition = that_.position_;
728 CheckStowAnswer(answerBody, serverName, endPosition - startPosition);
729
730 if (that_.action_ == Action_Cancel)
731 {
732 that_.position_ = 0;
733 }
734 }
735 }
736 };
737
738
739 virtual void CancelFunction()
740 {
741 boost::mutex::scoped_lock lock(mutex_);
742 action_ = Action_Cancel;
743 }
744
745
746 virtual void PauseFunction()
747 {
748 boost::mutex::scoped_lock lock(mutex_);
749 action_ = Action_Pause;
750 }
751
752
753 virtual IFunction* CreateFunction()
754 {
755 action_ = Action_None;
756 return new F(*this);
757 }
758
759
760 public:
761 StowClientJob(const std::string& serverName,
762 const std::list<std::string>& instances,
763 const OrthancPlugins::HttpClient::HttpHeaders& headers) :
764 SingleFunctionJob("DicomWebStowClient"),
765 serverName_(serverName),
766 headers_(headers),
767 position_(0),
768 action_(Action_None),
769 networkSize_(0),
770 debug_(false)
771 {
772 SetFactory(*this);
773
774 instances_.reserve(instances.size());
775
776 for (std::list<std::string>::const_iterator
777 it = instances.begin(); it != instances.end(); ++it)
778 {
779 instances_.push_back(*it);
780 }
781
782 {
783 OrthancPlugins::OrthancString tmp;
784 tmp.Assign(OrthancPluginGenerateUuid(OrthancPlugins::GetGlobalContext()));
785 tmp.ToString(boundary_);
786 }
787
788 boundary_ = (boundary_ + "-" + boundary_); // Make the boundary longer
789
790 headers_["Accept"] = "application/dicom+json";
791 headers_["Expect"] = "";
792 headers_["Content-Type"] = "multipart/related; type=\"application/dicom\"; boundary=" + boundary_;
793 }
794
795 void SetDebug(bool debug)
796 {
797 debug_ = debug;
798 }
799 };
800
257801
258802
259803 void StowClient(OrthancPluginRestOutput* output,
262806 {
263807 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
264808
265 if (request->groupsCount != 1)
266 {
267 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
268 }
269
270809 if (request->method != OrthancPluginHttpMethod_Post)
271810 {
272811 OrthancPluginSendMethodNotAllowed(context, output, "POST");
273812 return;
274813 }
275814
276 Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
277
278 std::string boundary;
279
280 {
281 char* uuid = OrthancPluginGenerateUuid(context);
282 try
283 {
284 boundary.assign(uuid);
285 }
286 catch (...)
287 {
288 OrthancPluginFreeString(context, uuid);
289 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
290 }
291
292 OrthancPluginFreeString(context, uuid);
293 }
294
295 std::string mime = "multipart/related; type=\"application/dicom\"; boundary=" + boundary;
296
297 std::map<std::string, std::string> queryArguments;
815 if (request->groupsCount != 1)
816 {
817 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
818 }
819
820 std::string serverName(request->groups[0]);
821
822 Json::Value body;
823 OrthancPlugins::ParseJsonBody(body, request);
824
825 std::list<std::string> instances;
298826 std::map<std::string, std::string> httpHeaders;
299 httpHeaders["Accept"] = "application/dicom+json";
300 httpHeaders["Expect"] = "";
301 httpHeaders["Content-Type"] = mime;
302
303 std::list<std::string> instances;
304 ParseStowRequest(instances, httpHeaders, queryArguments, request);
827 ParseStowRequest(instances, httpHeaders, body);
305828
306829 OrthancPlugins::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) +
307 " instances using STOW-RS to DICOMweb server: " + server.GetUrl());
308
309 Orthanc::ChunkedBuffer chunks;
310 size_t countInstances = 0;
311
312 for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it)
313 {
314 OrthancPlugins::MemoryBuffer dicom;
315 if (dicom.RestApiGet("/instances/" + *it + "/file", false))
316 {
317 chunks.AddChunk("\r\n--" + boundary + "\r\n" +
318 "Content-Type: application/dicom\r\n" +
319 "Content-Length: " + boost::lexical_cast<std::string>(dicom.GetSize()) +
320 "\r\n\r\n");
321 chunks.AddChunk(dicom.GetData(), dicom.GetSize());
322 countInstances ++;
323
324 SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, false);
325 }
326 }
327
328 SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, true);
329
330 std::string answer = "{}\n";
331 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
830 " instances using STOW-RS to DICOMweb server: " + serverName);
831
832 std::auto_ptr<StowClientJob> job(new StowClientJob(serverName, instances, httpHeaders));
833
834 bool debug;
835 if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
836 {
837 job->SetDebug(debug);
838 }
839
840 Json::Value answer;
841 SubmitJob(output, job.release(), body,
842 true /* synchronous by default, for compatibility with <= 0.6 */);
332843 }
333844
334845
335 static bool GetStringValue(std::string& target,
336 const Json::Value& json,
337 const std::string& key)
338 {
339 if (json.type() != Json::objectValue)
340 {
341 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
342 }
343 else if (!json.isMember(key))
344 {
345 target.clear();
346 return false;
347 }
348 else if (json[key].type() != Json::stringValue)
349 {
350 throw Orthanc::OrthancException(
351 Orthanc::ErrorCode_BadFileFormat,
352 "The field \"" + key + "\" in a JSON object should be a string");
353 }
354 else
355 {
356 target = json[key].asString();
357 return true;
358 }
846
847 static void ParseGetFromServer(std::string& uri,
848 std::map<std::string, std::string>& additionalHeaders,
849 const Json::Value& resource)
850 {
851 static const char* URI = "Uri";
852 static const char* HTTP_HEADERS = "HttpHeaders";
853 static const char* GET_ARGUMENTS = "Arguments";
854
855 std::string tmp;
856 if (resource.type() != Json::objectValue ||
857 !OrthancPlugins::LookupStringValue(tmp, resource, URI))
858 {
859 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
860 "A request to the DICOMweb client must provide a JSON object "
861 "with the field \"Uri\" containing the URI of interest");
862 }
863
864 std::map<std::string, std::string> getArguments;
865 OrthancPlugins::ParseAssociativeArray(getArguments, resource, GET_ARGUMENTS);
866 OrthancPlugins::DicomWebServers::UriEncode(uri, tmp, getArguments);
867
868 OrthancPlugins::ParseAssociativeArray(additionalHeaders, resource, HTTP_HEADERS);
359869 }
870
871
872
873 static void ConfigureGetFromServer(OrthancPlugins::HttpClient& client,
874 const OrthancPluginHttpRequest* request)
875 {
876 if (request->method != OrthancPluginHttpMethod_Post)
877 {
878 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
879 }
880
881 Json::Value body;
882 OrthancPlugins::ParseJsonBody(body, request);
883
884 std::string uri;
885 std::map<std::string, std::string> additionalHeaders;
886 ParseGetFromServer(uri, additionalHeaders, body);
887
888 OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(client, request->groups[0], uri);
889 client.AddHeaders(additionalHeaders);
890 }
891
360892
361893
362894 void GetFromServer(OrthancPluginRestOutput* output,
363895 const char* /*url*/,
364896 const OrthancPluginHttpRequest* request)
365897 {
366 static const char* URI = "Uri";
367 static const char* HTTP_HEADERS = "HttpHeaders";
368 static const char* GET_ARGUMENTS = "Arguments";
369
370898 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
371899
372900 if (request->method != OrthancPluginHttpMethod_Post)
375903 return;
376904 }
377905
378 Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
379
380 std::string tmp;
381 Json::Value body;
382 Json::Reader reader;
383 if (!reader.parse(request->body, request->body + request->bodySize, body) ||
384 body.type() != Json::objectValue ||
385 !GetStringValue(tmp, body, URI))
386 {
387 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
388 "A request to the DICOMweb client must provide a JSON object "
389 "with the field \"Uri\" containing the URI of interest");
390 }
391
392 std::map<std::string, std::string> getArguments;
393 OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
394
395 std::string uri;
396 OrthancPlugins::UriEncode(uri, tmp, getArguments);
397
398 std::map<std::string, std::string> httpHeaders;
399 OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
400
401 OrthancPlugins::MemoryBuffer answerBody;
906 OrthancPlugins::HttpClient client;
907 ConfigureGetFromServer(client, request);
908
402909 std::map<std::string, std::string> answerHeaders;
403 OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
910 std::string answer;
911 client.Execute(answerHeaders, answer);
404912
405913 std::string contentType = "application/octet-stream";
406914
414922 {
415923 contentType = it->second;
416924 }
417 else if (key == "transfer-encoding")
418 {
419 // Do not forward this header
925 else if (key == "transfer-encoding" ||
926 key == "content-length" ||
927 key == "connection")
928 {
929 // Do not forward these headers
420930 }
421931 else
422932 {
424934 }
425935 }
426936
427 OrthancPluginAnswerBuffer(context, output,
428 reinterpret_cast<const char*>(answerBody.GetData()),
429 answerBody.GetSize(), contentType.c_str());
937 OrthancPluginAnswerBuffer(context, output, answer.empty() ? NULL : answer.c_str(),
938 answer.size(), contentType.c_str());
430939 }
431940
432941
433
434 static void RetrieveFromServerInternal(std::set<std::string>& instances,
435 const Orthanc::WebServiceParameters& server,
436 const std::map<std::string, std::string>& httpHeaders,
437 const std::map<std::string, std::string>& getArguments,
438 const Json::Value& resource)
439 {
440 static const std::string STUDY = "Study";
441 static const std::string SERIES = "Series";
442 static const std::string INSTANCE = "Instance";
443 static const std::string MULTIPART_RELATED = "multipart/related";
444 static const std::string APPLICATION_DICOM = "application/dicom";
445
446 if (resource.type() != Json::objectValue)
447 {
448 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
449 "Resources of interest for the DICOMweb WADO-RS Retrieve client "
450 "must be provided as a JSON object");
451 }
452
453 std::string study, series, instance;
454 if (!GetStringValue(study, resource, STUDY) ||
455 study.empty())
456 {
457 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
458 "A non-empty \"" + STUDY + "\" field is mandatory for the "
459 "DICOMweb WADO-RS Retrieve client");
460 }
461
462 GetStringValue(series, resource, SERIES);
463 GetStringValue(instance, resource, INSTANCE);
464
465 if (series.empty() &&
466 !instance.empty())
467 {
468 throw Orthanc::OrthancException(
469 Orthanc::ErrorCode_BadFileFormat,
470 "When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb "
471 "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory");
472 }
473
474 std::string tmpUri = "studies/" + study;
475 if (!series.empty())
476 {
477 tmpUri += "/series/" + series;
478 if (!instance.empty())
479 {
480 tmpUri += "/instances/" + instance;
481 }
482 }
483
484 std::string uri;
485 OrthancPlugins::UriEncode(uri, tmpUri, getArguments);
486
487 OrthancPlugins::MemoryBuffer answerBody;
942 void GetFromServer(Json::Value& result,
943 const OrthancPluginHttpRequest* request)
944 {
945 OrthancPlugins::HttpClient client;
946 ConfigureGetFromServer(client, request);
947
488948 std::map<std::string, std::string> answerHeaders;
489 OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
490
491 std::string contentTypeFull;
492 std::vector<std::string> contentType;
493 for (std::map<std::string, std::string>::const_iterator
494 it = answerHeaders.begin(); it != answerHeaders.end(); ++it)
495 {
496 std::string s = Orthanc::Toolbox::StripSpaces(it->first);
497 Orthanc::Toolbox::ToLowerCase(s);
498 if (s == "content-type")
499 {
500 contentTypeFull = it->second;
501 Orthanc::Toolbox::TokenizeString(contentType, it->second, ';');
502 break;
503 }
504 }
505
506 OrthancPlugins::LogInfo("Got " + boost::lexical_cast<std::string>(answerBody.GetSize()) +
507 " bytes from a WADO-RS query with content type: " + contentTypeFull);
508
509 if (contentType.empty())
510 {
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
512 "No Content-Type provided by the remote WADO-RS server");
513 }
514
515 Orthanc::Toolbox::ToLowerCase(contentType[0]);
516 if (Orthanc::Toolbox::StripSpaces(contentType[0]) != MULTIPART_RELATED)
517 {
518 throw Orthanc::OrthancException(
519 Orthanc::ErrorCode_NetworkProtocol,
520 "The remote WADO-RS server answers with a \"" + contentType[0] +
521 "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
522 }
523
524 std::string type, boundary;
525 for (size_t i = 1; i < contentType.size(); i++)
526 {
527 std::vector<std::string> tokens;
528 Orthanc::Toolbox::TokenizeString(tokens, contentType[i], '=');
529
530 if (tokens.size() == 2)
531 {
532 std::string s = Orthanc::Toolbox::StripSpaces(tokens[0]);
533 Orthanc::Toolbox::ToLowerCase(s);
534
535 if (s == "type")
536 {
537 type = Orthanc::Toolbox::StripSpaces(tokens[1]);
538
539 // This covers the case where the content-type is quoted,
540 // which COULD be the case
541 // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1
542 size_t len = type.length();
543 if (len >= 2 &&
544 type[0] == '"' &&
545 type[len - 1] == '"')
546 {
547 type = type.substr(1, len - 2);
548 }
549
550 Orthanc::Toolbox::ToLowerCase(type);
551 }
552 else if (s == "boundary")
553 {
554 boundary = Orthanc::Toolbox::StripSpaces(tokens[1]);
555 }
556 }
557 }
558
559 // Strip the trailing and heading quotes if present
560 if (boundary.length() > 2 &&
561 boundary[0] == '"' &&
562 boundary[boundary.size() - 1] == '"')
563 {
564 boundary = boundary.substr(1, boundary.size() - 2);
565 }
566
567 OrthancPlugins::LogInfo(" Parsing the multipart content type: " + type +
568 " with boundary: " + boundary);
569
570 if (type != APPLICATION_DICOM)
571 {
572 throw Orthanc::OrthancException(
573 Orthanc::ErrorCode_NetworkProtocol,
574 "The remote WADO-RS server answers with a \"" + type +
575 "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected");
576 }
577
578 if (boundary.empty())
579 {
580 throw Orthanc::OrthancException(
581 Orthanc::ErrorCode_NetworkProtocol,
582 "The remote WADO-RS server does not provide a boundary for its multipart answer");
583 }
584
585 std::vector<OrthancPlugins::MultipartItem> parts;
586 OrthancPlugins::ParseMultipartBody(parts,
587 reinterpret_cast<const char*>(answerBody.GetData()),
588 answerBody.GetSize(), boundary);
589
590 OrthancPlugins::LogInfo("The remote WADO-RS server has provided " +
591 boost::lexical_cast<std::string>(parts.size()) +
592 " DICOM instances");
593
594 for (size_t i = 0; i < parts.size(); i++)
595 {
596 std::vector<std::string> tokens;
597 Orthanc::Toolbox::TokenizeString(tokens, parts[i].contentType_, ';');
598
599 std::string partType;
600 if (tokens.size() > 0)
601 {
602 partType = Orthanc::Toolbox::StripSpaces(tokens[0]);
603 }
604
605 if (partType != APPLICATION_DICOM)
949 client.Execute(answerHeaders, result);
950 }
951
952
953
954
955
956 class WadoRetrieveAnswer :
957 public OrthancPlugins::HttpClient::IAnswer,
958 private Orthanc::MultipartStreamReader::IHandler
959 {
960 private:
961 enum State
962 {
963 State_Headers,
964 State_Body,
965 State_Canceled
966 };
967
968 bool debug_;
969 boost::mutex mutex_;
970 State state_;
971 std::list<std::string> instances_;
972 std::auto_ptr<Orthanc::MultipartStreamReader> reader_;
973 uint64_t networkSize_;
974
975 virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
976 const void* part,
977 size_t size)
978 {
979 std::string contentType;
980 if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers))
981 {
982 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
983 "Missing Content-Type for a part of WADO-RS answer");
984 }
985
986 size_t pos = contentType.find(';');
987 if (pos != std::string::npos)
988 {
989 contentType = contentType.substr(0, pos);
990 }
991
992 contentType = Orthanc::Toolbox::StripSpaces(contentType);
993 if (!boost::iequals(contentType, "application/dicom"))
606994 {
607995 throw Orthanc::OrthancException(
608996 Orthanc::ErrorCode_NetworkProtocol,
609 "The remote WADO-RS server has provided a non-DICOM file in its multipart answer"
610 " (content type: " + parts[i].contentType_ + ")");
997 "Parts of a WADO-RS retrieve should have \"application/dicom\" type, but received: " + contentType);
611998 }
612999
6131000 OrthancPlugins::MemoryBuffer tmp;
614 tmp.RestApiPost("/instances", parts[i].data_, parts[i].size_, false);
1001 tmp.RestApiPost("/instances", part, size, false);
6151002
6161003 Json::Value result;
6171004 tmp.ToJson(result);
6181005
619 if (result.type() != Json::objectValue ||
620 !result.isMember("ID") ||
621 result["ID"].type() != Json::stringValue)
1006 std::string id;
1007 if (OrthancPlugins::LookupStringValue(id, result, "ID"))
1008 {
1009 instances_.push_back(id);
1010 }
1011 else
6221012 {
6231013 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
6241014 }
1015
1016 if (debug_)
1017 {
1018 boost::this_thread::sleep(boost::posix_time::milliseconds(50));
1019 }
1020 }
1021
1022 public:
1023 WadoRetrieveAnswer() :
1024 debug_(false),
1025 state_(State_Headers),
1026 networkSize_(0)
1027 {
1028 }
1029
1030 virtual ~WadoRetrieveAnswer()
1031 {
1032 }
1033
1034 void SetDebug(bool debug)
1035 {
1036 debug_ = debug;
1037 }
1038
1039 void Close()
1040 {
1041 boost::mutex::scoped_lock lock(mutex_);
1042
1043 if (state_ != State_Canceled &&
1044 reader_.get() != NULL)
1045 {
1046 reader_->CloseStream();
1047 }
1048 }
1049
1050 virtual void AddHeader(const std::string& key,
1051 const std::string& value)
1052 {
1053 boost::mutex::scoped_lock lock(mutex_);
1054
1055 if (state_ == State_Canceled)
1056 {
1057 return;
1058 }
1059 else if (state_ != State_Headers)
1060 {
1061 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1062 }
1063
1064 if (boost::iequals(key, "Content-Type"))
1065 {
1066 if (reader_.get() != NULL)
1067 {
1068 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
1069 "Received twice a Content-Type header in WADO-RS");
1070 }
1071
1072 std::string contentType, subType, boundary;
1073
1074 if (!Orthanc::MultipartStreamReader::ParseMultipartContentType
1075 (contentType, subType, boundary, value))
1076 {
1077 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
1078 "Cannot parse the Content-Type for WADO-RS: " + value);
1079 }
1080
1081 if (!boost::iequals(contentType, MULTIPART_RELATED))
1082 {
1083 throw Orthanc::OrthancException(
1084 Orthanc::ErrorCode_NetworkProtocol,
1085 "The remote WADO-RS server answers with a \"" + contentType +
1086 "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
1087 }
1088
1089 reader_.reset(new Orthanc::MultipartStreamReader(boundary));
1090 reader_->SetHandler(*this);
1091
1092 if (debug_)
1093 {
1094 reader_->SetBlockSize(1024 * 64);
1095 }
1096 }
1097 }
1098
1099 virtual void AddChunk(const void* data,
1100 size_t size)
1101 {
1102 boost::mutex::scoped_lock lock(mutex_);
1103
1104 if (state_ == State_Canceled)
1105 {
1106 throw Orthanc::OrthancException(Orthanc::ErrorCode_CanceledJob);
1107 }
1108 else if (reader_.get() == NULL)
1109 {
1110 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
1111 "No Content-Type provided by the remote WADO-RS server");
1112 }
6251113 else
6261114 {
627 instances.insert(result["ID"].asString());
628 }
629 }
1115 state_ = State_Body;
1116 networkSize_ += size;
1117 reader_->AddChunk(data, size);
1118 }
1119 }
1120
1121 void GetReceivedInstances(std::list<std::string>& target)
1122 {
1123 boost::mutex::scoped_lock lock(mutex_);
1124 target = instances_;
1125 }
1126
1127 void Cancel()
1128 {
1129 boost::mutex::scoped_lock lock(mutex_);
1130 LOG(ERROR) << "A WADO-RS retrieve job has been canceled, expect \"Error in the network protocol\" errors";
1131 state_ = State_Canceled;
1132 }
1133
1134 uint64_t GetNetworkSize()
1135 {
1136 boost::mutex::scoped_lock lock(mutex_);
1137 return networkSize_;
1138 }
1139 };
1140
1141
1142
1143
1144
1145 class WadoRetrieveJob :
1146 public SingleFunctionJob,
1147 private SingleFunctionJob::IFunctionFactory
1148 {
1149 private:
1150 class Resource : public boost::noncopyable
1151 {
1152 private:
1153 std::string uri_;
1154 std::map<std::string, std::string> additionalHeaders_;
1155
1156 public:
1157 explicit Resource(const std::string& uri) :
1158 uri_(uri)
1159 {
1160 }
1161
1162 Resource(const std::string& uri,
1163 const std::map<std::string, std::string>& additionalHeaders) :
1164 uri_(uri),
1165 additionalHeaders_(additionalHeaders)
1166 {
1167 }
1168
1169 const std::string& GetUri() const
1170 {
1171 return uri_;
1172 }
1173
1174 const std::map<std::string, std::string>& GetAdditionalHeaders() const
1175 {
1176 return additionalHeaders_;
1177 }
1178 };
1179
1180
1181 class F : public IFunction
1182 {
1183 private:
1184 WadoRetrieveJob& that_;
1185
1186 public:
1187 explicit F(WadoRetrieveJob& that) :
1188 that_(that)
1189 {
1190 }
1191
1192 virtual void Execute(JobContext& context)
1193 {
1194 for (;;)
1195 {
1196 OrthancPlugins::HttpClient client;
1197
1198 if (that_.SetupNextResource(client, context))
1199 {
1200 client.Execute(*that_.answer_);
1201 that_.CloseResource(context);
1202 }
1203 else
1204 {
1205 return; // We're done
1206 }
1207 }
1208 }
1209 };
1210
1211
1212 boost::mutex mutex_;
1213 std::string serverName_;
1214 size_t position_;
1215 std::vector<Resource*> resources_;
1216 bool stopped_;
1217 std::list<std::string> retrievedInstances_;
1218 std::auto_ptr<WadoRetrieveAnswer> answer_;
1219 uint64_t networkSize_;
1220 bool debug_;
1221
1222 bool SetupNextResource(OrthancPlugins::HttpClient& client,
1223 JobContext& context)
1224 {
1225 boost::mutex::scoped_lock lock(mutex_);
1226
1227 if (stopped_ ||
1228 position_ == resources_.size())
1229 {
1230 return false;
1231 }
1232 else
1233 {
1234 context.SetProgress(position_, resources_.size());
1235
1236 answer_.reset(new WadoRetrieveAnswer);
1237 answer_->SetDebug(debug_);
1238
1239 const Resource* resource = resources_[position_++];
1240 if (resource == NULL)
1241 {
1242 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1243 }
1244
1245 OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient
1246 (client, serverName_, resource->GetUri());
1247 client.AddHeaders(resource->GetAdditionalHeaders());
1248
1249 return true;
1250 }
1251 }
1252
1253
1254 void CloseResource(JobContext& context)
1255 {
1256 boost::mutex::scoped_lock lock(mutex_);
1257 answer_->Close();
1258
1259 std::list<std::string> instances;
1260 answer_->GetReceivedInstances(instances);
1261 networkSize_ += answer_->GetNetworkSize();
1262
1263 answer_.reset();
1264
1265 retrievedInstances_.splice(retrievedInstances_.end(), instances);
1266
1267 context.SetProgress(position_, resources_.size());
1268 context.SetContent("NetworkUsageMB", boost::lexical_cast<std::string>
1269 (networkSize_ / static_cast<uint64_t>(1024 * 1024)));
1270 context.SetContent("ReceivedInstancesCount", boost::lexical_cast<std::string>(retrievedInstances_.size()));
1271 }
1272
1273
1274 virtual void CancelFunction()
1275 {
1276 boost::mutex::scoped_lock lock(mutex_);
1277
1278 stopped_ = true;
1279 if (answer_.get() != NULL)
1280 {
1281 answer_->Cancel();
1282 }
1283 }
1284
1285 virtual void PauseFunction()
1286 {
1287 // This type of job cannot be paused
1288 CancelFunction();
1289 }
1290
1291 virtual IFunction* CreateFunction()
1292 {
1293 // This type of job cannot be paused: If restarting, always go
1294 // back to the beginning
1295
1296 stopped_ = false;
1297 position_ = 0;
1298 retrievedInstances_.clear();
1299
1300 return new F(*this);
1301 }
1302
1303 public:
1304 explicit WadoRetrieveJob(const std::string& serverName) :
1305 SingleFunctionJob("DicomWebWadoRetrieveClient"),
1306 serverName_(serverName),
1307 position_(0),
1308 stopped_(false),
1309 networkSize_(0),
1310 debug_(false)
1311 {
1312 SetFactory(*this);
1313 }
1314
1315 virtual ~WadoRetrieveJob()
1316 {
1317 SingleFunctionJob::Finalize();
1318
1319 for (size_t i = 0; i < resources_.size(); i++)
1320 {
1321 assert(resources_[i] != NULL);
1322 delete resources_[i];
1323 }
1324 }
1325
1326 void SetDebug(bool debug)
1327 {
1328 debug_ = debug;
1329 }
1330
1331 void AddResource(const std::string& uri)
1332 {
1333 resources_.push_back(new Resource(uri));
1334 }
1335
1336 void AddResource(const std::string& uri,
1337 const std::map<std::string, std::string>& additionalHeaders)
1338 {
1339 resources_.push_back(new Resource(uri, additionalHeaders));
1340 }
1341
1342 void AddResourceFromRequest(const Json::Value& resource)
1343 {
1344 std::string uri;
1345 std::map<std::string, std::string> additionalHeaders;
1346 ParseGetFromServer(uri, additionalHeaders, resource);
1347
1348 resources_.push_back(new Resource(uri, additionalHeaders));
1349 }
1350 };
1351
1352
1353 void WadoRetrieveClient(OrthancPluginRestOutput* output,
1354 const char* url,
1355 const OrthancPluginHttpRequest* request)
1356 {
1357 if (request->method != OrthancPluginHttpMethod_Post)
1358 {
1359 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1360 }
1361
1362 if (request->groupsCount != 1)
1363 {
1364 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
1365 }
1366
1367 std::string serverName(request->groups[0]);
1368
1369 Json::Value body;
1370 OrthancPlugins::ParseJsonBody(body, request);
1371
1372 std::auto_ptr<WadoRetrieveJob> job(new WadoRetrieveJob(serverName));
1373 job->AddResourceFromRequest(body);
1374
1375 bool debug;
1376 if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
1377 {
1378 job->SetDebug(debug);
1379 }
1380
1381 SubmitJob(output, job.release(), body, false /* asynchronous by default */);
6301382 }
6311383
6321384
6331385
6341386 void RetrieveFromServer(OrthancPluginRestOutput* output,
635 const char* /*url*/,
1387 const char* url,
6361388 const OrthancPluginHttpRequest* request)
6371389 {
638 static const std::string RESOURCES("Resources");
639 static const char* HTTP_HEADERS = "HttpHeaders";
640 static const std::string GET_ARGUMENTS = "Arguments";
641
642 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
1390 static const char* const GET_ARGUMENTS = "GetArguments";
1391 static const char* const HTTP_HEADERS = "HttpHeaders";
1392 static const char* const RESOURCES = "Resources";
1393 static const char* const STUDY = "Study";
1394 static const char* const SERIES = "Series";
1395 static const char* const INSTANCE = "Instance";
6431396
6441397 if (request->method != OrthancPluginHttpMethod_Post)
6451398 {
646 OrthancPluginSendMethodNotAllowed(context, output, "POST");
647 return;
648 }
649
650 Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
1399 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1400 }
1401
1402 if (request->groupsCount != 1)
1403 {
1404 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
1405 }
1406
1407 std::string serverName(request->groups[0]);
6511408
6521409 Json::Value body;
653 Json::Reader reader;
654 if (!reader.parse(request->body, request->body + request->bodySize, body) ||
655 body.type() != Json::objectValue ||
1410 OrthancPlugins::ParseJsonBody(body, request);
1411
1412 std::map<std::string, std::string> getArguments;
1413 OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
1414
1415 std::map<std::string, std::string> additionalHeaders;
1416 OrthancPlugins::ParseAssociativeArray(additionalHeaders, body, HTTP_HEADERS);
1417
1418 std::auto_ptr<WadoRetrieveJob> job(new WadoRetrieveJob(serverName));
1419
1420 if (body.type() != Json::objectValue ||
6561421 !body.isMember(RESOURCES) ||
6571422 body[RESOURCES].type() != Json::arrayValue)
6581423 {
659 throw Orthanc::OrthancException(
660 Orthanc::ErrorCode_BadFileFormat,
661 "A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object "
662 "with the field \"" + RESOURCES + "\" containing an array of resources");
663 }
664
665 std::map<std::string, std::string> httpHeaders;
666 OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
667
668 std::map<std::string, std::string> getArguments;
669 OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
670
671
672 std::set<std::string> instances;
673 for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++)
674 {
675 RetrieveFromServerInternal(instances, server, httpHeaders, getArguments, body[RESOURCES][i]);
676 }
677
678 Json::Value status = Json::objectValue;
679 status["Instances"] = Json::arrayValue;
680
681 for (std::set<std::string>::const_iterator
682 it = instances.begin(); it != instances.end(); ++it)
683 {
684 status["Instances"].append(*it);
685 }
686
687 std::string s = status.toStyledString();
688 OrthancPluginAnswerBuffer(context, output, s.c_str(), s.size(), "application/json");
1424 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
1425 "The body must be a JSON object containing an array \"" +
1426 std::string(RESOURCES) + "\"");
1427 }
1428
1429 const Json::Value& resources = body[RESOURCES];
1430
1431 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
1432 {
1433 std::string study;
1434 if (!OrthancPlugins::LookupStringValue(study, resources[i], STUDY))
1435 {
1436 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
1437 "Missing \"Study\" field in the body");
1438 }
1439
1440 std::string series;
1441 if (!OrthancPlugins::LookupStringValue(series, resources[i], SERIES))
1442 {
1443 series.clear();
1444 }
1445
1446 std::string instance;
1447 if (!OrthancPlugins::LookupStringValue(instance, resources[i], INSTANCE))
1448 {
1449 instance.clear();
1450 }
1451
1452 if (series.empty() &&
1453 !instance.empty())
1454 {
1455 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
1456 "Missing \"Series\" field in the body, as \"Instance\" is present");
1457 }
1458
1459 std::string tmp = "/studies/" + study;
1460
1461 if (!series.empty())
1462 {
1463 tmp += "/series/" + series;
1464 }
1465
1466 if (!instance.empty())
1467 {
1468 tmp += "/instances/" + instance;
1469 }
1470
1471 std::string uri;
1472 OrthancPlugins::DicomWebServers::UriEncode(uri, tmp, getArguments);
1473
1474 job->AddResource(uri, additionalHeaders);
1475 }
1476
1477 bool debug;
1478 if (OrthancPlugins::LookupBooleanValue(debug, body, "Debug"))
1479 {
1480 job->SetDebug(debug);
1481 }
1482
1483 SubmitJob(output, job.release(), body,
1484 true /* synchronous by default, for compatibility with <= 0.6 */);
6891485 }
1486
3131 const char* /*url*/,
3232 const OrthancPluginHttpRequest* request);
3333
34 void GetFromServer(Json::Value& result,
35 const OrthancPluginHttpRequest* request);
36
37 // TODO => Mark as deprecated
3438 void RetrieveFromServer(OrthancPluginRestOutput* output,
3539 const char* /*url*/,
3640 const OrthancPluginHttpRequest* request);
41
42 void WadoRetrieveClient(OrthancPluginRestOutput* output,
43 const char* url,
44 const OrthancPluginHttpRequest* request);
2424
2525 #include <Core/Toolbox.h>
2626
27 #include <boost/algorithm/string/predicate.hpp>
28
2729 namespace OrthancPlugins
2830 {
2931 void DicomWebServers::Clear()
114116 }
115117 }
116118
119
120 void DicomWebServers::ConfigureHttpClient(HttpClient& client,
121 const std::string& name,
122 const std::string& uri)
123 {
124 static const char* HAS_CHUNKED_TRANSFERS = "ChunkedTransfers";
125
126 const Orthanc::WebServiceParameters parameters = GetServer(name);
127
128 client.SetUrl(RemoveMultipleSlashes(parameters.GetUrl() + "/" + uri));
129 client.SetHeaders(parameters.GetHttpHeaders());
130
131 if (!parameters.GetUsername().empty())
132 {
133 client.SetCredentials(parameters.GetUsername(), parameters.GetPassword());
134 }
135
136 // By default, enable chunked transfers
137 client.SetChunkedTransfersAllowed(
138 parameters.GetBooleanUserProperty(HAS_CHUNKED_TRANSFERS, true));
139 }
140
141
142 void DicomWebServers::DeleteServer(const std::string& name)
143 {
144 boost::mutex::scoped_lock lock(mutex_);
145
146 Servers::iterator found = servers_.find(name);
147
148 if (found == servers_.end())
149 {
150 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
151 "Unknown DICOMweb server: " + name);
152 }
153 else
154 {
155 assert(found->second != NULL);
156 delete found->second;
157 servers_.erase(found);
158 }
159 }
160
161
162 void DicomWebServers::SetServer(const std::string& name,
163 const Orthanc::WebServiceParameters& parameters)
164 {
165 boost::mutex::scoped_lock lock(mutex_);
166
167 Servers::iterator found = servers_.find(name);
168
169 if (found != servers_.end())
170 {
171 assert(found->second != NULL);
172 delete found->second;
173 servers_.erase(found);
174 }
175
176 servers_[name] = new Orthanc::WebServiceParameters(parameters);
177 }
178
179
117180
118181 static const char* ConvertToCString(const std::string& s)
119182 {
251314 }
252315
253316
254 void UriEncode(std::string& uri,
255 const std::string& resource,
256 const std::map<std::string, std::string>& getArguments)
317 void DicomWebServers::UriEncode(std::string& uri,
318 const std::string& resource,
319 const std::map<std::string, std::string>& getArguments)
257320 {
258321 if (resource.find('?') != std::string::npos)
259322 {
4444 }
4545
4646 public:
47 static void UriEncode(std::string& uri,
48 const std::string& resource,
49 const std::map<std::string, std::string>& getArguments);
50
4751 void Load(const Json::Value& configuration);
4852
4953 ~DicomWebServers()
5660 Orthanc::WebServiceParameters GetServer(const std::string& name);
5761
5862 void ListServers(std::list<std::string>& servers);
63
64 void ConfigureHttpClient(HttpClient& client,
65 const std::string& name,
66 const std::string& uri);
67
68 void DeleteServer(const std::string& name);
69
70 void SetServer(const std::string& name,
71 const Orthanc::WebServiceParameters& parameters);
5972 };
6073
6174
6679 const std::map<std::string, std::string>& httpHeaders,
6780 const std::string& uri,
6881 const std::string& body);
69
70 void UriEncode(std::string& uri,
71 const std::string& resource,
72 const std::map<std::string, std::string>& getArguments);
7382 }
0 function ChooseDicomWebServer(callback)
1 {
2 var clickedModality = '';
3 var clickedPeer = '';
4 var items = $('<ul>')
5 .attr('data-divider-theme', 'd')
6 .attr('data-role', 'listview');
7
8 $.ajax({
9 url: '../${DICOMWEB_ROOT}/servers',
10 type: 'GET',
11 dataType: 'json',
12 async: false,
13 cache: false,
14 success: function(servers) {
15 var name, item;
16
17 if (servers.length > 0)
18 {
19 items.append('<li data-role="list-divider">DICOMweb servers</li>');
20
21 for (var i = 0; i < servers.length; i++) {
22 name = servers[i];
23 item = $('<li>')
24 .html('<a href="#" rel="close">' + name + '</a>')
25 .attr('name', name)
26 .click(function() {
27 clickedModality = $(this).attr('name');
28 });
29 items.append(item);
30 }
31 }
32
33 // Launch the dialog
34 $(document).simpledialog2({
35 mode: 'blank',
36 animate: false,
37 headerText: 'Choose target',
38 headerClose: true,
39 forceInput: false,
40 width: '100%',
41 blankContent: items,
42 callbackClose: function() {
43 var timer;
44 function WaitForDialogToClose() {
45 if (!$('#dialog').is(':visible')) {
46 clearInterval(timer);
47 callback(clickedModality, clickedPeer);
48 }
49 }
50 timer = setInterval(WaitForDialogToClose, 100);
51 }
52 });
53 }
54 });
55 }
56
57
58 function ConfigureDicomWebStowClient(resourceId, buttonId, positionOnPage)
59 {
60 $('#' + buttonId).remove();
61
62 var b = $('<a>')
63 .attr('id', buttonId)
64 .attr('data-role', 'button')
65 .attr('href', '#')
66 .attr('data-icon', 'forward')
67 .attr('data-theme', 'e')
68 .text('Send to DICOMweb server')
69 .button();
70
71 b.insertAfter($('#' + positionOnPage));
72
73 b.click(function() {
74 if ($.mobile.pageData) {
75 ChooseDicomWebServer(function(server) {
76 if (server != '' && resourceId != '') {
77 var query = {
78 'Resources' : [ resourceId ],
79 'Synchronous' : false
80 };
81
82 $.ajax({
83 url: '../${DICOMWEB_ROOT}/servers/' + server + '/stow',
84 type: 'POST',
85 dataType: 'json',
86 data: JSON.stringify(query),
87 async: false,
88 error: function() {
89 alert('Cannot submit job');
90 },
91 success: function(job) {
92 }
93 });
94 }
95 });
96 }
97 });
98 }
99
100
101 $('#patient').live('pagebeforeshow', function() {
102 ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-patient', 'patient-info');
103 });
104
105 $('#study').live('pagebeforeshow', function() {
106 ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-study', 'study-info');
107 });
108
109 $('#series').live('pagebeforeshow', function() {
110 ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-series', 'series-info');
111 });
112
113 $('#instance').live('pagebeforeshow', function() {
114 ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-instance', 'instance-info');
115 });
116
117 $('#lookup').live('pagebeforeshow', function() {
118 $('#open-dicomweb-client').remove();
119
120 var b = $('<fieldset>')
121 .attr('id', 'open-dicomweb-client')
122 .addClass('ui-grid-b')
123 .append($('<div>')
124 .addClass('ui-block-a'))
125 .append($('<div>')
126 .addClass('ui-block-b')
127 .append($('<a>')
128 .attr('id', 'coucou')
129 .attr('data-role', 'button')
130 .attr('href', '#')
131 .attr('data-icon', 'forward')
132 .attr('data-theme', 'a')
133 .text('Open DICOMweb client')
134 .button()
135 .click(function(e) {
136 window.open('../${DICOMWEB_ROOT}/app/client/index.html');
137 })));
138
139 b.insertAfter($('#lookup-result'));
140 });
141
1717 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 **/
1919
20
2120 #include "DicomWebClient.h"
2221 #include "DicomWebServers.h"
2322 #include "GdcmParsedDicomFile.h"
2726 #include "WadoUri.h"
2827
2928 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
29 #include <Core/SystemToolbox.h>
3030 #include <Core/Toolbox.h>
3131
32
33 void SwitchStudies(OrthancPluginRestOutput* output,
34 const char* url,
35 const OrthancPluginHttpRequest* request)
36 {
37 switch (request->method)
38 {
39 case OrthancPluginHttpMethod_Get:
40 // This is QIDO-RS
41 SearchForStudies(output, url, request);
42 break;
43
44 case OrthancPluginHttpMethod_Post:
45 // This is STOW-RS
46 StowCallback(output, url, request);
47 break;
48
49 default:
50 OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
51 break;
52 }
53 }
54
55
56 void SwitchStudy(OrthancPluginRestOutput* output,
57 const char* url,
58 const OrthancPluginHttpRequest* request)
59 {
60 switch (request->method)
61 {
62 case OrthancPluginHttpMethod_Get:
63 // This is WADO-RS
64 RetrieveDicomStudy(output, url, request);
65 break;
66
67 case OrthancPluginHttpMethod_Post:
68 // This is STOW-RS
69 StowCallback(output, url, request);
70 break;
71
72 default:
73 OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
74 break;
75 }
76 }
32 #include <EmbeddedResources.h>
33
34 #include <boost/algorithm/string/predicate.hpp>
35
36
37 static const char* const HAS_DELETE = "HasDelete";
38
39
7740
7841 bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key)
7942 {
10972 Orthanc::WebServiceParameters server = OrthancPlugins::DicomWebServers::GetInstance().GetServer(*it);
11073 Json::Value jsonServer;
11174 // only return the minimum information to identify the destination, do not include "security" information like passwords
112 jsonServer["Url"] = server.GetUrl();
113 if (!server.GetUsername().empty())
114 {
115 jsonServer["Username"] = server.GetUsername();
116 }
75 server.FormatPublic(jsonServer);
11776 result[*it] = jsonServer;
11877 }
11978
14099 {
141100 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
142101
102 switch (request->method)
103 {
104 case OrthancPluginHttpMethod_Get:
105 {
106 // Make sure the server does exist
107 const Orthanc::WebServiceParameters& server =
108 OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]);
109
110 Json::Value json = Json::arrayValue;
111 json.append("get");
112 json.append("retrieve");
113 json.append("stow");
114 json.append("wado");
115 json.append("qido");
116
117 if (server.GetBooleanUserProperty(HAS_DELETE, false))
118 {
119 json.append("delete");
120 }
121
122 std::string answer = json.toStyledString();
123 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
124 break;
125 }
126
127 case OrthancPluginHttpMethod_Delete:
128 {
129 OrthancPlugins::DicomWebServers::GetInstance().DeleteServer(request->groups[0]);
130 std::string answer = "{}";
131 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
132 break;
133 }
134
135 case OrthancPluginHttpMethod_Put:
136 {
137 Json::Value body;
138 OrthancPlugins::ParseJsonBody(body, request);
139
140 Orthanc::WebServiceParameters parameters(body);
141
142 OrthancPlugins::DicomWebServers::GetInstance().SetServer(request->groups[0], parameters);
143 std::string answer = "{}";
144 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
145 break;
146 }
147
148 default:
149 OrthancPluginSendMethodNotAllowed(context, output, "GET,PUT,DELETE");
150 break;
151 }
152 }
153
154
155
156 void GetClientInformation(OrthancPluginRestOutput* output,
157 const char* /*url*/,
158 const OrthancPluginHttpRequest* request)
159 {
160 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
161
143162 if (request->method != OrthancPluginHttpMethod_Get)
144163 {
145164 OrthancPluginSendMethodNotAllowed(context, output, "GET");
146165 }
147166 else
148167 {
149 // Make sure the server does exist
150 OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]);
151
152 Json::Value json = Json::arrayValue;
153 json.append("get");
154 json.append("retrieve");
155 json.append("stow");
156
157 std::string answer = json.toStyledString();
168 Json::Value info = Json::objectValue;
169 info["DicomWebRoot"] = OrthancPlugins::Configuration::GetDicomWebRoot();
170 info["OrthancApiRoot"] = OrthancPlugins::Configuration::GetOrthancApiRoot();
171
172 std::string answer = info.toStyledString();
158173 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
159174 }
160175 }
176
177
178
179 void QidoClient(OrthancPluginRestOutput* output,
180 const char* /*url*/,
181 const OrthancPluginHttpRequest* request)
182 {
183 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
184
185 if (request->method != OrthancPluginHttpMethod_Post)
186 {
187 OrthancPluginSendMethodNotAllowed(context, output, "POST");
188 }
189 else
190 {
191 Json::Value answer;
192 GetFromServer(answer, request);
193
194 if (answer.type() != Json::arrayValue)
195 {
196 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
197 }
198
199 Json::Value result = Json::arrayValue;
200 for (Json::Value::ArrayIndex i = 0; i < answer.size(); i++)
201 {
202 if (answer[i].type() != Json::objectValue)
203 {
204 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
205 }
206
207 Json::Value::Members tags = answer[i].getMemberNames();
208
209 Json::Value item = Json::objectValue;
210
211 for (size_t j = 0; j < tags.size(); j++)
212 {
213 Orthanc::DicomTag tag(0, 0);
214 if (Orthanc::DicomTag::ParseHexadecimal(tag, tags[j].c_str()))
215 {
216 Json::Value value = Json::objectValue;
217 value["Group"] = tag.GetGroup();
218 value["Element"] = tag.GetElement();
219
220 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
221 OrthancPlugins::OrthancString name;
222
223 name.Assign(OrthancPluginGetTagName(context, tag.GetGroup(), tag.GetElement(), NULL));
224 if (name.GetContent() != NULL)
225 {
226 value["Name"] = std::string(name.GetContent());
227 }
228 #endif
229
230 const Json::Value& source = answer[i][tags[j]];
231 if (source.type() != Json::objectValue ||
232 !source.isMember("vr") ||
233 source["vr"].type() != Json::stringValue)
234 {
235 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
236 }
237
238 value["vr"] = source["vr"].asString();
239
240 if (source.isMember("Value") &&
241 source["Value"].type() == Json::arrayValue &&
242 source["Value"].size() >= 1)
243 {
244 const Json::Value& content = source["Value"][0];
245
246 switch (content.type())
247 {
248 case Json::stringValue:
249 value["Value"] = content.asString();
250 break;
251
252 case Json::objectValue:
253 if (content.isMember("Alphabetic") &&
254 content["Alphabetic"].type() == Json::stringValue)
255 {
256 value["Value"] = content["Alphabetic"].asString();
257 }
258 break;
259
260 default:
261 break;
262 }
263 }
264
265 item[tags[j]] = value;
266 }
267 }
268
269 result.append(item);
270 }
271
272 std::string tmp = result.toStyledString();
273 OrthancPluginAnswerBuffer(context, output, tmp.c_str(), tmp.size(), "application/json");
274 }
275 }
276
277
278 void DeleteClient(OrthancPluginRestOutput* output,
279 const char* /*url*/,
280 const OrthancPluginHttpRequest* request)
281 {
282 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
283
284 if (request->method != OrthancPluginHttpMethod_Post)
285 {
286 OrthancPluginSendMethodNotAllowed(context, output, "POST");
287 }
288 else
289 {
290 static const char* const LEVEL = "Level";
291 static const char* const SERIES_INSTANCE_UID = "SeriesInstanceUID";
292 static const char* const STUDY_INSTANCE_UID = "StudyInstanceUID";
293 static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
294
295 const std::string serverName = request->groups[0];
296
297 const Orthanc::WebServiceParameters& server =
298 OrthancPlugins::DicomWebServers::GetInstance().GetServer(serverName);
299
300 if (!server.GetBooleanUserProperty(HAS_DELETE, false))
301 {
302 throw Orthanc::OrthancException(
303 Orthanc::ErrorCode_BadFileFormat,
304 "Cannot delete on DICOMweb server, check out property \"" + std::string(HAS_DELETE) + "\": " + serverName);
305 }
306
307 Json::Value body;
308 OrthancPlugins::ParseJsonBody(body, request);
309
310 if (body.type() != Json::objectValue ||
311 !body.isMember(LEVEL) ||
312 !body.isMember(STUDY_INSTANCE_UID) ||
313 body[LEVEL].type() != Json::stringValue ||
314 body[STUDY_INSTANCE_UID].type() != Json::stringValue)
315 {
316 throw Orthanc::OrthancException(
317 Orthanc::ErrorCode_BadFileFormat,
318 "The request body must contain a JSON object with fields \"Level\" and \"StudyInstanceUID\"");
319 }
320
321 Orthanc::ResourceType level = Orthanc::StringToResourceType(body[LEVEL].asCString());
322
323 const std::string study = body[STUDY_INSTANCE_UID].asString();
324
325 std::string series;
326 if (level == Orthanc::ResourceType_Series ||
327 level == Orthanc::ResourceType_Instance)
328 {
329 if (!body.isMember(SERIES_INSTANCE_UID) ||
330 body[SERIES_INSTANCE_UID].type() != Json::stringValue)
331 {
332 throw Orthanc::OrthancException(
333 Orthanc::ErrorCode_BadFileFormat,
334 "The request body must contain the field \"SeriesInstanceUID\"");
335 }
336 else
337 {
338 series = body[SERIES_INSTANCE_UID].asString();
339 }
340 }
341
342 std::string instance;
343 if (level == Orthanc::ResourceType_Instance)
344 {
345 if (!body.isMember(SOP_INSTANCE_UID) ||
346 body[SOP_INSTANCE_UID].type() != Json::stringValue)
347 {
348 throw Orthanc::OrthancException(
349 Orthanc::ErrorCode_BadFileFormat,
350 "The request body must contain the field \"SOPInstanceUID\"");
351 }
352 else
353 {
354 instance = body[SOP_INSTANCE_UID].asString();
355 }
356 }
357
358 std::string uri;
359 switch (level)
360 {
361 case Orthanc::ResourceType_Study:
362 uri = "/studies/" + study;
363 break;
364
365 case Orthanc::ResourceType_Series:
366 uri = "/studies/" + study + "/series/" + series;
367 break;
368
369 case Orthanc::ResourceType_Instance:
370 uri = "/studies/" + study + "/series/" + series + "/instances/" + instance;
371 break;
372
373 default:
374 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
375 }
376
377 OrthancPlugins::HttpClient client;
378 OrthancPlugins::DicomWebServers::GetInstance().ConfigureHttpClient(client, serverName, uri);
379 client.SetMethod(OrthancPluginHttpMethod_Delete);
380 client.Execute();
381
382 std::string tmp = "{}";
383 OrthancPluginAnswerBuffer(context, output, tmp.c_str(), tmp.size(), "application/json");
384 }
385 }
386
387
388
389
390 static void AnswerFrameRendered(OrthancPluginRestOutput* output,
391 int frame,
392 const OrthancPluginHttpRequest* request)
393 {
394 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
395
396 if (request->method != OrthancPluginHttpMethod_Get)
397 {
398 OrthancPluginSendMethodNotAllowed(context, output, "GET");
399 }
400 else
401 {
402 std::string instanceId;
403 if (LocateInstance(output, instanceId, request))
404 {
405 Orthanc::MimeType mime = Orthanc::MimeType_Jpeg; // This is the default in DICOMweb
406
407 for (uint32_t i = 0; i < request->headersCount; i++)
408 {
409 if (boost::iequals(request->headersKeys[i], "Accept") &&
410 !boost::iequals(request->headersValues[i], "*/*"))
411 {
412 try
413 {
414 // TODO - Support conversion to GIF
415
416 mime = Orthanc::StringToMimeType(request->headersValues[i]);
417 if (mime != Orthanc::MimeType_Png &&
418 mime != Orthanc::MimeType_Jpeg)
419 {
420 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
421 }
422 }
423 catch (Orthanc::OrthancException&)
424 {
425 LOG(ERROR) << "Unsupported MIME type in WADO-RS rendered frame: " << request->headersValues[i];
426 throw;
427 }
428 }
429 }
430
431 std::map<std::string, std::string> headers;
432 headers["Accept"] = Orthanc::EnumerationToString(mime);
433
434 // NB: In DICOMweb, the "frame" parameter is in the range [1..N], whereas
435 // Orthanc uses range [0..N-1], hence the "-1" below
436 OrthancPlugins::MemoryBuffer buffer;
437 if (buffer.RestApiGet("/instances/" + instanceId + "/frames/" +
438 boost::lexical_cast<std::string>(frame - 1) + "/preview", headers, false))
439 {
440 OrthancPluginAnswerBuffer(context, output, buffer.GetData(),
441 buffer.GetSize(), Orthanc::EnumerationToString(mime));
442 }
443 }
444 }
445 }
446
447
448 void RetrieveInstanceRendered(OrthancPluginRestOutput* output,
449 const char* url,
450 const OrthancPluginHttpRequest* request)
451 {
452 AnswerFrameRendered(output, 1 /* first frame */, request);
453 }
454
455
456 void RetrieveFrameRendered(OrthancPluginRestOutput* output,
457 const char* url,
458 const OrthancPluginHttpRequest* request)
459 {
460 assert(request->groupsCount == 4);
461 const char* frame = request->groups[3];
462
463 AnswerFrameRendered(output, boost::lexical_cast<int>(frame), request);
464 }
465
466
467
161468
162469
163470 static bool DisplayPerformanceWarning(OrthancPluginContext* context)
169476 }
170477
171478
479 template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder>
480 void ServeEmbeddedFolder(OrthancPluginRestOutput* output,
481 const char* url,
482 const OrthancPluginHttpRequest* request)
483 {
484 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
485
486 if (request->method != OrthancPluginHttpMethod_Get)
487 {
488 OrthancPluginSendMethodNotAllowed(context, output, "GET");
489 }
490 else
491 {
492 std::string path = "/" + std::string(request->groups[0]);
493 const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
494
495 std::string s;
496 Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str());
497
498 const char* resource = s.size() ? s.c_str() : NULL;
499 OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime);
500 }
501 }
502
503
504 #if ORTHANC_STANDALONE == 0
505 void ServeDicomWebClient(OrthancPluginRestOutput* output,
506 const char* url,
507 const OrthancPluginHttpRequest* request)
508 {
509 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
510
511 if (request->method != OrthancPluginHttpMethod_Get)
512 {
513 OrthancPluginSendMethodNotAllowed(context, output, "GET");
514 }
515 else
516 {
517 const std::string path = std::string(DICOMWEB_CLIENT_PATH) + std::string(request->groups[0]);
518 const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
519
520 OrthancPlugins::MemoryBuffer f;
521 f.ReadFile(path);
522
523 OrthancPluginAnswerBuffer(context, output, f.GetData(), f.GetSize(), mime);
524 }
525 }
526 #endif
527
528
172529 extern "C"
173530 {
174531 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
175532 {
176533 assert(DisplayPerformanceWarning(context));
534
177535 OrthancPlugins::SetGlobalContext(context);
178536 Orthanc::Logging::Initialize(context);
179537
190548 return -1;
191549 }
192550
551 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 0
552 LOG(WARNING) << "Performance warning in DICOMweb: The plugin was compiled against "
553 << "Orthanc SDK <= 1.5.6. STOW and WADO chunked transfers will be entirely stored in RAM.";
554 #endif
555
193556 OrthancPluginSetDescription(context, "Implementation of DICOMweb (QIDO-RS, STOW-RS and WADO-RS) and WADO-URI.");
194557
195558 try
203566 // Configure the DICOMweb callbacks
204567 if (OrthancPlugins::Configuration::GetBooleanValue("Enable", true))
205568 {
206 std::string root = OrthancPlugins::Configuration::GetRoot();
569 std::string root = OrthancPlugins::Configuration::GetDicomWebRoot();
207570 assert(!root.empty() && root[root.size() - 1] == '/');
208571
209572 OrthancPlugins::LogWarning("URI to the DICOMweb REST API: " + root);
573
574 OrthancPlugins::ChunkedRestRegistration<
575 SearchForStudies /* TODO => Rename as QIDO-RS */,
576 OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies");
577
578 OrthancPlugins::ChunkedRestRegistration<
579 RetrieveDicomStudy /* TODO => Rename as WADO-RS */,
580 OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)");
210581
211582 OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true);
212583 OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", true);
213 OrthancPlugins::RegisterRestCallback<SwitchStudies>(root + "studies", true);
214 OrthancPlugins::RegisterRestCallback<SwitchStudy>(root + "studies/([^/]*)", true);
215584 OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "studies/([^/]*)/instances", true);
216585 OrthancPlugins::RegisterRestCallback<RetrieveStudyMetadata>(root + "studies/([^/]*)/metadata", true);
217586 OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "studies/([^/]*)/series", true);
227596 OrthancPlugins::RegisterRestCallback<ListServers>(root + "servers", true);
228597 OrthancPlugins::RegisterRestCallback<ListServerOperations>(root + "servers/([^/]*)", true);
229598 OrthancPlugins::RegisterRestCallback<StowClient>(root + "servers/([^/]*)/stow", true);
599 OrthancPlugins::RegisterRestCallback<WadoRetrieveClient>(root + "servers/([^/]*)/wado", true);
230600 OrthancPlugins::RegisterRestCallback<GetFromServer>(root + "servers/([^/]*)/get", true);
231601 OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(root + "servers/([^/]*)/retrieve", true);
602 OrthancPlugins::RegisterRestCallback<QidoClient>(root + "servers/([^/]*)/qido", true);
603 OrthancPlugins::RegisterRestCallback<DeleteClient>(root + "servers/([^/]*)/delete", true);
604
605 OrthancPlugins::RegisterRestCallback
606 <ServeEmbeddedFolder<Orthanc::EmbeddedResources::JAVASCRIPT_LIBS> >
607 (root + "app/libs/(.*)", true);
608
609 OrthancPlugins::RegisterRestCallback<GetClientInformation>(root + "info", true);
610
611 OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
612 OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
613
614
615 // Extend the default Orthanc Explorer with custom JavaScript for STOW client
616 std::string explorer;
617
618 #if ORTHANC_STANDALONE == 1
619 Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
620 OrthancPlugins::RegisterRestCallback
621 <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> >
622 (root + "app/client/(.*)", true);
623 #else
624 Orthanc::SystemToolbox::ReadFile(explorer, std::string(DICOMWEB_CLIENT_PATH) + "../Plugin/OrthancExplorer.js");
625 OrthancPlugins::RegisterRestCallback<ServeDicomWebClient>(root + "app/client/(.*)", true);
626 #endif
627
628 {
629 if (root.size() < 2 ||
630 root[0] != '/' ||
631 root[root.size() - 1] != '/')
632 {
633 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
634 }
635
636 std::map<std::string, std::string> dictionary;
637 dictionary["DICOMWEB_ROOT"] = root.substr(1, root.size() - 2); // Remove heading and trailing slashes
638 std::string configured = Orthanc::Toolbox::SubstituteVariables(explorer, dictionary);
639
640 OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), configured.c_str());
641 }
642
643
644 std::string uri = root + "app/client/index.html";
645 OrthancPluginSetRootUri(context, uri.c_str());
232646 }
233647 else
234648 {
2020
2121 #include "QidoRs.h"
2222
23 #include "StowRs.h" // For IsXmlExpected()
2423 #include "Configuration.h"
2524 #include "DicomWebFormatter.h"
2625
493492
494493 std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
495494
496 OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, IsXmlExpected(request));
495 OrthancPlugins::DicomWebFormatter::HttpWriter writer(
496 output, OrthancPlugins::Configuration::IsXmlExpected(request));
497497
498498 // Fix of issue #13
499499 for (ResourcesAndInstances::const_iterator
2323 #include "Configuration.h"
2424 #include "DicomWebFormatter.h"
2525
26 #include <Core/Toolbox.h>
27 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
28
29 bool IsXmlExpected(const OrthancPluginHttpRequest* request)
26
27 namespace OrthancPlugins
3028 {
31 std::string accept;
32
33 if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
34 {
35 return false; // By default, return DICOM+JSON
36 }
37
38 Orthanc::Toolbox::ToLowerCase(accept);
39 if (accept == "application/dicom+json" ||
40 accept == "application/json" ||
41 accept == "*/*")
42 {
43 return false;
44 }
45 else if (accept == "application/dicom+xml" ||
46 accept == "application/xml" ||
47 accept == "text/xml")
48 {
49 return true;
50 }
51 else
52 {
53 OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
54 ", will return DICOM+JSON");
55 return false;
56 }
57 }
58
59
60
61 void StowCallback(OrthancPluginRestOutput* output,
62 const char* url,
63 const OrthancPluginHttpRequest* request)
64 {
65 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
66
67 const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
68
69 if (request->method != OrthancPluginHttpMethod_Post)
70 {
71 OrthancPluginSendMethodNotAllowed(context, output, "POST");
72 return;
73 }
74
75 std::string expectedStudy;
76 if (request->groupsCount == 1)
77 {
78 expectedStudy = request->groups[0];
79 }
80
81 if (expectedStudy.empty())
82 {
83 OrthancPlugins::LogInfo("STOW-RS request without study");
84 }
85 else
86 {
87 OrthancPlugins::LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
88 }
89
90 std::string header;
91 if (!OrthancPlugins::LookupHttpHeader(header, request, "content-type"))
92 {
93 OrthancPlugins::LogError("No content type in the HTTP header of a STOW-RS request");
94 OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
95 return;
96 }
97
98 std::string application;
99 std::map<std::string, std::string> attributes;
100 OrthancPlugins::ParseContentType(application, attributes, header);
101
102 if (application != "multipart/related" ||
103 attributes.find("type") == attributes.end() ||
104 attributes.find("boundary") == attributes.end())
105 {
106 OrthancPlugins::LogError("Unable to parse the content type of a STOW-RS request (" + application + ")");
107 OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
108 return;
109 }
110
111
112 std::string boundary = attributes["boundary"];
113
114 if (attributes["type"] != "application/dicom")
115 {
116 OrthancPlugins::LogError("The STOW-RS plugin currently only supports application/dicom");
117 OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
118 return;
119 }
120
121
122 bool isFirst = true;
123
124 Json::Value result = Json::objectValue;
125 Json::Value success = Json::arrayValue;
126 Json::Value failed = Json::arrayValue;
127
128 std::vector<OrthancPlugins::MultipartItem> items;
129 OrthancPlugins::ParseMultipartBody(items, request->body, request->bodySize, boundary);
130
131 for (size_t i = 0; i < items.size(); i++)
132 {
133 OrthancPlugins::LogInfo("Detected multipart item with content type \"" +
134 items[i].contentType_ + "\" of size " +
135 boost::lexical_cast<std::string>(items[i].size_));
136 }
137
138 for (size_t i = 0; i < items.size(); i++)
139 {
140 if (!items[i].contentType_.empty() &&
141 items[i].contentType_ != "application/dicom")
142 {
143 OrthancPlugins::LogError("The STOW-RS request contains a part that is not "
144 "\"application/dicom\" (it is: \"" + items[i].contentType_ + "\")");
145 OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
146 return;
29 StowServer::StowServer(OrthancPluginContext* context,
30 const std::map<std::string, std::string>& headers,
31 const std::string& expectedStudy) :
32 context_(context),
33 xml_(Configuration::IsXmlExpected(headers)),
34 wadoBase_(Configuration::GetBaseUrl(headers)),
35 expectedStudy_(expectedStudy),
36 isFirst_(true),
37 result_(Json::objectValue),
38 success_(Json::arrayValue),
39 failed_(Json::arrayValue)
40 {
41 std::string tmp, contentType, subType, boundary;
42 if (!Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers) ||
43 !Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp))
44 {
45 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
46 "The STOW-RS server expects a multipart body in its request");
47 }
48
49 if (contentType != "multipart/related")
50 {
51 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
52 "The Content-Type of a STOW-RS request must be \"multipart/related\"");
53 }
54
55 if (subType != "application/dicom")
56 {
57 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
58 "The STOW-RS plugin currently only supports \"application/dicom\" subtype");
59 }
60
61 parser_.reset(new Orthanc::MultipartStreamReader(boundary));
62 parser_->SetHandler(*this);
63 }
64
65
66 void StowServer::HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
67 const void* part,
68 size_t size)
69 {
70 std::string contentType;
71
72 if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers) ||
73 contentType != "application/dicom")
74 {
75 throw Orthanc::OrthancException(
76 Orthanc::ErrorCode_UnsupportedMediaType,
77 "The STOW-RS request contains a part that is not "
78 "\"application/dicom\" (it is: \"" + contentType + "\")");
14779 }
14880
14981 Json::Value dicom;
15082
15183 try
15284 {
153 OrthancPlugins::OrthancString s;
154 s.Assign(OrthancPluginDicomBufferToJson(context, items[i].data_, items[i].size_,
85 OrthancString s;
86 s.Assign(OrthancPluginDicomBufferToJson(context_, part, size,
15587 OrthancPluginDicomToJsonFormat_Short,
15688 OrthancPluginDicomToJsonFlags_None, 256));
15789 s.ToJson(dicom);
15991 catch (Orthanc::OrthancException&)
16092 {
16193 // Bad DICOM file => TODO add to error
162 OrthancPlugins::LogWarning("STOW-RS cannot parse an incoming DICOM file");
163 continue;
94 LogWarning("STOW-RS cannot parse an incoming DICOM file");
95 return;
16496 }
16597
16698 if (dicom.type() != Json::objectValue ||
173105 dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue ||
174106 dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue)
175107 {
176 OrthancPlugins::LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
177 continue;
108 LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
109 return;
178110 }
179111
180112 const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString();
183115 const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString();
184116
185117 Json::Value item = Json::objectValue;
186 item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
187 item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
188
189 if (!expectedStudy.empty() &&
190 studyInstanceUid != expectedStudy)
191 {
192 OrthancPlugins::LogInfo("STOW-RS request restricted to study [" + expectedStudy +
193 "]: Ignoring instance from study [" + studyInstanceUid + "]");
194
195 /*item[OrthancPlugins::DICOM_TAG_WARNING_REASON.Format()] =
118 item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
119 item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
120
121 if (!expectedStudy_.empty() &&
122 studyInstanceUid != expectedStudy_)
123 {
124 LogInfo("STOW-RS request restricted to study [" + expectedStudy_ +
125 "]: Ignoring instance from study [" + studyInstanceUid + "]");
126
127 /*item[DICOM_TAG_WARNING_REASON.Format()] =
196128 boost::lexical_cast<std::string>(0xB006); // Elements discarded
197129 success.append(item);*/
198130 }
199131 else
200132 {
201 if (isFirst)
133 if (isFirst_)
202134 {
203 std::string url = wadoBase + "studies/" + studyInstanceUid;
204 result[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
205 isFirst = false;
135 std::string url = wadoBase_ + "studies/" + studyInstanceUid;
136 result_[DICOM_TAG_RETRIEVE_URL.Format()] = url;
137 isFirst_ = false;
206138 }
207139
208 OrthancPlugins::MemoryBuffer tmp;
209 bool ok = tmp.RestApiPost("/instances", items[i].data_, items[i].size_, false);
140 MemoryBuffer tmp;
141 bool ok = tmp.RestApiPost("/instances", part, size, false);
210142 tmp.Clear();
211143
212144 if (ok)
213145 {
214 std::string url = (wadoBase +
146 std::string url = (wadoBase_ +
215147 "studies/" + studyInstanceUid +
216148 "/series/" + seriesInstanceUid +
217149 "/instances/" + sopInstanceUid);
218150
219 item[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
220 success.append(item);
151 item[DICOM_TAG_RETRIEVE_URL.Format()] = url;
152 success_.append(item);
221153 }
222154 else
223155 {
224 OrthancPlugins::LogError("Orthanc was unable to store instance through STOW-RS request");
225 item[OrthancPlugins::DICOM_TAG_FAILURE_REASON.Format()] =
156 LogError("Orthanc was unable to store one instance in a STOW-RS request");
157 item[DICOM_TAG_FAILURE_REASON.Format()] =
226158 boost::lexical_cast<std::string>(0x0110); // Processing failure
227 failed.append(item);
159 failed_.append(item);
228160 }
229161 }
230162 }
231163
232 result[OrthancPlugins::DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed;
233 result[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success;
234
235 const bool isXml = IsXmlExpected(request);
236 std::string answer;
164
165 void StowServer::AddChunk(const void* data,
166 size_t size)
167 {
168 assert(parser_.get() != NULL);
169 parser_->AddChunk(data, size);
170 }
171
172
173 void StowServer::Execute(OrthancPluginRestOutput* output)
174 {
175 assert(parser_.get() != NULL);
176 parser_->CloseStream();
177
178 result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_;
179 result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_;
180
181 std::string answer;
182
183 {
184 DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
185 locker.Apply(answer, context_, result_, xml_);
186 }
187
188 OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(),
189 xml_ ? "application/dicom+xml" : "application/dicom+json");
190 };
191
237192
238 {
239 OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
240 locker.Apply(answer, context, result, isXml);
241 }
242
243 OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(),
244 isXml ? "application/dicom+xml" : "application/dicom+json");
193 IChunkedRequestReader* StowServer::PostCallback(const char* url,
194 const OrthancPluginHttpRequest* request)
195 {
196 OrthancPluginContext* context = GetGlobalContext();
197
198 if (request->method != OrthancPluginHttpMethod_Post)
199 {
200 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
201 }
202
203 std::map<std::string, std::string> headers;
204 for (uint32_t i = 0; i < request->headersCount; i++)
205 {
206 headers[request->headersKeys[i]] = request->headersValues[i];
207 }
208
209 std::string expectedStudy;
210 if (request->groupsCount == 1)
211 {
212 expectedStudy = request->groups[0];
213 }
214
215 if (expectedStudy.empty())
216 {
217 LogInfo("STOW-RS request without study");
218 }
219 else
220 {
221 LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
222 }
223
224 return new StowServer(context, headers, expectedStudy);
225 }
245226 }
2020
2121 #pragma once
2222
23 #include "Configuration.h"
23 #include <Core/HttpServer/MultipartStreamReader.h>
24 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
2425
25 bool IsXmlExpected(const OrthancPluginHttpRequest* request);
26 namespace OrthancPlugins
27 {
28 class StowServer :
29 public IChunkedRequestReader,
30 private Orthanc::MultipartStreamReader::IHandler
31 {
32 private:
33 OrthancPluginContext* context_;
34 bool xml_;
35 std::string wadoBase_;
36 std::string expectedStudy_;
37 bool isFirst_;
38 Json::Value result_;
39 Json::Value success_;
40 Json::Value failed_;
2641
27 void StowCallback(OrthancPluginRestOutput* output,
28 const char* url,
29 const OrthancPluginHttpRequest* request);
42 std::auto_ptr<Orthanc::MultipartStreamReader> parser_;
43
44 virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
45 const void* part,
46 size_t size);
47
48 public:
49 StowServer(OrthancPluginContext* context,
50 const std::map<std::string, std::string>& headers,
51 const std::string& expectedStudy);
52
53 virtual void AddChunk(const void* data,
54 size_t size);
55
56 virtual void Execute(OrthancPluginRestOutput* output);
57
58 static IChunkedRequestReader* PostCallback(const char* url,
59 const OrthancPluginHttpRequest* request);
60 };
61 }
438438 series["MainDicomTags"]["SeriesInstanceUID"].asString() != std::string(request->groups[1]))
439439 {
440440 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
441 "No instance " + std::string(request->groups[2]) +
442 " in study " + std::string(request->groups[0]) +
443 " or in series " + std::string(request->groups[1]));
441 "Instance " + std::string(request->groups[2]) +
442 " is not both in study " + std::string(request->groups[0]) +
443 " and in series " + std::string(request->groups[1]));
444444 }
445445 else
446446 {
0 # Orthanc - A Lightweight, RESTful DICOM Store
1 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
2 # Department, University Hospital of Liege, Belgium
3 # Copyright (C) 2017-2019 Osimis S.A., Belgium
4 #
5 # This program is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Affero General Public License
7 # as published by the Free Software Foundation, either version 3 of
8 # the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
19 set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dicom-web")
20
21 DownloadPackage(
22 "da0189f7c33bf9f652ea65401e0a3dc9"
23 "${BASE_URL}/bootstrap-4.3.1.zip"
24 "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1")
25
26 DownloadPackage(
27 "8242afdc5bd44105d9dc9e6535315484"
28 "${BASE_URL}/vuejs-2.6.10.tar.gz"
29 "${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10")
30
31 DownloadPackage(
32 "3e2b4e1522661f7fcf8ad49cb933296c"
33 "${BASE_URL}/axios-0.19.0.tar.gz"
34 "${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0")
35
36 DownloadPackage(
37 "a6145901f233f7d54165d8ade779082e"
38 "${BASE_URL}/Font-Awesome-4.7.0.tar.gz"
39 "${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0")
40
41
42 set(BOOTSTRAP_VUE_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-vue-2.0.0-rc.24)
43
44 if (BUILD_BOOTSTRAP_VUE OR
45 BUILD_BABEL_POLYFILL)
46 find_program(NPM_EXECUTABLE npm)
47 if (${NPM_EXECUTABLE} MATCHES "NPM_EXECUTABLE-NOTFOUND")
48 message(FATAL_ERROR "Please install the 'npm' standard command-line tool")
49 endif()
50 endif()
51
52 if (BUILD_BOOTSTRAP_VUE)
53 DownloadPackage(
54 "36ab31495ab94162e159619532e8def5"
55 "${BASE_URL}/bootstrap-vue-2.0.0-rc.24.tar.gz"
56 "${BOOTSTRAP_VUE_SOURCES_DIR}")
57
58 if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/node_modules")
59 execute_process(
60 COMMAND ${NPM_EXECUTABLE} install
61 WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
62 RESULT_VARIABLE Failure
63 OUTPUT_QUIET
64 )
65
66 if (Failure)
67 message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
68 endif()
69 endif()
70
71 if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/dist")
72 execute_process(
73 COMMAND ${NPM_EXECUTABLE} run build
74 WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
75 RESULT_VARIABLE Failure
76 OUTPUT_QUIET
77 )
78
79 if (Failure)
80 message(FATAL_ERROR "Error while running 'npm build' on Bootstrap-Vue")
81 endif()
82 endif()
83
84 else()
85
86 ##
87 ## Generation of the precompiled Bootstrap-Vue package:
88 ##
89 ## Possibility 1 (build from sources):
90 ## $ cmake -DBUILD_BOOTSTRAP_VUE=ON .
91 ## $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
92 ##
93 ## Possibility 2 (download from CDN):
94 ## $ mkdir /tmp/i && cd /tmp/i
95 ## $ wget -r --no-parent https://unpkg.com/bootstrap-vue@2.0.0-rc.24/dist/
96 ## $ mv unpkg.com/bootstrap-vue@2.0.0-rc.24/ bootstrap-vue-2.0.0-rc.24
97 ## $ rm bootstrap-vue-2.0.0-rc.24/dist/index.html
98 ## $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
99
100 DownloadPackage(
101 "ba0e67b1f0b4ce64e072b42b17f6c578"
102 "${BASE_URL}/bootstrap-vue-2.0.0-rc.24-dist.tar.gz"
103 "${BOOTSTRAP_VUE_SOURCES_DIR}")
104
105 endif()
106
107
108 if (BUILD_BABEL_POLYFILL)
109 set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/node_modules/babel-polyfill/dist)
110
111 if (NOT IS_DIRECTORY "${BABEL_POLYFILL_SOURCES_DIR}")
112 execute_process(
113 COMMAND ${NPM_EXECUTABLE} install babel-polyfill
114 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
115 RESULT_VARIABLE Failure
116 OUTPUT_QUIET
117 )
118
119 if (Failure)
120 message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
121 endif()
122 endif()
123 else()
124
125 ## curl -L https://unpkg.com/babel-polyfill@6.26.0/dist/polyfill.min.js | gzip > babel-polyfill-6.26.0.min.js.gz
126
127 set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR})
128 DownloadCompressedFile(
129 "49f7bad4176d715ce145e75c903988ef"
130 "${BASE_URL}/babel-polyfill-6.26.0.min.js.gz"
131 "${CMAKE_CURRENT_BINARY_DIR}/polyfill.min.js")
132
133 endif()
134
135
136 set(JAVASCRIPT_LIBS_DIR ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
137 file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
138
139 file(COPY
140 ${BABEL_POLYFILL_SOURCES_DIR}/polyfill.min.js
141 ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js
142 ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js.map
143 ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js
144 ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map
145 ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js
146 ${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js
147 DESTINATION
148 ${JAVASCRIPT_LIBS_DIR}/js
149 )
150
151 file(COPY
152 ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css
153 ${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css.map
154 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/css/font-awesome.min.css
155 ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css
156 ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map
157 DESTINATION
158 ${JAVASCRIPT_LIBS_DIR}/css
159 )
160
161 file(COPY
162 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/FontAwesome.otf
163 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.eot
164 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.svg
165 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.ttf
166 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff
167 ${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff2
168 DESTINATION
169 ${JAVASCRIPT_LIBS_DIR}/fonts
170 )
171
172 file(COPY
173 ${ORTHANC_ROOT}/Resources/OrthancLogo.png
174 DESTINATION
175 ${JAVASCRIPT_LIBS_DIR}/img
176 )
6565 if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH)
6666 if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline")
6767 set(ORTHANC_FRAMEWORK_BRANCH "default")
68 set(ORTHANC_FRAMEWORK_MAJOR 999)
69 set(ORTHANC_FRAMEWORK_MINOR 999)
70 set(ORTHANC_FRAMEWORK_REVISION 999)
6871
6972 else()
7073 set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
102105 set(ORTHANC_FRAMEWORK_MD5 "404baef5d4c43e7c5d9410edda8ef5a5")
103106 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.5")
104107 set(ORTHANC_FRAMEWORK_MD5 "cfc437e0687ae4bd725fd93dc1f08bc4")
108 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.6")
109 set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0")
110 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7")
111 set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3")
105112 endif()
106113 endif()
107114 endif()
115 else()
116 message("Using the Orthanc framework from a path of the filesystem. Assuming mainline version.")
117 set(ORTHANC_FRAMEWORK_MAJOR 999)
118 set(ORTHANC_FRAMEWORK_MINOR 999)
119 set(ORTHANC_FRAMEWORK_REVISION 999)
108120 endif()
109121
110122
0 /**
1 * \mainpage
2 *
3 * This C/C++ SDK allows external developers to create plugins that
4 * can be loaded into Orthanc to extend its functionality. Each
5 * Orthanc plugin must expose 4 public functions with the following
6 * signatures:
7 *
8 * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
9 * This function is invoked by Orthanc when it loads the plugin on startup.
10 * The plugin must:
11 * - Check its compatibility with the Orthanc version using
12 * ::OrthancPluginCheckVersion().
13 * - Store the context pointer so that it can use the plugin
14 * services of Orthanc.
15 * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
16 * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
17 * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
18 * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
19 * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
20 * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
21 * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
22 * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback().
23 * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
24 * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2().
25 * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer().
26 * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback().
27 * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback().
28 * -# <tt>void OrthancPluginFinalize()</tt>:
29 * This function is invoked by Orthanc during its shutdown. The plugin
30 * must free all its memory.
31 * -# <tt>const char* OrthancPluginGetName()</tt>:
32 * The plugin must return a short string to identify itself.
33 * -# <tt>const char* OrthancPluginGetVersion()</tt>:
34 * The plugin must return a string containing its version number.
35 *
36 * The name and the version of a plugin is only used to prevent it
37 * from being loaded twice. Note that, in C++, it is mandatory to
38 * declare these functions within an <tt>extern "C"</tt> section.
39 *
40 * To ensure multi-threading safety, the various REST callbacks are
41 * guaranteed to be executed in mutual exclusion since Orthanc
42 * 0.8.5. If this feature is undesired (notably when developing
43 * high-performance plugins handling simultaneous requests), use
44 * ::OrthancPluginRegisterRestCallbackNoLock().
45 **/
46
47
48
49 /**
50 * @defgroup Images Images and compression
51 * @brief Functions to deal with images and compressed buffers.
52 *
53 * @defgroup REST REST
54 * @brief Functions to answer REST requests in a callback.
55 *
56 * @defgroup Callbacks Callbacks
57 * @brief Functions to register and manage callbacks by the plugins.
58 *
59 * @defgroup DicomCallbacks DicomCallbacks
60 * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE).
61 *
62 * @defgroup Orthanc Orthanc
63 * @brief Functions to access the content of the Orthanc server.
64 **/
65
66
67
68 /**
69 * @defgroup Toolbox Toolbox
70 * @brief Generic functions to help with the creation of plugins.
71 **/
72
73
74
75 /**
76 * Orthanc - A Lightweight, RESTful DICOM Store
77 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
78 * Department, University Hospital of Liege, Belgium
79 * Copyright (C) 2017-2019 Osimis S.A., Belgium
80 *
81 * This program is free software: you can redistribute it and/or
82 * modify it under the terms of the GNU General Public License as
83 * published by the Free Software Foundation, either version 3 of the
84 * License, or (at your option) any later version.
85 *
86 * In addition, as a special exception, the copyright holders of this
87 * program give permission to link the code of its release with the
88 * OpenSSL project's "OpenSSL" library (or with modified versions of it
89 * that use the same license as the "OpenSSL" library), and distribute
90 * the linked executables. You must obey the GNU General Public License
91 * in all respects for all of the code used other than "OpenSSL". If you
92 * modify file(s) with this exception, you may extend this exception to
93 * your version of the file(s), but you are not obligated to do so. If
94 * you do not wish to do so, delete this exception statement from your
95 * version. If you delete this exception statement from all source files
96 * in the program, then also delete it here.
97 *
98 * This program is distributed in the hope that it will be useful, but
99 * WITHOUT ANY WARRANTY; without even the implied warranty of
100 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
101 * General Public License for more details.
102 *
103 * You should have received a copy of the GNU General Public License
104 * along with this program. If not, see <http://www.gnu.org/licenses/>.
105 **/
106
107
108
109 #pragma once
110
111
112 #include <stdio.h>
113 #include <string.h>
114
115 #ifdef WIN32
116 # define ORTHANC_PLUGINS_API __declspec(dllexport)
117 #elif __GNUC__ >= 4
118 # define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default")))
119 #else
120 # define ORTHANC_PLUGINS_API
121 #endif
122
123 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1
124 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 5
125 #define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 7
126
127
128 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
129 #define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
130 (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \
131 (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \
132 (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \
133 (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \
134 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
135 #endif
136
137
138
139 /********************************************************************
140 ** Check that function inlining is properly supported. The use of
141 ** inlining is required, to avoid the duplication of object code
142 ** between two compilation modules that would use the Orthanc Plugin
143 ** API.
144 ********************************************************************/
145
146 /* If the auto-detection of the "inline" keyword below does not work
147 automatically and that your compiler is known to properly support
148 inlining, uncomment the following #define and adapt the definition
149 of "static inline". */
150
151 /* #define ORTHANC_PLUGIN_INLINE static inline */
152
153 #ifndef ORTHANC_PLUGIN_INLINE
154 # if __STDC_VERSION__ >= 199901L
155 /* This is C99 or above: http://predef.sourceforge.net/prestd.html */
156 # define ORTHANC_PLUGIN_INLINE static inline
157 # elif defined(__cplusplus)
158 /* This is C++ */
159 # define ORTHANC_PLUGIN_INLINE static inline
160 # elif defined(__GNUC__)
161 /* This is GCC running in C89 mode */
162 # define ORTHANC_PLUGIN_INLINE static __inline
163 # elif defined(_MSC_VER)
164 /* This is Visual Studio running in C89 mode */
165 # define ORTHANC_PLUGIN_INLINE static __inline
166 # else
167 # error Your compiler is not known to support the "inline" keyword
168 # endif
169 #endif
170
171
172
173 /********************************************************************
174 ** Inclusion of standard libraries.
175 ********************************************************************/
176
177 /**
178 * For Microsoft Visual Studio, a compatibility "stdint.h" can be
179 * downloaded at the following URL:
180 * https://bitbucket.org/sjodogne/orthanc/raw/default/Resources/ThirdParty/VisualStudio/stdint.h
181 **/
182 #include <stdint.h>
183
184 #include <stdlib.h>
185
186
187
188 /********************************************************************
189 ** Definition of the Orthanc Plugin API.
190 ********************************************************************/
191
192 /** @{ */
193
194 #ifdef __cplusplus
195 extern "C"
196 {
197 #endif
198
199 /**
200 * The various error codes that can be returned by the Orthanc core.
201 **/
202 typedef enum
203 {
204 OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */,
205 OrthancPluginErrorCode_Success = 0 /*!< Success */,
206 OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */,
207 OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */,
208 OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */,
209 OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */,
210 OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */,
211 OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */,
212 OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */,
213 OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */,
214 OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */,
215 OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */,
216 OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */,
217 OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */,
218 OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */,
219 OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */,
220 OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */,
221 OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */,
222 OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */,
223 OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */,
224 OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */,
225 OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
226 OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */,
227 OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */,
228 OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */,
229 OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */,
230 OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */,
231 OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */,
232 OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */,
233 OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */,
234 OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */,
235 OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */,
236 OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
237 OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */,
238 OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */,
239 OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
240 OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */,
241 OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */,
242 OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */,
243 OrthancPluginErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */,
244 OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */,
245 OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */,
246 OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */,
247 OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */,
248 OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */,
249 OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
250 OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */,
251 OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */,
252 OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */,
253 OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */,
254 OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */,
255 OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */,
256 OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */,
257 OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */,
258 OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */,
259 OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */,
260 OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */,
261 OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */,
262 OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */,
263 OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */,
264 OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */,
265 OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */,
266 OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */,
267 OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */,
268 OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */,
269 OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
270 OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */,
271 OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */,
272 OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */,
273 OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */,
274 OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */,
275 OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */,
276 OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
277 OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
278 OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */,
279 OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */,
280 OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */,
281 OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */,
282 OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */,
283 OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
284 OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */,
285 OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
286 OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */,
287 OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */,
288 OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */,
289 OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */,
290 OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */,
291 OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */,
292 OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */,
293 OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */,
294 OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
295 OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */,
296 OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */,
297 OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */,
298 OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */,
299 OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */,
300 OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */,
301 OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */,
302 OrthancPluginErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */,
303 OrthancPluginErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */,
304
305 _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
306 } OrthancPluginErrorCode;
307
308
309 /**
310 * Forward declaration of one of the mandatory functions for Orthanc
311 * plugins.
312 **/
313 ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
314
315
316 /**
317 * The various HTTP methods for a REST call.
318 **/
319 typedef enum
320 {
321 OrthancPluginHttpMethod_Get = 1, /*!< GET request */
322 OrthancPluginHttpMethod_Post = 2, /*!< POST request */
323 OrthancPluginHttpMethod_Put = 3, /*!< PUT request */
324 OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
325
326 _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
327 } OrthancPluginHttpMethod;
328
329
330 /**
331 * @brief The parameters of a REST request.
332 * @ingroup Callbacks
333 **/
334 typedef struct
335 {
336 /**
337 * @brief The HTTP method.
338 **/
339 OrthancPluginHttpMethod method;
340
341 /**
342 * @brief The number of groups of the regular expression.
343 **/
344 uint32_t groupsCount;
345
346 /**
347 * @brief The matched values for the groups of the regular expression.
348 **/
349 const char* const* groups;
350
351 /**
352 * @brief For a GET request, the number of GET parameters.
353 **/
354 uint32_t getCount;
355
356 /**
357 * @brief For a GET request, the keys of the GET parameters.
358 **/
359 const char* const* getKeys;
360
361 /**
362 * @brief For a GET request, the values of the GET parameters.
363 **/
364 const char* const* getValues;
365
366 /**
367 * @brief For a PUT or POST request, the content of the body.
368 **/
369 const void* body;
370
371 /**
372 * @brief For a PUT or POST request, the number of bytes of the body.
373 **/
374 uint32_t bodySize;
375
376
377 /* --------------------------------------------------
378 New in version 0.8.1
379 -------------------------------------------------- */
380
381 /**
382 * @brief The number of HTTP headers.
383 **/
384 uint32_t headersCount;
385
386 /**
387 * @brief The keys of the HTTP headers (always converted to low-case).
388 **/
389 const char* const* headersKeys;
390
391 /**
392 * @brief The values of the HTTP headers.
393 **/
394 const char* const* headersValues;
395
396 } OrthancPluginHttpRequest;
397
398
399 typedef enum
400 {
401 /* Generic services */
402 _OrthancPluginService_LogInfo = 1,
403 _OrthancPluginService_LogWarning = 2,
404 _OrthancPluginService_LogError = 3,
405 _OrthancPluginService_GetOrthancPath = 4,
406 _OrthancPluginService_GetOrthancDirectory = 5,
407 _OrthancPluginService_GetConfigurationPath = 6,
408 _OrthancPluginService_SetPluginProperty = 7,
409 _OrthancPluginService_GetGlobalProperty = 8,
410 _OrthancPluginService_SetGlobalProperty = 9,
411 _OrthancPluginService_GetCommandLineArgumentsCount = 10,
412 _OrthancPluginService_GetCommandLineArgument = 11,
413 _OrthancPluginService_GetExpectedDatabaseVersion = 12,
414 _OrthancPluginService_GetConfiguration = 13,
415 _OrthancPluginService_BufferCompression = 14,
416 _OrthancPluginService_ReadFile = 15,
417 _OrthancPluginService_WriteFile = 16,
418 _OrthancPluginService_GetErrorDescription = 17,
419 _OrthancPluginService_CallHttpClient = 18,
420 _OrthancPluginService_RegisterErrorCode = 19,
421 _OrthancPluginService_RegisterDictionaryTag = 20,
422 _OrthancPluginService_DicomBufferToJson = 21,
423 _OrthancPluginService_DicomInstanceToJson = 22,
424 _OrthancPluginService_CreateDicom = 23,
425 _OrthancPluginService_ComputeMd5 = 24,
426 _OrthancPluginService_ComputeSha1 = 25,
427 _OrthancPluginService_LookupDictionary = 26,
428 _OrthancPluginService_CallHttpClient2 = 27,
429 _OrthancPluginService_GenerateUuid = 28,
430 _OrthancPluginService_RegisterPrivateDictionaryTag = 29,
431 _OrthancPluginService_AutodetectMimeType = 30,
432 _OrthancPluginService_SetMetricsValue = 31,
433 _OrthancPluginService_EncodeDicomWebJson = 32,
434 _OrthancPluginService_EncodeDicomWebXml = 33,
435 _OrthancPluginService_ChunkedHttpClient = 34, /* New in Orthanc 1.5.7 */
436 _OrthancPluginService_GetTagName = 35, /* New in Orthanc 1.5.7 */
437
438 /* Registration of callbacks */
439 _OrthancPluginService_RegisterRestCallback = 1000,
440 _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
441 _OrthancPluginService_RegisterStorageArea = 1002,
442 _OrthancPluginService_RegisterOnChangeCallback = 1003,
443 _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
444 _OrthancPluginService_RegisterWorklistCallback = 1005,
445 _OrthancPluginService_RegisterDecodeImageCallback = 1006,
446 _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
447 _OrthancPluginService_RegisterFindCallback = 1008,
448 _OrthancPluginService_RegisterMoveCallback = 1009,
449 _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010,
450 _OrthancPluginService_RegisterRefreshMetricsCallback = 1011,
451 _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */
452
453 /* Sending answers to REST calls */
454 _OrthancPluginService_AnswerBuffer = 2000,
455 _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */
456 _OrthancPluginService_Redirect = 2002,
457 _OrthancPluginService_SendHttpStatusCode = 2003,
458 _OrthancPluginService_SendUnauthorized = 2004,
459 _OrthancPluginService_SendMethodNotAllowed = 2005,
460 _OrthancPluginService_SetCookie = 2006,
461 _OrthancPluginService_SetHttpHeader = 2007,
462 _OrthancPluginService_StartMultipartAnswer = 2008,
463 _OrthancPluginService_SendMultipartItem = 2009,
464 _OrthancPluginService_SendHttpStatus = 2010,
465 _OrthancPluginService_CompressAndAnswerImage = 2011,
466 _OrthancPluginService_SendMultipartItem2 = 2012,
467 _OrthancPluginService_SetHttpErrorDetails = 2013,
468
469 /* Access to the Orthanc database and API */
470 _OrthancPluginService_GetDicomForInstance = 3000,
471 _OrthancPluginService_RestApiGet = 3001,
472 _OrthancPluginService_RestApiPost = 3002,
473 _OrthancPluginService_RestApiDelete = 3003,
474 _OrthancPluginService_RestApiPut = 3004,
475 _OrthancPluginService_LookupPatient = 3005,
476 _OrthancPluginService_LookupStudy = 3006,
477 _OrthancPluginService_LookupSeries = 3007,
478 _OrthancPluginService_LookupInstance = 3008,
479 _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
480 _OrthancPluginService_RestApiGetAfterPlugins = 3010,
481 _OrthancPluginService_RestApiPostAfterPlugins = 3011,
482 _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
483 _OrthancPluginService_RestApiPutAfterPlugins = 3013,
484 _OrthancPluginService_ReconstructMainDicomTags = 3014,
485 _OrthancPluginService_RestApiGet2 = 3015,
486
487 /* Access to DICOM instances */
488 _OrthancPluginService_GetInstanceRemoteAet = 4000,
489 _OrthancPluginService_GetInstanceSize = 4001,
490 _OrthancPluginService_GetInstanceData = 4002,
491 _OrthancPluginService_GetInstanceJson = 4003,
492 _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
493 _OrthancPluginService_HasInstanceMetadata = 4005,
494 _OrthancPluginService_GetInstanceMetadata = 4006,
495 _OrthancPluginService_GetInstanceOrigin = 4007,
496
497 /* Services for plugins implementing a database back-end */
498 _OrthancPluginService_RegisterDatabaseBackend = 5000,
499 _OrthancPluginService_DatabaseAnswer = 5001,
500 _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
501 _OrthancPluginService_StorageAreaCreate = 5003,
502 _OrthancPluginService_StorageAreaRead = 5004,
503 _OrthancPluginService_StorageAreaRemove = 5005,
504
505 /* Primitives for handling images */
506 _OrthancPluginService_GetImagePixelFormat = 6000,
507 _OrthancPluginService_GetImageWidth = 6001,
508 _OrthancPluginService_GetImageHeight = 6002,
509 _OrthancPluginService_GetImagePitch = 6003,
510 _OrthancPluginService_GetImageBuffer = 6004,
511 _OrthancPluginService_UncompressImage = 6005,
512 _OrthancPluginService_FreeImage = 6006,
513 _OrthancPluginService_CompressImage = 6007,
514 _OrthancPluginService_ConvertPixelFormat = 6008,
515 _OrthancPluginService_GetFontsCount = 6009,
516 _OrthancPluginService_GetFontInfo = 6010,
517 _OrthancPluginService_DrawText = 6011,
518 _OrthancPluginService_CreateImage = 6012,
519 _OrthancPluginService_CreateImageAccessor = 6013,
520 _OrthancPluginService_DecodeDicomImage = 6014,
521
522 /* Primitives for handling C-Find, C-Move and worklists */
523 _OrthancPluginService_WorklistAddAnswer = 7000,
524 _OrthancPluginService_WorklistMarkIncomplete = 7001,
525 _OrthancPluginService_WorklistIsMatch = 7002,
526 _OrthancPluginService_WorklistGetDicomQuery = 7003,
527 _OrthancPluginService_FindAddAnswer = 7004,
528 _OrthancPluginService_FindMarkIncomplete = 7005,
529 _OrthancPluginService_GetFindQuerySize = 7006,
530 _OrthancPluginService_GetFindQueryTag = 7007,
531 _OrthancPluginService_GetFindQueryTagName = 7008,
532 _OrthancPluginService_GetFindQueryValue = 7009,
533 _OrthancPluginService_CreateFindMatcher = 7010,
534 _OrthancPluginService_FreeFindMatcher = 7011,
535 _OrthancPluginService_FindMatcherIsMatch = 7012,
536
537 /* Primitives for accessing Orthanc Peers (new in 1.4.2) */
538 _OrthancPluginService_GetPeers = 8000,
539 _OrthancPluginService_FreePeers = 8001,
540 _OrthancPluginService_GetPeersCount = 8003,
541 _OrthancPluginService_GetPeerName = 8004,
542 _OrthancPluginService_GetPeerUrl = 8005,
543 _OrthancPluginService_CallPeerApi = 8006,
544 _OrthancPluginService_GetPeerUserProperty = 8007,
545
546 /* Primitives for handling jobs (new in 1.4.2) */
547 _OrthancPluginService_CreateJob = 9000,
548 _OrthancPluginService_FreeJob = 9001,
549 _OrthancPluginService_SubmitJob = 9002,
550 _OrthancPluginService_RegisterJobsUnserializer = 9003,
551
552 _OrthancPluginService_INTERNAL = 0x7fffffff
553 } _OrthancPluginService;
554
555
556 typedef enum
557 {
558 _OrthancPluginProperty_Description = 1,
559 _OrthancPluginProperty_RootUri = 2,
560 _OrthancPluginProperty_OrthancExplorer = 3,
561
562 _OrthancPluginProperty_INTERNAL = 0x7fffffff
563 } _OrthancPluginProperty;
564
565
566
567 /**
568 * The memory layout of the pixels of an image.
569 * @ingroup Images
570 **/
571 typedef enum
572 {
573 /**
574 * @brief Graylevel 8bpp image.
575 *
576 * The image is graylevel. Each pixel is unsigned and stored in
577 * one byte.
578 **/
579 OrthancPluginPixelFormat_Grayscale8 = 1,
580
581 /**
582 * @brief Graylevel, unsigned 16bpp image.
583 *
584 * The image is graylevel. Each pixel is unsigned and stored in
585 * two bytes.
586 **/
587 OrthancPluginPixelFormat_Grayscale16 = 2,
588
589 /**
590 * @brief Graylevel, signed 16bpp image.
591 *
592 * The image is graylevel. Each pixel is signed and stored in two
593 * bytes.
594 **/
595 OrthancPluginPixelFormat_SignedGrayscale16 = 3,
596
597 /**
598 * @brief Color image in RGB24 format.
599 *
600 * This format describes a color image. The pixels are stored in 3
601 * consecutive bytes. The memory layout is RGB.
602 **/
603 OrthancPluginPixelFormat_RGB24 = 4,
604
605 /**
606 * @brief Color image in RGBA32 format.
607 *
608 * This format describes a color image. The pixels are stored in 4
609 * consecutive bytes. The memory layout is RGBA.
610 **/
611 OrthancPluginPixelFormat_RGBA32 = 5,
612
613 OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */
614
615 /**
616 * @brief Color image in RGB48 format.
617 *
618 * This format describes a color image. The pixels are stored in 6
619 * consecutive bytes. The memory layout is RRGGBB.
620 **/
621 OrthancPluginPixelFormat_RGB48 = 7,
622
623 /**
624 * @brief Graylevel, unsigned 32bpp image.
625 *
626 * The image is graylevel. Each pixel is unsigned and stored in
627 * four bytes.
628 **/
629 OrthancPluginPixelFormat_Grayscale32 = 8,
630
631 /**
632 * @brief Graylevel, floating-point 32bpp image.
633 *
634 * The image is graylevel. Each pixel is floating-point and stored
635 * in four bytes.
636 **/
637 OrthancPluginPixelFormat_Float32 = 9,
638
639 /**
640 * @brief Color image in BGRA32 format.
641 *
642 * This format describes a color image. The pixels are stored in 4
643 * consecutive bytes. The memory layout is BGRA.
644 **/
645 OrthancPluginPixelFormat_BGRA32 = 10,
646
647 /**
648 * @brief Graylevel, unsigned 64bpp image.
649 *
650 * The image is graylevel. Each pixel is unsigned and stored in
651 * eight bytes.
652 **/
653 OrthancPluginPixelFormat_Grayscale64 = 11,
654
655 _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
656 } OrthancPluginPixelFormat;
657
658
659
660 /**
661 * The content types that are supported by Orthanc plugins.
662 **/
663 typedef enum
664 {
665 OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */
666 OrthancPluginContentType_Dicom = 1, /*!< DICOM */
667 OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */
668
669 _OrthancPluginContentType_INTERNAL = 0x7fffffff
670 } OrthancPluginContentType;
671
672
673
674 /**
675 * The supported types of DICOM resources.
676 **/
677 typedef enum
678 {
679 OrthancPluginResourceType_Patient = 0, /*!< Patient */
680 OrthancPluginResourceType_Study = 1, /*!< Study */
681 OrthancPluginResourceType_Series = 2, /*!< Series */
682 OrthancPluginResourceType_Instance = 3, /*!< Instance */
683 OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */
684
685 _OrthancPluginResourceType_INTERNAL = 0x7fffffff
686 } OrthancPluginResourceType;
687
688
689
690 /**
691 * The supported types of changes that can happen to DICOM resources.
692 * @ingroup Callbacks
693 **/
694 typedef enum
695 {
696 OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */
697 OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */
698 OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */
699 OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */
700 OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */
701 OrthancPluginChangeType_NewSeries = 5, /*!< New series created */
702 OrthancPluginChangeType_NewStudy = 6, /*!< New study created */
703 OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */
704 OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */
705 OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */
706 OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */
707 OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */
708 OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
709 OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */
710 OrthancPluginChangeType_UpdatedPeers = 14, /*!< The list of Orthanc peers has changed */
711 OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */
712
713 _OrthancPluginChangeType_INTERNAL = 0x7fffffff
714 } OrthancPluginChangeType;
715
716
717 /**
718 * The compression algorithms that are supported by the Orthanc core.
719 * @ingroup Images
720 **/
721 typedef enum
722 {
723 OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */
724 OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */
725 OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */
726 OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */
727
728 _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
729 } OrthancPluginCompressionType;
730
731
732 /**
733 * The image formats that are supported by the Orthanc core.
734 * @ingroup Images
735 **/
736 typedef enum
737 {
738 OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */
739 OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */
740 OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */
741
742 _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
743 } OrthancPluginImageFormat;
744
745
746 /**
747 * The value representations present in the DICOM standard (version 2013).
748 * @ingroup Toolbox
749 **/
750 typedef enum
751 {
752 OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */
753 OrthancPluginValueRepresentation_AS = 2, /*!< Age String */
754 OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */
755 OrthancPluginValueRepresentation_CS = 4, /*!< Code String */
756 OrthancPluginValueRepresentation_DA = 5, /*!< Date */
757 OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */
758 OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */
759 OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */
760 OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */
761 OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */
762 OrthancPluginValueRepresentation_LO = 11, /*!< Long String */
763 OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */
764 OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */
765 OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */
766 OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */
767 OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */
768 OrthancPluginValueRepresentation_SH = 17, /*!< Short String */
769 OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */
770 OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */
771 OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */
772 OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */
773 OrthancPluginValueRepresentation_TM = 22, /*!< Time */
774 OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */
775 OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */
776 OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */
777 OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */
778 OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */
779
780 _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
781 } OrthancPluginValueRepresentation;
782
783
784 /**
785 * The possible output formats for a DICOM-to-JSON conversion.
786 * @ingroup Toolbox
787 * @see OrthancPluginDicomToJson()
788 **/
789 typedef enum
790 {
791 OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */
792 OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */
793 OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */
794
795 _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
796 } OrthancPluginDicomToJsonFormat;
797
798
799 /**
800 * Flags to customize a DICOM-to-JSON conversion. By default, binary
801 * tags are formatted using Data URI scheme.
802 * @ingroup Toolbox
803 **/
804 typedef enum
805 {
806 OrthancPluginDicomToJsonFlags_None = 0,
807 OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */
808 OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */
809 OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */
810 OrthancPluginDicomToJsonFlags_IncludePixelData = (1 << 3), /*!< Include the pixel data */
811 OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), /*!< Output binary tags as-is, dropping non-ASCII */
812 OrthancPluginDicomToJsonFlags_ConvertBinaryToNull = (1 << 5), /*!< Signal binary tags as null values */
813
814 _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
815 } OrthancPluginDicomToJsonFlags;
816
817
818 /**
819 * Flags to the creation of a DICOM file.
820 * @ingroup Toolbox
821 * @see OrthancPluginCreateDicom()
822 **/
823 typedef enum
824 {
825 OrthancPluginCreateDicomFlags_None = 0,
826 OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */
827 OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */
828
829 _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
830 } OrthancPluginCreateDicomFlags;
831
832
833 /**
834 * The constraints on the DICOM identifiers that must be supported
835 * by the database plugins.
836 * @deprecated Plugins using OrthancPluginConstraintType will be faster
837 **/
838 typedef enum
839 {
840 OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */
841 OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */
842 OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */
843 OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */
844
845 _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
846 } OrthancPluginIdentifierConstraint;
847
848
849 /**
850 * The constraints on the tags (main DICOM tags and identifier tags)
851 * that must be supported by the database plugins.
852 **/
853 typedef enum
854 {
855 OrthancPluginConstraintType_Equal = 1, /*!< Equal */
856 OrthancPluginConstraintType_SmallerOrEqual = 2, /*!< Less or equal */
857 OrthancPluginConstraintType_GreaterOrEqual = 3, /*!< More or equal */
858 OrthancPluginConstraintType_Wildcard = 4, /*!< Wildcard matching */
859 OrthancPluginConstraintType_List = 5, /*!< List of values */
860
861 _OrthancPluginConstraintType_INTERNAL = 0x7fffffff
862 } OrthancPluginConstraintType;
863
864
865 /**
866 * The origin of a DICOM instance that has been received by Orthanc.
867 **/
868 typedef enum
869 {
870 OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */
871 OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */
872 OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */
873 OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */
874 OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */
875
876 _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
877 } OrthancPluginInstanceOrigin;
878
879
880 /**
881 * The possible status for one single step of a job.
882 **/
883 typedef enum
884 {
885 OrthancPluginJobStepStatus_Success = 1, /*!< The job has successfully executed all its steps */
886 OrthancPluginJobStepStatus_Failure = 2, /*!< The job has failed while executing this step */
887 OrthancPluginJobStepStatus_Continue = 3 /*!< The job has still data to process after this step */
888 } OrthancPluginJobStepStatus;
889
890
891 /**
892 * Explains why the job should stop and release the resources it has
893 * allocated. This is especially important to disambiguate between
894 * the "paused" condition and the "final" conditions (success,
895 * failure, or canceled).
896 **/
897 typedef enum
898 {
899 OrthancPluginJobStopReason_Success = 1, /*!< The job has succeeded */
900 OrthancPluginJobStopReason_Paused = 2, /*!< The job was paused, and will be resumed later */
901 OrthancPluginJobStopReason_Failure = 3, /*!< The job has failed, and might be resubmitted later */
902 OrthancPluginJobStopReason_Canceled = 4 /*!< The job was canceled, and might be resubmitted later */
903 } OrthancPluginJobStopReason;
904
905
906 /**
907 * The available types of metrics.
908 **/
909 typedef enum
910 {
911 OrthancPluginMetricsType_Default, /*!< Default metrics */
912
913 /**
914 * This metrics represents a time duration. Orthanc will keep the
915 * maximum value of the metrics over a sliding window of ten
916 * seconds, which is useful if the metrics is sampled frequently.
917 **/
918 OrthancPluginMetricsType_Timer
919 } OrthancPluginMetricsType;
920
921
922 /**
923 * The available modes to export a binary DICOM tag into a DICOMweb
924 * JSON or XML document.
925 **/
926 typedef enum
927 {
928 OrthancPluginDicomWebBinaryMode_Ignore, /*!< Don't include binary tags */
929 OrthancPluginDicomWebBinaryMode_InlineBinary, /*!< Inline encoding using Base64 */
930 OrthancPluginDicomWebBinaryMode_BulkDataUri /*!< Use a bulk data URI field */
931 } OrthancPluginDicomWebBinaryMode;
932
933
934
935 /**
936 * @brief A memory buffer allocated by the core system of Orthanc.
937 *
938 * A memory buffer allocated by the core system of Orthanc. When the
939 * content of the buffer is not useful anymore, it must be free by a
940 * call to ::OrthancPluginFreeMemoryBuffer().
941 **/
942 typedef struct
943 {
944 /**
945 * @brief The content of the buffer.
946 **/
947 void* data;
948
949 /**
950 * @brief The number of bytes in the buffer.
951 **/
952 uint32_t size;
953 } OrthancPluginMemoryBuffer;
954
955
956
957
958 /**
959 * @brief Opaque structure that represents the HTTP connection to the client application.
960 * @ingroup Callback
961 **/
962 typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
963
964
965
966 /**
967 * @brief Opaque structure that represents a DICOM instance received by Orthanc.
968 **/
969 typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
970
971
972
973 /**
974 * @brief Opaque structure that represents an image that is uncompressed in memory.
975 * @ingroup Images
976 **/
977 typedef struct _OrthancPluginImage_t OrthancPluginImage;
978
979
980
981 /**
982 * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
983 * @ingroup Images
984 **/
985 typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
986
987
988
989 /**
990 * @brief Opaque structure to an object that represents a C-Find query for worklists.
991 * @ingroup DicomCallbacks
992 **/
993 typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
994
995
996
997 /**
998 * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
999 * @ingroup DicomCallbacks
1000 **/
1001 typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
1002
1003
1004
1005 /**
1006 * @brief Opaque structure to an object that represents a C-Find query.
1007 * @ingroup DicomCallbacks
1008 **/
1009 typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery;
1010
1011
1012
1013 /**
1014 * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
1015 * @ingroup DicomCallbacks
1016 **/
1017 typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers;
1018
1019
1020
1021 /**
1022 * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query.
1023 * @ingroup Toolbox
1024 **/
1025 typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindMatcher;
1026
1027
1028
1029 /**
1030 * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server.
1031 * @ingroup Toolbox
1032 **/
1033 typedef struct _OrthancPluginPeers_t OrthancPluginPeers;
1034
1035
1036
1037 /**
1038 * @brief Opaque structure to a job to be executed by Orthanc.
1039 * @ingroup Toolbox
1040 **/
1041 typedef struct _OrthancPluginJob_t OrthancPluginJob;
1042
1043
1044
1045 /**
1046 * @brief Opaque structure that represents a node in a JSON or XML
1047 * document used in DICOMweb.
1048 * @ingroup Toolbox
1049 **/
1050 typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode;
1051
1052
1053
1054 /**
1055 * @brief Signature of a callback function that answers to a REST request.
1056 * @ingroup Callbacks
1057 **/
1058 typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
1059 OrthancPluginRestOutput* output,
1060 const char* url,
1061 const OrthancPluginHttpRequest* request);
1062
1063
1064
1065 /**
1066 * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
1067 * @ingroup Callbacks
1068 **/
1069 typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
1070 OrthancPluginDicomInstance* instance,
1071 const char* instanceId);
1072
1073
1074
1075 /**
1076 * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
1077 * @ingroup Callbacks
1078 **/
1079 typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
1080 OrthancPluginChangeType changeType,
1081 OrthancPluginResourceType resourceType,
1082 const char* resourceId);
1083
1084
1085
1086 /**
1087 * @brief Signature of a callback function to decode a DICOM instance as an image.
1088 * @ingroup Callbacks
1089 **/
1090 typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
1091 OrthancPluginImage** target,
1092 const void* dicom,
1093 const uint32_t size,
1094 uint32_t frameIndex);
1095
1096
1097
1098 /**
1099 * @brief Signature of a function to free dynamic memory.
1100 * @ingroup Callbacks
1101 **/
1102 typedef void (*OrthancPluginFree) (void* buffer);
1103
1104
1105
1106 /**
1107 * @brief Signature of a function to set the content of a node
1108 * encoding a binary DICOM tag, into a JSON or XML document
1109 * generated for DICOMweb.
1110 * @ingroup Callbacks
1111 **/
1112 typedef void (*OrthancPluginDicomWebSetBinaryNode) (
1113 OrthancPluginDicomWebNode* node,
1114 OrthancPluginDicomWebBinaryMode mode,
1115 const char* bulkDataUri);
1116
1117
1118
1119 /**
1120 * @brief Callback for writing to the storage area.
1121 *
1122 * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
1123 *
1124 * @param uuid The UUID of the file.
1125 * @param content The content of the file.
1126 * @param size The size of the file.
1127 * @param type The content type corresponding to this file.
1128 * @return 0 if success, other value if error.
1129 * @ingroup Callbacks
1130 **/
1131 typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
1132 const char* uuid,
1133 const void* content,
1134 int64_t size,
1135 OrthancPluginContentType type);
1136
1137
1138
1139 /**
1140 * @brief Callback for reading from the storage area.
1141 *
1142 * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
1143 *
1144 * @param content The content of the file (output).
1145 * @param size The size of the file (output).
1146 * @param uuid The UUID of the file of interest.
1147 * @param type The content type corresponding to this file.
1148 * @return 0 if success, other value if error.
1149 * @ingroup Callbacks
1150 **/
1151 typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
1152 void** content,
1153 int64_t* size,
1154 const char* uuid,
1155 OrthancPluginContentType type);
1156
1157
1158
1159 /**
1160 * @brief Callback for removing a file from the storage area.
1161 *
1162 * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
1163 *
1164 * @param uuid The UUID of the file to be removed.
1165 * @param type The content type corresponding to this file.
1166 * @return 0 if success, other value if error.
1167 * @ingroup Callbacks
1168 **/
1169 typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
1170 const char* uuid,
1171 OrthancPluginContentType type);
1172
1173
1174
1175 /**
1176 * @brief Callback to handle the C-Find SCP requests for worklists.
1177 *
1178 * Signature of a callback function that is triggered when Orthanc
1179 * receives a C-Find SCP request against modality worklists.
1180 *
1181 * @param answers The target structure where answers must be stored.
1182 * @param query The worklist query.
1183 * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
1184 * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
1185 * @return 0 if success, other value if error.
1186 * @ingroup DicomCallbacks
1187 **/
1188 typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
1189 OrthancPluginWorklistAnswers* answers,
1190 const OrthancPluginWorklistQuery* query,
1191 const char* issuerAet,
1192 const char* calledAet);
1193
1194
1195
1196 /**
1197 * @brief Callback to filter incoming HTTP requests received by Orthanc.
1198 *
1199 * Signature of a callback function that is triggered whenever
1200 * Orthanc receives an HTTP/REST request, and that answers whether
1201 * this request should be allowed. If the callback returns "0"
1202 * ("false"), the server answers with HTTP status code 403
1203 * (Forbidden).
1204 *
1205 * @param method The HTTP method used by the request.
1206 * @param uri The URI of interest.
1207 * @param ip The IP address of the HTTP client.
1208 * @param headersCount The number of HTTP headers.
1209 * @param headersKeys The keys of the HTTP headers (always converted to low-case).
1210 * @param headersValues The values of the HTTP headers.
1211 * @return 0 if forbidden access, 1 if allowed access, -1 if error.
1212 * @ingroup Callback
1213 * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2()
1214 **/
1215 typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) (
1216 OrthancPluginHttpMethod method,
1217 const char* uri,
1218 const char* ip,
1219 uint32_t headersCount,
1220 const char* const* headersKeys,
1221 const char* const* headersValues);
1222
1223
1224
1225 /**
1226 * @brief Callback to filter incoming HTTP requests received by Orthanc.
1227 *
1228 * Signature of a callback function that is triggered whenever
1229 * Orthanc receives an HTTP/REST request, and that answers whether
1230 * this request should be allowed. If the callback returns "0"
1231 * ("false"), the server answers with HTTP status code 403
1232 * (Forbidden).
1233 *
1234 * @param method The HTTP method used by the request.
1235 * @param uri The URI of interest.
1236 * @param ip The IP address of the HTTP client.
1237 * @param headersCount The number of HTTP headers.
1238 * @param headersKeys The keys of the HTTP headers (always converted to low-case).
1239 * @param headersValues The values of the HTTP headers.
1240 * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method).
1241 * @param getArgumentsKeys The keys of the GET arguments (only for the GET HTTP method).
1242 * @param getArgumentsValues The values of the GET arguments (only for the GET HTTP method).
1243 * @return 0 if forbidden access, 1 if allowed access, -1 if error.
1244 * @ingroup Callback
1245 **/
1246 typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) (
1247 OrthancPluginHttpMethod method,
1248 const char* uri,
1249 const char* ip,
1250 uint32_t headersCount,
1251 const char* const* headersKeys,
1252 const char* const* headersValues,
1253 uint32_t getArgumentsCount,
1254 const char* const* getArgumentsKeys,
1255 const char* const* getArgumentsValues);
1256
1257
1258
1259 /**
1260 * @brief Callback to handle incoming C-Find SCP requests.
1261 *
1262 * Signature of a callback function that is triggered whenever
1263 * Orthanc receives a C-Find SCP request not concerning modality
1264 * worklists.
1265 *
1266 * @param answers The target structure where answers must be stored.
1267 * @param query The worklist query.
1268 * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
1269 * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
1270 * @return 0 if success, other value if error.
1271 * @ingroup DicomCallbacks
1272 **/
1273 typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
1274 OrthancPluginFindAnswers* answers,
1275 const OrthancPluginFindQuery* query,
1276 const char* issuerAet,
1277 const char* calledAet);
1278
1279
1280
1281 /**
1282 * @brief Callback to handle incoming C-Move SCP requests.
1283 *
1284 * Signature of a callback function that is triggered whenever
1285 * Orthanc receives a C-Move SCP request. The callback receives the
1286 * type of the resource of interest (study, series, instance...)
1287 * together with the DICOM tags containing its identifiers. In turn,
1288 * the plugin must create a driver object that will be responsible
1289 * for driving the successive move suboperations.
1290 *
1291 * @param resourceType The type of the resource of interest. Note
1292 * that this might be set to ResourceType_None if the
1293 * QueryRetrieveLevel (0008,0052) tag was not provided by the
1294 * issuer (i.e. the originator modality).
1295 * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL.
1296 * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL.
1297 * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL.
1298 * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL.
1299 * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL.
1300 * @param originatorAet The Application Entity Title (AET) of the
1301 * modality from which the request originates.
1302 * @param sourceAet The Application Entity Title (AET) of the
1303 * modality that should send its DICOM files to another modality.
1304 * @param targetAet The Application Entity Title (AET) of the
1305 * modality that should receive the DICOM files.
1306 * @param originatorId The Message ID issued by the originator modality,
1307 * as found in tag (0000,0110) of the DICOM query emitted by the issuer.
1308 *
1309 * @return The NULL value if the plugin cannot deal with this query,
1310 * or a pointer to the driver object that is responsible for
1311 * handling the successive move suboperations.
1312 *
1313 * @note If targetAet equals sourceAet, this is actually a query/retrieve operation.
1314 * @ingroup DicomCallbacks
1315 **/
1316 typedef void* (*OrthancPluginMoveCallback) (
1317 OrthancPluginResourceType resourceType,
1318 const char* patientId,
1319 const char* accessionNumber,
1320 const char* studyInstanceUid,
1321 const char* seriesInstanceUid,
1322 const char* sopInstanceUid,
1323 const char* originatorAet,
1324 const char* sourceAet,
1325 const char* targetAet,
1326 uint16_t originatorId);
1327
1328
1329 /**
1330 * @brief Callback to read the size of a C-Move driver.
1331 *
1332 * Signature of a callback function that returns the number of
1333 * C-Move suboperations that are to be achieved by the given C-Move
1334 * driver. This driver is the return value of a previous call to the
1335 * OrthancPluginMoveCallback() callback.
1336 *
1337 * @param moveDriver The C-Move driver of interest.
1338 * @return The number of suboperations.
1339 * @ingroup DicomCallbacks
1340 **/
1341 typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver);
1342
1343
1344 /**
1345 * @brief Callback to apply one C-Move suboperation.
1346 *
1347 * Signature of a callback function that applies the next C-Move
1348 * suboperation that os to be achieved by the given C-Move
1349 * driver. This driver is the return value of a previous call to the
1350 * OrthancPluginMoveCallback() callback.
1351 *
1352 * @param moveDriver The C-Move driver of interest.
1353 * @return 0 if success, or the error code if failure.
1354 * @ingroup DicomCallbacks
1355 **/
1356 typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver);
1357
1358
1359 /**
1360 * @brief Callback to free one C-Move driver.
1361 *
1362 * Signature of a callback function that releases the resources
1363 * allocated by the given C-Move driver. This driver is the return
1364 * value of a previous call to the OrthancPluginMoveCallback()
1365 * callback.
1366 *
1367 * @param moveDriver The C-Move driver of interest.
1368 * @ingroup DicomCallbacks
1369 **/
1370 typedef void (*OrthancPluginFreeMove) (void* moveDriver);
1371
1372
1373 /**
1374 * @brief Callback to finalize one custom job.
1375 *
1376 * Signature of a callback function that releases all the resources
1377 * allocated by the given job. This job is the argument provided to
1378 * OrthancPluginCreateJob().
1379 *
1380 * @param job The job of interest.
1381 * @ingroup Toolbox
1382 **/
1383 typedef void (*OrthancPluginJobFinalize) (void* job);
1384
1385
1386 /**
1387 * @brief Callback to check the progress of one custom job.
1388 *
1389 * Signature of a callback function that returns the progress of the
1390 * job.
1391 *
1392 * @param job The job of interest.
1393 * @return The progress, as a floating-point number ranging from 0 to 1.
1394 * @ingroup Toolbox
1395 **/
1396 typedef float (*OrthancPluginJobGetProgress) (void* job);
1397
1398
1399 /**
1400 * @brief Callback to retrieve the content of one custom job.
1401 *
1402 * Signature of a callback function that returns human-readable
1403 * statistics about the job. This statistics must be formatted as a
1404 * JSON object. This information is notably displayed in the "Jobs"
1405 * tab of "Orthanc Explorer".
1406 *
1407 * @param job The job of interest.
1408 * @return The statistics, as a JSON object encoded as a string.
1409 * @ingroup Toolbox
1410 **/
1411 typedef const char* (*OrthancPluginJobGetContent) (void* job);
1412
1413
1414 /**
1415 * @brief Callback to serialize one custom job.
1416 *
1417 * Signature of a callback function that returns a serialized
1418 * version of the job, formatted as a JSON object. This
1419 * serialization is stored in the Orthanc database, and is used to
1420 * reload the job on the restart of Orthanc. The "unserialization"
1421 * callback (with OrthancPluginJobsUnserializer signature) will
1422 * receive this serialized object.
1423 *
1424 * @param job The job of interest.
1425 * @return The serialized job, as a JSON object encoded as a string.
1426 * @see OrthancPluginRegisterJobsUnserializer()
1427 * @ingroup Toolbox
1428 **/
1429 typedef const char* (*OrthancPluginJobGetSerialized) (void* job);
1430
1431
1432 /**
1433 * @brief Callback to execute one step of a custom job.
1434 *
1435 * Signature of a callback function that executes one step in the
1436 * job. The jobs engine of Orthanc will make successive calls to
1437 * this method, as long as it returns
1438 * OrthancPluginJobStepStatus_Continue.
1439 *
1440 * @param job The job of interest.
1441 * @return The status of execution.
1442 * @ingroup Toolbox
1443 **/
1444 typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job);
1445
1446
1447 /**
1448 * @brief Callback executed once one custom job leaves the "running" state.
1449 *
1450 * Signature of a callback function that is invoked once a job
1451 * leaves the "running" state. This can happen if the previous call
1452 * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc
1453 * server is being stopped, or if the user manually tags the job as
1454 * paused/canceled. This callback allows the plugin to free
1455 * resources allocated for running this custom job (e.g. to stop
1456 * threads, or to remove temporary files).
1457 *
1458 * Note that handling pauses might involves a specific treatment
1459 * (such a stopping threads, but keeping temporary files on the
1460 * disk). This "paused" situation can be checked by looking at the
1461 * "reason" parameter.
1462 *
1463 * @param job The job of interest.
1464 * @param reason The reason for leaving the "running" state.
1465 * @return 0 if success, or the error code if failure.
1466 * @ingroup Toolbox
1467 **/
1468 typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job,
1469 OrthancPluginJobStopReason reason);
1470
1471
1472 /**
1473 * @brief Callback executed once one stopped custom job is started again.
1474 *
1475 * Signature of a callback function that is invoked once a job
1476 * leaves the "failure/canceled" state, to be started again. This
1477 * function will typically reset the progress to zero. Note that
1478 * before being actually executed, the job would first be tagged as
1479 * "pending" in the Orthanc jobs engine.
1480 *
1481 * @param job The job of interest.
1482 * @return 0 if success, or the error code if failure.
1483 * @ingroup Toolbox
1484 **/
1485 typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job);
1486
1487
1488 /**
1489 * @brief Callback executed to unserialize a custom job.
1490 *
1491 * Signature of a callback function that unserializes a job that was
1492 * saved in the Orthanc database.
1493 *
1494 * @param jobType The type of the job, as provided to OrthancPluginCreateJob().
1495 * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized.
1496 * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL
1497 * if this unserializer cannot handle this job type.
1498 * @see OrthancPluginRegisterJobsUnserializer()
1499 * @ingroup Callbacks
1500 **/
1501 typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType,
1502 const char* serialized);
1503
1504
1505
1506 /**
1507 * @brief Callback executed to update the metrics of the plugin.
1508 *
1509 * Signature of a callback function that is called by Orthanc
1510 * whenever a monitoring tool (such as Prometheus) asks the current
1511 * values of the metrics. This callback gives the plugin a chance to
1512 * update its metrics, by calling OrthancPluginSetMetricsValue().
1513 * This is typically useful for metrics that are expensive to
1514 * acquire.
1515 *
1516 * @see OrthancPluginRegisterRefreshMetrics()
1517 * @ingroup Callbacks
1518 **/
1519 typedef void (*OrthancPluginRefreshMetricsCallback) ();
1520
1521
1522
1523 /**
1524 * @brief Callback executed to encode a binary tag in DICOMweb.
1525 *
1526 * Signature of a callback function that is called by Orthanc
1527 * whenever a DICOM tag that contains a binary value must be written
1528 * to a JSON or XML node, while a DICOMweb document is being
1529 * generated. The value representation (VR) of the DICOM tag can be
1530 * OB, OD, OF, OL, OW, or UN.
1531 *
1532 * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml()
1533 * @param node The node being generated, as provided by Orthanc.
1534 * @param setter The setter to be used to encode the content of the node. If
1535 * the setter is not called, the binary tag is not written to the output document.
1536 * @param levelDepth The depth of the node in the DICOM hierarchy of sequences.
1537 * This parameter gives the number of elements in the "levelTagGroup",
1538 * "levelTagElement", and "levelIndex" arrays.
1539 * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
1540 * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
1541 * @param levelIndex The index of the node in the parent sequences of the hiearchy.
1542 * @param tagGroup The group of the DICOM tag of interest.
1543 * @param tagElement The element of the DICOM tag of interest.
1544 * @param vr The value representation of the binary DICOM node.
1545 * @ingroup Callbacks
1546 **/
1547 typedef void (*OrthancPluginDicomWebBinaryCallback) (
1548 OrthancPluginDicomWebNode* node,
1549 OrthancPluginDicomWebSetBinaryNode setter,
1550 uint32_t levelDepth,
1551 const uint16_t* levelTagGroup,
1552 const uint16_t* levelTagElement,
1553 const uint32_t* levelIndex,
1554 uint16_t tagGroup,
1555 uint16_t tagElement,
1556 OrthancPluginValueRepresentation vr);
1557
1558
1559
1560 /**
1561 * @brief Data structure that contains information about the Orthanc core.
1562 **/
1563 typedef struct _OrthancPluginContext_t
1564 {
1565 void* pluginsManager;
1566 const char* orthancVersion;
1567 OrthancPluginFree Free;
1568 OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context,
1569 _OrthancPluginService service,
1570 const void* params);
1571 } OrthancPluginContext;
1572
1573
1574
1575 /**
1576 * @brief An entry in the dictionary of DICOM tags.
1577 **/
1578 typedef struct
1579 {
1580 uint16_t group; /*!< The group of the tag */
1581 uint16_t element; /*!< The element of the tag */
1582 OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */
1583 uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */
1584 uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */
1585 } OrthancPluginDictionaryEntry;
1586
1587
1588
1589 /**
1590 * @brief Free a string.
1591 *
1592 * Free a string that was allocated by the core system of Orthanc.
1593 *
1594 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1595 * @param str The string to be freed.
1596 **/
1597 ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString(
1598 OrthancPluginContext* context,
1599 char* str)
1600 {
1601 if (str != NULL)
1602 {
1603 context->Free(str);
1604 }
1605 }
1606
1607
1608 /**
1609 * @brief Check that the version of the hosting Orthanc is above a given version.
1610 *
1611 * This function checks whether the version of the Orthanc server
1612 * running this plugin, is above the given version. Contrarily to
1613 * OrthancPluginCheckVersion(), it is up to the developer of the
1614 * plugin to make sure that all the Orthanc SDK services called by
1615 * the plugin are actually implemented in the given version of
1616 * Orthanc.
1617 *
1618 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1619 * @param expectedMajor Expected major version.
1620 * @param expectedMinor Expected minor version.
1621 * @param expectedRevision Expected revision.
1622 * @return 1 if and only if the versions are compatible. If the
1623 * result is 0, the initialization of the plugin should fail.
1624 * @see OrthancPluginCheckVersion
1625 * @ingroup Callbacks
1626 **/
1627 ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersionAdvanced(
1628 OrthancPluginContext* context,
1629 int expectedMajor,
1630 int expectedMinor,
1631 int expectedRevision)
1632 {
1633 int major, minor, revision;
1634
1635 if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
1636 sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
1637 sizeof(int32_t) != sizeof(_OrthancPluginService) ||
1638 sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
1639 sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
1640 sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
1641 sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
1642 sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
1643 sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
1644 sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
1645 sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
1646 sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
1647 sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
1648 sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
1649 sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
1650 sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
1651 sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
1652 sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
1653 sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
1654 sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode))
1655 {
1656 /* Mismatch in the size of the enumerations */
1657 return 0;
1658 }
1659
1660 /* Assume compatibility with the mainline */
1661 if (!strcmp(context->orthancVersion, "mainline"))
1662 {
1663 return 1;
1664 }
1665
1666 /* Parse the version of the Orthanc core */
1667 if (
1668 #ifdef _MSC_VER
1669 sscanf_s
1670 #else
1671 sscanf
1672 #endif
1673 (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
1674 {
1675 return 0;
1676 }
1677
1678 /* Check the major number of the version */
1679
1680 if (major > expectedMajor)
1681 {
1682 return 1;
1683 }
1684
1685 if (major < expectedMajor)
1686 {
1687 return 0;
1688 }
1689
1690 /* Check the minor number of the version */
1691
1692 if (minor > expectedMinor)
1693 {
1694 return 1;
1695 }
1696
1697 if (minor < expectedMinor)
1698 {
1699 return 0;
1700 }
1701
1702 /* Check the revision number of the version */
1703
1704 if (revision >= expectedRevision)
1705 {
1706 return 1;
1707 }
1708 else
1709 {
1710 return 0;
1711 }
1712 }
1713
1714
1715 /**
1716 * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
1717 *
1718 * This function checks whether the version of the Orthanc server
1719 * running this plugin, is above the version of the current Orthanc
1720 * SDK header. This guarantees that the plugin is compatible with
1721 * the hosting Orthanc (i.e. it will not call unavailable services).
1722 * The result of this function should always be checked in the
1723 * OrthancPluginInitialize() entry point of the plugin.
1724 *
1725 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1726 * @return 1 if and only if the versions are compatible. If the
1727 * result is 0, the initialization of the plugin should fail.
1728 * @see OrthancPluginCheckVersionAdvanced
1729 * @ingroup Callbacks
1730 **/
1731 ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion(
1732 OrthancPluginContext* context)
1733 {
1734 return OrthancPluginCheckVersionAdvanced(
1735 context,
1736 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
1737 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
1738 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
1739 }
1740
1741
1742 /**
1743 * @brief Free a memory buffer.
1744 *
1745 * Free a memory buffer that was allocated by the core system of Orthanc.
1746 *
1747 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1748 * @param buffer The memory buffer to release.
1749 **/
1750 ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer(
1751 OrthancPluginContext* context,
1752 OrthancPluginMemoryBuffer* buffer)
1753 {
1754 context->Free(buffer->data);
1755 }
1756
1757
1758 /**
1759 * @brief Log an error.
1760 *
1761 * Log an error message using the Orthanc logging system.
1762 *
1763 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1764 * @param message The message to be logged.
1765 **/
1766 ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
1767 OrthancPluginContext* context,
1768 const char* message)
1769 {
1770 context->InvokeService(context, _OrthancPluginService_LogError, message);
1771 }
1772
1773
1774 /**
1775 * @brief Log a warning.
1776 *
1777 * Log a warning message using the Orthanc logging system.
1778 *
1779 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1780 * @param message The message to be logged.
1781 **/
1782 ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
1783 OrthancPluginContext* context,
1784 const char* message)
1785 {
1786 context->InvokeService(context, _OrthancPluginService_LogWarning, message);
1787 }
1788
1789
1790 /**
1791 * @brief Log an information.
1792 *
1793 * Log an information message using the Orthanc logging system.
1794 *
1795 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1796 * @param message The message to be logged.
1797 **/
1798 ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
1799 OrthancPluginContext* context,
1800 const char* message)
1801 {
1802 context->InvokeService(context, _OrthancPluginService_LogInfo, message);
1803 }
1804
1805
1806
1807 typedef struct
1808 {
1809 const char* pathRegularExpression;
1810 OrthancPluginRestCallback callback;
1811 } _OrthancPluginRestCallback;
1812
1813 /**
1814 * @brief Register a REST callback.
1815 *
1816 * This function registers a REST callback against a regular
1817 * expression for a URI. This function must be called during the
1818 * initialization of the plugin, i.e. inside the
1819 * OrthancPluginInitialize() public function.
1820 *
1821 * Each REST callback is guaranteed to run in mutual exclusion.
1822 *
1823 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1824 * @param pathRegularExpression Regular expression for the URI. May contain groups.
1825 * @param callback The callback function to handle the REST call.
1826 * @see OrthancPluginRegisterRestCallbackNoLock()
1827 *
1828 * @note
1829 * The regular expression is case sensitive and must follow the
1830 * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
1831 *
1832 * @ingroup Callbacks
1833 **/
1834 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
1835 OrthancPluginContext* context,
1836 const char* pathRegularExpression,
1837 OrthancPluginRestCallback callback)
1838 {
1839 _OrthancPluginRestCallback params;
1840 params.pathRegularExpression = pathRegularExpression;
1841 params.callback = callback;
1842 context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
1843 }
1844
1845
1846
1847 /**
1848 * @brief Register a REST callback, without locking.
1849 *
1850 * This function registers a REST callback against a regular
1851 * expression for a URI. This function must be called during the
1852 * initialization of the plugin, i.e. inside the
1853 * OrthancPluginInitialize() public function.
1854 *
1855 * Contrarily to OrthancPluginRegisterRestCallback(), the callback
1856 * will NOT be invoked in mutual exclusion. This can be useful for
1857 * high-performance plugins that must handle concurrent requests
1858 * (Orthanc uses a pool of threads, one thread being assigned to
1859 * each incoming HTTP request). Of course, if using this function,
1860 * it is up to the plugin to implement the required locking
1861 * mechanisms.
1862 *
1863 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1864 * @param pathRegularExpression Regular expression for the URI. May contain groups.
1865 * @param callback The callback function to handle the REST call.
1866 * @see OrthancPluginRegisterRestCallback()
1867 *
1868 * @note
1869 * The regular expression is case sensitive and must follow the
1870 * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
1871 *
1872 * @ingroup Callbacks
1873 **/
1874 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
1875 OrthancPluginContext* context,
1876 const char* pathRegularExpression,
1877 OrthancPluginRestCallback callback)
1878 {
1879 _OrthancPluginRestCallback params;
1880 params.pathRegularExpression = pathRegularExpression;
1881 params.callback = callback;
1882 context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
1883 }
1884
1885
1886
1887 typedef struct
1888 {
1889 OrthancPluginOnStoredInstanceCallback callback;
1890 } _OrthancPluginOnStoredInstanceCallback;
1891
1892 /**
1893 * @brief Register a callback for received instances.
1894 *
1895 * This function registers a callback function that is called
1896 * whenever a new DICOM instance is stored into the Orthanc core.
1897 *
1898 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1899 * @param callback The callback function.
1900 * @ingroup Callbacks
1901 **/
1902 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
1903 OrthancPluginContext* context,
1904 OrthancPluginOnStoredInstanceCallback callback)
1905 {
1906 _OrthancPluginOnStoredInstanceCallback params;
1907 params.callback = callback;
1908
1909 context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
1910 }
1911
1912
1913
1914 typedef struct
1915 {
1916 OrthancPluginRestOutput* output;
1917 const char* answer;
1918 uint32_t answerSize;
1919 const char* mimeType;
1920 } _OrthancPluginAnswerBuffer;
1921
1922 /**
1923 * @brief Answer to a REST request.
1924 *
1925 * This function answers to a REST request with the content of a memory buffer.
1926 *
1927 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1928 * @param output The HTTP connection to the client application.
1929 * @param answer Pointer to the memory buffer containing the answer.
1930 * @param answerSize Number of bytes of the answer.
1931 * @param mimeType The MIME type of the answer.
1932 * @ingroup REST
1933 **/
1934 ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
1935 OrthancPluginContext* context,
1936 OrthancPluginRestOutput* output,
1937 const char* answer,
1938 uint32_t answerSize,
1939 const char* mimeType)
1940 {
1941 _OrthancPluginAnswerBuffer params;
1942 params.output = output;
1943 params.answer = answer;
1944 params.answerSize = answerSize;
1945 params.mimeType = mimeType;
1946 context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
1947 }
1948
1949
1950 typedef struct
1951 {
1952 OrthancPluginRestOutput* output;
1953 OrthancPluginPixelFormat format;
1954 uint32_t width;
1955 uint32_t height;
1956 uint32_t pitch;
1957 const void* buffer;
1958 } _OrthancPluginCompressAndAnswerPngImage;
1959
1960 typedef struct
1961 {
1962 OrthancPluginRestOutput* output;
1963 OrthancPluginImageFormat imageFormat;
1964 OrthancPluginPixelFormat pixelFormat;
1965 uint32_t width;
1966 uint32_t height;
1967 uint32_t pitch;
1968 const void* buffer;
1969 uint8_t quality;
1970 } _OrthancPluginCompressAndAnswerImage;
1971
1972
1973 /**
1974 * @brief Answer to a REST request with a PNG image.
1975 *
1976 * This function answers to a REST request with a PNG image. The
1977 * parameters of this function describe a memory buffer that
1978 * contains an uncompressed image. The image will be automatically compressed
1979 * as a PNG image by the core system of Orthanc.
1980 *
1981 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
1982 * @param output The HTTP connection to the client application.
1983 * @param format The memory layout of the uncompressed image.
1984 * @param width The width of the image.
1985 * @param height The height of the image.
1986 * @param pitch The pitch of the image (i.e. the number of bytes
1987 * between 2 successive lines of the image in the memory buffer).
1988 * @param buffer The memory buffer containing the uncompressed image.
1989 * @ingroup REST
1990 **/
1991 ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
1992 OrthancPluginContext* context,
1993 OrthancPluginRestOutput* output,
1994 OrthancPluginPixelFormat format,
1995 uint32_t width,
1996 uint32_t height,
1997 uint32_t pitch,
1998 const void* buffer)
1999 {
2000 _OrthancPluginCompressAndAnswerImage params;
2001 params.output = output;
2002 params.imageFormat = OrthancPluginImageFormat_Png;
2003 params.pixelFormat = format;
2004 params.width = width;
2005 params.height = height;
2006 params.pitch = pitch;
2007 params.buffer = buffer;
2008 params.quality = 0; /* No quality for PNG */
2009 context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
2010 }
2011
2012
2013
2014 typedef struct
2015 {
2016 OrthancPluginMemoryBuffer* target;
2017 const char* instanceId;
2018 } _OrthancPluginGetDicomForInstance;
2019
2020 /**
2021 * @brief Retrieve a DICOM instance using its Orthanc identifier.
2022 *
2023 * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
2024 * file is stored into a newly allocated memory buffer.
2025 *
2026 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2027 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2028 * @param instanceId The Orthanc identifier of the DICOM instance of interest.
2029 * @return 0 if success, or the error code if failure.
2030 * @ingroup Orthanc
2031 **/
2032 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance(
2033 OrthancPluginContext* context,
2034 OrthancPluginMemoryBuffer* target,
2035 const char* instanceId)
2036 {
2037 _OrthancPluginGetDicomForInstance params;
2038 params.target = target;
2039 params.instanceId = instanceId;
2040 return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
2041 }
2042
2043
2044
2045 typedef struct
2046 {
2047 OrthancPluginMemoryBuffer* target;
2048 const char* uri;
2049 } _OrthancPluginRestApiGet;
2050
2051 /**
2052 * @brief Make a GET call to the built-in Orthanc REST API.
2053 *
2054 * Make a GET call to the built-in Orthanc REST API. The result to
2055 * the query is stored into a newly allocated memory buffer.
2056 *
2057 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2058 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2059 * @param uri The URI in the built-in Orthanc API.
2060 * @return 0 if success, or the error code if failure.
2061 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2062 * @see OrthancPluginRestApiGetAfterPlugins
2063 * @ingroup Orthanc
2064 **/
2065 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet(
2066 OrthancPluginContext* context,
2067 OrthancPluginMemoryBuffer* target,
2068 const char* uri)
2069 {
2070 _OrthancPluginRestApiGet params;
2071 params.target = target;
2072 params.uri = uri;
2073 return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
2074 }
2075
2076
2077
2078 /**
2079 * @brief Make a GET call to the REST API, as tainted by the plugins.
2080 *
2081 * Make a GET call to the Orthanc REST API, after all the plugins
2082 * are applied. In other words, if some plugin overrides or adds the
2083 * called URI to the built-in Orthanc REST API, this call will
2084 * return the result provided by this plugin. The result to the
2085 * query is stored into a newly allocated memory buffer.
2086 *
2087 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2088 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2089 * @param uri The URI in the built-in Orthanc API.
2090 * @return 0 if success, or the error code if failure.
2091 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2092 * @see OrthancPluginRestApiGet
2093 * @ingroup Orthanc
2094 **/
2095 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins(
2096 OrthancPluginContext* context,
2097 OrthancPluginMemoryBuffer* target,
2098 const char* uri)
2099 {
2100 _OrthancPluginRestApiGet params;
2101 params.target = target;
2102 params.uri = uri;
2103 return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
2104 }
2105
2106
2107
2108 typedef struct
2109 {
2110 OrthancPluginMemoryBuffer* target;
2111 const char* uri;
2112 const void* body;
2113 uint32_t bodySize;
2114 } _OrthancPluginRestApiPostPut;
2115
2116 /**
2117 * @brief Make a POST call to the built-in Orthanc REST API.
2118 *
2119 * Make a POST call to the built-in Orthanc REST API. The result to
2120 * the query is stored into a newly allocated memory buffer.
2121 *
2122 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2123 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2124 * @param uri The URI in the built-in Orthanc API.
2125 * @param body The body of the POST request.
2126 * @param bodySize The size of the body.
2127 * @return 0 if success, or the error code if failure.
2128 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2129 * @see OrthancPluginRestApiPostAfterPlugins
2130 * @ingroup Orthanc
2131 **/
2132 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost(
2133 OrthancPluginContext* context,
2134 OrthancPluginMemoryBuffer* target,
2135 const char* uri,
2136 const void* body,
2137 uint32_t bodySize)
2138 {
2139 _OrthancPluginRestApiPostPut params;
2140 params.target = target;
2141 params.uri = uri;
2142 params.body = body;
2143 params.bodySize = bodySize;
2144 return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
2145 }
2146
2147
2148 /**
2149 * @brief Make a POST call to the REST API, as tainted by the plugins.
2150 *
2151 * Make a POST call to the Orthanc REST API, after all the plugins
2152 * are applied. In other words, if some plugin overrides or adds the
2153 * called URI to the built-in Orthanc REST API, this call will
2154 * return the result provided by this plugin. The result to the
2155 * query is stored into a newly allocated memory buffer.
2156 *
2157 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2158 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2159 * @param uri The URI in the built-in Orthanc API.
2160 * @param body The body of the POST request.
2161 * @param bodySize The size of the body.
2162 * @return 0 if success, or the error code if failure.
2163 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2164 * @see OrthancPluginRestApiPost
2165 * @ingroup Orthanc
2166 **/
2167 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins(
2168 OrthancPluginContext* context,
2169 OrthancPluginMemoryBuffer* target,
2170 const char* uri,
2171 const void* body,
2172 uint32_t bodySize)
2173 {
2174 _OrthancPluginRestApiPostPut params;
2175 params.target = target;
2176 params.uri = uri;
2177 params.body = body;
2178 params.bodySize = bodySize;
2179 return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
2180 }
2181
2182
2183
2184 /**
2185 * @brief Make a DELETE call to the built-in Orthanc REST API.
2186 *
2187 * Make a DELETE call to the built-in Orthanc REST API.
2188 *
2189 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2190 * @param uri The URI to delete in the built-in Orthanc API.
2191 * @return 0 if success, or the error code if failure.
2192 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2193 * @see OrthancPluginRestApiDeleteAfterPlugins
2194 * @ingroup Orthanc
2195 **/
2196 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete(
2197 OrthancPluginContext* context,
2198 const char* uri)
2199 {
2200 return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
2201 }
2202
2203
2204 /**
2205 * @brief Make a DELETE call to the REST API, as tainted by the plugins.
2206 *
2207 * Make a DELETE call to the Orthanc REST API, after all the plugins
2208 * are applied. In other words, if some plugin overrides or adds the
2209 * called URI to the built-in Orthanc REST API, this call will
2210 * return the result provided by this plugin.
2211 *
2212 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2213 * @param uri The URI to delete in the built-in Orthanc API.
2214 * @return 0 if success, or the error code if failure.
2215 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2216 * @see OrthancPluginRestApiDelete
2217 * @ingroup Orthanc
2218 **/
2219 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins(
2220 OrthancPluginContext* context,
2221 const char* uri)
2222 {
2223 return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
2224 }
2225
2226
2227
2228 /**
2229 * @brief Make a PUT call to the built-in Orthanc REST API.
2230 *
2231 * Make a PUT call to the built-in Orthanc REST API. The result to
2232 * the query is stored into a newly allocated memory buffer.
2233 *
2234 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2235 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2236 * @param uri The URI in the built-in Orthanc API.
2237 * @param body The body of the PUT request.
2238 * @param bodySize The size of the body.
2239 * @return 0 if success, or the error code if failure.
2240 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2241 * @see OrthancPluginRestApiPutAfterPlugins
2242 * @ingroup Orthanc
2243 **/
2244 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut(
2245 OrthancPluginContext* context,
2246 OrthancPluginMemoryBuffer* target,
2247 const char* uri,
2248 const void* body,
2249 uint32_t bodySize)
2250 {
2251 _OrthancPluginRestApiPostPut params;
2252 params.target = target;
2253 params.uri = uri;
2254 params.body = body;
2255 params.bodySize = bodySize;
2256 return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
2257 }
2258
2259
2260
2261 /**
2262 * @brief Make a PUT call to the REST API, as tainted by the plugins.
2263 *
2264 * Make a PUT call to the Orthanc REST API, after all the plugins
2265 * are applied. In other words, if some plugin overrides or adds the
2266 * called URI to the built-in Orthanc REST API, this call will
2267 * return the result provided by this plugin. The result to the
2268 * query is stored into a newly allocated memory buffer.
2269 *
2270 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2271 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
2272 * @param uri The URI in the built-in Orthanc API.
2273 * @param body The body of the PUT request.
2274 * @param bodySize The size of the body.
2275 * @return 0 if success, or the error code if failure.
2276 * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
2277 * @see OrthancPluginRestApiPut
2278 * @ingroup Orthanc
2279 **/
2280 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins(
2281 OrthancPluginContext* context,
2282 OrthancPluginMemoryBuffer* target,
2283 const char* uri,
2284 const void* body,
2285 uint32_t bodySize)
2286 {
2287 _OrthancPluginRestApiPostPut params;
2288 params.target = target;
2289 params.uri = uri;
2290 params.body = body;
2291 params.bodySize = bodySize;
2292 return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
2293 }
2294
2295
2296
2297 typedef struct
2298 {
2299 OrthancPluginRestOutput* output;
2300 const char* argument;
2301 } _OrthancPluginOutputPlusArgument;
2302
2303 /**
2304 * @brief Redirect a REST request.
2305 *
2306 * This function answers to a REST request by redirecting the user
2307 * to another URI using HTTP status 301.
2308 *
2309 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2310 * @param output The HTTP connection to the client application.
2311 * @param redirection Where to redirect.
2312 * @ingroup REST
2313 **/
2314 ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
2315 OrthancPluginContext* context,
2316 OrthancPluginRestOutput* output,
2317 const char* redirection)
2318 {
2319 _OrthancPluginOutputPlusArgument params;
2320 params.output = output;
2321 params.argument = redirection;
2322 context->InvokeService(context, _OrthancPluginService_Redirect, &params);
2323 }
2324
2325
2326
2327 typedef struct
2328 {
2329 char** result;
2330 const char* argument;
2331 } _OrthancPluginRetrieveDynamicString;
2332
2333 /**
2334 * @brief Look for a patient.
2335 *
2336 * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
2337 * This function uses the database index to run as fast as possible (it does not loop
2338 * over all the stored patients).
2339 *
2340 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2341 * @param patientID The Patient ID of interest.
2342 * @return The NULL value if the patient is non-existent, or a string containing the
2343 * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
2344 * @ingroup Orthanc
2345 **/
2346 ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
2347 OrthancPluginContext* context,
2348 const char* patientID)
2349 {
2350 char* result;
2351
2352 _OrthancPluginRetrieveDynamicString params;
2353 params.result = &result;
2354 params.argument = patientID;
2355
2356 if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
2357 {
2358 /* Error */
2359 return NULL;
2360 }
2361 else
2362 {
2363 return result;
2364 }
2365 }
2366
2367
2368 /**
2369 * @brief Look for a study.
2370 *
2371 * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
2372 * This function uses the database index to run as fast as possible (it does not loop
2373 * over all the stored studies).
2374 *
2375 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2376 * @param studyUID The Study Instance UID of interest.
2377 * @return The NULL value if the study is non-existent, or a string containing the
2378 * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
2379 * @ingroup Orthanc
2380 **/
2381 ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
2382 OrthancPluginContext* context,
2383 const char* studyUID)
2384 {
2385 char* result;
2386
2387 _OrthancPluginRetrieveDynamicString params;
2388 params.result = &result;
2389 params.argument = studyUID;
2390
2391 if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
2392 {
2393 /* Error */
2394 return NULL;
2395 }
2396 else
2397 {
2398 return result;
2399 }
2400 }
2401
2402
2403 /**
2404 * @brief Look for a study, using the accession number.
2405 *
2406 * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
2407 * This function uses the database index to run as fast as possible (it does not loop
2408 * over all the stored studies).
2409 *
2410 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2411 * @param accessionNumber The Accession Number of interest.
2412 * @return The NULL value if the study is non-existent, or a string containing the
2413 * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
2414 * @ingroup Orthanc
2415 **/
2416 ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
2417 OrthancPluginContext* context,
2418 const char* accessionNumber)
2419 {
2420 char* result;
2421
2422 _OrthancPluginRetrieveDynamicString params;
2423 params.result = &result;
2424 params.argument = accessionNumber;
2425
2426 if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
2427 {
2428 /* Error */
2429 return NULL;
2430 }
2431 else
2432 {
2433 return result;
2434 }
2435 }
2436
2437
2438 /**
2439 * @brief Look for a series.
2440 *
2441 * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
2442 * This function uses the database index to run as fast as possible (it does not loop
2443 * over all the stored series).
2444 *
2445 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2446 * @param seriesUID The Series Instance UID of interest.
2447 * @return The NULL value if the series is non-existent, or a string containing the
2448 * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
2449 * @ingroup Orthanc
2450 **/
2451 ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
2452 OrthancPluginContext* context,
2453 const char* seriesUID)
2454 {
2455 char* result;
2456
2457 _OrthancPluginRetrieveDynamicString params;
2458 params.result = &result;
2459 params.argument = seriesUID;
2460
2461 if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
2462 {
2463 /* Error */
2464 return NULL;
2465 }
2466 else
2467 {
2468 return result;
2469 }
2470 }
2471
2472
2473 /**
2474 * @brief Look for an instance.
2475 *
2476 * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
2477 * This function uses the database index to run as fast as possible (it does not loop
2478 * over all the stored instances).
2479 *
2480 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2481 * @param sopInstanceUID The SOP Instance UID of interest.
2482 * @return The NULL value if the instance is non-existent, or a string containing the
2483 * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
2484 * @ingroup Orthanc
2485 **/
2486 ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
2487 OrthancPluginContext* context,
2488 const char* sopInstanceUID)
2489 {
2490 char* result;
2491
2492 _OrthancPluginRetrieveDynamicString params;
2493 params.result = &result;
2494 params.argument = sopInstanceUID;
2495
2496 if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
2497 {
2498 /* Error */
2499 return NULL;
2500 }
2501 else
2502 {
2503 return result;
2504 }
2505 }
2506
2507
2508
2509 typedef struct
2510 {
2511 OrthancPluginRestOutput* output;
2512 uint16_t status;
2513 } _OrthancPluginSendHttpStatusCode;
2514
2515 /**
2516 * @brief Send a HTTP status code.
2517 *
2518 * This function answers to a REST request by sending a HTTP status
2519 * code (such as "400 - Bad Request"). Note that:
2520 * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
2521 * - Redirections (status 301) must use ::OrthancPluginRedirect().
2522 * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
2523 * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
2524 *
2525 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2526 * @param output The HTTP connection to the client application.
2527 * @param status The HTTP status code to be sent.
2528 * @ingroup REST
2529 * @see OrthancPluginSendHttpStatus()
2530 **/
2531 ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
2532 OrthancPluginContext* context,
2533 OrthancPluginRestOutput* output,
2534 uint16_t status)
2535 {
2536 _OrthancPluginSendHttpStatusCode params;
2537 params.output = output;
2538 params.status = status;
2539 context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
2540 }
2541
2542
2543 /**
2544 * @brief Signal that a REST request is not authorized.
2545 *
2546 * This function answers to a REST request by signaling that it is
2547 * not authorized.
2548 *
2549 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2550 * @param output The HTTP connection to the client application.
2551 * @param realm The realm for the authorization process.
2552 * @ingroup REST
2553 **/
2554 ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
2555 OrthancPluginContext* context,
2556 OrthancPluginRestOutput* output,
2557 const char* realm)
2558 {
2559 _OrthancPluginOutputPlusArgument params;
2560 params.output = output;
2561 params.argument = realm;
2562 context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
2563 }
2564
2565
2566 /**
2567 * @brief Signal that this URI does not support this HTTP method.
2568 *
2569 * This function answers to a REST request by signaling that the
2570 * queried URI does not support this method.
2571 *
2572 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2573 * @param output The HTTP connection to the client application.
2574 * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
2575 * @ingroup REST
2576 **/
2577 ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
2578 OrthancPluginContext* context,
2579 OrthancPluginRestOutput* output,
2580 const char* allowedMethods)
2581 {
2582 _OrthancPluginOutputPlusArgument params;
2583 params.output = output;
2584 params.argument = allowedMethods;
2585 context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
2586 }
2587
2588
2589 typedef struct
2590 {
2591 OrthancPluginRestOutput* output;
2592 const char* key;
2593 const char* value;
2594 } _OrthancPluginSetHttpHeader;
2595
2596 /**
2597 * @brief Set a cookie.
2598 *
2599 * This function sets a cookie in the HTTP client.
2600 *
2601 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2602 * @param output The HTTP connection to the client application.
2603 * @param cookie The cookie to be set.
2604 * @param value The value of the cookie.
2605 * @ingroup REST
2606 **/
2607 ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
2608 OrthancPluginContext* context,
2609 OrthancPluginRestOutput* output,
2610 const char* cookie,
2611 const char* value)
2612 {
2613 _OrthancPluginSetHttpHeader params;
2614 params.output = output;
2615 params.key = cookie;
2616 params.value = value;
2617 context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
2618 }
2619
2620
2621 /**
2622 * @brief Set some HTTP header.
2623 *
2624 * This function sets a HTTP header in the HTTP answer.
2625 *
2626 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2627 * @param output The HTTP connection to the client application.
2628 * @param key The HTTP header to be set.
2629 * @param value The value of the HTTP header.
2630 * @ingroup REST
2631 **/
2632 ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
2633 OrthancPluginContext* context,
2634 OrthancPluginRestOutput* output,
2635 const char* key,
2636 const char* value)
2637 {
2638 _OrthancPluginSetHttpHeader params;
2639 params.output = output;
2640 params.key = key;
2641 params.value = value;
2642 context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
2643 }
2644
2645
2646 typedef struct
2647 {
2648 char** resultStringToFree;
2649 const char** resultString;
2650 int64_t* resultInt64;
2651 const char* key;
2652 OrthancPluginDicomInstance* instance;
2653 OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */
2654 } _OrthancPluginAccessDicomInstance;
2655
2656
2657 /**
2658 * @brief Get the AET of a DICOM instance.
2659 *
2660 * This function returns the Application Entity Title (AET) of the
2661 * DICOM modality from which a DICOM instance originates.
2662 *
2663 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2664 * @param instance The instance of interest.
2665 * @return The AET if success, NULL if error.
2666 * @ingroup Callbacks
2667 **/
2668 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
2669 OrthancPluginContext* context,
2670 OrthancPluginDicomInstance* instance)
2671 {
2672 const char* result;
2673
2674 _OrthancPluginAccessDicomInstance params;
2675 memset(&params, 0, sizeof(params));
2676 params.resultString = &result;
2677 params.instance = instance;
2678
2679 if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
2680 {
2681 /* Error */
2682 return NULL;
2683 }
2684 else
2685 {
2686 return result;
2687 }
2688 }
2689
2690
2691 /**
2692 * @brief Get the size of a DICOM file.
2693 *
2694 * This function returns the number of bytes of the given DICOM instance.
2695 *
2696 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2697 * @param instance The instance of interest.
2698 * @return The size of the file, -1 in case of error.
2699 * @ingroup Callbacks
2700 **/
2701 ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
2702 OrthancPluginContext* context,
2703 OrthancPluginDicomInstance* instance)
2704 {
2705 int64_t size;
2706
2707 _OrthancPluginAccessDicomInstance params;
2708 memset(&params, 0, sizeof(params));
2709 params.resultInt64 = &size;
2710 params.instance = instance;
2711
2712 if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
2713 {
2714 /* Error */
2715 return -1;
2716 }
2717 else
2718 {
2719 return size;
2720 }
2721 }
2722
2723
2724 /**
2725 * @brief Get the data of a DICOM file.
2726 *
2727 * This function returns a pointer to the content of the given DICOM instance.
2728 *
2729 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2730 * @param instance The instance of interest.
2731 * @return The pointer to the DICOM data, NULL in case of error.
2732 * @ingroup Callbacks
2733 **/
2734 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
2735 OrthancPluginContext* context,
2736 OrthancPluginDicomInstance* instance)
2737 {
2738 const char* result;
2739
2740 _OrthancPluginAccessDicomInstance params;
2741 memset(&params, 0, sizeof(params));
2742 params.resultString = &result;
2743 params.instance = instance;
2744
2745 if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
2746 {
2747 /* Error */
2748 return NULL;
2749 }
2750 else
2751 {
2752 return result;
2753 }
2754 }
2755
2756
2757 /**
2758 * @brief Get the DICOM tag hierarchy as a JSON file.
2759 *
2760 * This function returns a pointer to a newly created string
2761 * containing a JSON file. This JSON file encodes the tag hierarchy
2762 * of the given DICOM instance.
2763 *
2764 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2765 * @param instance The instance of interest.
2766 * @return The NULL value in case of error, or a string containing the JSON file.
2767 * This string must be freed by OrthancPluginFreeString().
2768 * @ingroup Callbacks
2769 **/
2770 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
2771 OrthancPluginContext* context,
2772 OrthancPluginDicomInstance* instance)
2773 {
2774 char* result;
2775
2776 _OrthancPluginAccessDicomInstance params;
2777 memset(&params, 0, sizeof(params));
2778 params.resultStringToFree = &result;
2779 params.instance = instance;
2780
2781 if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
2782 {
2783 /* Error */
2784 return NULL;
2785 }
2786 else
2787 {
2788 return result;
2789 }
2790 }
2791
2792
2793 /**
2794 * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
2795 *
2796 * This function returns a pointer to a newly created string
2797 * containing a JSON file. This JSON file encodes the tag hierarchy
2798 * of the given DICOM instance. In contrast with
2799 * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
2800 * its simplified version.
2801 *
2802 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2803 * @param instance The instance of interest.
2804 * @return The NULL value in case of error, or a string containing the JSON file.
2805 * This string must be freed by OrthancPluginFreeString().
2806 * @ingroup Callbacks
2807 **/
2808 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
2809 OrthancPluginContext* context,
2810 OrthancPluginDicomInstance* instance)
2811 {
2812 char* result;
2813
2814 _OrthancPluginAccessDicomInstance params;
2815 memset(&params, 0, sizeof(params));
2816 params.resultStringToFree = &result;
2817 params.instance = instance;
2818
2819 if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
2820 {
2821 /* Error */
2822 return NULL;
2823 }
2824 else
2825 {
2826 return result;
2827 }
2828 }
2829
2830
2831 /**
2832 * @brief Check whether a DICOM instance is associated with some metadata.
2833 *
2834 * This function checks whether the DICOM instance of interest is
2835 * associated with some metadata. As of Orthanc 0.8.1, in the
2836 * callbacks registered by
2837 * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
2838 * possibly available metadata are "ReceptionDate", "RemoteAET" and
2839 * "IndexInSeries".
2840 *
2841 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2842 * @param instance The instance of interest.
2843 * @param metadata The metadata of interest.
2844 * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
2845 * @ingroup Callbacks
2846 **/
2847 ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata(
2848 OrthancPluginContext* context,
2849 OrthancPluginDicomInstance* instance,
2850 const char* metadata)
2851 {
2852 int64_t result;
2853
2854 _OrthancPluginAccessDicomInstance params;
2855 memset(&params, 0, sizeof(params));
2856 params.resultInt64 = &result;
2857 params.instance = instance;
2858 params.key = metadata;
2859
2860 if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
2861 {
2862 /* Error */
2863 return -1;
2864 }
2865 else
2866 {
2867 return (result != 0);
2868 }
2869 }
2870
2871
2872 /**
2873 * @brief Get the value of some metadata associated with a given DICOM instance.
2874 *
2875 * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
2876 * Before calling this function, the existence of the metadata must have been checked with
2877 * ::OrthancPluginHasInstanceMetadata().
2878 *
2879 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2880 * @param instance The instance of interest.
2881 * @param metadata The metadata of interest.
2882 * @return The metadata value if success, NULL if error.
2883 * @ingroup Callbacks
2884 **/
2885 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
2886 OrthancPluginContext* context,
2887 OrthancPluginDicomInstance* instance,
2888 const char* metadata)
2889 {
2890 const char* result;
2891
2892 _OrthancPluginAccessDicomInstance params;
2893 memset(&params, 0, sizeof(params));
2894 params.resultString = &result;
2895 params.instance = instance;
2896 params.key = metadata;
2897
2898 if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
2899 {
2900 /* Error */
2901 return NULL;
2902 }
2903 else
2904 {
2905 return result;
2906 }
2907 }
2908
2909
2910
2911 typedef struct
2912 {
2913 OrthancPluginStorageCreate create;
2914 OrthancPluginStorageRead read;
2915 OrthancPluginStorageRemove remove;
2916 OrthancPluginFree free;
2917 } _OrthancPluginRegisterStorageArea;
2918
2919 /**
2920 * @brief Register a custom storage area.
2921 *
2922 * This function registers a custom storage area, to replace the
2923 * built-in way Orthanc stores its files on the filesystem. This
2924 * function must be called during the initialization of the plugin,
2925 * i.e. inside the OrthancPluginInitialize() public function.
2926 *
2927 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2928 * @param create The callback function to store a file on the custom storage area.
2929 * @param read The callback function to read a file from the custom storage area.
2930 * @param remove The callback function to remove a file from the custom storage area.
2931 * @ingroup Callbacks
2932 **/
2933 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
2934 OrthancPluginContext* context,
2935 OrthancPluginStorageCreate create,
2936 OrthancPluginStorageRead read,
2937 OrthancPluginStorageRemove remove)
2938 {
2939 _OrthancPluginRegisterStorageArea params;
2940 params.create = create;
2941 params.read = read;
2942 params.remove = remove;
2943
2944 #ifdef __cplusplus
2945 params.free = ::free;
2946 #else
2947 params.free = free;
2948 #endif
2949
2950 context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
2951 }
2952
2953
2954
2955 /**
2956 * @brief Return the path to the Orthanc executable.
2957 *
2958 * This function returns the path to the Orthanc executable.
2959 *
2960 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2961 * @return NULL in the case of an error, or a newly allocated string
2962 * containing the path. This string must be freed by
2963 * OrthancPluginFreeString().
2964 **/
2965 ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
2966 {
2967 char* result;
2968
2969 _OrthancPluginRetrieveDynamicString params;
2970 params.result = &result;
2971 params.argument = NULL;
2972
2973 if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
2974 {
2975 /* Error */
2976 return NULL;
2977 }
2978 else
2979 {
2980 return result;
2981 }
2982 }
2983
2984
2985 /**
2986 * @brief Return the directory containing the Orthanc.
2987 *
2988 * This function returns the path to the directory containing the Orthanc executable.
2989 *
2990 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
2991 * @return NULL in the case of an error, or a newly allocated string
2992 * containing the path. This string must be freed by
2993 * OrthancPluginFreeString().
2994 **/
2995 ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
2996 {
2997 char* result;
2998
2999 _OrthancPluginRetrieveDynamicString params;
3000 params.result = &result;
3001 params.argument = NULL;
3002
3003 if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
3004 {
3005 /* Error */
3006 return NULL;
3007 }
3008 else
3009 {
3010 return result;
3011 }
3012 }
3013
3014
3015 /**
3016 * @brief Return the path to the configuration file(s).
3017 *
3018 * This function returns the path to the configuration file(s) that
3019 * was specified when starting Orthanc. Since version 0.9.1, this
3020 * path can refer to a folder that stores a set of configuration
3021 * files. This function is deprecated in favor of
3022 * OrthancPluginGetConfiguration().
3023 *
3024 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3025 * @return NULL in the case of an error, or a newly allocated string
3026 * containing the path. This string must be freed by
3027 * OrthancPluginFreeString().
3028 * @see OrthancPluginGetConfiguration()
3029 **/
3030 ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
3031 {
3032 char* result;
3033
3034 _OrthancPluginRetrieveDynamicString params;
3035 params.result = &result;
3036 params.argument = NULL;
3037
3038 if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
3039 {
3040 /* Error */
3041 return NULL;
3042 }
3043 else
3044 {
3045 return result;
3046 }
3047 }
3048
3049
3050
3051 typedef struct
3052 {
3053 OrthancPluginOnChangeCallback callback;
3054 } _OrthancPluginOnChangeCallback;
3055
3056 /**
3057 * @brief Register a callback to monitor changes.
3058 *
3059 * This function registers a callback function that is called
3060 * whenever a change happens to some DICOM resource.
3061 *
3062 * @warning If your change callback has to call the REST API of
3063 * Orthanc, you should make these calls in a separate thread (with
3064 * the events passing through a message queue). Otherwise, this
3065 * could result in deadlocks in the presence of other plugins or Lua
3066 * scripts.
3067 *
3068 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3069 * @param callback The callback function.
3070 * @ingroup Callbacks
3071 **/
3072 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
3073 OrthancPluginContext* context,
3074 OrthancPluginOnChangeCallback callback)
3075 {
3076 _OrthancPluginOnChangeCallback params;
3077 params.callback = callback;
3078
3079 context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
3080 }
3081
3082
3083
3084 typedef struct
3085 {
3086 const char* plugin;
3087 _OrthancPluginProperty property;
3088 const char* value;
3089 } _OrthancPluginSetPluginProperty;
3090
3091
3092 /**
3093 * @brief Set the URI where the plugin provides its Web interface.
3094 *
3095 * For plugins that come with a Web interface, this function
3096 * declares the entry path where to find this interface. This
3097 * information is notably used in the "Plugins" page of Orthanc
3098 * Explorer.
3099 *
3100 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3101 * @param uri The root URI for this plugin.
3102 **/
3103 ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
3104 OrthancPluginContext* context,
3105 const char* uri)
3106 {
3107 _OrthancPluginSetPluginProperty params;
3108 params.plugin = OrthancPluginGetName();
3109 params.property = _OrthancPluginProperty_RootUri;
3110 params.value = uri;
3111
3112 context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
3113 }
3114
3115
3116 /**
3117 * @brief Set a description for this plugin.
3118 *
3119 * Set a description for this plugin. It is displayed in the
3120 * "Plugins" page of Orthanc Explorer.
3121 *
3122 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3123 * @param description The description.
3124 **/
3125 ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
3126 OrthancPluginContext* context,
3127 const char* description)
3128 {
3129 _OrthancPluginSetPluginProperty params;
3130 params.plugin = OrthancPluginGetName();
3131 params.property = _OrthancPluginProperty_Description;
3132 params.value = description;
3133
3134 context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
3135 }
3136
3137
3138 /**
3139 * @brief Extend the JavaScript code of Orthanc Explorer.
3140 *
3141 * Add JavaScript code to customize the default behavior of Orthanc
3142 * Explorer. This can for instance be used to add new buttons.
3143 *
3144 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3145 * @param javascript The custom JavaScript code.
3146 **/
3147 ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
3148 OrthancPluginContext* context,
3149 const char* javascript)
3150 {
3151 _OrthancPluginSetPluginProperty params;
3152 params.plugin = OrthancPluginGetName();
3153 params.property = _OrthancPluginProperty_OrthancExplorer;
3154 params.value = javascript;
3155
3156 context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
3157 }
3158
3159
3160 typedef struct
3161 {
3162 char** result;
3163 int32_t property;
3164 const char* value;
3165 } _OrthancPluginGlobalProperty;
3166
3167
3168 /**
3169 * @brief Get the value of a global property.
3170 *
3171 * Get the value of a global property that is stored in the Orthanc database. Global
3172 * properties whose index is below 1024 are reserved by Orthanc.
3173 *
3174 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3175 * @param property The global property of interest.
3176 * @param defaultValue The value to return, if the global property is unset.
3177 * @return The value of the global property, or NULL in the case of an error. This
3178 * string must be freed by OrthancPluginFreeString().
3179 * @ingroup Orthanc
3180 **/
3181 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
3182 OrthancPluginContext* context,
3183 int32_t property,
3184 const char* defaultValue)
3185 {
3186 char* result;
3187
3188 _OrthancPluginGlobalProperty params;
3189 params.result = &result;
3190 params.property = property;
3191 params.value = defaultValue;
3192
3193 if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
3194 {
3195 /* Error */
3196 return NULL;
3197 }
3198 else
3199 {
3200 return result;
3201 }
3202 }
3203
3204
3205 /**
3206 * @brief Set the value of a global property.
3207 *
3208 * Set the value of a global property into the Orthanc
3209 * database. Setting a global property can be used by plugins to
3210 * save their internal parameters. Plugins are only allowed to set
3211 * properties whose index are above or equal to 1024 (properties
3212 * below 1024 are read-only and reserved by Orthanc).
3213 *
3214 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3215 * @param property The global property of interest.
3216 * @param value The value to be set in the global property.
3217 * @return 0 if success, or the error code if failure.
3218 * @ingroup Orthanc
3219 **/
3220 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
3221 OrthancPluginContext* context,
3222 int32_t property,
3223 const char* value)
3224 {
3225 _OrthancPluginGlobalProperty params;
3226 params.result = NULL;
3227 params.property = property;
3228 params.value = value;
3229
3230 return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
3231 }
3232
3233
3234
3235 typedef struct
3236 {
3237 int32_t *resultInt32;
3238 uint32_t *resultUint32;
3239 int64_t *resultInt64;
3240 uint64_t *resultUint64;
3241 } _OrthancPluginReturnSingleValue;
3242
3243 /**
3244 * @brief Get the number of command-line arguments.
3245 *
3246 * Retrieve the number of command-line arguments that were used to launch Orthanc.
3247 *
3248 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3249 * @return The number of arguments.
3250 **/
3251 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
3252 OrthancPluginContext* context)
3253 {
3254 uint32_t count = 0;
3255
3256 _OrthancPluginReturnSingleValue params;
3257 memset(&params, 0, sizeof(params));
3258 params.resultUint32 = &count;
3259
3260 if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
3261 {
3262 /* Error */
3263 return 0;
3264 }
3265 else
3266 {
3267 return count;
3268 }
3269 }
3270
3271
3272
3273 /**
3274 * @brief Get the value of a command-line argument.
3275 *
3276 * Get the value of one of the command-line arguments that were used
3277 * to launch Orthanc. The number of available arguments can be
3278 * retrieved by OrthancPluginGetCommandLineArgumentsCount().
3279 *
3280 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3281 * @param argument The index of the argument.
3282 * @return The value of the argument, or NULL in the case of an error. This
3283 * string must be freed by OrthancPluginFreeString().
3284 **/
3285 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
3286 OrthancPluginContext* context,
3287 uint32_t argument)
3288 {
3289 char* result;
3290
3291 _OrthancPluginGlobalProperty params;
3292 params.result = &result;
3293 params.property = (int32_t) argument;
3294 params.value = NULL;
3295
3296 if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
3297 {
3298 /* Error */
3299 return NULL;
3300 }
3301 else
3302 {
3303 return result;
3304 }
3305 }
3306
3307
3308 /**
3309 * @brief Get the expected version of the database schema.
3310 *
3311 * Retrieve the expected version of the database schema.
3312 *
3313 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3314 * @return The version.
3315 * @ingroup Callbacks
3316 **/
3317 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
3318 OrthancPluginContext* context)
3319 {
3320 uint32_t count = 0;
3321
3322 _OrthancPluginReturnSingleValue params;
3323 memset(&params, 0, sizeof(params));
3324 params.resultUint32 = &count;
3325
3326 if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
3327 {
3328 /* Error */
3329 return 0;
3330 }
3331 else
3332 {
3333 return count;
3334 }
3335 }
3336
3337
3338
3339 /**
3340 * @brief Return the content of the configuration file(s).
3341 *
3342 * This function returns the content of the configuration that is
3343 * used by Orthanc, formatted as a JSON string.
3344 *
3345 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3346 * @return NULL in the case of an error, or a newly allocated string
3347 * containing the configuration. This string must be freed by
3348 * OrthancPluginFreeString().
3349 **/
3350 ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
3351 {
3352 char* result;
3353
3354 _OrthancPluginRetrieveDynamicString params;
3355 params.result = &result;
3356 params.argument = NULL;
3357
3358 if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
3359 {
3360 /* Error */
3361 return NULL;
3362 }
3363 else
3364 {
3365 return result;
3366 }
3367 }
3368
3369
3370
3371 typedef struct
3372 {
3373 OrthancPluginRestOutput* output;
3374 const char* subType;
3375 const char* contentType;
3376 } _OrthancPluginStartMultipartAnswer;
3377
3378 /**
3379 * @brief Start an HTTP multipart answer.
3380 *
3381 * Initiates a HTTP multipart answer, as the result of a REST request.
3382 *
3383 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3384 * @param output The HTTP connection to the client application.
3385 * @param subType The sub-type of the multipart answer ("mixed" or "related").
3386 * @param contentType The MIME type of the items in the multipart answer.
3387 * @return 0 if success, or the error code if failure.
3388 * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
3389 * @ingroup REST
3390 **/
3391 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
3392 OrthancPluginContext* context,
3393 OrthancPluginRestOutput* output,
3394 const char* subType,
3395 const char* contentType)
3396 {
3397 _OrthancPluginStartMultipartAnswer params;
3398 params.output = output;
3399 params.subType = subType;
3400 params.contentType = contentType;
3401 return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
3402 }
3403
3404
3405 /**
3406 * @brief Send an item as a part of some HTTP multipart answer.
3407 *
3408 * This function sends an item as a part of some HTTP multipart
3409 * answer that was initiated by OrthancPluginStartMultipartAnswer().
3410 *
3411 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3412 * @param output The HTTP connection to the client application.
3413 * @param answer Pointer to the memory buffer containing the item.
3414 * @param answerSize Number of bytes of the item.
3415 * @return 0 if success, or the error code if failure (this notably happens
3416 * if the connection is closed by the client).
3417 * @see OrthancPluginSendMultipartItem2()
3418 * @ingroup REST
3419 **/
3420 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
3421 OrthancPluginContext* context,
3422 OrthancPluginRestOutput* output,
3423 const char* answer,
3424 uint32_t answerSize)
3425 {
3426 _OrthancPluginAnswerBuffer params;
3427 params.output = output;
3428 params.answer = answer;
3429 params.answerSize = answerSize;
3430 params.mimeType = NULL;
3431 return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
3432 }
3433
3434
3435
3436 typedef struct
3437 {
3438 OrthancPluginMemoryBuffer* target;
3439 const void* source;
3440 uint32_t size;
3441 OrthancPluginCompressionType compression;
3442 uint8_t uncompress;
3443 } _OrthancPluginBufferCompression;
3444
3445
3446 /**
3447 * @brief Compress or decompress a buffer.
3448 *
3449 * This function compresses or decompresses a buffer, using the
3450 * version of the zlib library that is used by the Orthanc core.
3451 *
3452 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3453 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
3454 * @param source The source buffer.
3455 * @param size The size in bytes of the source buffer.
3456 * @param compression The compression algorithm.
3457 * @param uncompress If set to "0", the buffer must be compressed.
3458 * If set to "1", the buffer must be uncompressed.
3459 * @return 0 if success, or the error code if failure.
3460 * @ingroup Images
3461 **/
3462 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
3463 OrthancPluginContext* context,
3464 OrthancPluginMemoryBuffer* target,
3465 const void* source,
3466 uint32_t size,
3467 OrthancPluginCompressionType compression,
3468 uint8_t uncompress)
3469 {
3470 _OrthancPluginBufferCompression params;
3471 params.target = target;
3472 params.source = source;
3473 params.size = size;
3474 params.compression = compression;
3475 params.uncompress = uncompress;
3476
3477 return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
3478 }
3479
3480
3481
3482 typedef struct
3483 {
3484 OrthancPluginMemoryBuffer* target;
3485 const char* path;
3486 } _OrthancPluginReadFile;
3487
3488 /**
3489 * @brief Read a file.
3490 *
3491 * Read the content of a file on the filesystem, and returns it into
3492 * a newly allocated memory buffer.
3493 *
3494 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3495 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
3496 * @param path The path of the file to be read.
3497 * @return 0 if success, or the error code if failure.
3498 **/
3499 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile(
3500 OrthancPluginContext* context,
3501 OrthancPluginMemoryBuffer* target,
3502 const char* path)
3503 {
3504 _OrthancPluginReadFile params;
3505 params.target = target;
3506 params.path = path;
3507 return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
3508 }
3509
3510
3511
3512 typedef struct
3513 {
3514 const char* path;
3515 const void* data;
3516 uint32_t size;
3517 } _OrthancPluginWriteFile;
3518
3519 /**
3520 * @brief Write a file.
3521 *
3522 * Write the content of a memory buffer to the filesystem.
3523 *
3524 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3525 * @param path The path of the file to be written.
3526 * @param data The content of the memory buffer.
3527 * @param size The size of the memory buffer.
3528 * @return 0 if success, or the error code if failure.
3529 **/
3530 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile(
3531 OrthancPluginContext* context,
3532 const char* path,
3533 const void* data,
3534 uint32_t size)
3535 {
3536 _OrthancPluginWriteFile params;
3537 params.path = path;
3538 params.data = data;
3539 params.size = size;
3540 return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
3541 }
3542
3543
3544
3545 typedef struct
3546 {
3547 const char** target;
3548 OrthancPluginErrorCode error;
3549 } _OrthancPluginGetErrorDescription;
3550
3551 /**
3552 * @brief Get the description of a given error code.
3553 *
3554 * This function returns the description of a given error code.
3555 *
3556 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3557 * @param error The error code of interest.
3558 * @return The error description. This is a statically-allocated
3559 * string, do not free it.
3560 **/
3561 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
3562 OrthancPluginContext* context,
3563 OrthancPluginErrorCode error)
3564 {
3565 const char* result = NULL;
3566
3567 _OrthancPluginGetErrorDescription params;
3568 params.target = &result;
3569 params.error = error;
3570
3571 if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
3572 result == NULL)
3573 {
3574 return "Unknown error code";
3575 }
3576 else
3577 {
3578 return result;
3579 }
3580 }
3581
3582
3583
3584 typedef struct
3585 {
3586 OrthancPluginRestOutput* output;
3587 uint16_t status;
3588 const char* body;
3589 uint32_t bodySize;
3590 } _OrthancPluginSendHttpStatus;
3591
3592 /**
3593 * @brief Send a HTTP status, with a custom body.
3594 *
3595 * This function answers to a HTTP request by sending a HTTP status
3596 * code (such as "400 - Bad Request"), together with a body
3597 * describing the error. The body will only be returned if the
3598 * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
3599 *
3600 * Note that:
3601 * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
3602 * - Redirections (status 301) must use ::OrthancPluginRedirect().
3603 * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
3604 * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
3605 *
3606 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3607 * @param output The HTTP connection to the client application.
3608 * @param status The HTTP status code to be sent.
3609 * @param body The body of the answer.
3610 * @param bodySize The size of the body.
3611 * @see OrthancPluginSendHttpStatusCode()
3612 * @ingroup REST
3613 **/
3614 ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
3615 OrthancPluginContext* context,
3616 OrthancPluginRestOutput* output,
3617 uint16_t status,
3618 const char* body,
3619 uint32_t bodySize)
3620 {
3621 _OrthancPluginSendHttpStatus params;
3622 params.output = output;
3623 params.status = status;
3624 params.body = body;
3625 params.bodySize = bodySize;
3626 context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
3627 }
3628
3629
3630
3631 typedef struct
3632 {
3633 const OrthancPluginImage* image;
3634 uint32_t* resultUint32;
3635 OrthancPluginPixelFormat* resultPixelFormat;
3636 void** resultBuffer;
3637 } _OrthancPluginGetImageInfo;
3638
3639
3640 /**
3641 * @brief Return the pixel format of an image.
3642 *
3643 * This function returns the type of memory layout for the pixels of the given image.
3644 *
3645 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3646 * @param image The image of interest.
3647 * @return The pixel format.
3648 * @ingroup Images
3649 **/
3650 ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat(
3651 OrthancPluginContext* context,
3652 const OrthancPluginImage* image)
3653 {
3654 OrthancPluginPixelFormat target;
3655
3656 _OrthancPluginGetImageInfo params;
3657 memset(&params, 0, sizeof(params));
3658 params.image = image;
3659 params.resultPixelFormat = &target;
3660
3661 if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
3662 {
3663 return OrthancPluginPixelFormat_Unknown;
3664 }
3665 else
3666 {
3667 return (OrthancPluginPixelFormat) target;
3668 }
3669 }
3670
3671
3672
3673 /**
3674 * @brief Return the width of an image.
3675 *
3676 * This function returns the width of the given image.
3677 *
3678 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3679 * @param image The image of interest.
3680 * @return The width.
3681 * @ingroup Images
3682 **/
3683 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth(
3684 OrthancPluginContext* context,
3685 const OrthancPluginImage* image)
3686 {
3687 uint32_t width;
3688
3689 _OrthancPluginGetImageInfo params;
3690 memset(&params, 0, sizeof(params));
3691 params.image = image;
3692 params.resultUint32 = &width;
3693
3694 if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
3695 {
3696 return 0;
3697 }
3698 else
3699 {
3700 return width;
3701 }
3702 }
3703
3704
3705
3706 /**
3707 * @brief Return the height of an image.
3708 *
3709 * This function returns the height of the given image.
3710 *
3711 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3712 * @param image The image of interest.
3713 * @return The height.
3714 * @ingroup Images
3715 **/
3716 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight(
3717 OrthancPluginContext* context,
3718 const OrthancPluginImage* image)
3719 {
3720 uint32_t height;
3721
3722 _OrthancPluginGetImageInfo params;
3723 memset(&params, 0, sizeof(params));
3724 params.image = image;
3725 params.resultUint32 = &height;
3726
3727 if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
3728 {
3729 return 0;
3730 }
3731 else
3732 {
3733 return height;
3734 }
3735 }
3736
3737
3738
3739 /**
3740 * @brief Return the pitch of an image.
3741 *
3742 * This function returns the pitch of the given image. The pitch is
3743 * defined as the number of bytes between 2 successive lines of the
3744 * image in the memory buffer.
3745 *
3746 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3747 * @param image The image of interest.
3748 * @return The pitch.
3749 * @ingroup Images
3750 **/
3751 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch(
3752 OrthancPluginContext* context,
3753 const OrthancPluginImage* image)
3754 {
3755 uint32_t pitch;
3756
3757 _OrthancPluginGetImageInfo params;
3758 memset(&params, 0, sizeof(params));
3759 params.image = image;
3760 params.resultUint32 = &pitch;
3761
3762 if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
3763 {
3764 return 0;
3765 }
3766 else
3767 {
3768 return pitch;
3769 }
3770 }
3771
3772
3773
3774 /**
3775 * @brief Return a pointer to the content of an image.
3776 *
3777 * This function returns a pointer to the memory buffer that
3778 * contains the pixels of the image.
3779 *
3780 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3781 * @param image The image of interest.
3782 * @return The pointer.
3783 * @ingroup Images
3784 **/
3785 ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer(
3786 OrthancPluginContext* context,
3787 const OrthancPluginImage* image)
3788 {
3789 void* target = NULL;
3790
3791 _OrthancPluginGetImageInfo params;
3792 memset(&params, 0, sizeof(params));
3793 params.resultBuffer = &target;
3794 params.image = image;
3795
3796 if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
3797 {
3798 return NULL;
3799 }
3800 else
3801 {
3802 return target;
3803 }
3804 }
3805
3806
3807 typedef struct
3808 {
3809 OrthancPluginImage** target;
3810 const void* data;
3811 uint32_t size;
3812 OrthancPluginImageFormat format;
3813 } _OrthancPluginUncompressImage;
3814
3815
3816 /**
3817 * @brief Decode a compressed image.
3818 *
3819 * This function decodes a compressed image from a memory buffer.
3820 *
3821 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3822 * @param data Pointer to a memory buffer containing the compressed image.
3823 * @param size Size of the memory buffer containing the compressed image.
3824 * @param format The file format of the compressed image.
3825 * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
3826 * @ingroup Images
3827 **/
3828 ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
3829 OrthancPluginContext* context,
3830 const void* data,
3831 uint32_t size,
3832 OrthancPluginImageFormat format)
3833 {
3834 OrthancPluginImage* target = NULL;
3835
3836 _OrthancPluginUncompressImage params;
3837 memset(&params, 0, sizeof(params));
3838 params.target = &target;
3839 params.data = data;
3840 params.size = size;
3841 params.format = format;
3842
3843 if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
3844 {
3845 return NULL;
3846 }
3847 else
3848 {
3849 return target;
3850 }
3851 }
3852
3853
3854
3855
3856 typedef struct
3857 {
3858 OrthancPluginImage* image;
3859 } _OrthancPluginFreeImage;
3860
3861 /**
3862 * @brief Free an image.
3863 *
3864 * This function frees an image that was decoded with OrthancPluginUncompressImage().
3865 *
3866 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3867 * @param image The image.
3868 * @ingroup Images
3869 **/
3870 ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage(
3871 OrthancPluginContext* context,
3872 OrthancPluginImage* image)
3873 {
3874 _OrthancPluginFreeImage params;
3875 params.image = image;
3876
3877 context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
3878 }
3879
3880
3881
3882
3883 typedef struct
3884 {
3885 OrthancPluginMemoryBuffer* target;
3886 OrthancPluginImageFormat imageFormat;
3887 OrthancPluginPixelFormat pixelFormat;
3888 uint32_t width;
3889 uint32_t height;
3890 uint32_t pitch;
3891 const void* buffer;
3892 uint8_t quality;
3893 } _OrthancPluginCompressImage;
3894
3895
3896 /**
3897 * @brief Encode a PNG image.
3898 *
3899 * This function compresses the given memory buffer containing an
3900 * image using the PNG specification, and stores the result of the
3901 * compression into a newly allocated memory buffer.
3902 *
3903 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3904 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
3905 * @param format The memory layout of the uncompressed image.
3906 * @param width The width of the image.
3907 * @param height The height of the image.
3908 * @param pitch The pitch of the image (i.e. the number of bytes
3909 * between 2 successive lines of the image in the memory buffer).
3910 * @param buffer The memory buffer containing the uncompressed image.
3911 * @return 0 if success, or the error code if failure.
3912 * @see OrthancPluginCompressAndAnswerPngImage()
3913 * @ingroup Images
3914 **/
3915 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
3916 OrthancPluginContext* context,
3917 OrthancPluginMemoryBuffer* target,
3918 OrthancPluginPixelFormat format,
3919 uint32_t width,
3920 uint32_t height,
3921 uint32_t pitch,
3922 const void* buffer)
3923 {
3924 _OrthancPluginCompressImage params;
3925 memset(&params, 0, sizeof(params));
3926 params.target = target;
3927 params.imageFormat = OrthancPluginImageFormat_Png;
3928 params.pixelFormat = format;
3929 params.width = width;
3930 params.height = height;
3931 params.pitch = pitch;
3932 params.buffer = buffer;
3933 params.quality = 0; /* Unused for PNG */
3934
3935 return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
3936 }
3937
3938
3939 /**
3940 * @brief Encode a JPEG image.
3941 *
3942 * This function compresses the given memory buffer containing an
3943 * image using the JPEG specification, and stores the result of the
3944 * compression into a newly allocated memory buffer.
3945 *
3946 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3947 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
3948 * @param format The memory layout of the uncompressed image.
3949 * @param width The width of the image.
3950 * @param height The height of the image.
3951 * @param pitch The pitch of the image (i.e. the number of bytes
3952 * between 2 successive lines of the image in the memory buffer).
3953 * @param buffer The memory buffer containing the uncompressed image.
3954 * @param quality The quality of the JPEG encoding, between 1 (worst
3955 * quality, best compression) and 100 (best quality, worst
3956 * compression).
3957 * @return 0 if success, or the error code if failure.
3958 * @ingroup Images
3959 **/
3960 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
3961 OrthancPluginContext* context,
3962 OrthancPluginMemoryBuffer* target,
3963 OrthancPluginPixelFormat format,
3964 uint32_t width,
3965 uint32_t height,
3966 uint32_t pitch,
3967 const void* buffer,
3968 uint8_t quality)
3969 {
3970 _OrthancPluginCompressImage params;
3971 memset(&params, 0, sizeof(params));
3972 params.target = target;
3973 params.imageFormat = OrthancPluginImageFormat_Jpeg;
3974 params.pixelFormat = format;
3975 params.width = width;
3976 params.height = height;
3977 params.pitch = pitch;
3978 params.buffer = buffer;
3979 params.quality = quality;
3980
3981 return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
3982 }
3983
3984
3985
3986 /**
3987 * @brief Answer to a REST request with a JPEG image.
3988 *
3989 * This function answers to a REST request with a JPEG image. The
3990 * parameters of this function describe a memory buffer that
3991 * contains an uncompressed image. The image will be automatically compressed
3992 * as a JPEG image by the core system of Orthanc.
3993 *
3994 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
3995 * @param output The HTTP connection to the client application.
3996 * @param format The memory layout of the uncompressed image.
3997 * @param width The width of the image.
3998 * @param height The height of the image.
3999 * @param pitch The pitch of the image (i.e. the number of bytes
4000 * between 2 successive lines of the image in the memory buffer).
4001 * @param buffer The memory buffer containing the uncompressed image.
4002 * @param quality The quality of the JPEG encoding, between 1 (worst
4003 * quality, best compression) and 100 (best quality, worst
4004 * compression).
4005 * @ingroup REST
4006 **/
4007 ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
4008 OrthancPluginContext* context,
4009 OrthancPluginRestOutput* output,
4010 OrthancPluginPixelFormat format,
4011 uint32_t width,
4012 uint32_t height,
4013 uint32_t pitch,
4014 const void* buffer,
4015 uint8_t quality)
4016 {
4017 _OrthancPluginCompressAndAnswerImage params;
4018 params.output = output;
4019 params.imageFormat = OrthancPluginImageFormat_Jpeg;
4020 params.pixelFormat = format;
4021 params.width = width;
4022 params.height = height;
4023 params.pitch = pitch;
4024 params.buffer = buffer;
4025 params.quality = quality;
4026 context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
4027 }
4028
4029
4030
4031
4032 typedef struct
4033 {
4034 OrthancPluginMemoryBuffer* target;
4035 OrthancPluginHttpMethod method;
4036 const char* url;
4037 const char* username;
4038 const char* password;
4039 const void* body;
4040 uint32_t bodySize;
4041 } _OrthancPluginCallHttpClient;
4042
4043
4044 /**
4045 * @brief Issue a HTTP GET call.
4046 *
4047 * Make a HTTP GET call to the given URL. The result to the query is
4048 * stored into a newly allocated memory buffer. Favor
4049 * OrthancPluginRestApiGet() if calling the built-in REST API of the
4050 * Orthanc instance that hosts this plugin.
4051 *
4052 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4053 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
4054 * @param url The URL of interest.
4055 * @param username The username (can be <tt>NULL</tt> if no password protection).
4056 * @param password The password (can be <tt>NULL</tt> if no password protection).
4057 * @return 0 if success, or the error code if failure.
4058 * @ingroup Toolbox
4059 **/
4060 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet(
4061 OrthancPluginContext* context,
4062 OrthancPluginMemoryBuffer* target,
4063 const char* url,
4064 const char* username,
4065 const char* password)
4066 {
4067 _OrthancPluginCallHttpClient params;
4068 memset(&params, 0, sizeof(params));
4069
4070 params.target = target;
4071 params.method = OrthancPluginHttpMethod_Get;
4072 params.url = url;
4073 params.username = username;
4074 params.password = password;
4075
4076 return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
4077 }
4078
4079
4080 /**
4081 * @brief Issue a HTTP POST call.
4082 *
4083 * Make a HTTP POST call to the given URL. The result to the query
4084 * is stored into a newly allocated memory buffer. Favor
4085 * OrthancPluginRestApiPost() if calling the built-in REST API of
4086 * the Orthanc instance that hosts this plugin.
4087 *
4088 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4089 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
4090 * @param url The URL of interest.
4091 * @param body The content of the body of the request.
4092 * @param bodySize The size of the body of the request.
4093 * @param username The username (can be <tt>NULL</tt> if no password protection).
4094 * @param password The password (can be <tt>NULL</tt> if no password protection).
4095 * @return 0 if success, or the error code if failure.
4096 * @ingroup Toolbox
4097 **/
4098 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost(
4099 OrthancPluginContext* context,
4100 OrthancPluginMemoryBuffer* target,
4101 const char* url,
4102 const void* body,
4103 uint32_t bodySize,
4104 const char* username,
4105 const char* password)
4106 {
4107 _OrthancPluginCallHttpClient params;
4108 memset(&params, 0, sizeof(params));
4109
4110 params.target = target;
4111 params.method = OrthancPluginHttpMethod_Post;
4112 params.url = url;
4113 params.body = body;
4114 params.bodySize = bodySize;
4115 params.username = username;
4116 params.password = password;
4117
4118 return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
4119 }
4120
4121
4122 /**
4123 * @brief Issue a HTTP PUT call.
4124 *
4125 * Make a HTTP PUT call to the given URL. The result to the query is
4126 * stored into a newly allocated memory buffer. Favor
4127 * OrthancPluginRestApiPut() if calling the built-in REST API of the
4128 * Orthanc instance that hosts this plugin.
4129 *
4130 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4131 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
4132 * @param url The URL of interest.
4133 * @param body The content of the body of the request.
4134 * @param bodySize The size of the body of the request.
4135 * @param username The username (can be <tt>NULL</tt> if no password protection).
4136 * @param password The password (can be <tt>NULL</tt> if no password protection).
4137 * @return 0 if success, or the error code if failure.
4138 * @ingroup Toolbox
4139 **/
4140 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut(
4141 OrthancPluginContext* context,
4142 OrthancPluginMemoryBuffer* target,
4143 const char* url,
4144 const void* body,
4145 uint32_t bodySize,
4146 const char* username,
4147 const char* password)
4148 {
4149 _OrthancPluginCallHttpClient params;
4150 memset(&params, 0, sizeof(params));
4151
4152 params.target = target;
4153 params.method = OrthancPluginHttpMethod_Put;
4154 params.url = url;
4155 params.body = body;
4156 params.bodySize = bodySize;
4157 params.username = username;
4158 params.password = password;
4159
4160 return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
4161 }
4162
4163
4164 /**
4165 * @brief Issue a HTTP DELETE call.
4166 *
4167 * Make a HTTP DELETE call to the given URL. Favor
4168 * OrthancPluginRestApiDelete() if calling the built-in REST API of
4169 * the Orthanc instance that hosts this plugin.
4170 *
4171 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4172 * @param url The URL of interest.
4173 * @param username The username (can be <tt>NULL</tt> if no password protection).
4174 * @param password The password (can be <tt>NULL</tt> if no password protection).
4175 * @return 0 if success, or the error code if failure.
4176 * @ingroup Toolbox
4177 **/
4178 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete(
4179 OrthancPluginContext* context,
4180 const char* url,
4181 const char* username,
4182 const char* password)
4183 {
4184 _OrthancPluginCallHttpClient params;
4185 memset(&params, 0, sizeof(params));
4186
4187 params.method = OrthancPluginHttpMethod_Delete;
4188 params.url = url;
4189 params.username = username;
4190 params.password = password;
4191
4192 return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
4193 }
4194
4195
4196
4197 typedef struct
4198 {
4199 OrthancPluginImage** target;
4200 const OrthancPluginImage* source;
4201 OrthancPluginPixelFormat targetFormat;
4202 } _OrthancPluginConvertPixelFormat;
4203
4204
4205 /**
4206 * @brief Change the pixel format of an image.
4207 *
4208 * This function creates a new image, changing the memory layout of the pixels.
4209 *
4210 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4211 * @param source The source image.
4212 * @param targetFormat The target pixel format.
4213 * @return The resulting image. It must be freed with OrthancPluginFreeImage().
4214 * @ingroup Images
4215 **/
4216 ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
4217 OrthancPluginContext* context,
4218 const OrthancPluginImage* source,
4219 OrthancPluginPixelFormat targetFormat)
4220 {
4221 OrthancPluginImage* target = NULL;
4222
4223 _OrthancPluginConvertPixelFormat params;
4224 params.target = &target;
4225 params.source = source;
4226 params.targetFormat = targetFormat;
4227
4228 if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
4229 {
4230 return NULL;
4231 }
4232 else
4233 {
4234 return target;
4235 }
4236 }
4237
4238
4239
4240 /**
4241 * @brief Return the number of available fonts.
4242 *
4243 * This function returns the number of fonts that are built in the
4244 * Orthanc core. These fonts can be used to draw texts on images
4245 * through OrthancPluginDrawText().
4246 *
4247 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4248 * @return The number of fonts.
4249 * @ingroup Images
4250 **/
4251 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
4252 OrthancPluginContext* context)
4253 {
4254 uint32_t count = 0;
4255
4256 _OrthancPluginReturnSingleValue params;
4257 memset(&params, 0, sizeof(params));
4258 params.resultUint32 = &count;
4259
4260 if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
4261 {
4262 /* Error */
4263 return 0;
4264 }
4265 else
4266 {
4267 return count;
4268 }
4269 }
4270
4271
4272
4273
4274 typedef struct
4275 {
4276 uint32_t fontIndex; /* in */
4277 const char** name; /* out */
4278 uint32_t* size; /* out */
4279 } _OrthancPluginGetFontInfo;
4280
4281 /**
4282 * @brief Return the name of a font.
4283 *
4284 * This function returns the name of a font that is built in the Orthanc core.
4285 *
4286 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4287 * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
4288 * @return The font name. This is a statically-allocated string, do not free it.
4289 * @ingroup Images
4290 **/
4291 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
4292 OrthancPluginContext* context,
4293 uint32_t fontIndex)
4294 {
4295 const char* result = NULL;
4296
4297 _OrthancPluginGetFontInfo params;
4298 memset(&params, 0, sizeof(params));
4299 params.name = &result;
4300 params.fontIndex = fontIndex;
4301
4302 if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
4303 {
4304 return NULL;
4305 }
4306 else
4307 {
4308 return result;
4309 }
4310 }
4311
4312
4313 /**
4314 * @brief Return the size of a font.
4315 *
4316 * This function returns the size of a font that is built in the Orthanc core.
4317 *
4318 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4319 * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
4320 * @return The font size.
4321 * @ingroup Images
4322 **/
4323 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
4324 OrthancPluginContext* context,
4325 uint32_t fontIndex)
4326 {
4327 uint32_t result;
4328
4329 _OrthancPluginGetFontInfo params;
4330 memset(&params, 0, sizeof(params));
4331 params.size = &result;
4332 params.fontIndex = fontIndex;
4333
4334 if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
4335 {
4336 return 0;
4337 }
4338 else
4339 {
4340 return result;
4341 }
4342 }
4343
4344
4345
4346 typedef struct
4347 {
4348 OrthancPluginImage* image;
4349 uint32_t fontIndex;
4350 const char* utf8Text;
4351 int32_t x;
4352 int32_t y;
4353 uint8_t r;
4354 uint8_t g;
4355 uint8_t b;
4356 } _OrthancPluginDrawText;
4357
4358
4359 /**
4360 * @brief Draw text on an image.
4361 *
4362 * This function draws some text on some image.
4363 *
4364 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4365 * @param image The image upon which to draw the text.
4366 * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
4367 * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
4368 * @param x The X position of the text over the image.
4369 * @param y The Y position of the text over the image.
4370 * @param r The value of the red color channel of the text.
4371 * @param g The value of the green color channel of the text.
4372 * @param b The value of the blue color channel of the text.
4373 * @return 0 if success, other value if error.
4374 * @ingroup Images
4375 **/
4376 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText(
4377 OrthancPluginContext* context,
4378 OrthancPluginImage* image,
4379 uint32_t fontIndex,
4380 const char* utf8Text,
4381 int32_t x,
4382 int32_t y,
4383 uint8_t r,
4384 uint8_t g,
4385 uint8_t b)
4386 {
4387 _OrthancPluginDrawText params;
4388 memset(&params, 0, sizeof(params));
4389 params.image = image;
4390 params.fontIndex = fontIndex;
4391 params.utf8Text = utf8Text;
4392 params.x = x;
4393 params.y = y;
4394 params.r = r;
4395 params.g = g;
4396 params.b = b;
4397
4398 return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
4399 }
4400
4401
4402
4403 typedef struct
4404 {
4405 OrthancPluginStorageArea* storageArea;
4406 const char* uuid;
4407 const void* content;
4408 uint64_t size;
4409 OrthancPluginContentType type;
4410 } _OrthancPluginStorageAreaCreate;
4411
4412
4413 /**
4414 * @brief Create a file inside the storage area.
4415 *
4416 * This function creates a new file inside the storage area that is
4417 * currently used by Orthanc.
4418 *
4419 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4420 * @param storageArea The storage area.
4421 * @param uuid The identifier of the file to be created.
4422 * @param content The content to store in the newly created file.
4423 * @param size The size of the content.
4424 * @param type The type of the file content.
4425 * @return 0 if success, other value if error.
4426 * @ingroup Callbacks
4427 **/
4428 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate(
4429 OrthancPluginContext* context,
4430 OrthancPluginStorageArea* storageArea,
4431 const char* uuid,
4432 const void* content,
4433 uint64_t size,
4434 OrthancPluginContentType type)
4435 {
4436 _OrthancPluginStorageAreaCreate params;
4437 params.storageArea = storageArea;
4438 params.uuid = uuid;
4439 params.content = content;
4440 params.size = size;
4441 params.type = type;
4442
4443 return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
4444 }
4445
4446
4447 typedef struct
4448 {
4449 OrthancPluginMemoryBuffer* target;
4450 OrthancPluginStorageArea* storageArea;
4451 const char* uuid;
4452 OrthancPluginContentType type;
4453 } _OrthancPluginStorageAreaRead;
4454
4455
4456 /**
4457 * @brief Read a file from the storage area.
4458 *
4459 * This function reads the content of a given file from the storage
4460 * area that is currently used by Orthanc.
4461 *
4462 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4463 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
4464 * @param storageArea The storage area.
4465 * @param uuid The identifier of the file to be read.
4466 * @param type The type of the file content.
4467 * @return 0 if success, other value if error.
4468 * @ingroup Callbacks
4469 **/
4470 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead(
4471 OrthancPluginContext* context,
4472 OrthancPluginMemoryBuffer* target,
4473 OrthancPluginStorageArea* storageArea,
4474 const char* uuid,
4475 OrthancPluginContentType type)
4476 {
4477 _OrthancPluginStorageAreaRead params;
4478 params.target = target;
4479 params.storageArea = storageArea;
4480 params.uuid = uuid;
4481 params.type = type;
4482
4483 return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
4484 }
4485
4486
4487 typedef struct
4488 {
4489 OrthancPluginStorageArea* storageArea;
4490 const char* uuid;
4491 OrthancPluginContentType type;
4492 } _OrthancPluginStorageAreaRemove;
4493
4494 /**
4495 * @brief Remove a file from the storage area.
4496 *
4497 * This function removes a given file from the storage area that is
4498 * currently used by Orthanc.
4499 *
4500 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4501 * @param storageArea The storage area.
4502 * @param uuid The identifier of the file to be removed.
4503 * @param type The type of the file content.
4504 * @return 0 if success, other value if error.
4505 * @ingroup Callbacks
4506 **/
4507 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove(
4508 OrthancPluginContext* context,
4509 OrthancPluginStorageArea* storageArea,
4510 const char* uuid,
4511 OrthancPluginContentType type)
4512 {
4513 _OrthancPluginStorageAreaRemove params;
4514 params.storageArea = storageArea;
4515 params.uuid = uuid;
4516 params.type = type;
4517
4518 return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
4519 }
4520
4521
4522
4523 typedef struct
4524 {
4525 OrthancPluginErrorCode* target;
4526 int32_t code;
4527 uint16_t httpStatus;
4528 const char* message;
4529 } _OrthancPluginRegisterErrorCode;
4530
4531 /**
4532 * @brief Declare a custom error code for this plugin.
4533 *
4534 * This function declares a custom error code that can be generated
4535 * by this plugin. This declaration is used to enrich the body of
4536 * the HTTP answer in the case of an error, and to set the proper
4537 * HTTP status code.
4538 *
4539 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4540 * @param code The error code that is internal to this plugin.
4541 * @param httpStatus The HTTP status corresponding to this error.
4542 * @param message The description of the error.
4543 * @return The error code that has been assigned inside the Orthanc core.
4544 * @ingroup Toolbox
4545 **/
4546 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode(
4547 OrthancPluginContext* context,
4548 int32_t code,
4549 uint16_t httpStatus,
4550 const char* message)
4551 {
4552 OrthancPluginErrorCode target;
4553
4554 _OrthancPluginRegisterErrorCode params;
4555 params.target = &target;
4556 params.code = code;
4557 params.httpStatus = httpStatus;
4558 params.message = message;
4559
4560 if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
4561 {
4562 return target;
4563 }
4564 else
4565 {
4566 /* There was an error while assigned the error. Use a generic code. */
4567 return OrthancPluginErrorCode_Plugin;
4568 }
4569 }
4570
4571
4572
4573 typedef struct
4574 {
4575 uint16_t group;
4576 uint16_t element;
4577 OrthancPluginValueRepresentation vr;
4578 const char* name;
4579 uint32_t minMultiplicity;
4580 uint32_t maxMultiplicity;
4581 } _OrthancPluginRegisterDictionaryTag;
4582
4583 /**
4584 * @brief Register a new tag into the DICOM dictionary.
4585 *
4586 * This function declares a new public tag in the dictionary of
4587 * DICOM tags that are known to Orthanc. This function should be
4588 * used in the OrthancPluginInitialize() callback.
4589 *
4590 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4591 * @param group The group of the tag.
4592 * @param element The element of the tag.
4593 * @param vr The value representation of the tag.
4594 * @param name The nickname of the tag.
4595 * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
4596 * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
4597 * an arbitrary multiplicity ("<tt>n</tt>").
4598 * @return 0 if success, other value if error.
4599 * @see OrthancPluginRegisterPrivateDictionaryTag()
4600 * @ingroup Toolbox
4601 **/
4602 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag(
4603 OrthancPluginContext* context,
4604 uint16_t group,
4605 uint16_t element,
4606 OrthancPluginValueRepresentation vr,
4607 const char* name,
4608 uint32_t minMultiplicity,
4609 uint32_t maxMultiplicity)
4610 {
4611 _OrthancPluginRegisterDictionaryTag params;
4612 params.group = group;
4613 params.element = element;
4614 params.vr = vr;
4615 params.name = name;
4616 params.minMultiplicity = minMultiplicity;
4617 params.maxMultiplicity = maxMultiplicity;
4618
4619 return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
4620 }
4621
4622
4623
4624 typedef struct
4625 {
4626 uint16_t group;
4627 uint16_t element;
4628 OrthancPluginValueRepresentation vr;
4629 const char* name;
4630 uint32_t minMultiplicity;
4631 uint32_t maxMultiplicity;
4632 const char* privateCreator;
4633 } _OrthancPluginRegisterPrivateDictionaryTag;
4634
4635 /**
4636 * @brief Register a new private tag into the DICOM dictionary.
4637 *
4638 * This function declares a new private tag in the dictionary of
4639 * DICOM tags that are known to Orthanc. This function should be
4640 * used in the OrthancPluginInitialize() callback.
4641 *
4642 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4643 * @param group The group of the tag.
4644 * @param element The element of the tag.
4645 * @param vr The value representation of the tag.
4646 * @param name The nickname of the tag.
4647 * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
4648 * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
4649 * an arbitrary multiplicity ("<tt>n</tt>").
4650 * @param privateCreator The private creator of this private tag.
4651 * @return 0 if success, other value if error.
4652 * @see OrthancPluginRegisterDictionaryTag()
4653 * @ingroup Toolbox
4654 **/
4655 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag(
4656 OrthancPluginContext* context,
4657 uint16_t group,
4658 uint16_t element,
4659 OrthancPluginValueRepresentation vr,
4660 const char* name,
4661 uint32_t minMultiplicity,
4662 uint32_t maxMultiplicity,
4663 const char* privateCreator)
4664 {
4665 _OrthancPluginRegisterPrivateDictionaryTag params;
4666 params.group = group;
4667 params.element = element;
4668 params.vr = vr;
4669 params.name = name;
4670 params.minMultiplicity = minMultiplicity;
4671 params.maxMultiplicity = maxMultiplicity;
4672 params.privateCreator = privateCreator;
4673
4674 return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, &params);
4675 }
4676
4677
4678
4679 typedef struct
4680 {
4681 OrthancPluginStorageArea* storageArea;
4682 OrthancPluginResourceType level;
4683 } _OrthancPluginReconstructMainDicomTags;
4684
4685 /**
4686 * @brief Reconstruct the main DICOM tags.
4687 *
4688 * This function requests the Orthanc core to reconstruct the main
4689 * DICOM tags of all the resources of the given type. This function
4690 * can only be used as a part of the upgrade of a custom database
4691 * back-end. A database transaction will be automatically setup.
4692 *
4693 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4694 * @param storageArea The storage area.
4695 * @param level The type of the resources of interest.
4696 * @return 0 if success, other value if error.
4697 * @ingroup Callbacks
4698 **/
4699 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags(
4700 OrthancPluginContext* context,
4701 OrthancPluginStorageArea* storageArea,
4702 OrthancPluginResourceType level)
4703 {
4704 _OrthancPluginReconstructMainDicomTags params;
4705 params.level = level;
4706 params.storageArea = storageArea;
4707
4708 return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
4709 }
4710
4711
4712 typedef struct
4713 {
4714 char** result;
4715 const char* instanceId;
4716 const void* buffer;
4717 uint32_t size;
4718 OrthancPluginDicomToJsonFormat format;
4719 OrthancPluginDicomToJsonFlags flags;
4720 uint32_t maxStringLength;
4721 } _OrthancPluginDicomToJson;
4722
4723
4724 /**
4725 * @brief Format a DICOM memory buffer as a JSON string.
4726 *
4727 * This function takes as input a memory buffer containing a DICOM
4728 * file, and outputs a JSON string representing the tags of this
4729 * DICOM file.
4730 *
4731 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4732 * @param buffer The memory buffer containing the DICOM file.
4733 * @param size The size of the memory buffer.
4734 * @param format The output format.
4735 * @param flags Flags governing the output.
4736 * @param maxStringLength The maximum length of a field. Too long fields will
4737 * be output as "null". The 0 value means no maximum length.
4738 * @return The NULL value if the case of an error, or the JSON
4739 * string. This string must be freed by OrthancPluginFreeString().
4740 * @ingroup Toolbox
4741 * @see OrthancPluginDicomInstanceToJson
4742 **/
4743 ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
4744 OrthancPluginContext* context,
4745 const void* buffer,
4746 uint32_t size,
4747 OrthancPluginDicomToJsonFormat format,
4748 OrthancPluginDicomToJsonFlags flags,
4749 uint32_t maxStringLength)
4750 {
4751 char* result;
4752
4753 _OrthancPluginDicomToJson params;
4754 memset(&params, 0, sizeof(params));
4755 params.result = &result;
4756 params.buffer = buffer;
4757 params.size = size;
4758 params.format = format;
4759 params.flags = flags;
4760 params.maxStringLength = maxStringLength;
4761
4762 if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
4763 {
4764 /* Error */
4765 return NULL;
4766 }
4767 else
4768 {
4769 return result;
4770 }
4771 }
4772
4773
4774 /**
4775 * @brief Format a DICOM instance as a JSON string.
4776 *
4777 * This function formats a DICOM instance that is stored in Orthanc,
4778 * and outputs a JSON string representing the tags of this DICOM
4779 * instance.
4780 *
4781 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4782 * @param instanceId The Orthanc identifier of the instance.
4783 * @param format The output format.
4784 * @param flags Flags governing the output.
4785 * @param maxStringLength The maximum length of a field. Too long fields will
4786 * be output as "null". The 0 value means no maximum length.
4787 * @return The NULL value if the case of an error, or the JSON
4788 * string. This string must be freed by OrthancPluginFreeString().
4789 * @ingroup Toolbox
4790 * @see OrthancPluginDicomInstanceToJson
4791 **/
4792 ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
4793 OrthancPluginContext* context,
4794 const char* instanceId,
4795 OrthancPluginDicomToJsonFormat format,
4796 OrthancPluginDicomToJsonFlags flags,
4797 uint32_t maxStringLength)
4798 {
4799 char* result;
4800
4801 _OrthancPluginDicomToJson params;
4802 memset(&params, 0, sizeof(params));
4803 params.result = &result;
4804 params.instanceId = instanceId;
4805 params.format = format;
4806 params.flags = flags;
4807 params.maxStringLength = maxStringLength;
4808
4809 if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
4810 {
4811 /* Error */
4812 return NULL;
4813 }
4814 else
4815 {
4816 return result;
4817 }
4818 }
4819
4820
4821 typedef struct
4822 {
4823 OrthancPluginMemoryBuffer* target;
4824 const char* uri;
4825 uint32_t headersCount;
4826 const char* const* headersKeys;
4827 const char* const* headersValues;
4828 int32_t afterPlugins;
4829 } _OrthancPluginRestApiGet2;
4830
4831 /**
4832 * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
4833 *
4834 * Make a GET call to the Orthanc REST API with extended
4835 * parameters. The result to the query is stored into a newly
4836 * allocated memory buffer.
4837 *
4838 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4839 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
4840 * @param uri The URI in the built-in Orthanc API.
4841 * @param headersCount The number of HTTP headers.
4842 * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
4843 * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
4844 * @param afterPlugins If 0, the built-in API of Orthanc is used.
4845 * If 1, the API is tainted by the plugins.
4846 * @return 0 if success, or the error code if failure.
4847 * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
4848 * @ingroup Orthanc
4849 **/
4850 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2(
4851 OrthancPluginContext* context,
4852 OrthancPluginMemoryBuffer* target,
4853 const char* uri,
4854 uint32_t headersCount,
4855 const char* const* headersKeys,
4856 const char* const* headersValues,
4857 int32_t afterPlugins)
4858 {
4859 _OrthancPluginRestApiGet2 params;
4860 params.target = target;
4861 params.uri = uri;
4862 params.headersCount = headersCount;
4863 params.headersKeys = headersKeys;
4864 params.headersValues = headersValues;
4865 params.afterPlugins = afterPlugins;
4866
4867 return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
4868 }
4869
4870
4871
4872 typedef struct
4873 {
4874 OrthancPluginWorklistCallback callback;
4875 } _OrthancPluginWorklistCallback;
4876
4877 /**
4878 * @brief Register a callback to handle modality worklists requests.
4879 *
4880 * This function registers a callback to handle C-Find SCP requests
4881 * on modality worklists.
4882 *
4883 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4884 * @param callback The callback.
4885 * @return 0 if success, other value if error.
4886 * @ingroup DicomCallbacks
4887 **/
4888 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
4889 OrthancPluginContext* context,
4890 OrthancPluginWorklistCallback callback)
4891 {
4892 _OrthancPluginWorklistCallback params;
4893 params.callback = callback;
4894
4895 return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
4896 }
4897
4898
4899
4900 typedef struct
4901 {
4902 OrthancPluginWorklistAnswers* answers;
4903 const OrthancPluginWorklistQuery* query;
4904 const void* dicom;
4905 uint32_t size;
4906 } _OrthancPluginWorklistAnswersOperation;
4907
4908 /**
4909 * @brief Add one answer to some modality worklist request.
4910 *
4911 * This function adds one worklist (encoded as a DICOM file) to the
4912 * set of answers corresponding to some C-Find SCP request against
4913 * modality worklists.
4914 *
4915 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4916 * @param answers The set of answers.
4917 * @param query The worklist query, as received by the callback.
4918 * @param dicom The worklist to answer, encoded as a DICOM file.
4919 * @param size The size of the DICOM file.
4920 * @return 0 if success, other value if error.
4921 * @ingroup DicomCallbacks
4922 * @see OrthancPluginCreateDicom()
4923 **/
4924 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer(
4925 OrthancPluginContext* context,
4926 OrthancPluginWorklistAnswers* answers,
4927 const OrthancPluginWorklistQuery* query,
4928 const void* dicom,
4929 uint32_t size)
4930 {
4931 _OrthancPluginWorklistAnswersOperation params;
4932 params.answers = answers;
4933 params.query = query;
4934 params.dicom = dicom;
4935 params.size = size;
4936
4937 return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
4938 }
4939
4940
4941 /**
4942 * @brief Mark the set of worklist answers as incomplete.
4943 *
4944 * This function marks as incomplete the set of answers
4945 * corresponding to some C-Find SCP request against modality
4946 * worklists. This must be used if canceling the handling of a
4947 * request when too many answers are to be returned.
4948 *
4949 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4950 * @param answers The set of answers.
4951 * @return 0 if success, other value if error.
4952 * @ingroup DicomCallbacks
4953 **/
4954 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete(
4955 OrthancPluginContext* context,
4956 OrthancPluginWorklistAnswers* answers)
4957 {
4958 _OrthancPluginWorklistAnswersOperation params;
4959 params.answers = answers;
4960 params.query = NULL;
4961 params.dicom = NULL;
4962 params.size = 0;
4963
4964 return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
4965 }
4966
4967
4968 typedef struct
4969 {
4970 const OrthancPluginWorklistQuery* query;
4971 const void* dicom;
4972 uint32_t size;
4973 int32_t* isMatch;
4974 OrthancPluginMemoryBuffer* target;
4975 } _OrthancPluginWorklistQueryOperation;
4976
4977 /**
4978 * @brief Test whether a worklist matches the query.
4979 *
4980 * This function checks whether one worklist (encoded as a DICOM
4981 * file) matches the C-Find SCP query against modality
4982 * worklists. This function must be called before adding the
4983 * worklist as an answer through OrthancPluginWorklistAddAnswer().
4984 *
4985 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
4986 * @param query The worklist query, as received by the callback.
4987 * @param dicom The worklist to answer, encoded as a DICOM file.
4988 * @param size The size of the DICOM file.
4989 * @return 1 if the worklist matches the query, 0 otherwise.
4990 * @ingroup DicomCallbacks
4991 **/
4992 ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch(
4993 OrthancPluginContext* context,
4994 const OrthancPluginWorklistQuery* query,
4995 const void* dicom,
4996 uint32_t size)
4997 {
4998 int32_t isMatch = 0;
4999
5000 _OrthancPluginWorklistQueryOperation params;
5001 params.query = query;
5002 params.dicom = dicom;
5003 params.size = size;
5004 params.isMatch = &isMatch;
5005 params.target = NULL;
5006
5007 if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
5008 {
5009 return isMatch;
5010 }
5011 else
5012 {
5013 /* Error: Assume non-match */
5014 return 0;
5015 }
5016 }
5017
5018
5019 /**
5020 * @brief Retrieve the worklist query as a DICOM file.
5021 *
5022 * This function retrieves the DICOM file that underlies a C-Find
5023 * SCP query against modality worklists.
5024 *
5025 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5026 * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
5027 * @param query The worklist query, as received by the callback.
5028 * @return 0 if success, other value if error.
5029 * @ingroup DicomCallbacks
5030 **/
5031 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery(
5032 OrthancPluginContext* context,
5033 OrthancPluginMemoryBuffer* target,
5034 const OrthancPluginWorklistQuery* query)
5035 {
5036 _OrthancPluginWorklistQueryOperation params;
5037 params.query = query;
5038 params.dicom = NULL;
5039 params.size = 0;
5040 params.isMatch = NULL;
5041 params.target = target;
5042
5043 return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
5044 }
5045
5046
5047 /**
5048 * @brief Get the origin of a DICOM file.
5049 *
5050 * This function returns the origin of a DICOM instance that has been received by Orthanc.
5051 *
5052 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5053 * @param instance The instance of interest.
5054 * @return The origin of the instance.
5055 * @ingroup Callbacks
5056 **/
5057 ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
5058 OrthancPluginContext* context,
5059 OrthancPluginDicomInstance* instance)
5060 {
5061 OrthancPluginInstanceOrigin origin;
5062
5063 _OrthancPluginAccessDicomInstance params;
5064 memset(&params, 0, sizeof(params));
5065 params.resultOrigin = &origin;
5066 params.instance = instance;
5067
5068 if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
5069 {
5070 /* Error */
5071 return OrthancPluginInstanceOrigin_Unknown;
5072 }
5073 else
5074 {
5075 return origin;
5076 }
5077 }
5078
5079
5080 typedef struct
5081 {
5082 OrthancPluginMemoryBuffer* target;
5083 const char* json;
5084 const OrthancPluginImage* pixelData;
5085 OrthancPluginCreateDicomFlags flags;
5086 } _OrthancPluginCreateDicom;
5087
5088 /**
5089 * @brief Create a DICOM instance from a JSON string and an image.
5090 *
5091 * This function takes as input a string containing a JSON file
5092 * describing the content of a DICOM instance. As an output, it
5093 * writes the corresponding DICOM instance to a newly allocated
5094 * memory buffer. Additionally, an image to be encoded within the
5095 * DICOM instance can also be provided.
5096 *
5097 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5098 * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
5099 * @param json The input JSON file.
5100 * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
5101 * @param flags Flags governing the output.
5102 * @return 0 if success, other value if error.
5103 * @ingroup Toolbox
5104 * @see OrthancPluginDicomBufferToJson
5105 **/
5106 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
5107 OrthancPluginContext* context,
5108 OrthancPluginMemoryBuffer* target,
5109 const char* json,
5110 const OrthancPluginImage* pixelData,
5111 OrthancPluginCreateDicomFlags flags)
5112 {
5113 _OrthancPluginCreateDicom params;
5114 params.target = target;
5115 params.json = json;
5116 params.pixelData = pixelData;
5117 params.flags = flags;
5118
5119 return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
5120 }
5121
5122
5123 typedef struct
5124 {
5125 OrthancPluginDecodeImageCallback callback;
5126 } _OrthancPluginDecodeImageCallback;
5127
5128 /**
5129 * @brief Register a callback to handle the decoding of DICOM images.
5130 *
5131 * This function registers a custom callback to the decoding of
5132 * DICOM images, replacing the built-in decoder of Orthanc.
5133 *
5134 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5135 * @param callback The callback.
5136 * @return 0 if success, other value if error.
5137 * @ingroup Callbacks
5138 **/
5139 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
5140 OrthancPluginContext* context,
5141 OrthancPluginDecodeImageCallback callback)
5142 {
5143 _OrthancPluginDecodeImageCallback params;
5144 params.callback = callback;
5145
5146 return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
5147 }
5148
5149
5150
5151 typedef struct
5152 {
5153 OrthancPluginImage** target;
5154 OrthancPluginPixelFormat format;
5155 uint32_t width;
5156 uint32_t height;
5157 uint32_t pitch;
5158 void* buffer;
5159 const void* constBuffer;
5160 uint32_t bufferSize;
5161 uint32_t frameIndex;
5162 } _OrthancPluginCreateImage;
5163
5164
5165 /**
5166 * @brief Create an image.
5167 *
5168 * This function creates an image of given size and format.
5169 *
5170 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5171 * @param format The format of the pixels.
5172 * @param width The width of the image.
5173 * @param height The height of the image.
5174 * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
5175 * @ingroup Images
5176 **/
5177 ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
5178 OrthancPluginContext* context,
5179 OrthancPluginPixelFormat format,
5180 uint32_t width,
5181 uint32_t height)
5182 {
5183 OrthancPluginImage* target = NULL;
5184
5185 _OrthancPluginCreateImage params;
5186 memset(&params, 0, sizeof(params));
5187 params.target = &target;
5188 params.format = format;
5189 params.width = width;
5190 params.height = height;
5191
5192 if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
5193 {
5194 return NULL;
5195 }
5196 else
5197 {
5198 return target;
5199 }
5200 }
5201
5202
5203 /**
5204 * @brief Create an image pointing to a memory buffer.
5205 *
5206 * This function creates an image whose content points to a memory
5207 * buffer managed by the plugin. Note that the buffer is directly
5208 * accessed, no memory is allocated and no data is copied.
5209 *
5210 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5211 * @param format The format of the pixels.
5212 * @param width The width of the image.
5213 * @param height The height of the image.
5214 * @param pitch The pitch of the image (i.e. the number of bytes
5215 * between 2 successive lines of the image in the memory buffer).
5216 * @param buffer The memory buffer.
5217 * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
5218 * @ingroup Images
5219 **/
5220 ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
5221 OrthancPluginContext* context,
5222 OrthancPluginPixelFormat format,
5223 uint32_t width,
5224 uint32_t height,
5225 uint32_t pitch,
5226 void* buffer)
5227 {
5228 OrthancPluginImage* target = NULL;
5229
5230 _OrthancPluginCreateImage params;
5231 memset(&params, 0, sizeof(params));
5232 params.target = &target;
5233 params.format = format;
5234 params.width = width;
5235 params.height = height;
5236 params.pitch = pitch;
5237 params.buffer = buffer;
5238
5239 if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
5240 {
5241 return NULL;
5242 }
5243 else
5244 {
5245 return target;
5246 }
5247 }
5248
5249
5250
5251 /**
5252 * @brief Decode one frame from a DICOM instance.
5253 *
5254 * This function decodes one frame of a DICOM image that is stored
5255 * in a memory buffer. This function will give the same result as
5256 * OrthancPluginUncompressImage() for single-frame DICOM images.
5257 *
5258 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5259 * @param buffer Pointer to a memory buffer containing the DICOM image.
5260 * @param bufferSize Size of the memory buffer containing the DICOM image.
5261 * @param frameIndex The index of the frame of interest in a multi-frame image.
5262 * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
5263 * @ingroup Images
5264 **/
5265 ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
5266 OrthancPluginContext* context,
5267 const void* buffer,
5268 uint32_t bufferSize,
5269 uint32_t frameIndex)
5270 {
5271 OrthancPluginImage* target = NULL;
5272
5273 _OrthancPluginCreateImage params;
5274 memset(&params, 0, sizeof(params));
5275 params.target = &target;
5276 params.constBuffer = buffer;
5277 params.bufferSize = bufferSize;
5278 params.frameIndex = frameIndex;
5279
5280 if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
5281 {
5282 return NULL;
5283 }
5284 else
5285 {
5286 return target;
5287 }
5288 }
5289
5290
5291
5292 typedef struct
5293 {
5294 char** result;
5295 const void* buffer;
5296 uint32_t size;
5297 } _OrthancPluginComputeHash;
5298
5299 /**
5300 * @brief Compute an MD5 hash.
5301 *
5302 * This functions computes the MD5 cryptographic hash of the given memory buffer.
5303 *
5304 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5305 * @param buffer The source memory buffer.
5306 * @param size The size in bytes of the source buffer.
5307 * @return The NULL value in case of error, or a string containing the cryptographic hash.
5308 * This string must be freed by OrthancPluginFreeString().
5309 * @ingroup Toolbox
5310 **/
5311 ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
5312 OrthancPluginContext* context,
5313 const void* buffer,
5314 uint32_t size)
5315 {
5316 char* result;
5317
5318 _OrthancPluginComputeHash params;
5319 params.result = &result;
5320 params.buffer = buffer;
5321 params.size = size;
5322
5323 if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
5324 {
5325 /* Error */
5326 return NULL;
5327 }
5328 else
5329 {
5330 return result;
5331 }
5332 }
5333
5334
5335 /**
5336 * @brief Compute a SHA-1 hash.
5337 *
5338 * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
5339 *
5340 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5341 * @param buffer The source memory buffer.
5342 * @param size The size in bytes of the source buffer.
5343 * @return The NULL value in case of error, or a string containing the cryptographic hash.
5344 * This string must be freed by OrthancPluginFreeString().
5345 * @ingroup Toolbox
5346 **/
5347 ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
5348 OrthancPluginContext* context,
5349 const void* buffer,
5350 uint32_t size)
5351 {
5352 char* result;
5353
5354 _OrthancPluginComputeHash params;
5355 params.result = &result;
5356 params.buffer = buffer;
5357 params.size = size;
5358
5359 if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
5360 {
5361 /* Error */
5362 return NULL;
5363 }
5364 else
5365 {
5366 return result;
5367 }
5368 }
5369
5370
5371
5372 typedef struct
5373 {
5374 OrthancPluginDictionaryEntry* target;
5375 const char* name;
5376 } _OrthancPluginLookupDictionary;
5377
5378 /**
5379 * @brief Get information about the given DICOM tag.
5380 *
5381 * This functions makes a lookup in the dictionary of DICOM tags
5382 * that are known to Orthanc, and returns information about this
5383 * tag. The tag can be specified using its human-readable name
5384 * (e.g. "PatientName") or a set of two hexadecimal numbers
5385 * (e.g. "0010-0020").
5386 *
5387 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5388 * @param target Where to store the information about the tag.
5389 * @param name The name of the DICOM tag.
5390 * @return 0 if success, other value if error.
5391 * @ingroup Toolbox
5392 **/
5393 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary(
5394 OrthancPluginContext* context,
5395 OrthancPluginDictionaryEntry* target,
5396 const char* name)
5397 {
5398 _OrthancPluginLookupDictionary params;
5399 params.target = target;
5400 params.name = name;
5401 return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
5402 }
5403
5404
5405
5406 typedef struct
5407 {
5408 OrthancPluginRestOutput* output;
5409 const char* answer;
5410 uint32_t answerSize;
5411 uint32_t headersCount;
5412 const char* const* headersKeys;
5413 const char* const* headersValues;
5414 } _OrthancPluginSendMultipartItem2;
5415
5416 /**
5417 * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
5418 *
5419 * This function sends an item as a part of some HTTP multipart
5420 * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
5421 * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
5422 * with the item.
5423 *
5424 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5425 * @param output The HTTP connection to the client application.
5426 * @param answer Pointer to the memory buffer containing the item.
5427 * @param answerSize Number of bytes of the item.
5428 * @param headersCount The number of HTTP headers.
5429 * @param headersKeys Array containing the keys of the HTTP headers.
5430 * @param headersValues Array containing the values of the HTTP headers.
5431 * @return 0 if success, or the error code if failure (this notably happens
5432 * if the connection is closed by the client).
5433 * @see OrthancPluginSendMultipartItem()
5434 * @ingroup REST
5435 **/
5436 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
5437 OrthancPluginContext* context,
5438 OrthancPluginRestOutput* output,
5439 const char* answer,
5440 uint32_t answerSize,
5441 uint32_t headersCount,
5442 const char* const* headersKeys,
5443 const char* const* headersValues)
5444 {
5445 _OrthancPluginSendMultipartItem2 params;
5446 params.output = output;
5447 params.answer = answer;
5448 params.answerSize = answerSize;
5449 params.headersCount = headersCount;
5450 params.headersKeys = headersKeys;
5451 params.headersValues = headersValues;
5452
5453 return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
5454 }
5455
5456
5457 typedef struct
5458 {
5459 OrthancPluginIncomingHttpRequestFilter callback;
5460 } _OrthancPluginIncomingHttpRequestFilter;
5461
5462 /**
5463 * @brief Register a callback to filter incoming HTTP requests.
5464 *
5465 * This function registers a custom callback to filter incoming HTTP/REST
5466 * requests received by the HTTP server of Orthanc.
5467 *
5468 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5469 * @param callback The callback.
5470 * @return 0 if success, other value if error.
5471 * @ingroup Callbacks
5472 * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2()
5473 **/
5474 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter(
5475 OrthancPluginContext* context,
5476 OrthancPluginIncomingHttpRequestFilter callback)
5477 {
5478 _OrthancPluginIncomingHttpRequestFilter params;
5479 params.callback = callback;
5480
5481 return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, &params);
5482 }
5483
5484
5485
5486 typedef struct
5487 {
5488 OrthancPluginMemoryBuffer* answerBody;
5489 OrthancPluginMemoryBuffer* answerHeaders;
5490 uint16_t* httpStatus;
5491 OrthancPluginHttpMethod method;
5492 const char* url;
5493 uint32_t headersCount;
5494 const char* const* headersKeys;
5495 const char* const* headersValues;
5496 const void* body;
5497 uint32_t bodySize;
5498 const char* username;
5499 const char* password;
5500 uint32_t timeout;
5501 const char* certificateFile;
5502 const char* certificateKeyFile;
5503 const char* certificateKeyPassword;
5504 uint8_t pkcs11;
5505 } _OrthancPluginCallHttpClient2;
5506
5507
5508
5509 /**
5510 * @brief Issue a HTTP call with full flexibility.
5511 *
5512 * Make a HTTP call to the given URL. The result to the query is
5513 * stored into a newly allocated memory buffer. The HTTP request
5514 * will be done accordingly to the global configuration of Orthanc
5515 * (in particular, the options "HttpProxy", "HttpTimeout",
5516 * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be
5517 * taken into account).
5518 *
5519 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5520 * @param answerBody The target memory buffer (out argument).
5521 * It must be freed with OrthancPluginFreeMemoryBuffer().
5522 * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument).
5523 * The answer headers are formatted as a JSON object (associative array).
5524 * The buffer must be freed with OrthancPluginFreeMemoryBuffer().
5525 * This argument can be set to NULL if the plugin has no interest in the HTTP headers.
5526 * @param httpStatus The HTTP status after the execution of the request (out argument).
5527 * @param method HTTP method to be used.
5528 * @param url The URL of interest.
5529 * @param headersCount The number of HTTP headers.
5530 * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
5531 * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
5532 * @param username The username (can be <tt>NULL</tt> if no password protection).
5533 * @param password The password (can be <tt>NULL</tt> if no password protection).
5534 * @param body The HTTP body for a POST or PUT request.
5535 * @param bodySize The size of the body.
5536 * @param timeout Timeout in seconds (0 for default timeout).
5537 * @param certificateFile Path to the client certificate for HTTPS, in PEM format
5538 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
5539 * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
5540 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
5541 * @param certificateKeyPassword Password to unlock the key of the client certificate
5542 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
5543 * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
5544 * @return 0 if success, or the error code if failure.
5545 * @see OrthancPluginCallPeerApi()
5546 * @ingroup Toolbox
5547 **/
5548 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient(
5549 OrthancPluginContext* context,
5550 OrthancPluginMemoryBuffer* answerBody,
5551 OrthancPluginMemoryBuffer* answerHeaders,
5552 uint16_t* httpStatus,
5553 OrthancPluginHttpMethod method,
5554 const char* url,
5555 uint32_t headersCount,
5556 const char* const* headersKeys,
5557 const char* const* headersValues,
5558 const void* body,
5559 uint32_t bodySize,
5560 const char* username,
5561 const char* password,
5562 uint32_t timeout,
5563 const char* certificateFile,
5564 const char* certificateKeyFile,
5565 const char* certificateKeyPassword,
5566 uint8_t pkcs11)
5567 {
5568 _OrthancPluginCallHttpClient2 params;
5569 memset(&params, 0, sizeof(params));
5570
5571 params.answerBody = answerBody;
5572 params.answerHeaders = answerHeaders;
5573 params.httpStatus = httpStatus;
5574 params.method = method;
5575 params.url = url;
5576 params.headersCount = headersCount;
5577 params.headersKeys = headersKeys;
5578 params.headersValues = headersValues;
5579 params.body = body;
5580 params.bodySize = bodySize;
5581 params.username = username;
5582 params.password = password;
5583 params.timeout = timeout;
5584 params.certificateFile = certificateFile;
5585 params.certificateKeyFile = certificateKeyFile;
5586 params.certificateKeyPassword = certificateKeyPassword;
5587 params.pkcs11 = pkcs11;
5588
5589 return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, &params);
5590 }
5591
5592
5593 /**
5594 * @brief Generate an UUID.
5595 *
5596 * Generate a random GUID/UUID (globally unique identifier).
5597 *
5598 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5599 * @return NULL in the case of an error, or a newly allocated string
5600 * containing the UUID. This string must be freed by OrthancPluginFreeString().
5601 * @ingroup Toolbox
5602 **/
5603 ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid(
5604 OrthancPluginContext* context)
5605 {
5606 char* result;
5607
5608 _OrthancPluginRetrieveDynamicString params;
5609 params.result = &result;
5610 params.argument = NULL;
5611
5612 if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, &params) != OrthancPluginErrorCode_Success)
5613 {
5614 /* Error */
5615 return NULL;
5616 }
5617 else
5618 {
5619 return result;
5620 }
5621 }
5622
5623
5624
5625
5626 typedef struct
5627 {
5628 OrthancPluginFindCallback callback;
5629 } _OrthancPluginFindCallback;
5630
5631 /**
5632 * @brief Register a callback to handle C-Find requests.
5633 *
5634 * This function registers a callback to handle C-Find SCP requests
5635 * that are not related to modality worklists.
5636 *
5637 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5638 * @param callback The callback.
5639 * @return 0 if success, other value if error.
5640 * @ingroup DicomCallbacks
5641 **/
5642 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback(
5643 OrthancPluginContext* context,
5644 OrthancPluginFindCallback callback)
5645 {
5646 _OrthancPluginFindCallback params;
5647 params.callback = callback;
5648
5649 return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, &params);
5650 }
5651
5652
5653 typedef struct
5654 {
5655 OrthancPluginFindAnswers *answers;
5656 const OrthancPluginFindQuery *query;
5657 const void *dicom;
5658 uint32_t size;
5659 uint32_t index;
5660 uint32_t *resultUint32;
5661 uint16_t *resultGroup;
5662 uint16_t *resultElement;
5663 char **resultString;
5664 } _OrthancPluginFindOperation;
5665
5666 /**
5667 * @brief Add one answer to some C-Find request.
5668 *
5669 * This function adds one answer (encoded as a DICOM file) to the
5670 * set of answers corresponding to some C-Find SCP request that is
5671 * not related to modality worklists.
5672 *
5673 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5674 * @param answers The set of answers.
5675 * @param dicom The answer to be added, encoded as a DICOM file.
5676 * @param size The size of the DICOM file.
5677 * @return 0 if success, other value if error.
5678 * @ingroup DicomCallbacks
5679 * @see OrthancPluginCreateDicom()
5680 **/
5681 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindAddAnswer(
5682 OrthancPluginContext* context,
5683 OrthancPluginFindAnswers* answers,
5684 const void* dicom,
5685 uint32_t size)
5686 {
5687 _OrthancPluginFindOperation params;
5688 memset(&params, 0, sizeof(params));
5689 params.answers = answers;
5690 params.dicom = dicom;
5691 params.size = size;
5692
5693 return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, &params);
5694 }
5695
5696
5697 /**
5698 * @brief Mark the set of C-Find answers as incomplete.
5699 *
5700 * This function marks as incomplete the set of answers
5701 * corresponding to some C-Find SCP request that is not related to
5702 * modality worklists. This must be used if canceling the handling
5703 * of a request when too many answers are to be returned.
5704 *
5705 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5706 * @param answers The set of answers.
5707 * @return 0 if success, other value if error.
5708 * @ingroup DicomCallbacks
5709 **/
5710 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindMarkIncomplete(
5711 OrthancPluginContext* context,
5712 OrthancPluginFindAnswers* answers)
5713 {
5714 _OrthancPluginFindOperation params;
5715 memset(&params, 0, sizeof(params));
5716 params.answers = answers;
5717
5718 return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, &params);
5719 }
5720
5721
5722
5723 /**
5724 * @brief Get the number of tags in a C-Find query.
5725 *
5726 * This function returns the number of tags that are contained in
5727 * the given C-Find query.
5728 *
5729 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5730 * @param query The C-Find query.
5731 * @return The number of tags.
5732 * @ingroup DicomCallbacks
5733 **/
5734 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFindQuerySize(
5735 OrthancPluginContext* context,
5736 const OrthancPluginFindQuery* query)
5737 {
5738 uint32_t count = 0;
5739
5740 _OrthancPluginFindOperation params;
5741 memset(&params, 0, sizeof(params));
5742 params.query = query;
5743 params.resultUint32 = &count;
5744
5745 if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, &params) != OrthancPluginErrorCode_Success)
5746 {
5747 /* Error */
5748 return 0;
5749 }
5750 else
5751 {
5752 return count;
5753 }
5754 }
5755
5756
5757 /**
5758 * @brief Get one tag in a C-Find query.
5759 *
5760 * This function returns the group and the element of one DICOM tag
5761 * in the given C-Find query.
5762 *
5763 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5764 * @param group The group of the tag (output).
5765 * @param element The element of the tag (output).
5766 * @param query The C-Find query.
5767 * @param index The index of the tag of interest.
5768 * @return 0 if success, other value if error.
5769 * @ingroup DicomCallbacks
5770 **/
5771 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetFindQueryTag(
5772 OrthancPluginContext* context,
5773 uint16_t* group,
5774 uint16_t* element,
5775 const OrthancPluginFindQuery* query,
5776 uint32_t index)
5777 {
5778 _OrthancPluginFindOperation params;
5779 memset(&params, 0, sizeof(params));
5780 params.query = query;
5781 params.index = index;
5782 params.resultGroup = group;
5783 params.resultElement = element;
5784
5785 return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, &params);
5786 }
5787
5788
5789 /**
5790 * @brief Get the symbolic name of one tag in a C-Find query.
5791 *
5792 * This function returns the symbolic name of one DICOM tag in the
5793 * given C-Find query.
5794 *
5795 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5796 * @param query The C-Find query.
5797 * @param index The index of the tag of interest.
5798 * @return The NULL value in case of error, or a string containing the name of the tag.
5799 * @return 0 if success, other value if error.
5800 * @ingroup DicomCallbacks
5801 **/
5802 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryTagName(
5803 OrthancPluginContext* context,
5804 const OrthancPluginFindQuery* query,
5805 uint32_t index)
5806 {
5807 char* result;
5808
5809 _OrthancPluginFindOperation params;
5810 memset(&params, 0, sizeof(params));
5811 params.query = query;
5812 params.index = index;
5813 params.resultString = &result;
5814
5815 if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, &params) != OrthancPluginErrorCode_Success)
5816 {
5817 /* Error */
5818 return NULL;
5819 }
5820 else
5821 {
5822 return result;
5823 }
5824 }
5825
5826
5827 /**
5828 * @brief Get the value associated with one tag in a C-Find query.
5829 *
5830 * This function returns the value associated with one tag in the
5831 * given C-Find query.
5832 *
5833 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5834 * @param query The C-Find query.
5835 * @param index The index of the tag of interest.
5836 * @return The NULL value in case of error, or a string containing the value of the tag.
5837 * @return 0 if success, other value if error.
5838 * @ingroup DicomCallbacks
5839 **/
5840 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryValue(
5841 OrthancPluginContext* context,
5842 const OrthancPluginFindQuery* query,
5843 uint32_t index)
5844 {
5845 char* result;
5846
5847 _OrthancPluginFindOperation params;
5848 memset(&params, 0, sizeof(params));
5849 params.query = query;
5850 params.index = index;
5851 params.resultString = &result;
5852
5853 if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, &params) != OrthancPluginErrorCode_Success)
5854 {
5855 /* Error */
5856 return NULL;
5857 }
5858 else
5859 {
5860 return result;
5861 }
5862 }
5863
5864
5865
5866
5867 typedef struct
5868 {
5869 OrthancPluginMoveCallback callback;
5870 OrthancPluginGetMoveSize getMoveSize;
5871 OrthancPluginApplyMove applyMove;
5872 OrthancPluginFreeMove freeMove;
5873 } _OrthancPluginMoveCallback;
5874
5875 /**
5876 * @brief Register a callback to handle C-Move requests.
5877 *
5878 * This function registers a callback to handle C-Move SCP requests.
5879 *
5880 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5881 * @param callback The main callback.
5882 * @param getMoveSize Callback to read the number of C-Move suboperations.
5883 * @param applyMove Callback to apply one C-Move suboperation.
5884 * @param freeMove Callback to free the C-Move driver.
5885 * @return 0 if success, other value if error.
5886 * @ingroup DicomCallbacks
5887 **/
5888 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback(
5889 OrthancPluginContext* context,
5890 OrthancPluginMoveCallback callback,
5891 OrthancPluginGetMoveSize getMoveSize,
5892 OrthancPluginApplyMove applyMove,
5893 OrthancPluginFreeMove freeMove)
5894 {
5895 _OrthancPluginMoveCallback params;
5896 params.callback = callback;
5897 params.getMoveSize = getMoveSize;
5898 params.applyMove = applyMove;
5899 params.freeMove = freeMove;
5900
5901 return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, &params);
5902 }
5903
5904
5905
5906 typedef struct
5907 {
5908 OrthancPluginFindMatcher** target;
5909 const void* query;
5910 uint32_t size;
5911 } _OrthancPluginCreateFindMatcher;
5912
5913
5914 /**
5915 * @brief Create a C-Find matcher.
5916 *
5917 * This function creates a "matcher" object that can be used to
5918 * check whether a DICOM instance matches a C-Find query. The C-Find
5919 * query must be expressed as a DICOM buffer.
5920 *
5921 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5922 * @param query The C-Find DICOM query.
5923 * @param size The size of the DICOM query.
5924 * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher().
5925 * @ingroup Toolbox
5926 **/
5927 ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher(
5928 OrthancPluginContext* context,
5929 const void* query,
5930 uint32_t size)
5931 {
5932 OrthancPluginFindMatcher* target = NULL;
5933
5934 _OrthancPluginCreateFindMatcher params;
5935 memset(&params, 0, sizeof(params));
5936 params.target = &target;
5937 params.query = query;
5938 params.size = size;
5939
5940 if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, &params) != OrthancPluginErrorCode_Success)
5941 {
5942 return NULL;
5943 }
5944 else
5945 {
5946 return target;
5947 }
5948 }
5949
5950
5951 typedef struct
5952 {
5953 OrthancPluginFindMatcher* matcher;
5954 } _OrthancPluginFreeFindMatcher;
5955
5956 /**
5957 * @brief Free a C-Find matcher.
5958 *
5959 * This function frees a matcher that was created using OrthancPluginCreateFindMatcher().
5960 *
5961 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5962 * @param matcher The matcher of interest.
5963 * @ingroup Toolbox
5964 **/
5965 ORTHANC_PLUGIN_INLINE void OrthancPluginFreeFindMatcher(
5966 OrthancPluginContext* context,
5967 OrthancPluginFindMatcher* matcher)
5968 {
5969 _OrthancPluginFreeFindMatcher params;
5970 params.matcher = matcher;
5971
5972 context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, &params);
5973 }
5974
5975
5976 typedef struct
5977 {
5978 const OrthancPluginFindMatcher* matcher;
5979 const void* dicom;
5980 uint32_t size;
5981 int32_t* isMatch;
5982 } _OrthancPluginFindMatcherIsMatch;
5983
5984 /**
5985 * @brief Test whether a DICOM instance matches a C-Find query.
5986 *
5987 * This function checks whether one DICOM instance matches C-Find
5988 * matcher that was previously allocated using
5989 * OrthancPluginCreateFindMatcher().
5990 *
5991 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
5992 * @param matcher The matcher of interest.
5993 * @param dicom The DICOM instance to be matched.
5994 * @param size The size of the DICOM instance.
5995 * @return 1 if the DICOM instance matches the query, 0 otherwise.
5996 * @ingroup Toolbox
5997 **/
5998 ORTHANC_PLUGIN_INLINE int32_t OrthancPluginFindMatcherIsMatch(
5999 OrthancPluginContext* context,
6000 const OrthancPluginFindMatcher* matcher,
6001 const void* dicom,
6002 uint32_t size)
6003 {
6004 int32_t isMatch = 0;
6005
6006 _OrthancPluginFindMatcherIsMatch params;
6007 params.matcher = matcher;
6008 params.dicom = dicom;
6009 params.size = size;
6010 params.isMatch = &isMatch;
6011
6012 if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, &params) == OrthancPluginErrorCode_Success)
6013 {
6014 return isMatch;
6015 }
6016 else
6017 {
6018 /* Error: Assume non-match */
6019 return 0;
6020 }
6021 }
6022
6023
6024 typedef struct
6025 {
6026 OrthancPluginIncomingHttpRequestFilter2 callback;
6027 } _OrthancPluginIncomingHttpRequestFilter2;
6028
6029 /**
6030 * @brief Register a callback to filter incoming HTTP requests.
6031 *
6032 * This function registers a custom callback to filter incoming HTTP/REST
6033 * requests received by the HTTP server of Orthanc.
6034 *
6035 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6036 * @param callback The callback.
6037 * @return 0 if success, other value if error.
6038 * @ingroup Callbacks
6039 **/
6040 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2(
6041 OrthancPluginContext* context,
6042 OrthancPluginIncomingHttpRequestFilter2 callback)
6043 {
6044 _OrthancPluginIncomingHttpRequestFilter2 params;
6045 params.callback = callback;
6046
6047 return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, &params);
6048 }
6049
6050
6051
6052 typedef struct
6053 {
6054 OrthancPluginPeers** peers;
6055 } _OrthancPluginGetPeers;
6056
6057 /**
6058 * @brief Return the list of available Orthanc peers.
6059 *
6060 * This function returns the parameters of the Orthanc peers that are known to
6061 * the Orthanc server hosting the plugin.
6062 *
6063 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6064 * @return NULL if error, or a newly allocated opaque data structure containing the peers.
6065 * This structure must be freed with OrthancPluginFreePeers().
6066 * @ingroup Toolbox
6067 **/
6068 ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers(
6069 OrthancPluginContext* context)
6070 {
6071 OrthancPluginPeers* peers = NULL;
6072
6073 _OrthancPluginGetPeers params;
6074 memset(&params, 0, sizeof(params));
6075 params.peers = &peers;
6076
6077 if (context->InvokeService(context, _OrthancPluginService_GetPeers, &params) != OrthancPluginErrorCode_Success)
6078 {
6079 return NULL;
6080 }
6081 else
6082 {
6083 return peers;
6084 }
6085 }
6086
6087
6088 typedef struct
6089 {
6090 OrthancPluginPeers* peers;
6091 } _OrthancPluginFreePeers;
6092
6093 /**
6094 * @brief Free the list of available Orthanc peers.
6095 *
6096 * This function frees the data structure returned by OrthancPluginGetPeers().
6097 *
6098 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6099 * @param peers The data structure describing the Orthanc peers.
6100 * @ingroup Toolbox
6101 **/
6102 ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers(
6103 OrthancPluginContext* context,
6104 OrthancPluginPeers* peers)
6105 {
6106 _OrthancPluginFreePeers params;
6107 params.peers = peers;
6108
6109 context->InvokeService(context, _OrthancPluginService_FreePeers, &params);
6110 }
6111
6112
6113 typedef struct
6114 {
6115 uint32_t* target;
6116 const OrthancPluginPeers* peers;
6117 } _OrthancPluginGetPeersCount;
6118
6119 /**
6120 * @brief Get the number of Orthanc peers.
6121 *
6122 * This function returns the number of Orthanc peers.
6123 *
6124 * This function is thread-safe: Several threads sharing the same
6125 * OrthancPluginPeers object can simultaneously call this function.
6126 *
6127 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6128 * @param peers The data structure describing the Orthanc peers.
6129 * @result The number of peers.
6130 * @ingroup Toolbox
6131 **/
6132 ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount(
6133 OrthancPluginContext* context,
6134 const OrthancPluginPeers* peers)
6135 {
6136 uint32_t target = 0;
6137
6138 _OrthancPluginGetPeersCount params;
6139 memset(&params, 0, sizeof(params));
6140 params.target = &target;
6141 params.peers = peers;
6142
6143 if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, &params) != OrthancPluginErrorCode_Success)
6144 {
6145 /* Error */
6146 return 0;
6147 }
6148 else
6149 {
6150 return target;
6151 }
6152 }
6153
6154
6155 typedef struct
6156 {
6157 const char** target;
6158 const OrthancPluginPeers* peers;
6159 uint32_t peerIndex;
6160 const char* userProperty;
6161 } _OrthancPluginGetPeerProperty;
6162
6163 /**
6164 * @brief Get the symbolic name of an Orthanc peer.
6165 *
6166 * This function returns the symbolic name of the Orthanc peer,
6167 * which corresponds to the key of the "OrthancPeers" configuration
6168 * option of Orthanc.
6169 *
6170 * This function is thread-safe: Several threads sharing the same
6171 * OrthancPluginPeers object can simultaneously call this function.
6172 *
6173 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6174 * @param peers The data structure describing the Orthanc peers.
6175 * @param peerIndex The index of the peer of interest.
6176 * This value must be lower than OrthancPluginGetPeersCount().
6177 * @result The symbolic name, or NULL in the case of an error.
6178 * @ingroup Toolbox
6179 **/
6180 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName(
6181 OrthancPluginContext* context,
6182 const OrthancPluginPeers* peers,
6183 uint32_t peerIndex)
6184 {
6185 const char* target = NULL;
6186
6187 _OrthancPluginGetPeerProperty params;
6188 memset(&params, 0, sizeof(params));
6189 params.target = &target;
6190 params.peers = peers;
6191 params.peerIndex = peerIndex;
6192 params.userProperty = NULL;
6193
6194 if (context->InvokeService(context, _OrthancPluginService_GetPeerName, &params) != OrthancPluginErrorCode_Success)
6195 {
6196 /* Error */
6197 return NULL;
6198 }
6199 else
6200 {
6201 return target;
6202 }
6203 }
6204
6205
6206 /**
6207 * @brief Get the base URL of an Orthanc peer.
6208 *
6209 * This function returns the base URL to the REST API of some Orthanc peer.
6210 *
6211 * This function is thread-safe: Several threads sharing the same
6212 * OrthancPluginPeers object can simultaneously call this function.
6213 *
6214 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6215 * @param peers The data structure describing the Orthanc peers.
6216 * @param peerIndex The index of the peer of interest.
6217 * This value must be lower than OrthancPluginGetPeersCount().
6218 * @result The URL, or NULL in the case of an error.
6219 * @ingroup Toolbox
6220 **/
6221 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl(
6222 OrthancPluginContext* context,
6223 const OrthancPluginPeers* peers,
6224 uint32_t peerIndex)
6225 {
6226 const char* target = NULL;
6227
6228 _OrthancPluginGetPeerProperty params;
6229 memset(&params, 0, sizeof(params));
6230 params.target = &target;
6231 params.peers = peers;
6232 params.peerIndex = peerIndex;
6233 params.userProperty = NULL;
6234
6235 if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, &params) != OrthancPluginErrorCode_Success)
6236 {
6237 /* Error */
6238 return NULL;
6239 }
6240 else
6241 {
6242 return target;
6243 }
6244 }
6245
6246
6247
6248 /**
6249 * @brief Get some user-defined property of an Orthanc peer.
6250 *
6251 * This function returns some user-defined property of some Orthanc
6252 * peer. An user-defined property is a property that is associated
6253 * with the peer in the Orthanc configuration file, but that is not
6254 * recognized by the Orthanc core.
6255 *
6256 * This function is thread-safe: Several threads sharing the same
6257 * OrthancPluginPeers object can simultaneously call this function.
6258 *
6259 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6260 * @param peers The data structure describing the Orthanc peers.
6261 * @param peerIndex The index of the peer of interest.
6262 * This value must be lower than OrthancPluginGetPeersCount().
6263 * @param userProperty The user property of interest.
6264 * @result The value of the user property, or NULL if it is not defined.
6265 * @ingroup Toolbox
6266 **/
6267 ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty(
6268 OrthancPluginContext* context,
6269 const OrthancPluginPeers* peers,
6270 uint32_t peerIndex,
6271 const char* userProperty)
6272 {
6273 const char* target = NULL;
6274
6275 _OrthancPluginGetPeerProperty params;
6276 memset(&params, 0, sizeof(params));
6277 params.target = &target;
6278 params.peers = peers;
6279 params.peerIndex = peerIndex;
6280 params.userProperty = userProperty;
6281
6282 if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, &params) != OrthancPluginErrorCode_Success)
6283 {
6284 /* No such user property */
6285 return NULL;
6286 }
6287 else
6288 {
6289 return target;
6290 }
6291 }
6292
6293
6294
6295 typedef struct
6296 {
6297 OrthancPluginMemoryBuffer* answerBody;
6298 OrthancPluginMemoryBuffer* answerHeaders;
6299 uint16_t* httpStatus;
6300 const OrthancPluginPeers* peers;
6301 uint32_t peerIndex;
6302 OrthancPluginHttpMethod method;
6303 const char* uri;
6304 uint32_t additionalHeadersCount;
6305 const char* const* additionalHeadersKeys;
6306 const char* const* additionalHeadersValues;
6307 const void* body;
6308 uint32_t bodySize;
6309 uint32_t timeout;
6310 } _OrthancPluginCallPeerApi;
6311
6312 /**
6313 * @brief Call the REST API of an Orthanc peer.
6314 *
6315 * Make a REST call to the given URI in the REST API of a remote
6316 * Orthanc peer. The result to the query is stored into a newly
6317 * allocated memory buffer. The HTTP request will be done according
6318 * to the "OrthancPeers" configuration option of Orthanc.
6319 *
6320 * This function is thread-safe: Several threads sharing the same
6321 * OrthancPluginPeers object can simultaneously call this function.
6322 *
6323 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6324 * @param answerBody The target memory buffer (out argument).
6325 * It must be freed with OrthancPluginFreeMemoryBuffer().
6326 * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument).
6327 * The answer headers are formatted as a JSON object (associative array).
6328 * The buffer must be freed with OrthancPluginFreeMemoryBuffer().
6329 * This argument can be set to NULL if the plugin has no interest in the HTTP headers.
6330 * @param httpStatus The HTTP status after the execution of the request (out argument).
6331 * @param peers The data structure describing the Orthanc peers.
6332 * @param peerIndex The index of the peer of interest.
6333 * This value must be lower than OrthancPluginGetPeersCount().
6334 * @param method HTTP method to be used.
6335 * @param uri The URI of interest in the REST API.
6336 * @param additionalHeadersCount The number of HTTP headers to be added to the
6337 * HTTP headers provided in the global configuration of Orthanc.
6338 * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
6339 * @param additionalHeadersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
6340 * @param body The HTTP body for a POST or PUT request.
6341 * @param bodySize The size of the body.
6342 * @param timeout Timeout in seconds (0 for default timeout).
6343 * @return 0 if success, or the error code if failure.
6344 * @see OrthancPluginHttpClient()
6345 * @ingroup Toolbox
6346 **/
6347 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi(
6348 OrthancPluginContext* context,
6349 OrthancPluginMemoryBuffer* answerBody,
6350 OrthancPluginMemoryBuffer* answerHeaders,
6351 uint16_t* httpStatus,
6352 const OrthancPluginPeers* peers,
6353 uint32_t peerIndex,
6354 OrthancPluginHttpMethod method,
6355 const char* uri,
6356 uint32_t additionalHeadersCount,
6357 const char* const* additionalHeadersKeys,
6358 const char* const* additionalHeadersValues,
6359 const void* body,
6360 uint32_t bodySize,
6361 uint32_t timeout)
6362 {
6363 _OrthancPluginCallPeerApi params;
6364 memset(&params, 0, sizeof(params));
6365
6366 params.answerBody = answerBody;
6367 params.answerHeaders = answerHeaders;
6368 params.httpStatus = httpStatus;
6369 params.peers = peers;
6370 params.peerIndex = peerIndex;
6371 params.method = method;
6372 params.uri = uri;
6373 params.additionalHeadersCount = additionalHeadersCount;
6374 params.additionalHeadersKeys = additionalHeadersKeys;
6375 params.additionalHeadersValues = additionalHeadersValues;
6376 params.body = body;
6377 params.bodySize = bodySize;
6378 params.timeout = timeout;
6379
6380 return context->InvokeService(context, _OrthancPluginService_CallPeerApi, &params);
6381 }
6382
6383
6384
6385
6386
6387 typedef struct
6388 {
6389 OrthancPluginJob** target;
6390 void *job;
6391 OrthancPluginJobFinalize finalize;
6392 const char *type;
6393 OrthancPluginJobGetProgress getProgress;
6394 OrthancPluginJobGetContent getContent;
6395 OrthancPluginJobGetSerialized getSerialized;
6396 OrthancPluginJobStep step;
6397 OrthancPluginJobStop stop;
6398 OrthancPluginJobReset reset;
6399 } _OrthancPluginCreateJob;
6400
6401 /**
6402 * @brief Create a custom job.
6403 *
6404 * This function creates a custom job to be run by the jobs engine
6405 * of Orthanc.
6406 *
6407 * Orthanc starts one dedicated thread per custom job that is
6408 * running. It is guaranteed that all the callbacks will only be
6409 * called from this single dedicated thread, in mutual exclusion: As
6410 * a consequence, it is *not* mandatory to protect the various
6411 * callbacks by mutexes.
6412 *
6413 * The custom job can nonetheless launch its own processing threads
6414 * on the first call to the "step()" callback, and stop them once
6415 * the "stop()" callback is called.
6416 *
6417 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6418 * @param job The job to be executed.
6419 * @param finalize The finalization callback.
6420 * @param type The type of the job, provided to the job unserializer.
6421 * See OrthancPluginRegisterJobsUnserializer().
6422 * @param getProgress The progress callback.
6423 * @param getContent The content callback.
6424 * @param getSerialized The serialization callback.
6425 * @param step The callback to execute the individual steps of the job.
6426 * @param stop The callback that is invoked once the job leaves the "running" state.
6427 * @param reset The callback that is invoked if a stopped job is started again.
6428 * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(),
6429 * as long as it is not submitted with OrthancPluginSubmitJob().
6430 * @ingroup Toolbox
6431 **/
6432 ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob(
6433 OrthancPluginContext *context,
6434 void *job,
6435 OrthancPluginJobFinalize finalize,
6436 const char *type,
6437 OrthancPluginJobGetProgress getProgress,
6438 OrthancPluginJobGetContent getContent,
6439 OrthancPluginJobGetSerialized getSerialized,
6440 OrthancPluginJobStep step,
6441 OrthancPluginJobStop stop,
6442 OrthancPluginJobReset reset)
6443 {
6444 OrthancPluginJob* target = NULL;
6445
6446 _OrthancPluginCreateJob params;
6447 memset(&params, 0, sizeof(params));
6448
6449 params.target = &target;
6450 params.job = job;
6451 params.finalize = finalize;
6452 params.type = type;
6453 params.getProgress = getProgress;
6454 params.getContent = getContent;
6455 params.getSerialized = getSerialized;
6456 params.step = step;
6457 params.stop = stop;
6458 params.reset = reset;
6459
6460 if (context->InvokeService(context, _OrthancPluginService_CreateJob, &params) != OrthancPluginErrorCode_Success ||
6461 target == NULL)
6462 {
6463 /* Error */
6464 return NULL;
6465 }
6466 else
6467 {
6468 return target;
6469 }
6470 }
6471
6472
6473 typedef struct
6474 {
6475 OrthancPluginJob* job;
6476 } _OrthancPluginFreeJob;
6477
6478 /**
6479 * @brief Free a custom job.
6480 *
6481 * This function frees an image that was created with OrthancPluginCreateJob().
6482 *
6483 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6484 * @param job The job.
6485 * @ingroup Toolbox
6486 **/
6487 ORTHANC_PLUGIN_INLINE void OrthancPluginFreeJob(
6488 OrthancPluginContext* context,
6489 OrthancPluginJob* job)
6490 {
6491 _OrthancPluginFreeJob params;
6492 params.job = job;
6493
6494 context->InvokeService(context, _OrthancPluginService_FreeJob, &params);
6495 }
6496
6497
6498
6499 typedef struct
6500 {
6501 char** resultId;
6502 OrthancPluginJob *job;
6503 int priority;
6504 } _OrthancPluginSubmitJob;
6505
6506 /**
6507 * @brief Submit a new job to the jobs engine of Orthanc.
6508 *
6509 * This function adds the given job to the pending jobs of
6510 * Orthanc. Orthanc will take take of freeing it by invoking the
6511 * finalization callback provided to OrthancPluginCreateJob().
6512 *
6513 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6514 * @param job The job, as received by OrthancPluginCreateJob().
6515 * @param priority The priority of the job.
6516 * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString().
6517 * @ingroup Toolbox
6518 **/
6519 ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob(
6520 OrthancPluginContext *context,
6521 OrthancPluginJob *job,
6522 int priority)
6523 {
6524 char* resultId = NULL;
6525
6526 _OrthancPluginSubmitJob params;
6527 memset(&params, 0, sizeof(params));
6528
6529 params.resultId = &resultId;
6530 params.job = job;
6531 params.priority = priority;
6532
6533 if (context->InvokeService(context, _OrthancPluginService_SubmitJob, &params) != OrthancPluginErrorCode_Success ||
6534 resultId == NULL)
6535 {
6536 /* Error */
6537 return NULL;
6538 }
6539 else
6540 {
6541 return resultId;
6542 }
6543 }
6544
6545
6546
6547 typedef struct
6548 {
6549 OrthancPluginJobsUnserializer unserializer;
6550 } _OrthancPluginJobsUnserializer;
6551
6552 /**
6553 * @brief Register an unserializer for custom jobs.
6554 *
6555 * This function registers an unserializer that decodes custom jobs
6556 * from a JSON string. This callback is invoked when the jobs engine
6557 * of Orthanc is started (on Orthanc initialization), for each job
6558 * that is stored in the Orthanc database.
6559 *
6560 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6561 * @param unserializer The job unserializer.
6562 * @ingroup Callbacks
6563 **/
6564 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer(
6565 OrthancPluginContext* context,
6566 OrthancPluginJobsUnserializer unserializer)
6567 {
6568 _OrthancPluginJobsUnserializer params;
6569 params.unserializer = unserializer;
6570
6571 context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, &params);
6572 }
6573
6574
6575
6576 typedef struct
6577 {
6578 OrthancPluginRestOutput* output;
6579 const char* details;
6580 uint8_t log;
6581 } _OrthancPluginSetHttpErrorDetails;
6582
6583 /**
6584 * @brief Provide a detailed description for an HTTP error.
6585 *
6586 * This function sets the detailed description associated with an
6587 * HTTP error. This description will be displayed in the "Details"
6588 * field of the JSON body of the HTTP answer. It is only taken into
6589 * consideration if the REST callback returns an error code that is
6590 * different from "OrthancPluginErrorCode_Success", and if the
6591 * "HttpDescribeErrors" configuration option of Orthanc is set to
6592 * "true".
6593 *
6594 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6595 * @param output The HTTP connection to the client application.
6596 * @param details The details of the error message.
6597 * @param log Whether to also write the detailed error to the Orthanc logs.
6598 * @ingroup REST
6599 **/
6600 ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails(
6601 OrthancPluginContext* context,
6602 OrthancPluginRestOutput* output,
6603 const char* details,
6604 uint8_t log)
6605 {
6606 _OrthancPluginSetHttpErrorDetails params;
6607 params.output = output;
6608 params.details = details;
6609 params.log = log;
6610 context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, &params);
6611 }
6612
6613
6614
6615 typedef struct
6616 {
6617 const char** result;
6618 const char* argument;
6619 } _OrthancPluginRetrieveStaticString;
6620
6621 /**
6622 * @brief Detect the MIME type of a file.
6623 *
6624 * This function returns the MIME type of a file by inspecting its extension.
6625 *
6626 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6627 * @param path Path to the file.
6628 * @return The MIME type. This is a statically-allocated
6629 * string, do not free it.
6630 * @ingroup Toolbox
6631 **/
6632 ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType(
6633 OrthancPluginContext* context,
6634 const char* path)
6635 {
6636 const char* result = NULL;
6637
6638 _OrthancPluginRetrieveStaticString params;
6639 params.result = &result;
6640 params.argument = path;
6641
6642 if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, &params) != OrthancPluginErrorCode_Success)
6643 {
6644 /* Error */
6645 return NULL;
6646 }
6647 else
6648 {
6649 return result;
6650 }
6651 }
6652
6653
6654
6655 typedef struct
6656 {
6657 const char* name;
6658 float value;
6659 OrthancPluginMetricsType type;
6660 } _OrthancPluginSetMetricsValue;
6661
6662 /**
6663 * @brief Set the value of a metrics.
6664 *
6665 * This function sets the value of a metrics to monitor the behavior
6666 * of the plugin through tools such as Prometheus. The values of all
6667 * the metrics are stored within the Orthanc context.
6668 *
6669 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6670 * @param name The name of the metrics to be set.
6671 * @param value The value of the metrics.
6672 * @param type The type of the metrics. This parameter is only taken into consideration
6673 * the first time this metrics is set.
6674 * @ingroup Toolbox
6675 **/
6676 ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue(
6677 OrthancPluginContext* context,
6678 const char* name,
6679 float value,
6680 OrthancPluginMetricsType type)
6681 {
6682 _OrthancPluginSetMetricsValue params;
6683 params.name = name;
6684 params.value = value;
6685 params.type = type;
6686 context->InvokeService(context, _OrthancPluginService_SetMetricsValue, &params);
6687 }
6688
6689
6690
6691 typedef struct
6692 {
6693 OrthancPluginRefreshMetricsCallback callback;
6694 } _OrthancPluginRegisterRefreshMetricsCallback;
6695
6696 /**
6697 * @brief Register a callback to refresh the metrics.
6698 *
6699 * This function registers a callback to refresh the metrics. The
6700 * callback must make calls to OrthancPluginSetMetricsValue().
6701 *
6702 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6703 * @param callback The callback function to handle the refresh.
6704 * @ingroup Callbacks
6705 **/
6706 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback(
6707 OrthancPluginContext* context,
6708 OrthancPluginRefreshMetricsCallback callback)
6709 {
6710 _OrthancPluginRegisterRefreshMetricsCallback params;
6711 params.callback = callback;
6712 context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, &params);
6713 }
6714
6715
6716
6717
6718 typedef struct
6719 {
6720 char** target;
6721 const void* dicom;
6722 uint32_t dicomSize;
6723 OrthancPluginDicomWebBinaryCallback callback;
6724 } _OrthancPluginEncodeDicomWeb;
6725
6726 /**
6727 * @brief Convert a DICOM instance to DICOMweb JSON.
6728 *
6729 * This function converts a memory buffer containing a DICOM instance,
6730 * into its DICOMweb JSON representation.
6731 *
6732 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6733 * @param dicom Pointer to the DICOM instance.
6734 * @param dicomSize Size of the DICOM instance.
6735 * @param callback Callback to set the value of the binary tags.
6736 * @see OrthancPluginCreateDicom()
6737 * @return The NULL value in case of error, or the JSON document. This string must
6738 * be freed by OrthancPluginFreeString().
6739 * @ingroup Toolbox
6740 **/
6741 ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson(
6742 OrthancPluginContext* context,
6743 const void* dicom,
6744 uint32_t dicomSize,
6745 OrthancPluginDicomWebBinaryCallback callback)
6746 {
6747 char* target = NULL;
6748
6749 _OrthancPluginEncodeDicomWeb params;
6750 params.target = &target;
6751 params.dicom = dicom;
6752 params.dicomSize = dicomSize;
6753 params.callback = callback;
6754
6755 if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, &params) != OrthancPluginErrorCode_Success)
6756 {
6757 /* Error */
6758 return NULL;
6759 }
6760 else
6761 {
6762 return target;
6763 }
6764 }
6765
6766
6767 /**
6768 * @brief Convert a DICOM instance to DICOMweb XML.
6769 *
6770 * This function converts a memory buffer containing a DICOM instance,
6771 * into its DICOMweb XML representation.
6772 *
6773 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6774 * @param dicom Pointer to the DICOM instance.
6775 * @param dicomSize Size of the DICOM instance.
6776 * @param callback Callback to set the value of the binary tags.
6777 * @return The NULL value in case of error, or the JSON document. This string must
6778 * be freed by OrthancPluginFreeString().
6779 * @see OrthancPluginCreateDicom()
6780 * @ingroup Toolbox
6781 **/
6782 ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml(
6783 OrthancPluginContext* context,
6784 const void* dicom,
6785 uint32_t dicomSize,
6786 OrthancPluginDicomWebBinaryCallback callback)
6787 {
6788 char* target = NULL;
6789
6790 _OrthancPluginEncodeDicomWeb params;
6791 params.target = &target;
6792 params.dicom = dicom;
6793 params.dicomSize = dicomSize;
6794 params.callback = callback;
6795
6796 if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, &params) != OrthancPluginErrorCode_Success)
6797 {
6798 /* Error */
6799 return NULL;
6800 }
6801 else
6802 {
6803 return target;
6804 }
6805 }
6806
6807
6808
6809 /**
6810 * @brief Callback executed when a HTTP header is received during a chunked transfer.
6811 *
6812 * Signature of a callback function that is called by Orthanc acting
6813 * as a HTTP client during a chunked HTTP transfer, as soon as it
6814 * receives one HTTP header from the answer of the remote HTTP
6815 * server.
6816 *
6817 * @see OrthancPluginChunkedHttpClient()
6818 * @param answer The user payload, as provided by the calling plugin.
6819 * @param key The key of the HTTP header.
6820 * @param value The value of the HTTP header.
6821 * @return 0 if success, or the error code if failure.
6822 * @ingroup Toolbox
6823 **/
6824 typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) (
6825 void* answer,
6826 const char* key,
6827 const char* value);
6828
6829
6830 /**
6831 * @brief Callback executed when an answer chunk is received during a chunked transfer.
6832 *
6833 * Signature of a callback function that is called by Orthanc acting
6834 * as a HTTP client during a chunked HTTP transfer, as soon as it
6835 * receives one data chunk from the answer of the remote HTTP
6836 * server.
6837 *
6838 * @see OrthancPluginChunkedHttpClient()
6839 * @param answer The user payload, as provided by the calling plugin.
6840 * @param data The content of the data chunk.
6841 * @param size The size of the data chunk.
6842 * @return 0 if success, or the error code if failure.
6843 * @ingroup Toolbox
6844 **/
6845 typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) (
6846 void* answer,
6847 const void* data,
6848 uint32_t size);
6849
6850
6851 /**
6852 * @brief Callback to know whether the request body is entirely read during a chunked transfer
6853 *
6854 * Signature of a callback function that is called by Orthanc acting
6855 * as a HTTP client during a chunked HTTP transfer, while reading
6856 * the body of a POST or PUT request. The plugin must answer "1" as
6857 * soon as the body is entirely read: The "request" data structure
6858 * must act as an iterator.
6859 *
6860 * @see OrthancPluginChunkedHttpClient()
6861 * @param request The user payload, as provided by the calling plugin.
6862 * @return "1" if the body is over, or "0" if there is still data to be read.
6863 * @ingroup Toolbox
6864 **/
6865 typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request);
6866
6867
6868 /**
6869 * @brief Callback to advance in the request body during a chunked transfer
6870 *
6871 * Signature of a callback function that is called by Orthanc acting
6872 * as a HTTP client during a chunked HTTP transfer, while reading
6873 * the body of a POST or PUT request. This function asks the plugin
6874 * to advance to the next chunk of data of the request body: The
6875 * "request" data structure must act as an iterator.
6876 *
6877 * @see OrthancPluginChunkedHttpClient()
6878 * @param request The user payload, as provided by the calling plugin.
6879 * @return 0 if success, or the error code if failure.
6880 * @ingroup Toolbox
6881 **/
6882 typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request);
6883
6884
6885 /**
6886 * @brief Callback to read the current chunk of the request body during a chunked transfer
6887 *
6888 * Signature of a callback function that is called by Orthanc acting
6889 * as a HTTP client during a chunked HTTP transfer, while reading
6890 * the body of a POST or PUT request. The plugin must provide the
6891 * content of the current chunk of data of the request body.
6892 *
6893 * @see OrthancPluginChunkedHttpClient()
6894 * @param request The user payload, as provided by the calling plugin.
6895 * @return The content of the current request chunk.
6896 * @ingroup Toolbox
6897 **/
6898 typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request);
6899
6900
6901 /**
6902 * @brief Callback to read the size of the current request chunk during a chunked transfer
6903 *
6904 * Signature of a callback function that is called by Orthanc acting
6905 * as a HTTP client during a chunked HTTP transfer, while reading
6906 * the body of a POST or PUT request. The plugin must provide the
6907 * size of the current chunk of data of the request body.
6908 *
6909 * @see OrthancPluginChunkedHttpClient()
6910 * @param request The user payload, as provided by the calling plugin.
6911 * @return The size of the current request chunk.
6912 * @ingroup Toolbox
6913 **/
6914 typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request);
6915
6916
6917 typedef struct
6918 {
6919 void* answer;
6920 OrthancPluginChunkedClientAnswerAddChunk answerAddChunk;
6921 OrthancPluginChunkedClientAnswerAddHeader answerAddHeader;
6922 uint16_t* httpStatus;
6923 OrthancPluginHttpMethod method;
6924 const char* url;
6925 uint32_t headersCount;
6926 const char* const* headersKeys;
6927 const char* const* headersValues;
6928 void* request;
6929 OrthancPluginChunkedClientRequestIsDone requestIsDone;
6930 OrthancPluginChunkedClientRequestGetChunkData requestChunkData;
6931 OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize;
6932 OrthancPluginChunkedClientRequestNext requestNext;
6933 const char* username;
6934 const char* password;
6935 uint32_t timeout;
6936 const char* certificateFile;
6937 const char* certificateKeyFile;
6938 const char* certificateKeyPassword;
6939 uint8_t pkcs11;
6940 } _OrthancPluginChunkedHttpClient;
6941
6942
6943 /**
6944 * @brief Issue a HTTP call, using chunked HTTP transfers.
6945 *
6946 * Make a HTTP call to the given URL using chunked HTTP
6947 * transfers. The request body is provided as an iterator over data
6948 * chunks. The answer is provided as a sequence of function calls
6949 * with the individual HTTP headers and answer chunks.
6950 *
6951 * Contrarily to OrthancPluginHttpClient() that entirely stores the
6952 * request body and the answer body in memory buffers, this function
6953 * uses chunked HTTP transfers. This results in a lower memory
6954 * consumption. Pay attention to the fact that Orthanc servers with
6955 * version <= 1.5.6 do not support chunked transfers: You must use
6956 * OrthancPluginHttpClient() if contacting such older servers.
6957 *
6958 * The HTTP request will be done accordingly to the global
6959 * configuration of Orthanc (in particular, the options "HttpProxy",
6960 * "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and
6961 * "Pkcs11" will be taken into account).
6962 *
6963 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
6964 * @param answer The user payload for the answer body. It will be provided to the callbacks for the answer.
6965 * @param answerAddChunk Callback function to report a data chunk from the answer body.
6966 * @param answerAddHeader Callback function to report an HTTP header sent by the remote server.
6967 * @param httpStatus The HTTP status after the execution of the request (out argument).
6968 * @param method HTTP method to be used.
6969 * @param url The URL of interest.
6970 * @param headersCount The number of HTTP headers.
6971 * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
6972 * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
6973 * @param request The user payload containing the request body, and acting as an iterator.
6974 * It will be provided to the callbacks for the request.
6975 * @param requestIsDone Callback function to tell whether the request body is entirely read.
6976 * @param requestChunkData Callback function to get the content of the current data chunk of the request body.
6977 * @param requestChunkSize Callback function to get the size of the current data chunk of the request body.
6978 * @param requestNext Callback function to advance to the next data chunk of the request body.
6979 * @param username The username (can be <tt>NULL</tt> if no password protection).
6980 * @param password The password (can be <tt>NULL</tt> if no password protection).
6981 * @param timeout Timeout in seconds (0 for default timeout).
6982 * @param certificateFile Path to the client certificate for HTTPS, in PEM format
6983 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
6984 * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
6985 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
6986 * @param certificateKeyPassword Password to unlock the key of the client certificate
6987 * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
6988 * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
6989 * @return 0 if success, or the error code if failure.
6990 * @see OrthancPluginHttpClient()
6991 * @ingroup Toolbox
6992 **/
6993 ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginChunkedHttpClient(
6994 OrthancPluginContext* context,
6995 void* answer,
6996 OrthancPluginChunkedClientAnswerAddChunk answerAddChunk,
6997 OrthancPluginChunkedClientAnswerAddHeader answerAddHeader,
6998 uint16_t* httpStatus,
6999 OrthancPluginHttpMethod method,
7000 const char* url,
7001 uint32_t headersCount,
7002 const char* const* headersKeys,
7003 const char* const* headersValues,
7004 void* request,
7005 OrthancPluginChunkedClientRequestIsDone requestIsDone,
7006 OrthancPluginChunkedClientRequestGetChunkData requestChunkData,
7007 OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize,
7008 OrthancPluginChunkedClientRequestNext requestNext,
7009 const char* username,
7010 const char* password,
7011 uint32_t timeout,
7012 const char* certificateFile,
7013 const char* certificateKeyFile,
7014 const char* certificateKeyPassword,
7015 uint8_t pkcs11)
7016 {
7017 _OrthancPluginChunkedHttpClient params;
7018 memset(&params, 0, sizeof(params));
7019
7020 /* In common with OrthancPluginHttpClient() */
7021 params.httpStatus = httpStatus;
7022 params.method = method;
7023 params.url = url;
7024 params.headersCount = headersCount;
7025 params.headersKeys = headersKeys;
7026 params.headersValues = headersValues;
7027 params.username = username;
7028 params.password = password;
7029 params.timeout = timeout;
7030 params.certificateFile = certificateFile;
7031 params.certificateKeyFile = certificateKeyFile;
7032 params.certificateKeyPassword = certificateKeyPassword;
7033 params.pkcs11 = pkcs11;
7034
7035 /* For chunked body/answer */
7036 params.answer = answer;
7037 params.answerAddChunk = answerAddChunk;
7038 params.answerAddHeader = answerAddHeader;
7039 params.request = request;
7040 params.requestIsDone = requestIsDone;
7041 params.requestChunkData = requestChunkData;
7042 params.requestChunkSize = requestChunkSize;
7043 params.requestNext = requestNext;
7044
7045 return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, &params);
7046 }
7047
7048
7049
7050 /**
7051 * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer.
7052 * @ingroup Callback
7053 **/
7054 typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader;
7055
7056
7057
7058 /**
7059 * @brief Callback to create a reader to handle incoming chunked HTTP transfers.
7060 *
7061 * Signature of a callback function that is called by Orthanc acting
7062 * as a HTTP server that supports chunked HTTP transfers. This
7063 * callback is only invoked if the HTTP method is POST or PUT. The
7064 * callback must create an user-specific "reader" object that will
7065 * be fed with the body of the incoming body.
7066 *
7067 * @see OrthancPluginRegisterChunkedRestCallback()
7068 * @param reader Memory location that must be filled with the newly-created reader.
7069 * @param url The URI that is accessed.
7070 * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used.
7071 * @return 0 if success, or the error code if failure.
7072 **/
7073 typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) (
7074 OrthancPluginServerChunkedRequestReader** reader,
7075 const char* url,
7076 const OrthancPluginHttpRequest* request);
7077
7078
7079 /**
7080 * @brief Callback invoked whenever a new data chunk is available during a chunked transfer.
7081 *
7082 * Signature of a callback function that is called by Orthanc acting
7083 * as a HTTP server that supports chunked HTTP transfers. This callback
7084 * is invoked as soon as a new data chunk is available for the request body.
7085 *
7086 * @see OrthancPluginRegisterChunkedRestCallback()
7087 * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
7088 * @param data The content of the data chunk.
7089 * @param size The size of the data chunk.
7090 * @return 0 if success, or the error code if failure.
7091 **/
7092 typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) (
7093 OrthancPluginServerChunkedRequestReader* reader,
7094 const void* data,
7095 uint32_t size);
7096
7097
7098 /**
7099 * @brief Callback invoked whenever the request body is entirely received.
7100 *
7101 * Signature of a callback function that is called by Orthanc acting
7102 * as a HTTP server that supports chunked HTTP transfers. This
7103 * callback is invoked as soon as the full body of the HTTP request
7104 * is available. The plugin can then send its answer thanks to the
7105 * provided "output" object.
7106 *
7107 * @see OrthancPluginRegisterChunkedRestCallback()
7108 * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
7109 * @param output The HTTP connection to the client application.
7110 * @return 0 if success, or the error code if failure.
7111 **/
7112 typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) (
7113 OrthancPluginServerChunkedRequestReader* reader,
7114 OrthancPluginRestOutput* output);
7115
7116
7117 /**
7118 * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer.
7119 *
7120 * Signature of a callback function that is called by Orthanc acting
7121 * as a HTTP server that supports chunked HTTP transfers. This
7122 * callback is invoked to release all the resources allocated by the
7123 * given reader. Note that this function might be invoked even if
7124 * the entire body was not read, to deal with client error or
7125 * disconnection.
7126 *
7127 * @see OrthancPluginRegisterChunkedRestCallback()
7128 * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback.
7129 **/
7130 typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) (
7131 OrthancPluginServerChunkedRequestReader* reader);
7132
7133 typedef struct
7134 {
7135 const char* pathRegularExpression;
7136 OrthancPluginRestCallback getHandler;
7137 OrthancPluginServerChunkedRequestReaderFactory postHandler;
7138 OrthancPluginRestCallback deleteHandler;
7139 OrthancPluginServerChunkedRequestReaderFactory putHandler;
7140 OrthancPluginServerChunkedRequestReaderAddChunk addChunk;
7141 OrthancPluginServerChunkedRequestReaderExecute execute;
7142 OrthancPluginServerChunkedRequestReaderFinalize finalize;
7143 } _OrthancPluginChunkedRestCallback;
7144
7145
7146 /**
7147 * @brief Register a REST callback to handle chunked HTTP transfers.
7148 *
7149 * This function registers a REST callback against a regular
7150 * expression for a URI. This function must be called during the
7151 * initialization of the plugin, i.e. inside the
7152 * OrthancPluginInitialize() public function.
7153 *
7154 * Contrarily to OrthancPluginRegisterRestCallback(), the callbacks
7155 * will NOT be invoked in mutual exclusion, so it is up to the
7156 * plugin to implement the required locking mechanisms.
7157 *
7158 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
7159 * @param pathRegularExpression Regular expression for the URI. May contain groups.
7160 * @param getHandler The callback function to handle REST calls using the GET HTTP method.
7161 * @param postHandler The callback function to handle REST calls using the GET POST method.
7162 * @param deleteHandler The callback function to handle REST calls using the GET DELETE method.
7163 * @param putHandler The callback function to handle REST calls using the GET PUT method.
7164 * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call.
7165 * @param execute The callback invoked once the entire body of a POST or PUT call is read.
7166 * @param finalize The callback invoked to release the resources associated with a POST or PUT call.
7167 * @see OrthancPluginRegisterRestCallbackNoLock()
7168 *
7169 * @note
7170 * The regular expression is case sensitive and must follow the
7171 * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html).
7172 *
7173 * @ingroup Callbacks
7174 **/
7175 ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback(
7176 OrthancPluginContext* context,
7177 const char* pathRegularExpression,
7178 OrthancPluginRestCallback getHandler,
7179 OrthancPluginServerChunkedRequestReaderFactory postHandler,
7180 OrthancPluginRestCallback deleteHandler,
7181 OrthancPluginServerChunkedRequestReaderFactory putHandler,
7182 OrthancPluginServerChunkedRequestReaderAddChunk addChunk,
7183 OrthancPluginServerChunkedRequestReaderExecute execute,
7184 OrthancPluginServerChunkedRequestReaderFinalize finalize)
7185 {
7186 _OrthancPluginChunkedRestCallback params;
7187 params.pathRegularExpression = pathRegularExpression;
7188 params.getHandler = getHandler;
7189 params.postHandler = postHandler;
7190 params.deleteHandler = deleteHandler;
7191 params.putHandler = putHandler;
7192 params.addChunk = addChunk;
7193 params.execute = execute;
7194 params.finalize = finalize;
7195
7196 context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, &params);
7197 }
7198
7199
7200
7201
7202
7203 typedef struct
7204 {
7205 char** result;
7206 uint16_t group;
7207 uint16_t element;
7208 const char* privateCreator;
7209 } _OrthancPluginGetTagName;
7210
7211 /**
7212 * @brief Returns the symbolic name of a DICOM tag.
7213 *
7214 * This function makes a lookup to the dictionary of DICOM tags that
7215 * are known to Orthanc, and returns the symbolic name of a DICOM tag.
7216 *
7217 * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
7218 * @param group The group of the tag.
7219 * @param element The element of the tag.
7220 * @param privateCreator For private tags, the name of the private creator (can be NULL).
7221 * @return NULL in the case of an error, or a newly allocated string
7222 * containing the path. This string must be freed by
7223 * OrthancPluginFreeString().
7224 * @ingroup Toolbox
7225 **/
7226 ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName(
7227 OrthancPluginContext* context,
7228 uint16_t group,
7229 uint16_t element,
7230 const char* privateCreator)
7231 {
7232 char* result;
7233
7234 _OrthancPluginGetTagName params;
7235 params.result = &result;
7236 params.group = group;
7237 params.element = element;
7238 params.privateCreator = privateCreator;
7239
7240 if (context->InvokeService(context, _OrthancPluginService_GetTagName, &params) != OrthancPluginErrorCode_Success)
7241 {
7242 /* Error */
7243 return NULL;
7244 }
7245 else
7246 {
7247 return result;
7248 }
7249 }
7250
7251
7252
7253 #ifdef __cplusplus
7254 }
7255 #endif
7256
7257
7258 /** @} */
7259
0 This is a sample configuration file for nginx to test the DICOMweb
1 plugin behind a HTTP proxy. To start the proxy as a regular user:
2
3 $ nginx -c ./nginx.local.conf -p $PWD
4
5
6 References about "Forwarded" header in nginx:
7 https://onefeed.xyz/posts/x-forwarded-for-vs-x-real-ip.html
8 https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
0 worker_processes 1;
1 error_log stderr;
2 daemon off;
3 pid nginx.pid;
4
5 # `events` section is mandatory
6 events {
7 worker_connections 1024; # Default: 1024
8 }
9
10 http {
11 # prevent nginx sync issues on OSX
12 proxy_buffering off;
13 access_log off;
14
15 server {
16 listen 9977 default_server;
17 client_max_body_size 4G;
18
19 # location may have to be adjusted depending on your OS and nginx install
20 include /etc/nginx/mime.types;
21
22 # if not in your system mime.types, add this line to support WASM:
23 # types {
24 # application/wasm wasm;
25 # }
26
27 # reverse proxy orthanc
28 location /orthanc/ {
29 rewrite /orthanc(.*) $1 break;
30 proxy_pass http://127.0.0.1:8042;
31 proxy_set_header Host $http_host;
32 proxy_set_header my-auth-header good-token;
33 #proxy_request_buffering off;
34 #proxy_max_temp_file_size 0;
35 #client_max_body_size 0;
36 }
37 }
38 }
3030 import json
3131 import uuid
3232
33 #if len(sys.argv) < 2:
34 if len(sys.argv) < 1:
33 if len(sys.argv) < 2:
3534 print('Usage: %s <StowUri> <file>...' % sys.argv[0])
3635 print('')
3736 print('Example: %s http://localhost:8042/dicom-web/studies hello.dcm world.dcm' % sys.argv[0])
5857 # Closing boundary
5958 body += bytearray('--%s--' % boundary, 'ascii')
6059
61 # Do the HTTP POST request to the STOW-RS server
62 r = requests.post(URL, data=body, headers= {
60 headers = {
6361 'Content-Type' : 'multipart/related; type=application/dicom; boundary=%s' % boundary,
6462 'Accept' : 'application/json',
65 })
63 }
64
65 # Do the HTTP POST request to the STOW-RS server
66 if False:
67 # Don't use chunked transfer (this code was in use in DICOMweb plugin <= 0.6)
68 r = requests.post(URL, data=body, headers=headers)
69 else:
70 # Use chunked transfer
71 # https://2.python-requests.org/en/master/user/advanced/#chunk-encoded-requests
72 def gen():
73 chunkSize = 1024 * 1024
74
75 l = len(body) / chunkSize
76 for i in range(l):
77 pos = i * chunkSize
78 yield body[pos : pos + chunkSize]
79
80 if len(body) % chunkSize != 0:
81 yield body[l * chunkSize :]
82
83 r = requests.post(URL, data=gen(), headers=headers)
84
6685
6786 j = json.loads(r.text)
6887
1010 import urllib2
1111
1212 TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
13 PLUGIN_SDK_VERSION = '1.5.4'
13 PLUGIN_SDK_VERSION = '1.5.7'
1414 REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
1515
1616 FILES = [
0 Reference: http://medical.nema.org/medical/dicom/current/output/html/part18.html
0 Reference: http://dicom.nema.org/MEDICAL/dicom/2019a/output/html/part18.html
11
22
33
8585 6.5.8 WADO-RS / RetrieveRenderedTransaction
8686 ===========================================
8787
88 Not supported yet.
88 http://dicom.nema.org/MEDICAL/dicom/2019a/output/chtml/part18/sect_6.5.8.html
89
90 Supported
91 ---------
92
93 * Single-frame and multi-frame retrieval
94 * JPEG and PNG output
95
96 Not supported
97 -------------
98
99 * GIF output
100 * None of the "Retrieve Rendered Query Parameters" (table 6.5.8-2)
89101
90102
91103
2020
2121 #include <gtest/gtest.h>
2222 #include <boost/lexical_cast.hpp>
23 #include <boost/algorithm/string/predicate.hpp>
2324
2425 #include "../Plugin/Configuration.h"
2526
2829 OrthancPluginContext* context_ = NULL;
2930
3031
32 // TODO => Remove this test (now in Orthanc core)
3133 TEST(ContentType, Parse)
3234 {
3335 std::string c;
0 var DICOM_TAG_ACCESSION_NUMBER = '00080050';
1 var DICOM_TAG_MODALITY = '00080060';
2 var DICOM_TAG_PATIENT_ID = '00100020';
3 var DICOM_TAG_PATIENT_NAME = '00100010';
4 var DICOM_TAG_SERIES_DESCRIPTION = '0008103E';
5 var DICOM_TAG_SERIES_INSTANCE_UID = '0020000E';
6 var DICOM_TAG_SOP_INSTANCE_UID = '00080018';
7 var DICOM_TAG_STUDY_DATE = '00080020';
8 var DICOM_TAG_STUDY_ID = '00200010';
9 var DICOM_TAG_STUDY_INSTANCE_UID = '0020000D';
10 var MAX_RESULTS = 100;
11
12 /**
13 * This is a minimal 1x1 PNG image with white background, as generated by:
14 * $ convert -size 1x1 -define png:include-chunk=none xc:white png:- | base64 -w 0
15 **/
16 var DEFAULT_PREVIEW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg==';
17
18 var app = new Vue({
19 el: '#app',
20 computed: {
21 studiesCount() {
22 return this.studies.length
23 },
24 seriesCount() {
25 return this.series.length
26 }
27 },
28 data: {
29 orthancApiRoot: '../../../',
30 previewFailure: true,
31 preview: DEFAULT_PREVIEW,
32 showTruncatedStudies: false,
33 showNoServer: false,
34 showStudies: false,
35 showSeries: false,
36 maxResults: MAX_RESULTS,
37 currentPage: 0,
38 perPage: 10,
39 servers: [ ],
40 serversInfo: { },
41 activeServer: '',
42 lookup: { },
43 studies: [ ],
44 currentStudy: null,
45 jobId: '',
46 jobLevel: '',
47 jobUri: '',
48 jobDetails: '',
49 studiesFields: [
50 {
51 key: DICOM_TAG_PATIENT_ID + '.Value',
52 label: 'Patient ID',
53 sortable: true
54 },
55 {
56 key: DICOM_TAG_PATIENT_NAME + '.Value',
57 label: 'Patient name',
58 sortable: true
59 },
60 {
61 key: DICOM_TAG_ACCESSION_NUMBER + '.Value',
62 label: 'Accession number',
63 sortable: true
64 },
65 {
66 key: DICOM_TAG_STUDY_DATE + '.Value',
67 label: 'Study date',
68 sortable: true
69 },
70 {
71 key: 'operations',
72 label: ''
73 }
74 ],
75 studyToDelete: null,
76 studyTags: [ ],
77 studyTagsFields: [
78 {
79 key: 'Tag',
80 sortable: true
81 },
82 {
83 key: 'Name',
84 label: 'Description',
85 sortable: true
86 },
87 {
88 key: 'Value',
89 sortable: true
90 }
91 ],
92 series: [ ],
93 seriesFields: [
94 {
95 key: DICOM_TAG_SERIES_DESCRIPTION + '.Value',
96 label: 'Series description',
97 sortable: true
98 },
99 {
100 key: DICOM_TAG_MODALITY + '.Value',
101 label: 'Modality',
102 sortable: true
103 },
104 {
105 key: 'operations',
106 label: ''
107 }
108 ],
109 seriesToDelete: null,
110 seriesTags: [ ],
111 seriesTagsFields: [
112 {
113 key: 'Tag',
114 sortable: true
115 },
116 {
117 key: 'Name',
118 label: 'Description',
119 sortable: true
120 },
121 {
122 key: 'Value',
123 sortable: true
124 }
125 ],
126 scrollToSeries: false,
127 scrollToStudies: false
128 },
129 mounted: () => {
130 axios
131 .get('../../servers?expand')
132 .then(response => {
133 app.serversInfo = response.data;
134 app.servers = Object.keys(response.data).map(i => i);
135 app.Clear();
136 });
137 axios
138 .get('../../info')
139 .then(response => {
140 app.orthancApiRoot = response.data.OrthancApiRoot;
141 if (!app.orthancApiRoot.endsWith('/')) {
142 app.orthancApiRoot += '/';
143 }
144 app.orthancApiRoot += '../../'; // To be at the same level as "info"
145 });
146 },
147 methods: {
148 /**
149 * Toolbox
150 **/
151
152 ScrollToRef: function(refName) {
153 var element = app.$refs[refName];
154 window.scrollTo(0, element.offsetTop);
155 },
156 ShowErrorModal: function() {
157 app.$refs['modal-error'].show();
158 },
159 RefreshJobDetails: function() {
160 axios
161 .get(app.jobUri)
162 .then(response => {
163 app.jobDetails = response.data;
164 })
165 .catch(response => {
166 app.jobDetails = 'Job details are not available';
167 })
168 },
169
170
171 /**
172 * Studies
173 **/
174
175 SetStudies: function(response) {
176 if (response.data.length > app.maxResults) {
177 app.showTruncatedStudies = true;
178 app.studies = response.data.splice(0, app.maxResults);
179 } else {
180 app.showTruncatedStudies = false;
181 app.studies = response.data;
182 }
183 app.showStudies = true;
184 app.showSeries = false;
185 app.studyToDelete = null;
186 app.scrollToStudies = true;
187 },
188 ExecuteLookup: function() {
189 var args = {
190 'fuzzymatching' : 'true',
191 'limit' : (app.maxResults + 1).toString()
192 };
193
194 if ('patientName' in app.lookup) {
195 args[DICOM_TAG_PATIENT_NAME] = app.lookup.patientName;
196 }
197
198 if ('patientID' in app.lookup) {
199 args[DICOM_TAG_PATIENT_ID] = app.lookup.patientID;
200 }
201
202 if ('studyDate' in app.lookup) {
203 args[DICOM_TAG_STUDY_DATE] = app.lookup.studyDate;
204 }
205
206 if ('accessionNumber' in app.lookup) {
207 args[DICOM_TAG_ACCESSION_NUMBER] = app.lookup.accessionNumber;
208 }
209
210 app.activeServer = app.lookup.server;
211 axios
212 .post('../../servers/' + app.activeServer + '/qido', {
213 'Uri' : '/studies',
214 'Arguments' : args,
215 })
216 .then(app.SetStudies)
217 .catch(response => {
218 app.showStudies = false;
219 app.showSeries = false;
220 app.ShowErrorModal();
221 });
222 },
223 Clear: function() {
224 app.lookup = {};
225 currentStudy = null;
226 app.showSeries = false;
227 app.showStudies = false;
228 if (app.servers.length == 0) {
229 app.showNoServer = true;
230 } else {
231 app.showNoServer = false;
232 app.lookup.server = app.servers[0];
233 }
234 },
235 OnLookup: function(event) {
236 event.preventDefault();
237 app.ExecuteLookup();
238 },
239 OnReset: function(event) {
240 event.preventDefault();
241 app.Clear();
242 },
243 OpenStudyDetails: function(study) {
244 app.studyTags = Object.keys(study).map(i => {
245 var item = study[i];
246 item['Tag'] = i;
247 return item;
248 });
249
250 app.$refs['study-details'].show();
251 },
252 RetrieveStudy: function(study) {
253 var base = '../../servers/';
254 axios
255 .post(base + app.activeServer + '/wado', {
256 'Uri' : '/studies/' + study[DICOM_TAG_STUDY_INSTANCE_UID].Value
257 })
258 .then(response => {
259 app.jobLevel = 'study';
260 app.jobId = response.data.ID;
261 app.jobUri = base + response.data.Path;
262 app.$refs['retrieve-job'].show();
263 app.RefreshJobDetails();
264 });
265 },
266 ConfirmDeleteStudy: function(study) {
267 app.studyToDelete = study;
268 app.$bvModal.show('study-delete-confirm');
269 },
270 ExecuteDeleteStudy: function(study) {
271 axios
272 .post('../../servers/' + app.activeServer + '/delete', {
273 'Level': 'Study',
274 'StudyInstanceUID': app.studyToDelete[DICOM_TAG_STUDY_INSTANCE_UID].Value
275 })
276 .then(app.ExecuteLookup)
277 .catch(app.ShowErrorModal)
278 },
279
280
281 /**
282 * Series
283 **/
284
285 LoadSeriesOfCurrentStudy: function() {
286 axios
287 .post('../../servers/' + app.activeServer + '/qido', {
288 'Uri' : '/studies/' + app.currentStudy + '/series'
289 })
290 .then(response => {
291 if (response.data.length > 0) {
292 app.series = response.data;
293 app.showSeries = true;
294 app.seriesToDelete = null;
295 app.scrollToSeries = true;
296 } else {
297 // No more series, so no more study, so re-lookup
298 app.ExecuteLookup();
299 }
300 })
301 .catch(app.ShowErrorModal);
302 },
303 OpenSeries: function(series) {
304 app.currentStudy = series[DICOM_TAG_STUDY_INSTANCE_UID].Value;
305 app.LoadSeriesOfCurrentStudy();
306 },
307 OpenSeriesDetails: function(series) {
308 app.seriesTags = Object.keys(series).map(i => {
309 var item = series[i];
310 item['Tag'] = i;
311 return item;
312 });
313
314 app.$refs['series-details'].show();
315 },
316 RetrieveSeries: function(series) {
317 var base = '../../servers/';
318 axios
319 .post(base + app.activeServer + '/wado', {
320 'Uri' : ('/studies/' + app.currentStudy +
321 '/series/' + series[DICOM_TAG_SERIES_INSTANCE_UID].Value)
322 })
323 .then(response => {
324 app.jobLevel = 'series';
325 app.jobId = response.data.ID;
326 app.jobUri = base + response.data.Path;
327 app.$refs['retrieve-job'].show();
328 app.RefreshJobDetails();
329 });
330 },
331 OpenSeriesPreview: function(series) {
332 axios
333 .post('../../servers/' + app.activeServer + '/get', {
334 'Uri' : ('/studies/' + app.currentStudy + '/series/' +
335 series[DICOM_TAG_SERIES_INSTANCE_UID].Value + '/instances')
336 })
337 .then(response => {
338 var instance = response.data[Math.floor(response.data.length / 2)];
339
340 axios
341 .post('../../servers/' + app.activeServer + '/get', {
342 'Uri' : ('/studies/' + app.currentStudy + '/series/' +
343 series[DICOM_TAG_SERIES_INSTANCE_UID].Value + '/instances/' +
344 instance[DICOM_TAG_SOP_INSTANCE_UID].Value + '/rendered')
345 }, {
346 responseType: 'arraybuffer'
347 })
348 .then(response => {
349 // https://github.com/axios/axios/issues/513
350 var image = btoa(new Uint8Array(response.data)
351 .reduce((data, byte) => data + String.fromCharCode(byte), ''));
352 app.preview = ("data:" +
353 response.headers['content-type'].toLowerCase() +
354 ";base64," + image);
355 app.previewFailure = false;
356 })
357 .catch(response => {
358 app.previewFailure = true;
359 })
360 .finally(function() {
361 app.$refs['series-preview'].show();
362 })
363 })
364 },
365 ConfirmDeleteSeries: function(series) {
366 app.seriesToDelete = series;
367 app.$bvModal.show('series-delete-confirm');
368 },
369 ExecuteDeleteSeries: function(series) {
370 axios
371 .post('../../servers/' + app.activeServer + '/delete', {
372 'Level': 'Series',
373 'StudyInstanceUID': app.currentStudy,
374 'SeriesInstanceUID': app.seriesToDelete[DICOM_TAG_SERIES_INSTANCE_UID].Value
375 })
376 .then(app.LoadSeriesOfCurrentStudy)
377 .catch(app.ShowErrorModal)
378 }
379 },
380
381 updated: function () {
382 this.$nextTick(function () {
383 // Code that will run only after the
384 // entire view has been re-rendered
385
386 if (app.scrollToStudies) {
387 app.scrollToStudies = false;
388 app.ScrollToRef('studies-top');
389 }
390
391 if (app.scrollToSeries) {
392 app.scrollToSeries = false;
393 app.ScrollToRef('series-top');
394 }
395 })
396 }
397 });
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <meta charset="utf-8">
4 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
5
6 <title>Orthanc - DICOMweb client</title>
7
8 <!-- Add Bootstrap and Bootstrap-Vue CSS to the <head> section -->
9 <link type="text/css" rel="stylesheet" href="../libs/css/bootstrap.min.css"/>
10 <link type="text/css" rel="stylesheet" href="../libs/css/bootstrap-vue.min.css"/>
11 <link type="text/css" rel="stylesheet" href="../libs/css/font-awesome.min.css"/>
12
13 <script src="../libs/js/polyfill.min.js"></script>
14
15 <!-- CSS style to truncate long text in tables, provided they have
16 class "table-layout:fixed;" or attribute ":fixed=true" -->
17 <style>
18 table td {
19 white-space: nowrap;
20 overflow: hidden;
21 text-overflow: ellipsis;
22 }
23 </style>
24
25 </head>
26 <body>
27 <div class="container" id="app">
28 <p style="height:1em"></p>
29
30 <div class="jumbotron">
31 <div class="row">
32 <div class="col-sm-8">
33 <h1 class="display-4">DICOMweb client</h1>
34 <p class="lead">
35 This is a simple client interface to the DICOMweb
36 servers that are configured in Orthanc. From this page,
37 you can search the content of remote DICOMweb servers
38 (QIDO-RS), then locally retrieve the DICOM
39 studies/series of interest
40 (WADO-RS). <a :href="orthancApiRoot"
41 target="_blank">Orthanc Explorer</a> can be used to send
42 DICOM resources to remote DICOMweb servers (STOW-RS).
43 </p>
44 <p>
45 <a class="btn btn-primary btn-lg"
46 href="https://book.orthanc-server.com/plugins/dicomweb.html"
47 target="_blank" role="button">Open documentation</a>
48 <a class="btn btn-primary btn-lg"
49 :href="orthancApiRoot"
50 target="_blank" role="button">Open Orthanc Explorer</a>
51 </p>
52 </div>
53 <div class="col-sm-4">
54 <a href="http://www.orthanc-server.com/" target="_blank">
55 <img class="img-fluid" alt="Orthanc" src="../libs/img/OrthancLogo.png" />
56 </a>
57 </div>
58 </div>
59 </div>
60
61
62 <b-modal ref="modal-error" size="xl" ok-only="true">
63 <template slot="modal-title">
64 Connection error
65 </template>
66 <div class="d-block">
67 <p>
68 There was an error connecting to "{{ activeServer }}" server.
69 </p>
70 </div>
71 </b-modal>
72
73
74 <!-- LOOKUP -->
75
76 <div class="row">
77 <b-alert variant="danger" dismissible v-model="showNoServer">
78 No DICOMweb server is configured!
79 </b-alert>
80 <b-form style="width:100%;padding:5px;">
81 <b-form-group label="DICOMweb server:" label-cols-sm="4" label-cols-lg="3">
82 <b-form-select v-model="lookup.server" :options="servers"></b-form-select>
83 </b-form-group>
84 <b-form-group label="Patient ID:" label-cols-sm="4" label-cols-lg="3">
85 <b-form-input v-model="lookup.patientID"></b-form-input>
86 </b-form-group>
87 <b-form-group label="Patient name:" label-cols-sm="4" label-cols-lg="3">
88 <b-form-input v-model="lookup.patientName"></b-form-input>
89 </b-form-group>
90 <b-form-group label="Accession number:" label-cols-sm="4" label-cols-lg="3">
91 <b-form-input v-model="lookup.accessionNumber"></b-form-input>
92 </b-form-group>
93 <b-form-group label="Study date:" label-cols-sm="4" label-cols-lg="3">
94 <b-form-input v-model="lookup.studyDate"></b-form-input>
95 </b-form-group>
96 <p class="pull-right">
97 <b-button type="submit" variant="success" @click="OnLookup"
98 size="lg">Do lookup</b-button>
99 <b-button type="reset" variant="outline-danger" @click="OnReset"
100 size="lg">Reset</b-button>
101 </p>
102 </b-form>
103 </div>
104
105
106 <!-- STUDIES -->
107
108 <hr v-show="showStudies" ref="studies-top" />
109 <div class="row" v-show="showStudies">
110 <h1>Studies</h1>
111 </div>
112 <div class="row" v-show="showStudies">
113 <b-alert variant="warning" dismissible v-model="showTruncatedStudies">
114 More than {{ maxResults }} matching studies, results have been truncated!
115 </b-alert>
116 </div>
117 <div class="row" v-show="showStudies">
118 <b-pagination v-model="currentPage" :per-page="perPage" :total-rows="studiesCount"></b-pagination>
119 <b-table striped hover :current-page="currentPage" :per-page="perPage"
120 :items="studies" :fields="studiesFields" :fixed="false">
121 <template slot="operations" slot-scope="data">
122 <b-button @click="OpenSeries(data.item)" title="Open series">
123 <i class="fa fa-folder-open"></i>
124 </b-button>
125 <b-button @click="OpenStudyDetails(data.item)" title="Open tags">
126 <i class="fa fa-address-card"></i>
127 </b-button>
128 <b-button @click="RetrieveStudy(data.item)" title="Retrieve study using WADO-RS">
129 <i class="fa fa-cloud-download"></i>
130 </b-button>
131 <b-button @click="ConfirmDeleteStudy(data.item)"
132 v-if="serversInfo[activeServer].HasDelete == '1'" title="Delete remote study">
133 <i class="fa fa-trash"></i>
134 </b-button>
135 </template>
136 </b-table>
137
138 <b-modal ref="study-details" size="xl" ok-only="true">
139 <template slot="modal-title">
140 Details of study
141 </template>
142 <div class="d-block text-center">
143 <b-table striped :items="studyTags" :fields="studyTagsFields" :fixed="true">
144 </b-table>
145 </div>
146 </b-modal>
147
148 <b-modal id="study-delete-confirm" size="xl" @ok="ExecuteDeleteStudy">
149 <template slot="modal-title">
150 Confirm deletion
151 </template>
152 <div class="d-block">
153 <p>
154 Are you sure you want to remove this study from the remote server?
155 </p>
156 <p>
157 Patient name: {{ studyToDelete && studyToDelete['00100010'] && studyToDelete['00100010'].Value }}
158 </p>
159 </div>
160 </b-modal>
161 </div>
162
163
164 <!-- SERIES -->
165
166 <hr v-show="showSeries" ref="series-top" />
167 <div class="row" v-show="showSeries">
168 <h1>Series</h1>
169 </div>
170 <div class="row" v-show="showSeries">
171 <b-table striped hover :items="series" :fields="seriesFields" :fixed="false">
172 <template slot="operations" slot-scope="data">
173 <b-button @click="OpenSeriesPreview(data.item)" title="Preview">
174 <i class="fa fa-eye"></i>
175 </b-button>
176 <b-button @click="OpenSeriesDetails(data.item)" title="Open tags">
177 <i class="fa fa-address-card"></i>
178 </b-button>
179 <b-button @click="RetrieveSeries(data.item)" title="Retrieve series using WADO-RS">
180 <i class="fa fa-cloud-download"></i>
181 </b-button>
182 <b-button @click="ConfirmDeleteSeries(data.item)"
183 v-if="serversInfo[activeServer].HasDelete" title="Delete remote series">
184 <i class="fa fa-trash"></i>
185 </b-button>
186 </template>
187 </b-table>
188
189 <b-modal ref="series-details" size="xl" ok-only="true">
190 <template slot="modal-title">
191 Details of series
192 </template>
193 <div class="d-block text-center">
194 <b-table striped :items="seriesTags" :fields="seriesTagsFields" :fixed="true">
195 </b-table>
196 </div>
197 </b-modal>
198
199 <b-modal ref="series-preview" size="xl" ok-only="true">
200 <template slot="modal-title">
201 Preview of series
202 </template>
203 <div class="d-block text-center">
204 <b-alert variant="danger" v-model="previewFailure">
205 The remote DICOMweb server cannot generate a preview for this image.
206 </b-alert>
207 <b-img v-if="!previewFailure" :src="preview" fluid alt=""></b-img>
208 </div>
209 </b-modal>
210
211 <b-modal id="series-delete-confirm" size="xl" @ok="ExecuteDeleteSeries">
212 <template slot="modal-title">
213 Confirm deletion
214 </template>
215 <div class="d-block">
216 <p>
217 Are you sure you want to remove this series from the remote server?
218 </p>
219 <p>
220 Series description: {{ seriesToDelete && seriesToDelete['0008103E'] && seriesToDelete['0008103E'].Value }}
221 </p>
222 </div>
223 </b-modal>
224 </div>
225
226
227 <b-modal ref="retrieve-job" size="xl" ok-only="true">
228 <template slot="modal-title">
229 Retrieving {{ jobLevel }}
230 </template>
231 <div class="d-block">
232 <p>
233 Orthanc is now running a background job to retrieve the
234 {{ jobLevel }} from remote server "{{ activeServer }}" using
235 WADO-RS.
236 </p>
237 <p>
238 Job ID: <tt>{{ jobId }}</tt>
239 </p>
240 <p>
241 Job details:
242 </p>
243 <pre>{{ jobDetails }}</pre>
244 <p>
245 <b-button variant="success" @click="RefreshJobDetails()">Refresh job details</b-button>
246 </p>
247 </div>
248 </b-modal>
249
250
251 <p style="height:5em"></p>
252 </div>
253
254 <!-- Add Vue and Bootstrap-Vue JS just before the closing </body> tag -->
255 <script src="../libs/js/vue.min.js"></script>
256 <script src="../libs/js/bootstrap-vue.min.js"></script>
257 <script src="../libs/js/axios.min.js"></script>
258 <script type="text/javascript" src="app.js"></script>
259 </body>
260 </html>