Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestArchive.cpp @ 1121:82567bac5e25
Creation of ZIP archives for media storage, with DICOMDIR
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 05 Sep 2014 14:28:43 +0200 |
parents | 6968356679c0 |
children | 6e7e5ed91c2d |
comparison
equal
deleted
inserted
replaced
1120:009dce4ea2f6 | 1121:82567bac5e25 |
---|---|
31 | 31 |
32 | 32 |
33 #include "../PrecompiledHeadersServer.h" | 33 #include "../PrecompiledHeadersServer.h" |
34 #include "OrthancRestApi.h" | 34 #include "OrthancRestApi.h" |
35 | 35 |
36 #include "../DicomDirWriter.h" | |
36 #include "../../Core/Compression/HierarchicalZipWriter.h" | 37 #include "../../Core/Compression/HierarchicalZipWriter.h" |
37 #include "../../Core/HttpServer/FilesystemHttpSender.h" | 38 #include "../../Core/HttpServer/FilesystemHttpSender.h" |
38 #include "../../Core/Uuid.h" | 39 #include "../../Core/Uuid.h" |
39 | 40 |
40 #include <glog/logging.h> | 41 #include <glog/logging.h> |
52 // Download of ZIP files ---------------------------------------------------- | 53 // Download of ZIP files ---------------------------------------------------- |
53 | 54 |
54 static std::string GetDirectoryNameInArchive(const Json::Value& resource, | 55 static std::string GetDirectoryNameInArchive(const Json::Value& resource, |
55 ResourceType resourceType) | 56 ResourceType resourceType) |
56 { | 57 { |
58 std::string s; | |
59 | |
57 switch (resourceType) | 60 switch (resourceType) |
58 { | 61 { |
59 case ResourceType_Patient: | 62 case ResourceType_Patient: |
60 { | 63 { |
61 std::string p = resource["MainDicomTags"]["PatientID"].asString(); | 64 std::string p = resource["MainDicomTags"]["PatientID"].asString(); |
62 std::string n = resource["MainDicomTags"]["PatientName"].asString(); | 65 std::string n = resource["MainDicomTags"]["PatientName"].asString(); |
63 return p + " " + n; | 66 s = p + " " + n; |
67 break; | |
64 } | 68 } |
65 | 69 |
66 case ResourceType_Study: | 70 case ResourceType_Study: |
67 { | 71 { |
68 return resource["MainDicomTags"]["StudyDescription"].asString(); | 72 s = resource["MainDicomTags"]["StudyDescription"].asString(); |
73 break; | |
69 } | 74 } |
70 | 75 |
71 case ResourceType_Series: | 76 case ResourceType_Series: |
72 { | 77 { |
73 std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); | 78 std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); |
74 std::string m = resource["MainDicomTags"]["Modality"].asString(); | 79 std::string m = resource["MainDicomTags"]["Modality"].asString(); |
75 return m + " " + d; | 80 s = m + " " + d; |
81 break; | |
76 } | 82 } |
77 | 83 |
78 default: | 84 default: |
79 throw OrthancException(ErrorCode_InternalError); | 85 throw OrthancException(ErrorCode_InternalError); |
80 } | 86 } |
87 | |
88 // Get rid of special characters | |
89 return Toolbox::ConvertToAscii(s); | |
81 } | 90 } |
82 | 91 |
83 static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, | 92 static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, |
84 ServerContext& context, | 93 ServerContext& context, |
85 const Json::Value& resource, | 94 const Json::Value& resource, |
124 static bool ArchiveInstance(HierarchicalZipWriter& writer, | 133 static bool ArchiveInstance(HierarchicalZipWriter& writer, |
125 ServerContext& context, | 134 ServerContext& context, |
126 const std::string& instancePublicId, | 135 const std::string& instancePublicId, |
127 const char* filename) | 136 const char* filename) |
128 { | 137 { |
129 Json::Value instance; | |
130 | |
131 if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) | |
132 { | |
133 return false; | |
134 } | |
135 | |
136 writer.OpenFile(filename); | 138 writer.OpenFile(filename); |
137 | 139 |
138 std::string dicom; | 140 std::string dicom; |
139 context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); | 141 context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); |
140 writer.Write(dicom); | 142 writer.Write(dicom); |
231 | 233 |
232 writer.CloseDirectory(); | 234 writer.CloseDirectory(); |
233 return true; | 235 return true; |
234 } | 236 } |
235 | 237 |
236 template <enum ResourceType resourceType> | 238 |
237 static void GetArchive(RestApiGetCall& call) | 239 static bool IsZip64Required(ServerIndex& index, |
238 { | 240 const std::string& id) |
239 ServerContext& context = OrthancRestApi::GetContext(call); | 241 { |
240 | |
241 std::string id = call.GetUriComponent("id", ""); | |
242 | |
243 /** | 242 /** |
244 * Determine whether ZIP64 is required. Original ZIP format can | 243 * Determine whether ZIP64 is required. Original ZIP format can |
245 * store up to 2GB of data (some implementation supporting up to | 244 * store up to 2GB of data (some implementation supporting up to |
246 * 4GB of data), and up to 65535 files. | 245 * 4GB of data), and up to 65535 files. |
247 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 | 246 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 |
250 uint64_t uncompressedSize; | 249 uint64_t uncompressedSize; |
251 uint64_t compressedSize; | 250 uint64_t compressedSize; |
252 unsigned int countStudies; | 251 unsigned int countStudies; |
253 unsigned int countSeries; | 252 unsigned int countSeries; |
254 unsigned int countInstances; | 253 unsigned int countInstances; |
255 context.GetIndex().GetStatistics(compressedSize, uncompressedSize, | 254 index.GetStatistics(compressedSize, uncompressedSize, |
256 countStudies, countSeries, countInstances, id); | 255 countStudies, countSeries, countInstances, id); |
257 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || | 256 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || |
258 countInstances >= 65535); | 257 countInstances >= 65535); |
259 | 258 |
260 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " | 259 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " |
261 << (uncompressedSize / MEGA_BYTES) << "MB using the " | 260 << (uncompressedSize / MEGA_BYTES) << "MB using the " |
262 << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; | 261 << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; |
262 | |
263 return isZip64; | |
264 } | |
265 | |
266 | |
267 template <enum ResourceType resourceType> | |
268 static void GetArchive(RestApiGetCall& call) | |
269 { | |
270 ServerContext& context = OrthancRestApi::GetContext(call); | |
271 | |
272 std::string id = call.GetUriComponent("id", ""); | |
273 bool isZip64 = IsZip64Required(context.GetIndex(), id); | |
263 | 274 |
264 // Create a RAII for the temporary file to manage the ZIP file | 275 // Create a RAII for the temporary file to manage the ZIP file |
265 Toolbox::TemporaryFile tmp; | 276 Toolbox::TemporaryFile tmp; |
266 | 277 |
267 { | 278 { |
286 | 297 |
287 // The temporary file is automatically removed thanks to the RAII | 298 // The temporary file is automatically removed thanks to the RAII |
288 } | 299 } |
289 | 300 |
290 | 301 |
302 static void GetMediaArchive(RestApiGetCall& call) | |
303 { | |
304 ServerContext& context = OrthancRestApi::GetContext(call); | |
305 | |
306 std::string id = call.GetUriComponent("id", ""); | |
307 bool isZip64 = IsZip64Required(context.GetIndex(), id); | |
308 | |
309 // Create a RAII for the temporary file to manage the ZIP file | |
310 Toolbox::TemporaryFile tmp; | |
311 | |
312 { | |
313 // Create a ZIP writer | |
314 HierarchicalZipWriter writer(tmp.GetPath().c_str()); | |
315 writer.SetZip64(isZip64); | |
316 writer.OpenDirectory("IMAGES"); | |
317 | |
318 // Create the DICOMDIR writer | |
319 DicomDirWriter dicomDir; | |
320 | |
321 // Retrieve the list of the instances | |
322 std::list<std::string> instances; | |
323 context.GetIndex().GetChildInstances(instances, id); | |
324 | |
325 size_t pos = 0; | |
326 for (std::list<std::string>::const_iterator | |
327 it = instances.begin(); it != instances.end(); it++, pos++) | |
328 { | |
329 // "DICOM restricts the filenames on DICOM media to 8 | |
330 // characters (some systems wrongly use 8.3, but this does not | |
331 // conform to the standard)." | |
332 std::string filename = "IM" + boost::lexical_cast<std::string>(pos); | |
333 writer.OpenFile(filename.c_str()); | |
334 | |
335 std::string dicom; | |
336 context.ReadFile(dicom, *it, FileContentType_Dicom); | |
337 writer.Write(dicom); | |
338 | |
339 ParsedDicomFile parsed(dicom); | |
340 dicomDir.Add("IMAGES", filename, parsed); | |
341 } | |
342 | |
343 // Add the DICOMDIR | |
344 writer.CloseDirectory(); | |
345 writer.OpenFile("DICOMDIR"); | |
346 std::string s; | |
347 dicomDir.Encode(s); | |
348 writer.Write(s); | |
349 } | |
350 | |
351 // Prepare the sending of the ZIP file | |
352 FilesystemHttpSender sender(tmp.GetPath().c_str()); | |
353 sender.SetContentType("application/zip"); | |
354 sender.SetDownloadFilename(id + ".zip"); | |
355 | |
356 // Send the ZIP | |
357 call.GetOutput().AnswerFile(sender); | |
358 | |
359 // The temporary file is automatically removed thanks to the RAII | |
360 } | |
361 | |
362 | |
291 void OrthancRestApi::RegisterArchive() | 363 void OrthancRestApi::RegisterArchive() |
292 { | 364 { |
293 Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); | 365 Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); |
294 Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); | 366 Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); |
295 Register("/series/{id}/archive", GetArchive<ResourceType_Series>); | 367 Register("/series/{id}/archive", GetArchive<ResourceType_Series>); |
368 | |
369 Register("/patients/{id}/media", GetMediaArchive); | |
370 Register("/studies/{id}/media", GetMediaArchive); | |
371 Register("/series/{id}/media", GetMediaArchive); | |
296 } | 372 } |
297 } | 373 } |