Mercurial > hg > orthanc
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 } |