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 }