1# Copyright 2008-2018 pydicom authors. See LICENSE file for details. 2"""Pydicom configuration options.""" 3 4# doc strings following items are picked up by sphinx for documentation 5 6import logging 7import os 8from typing import Optional, Dict, Any, TYPE_CHECKING 9 10have_numpy = True 11try: 12 import numpy 13except ImportError: 14 have_numpy = False 15 16if TYPE_CHECKING: # pragma: no cover 17 from pydicom.dataelem import RawDataElement 18 from typing import Protocol 19 20 class ElementCallback(Protocol): 21 def __call__( 22 self, 23 raw_elem: "RawDataElement", 24 **kwargs: Any, 25 ) -> "RawDataElement": ... 26 27 28# Set the type used to hold DS values 29# default False; was decimal-based in pydicom 0.9.7 30use_DS_decimal = False 31"""Set using :func:`~pydicom.config.DS_decimal` to control if elements with a 32VR of **DS** are represented as :class:`~decimal.Decimal`. 33 34Default ``False``. 35""" 36 37 38data_element_callback: Optional["ElementCallback"] = None 39"""Set to a callable function to be called from 40:func:`~pydicom.filereader.dcmread` every time a 41:class:`~pydicom.dataelem.RawDataElement` has been returned, 42before it is added to the :class:`~pydicom.dataset.Dataset`. 43 44Default ``None``. 45""" 46 47data_element_callback_kwargs: Dict[str, Any] = {} 48"""Set the keyword arguments passed to :func:`data_element_callback`. 49 50Default ``{}``. 51""" 52 53 54def reset_data_element_callback() -> None: 55 """Reset the :func:`data_element_callback` function to the default.""" 56 global data_element_callback 57 global data_element_callback_kwargs 58 data_element_callback = None 59 data_element_callback_kwargs = {} 60 61 62def DS_numpy(use_numpy: bool = True) -> None: 63 """Set whether multi-valued elements with VR of **DS** will be numpy arrays 64 65 .. versionadded:: 2.0 66 67 Parameters 68 ---------- 69 use_numpy : bool, optional 70 ``True`` (default) to read multi-value **DS** elements 71 as :class:`~numpy.ndarray`, ``False`` to read multi-valued **DS** 72 data elements as type :class:`~python.mulitval.MultiValue` 73 74 Note: once a value has been accessed, changing this setting will 75 no longer change its type 76 77 Raises 78 ------ 79 ValueError 80 If :data:`use_DS_decimal` and `use_numpy` are both True. 81 82 """ 83 84 global use_DS_numpy 85 86 if use_DS_decimal and use_numpy: 87 raise ValueError( 88 "Cannot use numpy arrays to read DS elements" 89 "if `use_DS_decimal` is True" 90 ) 91 use_DS_numpy = use_numpy 92 93 94def DS_decimal(use_Decimal_boolean: bool = True) -> None: 95 """Set DS class to be derived from :class:`decimal.Decimal` or 96 :class:`float`. 97 98 If this function is never called, the default in *pydicom* >= 0.9.8 99 is for DS to be based on :class:`float`. 100 101 Parameters 102 ---------- 103 use_Decimal_boolean : bool, optional 104 ``True`` (default) to derive :class:`~pydicom.valuerep.DS` from 105 :class:`decimal.Decimal`, ``False`` to derive it from :class:`float`. 106 107 Raises 108 ------ 109 ValueError 110 If `use_Decimal_boolean` and :data:`use_DS_numpy` are 111 both ``True``. 112 """ 113 global use_DS_decimal 114 115 use_DS_decimal = use_Decimal_boolean 116 117 if use_DS_decimal and use_DS_numpy: 118 raise ValueError( 119 "Cannot set use_DS_decimal True " "if use_DS_numpy is True" 120 ) 121 122 import pydicom.valuerep 123 124 if use_DS_decimal: 125 pydicom.valuerep.DSclass = pydicom.valuerep.DSdecimal 126 else: 127 pydicom.valuerep.DSclass = pydicom.valuerep.DSfloat 128 129 130# Configuration flags 131use_DS_numpy = False 132"""Set using the function :func:`~pydicom.config.DS_numpy` to control 133whether arrays of VR **DS** are returned as numpy arrays. 134Default: ``False``. 135 136.. versionadded:: 2.0 137""" 138 139use_IS_numpy = False 140"""Set to False to avoid IS values being returned as numpy ndarray objects. 141Default: ``False``. 142 143.. versionadded:: 2.0 144""" 145 146allow_DS_float = False 147"""Set to ``True`` to allow :class:`~pydicom.valuerep.DSdecimal` 148instances to be created using :class:`floats<float>`; otherwise, they must be 149explicitly converted to :class:`str`, with the user explicitly setting the 150precision of digits and rounding. 151 152Default ``False``. 153""" 154 155enforce_valid_values = False 156"""Raise exceptions if any value is not allowed by DICOM Standard. 157 158e.g. DS strings that are longer than 16 characters; IS strings outside 159the allowed range. 160 161Default ``False``. 162""" 163 164convert_wrong_length_to_UN = False 165"""Convert a field VR to "UN" and return bytes if bytes length is invalid. 166Default ``False``. 167""" 168 169datetime_conversion = False 170"""Set to ``True`` to convert the value(s) of elements with a VR of DA, DT and 171TM to :class:`datetime.date`, :class:`datetime.datetime` and 172:class:`datetime.time` respectively. 173Note that when datetime conversion is enabled then range matching in 174C-GET/C-FIND/C-MOVE queries is not possible anymore. So if you need range 175matching we recommend to do the conversion manually. 176 177Default ``False`` 178 179References 180---------- 181* :dcm:`Range Matching<part04/sect_C.2.2.2.5.html>` 182""" 183 184use_none_as_empty_text_VR_value = False 185""" If ``True``, the value of a decoded empty data element with 186a text VR is ``None``, otherwise (the default), it is is an empty string. 187For all other VRs the behavior does not change - the value is en empty 188list for VR **SQ** and ``None`` for all other VRs. 189Note that the default of this value may change to ``True`` in a later version. 190 191.. versionadded:: 1.4 192""" 193 194replace_un_with_known_vr = True 195""" If ``True``, and the VR of a known data element is encoded as **UN** in 196an explicit encoding, the VR is changed to the known value. 197Can be set to ``False`` where the content of the tag shown as **UN** is 198not DICOM conformant and would lead to a failure if accessing it. 199 200.. versionadded:: 2.0 201""" 202 203show_file_meta = True 204""" 205.. versionadded:: 2.0 206 207If ``True`` (default), the 'str' and 'repr' methods 208of :class:`~pydicom.dataset.Dataset` begin with a separate section 209displaying the file meta information data elements 210""" 211 212# Logging system and debug function to change logging level 213logger = logging.getLogger("pydicom") 214logger.addHandler(logging.NullHandler()) 215 216import pydicom.overlays.numpy_handler as overlay_np # noqa 217 218overlay_data_handlers = [overlay_np] 219"""Handlers for converting (60xx,3000) *Overlay Data* 220 221.. versionadded:: 1.4 222 223.. deprecated:: 2.1 224 225.. currentmodule:: pydicom.dataset 226 227This is an ordered list of *Overlay Data* handlers that the 228:meth:`~Dataset.overlay_array` method will use to try to extract a correctly 229sized numpy array from an *Overlay Data* element. 230 231Handlers have two required methods: 232 233def is_available(): 234 Return ``True`` if the handler's dependencies are installed, ``False`` 235 otherwise. 236 237def get_overlay_array(ds, group): 238 Return a correctly shaped :class:`numpy.ndarray` derived from the 239 *Overlay Data* with element tag `group`, in :class:`Dataset` `ds` or raise 240 an exception. 241 242And two required attributes: 243 244DEPENDENCIES : dict 245 A dict containing the dependencies of the handler as 246 {'package_import_name': ('http://package.com/url', 'Package Name')} 247HANDLER_NAME : str 248 The name of the handler, e.g. 'Numpy Overlay' 249 250The first handler that both announces that it supports the transfer syntax 251and does not raise an exception is the handler that will provide the 252data. 253 254If all handlers fail to convert the data only the last exception is raised. 255""" 256 257import pydicom.pixel_data_handlers.numpy_handler as np_handler # noqa 258import pydicom.pixel_data_handlers.rle_handler as rle_handler # noqa 259import pydicom.pixel_data_handlers.pillow_handler as pillow_handler # noqa 260import pydicom.pixel_data_handlers.jpeg_ls_handler as jpegls_handler # noqa 261import pydicom.pixel_data_handlers.gdcm_handler as gdcm_handler # noqa 262import pydicom.pixel_data_handlers.pylibjpeg_handler as pylibjpeg_handler # noqa 263 264pixel_data_handlers = [ 265 np_handler, 266 rle_handler, 267 gdcm_handler, 268 pillow_handler, 269 jpegls_handler, 270 pylibjpeg_handler, 271] 272"""Handlers for converting (7FE0,0010) *Pixel Data*. 273 274.. versionadded:: 1.2 275 276.. currentmodule:: pydicom.dataset 277 278This is an ordered list of *Pixel Data* handlers that the 279:meth:`~Dataset.convert_pixel_data` method will use to try to extract a 280correctly sized numpy array from the *Pixel Data* element. 281 282Handlers shall have four methods: 283 284def supports_transfer_syntax(transfer_syntax: UID) 285 Return ``True`` if the handler supports the transfer syntax indicated in 286 :class:`Dataset` `ds`, ``False`` otherwise. 287 288def is_available(): 289 Return ``True`` if the handler's dependencies are installed, ``False`` 290 otherwise. 291 292def get_pixeldata(ds): 293 Return a correctly sized 1D :class:`numpy.ndarray` derived from the 294 *Pixel Data* in :class:`Dataset` `ds` or raise an exception. Reshaping the 295 returned array to the correct dimensions is handled automatically. 296 297def needs_to_convert_to_RGB(ds): 298 Return ``True`` if the *Pixel Data* in the :class:`Dataset` `ds` needs to 299 be converted to the RGB colourspace, ``False`` otherwise. 300 301The first handler that both announces that it supports the transfer syntax 302and does not raise an exception, either in getting the data or when the data 303is reshaped to the correct dimensions, is the handler that will provide the 304data. 305 306If they all fail only the last exception is raised. 307 308If none raise an exception, but they all refuse to support the transfer 309syntax, then this fact is announced in a :class:`NotImplementedError` 310exception. 311""" 312 313APPLY_J2K_CORRECTIONS = True 314"""Use the information within JPEG 2000 data to correct the returned pixel data 315 316.. versionadded:: 2.1 317 318If ``True`` (default), then for handlers that support JPEG 2000 pixel data, 319use the component precision and sign to correct the returned ndarray when 320using the pixel data handlers. If ``False`` then only rely on the element 321values within the dataset when applying corrections. 322""" 323 324assume_implicit_vr_switch = True 325"""If invalid VR encountered, assume file switched to implicit VR 326 327.. versionadded:: 2.2 328 329If ``True`` (default), when reading an explicit VR file, 330if a VR is encountered that is not a valid two bytes within A-Z, 331then assume the original writer switched to implicit VR. This has been 332seen in particular in some sequences. This does not test that 333the VR is a valid DICOM VR, just that it has valid characters. 334""" 335 336 337INVALID_KEYWORD_BEHAVIOR = "WARN" 338"""Control the behavior when setting a :class:`~pydicom.dataset.Dataset` 339attribute that's not a known element keyword. 340 341.. versionadded:: 2.1 342 343If ``"WARN"`` (default), then warn when an element value is set using 344``Dataset.__setattr__()`` and the keyword is camel case but doesn't match a 345known DICOM element keyword. If ``"RAISE"`` then raise a :class:`ValueError` 346exception. If ``"IGNORE"`` then neither warn nor raise. 347 348Examples 349-------- 350 351>>> from pydicom import config 352>>> config.INVALID_KEYWORD_BEHAVIOR = "WARN" 353>>> ds = Dataset() 354>>> ds.PatientName = "Citizen^Jan" # OK 355>>> ds.PatientsName = "Citizen^Jan" 356../pydicom/dataset.py:1895: UserWarning: Camel case attribute 'PatientsName' 357used which is not in the element keyword data dictionary 358""" 359 360INVALID_KEY_BEHAVIOR = "WARN" 361"""Control the behavior when invalid keys are used with 362:meth:`~pydicom.dataset.Dataset.__contains__` (e.g. ``'invalid' in ds``). 363 364.. versionadded:: 2.1 365 366Invalid keys are objects that cannot be converted to a 367:class:`~pydicom.tag.BaseTag`, such as unknown element keywords or invalid 368element tags like ``0x100100010``. 369 370If ``"WARN"`` (default), then warn when an invalid key is used, if ``"RAISE"`` 371then raise a :class:`ValueError` exception. If ``"IGNORE"`` then neither warn 372nor raise. 373 374Examples 375-------- 376 377>>> from pydicom import config 378>>> config.INVALID_KEY_BEHAVIOR = "RAISE" 379>>> ds = Dataset() 380>>> 'PatientName' in ds # OK 381False 382>>> 'PatientsName' in ds 383Traceback (most recent call last): 384 File "<stdin>", line 1, in <module> 385 File ".../pydicom/dataset.py", line 494, in __contains__ 386 raise ValueError(msg) from exc 387ValueError: Invalid value used with the 'in' operator: must be an 388element tag as a 2-tuple or int, or an element keyword 389""" 390 391debugging: bool 392 393 394def debug(debug_on: bool = True, default_handler: bool = True) -> None: 395 """Turn on/off debugging of DICOM file reading and writing. 396 397 When debugging is on, file location and details about the elements read at 398 that location are logged to the 'pydicom' logger using Python's 399 :mod:`logging` 400 module. 401 402 .. versionchanged:1.4 403 404 Added `default_handler` keyword parameter. 405 406 Parameters 407 ---------- 408 debug_on : bool, optional 409 If ``True`` (default) then turn on debugging, ``False`` to turn off. 410 default_handler : bool, optional 411 If ``True`` (default) then use :class:`logging.StreamHandler` as the 412 handler for log messages. 413 """ 414 global logger, debugging 415 416 if default_handler: 417 handler = logging.StreamHandler() 418 formatter = logging.Formatter("%(message)s") 419 handler.setFormatter(formatter) 420 logger.addHandler(handler) 421 422 if debug_on: 423 logger.setLevel(logging.DEBUG) 424 debugging = True 425 else: 426 logger.setLevel(logging.WARNING) 427 debugging = False 428 429 430# force level=WARNING, in case logging default is set differently (issue 103) 431debug(False, False) 432 433_use_future = False 434_use_future_env = os.getenv("PYDICOM_FUTURE") 435 436if _use_future_env: 437 if _use_future_env.lower() in ["true", "yes", "on", "1"]: 438 _use_future = True 439 elif _use_future_env.lower() in ["false", "no", "off", "0"]: 440 _use_future = False 441 else: 442 raise ValueError( 443 "Unknown setting for environment variable " 444 "PYDICOM_FUTURE. Use True or False." 445 ) 446 447 448def future_behavior(enable_future: bool = True) -> None: 449 """Imitate the behavior for the next major version of *pydicom*. 450 451 .. versionadded:: 2.1 452 453 This can be used to ensure your code is "future-proof" for known 454 upcoming changes in the next major version of *pydicom*. Typically, 455 deprecations become errors, and default values of config flags may change. 456 457 Parameters 458 ---------- 459 enable_future: bool 460 Set ``True`` (default) to emulate future pydicom behavior, 461 ``False`` to reset to current pydicom behavior. 462 463 See also 464 -------- 465 :attr:`~pydicom.config.INVALID_KEYWORD_BEHAVIOR` 466 :attr:`~pydicom.config.INVALID_KEY_BEHAVIOR` 467 468 """ 469 global _use_future, INVALID_KEYWORD_BEHAVIOR 470 471 if enable_future: 472 _use_future = True 473 INVALID_KEYWORD_BEHAVIOR = "RAISE" 474 else: 475 _use_future = False 476 INVALID_KEYWORD_BEHAVIOR = "WARN" 477 478 479if _use_future: 480 future_behavior() 481