comparison OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp @ 4696:dd6274412ff4

new configuration option "ExternalDictionaries" to load external DICOM dictionaries
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 17 Jun 2021 15:47:21 +0200
parents 651f069c7f77
children 569d9ef165b1
comparison
equal deleted inserted replaced
4695:651f069c7f77 4696:dd6274412ff4
115 # include <dcmtk/dcmdata/dcrleerg.h> 115 # include <dcmtk/dcmdata/dcrleerg.h>
116 # include <dcmtk/dcmimage/diregist.h> // include to support color images 116 # include <dcmtk/dcmimage/diregist.h> // include to support color images
117 #endif 117 #endif
118 118
119 119
120 static bool hasExternalDictionaries_ = false;
121
122
120 namespace Orthanc 123 namespace Orthanc
121 { 124 {
122 static bool IsBinaryTag(const DcmTag& key) 125 static bool IsBinaryTag(const DcmTag& key)
123 { 126 {
124 return (key.isUnknownVR() || 127 return (key.isUnknownVR() ||
224 ss << std::setprecision(17) << v; 227 ss << std::setprecision(17) << v;
225 return ss.str(); 228 return ss.str();
226 } 229 }
227 230
228 231
229 #define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter, toStringFunction) \ 232 #define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter, toStringFunction) \
230 \ 233 \
231 struct converter \ 234 struct converter \
232 { \ 235 { \
233 typedef cType CType; \ 236 typedef cType CType; \
234 \ 237 \
235 ORTHANC_FORCE_INLINE \ 238 ORTHANC_FORCE_INLINE \
236 static bool Apply(CType& result, \ 239 static bool Apply(CType& result, \
237 DcmElement& element, \ 240 DcmElement& element, \
238 size_t i) \ 241 size_t i) \
239 { \ 242 { \
240 return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \ 243 return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \
241 } \ 244 } \
242 \ 245 \
243 ORTHANC_FORCE_INLINE \ 246 ORTHANC_FORCE_INLINE \
244 static std::string ToString(CType value) \ 247 static std::string ToString(CType value) \
245 { \ 248 { \
246 return toStringFunction(value); \ 249 return toStringFunction(value); \
247 } \ 250 } \
248 }; 251 };
249 252
283 } 286 }
284 287
285 288
286 void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) 289 void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
287 { 290 {
288 LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; 291 CLOG(INFO, DICOM) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER;
289 292
293 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
290 { 294 {
291 DictionaryLocker locker; 295 DictionaryLocker locker;
292 296
293 locker->clear(); 297 locker->clear();
294 298
295 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 299 CLOG(INFO, DICOM) << "Loading the embedded dictionaries";
296 LOG(INFO) << "Loading the embedded dictionaries";
297 /** 300 /**
298 * Do not load DICONDE dictionary, it breaks the other tags. The 301 * Do not load DICONDE dictionary, it breaks the other tags. The
299 * command "strace storescu 2>&1 |grep dic" shows that DICONDE 302 * command "strace storescu 2>&1 |grep dic" shows that DICONDE
300 * dictionary is not loaded by storescu. 303 * dictionary is not loaded by storescu.
301 **/ 304 **/
303 306
304 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM); 307 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM);
305 308
306 if (loadPrivateDictionary) 309 if (loadPrivateDictionary)
307 { 310 {
308 LOG(INFO) << "Loading the embedded dictionary of private tags"; 311 CLOG(INFO, DICOM) << "Loading the embedded dictionary of private tags";
309 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE); 312 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE);
310 } 313 }
311 else 314 else
312 { 315 {
313 LOG(INFO) << "The dictionary of private tags has not been loaded"; 316 CLOG(INFO, DICOM) << "The dictionary of private tags has not been loaded";
314 } 317 }
315 318 }
316 #else 319 #else
320 {
317 std::vector<std::string> dictionaries; 321 std::vector<std::string> dictionaries;
318 322
319 const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); 323 const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
320 if (env != NULL) 324 if (env != NULL)
321 { 325 {
329 } 333 }
330 else 334 else
331 { 335 {
332 boost::filesystem::path base = DCMTK_DICTIONARY_DIR; 336 boost::filesystem::path base = DCMTK_DICTIONARY_DIR;
333 dictionaries.push_back((base / "dicom.dic").string()); 337 dictionaries.push_back((base / "dicom.dic").string());
334 dictionaries.push_back((base / "private.dic").string()); 338
335 } 339 if (loadPrivateDictionary)
336 340 {
337 for (size_t i = 0; i < dictionaries.size(); i++) 341 dictionaries.push_back((base / "private.dic").string());
338 { 342 }
339 LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\""; 343 }
340 344
341 if (!locker->loadDictionary(dictionaries[i].c_str())) 345 LoadExternalDictionaries(dictionaries);
342 { 346 hasExternalDictionaries_ = false; // Fix the side-effect of "LoadExternalDictionaries()"
343 throw OrthancException(ErrorCode_InexistentFile); 347 }
344 }
345 }
346
347 #endif 348 #endif
348 }
349 349
350 /* make sure data dictionary is loaded */ 350 /* make sure data dictionary is loaded */
351 if (!dcmDataDict.isDictionaryLoaded()) 351 if (!dcmDataDict.isDictionaryLoaded())
352 { 352 {
353 throw OrthancException(ErrorCode_InternalError, 353 throw OrthancException(ErrorCode_InternalError,
362 { 362 {
363 throw OrthancException(ErrorCode_InternalError, 363 throw OrthancException(ErrorCode_InternalError,
364 "The DICOM dictionary has not been correctly read"); 364 "The DICOM dictionary has not been correctly read");
365 } 365 }
366 } 366 }
367 }
368
369
370 void FromDcmtkBridge::LoadExternalDictionaries(const std::vector<std::string>& dictionaries)
371 {
372 DictionaryLocker locker;
373
374 CLOG(INFO, DICOM) << "Clearing the DICOM dictionary";
375 locker->clear();
376
377 for (size_t i = 0; i < dictionaries.size(); i++)
378 {
379 LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\"";
380
381 if (!locker->loadDictionary(dictionaries[i].c_str()))
382 {
383 throw OrthancException(ErrorCode_InexistentFile);
384 }
385 }
386
387 hasExternalDictionaries_ = true;
367 } 388 }
368 389
369 390
370 void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, 391 void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
371 ValueRepresentation vr, 392 ValueRepresentation vr,
390 throw OrthancException(ErrorCode_ParameterOutOfRange); 411 throw OrthancException(ErrorCode_ParameterOutOfRange);
391 } 412 }
392 413
393 DcmEVR evr = ToDcmtkBridge::Convert(vr); 414 DcmEVR evr = ToDcmtkBridge::Convert(vr);
394 415
395 LOG(INFO) << "Registering tag in dictionary: (" << tag.Format() << ") " 416 CLOG(INFO, DICOM) << "Registering tag in dictionary: (" << tag.Format() << ") "
396 << (DcmVR(evr).getValidVRName()) << " " 417 << (DcmVR(evr).getValidVRName()) << " "
397 << name << " (multiplicity: " << minMultiplicity << "-" 418 << name << " (multiplicity: " << minMultiplicity << "-"
398 << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; 419 << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
399 420
400 std::unique_ptr<DcmDictEntry> entry; 421 std::unique_ptr<DcmDictEntry> entry;
401 if (privateCreator.empty()) 422 if (privateCreator.empty())
402 { 423 {
403 if (tag.GetGroup() % 2 == 1) 424 if (tag.GetGroup() % 2 == 1)
981 1002
982 if (element.isLeaf()) 1003 if (element.isLeaf())
983 { 1004 {
984 // The "0" below lets "LeafValueToJson()" take care of "TooLong" values 1005 // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
985 std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement 1006 std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
986 (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength)); 1007 (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength));
987 1008
988 if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) 1009 if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
989 { 1010 {
990 LeafValueToJson(target, *v, format, flags, maxStringLength); 1011 LeafValueToJson(target, *v, format, flags, maxStringLength);
991 } 1012 }
1111 target = Json::objectValue; 1132 target = Json::objectValue;
1112 DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength, 0); 1133 DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength, 0);
1113 } 1134 }
1114 1135
1115 1136
1116
1117 static std::string GetTagNameInternal(DcmTag& tag) 1137 static std::string GetTagNameInternal(DcmTag& tag)
1118 { 1138 {
1119 { 1139 if (!hasExternalDictionaries_)
1120 // Some patches for important tags because of different DICOM 1140 {
1121 // dictionaries between DCMTK versions 1141 /**
1142 * Some patches for important tags because of different DICOM
1143 * dictionaries between DCMTK versions. Since Orthanc 1.9.4, we
1144 * don't apply these patches if external dictionaries are
1145 * loaded, notably for compatibility with DICONDE. In Orthanc <=
1146 * 1.9.3, this was done by method "DicomTag::GetMainTagsName()".
1147 **/
1148
1122 DicomTag tmp(tag.getGroup(), tag.getElement()); 1149 DicomTag tmp(tag.getGroup(), tag.getElement());
1123 std::string n = tmp.GetMainTagsName(); 1150
1124 if (n.size() != 0) 1151 if (tmp == DICOM_TAG_ACCESSION_NUMBER)
1125 { 1152 return "AccessionNumber";
1126 return n; 1153
1127 } 1154 if (tmp == DICOM_TAG_SOP_INSTANCE_UID)
1155 return "SOPInstanceUID";
1156
1157 if (tmp == DICOM_TAG_PATIENT_ID)
1158 return "PatientID";
1159
1160 if (tmp == DICOM_TAG_SERIES_INSTANCE_UID)
1161 return "SeriesInstanceUID";
1162
1163 if (tmp == DICOM_TAG_STUDY_INSTANCE_UID)
1164 return "StudyInstanceUID";
1165
1166 if (tmp == DICOM_TAG_PIXEL_DATA)
1167 return "PixelData";
1168
1169 if (tmp == DICOM_TAG_IMAGE_INDEX)
1170 return "ImageIndex";
1171
1172 if (tmp == DICOM_TAG_INSTANCE_NUMBER)
1173 return "InstanceNumber";
1174
1175 if (tmp == DICOM_TAG_NUMBER_OF_SLICES)
1176 return "NumberOfSlices";
1177
1178 if (tmp == DICOM_TAG_NUMBER_OF_FRAMES)
1179 return "NumberOfFrames";
1180
1181 if (tmp == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)
1182 return "CardiacNumberOfImages";
1183
1184 if (tmp == DICOM_TAG_IMAGES_IN_ACQUISITION)
1185 return "ImagesInAcquisition";
1186
1187 if (tmp == DICOM_TAG_PATIENT_NAME)
1188 return "PatientName";
1189
1190 if (tmp == DICOM_TAG_IMAGE_POSITION_PATIENT)
1191 return "ImagePositionPatient";
1192
1193 if (tmp == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
1194 return "ImageOrientationPatient";
1195
1196 // New in Orthanc 1.6.0, as tagged as "RETIRED_" since DCMTK 3.6.4
1197 if (tmp == DICOM_TAG_OTHER_PATIENT_IDS)
1198 return "OtherPatientIDs";
1199
1128 // End of patches 1200 // End of patches
1129 } 1201 }
1130 1202
1131 #if 0 1203 #if 0
1132 // This version explicitly calls the dictionary 1204 // This version explicitly calls the dictionary
1215 { 1287 {
1216 return DicomTag(tag.getGTag(), tag.getETag()); 1288 return DicomTag(tag.getGTag(), tag.getETag());
1217 } 1289 }
1218 else 1290 else
1219 { 1291 {
1220 LOG(INFO) << "Unknown DICOM tag: \"" << name << "\""; 1292 CLOG(INFO, DICOM) << "Unknown DICOM tag: \"" << name << "\"";
1221 throw OrthancException(ErrorCode_UnknownDicomTag); 1293 throw OrthancException(ErrorCode_UnknownDicomTag);
1222 } 1294 }
1223 #endif 1295 #endif
1224 } 1296 }
1225 1297
1439 { 1511 {
1440 dicom.removeInvalidGroups(); 1512 dicom.removeInvalidGroups();
1441 1513
1442 if (known) 1514 if (known)
1443 { 1515 {
1444 LOG(INFO) << "Transcoded an image from transfer syntax " 1516 CLOG(INFO, DICOM) << "Transcoded an image from transfer syntax "
1445 << GetTransferSyntaxUid(sourceSyntax) << " to " 1517 << GetTransferSyntaxUid(sourceSyntax) << " to "
1446 << GetTransferSyntaxUid(syntax); 1518 << GetTransferSyntaxUid(syntax);
1447 } 1519 }
1448 else 1520 else
1449 { 1521 {
1450 LOG(INFO) << "Transcoded an image from unknown transfer syntax to " 1522 CLOG(INFO, DICOM) << "Transcoded an image from unknown transfer syntax to "
1451 << GetTransferSyntaxUid(syntax); 1523 << GetTransferSyntaxUid(syntax);
1452 } 1524 }
1453 1525
1454 return true; 1526 return true;
1455 } 1527 }
1456 } 1528 }
1505 1577
1506 case EVR_OB: 1578 case EVR_OB:
1507 return ValueRepresentation_OtherByte; 1579 return ValueRepresentation_OtherByte;
1508 1580
1509 #if DCMTK_VERSION_NUMBER >= 361 1581 #if DCMTK_VERSION_NUMBER >= 361
1510 case EVR_OD: 1582 case EVR_OD:
1511 return ValueRepresentation_OtherDouble; 1583 return ValueRepresentation_OtherDouble;
1512 #endif 1584 #endif
1513 1585
1514 case EVR_OF: 1586 case EVR_OF:
1515 return ValueRepresentation_OtherFloat; 1587 return ValueRepresentation_OtherFloat;
1516 1588
1517 #if DCMTK_VERSION_NUMBER >= 362 1589 #if DCMTK_VERSION_NUMBER >= 362
1518 case EVR_OL: 1590 case EVR_OL:
1519 return ValueRepresentation_OtherLong; 1591 return ValueRepresentation_OtherLong;
1520 #endif 1592 #endif
1521 1593
1522 case EVR_OW: 1594 case EVR_OW:
1523 return ValueRepresentation_OtherWord; 1595 return ValueRepresentation_OtherWord;
1524 1596
1693 1765
1694 case EVR_UN: // unknown value representation 1766 case EVR_UN: // unknown value representation
1695 throw OrthancException(ErrorCode_ParameterOutOfRange); 1767 throw OrthancException(ErrorCode_ParameterOutOfRange);
1696 1768
1697 1769
1698 /** 1770 /**
1699 * String types. 1771 * String types.
1700 **/ 1772 **/
1701 1773
1702 case EVR_DS: // decimal string 1774 case EVR_DS: // decimal string
1703 case EVR_IS: // integer string 1775 case EVR_IS: // integer string
1704 case EVR_AS: // age string 1776 case EVR_AS: // age string
1705 case EVR_DA: // date string 1777 case EVR_DA: // date string
2187 2259
2188 2260
2189 void FromDcmtkBridge::InitializeCodecs() 2261 void FromDcmtkBridge::InitializeCodecs()
2190 { 2262 {
2191 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 2263 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
2192 LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK"; 2264 CLOG(INFO, DICOM) << "Registering JPEG Lossless codecs in DCMTK";
2193 DJLSDecoderRegistration::registerCodecs(); 2265 DJLSDecoderRegistration::registerCodecs();
2194 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 2266 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2195 DJLSEncoderRegistration::registerCodecs(); 2267 DJLSEncoderRegistration::registerCodecs();
2196 # endif 2268 # endif
2197 #endif 2269 #endif
2198 2270
2199 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 2271 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2200 LOG(INFO) << "Registering JPEG codecs in DCMTK"; 2272 CLOG(INFO, DICOM) << "Registering JPEG codecs in DCMTK";
2201 DJDecoderRegistration::registerCodecs(); 2273 DJDecoderRegistration::registerCodecs();
2202 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 2274 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2203 DJEncoderRegistration::registerCodecs(); 2275 DJEncoderRegistration::registerCodecs();
2204 # endif 2276 # endif
2205 #endif 2277 #endif
2206 2278
2207 LOG(INFO) << "Registering RLE codecs in DCMTK"; 2279 CLOG(INFO, DICOM) << "Registering RLE codecs in DCMTK";
2208 DcmRLEDecoderRegistration::registerCodecs(); 2280 DcmRLEDecoderRegistration::registerCodecs();
2209 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 2281 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2210 DcmRLEEncoderRegistration::registerCodecs(); 2282 DcmRLEEncoderRegistration::registerCodecs();
2211 #endif 2283 #endif
2212 } 2284 }