1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include "WadoUri.h"
23
24 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
25 #include "Configuration.h"
26
27 #include <string>
28
29
MapWadoToOrthancIdentifier(std::string & orthanc,char * (* func)(OrthancPluginContext *,const char *),const std::string & dicom)30 static bool MapWadoToOrthancIdentifier(std::string& orthanc,
31 char* (*func) (OrthancPluginContext*, const char*),
32 const std::string& dicom)
33 {
34 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
35
36 char* tmp = func(context, dicom.c_str());
37
38 if (tmp)
39 {
40 orthanc = tmp;
41 OrthancPluginFreeString(context, tmp);
42 return true;
43 }
44 else
45 {
46 return false;
47 }
48 }
49
50
LocateInstanceWadoUri(std::string & instance,std::string & contentType,const OrthancPluginHttpRequest * request)51 static bool LocateInstanceWadoUri(std::string& instance,
52 std::string& contentType,
53 const OrthancPluginHttpRequest* request)
54 {
55 std::string requestType, studyUid, seriesUid, objectUid;
56
57 for (uint32_t i = 0; i < request->getCount; i++)
58 {
59 std::string key(request->getKeys[i]);
60 std::string value(request->getValues[i]);
61
62 if (key == "studyUID")
63 {
64 studyUid = value;
65 }
66 else if (key == "seriesUID")
67 {
68 seriesUid = value;
69 }
70 else if (key == "objectUID") // In WADO-URI, "objectUID" corresponds to "SOPInstanceUID"
71 {
72 objectUid = value;
73 }
74 else if (key == "requestType")
75 {
76 requestType = value;
77 }
78 else if (key == "contentType")
79 {
80 contentType = value;
81 }
82 }
83
84 if (requestType != "WADO")
85 {
86 OrthancPlugins::LogError("WADO-URI: Invalid requestType: \"" + requestType + "\"");
87 return false;
88 }
89
90 if (objectUid.empty())
91 {
92 OrthancPlugins::LogError("WADO-URI: No SOPInstanceUID provided");
93 return false;
94 }
95
96 if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
97 {
98 OrthancPlugins::LogError("WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"");
99 return false;
100 }
101
102 /**
103 * Below are only sanity checks to ensure that the possibly provided
104 * "seriesUID" and "studyUID" match that of the provided instance.
105 **/
106
107 if (!seriesUid.empty())
108 {
109 std::string series;
110 if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
111 {
112 OrthancPlugins::LogError("WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"");
113 return false;
114 }
115 else
116 {
117 Json::Value info;
118 if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/series", false) ||
119 info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
120 {
121 OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
122 return false;
123 }
124 }
125 }
126
127 if (!studyUid.empty())
128 {
129 std::string study;
130 if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
131 {
132 OrthancPlugins::LogError("WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"");
133 return false;
134 }
135 else
136 {
137 Json::Value info;
138 if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/study", false) ||
139 info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
140 {
141 OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
142 return false;
143 }
144 }
145 }
146
147 return true;
148 }
149
150
AnswerDicom(OrthancPluginRestOutput * output,const std::string & instance)151 static void AnswerDicom(OrthancPluginRestOutput* output,
152 const std::string& instance)
153 {
154 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
155
156 std::string uri = "/instances/" + instance + "/file";
157
158 OrthancPlugins::MemoryBuffer dicom;
159 if (dicom.RestApiGet(uri, false))
160 {
161 OrthancPluginAnswerBuffer(context, output,
162 dicom.GetData(), dicom.GetSize(), "application/dicom");
163 }
164 else
165 {
166 throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
167 "WADO-URI: Unable to retrieve DICOM file from " + uri);
168 }
169 }
170
171
AnswerPreview(OrthancPluginRestOutput * output,const std::string & instance,const std::map<std::string,std::string> & httpHeaders)172 static void AnswerPreview(OrthancPluginRestOutput* output,
173 const std::string& instance,
174 const std::map<std::string, std::string>& httpHeaders)
175 {
176 /**
177 * (*) We can use "/rendered" that was introduced in the REST API of
178 * Orthanc 1.6.0, as since release 1.2 of the DICOMweb plugin, the
179 * minimal SDK version is Orthanc 1.7.0 (in order to be able to use
180 * transcoding primitives). In releases <= 1.2, "/preview" was used,
181 * which caused one issue:
182 * https://groups.google.com/d/msg/orthanc-users/mKgr2QAKTCU/R7u4I1LvBAAJ
183 **/
184 const std::string uri = "/instances/" + instance + "/rendered";
185
186 OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
187
188 OrthancPlugins::MemoryBuffer png;
189 if (png.RestApiGet(uri, httpHeaders, true))
190 {
191 OrthancPluginAnswerBuffer(context, output, png.GetData(), png.GetSize(), "image/png");
192 }
193 else
194 {
195 OrthancPlugins::LogError("WADO-URI: Unable to generate a preview image for " + uri);
196 throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
197 }
198 }
199
200
AnswerPngPreview(OrthancPluginRestOutput * output,const std::string & instance)201 static void AnswerPngPreview(OrthancPluginRestOutput* output,
202 const std::string& instance)
203 {
204 std::map<std::string, std::string> httpHeaders;
205 httpHeaders["Accept"] = "image/png";
206 AnswerPreview(output, instance, httpHeaders);
207 }
208
209
AnswerJpegPreview(OrthancPluginRestOutput * output,const std::string & instance)210 static void AnswerJpegPreview(OrthancPluginRestOutput* output,
211 const std::string& instance)
212 {
213 std::map<std::string, std::string> httpHeaders;
214 httpHeaders["Accept"] = "image/jpeg";
215 AnswerPreview(output, instance, httpHeaders);
216 }
217
218
WadoUriCallback(OrthancPluginRestOutput * output,const char * url,const OrthancPluginHttpRequest * request)219 void WadoUriCallback(OrthancPluginRestOutput* output,
220 const char* url,
221 const OrthancPluginHttpRequest* request)
222 {
223 if (request->method != OrthancPluginHttpMethod_Get)
224 {
225 OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
226 return;
227 }
228
229 std::string instance;
230 std::string contentType = "image/jpg"; // By default, JPEG image will be returned
231 if (!LocateInstanceWadoUri(instance, contentType, request))
232 {
233 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
234 }
235
236 if (contentType == "application/dicom")
237 {
238 AnswerDicom(output, instance);
239 }
240 else if (contentType == "image/png")
241 {
242 AnswerPngPreview(output, instance);
243 }
244 else if (contentType == "image/jpeg" ||
245 contentType == "image/jpg")
246 {
247 AnswerJpegPreview(output, instance);
248 }
249 else
250 {
251 throw Orthanc::OrthancException(
252 Orthanc::ErrorCode_BadRequest,
253 "WADO-URI: Unsupported content type: \"" + contentType + "\"");
254 }
255 }
256