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 }