1# Copyright 2008-2018 pydicom authors. See LICENSE file for details. 2# -*- coding: utf-8 -*- 3"""Access dicom dictionary information""" 4 5from typing import Tuple, Optional, Dict 6 7from pydicom.config import logger 8from pydicom.tag import Tag, BaseTag, TagType 9 10# the actual dict of {tag: (VR, VM, name, is_retired, keyword), ...} 11from pydicom._dicom_dict import DicomDictionary 12 13# those with tags like "(50xx, 0005)" 14from pydicom._dicom_dict import RepeatersDictionary 15from pydicom._private_dict import private_dictionaries 16 17 18# Generate mask dict for checking repeating groups etc. 19# Map a true bitwise mask to the DICOM mask with "x"'s in it. 20masks: Dict[str, Tuple[int, int]] = {} 21for mask_x in RepeatersDictionary: 22 # mask1 is XOR'd to see that all non-"x" bits 23 # are identical (XOR result = 0 if bits same) 24 # then AND those out with 0 bits at the "x" 25 # ("we don't care") location using mask2 26 mask1 = int(mask_x.replace("x", "0"), 16) 27 mask2 = int("".join(["F0"[c == "x"] for c in mask_x]), 16) 28 masks[mask_x] = (mask1, mask2) 29 30 31def mask_match(tag: int) -> Optional[str]: 32 """Return the repeaters tag mask for `tag`. 33 34 Parameters 35 ---------- 36 tag : int 37 The tag to check. 38 39 Returns 40 ------- 41 str or None 42 If the tag is in the repeaters dictionary then returns the 43 corresponding masked tag, otherwise returns ``None``. 44 """ 45 for mask_x, (mask1, mask2) in masks.items(): 46 if (tag ^ mask1) & mask2 == 0: 47 return mask_x 48 return None 49 50 51def add_dict_entry( 52 tag: int, 53 VR: str, 54 keyword: str, 55 description: str, 56 VM: str = '1', 57 is_retired: str = '' 58) -> None: 59 """Update the DICOM dictionary with a new non-private entry. 60 61 Parameters 62 ---------- 63 tag : int 64 The tag number for the new dictionary entry. 65 VR : str 66 DICOM value representation. 67 description : str 68 The descriptive name used in printing the entry. Often the same as the 69 keyword, but with spaces between words. 70 VM : str, optional 71 DICOM value multiplicity. If not specified, then ``'1'`` is used. 72 is_retired : str, optional 73 Usually leave as blank string (default). Set to ``'Retired'`` if is a 74 retired data element. 75 76 Raises 77 ------ 78 ValueError 79 If the tag is a private tag. 80 81 Notes 82 ----- 83 Does not permanently update the dictionary, but only during run-time. 84 Will replace an existing entry if the tag already exists in the dictionary. 85 86 See Also 87 -------- 88 pydicom.examples.add_dict_entry 89 Example file which shows how to use this function 90 add_dict_entries 91 Update multiple values at once. 92 93 Examples 94 -------- 95 96 >>> from pydicom import Dataset 97 >>> add_dict_entry(0x10021001, "UL", "TestOne", "Test One") 98 >>> add_dict_entry(0x10021002, "DS", "TestTwo", "Test Two", VM='3') 99 >>> ds = Dataset() 100 >>> ds.TestOne = 'test' 101 >>> ds.TestTwo = ['1', '2', '3'] 102 103 """ 104 add_dict_entries({tag: (VR, VM, description, is_retired, keyword)}) 105 106 107def add_dict_entries( 108 new_entries_dict: Dict[int, Tuple[str, str, str, str, str]] 109) -> None: 110 """Update the DICOM dictionary with new non-private entries. 111 112 Parameters 113 ---------- 114 new_entries_dict : dict 115 :class:`dict` of form: 116 ``{tag: (VR, VM, description, is_retired, keyword), ...}`` 117 where parameters are as described in :func:`add_dict_entry`. 118 119 Raises 120 ------ 121 ValueError 122 If one of the entries is a private tag. 123 124 See Also 125 -------- 126 add_dict_entry 127 Add a single entry to the dictionary. 128 129 Examples 130 -------- 131 132 >>> from pydicom import Dataset 133 >>> new_dict_items = { 134 ... 0x10021001: ('UL', '1', "Test One", '', 'TestOne'), 135 ... 0x10021002: ('DS', '3', "Test Two", '', 'TestTwo'), 136 ... } 137 >>> add_dict_entries(new_dict_items) 138 >>> ds = Dataset() 139 >>> ds.TestOne = 'test' 140 >>> ds.TestTwo = ['1', '2', '3'] 141 142 """ 143 144 if any([BaseTag(tag).is_private for tag in new_entries_dict]): 145 raise ValueError( 146 'Private tags cannot be added using "add_dict_entries" - ' 147 'use "add_private_dict_entries" instead') 148 149 # Update the dictionary itself 150 DicomDictionary.update(new_entries_dict) 151 152 # Update the reverse mapping from name to tag 153 keyword_dict.update({val[4]: tag for tag, val in new_entries_dict.items()}) 154 155 156def add_private_dict_entry( 157 private_creator: str, tag: int, VR: str, description: str, VM: str = '1' 158) -> None: 159 """Update the private DICOM dictionary with a new entry. 160 161 .. versionadded:: 1.3 162 163 Parameters 164 ---------- 165 private_creator : str 166 The private creator for the new entry. 167 tag : int 168 The tag number for the new dictionary entry. Note that the 169 2 high bytes of the element part of the tag are ignored. 170 VR : str 171 DICOM value representation. 172 description : str 173 The descriptive name used in printing the entry. 174 VM : str, optional 175 DICOM value multiplicity. If not specified, then ``'1'`` is used. 176 177 Raises 178 ------ 179 ValueError 180 If the tag is a non-private tag. 181 182 Notes 183 ----- 184 Behaves like :func:`add_dict_entry`, only for a private tag entry. 185 186 See Also 187 -------- 188 add_private_dict_entries 189 Add or update multiple entries at once. 190 """ 191 new_dict_val = (VR, VM, description, '') 192 add_private_dict_entries(private_creator, {tag: new_dict_val}) 193 194 195def add_private_dict_entries( 196 private_creator: str, 197 new_entries_dict: Dict[int, Tuple[str, str, str, str]] 198) -> None: 199 """Update pydicom's private DICOM tag dictionary with new entries. 200 201 .. versionadded:: 1.3 202 203 Parameters 204 ---------- 205 private_creator: str 206 The private creator for all entries in `new_entries_dict`. 207 new_entries_dict : dict 208 :class:`dict` of form ``{tag: (VR, VM, description, is_retired), ...}`` 209 where parameters are as described in :func:`add_private_dict_entry`. 210 211 Raises 212 ------ 213 ValueError 214 If one of the entries is a non-private tag. 215 216 See Also 217 -------- 218 add_private_dict_entry 219 Function to add a single entry to the private tag dictionary. 220 221 Examples 222 -------- 223 >>> new_dict_items = { 224 ... 0x00410001: ('UL', '1', "Test One"), 225 ... 0x00410002: ('DS', '3', "Test Two", '3'), 226 ... } 227 >>> add_private_dict_entries("ACME LTD 1.2", new_dict_items) 228 >>> add_private_dict_entry("ACME LTD 1.3", 0x00410001, "US", "Test Three") 229 """ 230 231 if not all([BaseTag(tag).is_private for tag in new_entries_dict]): 232 raise ValueError( 233 "Non-private tags cannot be added using " 234 "'add_private_dict_entries()' - use 'add_dict_entries()' instead" 235 ) 236 237 new_entries = { 238 f"{tag >> 16:04x}xx{tag & 0xff:02x}": value 239 for tag, value in new_entries_dict.items() 240 } 241 private_dictionaries.setdefault(private_creator, {}).update(new_entries) 242 243 244def get_entry(tag: TagType) -> Tuple[str, str, str, str, str]: 245 """Return an entry from the DICOM dictionary as a tuple. 246 247 If the `tag` is not in the main DICOM dictionary, then the repeating 248 group dictionary will also be checked. 249 250 Parameters 251 ---------- 252 tag : int 253 The tag for the element whose entry is to be retrieved. Only entries 254 in the official DICOM dictionary will be checked, not entries in the 255 private dictionary. 256 257 Returns 258 ------- 259 tuple of str 260 The (VR, VM, name, is_retired, keyword) from the DICOM dictionary. 261 262 Raises 263 ------ 264 KeyError 265 If the tag is not present in the DICOM data dictionary. 266 267 See Also 268 -------- 269 get_private_entry 270 Return an entry from the private dictionary. 271 """ 272 # Note: tried the lookup with 'if tag in DicomDictionary' 273 # and with DicomDictionary.get, instead of try/except 274 # Try/except was fastest using timeit if tag is valid (usual case) 275 # My test had 5.2 usec vs 8.2 for 'contains' test, vs 5.32 for dict.get 276 if not isinstance(tag, BaseTag): 277 tag = Tag(tag) 278 try: 279 return DicomDictionary[tag] 280 except KeyError: 281 if not tag.is_private: 282 mask_x = mask_match(tag) 283 if mask_x: 284 return RepeatersDictionary[mask_x] 285 raise KeyError(f"Tag {tag} not found in DICOM dictionary") 286 287 288def dictionary_is_retired(tag: TagType) -> bool: 289 """Return ``True`` if the element corresponding to `tag` is retired. 290 291 Only performs the lookup for official DICOM elements. 292 293 Parameters 294 ---------- 295 tag : int 296 The tag for the element whose retirement status is being checked. 297 298 Returns 299 ------- 300 bool 301 ``True`` if the element's retirement status is 'Retired', ``False`` 302 otherwise. 303 304 Raises 305 ------ 306 KeyError 307 If the tag is not present in the DICOM data dictionary. 308 """ 309 if 'retired' in get_entry(tag)[3].lower(): 310 return True 311 return False 312 313 314def dictionary_VR(tag: TagType) -> str: 315 """Return the VR of the element corresponding to `tag`. 316 317 Only performs the lookup for official DICOM elements. 318 319 Parameters 320 ---------- 321 tag : int 322 The tag for the element whose value representation (VR) is being 323 retrieved. 324 325 Returns 326 ------- 327 str 328 The VR of the corresponding element. 329 330 Raises 331 ------ 332 KeyError 333 If the tag is not present in the DICOM data dictionary. 334 """ 335 return get_entry(tag)[0] 336 337 338def dictionary_VM(tag: TagType) -> str: 339 """Return the VM of the element corresponding to `tag`. 340 341 Only performs the lookup for official DICOM elements. 342 343 Parameters 344 ---------- 345 tag : int 346 The tag for the element whose value multiplicity (VM) is being 347 retrieved. 348 349 Returns 350 ------- 351 str 352 The VM of the corresponding element. 353 354 Raises 355 ------ 356 KeyError 357 If the tag is not present in the DICOM data dictionary. 358 """ 359 return get_entry(tag)[1] 360 361 362def dictionary_description(tag: TagType) -> str: 363 """Return the description of the element corresponding to `tag`. 364 365 Only performs the lookup for official DICOM elements. 366 367 Parameters 368 ---------- 369 tag : int 370 The tag for the element whose description is being retrieved. 371 372 Returns 373 ------- 374 str 375 The description of the corresponding element. 376 377 Raises 378 ------ 379 KeyError 380 If the tag is not present in the DICOM data dictionary. 381 """ 382 return get_entry(tag)[2] 383 384 385def dictionary_keyword(tag: TagType) -> str: 386 """Return the keyword of the element corresponding to `tag`. 387 388 Only performs the lookup for official DICOM elements. 389 390 Parameters 391 ---------- 392 tag : int 393 The tag for the element whose keyword is being retrieved. 394 395 Returns 396 ------- 397 str 398 The keyword of the corresponding element. 399 400 Raises 401 ------ 402 KeyError 403 If the tag is not present in the DICOM data dictionary. 404 """ 405 return get_entry(tag)[4] 406 407 408def dictionary_has_tag(tag: TagType) -> bool: 409 """Return ``True`` if `tag` is in the official DICOM data dictionary. 410 411 Parameters 412 ---------- 413 tag : int 414 The tag to check. 415 416 Returns 417 ------- 418 bool 419 ``True`` if the tag corresponds to an element present in the official 420 DICOM data dictionary, ``False`` otherwise. 421 """ 422 return (tag in DicomDictionary) 423 424 425def keyword_for_tag(tag: TagType) -> str: 426 """Return the keyword of the element corresponding to `tag`. 427 428 Parameters 429 ---------- 430 tag : int 431 The tag for the element whose keyword is being retrieved. 432 433 Returns 434 ------- 435 str 436 If the element is in the DICOM data dictionary then returns the 437 corresponding element's keyword, otherwise returns ``''``. For 438 group length elements will always return ``'GroupLength'``. 439 """ 440 try: 441 return dictionary_keyword(tag) 442 except KeyError: 443 return "" 444 445 446# Provide for the 'reverse' lookup. Given the keyword, what is the tag? 447keyword_dict: Dict[str, int] = { 448 dictionary_keyword(tag): tag for tag in DicomDictionary 449} 450 451 452def tag_for_keyword(keyword: str) -> Optional[int]: 453 """Return the tag of the element corresponding to `keyword`. 454 455 Only performs the lookup for official DICOM elements. 456 457 Parameters 458 ---------- 459 keyword : str 460 The keyword for the element whose tag is being retrieved. 461 462 Returns 463 ------- 464 int or None 465 If the element is in the DICOM data dictionary then returns the 466 corresponding element's tag, otherwise returns ``None``. 467 """ 468 return keyword_dict.get(keyword) 469 470 471def repeater_has_tag(tag: int) -> bool: 472 """Return ``True`` if `tag` is in the DICOM repeaters data dictionary. 473 474 Parameters 475 ---------- 476 tag : int 477 The tag to check. 478 479 Returns 480 ------- 481 bool 482 ``True`` if the tag is a non-private element tag present in the 483 official DICOM repeaters data dictionary, ``False`` otherwise. 484 """ 485 return (mask_match(tag) in RepeatersDictionary) 486 487 488REPEATER_KEYWORDS = [val[4] for val in RepeatersDictionary.values()] 489 490 491def repeater_has_keyword(keyword: str) -> bool: 492 """Return ``True`` if `keyword` is in the DICOM repeaters data dictionary. 493 494 Parameters 495 ---------- 496 keyword : str 497 The keyword to check. 498 499 Returns 500 ------- 501 bool 502 ``True`` if the keyword corresponding to an element present in the 503 official DICOM repeaters data dictionary, ``False`` otherwise. 504 """ 505 return keyword in REPEATER_KEYWORDS 506 507 508# PRIVATE DICTIONARY handling 509# functions in analogy with those of main DICOM dict 510def get_private_entry( 511 tag: TagType, private_creator: str 512) -> Tuple[str, str, str, str]: 513 """Return an entry from the private dictionary corresponding to `tag`. 514 515 Parameters 516 ---------- 517 tag : int 518 The tag for the element whose entry is to be retrieved. Only entries 519 in the private dictionary will be checked. 520 private_creator : str 521 The name of the private creator. 522 523 Returns 524 ------- 525 tuple of str 526 The (VR, VM, name, is_retired) from the private dictionary. 527 528 Raises 529 ------ 530 KeyError 531 If the tag or private creator is not present in the private dictionary. 532 533 See Also 534 -------- 535 get_entry 536 Return an entry from the DICOM data dictionary. 537 """ 538 if not isinstance(tag, BaseTag): 539 tag = Tag(tag) 540 541 try: 542 private_dict = private_dictionaries[private_creator] 543 except KeyError as exc: 544 raise KeyError( 545 f"Private creator '{private_creator}' not in the private " 546 "dictionary" 547 ) from exc 548 549 # private elements are usually agnostic for 550 # "block" (see PS3.5-2008 7.8.1 p44) 551 # Some elements in _private_dict are explicit; 552 # most have "xx" for high-byte of element 553 # so here put in the "xx" in the block position for key to look up 554 group_str = f"{tag.group:04x}" 555 elem_str = f"{tag.elem:04x}" 556 keys = [ 557 f"{group_str}{elem_str}", 558 f"{group_str}xx{elem_str[-2:]}", 559 f"{group_str[:2]}xxxx{elem_str[-2:]}" 560 ] 561 keys = [k for k in keys if k in private_dict] 562 if not keys: 563 raise KeyError( 564 f"Tag '{tag}' not in private dictionary " 565 f"for private creator '{private_creator}'" 566 ) 567 dict_entry = private_dict[keys[0]] 568 569 return dict_entry 570 571 572def private_dictionary_VR(tag: TagType, private_creator: str) -> str: 573 """Return the VR of the private element corresponding to `tag`. 574 575 Parameters 576 ---------- 577 tag : int 578 The tag for the element whose value representation (VR) is being 579 retrieved. 580 private_creator : str 581 The name of the private creator. 582 583 Returns 584 ------- 585 str 586 The VR of the corresponding element. 587 588 Raises 589 ------ 590 KeyError 591 If the tag is not present in the private dictionary. 592 """ 593 return get_private_entry(tag, private_creator)[0] 594 595 596def private_dictionary_VM(tag: TagType, private_creator: str) -> str: 597 """Return the VM of the private element corresponding to `tag`. 598 599 Parameters 600 ---------- 601 tag : int 602 The tag for the element whose value multiplicity (VM) is being 603 retrieved. 604 private_creator : str 605 The name of the private creator. 606 607 Returns 608 ------- 609 str 610 The VM of the corresponding element. 611 612 Raises 613 ------ 614 KeyError 615 If the tag is not present in the private dictionary. 616 """ 617 return get_private_entry(tag, private_creator)[1] 618 619 620def private_dictionary_description(tag: TagType, private_creator: str) -> str: 621 """Return the description of the private element corresponding to `tag`. 622 623 Parameters 624 ---------- 625 tag : int 626 The tag for the element whose description is being retrieved. 627 private_creator : str 628 The name of the private createor. 629 630 Returns 631 ------- 632 str 633 The description of the corresponding element. 634 635 Raises 636 ------ 637 KeyError 638 If the tag is not present in the private dictionary. 639 """ 640 return get_private_entry(tag, private_creator)[2] 641