comparison OrthancServer/DicomDirWriter.cpp @ 1885:5be57564ffc4

refactoring dicomdir generation
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 11 Dec 2015 15:43:33 +0100
parents 6a2d507ef064
children d7f63122c7f3
comparison
equal deleted inserted replaced
1883:71356f41ec2f 1885:5be57564ffc4
132 typedef std::pair<ResourceType, std::string> IndexKey; 132 typedef std::pair<ResourceType, std::string> IndexKey;
133 typedef std::map<IndexKey, DcmDirectoryRecord* > Index; 133 typedef std::map<IndexKey, DcmDirectoryRecord* > Index;
134 Index index_; 134 Index index_;
135 135
136 136
137 /*******************************************************************************
138 * Functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0
139 *******************************************************************************/
140
141 // print an error message to the console (stderr) that something went wrong with an attribute
142 static void printAttributeErrorMessage(const DcmTagKey &key,
143 const OFCondition &error,
144 const char *operation)
145 {
146 if (error.bad())
147 {
148 OFString str;
149 if (operation != NULL)
150 {
151 str = "cannot ";
152 str += operation;
153 str += " ";
154 }
155 LOG(ERROR) << error.text() << ": " << str << DcmTag(key).getTagName() << " " << key;
156 }
157 }
158
159 // copy element from dataset to directory record
160 static void copyElement(DcmItem& dataset,
161 const DcmTagKey &key,
162 DcmDirectoryRecord& record,
163 const OFBool optional,
164 const OFBool copyEmpty)
165 {
166 /* check whether tag exists in source dataset (if optional) */
167 if (!optional || (copyEmpty && dataset.tagExists(key)) || dataset.tagExistsWithValue(key))
168 {
169 DcmElement *delem = NULL;
170 /* get copy of element from source dataset */
171 OFCondition status = dataset.findAndGetElement(key, delem, OFFalse /*searchIntoSub*/, OFTrue /*createCopy*/);
172 if (status.good())
173 {
174 /* ... and insert it into the destination dataset (record) */
175 status = record.insert(delem, OFTrue /*replaceOld*/);
176 if (status.good())
177 {
178 DcmTag tag(key);
179 /* check for correct VR in the dataset */
180 if (delem->getVR() != tag.getEVR())
181 {
182 /* create warning message */
183 LOG(WARNING) << "DICOMDIR: possibly wrong VR: "
184 << tag.getTagName() << " " << key << " with "
185 << DcmVR(delem->getVR()).getVRName() << " found, expected "
186 << tag.getVRName() << " instead";
187 }
188 } else
189 delete delem;
190 } else if (status == EC_TagNotFound)
191 status = record.insertEmptyElement(key);
192 printAttributeErrorMessage(key, status, "insert");
193 }
194 }
195
196 // copy optional string value from dataset to directory record
197 static void copyStringWithDefault(DcmItem& dataset,
198 const DcmTagKey &key,
199 DcmDirectoryRecord& record,
200 const char *defaultValue,
201 const OFBool printWarning)
202 {
203 OFCondition status;
204 if (dataset.tagExistsWithValue(key))
205 {
206 OFString stringValue;
207 /* retrieve string value from source dataset and put it into the destination dataset */
208 status = dataset.findAndGetOFStringArray(key, stringValue);
209 if (status.good())
210 status = record.putAndInsertString(key, stringValue.c_str());
211 } else {
212 if (printWarning && (defaultValue != NULL))
213 {
214 /* create warning message */
215 LOG(WARNING) << "DICOMDIR: " << DcmTag(key).getTagName() << " "
216 << key << " missing, using alternative: " << defaultValue;
217 }
218 /* put default value */
219 status = record.putAndInsertString(key, defaultValue);
220 }
221 }
222
223 // create alternative study date if absent in dataset
224 static OFString &alternativeStudyDate(DcmItem& dataset,
225 OFString &result)
226 {
227 /* use another date if present */
228 if (dataset.findAndGetOFStringArray(DCM_SeriesDate, result).bad() || result.empty())
229 {
230 if (dataset.findAndGetOFStringArray(DCM_AcquisitionDate, result).bad() || result.empty())
231 {
232 if (dataset.findAndGetOFStringArray(DCM_ContentDate, result).bad() || result.empty())
233 {
234 /* use current date, "19000101" in case of error */
235 DcmDate::getCurrentDate(result);
236 }
237 }
238 }
239 return result;
240 }
241
242
243 // create alternative study time if absent in dataset
244 static OFString &alternativeStudyTime(DcmItem& dataset,
245 OFString &result)
246 {
247 /* use another time if present */
248 if (dataset.findAndGetOFStringArray(DCM_SeriesTime, result).bad() || result.empty())
249 {
250 if (dataset.findAndGetOFStringArray(DCM_AcquisitionTime, result).bad() || result.empty())
251 {
252 if (dataset.findAndGetOFStringArray(DCM_ContentTime, result).bad() || result.empty())
253 {
254 /* use current time, "0000" in case of error */
255 DcmTime::getCurrentTime(result);
256 }
257 }
258 }
259 return result;
260 }
261
262
263 static void copyElementType1(DcmItem& dataset,
264 const DcmTagKey &key,
265 DcmDirectoryRecord& record)
266 {
267 copyElement(dataset, key, record, OFFalse /*optional*/, OFFalse /*copyEmpty*/);
268 }
269
270 static void copyElementType1C(DcmItem& dataset,
271 const DcmTagKey &key,
272 DcmDirectoryRecord& record)
273 {
274 copyElement(dataset, key, record, OFTrue /*optional*/, OFFalse /*copyEmpty*/);
275 }
276
277 static void copyElementType2(DcmItem& dataset,
278 const DcmTagKey &key,
279 DcmDirectoryRecord& record)
280 {
281 copyElement(dataset, key, record, OFFalse /*optional*/, OFTrue /*copyEmpty*/);
282 }
283
284 /*******************************************************************************
285 * End of functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0
286 *******************************************************************************/
287
288
289 DcmDicomDir& GetDicomDir() 137 DcmDicomDir& GetDicomDir()
290 { 138 {
291 if (dir_.get() == NULL) 139 if (dir_.get() == NULL)
292 { 140 {
293 dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 141 dir_.reset(new DcmDicomDir(file_.GetPath().c_str(),
294 fileSetId_.c_str())); 142 fileSetId_.c_str()));
143 //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8));
295 } 144 }
296 145
297 return *dir_; 146 return *dir_;
298 } 147 }
299 148
300 149
301 DcmDirectoryRecord& GetRoot() 150 DcmDirectoryRecord& GetRoot()
302 { 151 {
303 return GetDicomDir().getRootRecord(); 152 return GetDicomDir().getRootRecord();
153 }
154
155
156 static bool GetUtf8TagValue(std::string& result,
157 DcmItem& source,
158 Encoding encoding,
159 const DcmTagKey& key)
160 {
161 DcmElement* element = NULL;
162 char* s = NULL;
163
164 if (source.findAndGetElement(key, element).good())
165 {
166 if (element->isLeaf() &&
167 element->getString(s).good() &&
168 s != NULL)
169 {
170 result = Toolbox::ConvertToUtf8(s, encoding);
171 return true;
172 }
173 }
174
175 result.clear();
176 return false;
177 }
178
179
180 static void SetTagValue(DcmDirectoryRecord& target,
181 const DcmTagKey& key,
182 const std::string& valueUtf8)
183 {
184 std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii);
185
186 if (!target.putAndInsertString(key, s.c_str()).good())
187 {
188 throw OrthancException(ErrorCode_InternalError);
189 }
190 }
191
192
193
194 static bool CopyString(DcmDirectoryRecord& target,
195 DcmDataset& source,
196 Encoding encoding,
197 const DcmTagKey& key,
198 bool optional,
199 bool copyEmpty)
200 {
201 if (optional &&
202 !source.tagExistsWithValue(key) &&
203 !(copyEmpty && source.tagExists(key)))
204 {
205 return false;
206 }
207
208 std::string value;
209 bool found = GetUtf8TagValue(value, source, encoding, key);
210
211 SetTagValue(target, key, value);
212 return found;
213 }
214
215
216 static void CopyStringType1(DcmDirectoryRecord& target,
217 DcmDataset& source,
218 Encoding encoding,
219 const DcmTagKey& key)
220 {
221 CopyString(target, source, encoding, key, false, false);
222 }
223
224 static void CopyStringType1C(DcmDirectoryRecord& target,
225 DcmDataset& source,
226 Encoding encoding,
227 const DcmTagKey& key)
228 {
229 CopyString(target, source, encoding, key, true, false);
230 }
231
232 static void CopyStringType2(DcmDirectoryRecord& target,
233 DcmDataset& source,
234 Encoding encoding,
235 const DcmTagKey& key)
236 {
237 CopyString(target, source, encoding, key, false, true);
304 } 238 }
305 239
306 240
307 public: 241 public:
308 PImpl() : fileSetId_("ORTHANC_MEDIA") 242 PImpl() : fileSetId_("ORTHANC_MEDIA")
309 { 243 {
310 } 244 }
311 245
312 void FillPatient(DcmDirectoryRecord& record, 246 void FillPatient(DcmDirectoryRecord& record,
313 DcmItem& dicom) 247 DcmDataset& dicom,
248 Encoding encoding)
314 { 249 {
315 // cf. "DicomDirInterface::buildPatientRecord()" 250 // cf. "DicomDirInterface::buildPatientRecord()"
316 251
317 copyElementType1C(dicom, DCM_PatientID, record); 252 CopyStringType1C(record, dicom, encoding, DCM_PatientID);
318 copyElementType2(dicom, DCM_PatientName, record); 253 CopyStringType2(record, dicom, encoding, DCM_PatientName);
319 } 254 }
320 255
321 void FillStudy(DcmDirectoryRecord& record, 256 void FillStudy(DcmDirectoryRecord& record,
322 DcmItem& dicom) 257 DcmDataset& dicom,
258 Encoding encoding)
323 { 259 {
324 // cf. "DicomDirInterface::buildStudyRecord()" 260 // cf. "DicomDirInterface::buildStudyRecord()"
325 261
326 OFString tmpString; 262 std::string nowDate, nowTime;
263 Toolbox::GetNowDicom(nowDate, nowTime);
264
265 std::string studyDate;
266 if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) &&
267 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) &&
268 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) &&
269 !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate))
270 {
271 studyDate = nowDate;
272 }
273
274 std::string studyTime;
275 if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) &&
276 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) &&
277 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) &&
278 !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime))
279 {
280 studyTime = nowTime;
281 }
282
327 /* copy attribute values from dataset to study record */ 283 /* copy attribute values from dataset to study record */
328 copyStringWithDefault(dicom, DCM_StudyDate, record, 284 SetTagValue(record, DCM_StudyDate, studyDate);
329 alternativeStudyDate(dicom, tmpString).c_str(), OFTrue /*printWarning*/); 285 SetTagValue(record, DCM_StudyTime, studyTime);
330 copyStringWithDefault(dicom, DCM_StudyTime, record, 286 CopyStringType2(record, dicom, encoding, DCM_StudyDescription);
331 alternativeStudyTime(dicom, tmpString).c_str(), OFTrue /*printWarning*/); 287 CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID);
332 copyElementType2(dicom, DCM_StudyDescription, record);
333 copyElementType1(dicom, DCM_StudyInstanceUID, record);
334 /* use type 1C instead of 1 in order to avoid unwanted overwriting */ 288 /* use type 1C instead of 1 in order to avoid unwanted overwriting */
335 copyElementType1C(dicom, DCM_StudyID, record); 289 CopyStringType1C(record, dicom, encoding, DCM_StudyID);
336 copyElementType2(dicom, DCM_AccessionNumber, record); 290 CopyStringType2(record, dicom, encoding, DCM_AccessionNumber);
337 } 291 }
338 292
339 void FillSeries(DcmDirectoryRecord& record, 293 void FillSeries(DcmDirectoryRecord& record,
340 DcmItem& dicom) 294 DcmDataset& dicom,
295 Encoding encoding)
341 { 296 {
342 // cf. "DicomDirInterface::buildSeriesRecord()" 297 // cf. "DicomDirInterface::buildSeriesRecord()"
343 298
344 /* copy attribute values from dataset to series record */ 299 /* copy attribute values from dataset to series record */
345 copyElementType1(dicom, DCM_Modality, record); 300 CopyStringType1(record, dicom, encoding, DCM_Modality);
346 copyElementType1(dicom, DCM_SeriesInstanceUID, record); 301 CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID);
347 /* use type 1C instead of 1 in order to avoid unwanted overwriting */ 302 /* use type 1C instead of 1 in order to avoid unwanted overwriting */
348 copyElementType1C(dicom, DCM_SeriesNumber, record); 303 CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber);
349 } 304 }
350 305
351 void FillInstance(DcmDirectoryRecord& record, 306 void FillInstance(DcmDirectoryRecord& record,
352 DcmItem& dicom, 307 DcmDataset& dicom,
308 Encoding encoding,
353 DcmMetaInfo& metaInfo, 309 DcmMetaInfo& metaInfo,
354 const char* path) 310 const char* path)
355 { 311 {
356 // cf. "DicomDirInterface::buildImageRecord()" 312 // cf. "DicomDirInterface::buildImageRecord()"
357 313
358 /* copy attribute values from dataset to image record */ 314 /* copy attribute values from dataset to image record */
359 copyElementType1(dicom, DCM_InstanceNumber, record); 315 CopyStringType1(record, dicom, encoding, DCM_InstanceNumber);
360 //copyElementType1C(dicom, DCM_ImageType, record); 316 //CopyElementType1C(record, dicom, encoding, DCM_ImageType);
361 copyElementType1C(dicom, DCM_ReferencedImageSequence, record); 317
362 318 // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record);
363 OFString tmp; 319
364 320 std::string sopClassUid, sopInstanceUid, transferSyntaxUid;
365 DcmElement* item = record.remove(DCM_ReferencedImageSequence); 321 if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) ||
366 if (item != NULL) 322 !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) ||
367 { 323 !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID))
368 delete item;
369 }
370
371 if (record.putAndInsertString(DCM_ReferencedFileID, path).bad() ||
372 dicom.findAndGetOFStringArray(DCM_SOPClassUID, tmp).bad() ||
373 record.putAndInsertString(DCM_ReferencedSOPClassUIDInFile, tmp.c_str()).bad() ||
374 dicom.findAndGetOFStringArray(DCM_SOPInstanceUID, tmp).bad() ||
375 record.putAndInsertString(DCM_ReferencedSOPInstanceUIDInFile, tmp.c_str()).bad() ||
376 metaInfo.findAndGetOFStringArray(DCM_TransferSyntaxUID, tmp).bad() ||
377 record.putAndInsertString(DCM_ReferencedTransferSyntaxUIDInFile, tmp.c_str()).bad())
378 { 324 {
379 throw OrthancException(ErrorCode_BadFileFormat); 325 throw OrthancException(ErrorCode_BadFileFormat);
380 } 326 }
327
328 SetTagValue(record, DCM_ReferencedFileID, path);
329 SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid);
330 SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid);
331 SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid);
381 } 332 }
382 333
383 334
384 335
385 bool CreateResource(DcmDirectoryRecord*& target, 336 bool CreateResource(DcmDirectoryRecord*& target,
386 ResourceType level, 337 ResourceType level,
387 DcmFileFormat& dicom, 338 ParsedDicomFile& dicom,
388 const char* filename, 339 const char* filename,
389 const char* path) 340 const char* path)
390 { 341 {
391 DcmDataset& dataset = *dicom.getDataset(); 342 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
392 343 Encoding encoding = dicom.GetEncoding();
393 OFCondition result; 344
394 OFString id; 345 bool found;
346 std::string id;
395 E_DirRecType type; 347 E_DirRecType type;
396 348
397 switch (level) 349 switch (level)
398 { 350 {
399 case ResourceType_Patient: 351 case ResourceType_Patient:
400 result = dataset.findAndGetOFString(DCM_PatientID, id); 352 found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID);
401 type = ERT_Patient; 353 type = ERT_Patient;
402 break; 354 break;
403 355
404 case ResourceType_Study: 356 case ResourceType_Study:
405 result = dataset.findAndGetOFString(DCM_StudyInstanceUID, id); 357 found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID);
406 type = ERT_Study; 358 type = ERT_Study;
407 break; 359 break;
408 360
409 case ResourceType_Series: 361 case ResourceType_Series:
410 result = dataset.findAndGetOFString(DCM_SeriesInstanceUID, id); 362 found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID);
411 type = ERT_Series; 363 type = ERT_Series;
412 break; 364 break;
413 365
414 case ResourceType_Instance: 366 case ResourceType_Instance:
415 result = dataset.findAndGetOFString(DCM_SOPInstanceUID, id); 367 found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID);
416 type = ERT_Image; 368 type = ERT_Image;
417 break; 369 break;
418 370
419 default: 371 default:
420 throw OrthancException(ErrorCode_InternalError); 372 throw OrthancException(ErrorCode_InternalError);
421 } 373 }
422 374
423 if (!result.good()) 375 if (!found)
424 { 376 {
425 throw OrthancException(ErrorCode_InternalError); 377 throw OrthancException(ErrorCode_BadFileFormat);
426 } 378 }
427 379
428 IndexKey key = std::make_pair(level, std::string(id.c_str())); 380 IndexKey key = std::make_pair(level, std::string(id.c_str()));
429 Index::iterator it = index_.find(key); 381 Index::iterator it = index_.find(key);
430 382
437 std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); 389 std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename));
438 390
439 switch (level) 391 switch (level)
440 { 392 {
441 case ResourceType_Patient: 393 case ResourceType_Patient:
442 FillPatient(*record, dataset); 394 FillPatient(*record, dataset, encoding);
443 break; 395 break;
444 396
445 case ResourceType_Study: 397 case ResourceType_Study:
446 FillStudy(*record, dataset); 398 FillStudy(*record, dataset, encoding);
447 break; 399 break;
448 400
449 case ResourceType_Series: 401 case ResourceType_Series:
450 FillSeries(*record, dataset); 402 FillSeries(*record, dataset, encoding);
451 break; 403 break;
452 404
453 case ResourceType_Instance: 405 case ResourceType_Instance:
454 FillInstance(*record, dataset, *dicom.getMetaInfo(), path); 406 FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path);
455 break; 407 break;
456 408
457 default: 409 default:
458 throw OrthancException(ErrorCode_InternalError); 410 throw OrthancException(ErrorCode_InternalError);
459 } 411 }
460 412
461 if (record->isAffectedBySpecificCharacterSet()) 413 CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet);
462 {
463 copyElementType1C(dataset, DCM_SpecificCharacterSet, *record);
464 }
465 414
466 target = record.get(); 415 target = record.get();
467 GetRoot().insertSub(record.release()); 416 GetRoot().insertSub(record.release());
468 index_[key] = target; 417 index_[key] = target;
469 418
525 } 474 }
526 475
527 path = directory + '\\' + filename; 476 path = directory + '\\' + filename;
528 } 477 }
529 478
530 DcmFileFormat& fileFormat = dicom.GetDcmtkObject();
531
532 DcmDirectoryRecord* instance; 479 DcmDirectoryRecord* instance;
533 bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, fileFormat, filename.c_str(), path.c_str()); 480 bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str());
534 if (isNewInstance) 481 if (isNewInstance)
535 { 482 {
536 DcmDirectoryRecord* series; 483 DcmDirectoryRecord* series;
537 bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, fileFormat, filename.c_str(), NULL); 484 bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL);
538 series->insertSub(instance); 485 series->insertSub(instance);
539 486
540 if (isNewSeries) 487 if (isNewSeries)
541 { 488 {
542 DcmDirectoryRecord* study; 489 DcmDirectoryRecord* study;
543 bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, fileFormat, filename.c_str(), NULL); 490 bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL);
544 study->insertSub(series); 491 study->insertSub(series);
545 492
546 if (isNewStudy) 493 if (isNewStudy)
547 { 494 {
548 DcmDirectoryRecord* patient; 495 DcmDirectoryRecord* patient;
549 pimpl_->CreateResource(patient, ResourceType_Patient, fileFormat, filename.c_str(), NULL); 496 pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL);
550 patient->insertSub(study); 497 patient->insertSub(study);
551 } 498 }
552 } 499 }
553 } 500 }
554 } 501 }