Mercurial > hg > orthanc
comparison Core/DicomParsing/DicomDirWriter.cpp @ 2382:7284093111b0
big reorganization to cleanly separate framework vs. server
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Aug 2017 21:17:35 +0200 |
parents | OrthancServer/DicomDirWriter.cpp@a3a65de1840f |
children | d0fe5ec7eb05 |
comparison
equal
deleted
inserted
replaced
2381:b8969010b534 | 2382:7284093111b0 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017 Osimis, Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 /*========================================================================= | |
38 | |
39 This file is based on portions of the following project: | |
40 | |
41 Program: DCMTK 3.6.0 | |
42 Module: http://dicom.offis.de/dcmtk.php.en | |
43 | |
44 Copyright (C) 1994-2011, OFFIS e.V. | |
45 All rights reserved. | |
46 | |
47 This software and supporting documentation were developed by | |
48 | |
49 OFFIS e.V. | |
50 R&D Division Health | |
51 Escherweg 2 | |
52 26121 Oldenburg, Germany | |
53 | |
54 Redistribution and use in source and binary forms, with or without | |
55 modification, are permitted provided that the following conditions | |
56 are met: | |
57 | |
58 - Redistributions of source code must retain the above copyright | |
59 notice, this list of conditions and the following disclaimer. | |
60 | |
61 - Redistributions in binary form must reproduce the above copyright | |
62 notice, this list of conditions and the following disclaimer in the | |
63 documentation and/or other materials provided with the distribution. | |
64 | |
65 - Neither the name of OFFIS nor the names of its contributors may be | |
66 used to endorse or promote products derived from this software | |
67 without specific prior written permission. | |
68 | |
69 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
70 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
71 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
72 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
73 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
74 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
75 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
76 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
77 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
78 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
79 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
80 | |
81 =========================================================================*/ | |
82 | |
83 | |
84 | |
85 /*** | |
86 | |
87 Validation: | |
88 | |
89 # sudo apt-get install dicom3tools | |
90 # dciodvfy DICOMDIR 2>&1 | less | |
91 # dcentvfy DICOMDIR 2>&1 | less | |
92 | |
93 http://www.dclunie.com/dicom3tools/dciodvfy.html | |
94 | |
95 DICOMDIR viewer working with Wine under Linux: | |
96 http://www.microdicom.com/ | |
97 | |
98 ***/ | |
99 | |
100 | |
101 #include "../PrecompiledHeaders.h" | |
102 #include "DicomDirWriter.h" | |
103 | |
104 #include "FromDcmtkBridge.h" | |
105 #include "ToDcmtkBridge.h" | |
106 | |
107 #include "../Logging.h" | |
108 #include "../OrthancException.h" | |
109 #include "../TemporaryFile.h" | |
110 #include "../Toolbox.h" | |
111 #include "../SystemToolbox.h" | |
112 | |
113 #include <dcmtk/dcmdata/dcdicdir.h> | |
114 #include <dcmtk/dcmdata/dcmetinf.h> | |
115 #include <dcmtk/dcmdata/dcdeftag.h> | |
116 #include <dcmtk/dcmdata/dcuid.h> | |
117 #include <dcmtk/dcmdata/dcddirif.h> | |
118 #include <dcmtk/dcmdata/dcvrui.h> | |
119 #include <dcmtk/dcmdata/dcsequen.h> | |
120 #include <dcmtk/dcmdata/dcostrmf.h> | |
121 #include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ | |
122 #include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ | |
123 | |
124 #include <memory> | |
125 | |
126 namespace Orthanc | |
127 { | |
128 class DicomDirWriter::PImpl | |
129 { | |
130 private: | |
131 std::string fileSetId_; | |
132 TemporaryFile file_; | |
133 std::auto_ptr<DcmDicomDir> dir_; | |
134 | |
135 typedef std::pair<ResourceType, std::string> IndexKey; | |
136 typedef std::map<IndexKey, DcmDirectoryRecord* > Index; | |
137 Index index_; | |
138 | |
139 | |
140 DcmDicomDir& GetDicomDir() | |
141 { | |
142 if (dir_.get() == NULL) | |
143 { | |
144 dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), | |
145 fileSetId_.c_str())); | |
146 //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); | |
147 } | |
148 | |
149 return *dir_; | |
150 } | |
151 | |
152 | |
153 DcmDirectoryRecord& GetRoot() | |
154 { | |
155 return GetDicomDir().getRootRecord(); | |
156 } | |
157 | |
158 | |
159 static bool GetUtf8TagValue(std::string& result, | |
160 DcmItem& source, | |
161 Encoding encoding, | |
162 const DcmTagKey& key) | |
163 { | |
164 DcmElement* element = NULL; | |
165 | |
166 if (source.findAndGetElement(key, element).good()) | |
167 { | |
168 char* s = NULL; | |
169 if (element->isLeaf() && | |
170 element->getString(s).good() && | |
171 s != NULL) | |
172 { | |
173 result = Toolbox::ConvertToUtf8(s, encoding); | |
174 return true; | |
175 } | |
176 } | |
177 | |
178 result.clear(); | |
179 return false; | |
180 } | |
181 | |
182 | |
183 static void SetTagValue(DcmDirectoryRecord& target, | |
184 const DcmTagKey& key, | |
185 const std::string& valueUtf8) | |
186 { | |
187 std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); | |
188 | |
189 if (!target.putAndInsertString(key, s.c_str()).good()) | |
190 { | |
191 throw OrthancException(ErrorCode_InternalError); | |
192 } | |
193 } | |
194 | |
195 | |
196 | |
197 static bool CopyString(DcmDirectoryRecord& target, | |
198 DcmDataset& source, | |
199 Encoding encoding, | |
200 const DcmTagKey& key, | |
201 bool optional, | |
202 bool copyEmpty) | |
203 { | |
204 if (optional && | |
205 !source.tagExistsWithValue(key) && | |
206 !(copyEmpty && source.tagExists(key))) | |
207 { | |
208 return false; | |
209 } | |
210 | |
211 std::string value; | |
212 bool found = GetUtf8TagValue(value, source, encoding, key); | |
213 | |
214 SetTagValue(target, key, value); | |
215 return found; | |
216 } | |
217 | |
218 | |
219 static void CopyStringType1(DcmDirectoryRecord& target, | |
220 DcmDataset& source, | |
221 Encoding encoding, | |
222 const DcmTagKey& key) | |
223 { | |
224 CopyString(target, source, encoding, key, false, false); | |
225 } | |
226 | |
227 static void CopyStringType1C(DcmDirectoryRecord& target, | |
228 DcmDataset& source, | |
229 Encoding encoding, | |
230 const DcmTagKey& key) | |
231 { | |
232 CopyString(target, source, encoding, key, true, false); | |
233 } | |
234 | |
235 static void CopyStringType2(DcmDirectoryRecord& target, | |
236 DcmDataset& source, | |
237 Encoding encoding, | |
238 const DcmTagKey& key) | |
239 { | |
240 CopyString(target, source, encoding, key, false, true); | |
241 } | |
242 | |
243 | |
244 public: | |
245 PImpl() : fileSetId_("ORTHANC_MEDIA") | |
246 { | |
247 } | |
248 | |
249 void FillPatient(DcmDirectoryRecord& record, | |
250 DcmDataset& dicom, | |
251 Encoding encoding) | |
252 { | |
253 // cf. "DicomDirInterface::buildPatientRecord()" | |
254 | |
255 CopyStringType1C(record, dicom, encoding, DCM_PatientID); | |
256 CopyStringType2(record, dicom, encoding, DCM_PatientName); | |
257 } | |
258 | |
259 void FillStudy(DcmDirectoryRecord& record, | |
260 DcmDataset& dicom, | |
261 Encoding encoding) | |
262 { | |
263 // cf. "DicomDirInterface::buildStudyRecord()" | |
264 | |
265 std::string nowDate, nowTime; | |
266 SystemToolbox::GetNowDicom(nowDate, nowTime); | |
267 | |
268 std::string studyDate; | |
269 if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && | |
270 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && | |
271 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && | |
272 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) | |
273 { | |
274 studyDate = nowDate; | |
275 } | |
276 | |
277 std::string studyTime; | |
278 if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && | |
279 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && | |
280 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && | |
281 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) | |
282 { | |
283 studyTime = nowTime; | |
284 } | |
285 | |
286 /* copy attribute values from dataset to study record */ | |
287 SetTagValue(record, DCM_StudyDate, studyDate); | |
288 SetTagValue(record, DCM_StudyTime, studyTime); | |
289 CopyStringType2(record, dicom, encoding, DCM_StudyDescription); | |
290 CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); | |
291 /* use type 1C instead of 1 in order to avoid unwanted overwriting */ | |
292 CopyStringType1C(record, dicom, encoding, DCM_StudyID); | |
293 CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); | |
294 } | |
295 | |
296 void FillSeries(DcmDirectoryRecord& record, | |
297 DcmDataset& dicom, | |
298 Encoding encoding) | |
299 { | |
300 // cf. "DicomDirInterface::buildSeriesRecord()" | |
301 | |
302 /* copy attribute values from dataset to series record */ | |
303 CopyStringType1(record, dicom, encoding, DCM_Modality); | |
304 CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); | |
305 /* use type 1C instead of 1 in order to avoid unwanted overwriting */ | |
306 CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); | |
307 } | |
308 | |
309 void FillInstance(DcmDirectoryRecord& record, | |
310 DcmDataset& dicom, | |
311 Encoding encoding, | |
312 DcmMetaInfo& metaInfo, | |
313 const char* path) | |
314 { | |
315 // cf. "DicomDirInterface::buildImageRecord()" | |
316 | |
317 /* copy attribute values from dataset to image record */ | |
318 CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); | |
319 //CopyElementType1C(record, dicom, encoding, DCM_ImageType); | |
320 | |
321 // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); | |
322 | |
323 std::string sopClassUid, sopInstanceUid, transferSyntaxUid; | |
324 if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || | |
325 !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || | |
326 !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) | |
327 { | |
328 throw OrthancException(ErrorCode_BadFileFormat); | |
329 } | |
330 | |
331 SetTagValue(record, DCM_ReferencedFileID, path); | |
332 SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); | |
333 SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); | |
334 SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); | |
335 } | |
336 | |
337 | |
338 | |
339 bool CreateResource(DcmDirectoryRecord*& target, | |
340 ResourceType level, | |
341 ParsedDicomFile& dicom, | |
342 const char* filename, | |
343 const char* path) | |
344 { | |
345 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); | |
346 Encoding encoding = dicom.GetEncoding(); | |
347 | |
348 bool found; | |
349 std::string id; | |
350 E_DirRecType type; | |
351 | |
352 switch (level) | |
353 { | |
354 case ResourceType_Patient: | |
355 found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID); | |
356 type = ERT_Patient; | |
357 break; | |
358 | |
359 case ResourceType_Study: | |
360 found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); | |
361 type = ERT_Study; | |
362 break; | |
363 | |
364 case ResourceType_Series: | |
365 found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); | |
366 type = ERT_Series; | |
367 break; | |
368 | |
369 case ResourceType_Instance: | |
370 found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); | |
371 type = ERT_Image; | |
372 break; | |
373 | |
374 default: | |
375 throw OrthancException(ErrorCode_InternalError); | |
376 } | |
377 | |
378 if (!found) | |
379 { | |
380 throw OrthancException(ErrorCode_BadFileFormat); | |
381 } | |
382 | |
383 IndexKey key = std::make_pair(level, std::string(id.c_str())); | |
384 Index::iterator it = index_.find(key); | |
385 | |
386 if (it != index_.end()) | |
387 { | |
388 target = it->second; | |
389 return false; // Already existing | |
390 } | |
391 | |
392 std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); | |
393 | |
394 switch (level) | |
395 { | |
396 case ResourceType_Patient: | |
397 FillPatient(*record, dataset, encoding); | |
398 break; | |
399 | |
400 case ResourceType_Study: | |
401 FillStudy(*record, dataset, encoding); | |
402 break; | |
403 | |
404 case ResourceType_Series: | |
405 FillSeries(*record, dataset, encoding); | |
406 break; | |
407 | |
408 case ResourceType_Instance: | |
409 FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); | |
410 break; | |
411 | |
412 default: | |
413 throw OrthancException(ErrorCode_InternalError); | |
414 } | |
415 | |
416 CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); | |
417 | |
418 target = record.get(); | |
419 GetRoot().insertSub(record.release()); | |
420 index_[key] = target; | |
421 | |
422 return true; // Newly created | |
423 } | |
424 | |
425 void Read(std::string& s) | |
426 { | |
427 if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, | |
428 EET_UndefinedLength /*encodingType*/, | |
429 EGL_withoutGL /*groupLength*/).good()) | |
430 { | |
431 throw OrthancException(ErrorCode_InternalError); | |
432 } | |
433 | |
434 file_.Read(s); | |
435 } | |
436 | |
437 void SetFileSetId(const std::string& id) | |
438 { | |
439 dir_.reset(NULL); | |
440 fileSetId_ = id; | |
441 } | |
442 }; | |
443 | |
444 | |
445 DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) | |
446 { | |
447 } | |
448 | |
449 DicomDirWriter::~DicomDirWriter() | |
450 { | |
451 if (pimpl_) | |
452 { | |
453 delete pimpl_; | |
454 } | |
455 } | |
456 | |
457 void DicomDirWriter::SetFileSetId(const std::string& id) | |
458 { | |
459 pimpl_->SetFileSetId(id); | |
460 } | |
461 | |
462 void DicomDirWriter::Add(const std::string& directory, | |
463 const std::string& filename, | |
464 ParsedDicomFile& dicom) | |
465 { | |
466 std::string path; | |
467 if (directory.empty()) | |
468 { | |
469 path = filename; | |
470 } | |
471 else | |
472 { | |
473 if (directory[directory.length() - 1] == '/' || | |
474 directory[directory.length() - 1] == '\\') | |
475 { | |
476 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
477 } | |
478 | |
479 path = directory + '\\' + filename; | |
480 } | |
481 | |
482 DcmDirectoryRecord* instance; | |
483 bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); | |
484 if (isNewInstance) | |
485 { | |
486 DcmDirectoryRecord* series; | |
487 bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); | |
488 series->insertSub(instance); | |
489 | |
490 if (isNewSeries) | |
491 { | |
492 DcmDirectoryRecord* study; | |
493 bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); | |
494 study->insertSub(series); | |
495 | |
496 if (isNewStudy) | |
497 { | |
498 DcmDirectoryRecord* patient; | |
499 pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); | |
500 patient->insertSub(study); | |
501 } | |
502 } | |
503 } | |
504 } | |
505 | |
506 void DicomDirWriter::Encode(std::string& target) | |
507 { | |
508 pimpl_->Read(target); | |
509 } | |
510 } |