Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestApi.cpp @ 750:4afad8cb94fd
moves
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 14 Apr 2014 10:27:53 +0200 |
parents | OrthancServer/OrthancRestApi.cpp@478f4f9de9eb |
children | 5197fd35333c |
comparison
equal
deleted
inserted
replaced
749:b8c49473be38 | 750:4afad8cb94fd |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, | |
4 * Belgium | |
5 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * In addition, as a special exception, the copyright holders of this | |
12 * program give permission to link the code of its release with the | |
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
14 * that use the same license as the "OpenSSL" library), and distribute | |
15 * the linked executables. You must obey the GNU General Public License | |
16 * in all respects for all of the code used other than "OpenSSL". If you | |
17 * modify file(s) with this exception, you may extend this exception to | |
18 * your version of the file(s), but you are not obligated to do so. If | |
19 * you do not wish to do so, delete this exception statement from your | |
20 * version. If you delete this exception statement from all source files | |
21 * in the program, then also delete it here. | |
22 * | |
23 * This program is distributed in the hope that it will be useful, but | |
24 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
26 * General Public License for more details. | |
27 * | |
28 * You should have received a copy of the GNU General Public License | |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
30 **/ | |
31 | |
32 | |
33 #include "OrthancRestApi.h" | |
34 | |
35 #include "../../Core/Compression/HierarchicalZipWriter.h" | |
36 #include "../../Core/HttpClient.h" | |
37 #include "../../Core/HttpServer/FilesystemHttpSender.h" | |
38 #include "../../Core/Uuid.h" | |
39 #include "../DicomProtocol/DicomUserConnection.h" | |
40 #include "../FromDcmtkBridge.h" | |
41 #include "../OrthancInitialization.h" | |
42 #include "../ServerToolbox.h" | |
43 | |
44 #include <dcmtk/dcmdata/dcistrmb.h> | |
45 #include <dcmtk/dcmdata/dcfilefo.h> | |
46 #include <boost/lexical_cast.hpp> | |
47 #include <glog/logging.h> | |
48 | |
49 #if defined(_MSC_VER) | |
50 #define snprintf _snprintf | |
51 #endif | |
52 | |
53 static const uint64_t MEGA_BYTES = 1024 * 1024; | |
54 static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; | |
55 | |
56 | |
57 namespace Orthanc | |
58 { | |
59 // TODO IMPROVE MULTITHREADING | |
60 // Every call to "ParsedDicomFile" must lock this mutex!!! | |
61 static boost::mutex cacheMutex_; | |
62 | |
63 | |
64 // DICOM SCU ---------------------------------------------------------------- | |
65 | |
66 static bool MergeQueryAndTemplate(DicomMap& result, | |
67 const std::string& postData) | |
68 { | |
69 Json::Value query; | |
70 Json::Reader reader; | |
71 | |
72 if (!reader.parse(postData, query) || | |
73 query.type() != Json::objectValue) | |
74 { | |
75 return false; | |
76 } | |
77 | |
78 Json::Value::Members members = query.getMemberNames(); | |
79 for (size_t i = 0; i < members.size(); i++) | |
80 { | |
81 DicomTag t = FromDcmtkBridge::ParseTag(members[i]); | |
82 result.SetValue(t, query[members[i]].asString()); | |
83 } | |
84 | |
85 return true; | |
86 } | |
87 | |
88 static void DicomFindPatient(RestApi::PostCall& call) | |
89 { | |
90 DicomMap m; | |
91 DicomMap::SetupFindPatientTemplate(m); | |
92 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
93 { | |
94 return; | |
95 } | |
96 | |
97 DicomUserConnection connection; | |
98 ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); | |
99 | |
100 DicomFindAnswers answers; | |
101 connection.FindPatient(answers, m); | |
102 | |
103 Json::Value result; | |
104 answers.ToJson(result); | |
105 call.GetOutput().AnswerJson(result); | |
106 } | |
107 | |
108 static void DicomFindStudy(RestApi::PostCall& call) | |
109 { | |
110 DicomMap m; | |
111 DicomMap::SetupFindStudyTemplate(m); | |
112 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
113 { | |
114 return; | |
115 } | |
116 | |
117 if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && | |
118 m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) | |
119 { | |
120 return; | |
121 } | |
122 | |
123 DicomUserConnection connection; | |
124 ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); | |
125 | |
126 DicomFindAnswers answers; | |
127 connection.FindStudy(answers, m); | |
128 | |
129 Json::Value result; | |
130 answers.ToJson(result); | |
131 call.GetOutput().AnswerJson(result); | |
132 } | |
133 | |
134 static void DicomFindSeries(RestApi::PostCall& call) | |
135 { | |
136 DicomMap m; | |
137 DicomMap::SetupFindSeriesTemplate(m); | |
138 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
139 { | |
140 return; | |
141 } | |
142 | |
143 if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && | |
144 m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || | |
145 m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) | |
146 { | |
147 return; | |
148 } | |
149 | |
150 DicomUserConnection connection; | |
151 ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); | |
152 | |
153 DicomFindAnswers answers; | |
154 connection.FindSeries(answers, m); | |
155 | |
156 Json::Value result; | |
157 answers.ToJson(result); | |
158 call.GetOutput().AnswerJson(result); | |
159 } | |
160 | |
161 static void DicomFindInstance(RestApi::PostCall& call) | |
162 { | |
163 DicomMap m; | |
164 DicomMap::SetupFindInstanceTemplate(m); | |
165 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
166 { | |
167 return; | |
168 } | |
169 | |
170 if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && | |
171 m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || | |
172 m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || | |
173 m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) | |
174 { | |
175 return; | |
176 } | |
177 | |
178 DicomUserConnection connection; | |
179 ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); | |
180 | |
181 DicomFindAnswers answers; | |
182 connection.FindInstance(answers, m); | |
183 | |
184 Json::Value result; | |
185 answers.ToJson(result); | |
186 call.GetOutput().AnswerJson(result); | |
187 } | |
188 | |
189 static void DicomFind(RestApi::PostCall& call) | |
190 { | |
191 DicomMap m; | |
192 DicomMap::SetupFindPatientTemplate(m); | |
193 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
194 { | |
195 return; | |
196 } | |
197 | |
198 DicomUserConnection connection; | |
199 ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); | |
200 | |
201 DicomFindAnswers patients; | |
202 connection.FindPatient(patients, m); | |
203 | |
204 // Loop over the found patients | |
205 Json::Value result = Json::arrayValue; | |
206 for (size_t i = 0; i < patients.GetSize(); i++) | |
207 { | |
208 Json::Value patient(Json::objectValue); | |
209 FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); | |
210 | |
211 DicomMap::SetupFindStudyTemplate(m); | |
212 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
213 { | |
214 return; | |
215 } | |
216 m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); | |
217 | |
218 DicomFindAnswers studies; | |
219 connection.FindStudy(studies, m); | |
220 | |
221 patient["Studies"] = Json::arrayValue; | |
222 | |
223 // Loop over the found studies | |
224 for (size_t j = 0; j < studies.GetSize(); j++) | |
225 { | |
226 Json::Value study(Json::objectValue); | |
227 FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); | |
228 | |
229 DicomMap::SetupFindSeriesTemplate(m); | |
230 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
231 { | |
232 return; | |
233 } | |
234 m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); | |
235 m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); | |
236 | |
237 DicomFindAnswers series; | |
238 connection.FindSeries(series, m); | |
239 | |
240 // Loop over the found series | |
241 study["Series"] = Json::arrayValue; | |
242 for (size_t k = 0; k < series.GetSize(); k++) | |
243 { | |
244 Json::Value series2(Json::objectValue); | |
245 FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); | |
246 study["Series"].append(series2); | |
247 } | |
248 | |
249 patient["Studies"].append(study); | |
250 } | |
251 | |
252 result.append(patient); | |
253 } | |
254 | |
255 call.GetOutput().AnswerJson(result); | |
256 } | |
257 | |
258 | |
259 static bool GetInstancesToExport(std::list<std::string>& instances, | |
260 const std::string& remote, | |
261 RestApi::PostCall& call) | |
262 { | |
263 ServerContext& context = OrthancRestApi::GetContext(call); | |
264 | |
265 std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); | |
266 | |
267 Json::Value request; | |
268 if (Toolbox::IsSHA1(stripped)) | |
269 { | |
270 // This is for compatibility with Orthanc <= 0.5.1. | |
271 request = stripped; | |
272 } | |
273 else if (!call.ParseJsonRequest(request)) | |
274 { | |
275 // Bad JSON request | |
276 return false; | |
277 } | |
278 | |
279 if (request.isString()) | |
280 { | |
281 context.GetIndex().LogExportedResource(request.asString(), remote); | |
282 context.GetIndex().GetChildInstances(instances, request.asString()); | |
283 } | |
284 else if (request.isArray()) | |
285 { | |
286 for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) | |
287 { | |
288 if (!request[i].isString()) | |
289 { | |
290 return false; | |
291 } | |
292 | |
293 std::string stripped = Toolbox::StripSpaces(request[i].asString()); | |
294 if (!Toolbox::IsSHA1(stripped)) | |
295 { | |
296 return false; | |
297 } | |
298 | |
299 context.GetIndex().LogExportedResource(stripped, remote); | |
300 | |
301 std::list<std::string> tmp; | |
302 context.GetIndex().GetChildInstances(tmp, stripped); | |
303 | |
304 for (std::list<std::string>::const_iterator | |
305 it = tmp.begin(); it != tmp.end(); ++it) | |
306 { | |
307 instances.push_back(*it); | |
308 } | |
309 } | |
310 } | |
311 else | |
312 { | |
313 // Neither a string, nor a list of strings. Bad request. | |
314 return false; | |
315 } | |
316 | |
317 return true; | |
318 } | |
319 | |
320 | |
321 static void DicomStore(RestApi::PostCall& call) | |
322 { | |
323 ServerContext& context = OrthancRestApi::GetContext(call); | |
324 | |
325 std::string remote = call.GetUriComponent("id", ""); | |
326 | |
327 std::list<std::string> instances; | |
328 if (!GetInstancesToExport(instances, remote, call)) | |
329 { | |
330 return; | |
331 } | |
332 | |
333 DicomUserConnection connection; | |
334 ConnectToModalityUsingSymbolicName(connection, remote); | |
335 | |
336 for (std::list<std::string>::const_iterator | |
337 it = instances.begin(); it != instances.end(); ++it) | |
338 { | |
339 LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\""; | |
340 | |
341 std::string dicom; | |
342 context.ReadFile(dicom, *it, FileContentType_Dicom); | |
343 connection.Store(dicom); | |
344 } | |
345 | |
346 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
347 } | |
348 | |
349 | |
350 | |
351 // System information ------------------------------------------------------- | |
352 | |
353 static void ServeRoot(RestApi::GetCall& call) | |
354 { | |
355 call.GetOutput().Redirect("app/explorer.html"); | |
356 } | |
357 | |
358 static void GetSystemInformation(RestApi::GetCall& call) | |
359 { | |
360 Json::Value result = Json::objectValue; | |
361 | |
362 result["Version"] = ORTHANC_VERSION; | |
363 result["Name"] = GetGlobalStringParameter("Name", ""); | |
364 | |
365 call.GetOutput().AnswerJson(result); | |
366 } | |
367 | |
368 static void GetStatistics(RestApi::GetCall& call) | |
369 { | |
370 Json::Value result = Json::objectValue; | |
371 OrthancRestApi::GetIndex(call).ComputeStatistics(result); | |
372 call.GetOutput().AnswerJson(result); | |
373 } | |
374 | |
375 static void GenerateUid(RestApi::GetCall& call) | |
376 { | |
377 std::string level = call.GetArgument("level", ""); | |
378 if (level == "patient") | |
379 { | |
380 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); | |
381 } | |
382 else if (level == "study") | |
383 { | |
384 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); | |
385 } | |
386 else if (level == "series") | |
387 { | |
388 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); | |
389 } | |
390 else if (level == "instance") | |
391 { | |
392 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); | |
393 } | |
394 } | |
395 | |
396 static void ExecuteScript(RestApi::PostCall& call) | |
397 { | |
398 std::string result; | |
399 ServerContext& context = OrthancRestApi::GetContext(call); | |
400 context.GetLuaContext().Execute(result, call.GetPostBody()); | |
401 call.GetOutput().AnswerBuffer(result, "text/plain"); | |
402 } | |
403 | |
404 static void GetNowIsoString(RestApi::GetCall& call) | |
405 { | |
406 call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); | |
407 } | |
408 | |
409 | |
410 | |
411 | |
412 | |
413 | |
414 // List all the patients, studies, series or instances ---------------------- | |
415 | |
416 template <enum ResourceType resourceType> | |
417 static void ListResources(RestApi::GetCall& call) | |
418 { | |
419 Json::Value result; | |
420 OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); | |
421 call.GetOutput().AnswerJson(result); | |
422 } | |
423 | |
424 template <enum ResourceType resourceType> | |
425 static void GetSingleResource(RestApi::GetCall& call) | |
426 { | |
427 Json::Value result; | |
428 if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) | |
429 { | |
430 call.GetOutput().AnswerJson(result); | |
431 } | |
432 } | |
433 | |
434 template <enum ResourceType resourceType> | |
435 static void DeleteSingleResource(RestApi::DeleteCall& call) | |
436 { | |
437 Json::Value result; | |
438 if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) | |
439 { | |
440 call.GetOutput().AnswerJson(result); | |
441 } | |
442 } | |
443 | |
444 | |
445 // Download of ZIP files ---------------------------------------------------- | |
446 | |
447 | |
448 static std::string GetDirectoryNameInArchive(const Json::Value& resource, | |
449 ResourceType resourceType) | |
450 { | |
451 switch (resourceType) | |
452 { | |
453 case ResourceType_Patient: | |
454 { | |
455 std::string p = resource["MainDicomTags"]["PatientID"].asString(); | |
456 std::string n = resource["MainDicomTags"]["PatientName"].asString(); | |
457 return p + " " + n; | |
458 } | |
459 | |
460 case ResourceType_Study: | |
461 { | |
462 return resource["MainDicomTags"]["StudyDescription"].asString(); | |
463 } | |
464 | |
465 case ResourceType_Series: | |
466 { | |
467 std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); | |
468 std::string m = resource["MainDicomTags"]["Modality"].asString(); | |
469 return m + " " + d; | |
470 } | |
471 | |
472 default: | |
473 throw OrthancException(ErrorCode_InternalError); | |
474 } | |
475 } | |
476 | |
477 static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, | |
478 ServerContext& context, | |
479 const Json::Value& resource, | |
480 ResourceType resourceType) | |
481 { | |
482 if (resourceType == ResourceType_Patient) | |
483 { | |
484 return true; | |
485 } | |
486 | |
487 ResourceType parentType = GetParentResourceType(resourceType); | |
488 Json::Value parent; | |
489 | |
490 switch (resourceType) | |
491 { | |
492 case ResourceType_Study: | |
493 { | |
494 if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) | |
495 { | |
496 return false; | |
497 } | |
498 | |
499 break; | |
500 } | |
501 | |
502 case ResourceType_Series: | |
503 if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || | |
504 !CreateRootDirectoryInArchive(writer, context, parent, parentType)) | |
505 { | |
506 return false; | |
507 } | |
508 break; | |
509 | |
510 default: | |
511 throw OrthancException(ErrorCode_NotImplemented); | |
512 } | |
513 | |
514 writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); | |
515 return true; | |
516 } | |
517 | |
518 static bool ArchiveInstance(HierarchicalZipWriter& writer, | |
519 ServerContext& context, | |
520 const std::string& instancePublicId, | |
521 const char* filename) | |
522 { | |
523 Json::Value instance; | |
524 | |
525 if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) | |
526 { | |
527 return false; | |
528 } | |
529 | |
530 writer.OpenFile(filename); | |
531 | |
532 std::string dicom; | |
533 context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); | |
534 writer.Write(dicom); | |
535 | |
536 return true; | |
537 } | |
538 | |
539 static bool ArchiveInternal(HierarchicalZipWriter& writer, | |
540 ServerContext& context, | |
541 const std::string& publicId, | |
542 ResourceType resourceType, | |
543 bool isFirstLevel) | |
544 { | |
545 Json::Value resource; | |
546 if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) | |
547 { | |
548 return false; | |
549 } | |
550 | |
551 if (isFirstLevel && | |
552 !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) | |
553 { | |
554 return false; | |
555 } | |
556 | |
557 writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); | |
558 | |
559 switch (resourceType) | |
560 { | |
561 case ResourceType_Patient: | |
562 for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) | |
563 { | |
564 std::string studyId = resource["Studies"][i].asString(); | |
565 if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) | |
566 { | |
567 return false; | |
568 } | |
569 } | |
570 break; | |
571 | |
572 case ResourceType_Study: | |
573 for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) | |
574 { | |
575 std::string seriesId = resource["Series"][i].asString(); | |
576 if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) | |
577 { | |
578 return false; | |
579 } | |
580 } | |
581 break; | |
582 | |
583 case ResourceType_Series: | |
584 { | |
585 // Create a filename prefix, depending on the modality | |
586 char format[16] = "%08d"; | |
587 | |
588 if (resource["MainDicomTags"].isMember("Modality")) | |
589 { | |
590 std::string modality = resource["MainDicomTags"]["Modality"].asString(); | |
591 | |
592 if (modality.size() == 1) | |
593 { | |
594 snprintf(format, sizeof(format) - 1, "%c%%07d", toupper(modality[0])); | |
595 } | |
596 else if (modality.size() >= 2) | |
597 { | |
598 snprintf(format, sizeof(format) - 1, "%c%c%%06d", toupper(modality[0]), toupper(modality[1])); | |
599 } | |
600 } | |
601 | |
602 char filename[16]; | |
603 | |
604 for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) | |
605 { | |
606 snprintf(filename, sizeof(filename) - 1, format, i); | |
607 | |
608 std::string publicId = resource["Instances"][i].asString(); | |
609 | |
610 // This was the implementation up to Orthanc 0.7.0: | |
611 // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; | |
612 | |
613 if (!ArchiveInstance(writer, context, publicId, filename)) | |
614 { | |
615 return false; | |
616 } | |
617 } | |
618 | |
619 break; | |
620 } | |
621 | |
622 default: | |
623 throw OrthancException(ErrorCode_InternalError); | |
624 } | |
625 | |
626 writer.CloseDirectory(); | |
627 return true; | |
628 } | |
629 | |
630 template <enum ResourceType resourceType> | |
631 static void GetArchive(RestApi::GetCall& call) | |
632 { | |
633 ServerContext& context = OrthancRestApi::GetContext(call); | |
634 | |
635 std::string id = call.GetUriComponent("id", ""); | |
636 | |
637 /** | |
638 * Determine whether ZIP64 is required. Original ZIP format can | |
639 * store up to 2GB of data (some implementation supporting up to | |
640 * 4GB of data), and up to 65535 files. | |
641 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 | |
642 **/ | |
643 | |
644 uint64_t uncompressedSize; | |
645 uint64_t compressedSize; | |
646 unsigned int countStudies; | |
647 unsigned int countSeries; | |
648 unsigned int countInstances; | |
649 context.GetIndex().GetStatistics(compressedSize, uncompressedSize, | |
650 countStudies, countSeries, countInstances, id); | |
651 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || | |
652 countInstances >= 65535); | |
653 | |
654 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " | |
655 << (uncompressedSize / MEGA_BYTES) << "MB using the " | |
656 << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; | |
657 | |
658 // Create a RAII for the temporary file to manage the ZIP file | |
659 Toolbox::TemporaryFile tmp; | |
660 | |
661 { | |
662 // Create a ZIP writer | |
663 HierarchicalZipWriter writer(tmp.GetPath().c_str()); | |
664 writer.SetZip64(isZip64); | |
665 | |
666 // Store the requested resource into the ZIP | |
667 if (!ArchiveInternal(writer, context, id, resourceType, true)) | |
668 { | |
669 return; | |
670 } | |
671 } | |
672 | |
673 // Prepare the sending of the ZIP file | |
674 FilesystemHttpSender sender(tmp.GetPath().c_str()); | |
675 sender.SetContentType("application/zip"); | |
676 sender.SetDownloadFilename(id + ".zip"); | |
677 | |
678 // Send the ZIP | |
679 call.GetOutput().AnswerFile(sender); | |
680 | |
681 // The temporary file is automatically removed thanks to the RAII | |
682 } | |
683 | |
684 | |
685 // Changes API -------------------------------------------------------------- | |
686 | |
687 static void GetSinceAndLimit(int64_t& since, | |
688 unsigned int& limit, | |
689 bool& last, | |
690 const RestApi::GetCall& call) | |
691 { | |
692 static const unsigned int MAX_RESULTS = 100; | |
693 | |
694 if (call.HasArgument("last")) | |
695 { | |
696 last = true; | |
697 return; | |
698 } | |
699 | |
700 last = false; | |
701 | |
702 try | |
703 { | |
704 since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); | |
705 limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); | |
706 } | |
707 catch (boost::bad_lexical_cast) | |
708 { | |
709 return; | |
710 } | |
711 | |
712 if (limit == 0 || limit > MAX_RESULTS) | |
713 { | |
714 limit = MAX_RESULTS; | |
715 } | |
716 } | |
717 | |
718 static void GetChanges(RestApi::GetCall& call) | |
719 { | |
720 ServerContext& context = OrthancRestApi::GetContext(call); | |
721 | |
722 //std::string filter = GetArgument(getArguments, "filter", ""); | |
723 int64_t since; | |
724 unsigned int limit; | |
725 bool last; | |
726 GetSinceAndLimit(since, limit, last, call); | |
727 | |
728 Json::Value result; | |
729 if ((!last && context.GetIndex().GetChanges(result, since, limit)) || | |
730 ( last && context.GetIndex().GetLastChange(result))) | |
731 { | |
732 call.GetOutput().AnswerJson(result); | |
733 } | |
734 } | |
735 | |
736 | |
737 static void DeleteChanges(RestApi::DeleteCall& call) | |
738 { | |
739 OrthancRestApi::GetIndex(call).DeleteChanges(); | |
740 call.GetOutput().AnswerBuffer("", "text/plain"); | |
741 } | |
742 | |
743 | |
744 static void GetExports(RestApi::GetCall& call) | |
745 { | |
746 ServerContext& context = OrthancRestApi::GetContext(call); | |
747 | |
748 int64_t since; | |
749 unsigned int limit; | |
750 bool last; | |
751 GetSinceAndLimit(since, limit, last, call); | |
752 | |
753 Json::Value result; | |
754 if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) || | |
755 ( last && context.GetIndex().GetLastExportedResource(result))) | |
756 { | |
757 call.GetOutput().AnswerJson(result); | |
758 } | |
759 } | |
760 | |
761 | |
762 static void DeleteExports(RestApi::DeleteCall& call) | |
763 { | |
764 OrthancRestApi::GetIndex(call).DeleteExportedResources(); | |
765 call.GetOutput().AnswerBuffer("", "text/plain"); | |
766 } | |
767 | |
768 | |
769 // Get information about a single patient ----------------------------------- | |
770 | |
771 static void IsProtectedPatient(RestApi::GetCall& call) | |
772 { | |
773 std::string publicId = call.GetUriComponent("id", ""); | |
774 bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); | |
775 call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); | |
776 } | |
777 | |
778 | |
779 static void SetPatientProtection(RestApi::PutCall& call) | |
780 { | |
781 ServerContext& context = OrthancRestApi::GetContext(call); | |
782 | |
783 std::string publicId = call.GetUriComponent("id", ""); | |
784 std::string s = Toolbox::StripSpaces(call.GetPutBody()); | |
785 | |
786 if (s == "0") | |
787 { | |
788 context.GetIndex().SetProtectedPatient(publicId, false); | |
789 call.GetOutput().AnswerBuffer("", "text/plain"); | |
790 } | |
791 else if (s == "1") | |
792 { | |
793 context.GetIndex().SetProtectedPatient(publicId, true); | |
794 call.GetOutput().AnswerBuffer("", "text/plain"); | |
795 } | |
796 else | |
797 { | |
798 // Bad request | |
799 } | |
800 } | |
801 | |
802 | |
803 // Get information about a single instance ---------------------------------- | |
804 | |
805 static void GetInstanceFile(RestApi::GetCall& call) | |
806 { | |
807 ServerContext& context = OrthancRestApi::GetContext(call); | |
808 | |
809 std::string publicId = call.GetUriComponent("id", ""); | |
810 context.AnswerDicomFile(call.GetOutput(), publicId, FileContentType_Dicom); | |
811 } | |
812 | |
813 | |
814 static void ExportInstanceFile(RestApi::PostCall& call) | |
815 { | |
816 ServerContext& context = OrthancRestApi::GetContext(call); | |
817 | |
818 std::string publicId = call.GetUriComponent("id", ""); | |
819 | |
820 std::string dicom; | |
821 context.ReadFile(dicom, publicId, FileContentType_Dicom); | |
822 | |
823 Toolbox::WriteFile(dicom, call.GetPostBody()); | |
824 | |
825 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
826 } | |
827 | |
828 | |
829 template <bool simplify> | |
830 static void GetInstanceTags(RestApi::GetCall& call) | |
831 { | |
832 ServerContext& context = OrthancRestApi::GetContext(call); | |
833 | |
834 std::string publicId = call.GetUriComponent("id", ""); | |
835 | |
836 Json::Value full; | |
837 context.ReadJson(full, publicId); | |
838 | |
839 if (simplify) | |
840 { | |
841 Json::Value simplified; | |
842 SimplifyTags(simplified, full); | |
843 call.GetOutput().AnswerJson(simplified); | |
844 } | |
845 else | |
846 { | |
847 call.GetOutput().AnswerJson(full); | |
848 } | |
849 } | |
850 | |
851 | |
852 static void ListFrames(RestApi::GetCall& call) | |
853 { | |
854 Json::Value instance; | |
855 if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) | |
856 { | |
857 unsigned int numberOfFrames = 1; | |
858 | |
859 try | |
860 { | |
861 Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; | |
862 numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); | |
863 } | |
864 catch (...) | |
865 { | |
866 } | |
867 | |
868 Json::Value result = Json::arrayValue; | |
869 for (unsigned int i = 0; i < numberOfFrames; i++) | |
870 { | |
871 result.append(i); | |
872 } | |
873 | |
874 call.GetOutput().AnswerJson(result); | |
875 } | |
876 } | |
877 | |
878 | |
879 template <enum ImageExtractionMode mode> | |
880 static void GetImage(RestApi::GetCall& call) | |
881 { | |
882 ServerContext& context = OrthancRestApi::GetContext(call); | |
883 | |
884 std::string frameId = call.GetUriComponent("frame", "0"); | |
885 | |
886 unsigned int frame; | |
887 try | |
888 { | |
889 frame = boost::lexical_cast<unsigned int>(frameId); | |
890 } | |
891 catch (boost::bad_lexical_cast) | |
892 { | |
893 return; | |
894 } | |
895 | |
896 std::string publicId = call.GetUriComponent("id", ""); | |
897 std::string dicomContent, png; | |
898 context.ReadFile(dicomContent, publicId, FileContentType_Dicom); | |
899 | |
900 try | |
901 { | |
902 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); | |
903 call.GetOutput().AnswerBuffer(png, "image/png"); | |
904 } | |
905 catch (OrthancException& e) | |
906 { | |
907 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) | |
908 { | |
909 // The frame number is out of the range for this DICOM | |
910 // instance, the resource is not existent | |
911 } | |
912 else | |
913 { | |
914 std::string root = ""; | |
915 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
916 { | |
917 root += "../"; | |
918 } | |
919 | |
920 call.GetOutput().Redirect(root + "app/images/unsupported.png"); | |
921 } | |
922 } | |
923 } | |
924 | |
925 | |
926 // Upload of DICOM files through HTTP --------------------------------------- | |
927 | |
928 static void UploadDicomFile(RestApi::PostCall& call) | |
929 { | |
930 ServerContext& context = OrthancRestApi::GetContext(call); | |
931 | |
932 const std::string& postData = call.GetPostBody(); | |
933 if (postData.size() == 0) | |
934 { | |
935 return; | |
936 } | |
937 | |
938 LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; | |
939 | |
940 std::string publicId; | |
941 StoreStatus status = context.Store(publicId, postData); | |
942 Json::Value result = Json::objectValue; | |
943 | |
944 if (status != StoreStatus_Failure) | |
945 { | |
946 result["ID"] = publicId; | |
947 result["Path"] = GetBasePath(ResourceType_Instance, publicId); | |
948 } | |
949 | |
950 result["Status"] = EnumerationToString(status); | |
951 call.GetOutput().AnswerJson(result); | |
952 } | |
953 | |
954 | |
955 | |
956 // DICOM bridge ------------------------------------------------------------- | |
957 | |
958 static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities, | |
959 const std::string& id) | |
960 { | |
961 return modalities.find(id) != modalities.end(); | |
962 } | |
963 | |
964 static void ListModalities(RestApi::GetCall& call) | |
965 { | |
966 OrthancRestApi::SetOfStrings modalities; | |
967 GetListOfDicomModalities(modalities); | |
968 | |
969 Json::Value result = Json::arrayValue; | |
970 for (OrthancRestApi::SetOfStrings::const_iterator | |
971 it = modalities.begin(); it != modalities.end(); ++it) | |
972 { | |
973 result.append(*it); | |
974 } | |
975 | |
976 call.GetOutput().AnswerJson(result); | |
977 } | |
978 | |
979 | |
980 static void ListModalityOperations(RestApi::GetCall& call) | |
981 { | |
982 OrthancRestApi::SetOfStrings modalities; | |
983 GetListOfDicomModalities(modalities); | |
984 | |
985 std::string id = call.GetUriComponent("id", ""); | |
986 if (IsExistingModality(modalities, id)) | |
987 { | |
988 Json::Value result = Json::arrayValue; | |
989 result.append("find-patient"); | |
990 result.append("find-study"); | |
991 result.append("find-series"); | |
992 result.append("find-instance"); | |
993 result.append("find"); | |
994 result.append("store"); | |
995 call.GetOutput().AnswerJson(result); | |
996 } | |
997 } | |
998 | |
999 | |
1000 | |
1001 // Raw access to the DICOM tags of an instance ------------------------------ | |
1002 | |
1003 static void GetRawContent(RestApi::GetCall& call) | |
1004 { | |
1005 boost::mutex::scoped_lock lock(cacheMutex_); | |
1006 | |
1007 ServerContext& context = OrthancRestApi::GetContext(call); | |
1008 | |
1009 std::string id = call.GetUriComponent("id", ""); | |
1010 ParsedDicomFile& dicom = context.GetDicomFile(id); | |
1011 dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); | |
1012 } | |
1013 | |
1014 | |
1015 | |
1016 // Modification of DICOM instances ------------------------------------------ | |
1017 | |
1018 namespace | |
1019 { | |
1020 typedef std::set<DicomTag> Removals; | |
1021 typedef std::map<DicomTag, std::string> Replacements; | |
1022 typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; | |
1023 } | |
1024 | |
1025 static void ReplaceInstanceInternal(ParsedDicomFile& toModify, | |
1026 const Removals& removals, | |
1027 const Replacements& replacements, | |
1028 DicomReplaceMode mode, | |
1029 bool removePrivateTags) | |
1030 { | |
1031 if (removePrivateTags) | |
1032 { | |
1033 toModify.RemovePrivateTags(); | |
1034 } | |
1035 | |
1036 for (Removals::const_iterator it = removals.begin(); | |
1037 it != removals.end(); ++it) | |
1038 { | |
1039 toModify.Remove(*it); | |
1040 } | |
1041 | |
1042 for (Replacements::const_iterator it = replacements.begin(); | |
1043 it != replacements.end(); ++it) | |
1044 { | |
1045 toModify.Replace(it->first, it->second, mode); | |
1046 } | |
1047 | |
1048 // A new SOP instance UID is automatically generated | |
1049 std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); | |
1050 toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); | |
1051 } | |
1052 | |
1053 | |
1054 static void ParseRemovals(Removals& target, | |
1055 const Json::Value& removals) | |
1056 { | |
1057 if (!removals.isArray()) | |
1058 { | |
1059 throw OrthancException(ErrorCode_BadRequest); | |
1060 } | |
1061 | |
1062 for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) | |
1063 { | |
1064 std::string name = removals[i].asString(); | |
1065 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
1066 target.insert(tag); | |
1067 | |
1068 VLOG(1) << "Removal: " << name << " " << tag << std::endl; | |
1069 } | |
1070 } | |
1071 | |
1072 | |
1073 static void ParseReplacements(Replacements& target, | |
1074 const Json::Value& replacements) | |
1075 { | |
1076 if (!replacements.isObject()) | |
1077 { | |
1078 throw OrthancException(ErrorCode_BadRequest); | |
1079 } | |
1080 | |
1081 Json::Value::Members members = replacements.getMemberNames(); | |
1082 for (size_t i = 0; i < members.size(); i++) | |
1083 { | |
1084 const std::string& name = members[i]; | |
1085 std::string value = replacements[name].asString(); | |
1086 | |
1087 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
1088 target[tag] = value; | |
1089 | |
1090 VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; | |
1091 } | |
1092 } | |
1093 | |
1094 | |
1095 static std::string GeneratePatientName(ServerContext& context) | |
1096 { | |
1097 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); | |
1098 return "Anonymized" + boost::lexical_cast<std::string>(seq); | |
1099 } | |
1100 | |
1101 | |
1102 static void SetupAnonymization(Removals& removals, | |
1103 Replacements& replacements) | |
1104 { | |
1105 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles | |
1106 removals.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID | |
1107 //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal() | |
1108 removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number | |
1109 removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name | |
1110 removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address | |
1111 removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name | |
1112 removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address | |
1113 removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers | |
1114 removals.insert(DicomTag(0x0008, 0x1010)); // Station Name | |
1115 removals.insert(DicomTag(0x0008, 0x1030)); // Study Description | |
1116 removals.insert(DicomTag(0x0008, 0x103e)); // Series Description | |
1117 removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name | |
1118 removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record | |
1119 removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name | |
1120 removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study | |
1121 removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name | |
1122 removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description | |
1123 removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID | |
1124 removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description | |
1125 removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name | |
1126 //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) | |
1127 removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date | |
1128 removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time | |
1129 removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex | |
1130 removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids | |
1131 removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names | |
1132 removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age | |
1133 removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size | |
1134 removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight | |
1135 removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator | |
1136 removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group | |
1137 removals.insert(DicomTag(0x0010, 0x2180)); // Occupation | |
1138 removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History | |
1139 removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments | |
1140 removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number | |
1141 removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name | |
1142 //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*) | |
1143 //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) | |
1144 removals.insert(DicomTag(0x0020, 0x0010)); // Study ID | |
1145 removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID | |
1146 removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID | |
1147 removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments | |
1148 removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence | |
1149 removals.insert(DicomTag(0x0040, 0xa124)); // UID | |
1150 removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence | |
1151 removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID | |
1152 removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID | |
1153 removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID | |
1154 | |
1155 /** | |
1156 * (*) Patient ID, Study Instance UID and Series Instance UID | |
1157 * are modified by "AnonymizeInstance()" if anonymizing a single | |
1158 * instance, or by "RetrieveMappedUid()" if anonymizing a | |
1159 * patient/study/series. | |
1160 **/ | |
1161 | |
1162 | |
1163 // Some more removals (from the experience of DICOM files at the CHU of Liege) | |
1164 removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address | |
1165 removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician | |
1166 removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers | |
1167 removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts | |
1168 | |
1169 // Set the DeidentificationMethod tag | |
1170 replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); | |
1171 | |
1172 // Set the PatientIdentityRemoved tag | |
1173 replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); | |
1174 } | |
1175 | |
1176 | |
1177 static bool ParseModifyRequest(Removals& removals, | |
1178 Replacements& replacements, | |
1179 bool& removePrivateTags, | |
1180 const RestApi::PostCall& call) | |
1181 { | |
1182 removePrivateTags = false; | |
1183 Json::Value request; | |
1184 if (call.ParseJsonRequest(request) && | |
1185 request.isObject()) | |
1186 { | |
1187 Json::Value removalsPart = Json::arrayValue; | |
1188 Json::Value replacementsPart = Json::objectValue; | |
1189 | |
1190 if (request.isMember("Remove")) | |
1191 { | |
1192 removalsPart = request["Remove"]; | |
1193 } | |
1194 | |
1195 if (request.isMember("Replace")) | |
1196 { | |
1197 replacementsPart = request["Replace"]; | |
1198 } | |
1199 | |
1200 if (request.isMember("RemovePrivateTags")) | |
1201 { | |
1202 removePrivateTags = true; | |
1203 } | |
1204 | |
1205 ParseRemovals(removals, removalsPart); | |
1206 ParseReplacements(replacements, replacementsPart); | |
1207 | |
1208 return true; | |
1209 } | |
1210 else | |
1211 { | |
1212 return false; | |
1213 } | |
1214 } | |
1215 | |
1216 | |
1217 static bool ParseAnonymizationRequest(Removals& removals, | |
1218 Replacements& replacements, | |
1219 bool& removePrivateTags, | |
1220 bool& keepPatientId, | |
1221 RestApi::PostCall& call) | |
1222 { | |
1223 ServerContext& context = OrthancRestApi::GetContext(call); | |
1224 | |
1225 removePrivateTags = true; | |
1226 keepPatientId = false; | |
1227 | |
1228 Json::Value request; | |
1229 if (call.ParseJsonRequest(request) && | |
1230 request.isObject()) | |
1231 { | |
1232 Json::Value keepPart = Json::arrayValue; | |
1233 Json::Value removalsPart = Json::arrayValue; | |
1234 Json::Value replacementsPart = Json::objectValue; | |
1235 | |
1236 if (request.isMember("Keep")) | |
1237 { | |
1238 keepPart = request["Keep"]; | |
1239 } | |
1240 | |
1241 if (request.isMember("KeepPrivateTags")) | |
1242 { | |
1243 removePrivateTags = false; | |
1244 } | |
1245 | |
1246 if (request.isMember("Replace")) | |
1247 { | |
1248 replacementsPart = request["Replace"]; | |
1249 } | |
1250 | |
1251 Removals toKeep; | |
1252 ParseRemovals(toKeep, keepPart); | |
1253 | |
1254 SetupAnonymization(removals, replacements); | |
1255 | |
1256 for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it) | |
1257 { | |
1258 if (*it == DICOM_TAG_PATIENT_ID) | |
1259 { | |
1260 keepPatientId = true; | |
1261 } | |
1262 | |
1263 removals.erase(*it); | |
1264 } | |
1265 | |
1266 Removals additionalRemovals; | |
1267 ParseRemovals(additionalRemovals, removalsPart); | |
1268 | |
1269 for (Removals::iterator it = additionalRemovals.begin(); | |
1270 it != additionalRemovals.end(); ++it) | |
1271 { | |
1272 removals.insert(*it); | |
1273 } | |
1274 | |
1275 ParseReplacements(replacements, replacementsPart); | |
1276 | |
1277 // Generate random Patient's Name if none is specified | |
1278 if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() && | |
1279 replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end()) | |
1280 { | |
1281 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context))); | |
1282 } | |
1283 | |
1284 return true; | |
1285 } | |
1286 else | |
1287 { | |
1288 return false; | |
1289 } | |
1290 } | |
1291 | |
1292 | |
1293 static void AnonymizeOrModifyInstance(Removals& removals, | |
1294 Replacements& replacements, | |
1295 bool removePrivateTags, | |
1296 RestApi::PostCall& call) | |
1297 { | |
1298 boost::mutex::scoped_lock lock(cacheMutex_); | |
1299 ServerContext& context = OrthancRestApi::GetContext(call); | |
1300 | |
1301 std::string id = call.GetUriComponent("id", ""); | |
1302 ParsedDicomFile& dicom = context.GetDicomFile(id); | |
1303 | |
1304 std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); | |
1305 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1306 modified->Answer(call.GetOutput()); | |
1307 } | |
1308 | |
1309 | |
1310 static bool RetrieveMappedUid(ParsedDicomFile& dicom, | |
1311 DicomRootLevel level, | |
1312 Replacements& replacements, | |
1313 UidMap& uidMap) | |
1314 { | |
1315 std::auto_ptr<DicomTag> tag; | |
1316 | |
1317 switch (level) | |
1318 { | |
1319 case DicomRootLevel_Series: | |
1320 tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); | |
1321 break; | |
1322 | |
1323 case DicomRootLevel_Study: | |
1324 tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); | |
1325 break; | |
1326 | |
1327 case DicomRootLevel_Patient: | |
1328 tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); | |
1329 break; | |
1330 | |
1331 default: | |
1332 throw OrthancException(ErrorCode_InternalError); | |
1333 } | |
1334 | |
1335 std::string original; | |
1336 if (!dicom.GetTagValue(original, *tag)) | |
1337 { | |
1338 throw OrthancException(ErrorCode_InternalError); | |
1339 } | |
1340 | |
1341 std::string mapped; | |
1342 bool isNew; | |
1343 | |
1344 UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original)); | |
1345 if (previous == uidMap.end()) | |
1346 { | |
1347 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); | |
1348 uidMap.insert(std::make_pair(std::make_pair(level, original), mapped)); | |
1349 isNew = true; | |
1350 } | |
1351 else | |
1352 { | |
1353 mapped = previous->second; | |
1354 isNew = false; | |
1355 } | |
1356 | |
1357 replacements[*tag] = mapped; | |
1358 return isNew; | |
1359 } | |
1360 | |
1361 | |
1362 static void AnonymizeOrModifyResource(Removals& removals, | |
1363 Replacements& replacements, | |
1364 bool removePrivateTags, | |
1365 bool keepPatientId, | |
1366 MetadataType metadataType, | |
1367 ChangeType changeType, | |
1368 ResourceType resourceType, | |
1369 RestApi::PostCall& call) | |
1370 { | |
1371 typedef std::list<std::string> Instances; | |
1372 | |
1373 bool isFirst = true; | |
1374 Json::Value result(Json::objectValue); | |
1375 | |
1376 boost::mutex::scoped_lock lock(cacheMutex_); | |
1377 ServerContext& context = OrthancRestApi::GetContext(call); | |
1378 | |
1379 Instances instances; | |
1380 std::string id = call.GetUriComponent("id", ""); | |
1381 context.GetIndex().GetChildInstances(instances, id); | |
1382 | |
1383 if (instances.empty()) | |
1384 { | |
1385 return; | |
1386 } | |
1387 | |
1388 /** | |
1389 * Loop over all the instances of the resource. | |
1390 **/ | |
1391 | |
1392 UidMap uidMap; | |
1393 for (Instances::const_iterator it = instances.begin(); | |
1394 it != instances.end(); ++it) | |
1395 { | |
1396 LOG(INFO) << "Modifying instance " << *it; | |
1397 ParsedDicomFile& original = context.GetDicomFile(*it); | |
1398 | |
1399 DicomInstanceHasher originalHasher = original.GetHasher(); | |
1400 | |
1401 if (isFirst && keepPatientId) | |
1402 { | |
1403 std::string patientId = originalHasher.GetPatientId(); | |
1404 uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; | |
1405 } | |
1406 | |
1407 bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); | |
1408 bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); | |
1409 bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); | |
1410 | |
1411 | |
1412 /** | |
1413 * Compute the resulting DICOM instance and store it into the Orthanc store. | |
1414 **/ | |
1415 | |
1416 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); | |
1417 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1418 | |
1419 std::string modifiedInstance; | |
1420 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) | |
1421 { | |
1422 LOG(ERROR) << "Error while storing a modified instance " << *it; | |
1423 return; | |
1424 } | |
1425 | |
1426 | |
1427 /** | |
1428 * Record metadata information (AnonymizedFrom/ModifiedFrom). | |
1429 **/ | |
1430 | |
1431 DicomInstanceHasher modifiedHasher = modified->GetHasher(); | |
1432 | |
1433 if (isNewSeries) | |
1434 { | |
1435 context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), | |
1436 metadataType, originalHasher.HashSeries()); | |
1437 } | |
1438 | |
1439 if (isNewStudy) | |
1440 { | |
1441 context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), | |
1442 metadataType, originalHasher.HashStudy()); | |
1443 } | |
1444 | |
1445 if (isNewPatient) | |
1446 { | |
1447 context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), | |
1448 metadataType, originalHasher.HashPatient()); | |
1449 } | |
1450 | |
1451 assert(*it == originalHasher.HashInstance()); | |
1452 assert(modifiedInstance == modifiedHasher.HashInstance()); | |
1453 context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); | |
1454 | |
1455 | |
1456 /** | |
1457 * Compute the JSON object that is returned by the REST call. | |
1458 **/ | |
1459 | |
1460 if (isFirst) | |
1461 { | |
1462 std::string newId; | |
1463 | |
1464 switch (resourceType) | |
1465 { | |
1466 case ResourceType_Series: | |
1467 newId = modifiedHasher.HashSeries(); | |
1468 break; | |
1469 | |
1470 case ResourceType_Study: | |
1471 newId = modifiedHasher.HashStudy(); | |
1472 break; | |
1473 | |
1474 case ResourceType_Patient: | |
1475 newId = modifiedHasher.HashPatient(); | |
1476 break; | |
1477 | |
1478 default: | |
1479 throw OrthancException(ErrorCode_InternalError); | |
1480 } | |
1481 | |
1482 result["Type"] = EnumerationToString(resourceType); | |
1483 result["ID"] = newId; | |
1484 result["Path"] = GetBasePath(resourceType, newId); | |
1485 result["PatientID"] = modifiedHasher.HashPatient(); | |
1486 isFirst = false; | |
1487 } | |
1488 } | |
1489 | |
1490 call.GetOutput().AnswerJson(result); | |
1491 } | |
1492 | |
1493 | |
1494 | |
1495 static void ModifyInstance(RestApi::PostCall& call) | |
1496 { | |
1497 Removals removals; | |
1498 Replacements replacements; | |
1499 bool removePrivateTags; | |
1500 | |
1501 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | |
1502 { | |
1503 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); | |
1504 } | |
1505 } | |
1506 | |
1507 | |
1508 static void AnonymizeInstance(RestApi::PostCall& call) | |
1509 { | |
1510 Removals removals; | |
1511 Replacements replacements; | |
1512 bool removePrivateTags, keepPatientId; | |
1513 | |
1514 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) | |
1515 { | |
1516 // TODO Handle "keepPatientId" | |
1517 | |
1518 // Generate random patient ID if not specified | |
1519 if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) | |
1520 { | |
1521 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, | |
1522 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); | |
1523 } | |
1524 | |
1525 // Generate random study UID if not specified | |
1526 if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) | |
1527 { | |
1528 replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, | |
1529 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); | |
1530 } | |
1531 | |
1532 // Generate random series UID if not specified | |
1533 if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) | |
1534 { | |
1535 replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, | |
1536 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); | |
1537 } | |
1538 | |
1539 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); | |
1540 } | |
1541 } | |
1542 | |
1543 | |
1544 static void ModifySeriesInplace(RestApi::PostCall& call) | |
1545 { | |
1546 Removals removals; | |
1547 Replacements replacements; | |
1548 bool removePrivateTags; | |
1549 | |
1550 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | |
1551 { | |
1552 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, | |
1553 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, | |
1554 ResourceType_Series, call); | |
1555 } | |
1556 } | |
1557 | |
1558 | |
1559 static void AnonymizeSeriesInplace(RestApi::PostCall& call) | |
1560 { | |
1561 Removals removals; | |
1562 Replacements replacements; | |
1563 bool removePrivateTags, keepPatientId; | |
1564 | |
1565 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) | |
1566 { | |
1567 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, | |
1568 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, | |
1569 ResourceType_Series, call); | |
1570 } | |
1571 } | |
1572 | |
1573 | |
1574 static void ModifyStudyInplace(RestApi::PostCall& call) | |
1575 { | |
1576 Removals removals; | |
1577 Replacements replacements; | |
1578 bool removePrivateTags; | |
1579 | |
1580 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | |
1581 { | |
1582 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, | |
1583 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, | |
1584 ResourceType_Study, call); | |
1585 } | |
1586 } | |
1587 | |
1588 | |
1589 static void AnonymizeStudyInplace(RestApi::PostCall& call) | |
1590 { | |
1591 Removals removals; | |
1592 Replacements replacements; | |
1593 bool removePrivateTags, keepPatientId; | |
1594 | |
1595 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) | |
1596 { | |
1597 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, | |
1598 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, | |
1599 ResourceType_Study, call); | |
1600 } | |
1601 } | |
1602 | |
1603 | |
1604 /*static void ModifyPatientInplace(RestApi::PostCall& call) | |
1605 { | |
1606 Removals removals; | |
1607 Replacements replacements; | |
1608 bool removePrivateTags; | |
1609 | |
1610 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | |
1611 { | |
1612 AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, | |
1613 MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, | |
1614 ResourceType_Patient, call); | |
1615 } | |
1616 }*/ | |
1617 | |
1618 | |
1619 static void AnonymizePatientInplace(RestApi::PostCall& call) | |
1620 { | |
1621 Removals removals; | |
1622 Replacements replacements; | |
1623 bool removePrivateTags, keepPatientId; | |
1624 | |
1625 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) | |
1626 { | |
1627 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, | |
1628 MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, | |
1629 ResourceType_Patient, call); | |
1630 } | |
1631 } | |
1632 | |
1633 | |
1634 // Handling of metadata ----------------------------------------------------- | |
1635 | |
1636 static void CheckValidResourceType(RestApi::Call& call) | |
1637 { | |
1638 std::string resourceType = call.GetUriComponent("resourceType", ""); | |
1639 StringToResourceType(resourceType.c_str()); | |
1640 } | |
1641 | |
1642 | |
1643 static void ListMetadata(RestApi::GetCall& call) | |
1644 { | |
1645 CheckValidResourceType(call); | |
1646 | |
1647 std::string publicId = call.GetUriComponent("id", ""); | |
1648 std::list<MetadataType> metadata; | |
1649 | |
1650 OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); | |
1651 Json::Value result = Json::arrayValue; | |
1652 | |
1653 for (std::list<MetadataType>::const_iterator | |
1654 it = metadata.begin(); it != metadata.end(); ++it) | |
1655 { | |
1656 result.append(EnumerationToString(*it)); | |
1657 } | |
1658 | |
1659 call.GetOutput().AnswerJson(result); | |
1660 } | |
1661 | |
1662 | |
1663 static void GetMetadata(RestApi::GetCall& call) | |
1664 { | |
1665 CheckValidResourceType(call); | |
1666 | |
1667 std::string publicId = call.GetUriComponent("id", ""); | |
1668 std::string name = call.GetUriComponent("name", ""); | |
1669 MetadataType metadata = StringToMetadata(name); | |
1670 | |
1671 std::string value; | |
1672 if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) | |
1673 { | |
1674 call.GetOutput().AnswerBuffer(value, "text/plain"); | |
1675 } | |
1676 } | |
1677 | |
1678 | |
1679 static void DeleteMetadata(RestApi::DeleteCall& call) | |
1680 { | |
1681 CheckValidResourceType(call); | |
1682 | |
1683 std::string publicId = call.GetUriComponent("id", ""); | |
1684 std::string name = call.GetUriComponent("name", ""); | |
1685 MetadataType metadata = StringToMetadata(name); | |
1686 | |
1687 if (metadata >= MetadataType_StartUser && | |
1688 metadata <= MetadataType_EndUser) | |
1689 { | |
1690 // It is forbidden to modify internal metadata | |
1691 OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); | |
1692 call.GetOutput().AnswerBuffer("", "text/plain"); | |
1693 } | |
1694 } | |
1695 | |
1696 | |
1697 static void SetMetadata(RestApi::PutCall& call) | |
1698 { | |
1699 CheckValidResourceType(call); | |
1700 | |
1701 std::string publicId = call.GetUriComponent("id", ""); | |
1702 std::string name = call.GetUriComponent("name", ""); | |
1703 MetadataType metadata = StringToMetadata(name); | |
1704 std::string value = call.GetPutBody(); | |
1705 | |
1706 if (metadata >= MetadataType_StartUser && | |
1707 metadata <= MetadataType_EndUser) | |
1708 { | |
1709 // It is forbidden to modify internal metadata | |
1710 OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); | |
1711 call.GetOutput().AnswerBuffer("", "text/plain"); | |
1712 } | |
1713 } | |
1714 | |
1715 | |
1716 static void GetResourceStatistics(RestApi::GetCall& call) | |
1717 { | |
1718 std::string publicId = call.GetUriComponent("id", ""); | |
1719 Json::Value result; | |
1720 OrthancRestApi::GetIndex(call).GetStatistics(result, publicId); | |
1721 call.GetOutput().AnswerJson(result); | |
1722 } | |
1723 | |
1724 | |
1725 | |
1726 // Orthanc Peers ------------------------------------------------------------ | |
1727 | |
1728 static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, | |
1729 const std::string& id) | |
1730 { | |
1731 return peers.find(id) != peers.end(); | |
1732 } | |
1733 | |
1734 static void ListPeers(RestApi::GetCall& call) | |
1735 { | |
1736 OrthancRestApi::SetOfStrings peers; | |
1737 GetListOfOrthancPeers(peers); | |
1738 | |
1739 Json::Value result = Json::arrayValue; | |
1740 for (OrthancRestApi::SetOfStrings::const_iterator | |
1741 it = peers.begin(); it != peers.end(); ++it) | |
1742 { | |
1743 result.append(*it); | |
1744 } | |
1745 | |
1746 call.GetOutput().AnswerJson(result); | |
1747 } | |
1748 | |
1749 static void ListPeerOperations(RestApi::GetCall& call) | |
1750 { | |
1751 OrthancRestApi::SetOfStrings peers; | |
1752 GetListOfOrthancPeers(peers); | |
1753 | |
1754 std::string id = call.GetUriComponent("id", ""); | |
1755 if (IsExistingPeer(peers, id)) | |
1756 { | |
1757 Json::Value result = Json::arrayValue; | |
1758 result.append("store"); | |
1759 call.GetOutput().AnswerJson(result); | |
1760 } | |
1761 } | |
1762 | |
1763 static void PeerStore(RestApi::PostCall& call) | |
1764 { | |
1765 ServerContext& context = OrthancRestApi::GetContext(call); | |
1766 | |
1767 std::string remote = call.GetUriComponent("id", ""); | |
1768 | |
1769 std::list<std::string> instances; | |
1770 if (!GetInstancesToExport(instances, remote, call)) | |
1771 { | |
1772 return; | |
1773 } | |
1774 | |
1775 std::string url, username, password; | |
1776 GetOrthancPeer(remote, url, username, password); | |
1777 | |
1778 // Configure the HTTP client | |
1779 HttpClient client; | |
1780 if (username.size() != 0 && password.size() != 0) | |
1781 { | |
1782 client.SetCredentials(username.c_str(), password.c_str()); | |
1783 } | |
1784 | |
1785 client.SetUrl(url + "instances"); | |
1786 client.SetMethod(HttpMethod_Post); | |
1787 | |
1788 // Loop over the instances that are to be sent | |
1789 for (std::list<std::string>::const_iterator | |
1790 it = instances.begin(); it != instances.end(); ++it) | |
1791 { | |
1792 LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\""; | |
1793 | |
1794 context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); | |
1795 | |
1796 std::string answer; | |
1797 if (!client.Apply(answer)) | |
1798 { | |
1799 LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\""; | |
1800 return; | |
1801 } | |
1802 } | |
1803 | |
1804 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
1805 } | |
1806 | |
1807 | |
1808 | |
1809 | |
1810 | |
1811 | |
1812 // Handling of attached files ----------------------------------------------- | |
1813 | |
1814 static void ListAttachments(RestApi::GetCall& call) | |
1815 { | |
1816 std::string resourceType = call.GetUriComponent("resourceType", ""); | |
1817 std::string publicId = call.GetUriComponent("id", ""); | |
1818 std::list<FileContentType> attachments; | |
1819 OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); | |
1820 | |
1821 Json::Value result = Json::arrayValue; | |
1822 | |
1823 for (std::list<FileContentType>::const_iterator | |
1824 it = attachments.begin(); it != attachments.end(); ++it) | |
1825 { | |
1826 result.append(EnumerationToString(*it)); | |
1827 } | |
1828 | |
1829 call.GetOutput().AnswerJson(result); | |
1830 } | |
1831 | |
1832 | |
1833 static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call) | |
1834 { | |
1835 CheckValidResourceType(call); | |
1836 | |
1837 std::string publicId = call.GetUriComponent("id", ""); | |
1838 std::string name = call.GetUriComponent("name", ""); | |
1839 FileContentType contentType = StringToContentType(name); | |
1840 | |
1841 return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); | |
1842 } | |
1843 | |
1844 | |
1845 static void GetAttachmentOperations(RestApi::GetCall& call) | |
1846 { | |
1847 FileInfo info; | |
1848 if (GetAttachmentInfo(info, call)) | |
1849 { | |
1850 Json::Value operations = Json::arrayValue; | |
1851 | |
1852 operations.append("compressed-data"); | |
1853 | |
1854 if (info.GetCompressedMD5() != "") | |
1855 { | |
1856 operations.append("compressed-md5"); | |
1857 } | |
1858 | |
1859 operations.append("compressed-size"); | |
1860 operations.append("data"); | |
1861 | |
1862 if (info.GetUncompressedMD5() != "") | |
1863 { | |
1864 operations.append("md5"); | |
1865 } | |
1866 | |
1867 operations.append("size"); | |
1868 | |
1869 if (info.GetCompressedMD5() != "" && | |
1870 info.GetUncompressedMD5() != "") | |
1871 { | |
1872 operations.append("verify-md5"); | |
1873 } | |
1874 | |
1875 call.GetOutput().AnswerJson(operations); | |
1876 } | |
1877 } | |
1878 | |
1879 | |
1880 template <int uncompress> | |
1881 static void GetAttachmentData(RestApi::GetCall& call) | |
1882 { | |
1883 ServerContext& context = OrthancRestApi::GetContext(call); | |
1884 | |
1885 CheckValidResourceType(call); | |
1886 | |
1887 std::string publicId = call.GetUriComponent("id", ""); | |
1888 std::string name = call.GetUriComponent("name", ""); | |
1889 | |
1890 std::string content; | |
1891 context.ReadFile(content, publicId, StringToContentType(name), | |
1892 (uncompress == 1)); | |
1893 | |
1894 call.GetOutput().AnswerBuffer(content, "application/octet-stream"); | |
1895 } | |
1896 | |
1897 | |
1898 static void GetAttachmentSize(RestApi::GetCall& call) | |
1899 { | |
1900 FileInfo info; | |
1901 if (GetAttachmentInfo(info, call)) | |
1902 { | |
1903 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), "text/plain"); | |
1904 } | |
1905 } | |
1906 | |
1907 | |
1908 static void GetAttachmentCompressedSize(RestApi::GetCall& call) | |
1909 { | |
1910 FileInfo info; | |
1911 if (GetAttachmentInfo(info, call)) | |
1912 { | |
1913 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), "text/plain"); | |
1914 } | |
1915 } | |
1916 | |
1917 | |
1918 static void GetAttachmentMD5(RestApi::GetCall& call) | |
1919 { | |
1920 FileInfo info; | |
1921 if (GetAttachmentInfo(info, call) && | |
1922 info.GetUncompressedMD5() != "") | |
1923 { | |
1924 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), "text/plain"); | |
1925 } | |
1926 } | |
1927 | |
1928 | |
1929 static void GetAttachmentCompressedMD5(RestApi::GetCall& call) | |
1930 { | |
1931 FileInfo info; | |
1932 if (GetAttachmentInfo(info, call) && | |
1933 info.GetCompressedMD5() != "") | |
1934 { | |
1935 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), "text/plain"); | |
1936 } | |
1937 } | |
1938 | |
1939 | |
1940 static void VerifyAttachment(RestApi::PostCall& call) | |
1941 { | |
1942 ServerContext& context = OrthancRestApi::GetContext(call); | |
1943 CheckValidResourceType(call); | |
1944 | |
1945 std::string publicId = call.GetUriComponent("id", ""); | |
1946 std::string name = call.GetUriComponent("name", ""); | |
1947 | |
1948 FileInfo info; | |
1949 if (!GetAttachmentInfo(info, call) || | |
1950 info.GetCompressedMD5() == "" || | |
1951 info.GetUncompressedMD5() == "") | |
1952 { | |
1953 // Inexistent resource, or no MD5 available | |
1954 return; | |
1955 } | |
1956 | |
1957 bool ok = false; | |
1958 | |
1959 // First check whether the compressed data is correctly stored in the disk | |
1960 std::string data; | |
1961 context.ReadFile(data, publicId, StringToContentType(name), false); | |
1962 | |
1963 std::string actualMD5; | |
1964 Toolbox::ComputeMD5(actualMD5, data); | |
1965 | |
1966 if (actualMD5 == info.GetCompressedMD5()) | |
1967 { | |
1968 // The compressed data is OK. If a compression algorithm was | |
1969 // applied to it, now check the MD5 of the uncompressed data. | |
1970 if (info.GetCompressionType() == CompressionType_None) | |
1971 { | |
1972 ok = true; | |
1973 } | |
1974 else | |
1975 { | |
1976 context.ReadFile(data, publicId, StringToContentType(name), true); | |
1977 Toolbox::ComputeMD5(actualMD5, data); | |
1978 ok = (actualMD5 == info.GetUncompressedMD5()); | |
1979 } | |
1980 } | |
1981 | |
1982 if (ok) | |
1983 { | |
1984 LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; | |
1985 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
1986 } | |
1987 else | |
1988 { | |
1989 LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; | |
1990 } | |
1991 } | |
1992 | |
1993 | |
1994 static void UploadAttachment(RestApi::PutCall& call) | |
1995 { | |
1996 ServerContext& context = OrthancRestApi::GetContext(call); | |
1997 CheckValidResourceType(call); | |
1998 | |
1999 std::string publicId = call.GetUriComponent("id", ""); | |
2000 std::string name = call.GetUriComponent("name", ""); | |
2001 | |
2002 const void* data = call.GetPutBody().size() ? &call.GetPutBody()[0] : NULL; | |
2003 | |
2004 FileContentType contentType = StringToContentType(name); | |
2005 if (contentType >= FileContentType_StartUser && // It is forbidden to modify internal attachments | |
2006 contentType <= FileContentType_EndUser && | |
2007 context.AddAttachment(publicId, StringToContentType(name), data, call.GetPutBody().size())) | |
2008 { | |
2009 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
2010 } | |
2011 } | |
2012 | |
2013 | |
2014 static void DeleteAttachment(RestApi::DeleteCall& call) | |
2015 { | |
2016 CheckValidResourceType(call); | |
2017 | |
2018 std::string publicId = call.GetUriComponent("id", ""); | |
2019 std::string name = call.GetUriComponent("name", ""); | |
2020 FileContentType contentType = StringToContentType(name); | |
2021 | |
2022 if (contentType >= FileContentType_StartUser && | |
2023 contentType <= FileContentType_EndUser) | |
2024 { | |
2025 // It is forbidden to delete internal attachments | |
2026 OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); | |
2027 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
2028 } | |
2029 } | |
2030 | |
2031 | |
2032 | |
2033 | |
2034 // Registration of the various REST handlers -------------------------------- | |
2035 | |
2036 OrthancRestApi::OrthancRestApi(ServerContext& context) : | |
2037 context_(context) | |
2038 { | |
2039 Register("/", ServeRoot); | |
2040 Register("/system", GetSystemInformation); | |
2041 Register("/statistics", GetStatistics); | |
2042 Register("/changes", GetChanges); | |
2043 Register("/changes", DeleteChanges); | |
2044 Register("/exports", GetExports); | |
2045 Register("/exports", DeleteExports); | |
2046 | |
2047 Register("/instances", UploadDicomFile); | |
2048 Register("/instances", ListResources<ResourceType_Instance>); | |
2049 Register("/patients", ListResources<ResourceType_Patient>); | |
2050 Register("/series", ListResources<ResourceType_Series>); | |
2051 Register("/studies", ListResources<ResourceType_Study>); | |
2052 | |
2053 Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); | |
2054 Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); | |
2055 Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); | |
2056 Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); | |
2057 Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); | |
2058 Register("/series/{id}", GetSingleResource<ResourceType_Series>); | |
2059 Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); | |
2060 Register("/studies/{id}", GetSingleResource<ResourceType_Study>); | |
2061 | |
2062 Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); | |
2063 Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); | |
2064 Register("/series/{id}/archive", GetArchive<ResourceType_Series>); | |
2065 | |
2066 Register("/instances/{id}/statistics", GetResourceStatistics); | |
2067 Register("/patients/{id}/statistics", GetResourceStatistics); | |
2068 Register("/studies/{id}/statistics", GetResourceStatistics); | |
2069 Register("/series/{id}/statistics", GetResourceStatistics); | |
2070 | |
2071 Register("/patients/{id}/protected", IsProtectedPatient); | |
2072 Register("/patients/{id}/protected", SetPatientProtection); | |
2073 Register("/instances/{id}/file", GetInstanceFile); | |
2074 Register("/instances/{id}/export", ExportInstanceFile); | |
2075 Register("/instances/{id}/tags", GetInstanceTags<false>); | |
2076 Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); | |
2077 Register("/instances/{id}/frames", ListFrames); | |
2078 Register("/instances/{id}/content/*", GetRawContent); | |
2079 | |
2080 Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); | |
2081 Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | |
2082 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | |
2083 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); | |
2084 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); | |
2085 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | |
2086 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | |
2087 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); | |
2088 | |
2089 Register("/modalities", ListModalities); | |
2090 Register("/modalities/{id}", ListModalityOperations); | |
2091 Register("/modalities/{id}/find-patient", DicomFindPatient); | |
2092 Register("/modalities/{id}/find-study", DicomFindStudy); | |
2093 Register("/modalities/{id}/find-series", DicomFindSeries); | |
2094 Register("/modalities/{id}/find-instance", DicomFindInstance); | |
2095 Register("/modalities/{id}/find", DicomFind); | |
2096 Register("/modalities/{id}/store", DicomStore); | |
2097 | |
2098 Register("/peers", ListPeers); | |
2099 Register("/peers/{id}", ListPeerOperations); | |
2100 Register("/peers/{id}/store", PeerStore); | |
2101 | |
2102 Register("/instances/{id}/modify", ModifyInstance); | |
2103 Register("/series/{id}/modify", ModifySeriesInplace); | |
2104 Register("/studies/{id}/modify", ModifyStudyInplace); | |
2105 //Register("/patients/{id}/modify", ModifyPatientInplace); | |
2106 | |
2107 Register("/instances/{id}/anonymize", AnonymizeInstance); | |
2108 Register("/series/{id}/anonymize", AnonymizeSeriesInplace); | |
2109 Register("/studies/{id}/anonymize", AnonymizeStudyInplace); | |
2110 Register("/patients/{id}/anonymize", AnonymizePatientInplace); | |
2111 | |
2112 Register("/tools/generate-uid", GenerateUid); | |
2113 Register("/tools/execute-script", ExecuteScript); | |
2114 Register("/tools/now", GetNowIsoString); | |
2115 | |
2116 Register("/{resourceType}/{id}/metadata", ListMetadata); | |
2117 Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); | |
2118 Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); | |
2119 Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); | |
2120 | |
2121 Register("/{resourceType}/{id}/attachments", ListAttachments); | |
2122 Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); | |
2123 Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); | |
2124 Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); | |
2125 Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); | |
2126 Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); | |
2127 Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); | |
2128 Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); | |
2129 Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); | |
2130 Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); | |
2131 Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); | |
2132 } | |
2133 } |