1# Copyright 2008-2020 pydicom authors. See LICENSE file for details. 2"""Special classes for DICOM value representations (VR)""" 3 4import datetime 5import re 6import sys 7import warnings 8from decimal import Decimal 9from math import floor, isfinite, log10 10from typing import ( 11 TypeVar, Type, Tuple, Optional, List, Dict, Union, Any, Callable, 12 MutableSequence, Sequence, cast, Iterator 13) 14 15# don't import datetime_conversion directly 16from pydicom import config 17from pydicom.multival import MultiValue 18 19 20# can't import from charset or get circular import 21default_encoding = "iso8859" 22 23# For reading/writing data elements, 24# these ones have longer explicit VR format 25# Taken from PS3.5 Section 7.1.2 26extra_length_VRs = ('OB', 'OD', 'OF', 'OL', 'OW', 'SQ', 'UC', 'UN', 'UR', 'UT') 27 28# VRs that can be affected by character repertoire 29# in (0008,0005) Specific Character Set 30# See PS-3.5 (2011), section 6.1.2 Graphic Characters 31# and PN, but it is handled separately. 32text_VRs: Tuple[str, ...] = ('SH', 'LO', 'ST', 'LT', 'UC', 'UT') 33 34# Delimiters for text strings and person name that reset the encoding. 35# See PS3.5, Section 6.1.2.5.3 36# Note: We use character codes for Python 3 37# because those are the types yielded if iterating over a byte string. 38 39# Characters/Character codes for text VR delimiters: LF, CR, TAB, FF 40TEXT_VR_DELIMS = {0x0d, 0x0a, 0x09, 0x0c} 41 42# Character/Character code for PN delimiter: name part separator '^' 43# (the component separator '=' is handled separately) 44PN_DELIMS = {0x5e} 45 46 47class _DateTimeBase: 48 """Base class for DT, DA and TM element sub-classes.""" 49 original_string: str 50 51 # Add pickling support for the mutable additions 52 def __getstate__(self) -> Dict[str, Any]: 53 return self.__dict__.copy() 54 55 def __setstate__(self, state: Dict[str, Any]) -> None: 56 self.__dict__.update(state) 57 58 def __reduce_ex__(self, protocol: int) -> Tuple[Any, ...]: 59 # datetime.time, and datetime.datetime return Tuple[Any, ...] 60 # datetime.date doesn't define __reduce_ex__ 61 reduce_ex = cast(Tuple[Any, ...], super().__reduce_ex__(protocol)) 62 return reduce_ex + (self.__getstate__(),) 63 64 def __str__(self) -> str: 65 if hasattr(self, 'original_string'): 66 return self.original_string 67 68 return super().__str__() 69 70 def __repr__(self) -> str: 71 return f'"{str(self)}"' 72 73 74class DA(_DateTimeBase, datetime.date): 75 """Store value for an element with VR **DA** as :class:`datetime.date`. 76 77 Note that the :class:`datetime.date` base class is immutable. 78 """ 79 def __new__( # type: ignore[misc] 80 cls: Type["DA"], *args: Any, **kwargs: Any 81 ) -> Optional["DA"]: 82 """Create an instance of DA object. 83 84 Raise an exception if the string cannot be parsed or the argument 85 is otherwise incompatible. 86 87 The arguments (``*args`` and ``**kwargs``) are either the ones 88 inherited from :class:`datetime.date`, or the first argument is 89 a string conformant to the DA definition in the DICOM Standard, 90 Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`, 91 or it is a :class:`datetime.date` object, or an object of type 92 :class:`~pydicom.valuerep.DA`. 93 """ 94 if not args or args[0] is None: 95 return None 96 97 val = args[0] 98 if isinstance(val, str): 99 if val.strip() == '': 100 return None # empty date 101 102 if len(val) == 8: 103 year = int(val[0:4]) 104 month = int(val[4:6]) 105 day = int(val[6:8]) 106 return super().__new__(cls, year, month, day) 107 108 if len(val) == 10 and val[4] == '.' and val[7] == '.': 109 # ACR-NEMA Standard 300, predecessor to DICOM 110 # for compatibility with a few old pydicom example files 111 year = int(val[0:4]) 112 month = int(val[5:7]) 113 day = int(val[8:10]) 114 return super().__new__(cls, year, month, day) 115 116 if isinstance(val, datetime.date): 117 return super().__new__(cls, val.year, val.month, val.day) 118 119 try: 120 return super().__new__(cls, *args, **kwargs) 121 except Exception as exc: 122 raise ValueError( 123 f"Unable to convert '{val}' to 'DA' object" 124 ) from exc 125 126 def __init__(self, *args: Any, **kwargs: Any) -> None: 127 """Create a new **DA** element value.""" 128 val = args[0] 129 if isinstance(val, str): 130 self.original_string = val 131 elif isinstance(val, DA) and hasattr(val, 'original_string'): 132 self.original_string = val.original_string 133 elif isinstance(val, datetime.date): 134 self.original_string = f"{val.year}{val.month:02}{val.day:02}" 135 136 137class DT(_DateTimeBase, datetime.datetime): 138 """Store value for an element with VR **DT** as :class:`datetime.datetime`. 139 140 Note that the :class:`datetime.datetime` base class is immutable. 141 """ 142 _regex_dt = re.compile(r"((\d{4,14})(\.(\d{1,6}))?)([+-]\d{4})?") 143 144 @staticmethod 145 def _utc_offset(value: str) -> datetime.timezone: 146 """Return the UTC Offset suffix as a :class:`datetime.timezone`. 147 148 Parameters 149 ---------- 150 value : str 151 The value of the UTC offset suffix, such as ``'-1000'`` or 152 ``'+0245'``. 153 154 Returns 155 ------- 156 datetime.timezone 157 """ 158 # Format is &ZZXX, & = '+' or '-', ZZ is hours, XX is minutes 159 hour = int(value[1:3]) * 60 # Convert hours to minutes 160 minute = int(value[3:5]) # In minutes 161 offset = (hour + minute) * 60 # Convert minutes to seconds 162 offset = -offset if value[0] == '-' else offset 163 164 return datetime.timezone( 165 datetime.timedelta(seconds=offset), 166 name=value 167 ) 168 169 def __new__( # type: ignore[misc] 170 cls: Type["DT"], *args: Any, **kwargs: Any 171 ) -> Optional["DT"]: 172 """Create an instance of DT object. 173 174 Raise an exception if the string cannot be parsed or the argument 175 is otherwise incompatible. 176 177 The arguments (``*args`` and ``**kwargs``) are either the ones 178 inherited from :class:`datetime.datetime`, or the first argument is 179 a string conformant to the DT definition in the DICOM Standard, 180 Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`, 181 or it is a :class:`datetime.datetime` object, or an object of type 182 :class:`~pydicom.valuerep.DT`. 183 """ 184 if not args or args[0] is None: 185 return None 186 187 val = args[0] 188 if isinstance(val, str): 189 if val.strip() == '': 190 return None 191 192 match = cls._regex_dt.match(val) 193 if not match or len(val) > 26: 194 raise ValueError( 195 f"Unable to convert non-conformant value '{val}' to 'DT' " 196 "object" 197 ) 198 199 dt_match = match.group(2) 200 args = ( 201 int(dt_match[0:4]), # year 202 1 if len(dt_match) < 6 else int(dt_match[4:6]), # month 203 1 if len(dt_match) < 8 else int(dt_match[6:8]), # day 204 ) 205 kwargs = { 206 'hour': 0 if len(dt_match) < 10 else int(dt_match[8:10]), 207 'minute': 0 if len(dt_match) < 12 else int(dt_match[10:12]), 208 'second': 0 if len(dt_match) < 14 else int(dt_match[12:14]), 209 'microsecond': 0 210 } 211 if len(dt_match) >= 14 and match.group(4): 212 kwargs['microsecond'] = int( 213 match.group(4).rstrip().ljust(6, '0') 214 ) 215 216 # Timezone offset 217 tz_match = match.group(5) 218 kwargs['tzinfo'] = cls._utc_offset(tz_match) if tz_match else None 219 220 # DT may include a leap second which isn't allowed by datetime 221 if kwargs['second'] == 60: 222 warnings.warn( 223 "'datetime.datetime' doesn't allow a value of '60' for " 224 "the seconds component, changing to '59'" 225 ) 226 kwargs['second'] = 59 227 228 return super().__new__(cls, *args, **kwargs) 229 230 if isinstance(val, datetime.datetime): 231 return super().__new__( 232 cls, *val.timetuple()[:6], val.microsecond, val.tzinfo 233 ) 234 235 try: 236 return super().__new__(cls, *args, **kwargs) 237 except Exception as exc: 238 raise ValueError( 239 f"Unable to convert '{val}' to 'DT' object" 240 ) from exc 241 242 def __init__(self, *args: Any, **kwargs: Any) -> None: 243 """Create a new **DT** element value.""" 244 val = args[0] 245 if isinstance(val, str): 246 self.original_string = val 247 elif isinstance(val, DT) and hasattr(val, 'original_string'): 248 self.original_string = val.original_string 249 elif isinstance(val, datetime.datetime): 250 self.original_string = ( 251 f"{val.year:04}{val.month:02}{val.day:02}" 252 f"{val.hour:02}{val.minute:02}{val.second:02}" 253 ) 254 # milliseconds are seldom used, add them only if needed 255 if val.microsecond > 0: 256 self.original_string += f".{val.microsecond:06}" 257 258 if val.tzinfo is not None: 259 # offset: Optional[datetime.timedelta] 260 offset = val.tzinfo.utcoffset(val) 261 if offset is not None: 262 offset_min = offset.days * 24 * 60 + offset.seconds // 60 263 sign = "+" if offset_min >= 0 else "-" 264 offset_min = abs(offset_min) 265 self.original_string += ( 266 f"{sign}{offset_min // 60:02}{offset_min % 60:02}" 267 ) 268 269 270class TM(_DateTimeBase, datetime.time): 271 """Store value for an element with VR **TM** as :class:`datetime.time`. 272 273 Note that the :class:`datetime.time` base class is immutable. 274 """ 275 _RE_TIME = re.compile( 276 r"(?P<h>^([01][0-9]|2[0-3]))" 277 r"((?P<m>([0-5][0-9]))?" 278 r"(?(5)(?P<s>([0-5][0-9]|60))?)" 279 r"(?(7)(\.(?P<ms>([0-9]{1,6})?))?))$" 280 ) 281 282 def __new__( # type: ignore[misc] 283 cls: Type["TM"], *args: Any, **kwargs: Any 284 ) -> Optional["TM"]: 285 """Create an instance of TM object from a string. 286 287 Raise an exception if the string cannot be parsed or the argument 288 is otherwise incompatible. 289 290 The arguments (``*args`` and ``**kwargs``) are either the ones 291 inherited from :class:`datetime.time`, or the first argument is 292 a string conformant to the TM definition in the DICOM Standard, 293 Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`, 294 or it is a :class:`datetime.time` object, or an object of type 295 :class:`~pydicom.valuerep.TM`. 296 """ 297 if not args or args[0] is None: 298 return None 299 300 val = args[0] 301 if isinstance(val, str): 302 if val.strip() == '': 303 return None # empty time 304 305 match = cls._RE_TIME.match(val) 306 if not match: 307 raise ValueError( 308 f"Unable to convert non-conformant value '{val}' to 'TM' " 309 "object" 310 ) 311 312 hour = int(match.group('h')) 313 minute = 0 if match.group('m') is None else int(match.group('m')) 314 second = 0 if match.group('s') is None else int(match.group('s')) 315 316 if second == 60: 317 warnings.warn( 318 "'datetime.time' doesn't allow a value of '60' for the " 319 "seconds component, changing to '59'" 320 ) 321 second = 59 322 323 microsecond = 0 324 if match.group('ms'): 325 microsecond = int(match.group('ms').rstrip().ljust(6, '0')) 326 327 return super().__new__( # type: ignore[call-arg, no-any-return] 328 cls, hour, minute, second, microsecond 329 ) 330 331 if isinstance(val, datetime.time): 332 return super().__new__( # type: ignore[call-arg, no-any-return] 333 cls, val.hour, val.minute, val.second, val.microsecond 334 ) 335 336 try: 337 return super().__new__( # type: ignore[call-arg, no-any-return] 338 cls, *args, **kwargs 339 ) 340 except Exception as exc: 341 raise ValueError( 342 f"Unable to convert '{val}' to 'TM' object" 343 ) from exc 344 345 def __init__(self, *args: Any, **kwargs: Any) -> None: 346 super().__init__() 347 val = args[0] 348 if isinstance(val, str): 349 self.original_string = val 350 elif isinstance(val, TM) and hasattr(val, 'original_string'): 351 self.original_string = val.original_string 352 elif isinstance(val, datetime.time): 353 self.original_string = ( 354 f"{val.hour:02}{val.minute:02}{val.second:02}" 355 ) 356 # milliseconds are seldom used, add them only if needed 357 if val.microsecond > 0: 358 self.original_string += f".{val.microsecond:06}" 359 360 361# Regex to match strings that represent valid DICOM decimal strings (DS) 362_DS_REGEX = re.compile(r'\s*[\+\-]?\d+(\.\d+)?([eE][\+\-]?\d+)?\s*$') 363 364 365def is_valid_ds(s: str) -> bool: 366 """Check whether this string is a valid decimal string. 367 368 Valid decimal strings must be 16 characters or fewer, and contain only 369 characters from a limited set. 370 371 Parameters 372 ---------- 373 s: str 374 String to test. 375 376 Returns 377 ------- 378 bool 379 True if the string is a valid decimal string. Otherwise False. 380 """ 381 # Check that the length is within the limits 382 if len(s) > 16: 383 return False 384 385 return _DS_REGEX.match(s) is not None 386 387 388def format_number_as_ds(val: Union[float, Decimal]) -> str: 389 """Truncate a float's representation to give a valid Decimal String (DS). 390 391 DICOM's decimal string (DS) representation is limited to strings with 16 392 characters and a limited set of characters. This function represents a 393 float that satisfies these constraints while retaining as much 394 precision as possible. Some floats are represented using scientific 395 notation to make more efficient use of the limited number of characters. 396 397 Note that this will incur a loss of precision if the number cannot be 398 represented with 16 characters. Furthermore, non-finite floats (infs and 399 nans) cannot be represented as decimal strings and will cause an error to 400 be raised. 401 402 Parameters 403 ---------- 404 val: Union[float, Decimal] 405 The floating point value whose representation is required. 406 407 Returns 408 ------- 409 str 410 String representation of the float satisfying the constraints of the 411 decimal string representation. 412 413 Raises 414 ------ 415 ValueError 416 If val does not represent a finite value 417 418 """ 419 if not isinstance(val, (float, Decimal)): 420 raise TypeError("'val' must be of type float or decimal.Decimal") 421 if not isfinite(val): 422 raise ValueError( 423 "Cannot encode non-finite floats as DICOM decimal strings. " 424 f"Got '{val}'" 425 ) 426 427 valstr = str(val) 428 429 # In the simple case, the default python string representation 430 # will do 431 if len(valstr) <= 16: 432 return valstr 433 434 # Decide whether to use scientific notation 435 logval = log10(cast(Union[float, Decimal], abs(val))) 436 437 # Characters needed for '-' at start 438 sign_chars = 1 if val < 0.0 else 0 439 440 # Numbers larger than 1e14 cannot be correctly represented by truncating 441 # their string representations to 16 chars, e.g pi * 10^13 would become 442 # '314159265358979.', which may not be universally understood. This limit 443 # is 1e13 for negative numbers because of the minus sign. 444 # For negative exponents, the point of equal precision between scientific 445 # and standard notation is 1e-4 e.g. '0.00031415926535' and 446 # '3.1415926535e-04' are both 16 chars 447 use_scientific = logval < -4 or logval >= (14 - sign_chars) 448 449 if use_scientific: 450 # In principle, we could have a number where the exponent 451 # needs three digits to be represented (bigger than this cannot be 452 # represented by floats). Due to floating point limitations 453 # this is best checked for by doing the string conversion 454 remaining_chars = 10 - sign_chars 455 trunc_str = f'%.{remaining_chars}e' % val 456 if len(trunc_str) > 16: 457 trunc_str = f'%.{remaining_chars - 1}e' % val 458 return trunc_str 459 else: 460 if logval >= 1.0: 461 # chars remaining for digits after sign, digits left of '.' and '.' 462 remaining_chars = 14 - sign_chars - int(floor(logval)) 463 else: 464 remaining_chars = 14 - sign_chars 465 return f'%.{remaining_chars}f' % val 466 467 468class DSfloat(float): 469 """Store value for an element with VR **DS** as :class:`float`. 470 471 If constructed from an empty string, return the empty string, 472 not an instance of this class. 473 474 Parameters 475 ---------- 476 val: Union[str, int, float, Decimal] 477 Value to store as a DS. 478 auto_format: bool 479 If True, automatically format the string representation of this 480 number to ensure it satisfies the constraints in the DICOM standard. 481 Note that this will lead to loss of precision for some numbers. 482 483 """ 484 auto_format: bool 485 486 def __new__( # type: ignore[misc] 487 cls: Type["DSfloat"], 488 val: Union[None, str, int, float, Decimal], 489 auto_format: bool = False 490 ) -> Optional[Union[str, "DSfloat"]]: 491 if val is None: 492 return val 493 494 if isinstance(val, str) and val.strip() == '': 495 return val 496 497 return super().__new__(cls, val) 498 499 def __init__( 500 self, val: Union[str, int, float, Decimal], 501 auto_format: bool = False 502 ) -> None: 503 """Store the original string if one given, for exact write-out of same 504 value later. 505 """ 506 # ... also if user changes a data element value, then will get 507 # a different object, because float is immutable. 508 has_attribute = hasattr(val, 'original_string') 509 pre_checked = False 510 if isinstance(val, str): 511 self.original_string = val.strip() 512 elif isinstance(val, (DSfloat, DSdecimal)): 513 if val.auto_format: 514 auto_format = True # override input parameter 515 pre_checked = True 516 if has_attribute: 517 self.original_string = val.original_string 518 519 self.auto_format = auto_format 520 if self.auto_format and not pre_checked: 521 # If auto_format is True, keep the float value the same, but change 522 # the string representation stored in original_string if necessary 523 if hasattr(self, 'original_string'): 524 if not is_valid_ds(self.original_string): 525 self.original_string = format_number_as_ds( 526 float(self.original_string) 527 ) 528 else: 529 self.original_string = format_number_as_ds(self) 530 531 if config.enforce_valid_values and not self.auto_format: 532 if len(repr(self)[1:-1]) > 16: 533 raise OverflowError( 534 "Values for elements with a VR of 'DS' must be <= 16 " 535 "characters long, but the float provided requires > 16 " 536 "characters to be accurately represented. Use a smaller " 537 "string, set 'config.enforce_valid_values' to False to " 538 "override the length check, or explicitly construct a DS " 539 "object with 'auto_format' set to True" 540 ) 541 if not is_valid_ds(repr(self)[1:-1]): 542 # This will catch nan and inf 543 raise ValueError( 544 f'Value "{str(self)}" is not valid for elements with a VR ' 545 'of DS' 546 ) 547 548 def __eq__(self, other: Any) -> Any: 549 """Override to allow string equality comparisons.""" 550 if isinstance(other, str): 551 return str(self) == other 552 553 return super().__eq__(other) 554 555 def __hash__(self) -> int: 556 return super().__hash__() 557 558 def __ne__(self, other: Any) -> Any: 559 return not self == other 560 561 def __str__(self) -> str: 562 if hasattr(self, 'original_string') and not self.auto_format: 563 return self.original_string 564 565 # Issue #937 (Python 3.8 compatibility) 566 return repr(self)[1:-1] 567 568 def __repr__(self) -> str: 569 if self.auto_format and hasattr(self, 'original_string'): 570 return f"'{self.original_string}'" 571 572 return f"'{super().__repr__()}'" 573 574 575class DSdecimal(Decimal): 576 """Store value for an element with VR **DS** as :class:`decimal.Decimal`. 577 578 Parameters 579 ---------- 580 val: Union[str, int, float, Decimal] 581 Value to store as a DS. 582 auto_format: bool 583 If True, automatically format the string representation of this 584 number to ensure it satisfies the constraints in the DICOM standard. 585 Note that this will lead to loss of precision for some numbers. 586 587 Notes 588 ----- 589 If constructed from an empty string, returns the empty string, not an 590 instance of this class. 591 592 """ 593 auto_format: bool 594 595 def __new__( # type: ignore[misc] 596 cls: Type["DSdecimal"], 597 val: Union[None, str, int, float, Decimal], 598 auto_format: bool = False 599 ) -> Optional[Union[str, "DSdecimal"]]: 600 """Create an instance of DS object, or return a blank string if one is 601 passed in, e.g. from a type 2 DICOM blank value. 602 603 Parameters 604 ---------- 605 val : str or numeric 606 A string or a number type which can be converted to a decimal. 607 """ 608 if val is None: 609 return val 610 611 if isinstance(val, str) and val.strip() == '': 612 return val 613 614 if isinstance(val, float) and not config.allow_DS_float: 615 raise TypeError( 616 "'DS' cannot be instantiated with a float value unless " 617 "'config.allow_DS_float' is set to True. You should convert " 618 "the value to a string with the desired number of digits, " 619 "or use 'Decimal.quantize()' and pass a 'Decimal' instance." 620 ) 621 622 return super().__new__(cls, val) 623 624 def __init__( 625 self, 626 val: Union[str, int, float, Decimal], 627 auto_format: bool = False 628 ) -> None: 629 """Store the original string if one given, for exact write-out of same 630 value later. E.g. if set ``'1.23e2'``, :class:`~decimal.Decimal` would 631 write ``'123'``, but :class:`DS` will use the original. 632 """ 633 # ... also if user changes a data element value, then will get 634 # a different Decimal, as Decimal is immutable. 635 pre_checked = False 636 if isinstance(val, str): 637 self.original_string = val.strip() 638 elif isinstance(val, (DSfloat, DSdecimal)): 639 if val.auto_format: 640 auto_format = True # override input parameter 641 pre_checked = True 642 643 if hasattr(val, 'original_string'): 644 self.original_string = val.original_string 645 646 self.auto_format = auto_format 647 if self.auto_format and not pre_checked: 648 # If auto_format is True, keep the float value the same, but change 649 # the string representation stored in original_string if necessary 650 if hasattr(self, 'original_string'): 651 if not is_valid_ds(self.original_string): 652 self.original_string = format_number_as_ds( 653 float(self.original_string) 654 ) 655 else: 656 self.original_string = format_number_as_ds(self) 657 658 if config.enforce_valid_values: 659 if len(repr(self).strip("'")) > 16: 660 raise OverflowError( 661 "Values for elements with a VR of 'DS' values must be " 662 "<= 16 characters long. Use a smaller string, set " 663 "'config.enforce_valid_values' to False to override the " 664 "length check, use 'Decimal.quantize()' and initialize " 665 "with a 'Decimal' instance, or explicitly construct a DS " 666 "instance with 'auto_format' set to True" 667 ) 668 if not is_valid_ds(repr(self).strip("'")): 669 # This will catch nan and inf 670 raise ValueError( 671 f'Value "{str(self)}" is not valid for elements with a VR ' 672 'of DS' 673 ) 674 675 def __eq__(self, other: Any) -> Any: 676 """Override to allow string equality comparisons.""" 677 if isinstance(other, str): 678 return str(self) == other 679 680 return super().__eq__(other) 681 682 def __hash__(self) -> int: 683 return super().__hash__() 684 685 def __ne__(self, other: Any) -> Any: 686 return not self == other 687 688 def __str__(self) -> str: 689 has_str = hasattr(self, 'original_string') 690 if has_str and len(self.original_string) <= 16: 691 return self.original_string 692 693 return super().__str__() 694 695 def __repr__(self) -> str: 696 if self.auto_format and hasattr(self, 'original_string'): 697 return f"'{self.original_string}'" 698 return f"'{str(self)}'" 699 700 701# CHOOSE TYPE OF DS 702DSclass: Any 703if config.use_DS_decimal: 704 DSclass = DSdecimal 705else: 706 DSclass = DSfloat 707 708 709def DS( 710 val: Union[None, str, int, float, Decimal], auto_format: bool = False 711) -> Union[None, str, DSfloat, DSdecimal]: 712 """Factory function for creating DS class instances. 713 714 Checks for blank string; if so, returns that, else calls :class:`DSfloat` 715 or :class:`DSdecimal` to create the class instance. This avoids overriding 716 ``DSfloat.__new__()`` (which carries a time penalty for large arrays of 717 DS). 718 719 Similarly the string clean and check can be avoided and :class:`DSfloat` 720 called directly if a string has already been processed. 721 """ 722 if val is None: 723 return val 724 725 if isinstance(val, str) and val.strip() == '': 726 return val 727 728 if config.use_DS_decimal: 729 return DSdecimal(val, auto_format=auto_format) 730 731 return DSfloat(val, auto_format=auto_format) 732 733 734class IS(int): 735 """Store value for an element with VR **IS** as :class:`int`. 736 737 Stores original integer string for exact rewriting of the string 738 originally read or stored. 739 """ 740 741 def __new__( # type: ignore[misc] 742 cls: Type["IS"], val: Union[None, str, int, float, Decimal] 743 ) -> Optional[Union[str, "IS"]]: 744 """Create instance if new integer string""" 745 if val is None: 746 return val 747 748 if isinstance(val, str) and val.strip() == '': 749 return val 750 751 try: 752 newval = super().__new__(cls, val) 753 except ValueError: 754 # accept float strings when no integer loss, e.g. "1.0" 755 newval = super().__new__(cls, float(val)) 756 757 # check if a float or Decimal passed in, then could have lost info, 758 # and will raise error. E.g. IS(Decimal('1')) is ok, but not IS(1.23) 759 # IS('1.23') will raise ValueError 760 if isinstance(val, (float, Decimal, str)) and newval != float(val): 761 raise TypeError("Could not convert value to integer without loss") 762 763 # Checks in case underlying int is >32 bits, DICOM does not allow this 764 if not -2**31 <= newval < 2**31 and config.enforce_valid_values: 765 raise OverflowError( 766 "Elements with a VR of IS must have a value between -2**31 " 767 "and (2**31 - 1). Set 'config.enforce_valid_values' to False " 768 "to override the value check" 769 ) 770 771 return newval 772 773 def __init__(self, val: Union[str, int, float, Decimal]) -> None: 774 # If a string passed, then store it 775 if isinstance(val, str): 776 self.original_string = val.strip() 777 elif isinstance(val, IS) and hasattr(val, 'original_string'): 778 self.original_string = val.original_string 779 780 def __eq__(self, other: Any) -> Any: 781 """Override to allow string equality comparisons.""" 782 if isinstance(other, str): 783 return str(self) == other 784 785 return super().__eq__(other) 786 787 def __hash__(self) -> int: 788 return super().__hash__() 789 790 def __ne__(self, other: Any) -> Any: 791 return not self == other 792 793 def __str__(self) -> str: 794 if hasattr(self, 'original_string'): 795 return self.original_string 796 797 # Issue #937 (Python 3.8 compatibility) 798 return repr(self)[1:-1] 799 800 def __repr__(self) -> str: 801 return f"'{super().__repr__()}'" 802 803 804_T = TypeVar('_T') 805 806 807def MultiString( 808 val: str, valtype: Optional[Callable[[str], _T]] = None 809) -> Union[_T, MutableSequence[_T]]: 810 """Split a string by delimiters if there are any 811 812 Parameters 813 ---------- 814 val : str 815 The string to split up. 816 valtype : type or callable, optional 817 Default :class:`str`, but can be e.g. :class:`~pydicom.uid.UID` to 818 overwrite to a specific type. 819 820 Returns 821 ------- 822 valtype or MultiValue of valtype 823 The split value as `valtype` or a :class:`list` of `valtype`. 824 """ 825 if valtype is None: 826 valtype = cast(Callable[[str], _T], str) 827 828 # Remove trailing blank used to pad to even length 829 # 2005.05.25: also check for trailing 0, error made 830 # in PET files we are converting 831 while val and val.endswith((' ', '\x00')): 832 val = val[:-1] 833 834 splitup: List[str] = val.split("\\") 835 if len(splitup) == 1: 836 return valtype(splitup[0]) 837 838 return MultiValue(valtype, splitup) 839 840 841def _verify_encodings( 842 encodings: Optional[Union[str, Sequence[str]]] 843) -> Optional[Tuple[str, ...]]: 844 """Checks the encoding to ensure proper format""" 845 if encodings is None: 846 return None 847 848 if isinstance(encodings, str): 849 return (encodings,) 850 851 return tuple(encodings) 852 853 854def _decode_personname( 855 components: Sequence[bytes], encodings: Sequence[str] 856) -> Tuple[str, ...]: 857 """Return a list of decoded person name components. 858 859 Parameters 860 ---------- 861 components : list of bytes 862 The list of the up to three encoded person name components 863 encodings : list of str 864 The Python encodings uses to decode `components`. 865 866 Returns 867 ------- 868 text type 869 The unicode string representing the person name. 870 If the decoding of some component parts is not possible using the 871 given encodings, they are decoded with the first encoding using 872 replacement characters for bytes that cannot be decoded. 873 """ 874 from pydicom.charset import decode_bytes 875 876 comps = [decode_bytes(c, encodings, PN_DELIMS) for c in components] 877 878 # Remove empty elements from the end to avoid trailing '=' 879 while len(comps) and not comps[-1]: 880 comps.pop() 881 882 return tuple(comps) 883 884 885def _encode_personname( 886 components: Sequence[str], encodings: Sequence[str] 887) -> bytes: 888 """Encode a list of text string person name components. 889 890 Parameters 891 ---------- 892 components : list of str 893 The list of the up to three unicode person name components 894 encodings : list of str 895 The Python encodings uses to encode `components`. 896 897 Returns 898 ------- 899 byte string 900 The byte string that can be written as a PN DICOM tag value. 901 If the encoding of some component parts is not possible using the 902 given encodings, they are encoded with the first encoding using 903 replacement bytes for characters that cannot be encoded. 904 """ 905 from pydicom.charset import encode_string 906 907 encoded_comps = [] 908 for comp in components: 909 groups = [ 910 encode_string(group, encodings) for group in comp.split('^') 911 ] 912 encoded_comps.append(b'^'.join(groups)) 913 914 # Remove empty elements from the end 915 while len(encoded_comps) and not encoded_comps[-1]: 916 encoded_comps.pop() 917 return b'='.join(encoded_comps) 918 919 920class PersonName: 921 """Representation of the value for an element with VR **PN**.""" 922 def __new__( # type: ignore[misc] 923 cls: Type["PersonName"], *args: Any, **kwargs: Any 924 ) -> Optional["PersonName"]: 925 if len(args) and args[0] is None: 926 return None 927 928 return cast("PersonName", super().__new__(cls)) 929 930 def __init__( 931 self, 932 val: Union[bytes, str, "PersonName"], 933 encodings: Optional[Sequence[str]] = None, 934 original_string: Optional[bytes] = None 935 ) -> None: 936 """Create a new ``PersonName``. 937 938 Parameters 939 ---------- 940 val: str, bytes, PersonName 941 The value to use for the **PN** element. 942 encodings: list of str, optional 943 A list of the encodings used for the value. 944 original_string: bytes, optional 945 When creating a ``PersonName`` using a decoded string, this is the 946 original encoded value. 947 948 Notes 949 ----- 950 A :class:`PersonName` may also be constructed by specifying individual 951 components using the :meth:`from_named_components` and 952 :meth:`from_named_components_veterinary` class methods. 953 """ 954 self.original_string: bytes 955 self._components: Optional[Tuple[str, ...]] = None 956 self.encodings: Optional[Tuple[str, ...]] 957 958 if isinstance(val, PersonName): 959 encodings = val.encodings 960 self.original_string = val.original_string 961 self._components = tuple(str(val).split('=')) 962 elif isinstance(val, bytes): 963 # this is the raw byte string - decode it on demand 964 self.original_string = val 965 self._components = None 966 else: 967 # val: str 968 # `val` is the decoded person name value 969 # `original_string` should be the original encoded value 970 self.original_string = cast(bytes, original_string) 971 components = val.split('=') 972 # Remove empty elements from the end to avoid trailing '=' 973 while len(components) and not components[-1]: 974 components.pop() 975 self._components = tuple(components) 976 977 # if the encoding is not given, leave it as undefined (None) 978 self.encodings = _verify_encodings(encodings) 979 980 def _create_dict(self) -> Dict[str, str]: 981 """Creates a dictionary of person name group and component names. 982 983 Used exclusively for `formatted` for backwards compatibility. 984 """ 985 parts = [ 986 'family_name', 'given_name', 'middle_name', 'name_prefix', 987 'name_suffix', 'ideographic', 'phonetic' 988 ] 989 return {c: getattr(self, c, '') for c in parts} 990 991 @property 992 def components(self) -> Tuple[str, ...]: 993 """Returns up to three decoded person name components as a 994 :class:`tuple` of :class:`str`. 995 996 .. versionadded:: 1.2 997 998 Returns 999 ------- 1000 Tuple[str, ...] 1001 The (alphabetic, ideographic, phonetic) components of the 1002 decoded person name. Any of the components may be absent. 1003 """ 1004 if self._components is None: 1005 groups = self.original_string.split(b'=') 1006 encodings = self.encodings or [default_encoding] 1007 self._components = _decode_personname(groups, encodings) 1008 1009 return self._components 1010 1011 def _name_part(self, i: int) -> str: 1012 """Return the `i`th part of the name.""" 1013 try: 1014 return self.components[0].split('^')[i] 1015 except IndexError: 1016 return '' 1017 1018 @property 1019 def family_name(self) -> str: 1020 """Return the first (family name) group of the alphabetic person name 1021 representation as a unicode string 1022 1023 .. versionadded:: 1.2 1024 """ 1025 return self._name_part(0) 1026 1027 @property 1028 def given_name(self) -> str: 1029 """Return the second (given name) group of the alphabetic person name 1030 representation as a unicode string 1031 1032 .. versionadded:: 1.2 1033 """ 1034 return self._name_part(1) 1035 1036 @property 1037 def middle_name(self) -> str: 1038 """Return the third (middle name) group of the alphabetic person name 1039 representation as a unicode string 1040 1041 .. versionadded:: 1.2 1042 """ 1043 return self._name_part(2) 1044 1045 @property 1046 def name_prefix(self) -> str: 1047 """Return the fourth (name prefix) group of the alphabetic person name 1048 representation as a unicode string 1049 1050 .. versionadded:: 1.2 1051 """ 1052 return self._name_part(3) 1053 1054 @property 1055 def name_suffix(self) -> str: 1056 """Return the fifth (name suffix) group of the alphabetic person name 1057 representation as a unicode string 1058 1059 .. versionadded:: 1.2 1060 """ 1061 return self._name_part(4) 1062 1063 @property 1064 def ideographic(self) -> str: 1065 """Return the second (ideographic) person name component as a 1066 unicode string 1067 1068 .. versionadded:: 1.2 1069 """ 1070 try: 1071 return self.components[1] 1072 except IndexError: 1073 return '' 1074 1075 @property 1076 def phonetic(self) -> str: 1077 """Return the third (phonetic) person name component as a 1078 unicode string 1079 1080 .. versionadded:: 1.2 1081 """ 1082 try: 1083 return self.components[2] 1084 except IndexError: 1085 return '' 1086 1087 def __eq__(self, other: Any) -> Any: 1088 """Return ``True`` if `other` equals the current name.""" 1089 return str(self) == other 1090 1091 def __ne__(self, other: Any) -> Any: 1092 """Return ``True`` if `other` doesn't equal the current name.""" 1093 return not self == other 1094 1095 def __str__(self) -> str: 1096 """Return a string representation of the name.""" 1097 return '='.join(self.components).__str__() 1098 1099 def __iter__(self) -> Iterator[str]: 1100 """Iterate through the name.""" 1101 yield from self.__str__() 1102 1103 def __len__(self) -> int: 1104 """Return the length of the person name.""" 1105 return len(self.__str__()) 1106 1107 def __contains__(self, x: Any) -> bool: 1108 """Return ``True`` if `x` is in the name.""" 1109 return x in self.__str__() 1110 1111 def __repr__(self) -> str: 1112 """Return a representation of the name.""" 1113 return '='.join(self.components).__repr__() 1114 1115 def __hash__(self) -> int: 1116 """Return a hash of the name.""" 1117 return hash(self.components) 1118 1119 def decode( 1120 self, encodings: Optional[Sequence[str]] = None 1121 ) -> "PersonName": 1122 """Return the patient name decoded by the given `encodings`. 1123 1124 Parameters 1125 ---------- 1126 encodings : list of str, optional 1127 The list of encodings used for decoding the byte string. If not 1128 given, the initial encodings set in the object are used. 1129 1130 Returns 1131 ------- 1132 valuerep.PersonName 1133 A person name object that will return the decoded string with 1134 the given encodings on demand. If the encodings are not given, 1135 the current object is returned. 1136 """ 1137 # in the common case (encoding did not change) we decode on demand 1138 if encodings is None or encodings == self.encodings: 1139 return self 1140 1141 # the encoding was unknown or incorrect - create a new 1142 # PersonName object with the changed encoding 1143 encodings = _verify_encodings(encodings) 1144 if self.original_string is None: 1145 # if the original encoding was not set, we set it now 1146 self.original_string = _encode_personname( 1147 self.components, self.encodings or [default_encoding] 1148 ) 1149 1150 return PersonName(self.original_string, encodings) 1151 1152 def encode(self, encodings: Optional[Sequence[str]] = None) -> bytes: 1153 """Return the patient name decoded by the given `encodings`. 1154 1155 Parameters 1156 ---------- 1157 encodings : list of str, optional 1158 The list of encodings used for encoding the unicode string. If 1159 not given, the initial encodings set in the object are used. 1160 1161 Returns 1162 ------- 1163 bytes 1164 The person name encoded with the given encodings as a byte string. 1165 If no encoding is given, the original byte string is returned, if 1166 available, otherwise each group of the patient name is encoded 1167 with the first matching of the given encodings. 1168 """ 1169 encodings = _verify_encodings(encodings) or self.encodings 1170 1171 # if the encoding is not the original encoding, we have to return 1172 # a re-encoded string (without updating the original string) 1173 if encodings != self.encodings and self.encodings is not None: 1174 return _encode_personname( 1175 self.components, cast(Sequence[str], encodings) 1176 ) 1177 1178 if self.original_string is None: 1179 # if the original encoding was not set, we set it now 1180 self.original_string = _encode_personname( 1181 self.components, encodings or [default_encoding] 1182 ) 1183 1184 return self.original_string 1185 1186 def family_comma_given(self) -> str: 1187 """Return the name as "Family, Given".""" 1188 return f"{self.family_name}, {self.given_name}" 1189 1190 def formatted(self, format_str: str) -> str: 1191 """Return the name as a :class:`str` formatted using `format_str`.""" 1192 return format_str % self._create_dict() 1193 1194 def __bool__(self) -> bool: 1195 """Return ``True`` if the name is not empty.""" 1196 if not self.original_string: 1197 return ( 1198 bool(self.components) 1199 and (len(self.components) > 1 or bool(self.components[0])) 1200 ) 1201 1202 return bool(self.original_string) 1203 1204 @staticmethod 1205 def _encode_component_groups( 1206 alphabetic_group: Sequence[Union[str, bytes]], 1207 ideographic_group: Sequence[Union[str, bytes]], 1208 phonetic_group: Sequence[Union[str, bytes]], 1209 encodings: Optional[List[str]] = None, 1210 ) -> bytes: 1211 """Creates a byte string for a person name from lists of parts. 1212 1213 Each of the three component groups (alphabetic, ideographic, phonetic) 1214 are supplied as a list of components. 1215 1216 Parameters 1217 ---------- 1218 alphabetic_group: Sequence[Union[str, bytes]] 1219 List of components for the alphabetic group. 1220 ideographic_group: Sequence[Union[str, bytes]] 1221 List of components for the ideographic group. 1222 phonetic_group: Sequence[Union[str, bytes]] 1223 List of components for the phonetic group. 1224 encodings: Optional[List[str]] 1225 A list of encodings used for the other input parameters. 1226 1227 Returns 1228 ------- 1229 bytes: 1230 Bytes string representation of the person name. 1231 1232 Raises 1233 ------ 1234 ValueError: 1235 If any of the input strings contain disallowed characters: 1236 '\\' (single backslash), '^', '='. 1237 """ 1238 from pydicom.charset import encode_string, decode_bytes 1239 1240 def enc(s: str) -> bytes: 1241 return encode_string(s, encodings or [default_encoding]) 1242 1243 def dec(s: bytes) -> str: 1244 return decode_bytes(s, encodings or [default_encoding], set()) 1245 1246 encoded_component_sep = enc('^') 1247 encoded_group_sep = enc('=') 1248 1249 disallowed_chars = ['\\', '=', '^'] 1250 1251 def standardize_encoding(val: Union[str, bytes]) -> bytes: 1252 # Return a byte encoded string regardless of the input type 1253 # This allows the user to supply a mixture of str and bytes 1254 # for different parts of the input 1255 if isinstance(val, bytes): 1256 val_enc = val 1257 val_dec = dec(val) 1258 else: 1259 val_enc = enc(val) 1260 val_dec = val 1261 1262 # Check for disallowed chars in the decoded string 1263 for c in disallowed_chars: 1264 if c in val_dec: 1265 raise ValueError( 1266 f'Strings may not contain the {c} character' 1267 ) 1268 1269 # Return the encoded string 1270 return val_enc 1271 1272 def make_component_group( 1273 components: Sequence[Union[str, bytes]] 1274 ) -> bytes: 1275 encoded_components = [standardize_encoding(c) for c in components] 1276 joined_components = encoded_component_sep.join(encoded_components) 1277 return joined_components.rstrip(encoded_component_sep) 1278 1279 component_groups: List[bytes] = [ 1280 make_component_group(alphabetic_group), 1281 make_component_group(ideographic_group), 1282 make_component_group(phonetic_group) 1283 ] 1284 joined_groups: bytes = encoded_group_sep.join(component_groups) 1285 joined_groups = joined_groups.rstrip(encoded_group_sep) 1286 return joined_groups 1287 1288 @classmethod 1289 def from_named_components( 1290 cls, 1291 family_name: Union[str, bytes] = '', 1292 given_name: Union[str, bytes] = '', 1293 middle_name: Union[str, bytes] = '', 1294 name_prefix: Union[str, bytes] = '', 1295 name_suffix: Union[str, bytes] = '', 1296 family_name_ideographic: Union[str, bytes] = '', 1297 given_name_ideographic: Union[str, bytes] = '', 1298 middle_name_ideographic: Union[str, bytes] = '', 1299 name_prefix_ideographic: Union[str, bytes] = '', 1300 name_suffix_ideographic: Union[str, bytes] = '', 1301 family_name_phonetic: Union[str, bytes] = '', 1302 given_name_phonetic: Union[str, bytes] = '', 1303 middle_name_phonetic: Union[str, bytes] = '', 1304 name_prefix_phonetic: Union[str, bytes] = '', 1305 name_suffix_phonetic: Union[str, bytes] = '', 1306 encodings: Optional[List[str]] = None, 1307 ) -> 'PersonName': 1308 """Construct a PersonName from explicit named components. 1309 1310 The DICOM standard describes human names using five components: 1311 family name, given name, middle name, name prefix, and name suffix. 1312 Any component may be an empty string (the default) if not used. 1313 A component may contain multiple space-separated words if there 1314 are, for example, multiple given names, middle names, or titles. 1315 1316 Additionally, each component may be represented in ideographic or 1317 phonetic form in addition to (or instead of) alphabetic form. 1318 1319 For more information see the following parts of the DICOM standard: 1320 - :dcm:`Value Representations <part05/sect_6.2.html>` 1321 - :dcm:`PN Examples <part05/sect_6.2.html#sect_6.2.1.1>` 1322 - :dcm:`PN Precise semantics <part05/sect_6.2.html#sect_6.2.1.2>` 1323 1324 Example 1325 ------- 1326 A case with multiple given names and suffixes (DICOM standard, 1327 part 5, sect 6.2.1.1): 1328 1329 >>> pn = PersonName.from_named_components( 1330 family_name='Adams', 1331 given_name='John Robert Quincy', 1332 name_prefix='Rev.', 1333 name_suffix='B.A. M.Div.' 1334 ) 1335 1336 A Korean case with phonetic and ideographic representations (PS3.5-2008 1337 section I.2 p. 108): 1338 1339 >>> pn = PersonName.from_named_components( 1340 family_name='Hong', 1341 given_name='Gildong', 1342 family_name_ideographic='洪', 1343 given_name_ideographic='吉洞', 1344 family_name_phonetic='홍', 1345 given_name_phonetic='길동', 1346 encodings=[default_encoding, 'euc_kr'] 1347 ) 1348 1349 Parameters 1350 ---------- 1351 family_name: Union[str, bytes] 1352 Family name in alphabetic form. 1353 given_name: Union[str, bytes] 1354 Given name in alphabetic form. 1355 middle_name: Union[str, bytes] 1356 Middle name in alphabetic form. 1357 name_prefix: Union[str, bytes] 1358 Name prefix in alphabetic form, e.g. 'Mrs.', 'Dr.', 'Sr.', 'Rev.'. 1359 name_suffix: Union[str, bytes] 1360 Name prefix in alphabetic form, e.g. 'M.D.', 'B.A., M.Div.', 1361 'Chief Executive Officer'. 1362 family_name_ideographic: Union[str, bytes] 1363 Family name in ideographic form. 1364 given_name_ideographic: Union[str, bytes] 1365 Given name in ideographic form. 1366 middle_name_ideographic: Union[str, bytes] 1367 Middle name in ideographic form. 1368 name_prefix_ideographic: Union[str, bytes] 1369 Name prefix in ideographic form. 1370 name_suffix_ideographic: Union[str, bytes] 1371 Name suffix in ideographic form. 1372 family_name_phonetic: Union[str, bytes] 1373 Family name in phonetic form. 1374 given_name_phonetic: Union[str, bytes] 1375 Given name in phonetic form. 1376 middle_name_phonetic: Union[str, bytes] 1377 Middle name in phonetic form. 1378 name_prefix_phonetic: Union[str, bytes] 1379 Name prefix in phonetic form. 1380 name_suffix_phonetic: Union[str, bytes] 1381 Name suffix in phonetic form. 1382 encodings: Optional[List[str]] 1383 A list of encodings used for the other input parameters. 1384 1385 Returns 1386 ------- 1387 PersonName: 1388 PersonName constructed from the supplied components. 1389 1390 Notes 1391 ----- 1392 Strings may not contain the following characters: '^', '=', 1393 or the backslash character. 1394 """ 1395 alphabetic_group: List[Union[str, bytes]] = [ 1396 family_name, 1397 given_name, 1398 middle_name, 1399 name_prefix, 1400 name_suffix, 1401 ] 1402 1403 # Ideographic component group 1404 ideographic_group: List[Union[str, bytes]] = [ 1405 family_name_ideographic, 1406 given_name_ideographic, 1407 middle_name_ideographic, 1408 name_prefix_ideographic, 1409 name_suffix_ideographic, 1410 ] 1411 1412 # Phonetic component group 1413 phonetic_group: List[Union[str, bytes]] = [ 1414 family_name_phonetic, 1415 given_name_phonetic, 1416 middle_name_phonetic, 1417 name_prefix_phonetic, 1418 name_suffix_phonetic, 1419 ] 1420 1421 encoded_value: bytes = cls._encode_component_groups( 1422 alphabetic_group, 1423 ideographic_group, 1424 phonetic_group, 1425 encodings, 1426 ) 1427 1428 return cls(encoded_value, encodings=encodings) 1429 1430 @classmethod 1431 def from_named_components_veterinary( 1432 cls, 1433 responsible_party_name: Union[str, bytes] = '', 1434 patient_name: Union[str, bytes] = '', 1435 responsible_party_name_ideographic: Union[str, bytes] = '', 1436 patient_name_ideographic: Union[str, bytes] = '', 1437 responsible_party_name_phonetic: Union[str, bytes] = '', 1438 patient_name_phonetic: Union[str, bytes] = '', 1439 encodings: Optional[List[str]] = None, 1440 ) -> 'PersonName': 1441 """Construct a PersonName from explicit named components following the 1442 veterinary usage convention. 1443 1444 The DICOM standard describes names for veterinary use with two components: 1445 responsible party family name OR responsible party organization name, 1446 and patient name. 1447 Any component may be an empty string (the default) if not used. 1448 A component may contain multiple space-separated words if necessary. 1449 1450 Additionally, each component may be represented in ideographic or 1451 phonetic form in addition to (or instead of) alphabetic form. 1452 1453 For more information see the following parts of the DICOM standard: 1454 - :dcm:`Value Representations <part05/sect_6.2.html>` 1455 - :dcm:`PN Examples <part05/sect_6.2.html#sect_6.2.1.1>` 1456 - :dcm:`PN Precise semantics <part05/sect_6.2.html#sect_6.2.1.1>` 1457 1458 Example 1459 ------- 1460 1461 A horse whose responsible organization is named "ABC Farms", and whose 1462 name is "Running On Water" 1463 1464 >>> pn = PersonName.from_named_components_veterinary( 1465 responsible_party_name='ABC Farms', 1466 patient_name='Running on Water' 1467 ) 1468 1469 Parameters 1470 ---------- 1471 responsible_party_name: Union[str, bytes] 1472 Name of the responsible party in alphabetic form. This may be 1473 either the family name of the responsible party, or the 1474 name of the responsible organization. 1475 patient_name: Union[str, bytes] 1476 Patient name in alphabetic form. 1477 responsible_party_name_ideographic: Union[str, bytes] 1478 Name of the responsible party in ideographic form. 1479 patient_name_ideographic: Union[str, bytes] 1480 Patient name in ideographic form. 1481 responsible_party_name_phonetic: Union[str, bytes] 1482 Name of the responsible party in phonetic form. 1483 patient_name_phonetic: Union[str, bytes] 1484 Patient name in phonetic form. 1485 encodings: Optional[List[str]] 1486 A list of encodings used for the other input parameters 1487 1488 Returns 1489 ------- 1490 PersonName: 1491 PersonName constructed from the supplied components 1492 1493 Notes 1494 ----- 1495 Strings may not contain the following characters: '^', '=', 1496 or the backslash character. 1497 """ 1498 alphabetic_group: List[Union[str, bytes]] = [ 1499 responsible_party_name, 1500 patient_name, 1501 ] 1502 1503 ideographic_group: List[Union[str, bytes]] = [ 1504 responsible_party_name_ideographic, 1505 patient_name_ideographic, 1506 ] 1507 1508 phonetic_group: List[Union[str, bytes]] = [ 1509 responsible_party_name_phonetic, 1510 patient_name_phonetic, 1511 ] 1512 1513 encoded_value: bytes = cls._encode_component_groups( 1514 alphabetic_group, 1515 ideographic_group, 1516 phonetic_group, 1517 encodings 1518 ) 1519 1520 return cls(encoded_value, encodings=encodings) 1521 1522 1523# Alias old class names for backwards compat in user code 1524def __getattr__(name: str) -> Any: 1525 if name == "PersonNameUnicode": 1526 warnings.warn( 1527 "'PersonNameUnicode' is deprecated and will be removed in " 1528 "pydicom v3.0, use 'PersonName' instead", 1529 DeprecationWarning 1530 ) 1531 return globals()['PersonName'] 1532 1533 raise AttributeError(f"module {__name__} has no attribute {name}") 1534 1535 1536if sys.version_info[:2] < (3, 7): 1537 PersonNameUnicode = PersonName 1538