/
dataset.py
2916 lines (2441 loc) · 104 KB
/
dataset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2008-2021 pydicom authors. See LICENSE file for details.
"""Define the Dataset and FileDataset classes.
The Dataset class represents the DICOM Dataset while the FileDataset class
adds extra functionality to Dataset when data is read from or written to file.
Overview of DICOM object model
------------------------------
Dataset (dict subclass)
Contains DataElement instances, each of which has a tag, VR, VM and value.
The DataElement value can be:
* A single value, such as a number, string, etc. (i.e. VM = 1)
* A list of numbers, strings, etc. (i.e. VM > 1)
* A Sequence (list subclass), where each item is a Dataset which
contains its own DataElements, and so on in a recursive manner.
"""
import copy
from bisect import bisect_left
import io
from importlib.util import find_spec as have_package
import inspect # for __dir__
from itertools import takewhile
import json
import os
import os.path
import re
from types import TracebackType
from typing import (
Optional, Tuple, Union, List, Any, cast, Dict, ValuesView,
Iterator, BinaryIO, AnyStr, Callable, TypeVar, Type, overload,
MutableSequence, MutableMapping, AbstractSet
)
import warnings
import weakref
from pydicom.filebase import DicomFileLike
try:
import numpy
except ImportError:
pass
import pydicom # for dcmwrite
from pydicom import jsonrep, config
from pydicom._version import __version_info__
from pydicom.charset import default_encoding, convert_encodings
from pydicom.config import logger
from pydicom.datadict import (
dictionary_VR, tag_for_keyword, keyword_for_tag, repeater_has_keyword
)
from pydicom.dataelem import DataElement, DataElement_from_raw, RawDataElement
from pydicom.encaps import encapsulate, encapsulate_extended
from pydicom.fileutil import path_from_pathlike, PathType
from pydicom.pixel_data_handlers.util import (
convert_color_space, reshape_pixel_array, get_image_pixel_ids
)
from pydicom.tag import Tag, BaseTag, tag_in_exception, TagType
from pydicom.uid import (
ExplicitVRLittleEndian, ImplicitVRLittleEndian, ExplicitVRBigEndian,
RLELossless, PYDICOM_IMPLEMENTATION_UID, UID
)
from pydicom.valuerep import VR as VR_, AMBIGUOUS_VR
from pydicom.waveforms import numpy_handler as wave_handler
class PrivateBlock:
"""Helper class for a private block in the :class:`Dataset`.
.. versionadded:: 1.3
See the DICOM Standard, Part 5,
:dcm:`Section 7.8.1<part05/sect_7.8.html#sect_7.8.1>` - Private Data
Element Tags
Attributes
----------
group : int
The private group where the private block is located as a 32-bit
:class:`int`.
private_creator : str
The private creator string related to the block.
dataset : Dataset
The parent dataset.
block_start : int
The start element of the private block as a 32-bit :class:`int`. Note
that the 2 low order hex digits of the element are always 0.
"""
def __init__(
self,
key: Tuple[int, str],
dataset: "Dataset",
private_creator_element: int
) -> None:
"""Initializes an object corresponding to a private tag block.
Parameters
----------
key : tuple
The private (tag group, creator) as ``(int, str)``. The group
must be an odd number.
dataset : Dataset
The parent :class:`Dataset`.
private_creator_element : int
The element of the private creator tag as a 32-bit :class:`int`.
"""
self.group = key[0]
self.private_creator = key[1]
self.dataset = dataset
self.block_start = private_creator_element << 8
def get_tag(self, element_offset: int) -> BaseTag:
"""Return the private tag ID for the given `element_offset`.
Parameters
----------
element_offset : int
The lower 16 bits (e.g. 2 hex numbers) of the element tag.
Returns
-------
The tag ID defined by the private block location and the
given element offset.
Raises
------
ValueError
If `element_offset` is too large.
"""
if element_offset > 0xff:
raise ValueError('Element offset must be less than 256')
return Tag(self.group, self.block_start + element_offset)
def __contains__(self, element_offset: int) -> bool:
"""Return ``True`` if the tag with given `element_offset` is in
the parent :class:`Dataset`.
"""
return self.get_tag(element_offset) in self.dataset
def __getitem__(self, element_offset: int) -> DataElement:
"""Return the data element in the parent dataset for the given element
offset.
Parameters
----------
element_offset : int
The lower 16 bits (e.g. 2 hex numbers) of the element tag.
Returns
-------
The data element of the tag in the parent dataset defined by the
private block location and the given element offset.
Raises
------
ValueError
If `element_offset` is too large.
KeyError
If no data element exists at that offset.
"""
return self.dataset.__getitem__(self.get_tag(element_offset))
def __delitem__(self, element_offset: int) -> None:
"""Delete the tag with the given `element_offset` from the dataset.
Parameters
----------
element_offset : int
The lower 16 bits (e.g. 2 hex numbers) of the element tag
to be deleted.
Raises
------
ValueError
If `element_offset` is too large.
KeyError
If no data element exists at that offset.
"""
del self.dataset[self.get_tag(element_offset)]
def add_new(self, element_offset: int, VR: str, value: object) -> None:
"""Add a private element to the parent :class:`Dataset`.
Adds the private tag with the given `VR` and `value` to the parent
:class:`Dataset` at the tag ID defined by the private block and the
given `element_offset`.
Parameters
----------
element_offset : int
The lower 16 bits (e.g. 2 hex numbers) of the element tag
to be added.
VR : str
The 2 character DICOM value representation.
value
The value of the data element. See :meth:`Dataset.add_new()`
for a description.
"""
tag = self.get_tag(element_offset)
self.dataset.add_new(tag, VR, value)
self.dataset[tag].private_creator = self.private_creator
def _dict_equal(
a: "Dataset", b: Any, exclude: Optional[List[str]] = None
) -> bool:
"""Common method for Dataset.__eq__ and FileDataset.__eq__
Uses .keys() as needed because Dataset iter return items not keys
`exclude` is used in FileDataset__eq__ ds.__dict__ compare, which
would also compare the wrapped _dict member (entire dataset) again.
"""
return (len(a) == len(b) and
all(key in b for key in a.keys()) and
all(a[key] == b[key] for key in a.keys()
if exclude is None or key not in exclude)
)
_DatasetValue = Union[DataElement, RawDataElement]
_DatasetType = Union["Dataset", MutableMapping[BaseTag, _DatasetValue]]
class Dataset:
"""A DICOM dataset as a mutable mapping of DICOM Data Elements.
Examples
--------
Add an element to the :class:`Dataset` (for elements in the DICOM
dictionary):
>>> ds = Dataset()
>>> ds.PatientName = "CITIZEN^Joan"
>>> ds.add_new(0x00100020, 'LO', '12345')
>>> ds[0x0010, 0x0030] = DataElement(0x00100030, 'DA', '20010101')
Add a sequence element to the :class:`Dataset`
>>> ds.BeamSequence = [Dataset(), Dataset(), Dataset()]
>>> ds.BeamSequence[0].Manufacturer = "Linac, co."
>>> ds.BeamSequence[1].Manufacturer = "Linac and Sons, co."
>>> ds.BeamSequence[2].Manufacturer = "Linac and Daughters, co."
Add private elements to the :class:`Dataset`
>>> block = ds.private_block(0x0041, 'My Creator', create=True)
>>> block.add_new(0x01, 'LO', '12345')
Updating and retrieving element values:
>>> ds.PatientName = "CITIZEN^Joan"
>>> ds.PatientName
'CITIZEN^Joan'
>>> ds.PatientName = "CITIZEN^John"
>>> ds.PatientName
'CITIZEN^John'
Retrieving an element's value from a Sequence:
>>> ds.BeamSequence[0].Manufacturer
'Linac, co.'
>>> ds.BeamSequence[1].Manufacturer
'Linac and Sons, co.'
Accessing the :class:`~pydicom.dataelem.DataElement` items:
>>> elem = ds['PatientName']
>>> elem
(0010, 0010) Patient's Name PN: 'CITIZEN^John'
>>> elem = ds[0x00100010]
>>> elem
(0010, 0010) Patient's Name PN: 'CITIZEN^John'
>>> elem = ds.data_element('PatientName')
>>> elem
(0010, 0010) Patient's Name PN: 'CITIZEN^John'
Accessing a private :class:`~pydicom.dataelem.DataElement`
item:
>>> block = ds.private_block(0x0041, 'My Creator')
>>> elem = block[0x01]
>>> elem
(0041, 1001) Private tag data LO: '12345'
>>> elem.value
'12345'
Alternatively:
>>> ds.get_private_item(0x0041, 0x01, 'My Creator').value
'12345'
Deleting an element from the :class:`Dataset`
>>> del ds.PatientID
>>> del ds.BeamSequence[1].Manufacturer
>>> del ds.BeamSequence[2]
Deleting a private element from the :class:`Dataset`
>>> block = ds.private_block(0x0041, 'My Creator')
>>> if 0x01 in block:
... del block[0x01]
Determining if an element is present in the :class:`Dataset`
>>> 'PatientName' in ds
True
>>> 'PatientID' in ds
False
>>> (0x0010, 0x0030) in ds
True
>>> 'Manufacturer' in ds.BeamSequence[0]
True
Iterating through the top level of a :class:`Dataset` only (excluding
Sequences):
>>> for elem in ds:
... print(elem)
(0010, 0010) Patient's Name PN: 'CITIZEN^John'
Iterating through the entire :class:`Dataset` (including Sequences):
>>> for elem in ds.iterall():
... print(elem)
(0010, 0010) Patient's Name PN: 'CITIZEN^John'
Recursively iterate through a :class:`Dataset` (including Sequences):
>>> def recurse(ds):
... for elem in ds:
... if elem.VR == 'SQ':
... [recurse(item) for item in elem.value]
... else:
... # Do something useful with each DataElement
Converting the :class:`Dataset` to and from JSON:
>>> ds = Dataset()
>>> ds.PatientName = "Some^Name"
>>> jsonmodel = ds.to_json()
>>> ds2 = Dataset()
>>> ds2.from_json(jsonmodel)
(0010, 0010) Patient's Name PN: 'Some^Name'
Attributes
----------
default_element_format : str
The default formatting for string display.
default_sequence_element_format : str
The default formatting for string display of sequences.
indent_chars : str
For string display, the characters used to indent nested Sequences.
Default is ``" "``.
is_little_endian : bool
Shall be set before writing with ``write_like_original=False``.
The :class:`Dataset` (excluding the pixel data) will be written using
the given endianness.
is_implicit_VR : bool
Shall be set before writing with ``write_like_original=False``.
The :class:`Dataset` will be written using the transfer syntax with
the given VR handling, e.g *Little Endian Implicit VR* if ``True``,
and *Little Endian Explicit VR* or *Big Endian Explicit VR* (depending
on ``Dataset.is_little_endian``) if ``False``.
"""
indent_chars = " "
def __init__(self, *args: _DatasetType, **kwargs: Any) -> None:
"""Create a new :class:`Dataset` instance."""
self._parent_encoding: List[str] = kwargs.get(
'parent_encoding', default_encoding
)
self._dict: MutableMapping[BaseTag, _DatasetValue]
if not args:
self._dict = {}
elif isinstance(args[0], Dataset):
self._dict = args[0]._dict
else:
self._dict = args[0]
self.is_decompressed = False
# the following read_XXX attributes are used internally to store
# the properties of the dataset after read from a file
# set depending on the endianness of the read dataset
self.read_little_endian: Optional[bool] = None
# set depending on the VR handling of the read dataset
self.read_implicit_vr: Optional[bool] = None
# The dataset's original character set encoding
self.read_encoding: Union[None, str, MutableSequence[str]] = None
self.is_little_endian: Optional[bool] = None
self.is_implicit_VR: Optional[bool] = None
# True if the dataset is a sequence item with undefined length
self.is_undefined_length_sequence_item = False
# the parent data set, if this dataset is a sequence item
self.parent: "Optional[weakref.ReferenceType[Dataset]]" = None
# known private creator blocks
self._private_blocks: Dict[Tuple[int, str], PrivateBlock] = {}
self._pixel_array: Optional["numpy.ndarray"] = None
self._pixel_id: Dict[str, int] = {}
self.file_meta: FileMetaDataset
def __enter__(self) -> "Dataset":
"""Method invoked on entry to a with statement."""
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]
) -> Optional[bool]:
"""Method invoked on exit from a with statement."""
# Returning anything other than True will re-raise any exceptions
return None
def add(self, data_element: DataElement) -> None:
"""Add an element to the :class:`Dataset`.
Equivalent to ``ds[data_element.tag] = data_element``
Parameters
----------
data_element : dataelem.DataElement
The :class:`~pydicom.dataelem.DataElement` to add.
"""
self[data_element.tag] = data_element
def add_new(self, tag: TagType, VR: str, value: Any) -> None:
"""Create a new element and add it to the :class:`Dataset`.
Parameters
----------
tag
The DICOM (group, element) tag in any form accepted by
:func:`~pydicom.tag.Tag` such as ``[0x0010, 0x0010]``,
``(0x10, 0x10)``, ``0x00100010``, etc.
VR : str
The 2 character DICOM value representation (see DICOM Standard,
Part 5, :dcm:`Section 6.2<part05/sect_6.2.html>`).
value
The value of the data element. One of the following:
* a single string or number
* a :class:`list` or :class:`tuple` with all strings or all numbers
* a multi-value string with backslash separator
* for a sequence element, an empty :class:`list` or ``list`` of
:class:`Dataset`
"""
self.add(DataElement(tag, VR, value))
def __array__(self) -> "numpy.ndarray":
"""Support accessing the dataset from a numpy array."""
return numpy.asarray(self._dict)
def data_element(self, name: str) -> Optional[DataElement]:
"""Return the element corresponding to the element keyword `name`.
Parameters
----------
name : str
A DICOM element keyword.
Returns
-------
dataelem.DataElement or None
For the given DICOM element `keyword`, return the corresponding
:class:`~pydicom.dataelem.DataElement` if present, ``None``
otherwise.
"""
tag = tag_for_keyword(name)
# Test against None as (0000,0000) is a possible tag
if tag is not None:
return self[tag]
return None
def __contains__(self, name: TagType) -> bool:
"""Simulate dict.__contains__() to handle DICOM keywords.
Examples
--------
>>> ds = Dataset()
>>> ds.SliceLocation = '2'
>>> 'SliceLocation' in ds
True
Parameters
----------
name : str or int or 2-tuple
The element keyword or tag to search for.
Returns
-------
bool
``True`` if the corresponding element is in the :class:`Dataset`,
``False`` otherwise.
"""
try:
return Tag(name) in self._dict
except Exception as exc:
msg = (
f"Invalid value '{name}' used with the 'in' operator: must be "
"an element tag as a 2-tuple or int, or an element keyword"
)
if isinstance(exc, OverflowError):
msg = (
"Invalid element tag value used with the 'in' operator: "
"tags have a maximum value of (0xFFFF, 0xFFFF)"
)
if config.INVALID_KEY_BEHAVIOR == "WARN":
warnings.warn(msg)
elif config.INVALID_KEY_BEHAVIOR == "RAISE":
raise ValueError(msg) from exc
return False
def decode(self) -> None:
"""Apply character set decoding to the elements in the
:class:`Dataset`.
See DICOM Standard, Part 5,
:dcm:`Section 6.1.1<part05/chapter_6.html#sect_6.1.1>`.
"""
# Find specific character set. 'ISO_IR 6' is default
# May be multi-valued, but let pydicom.charset handle all logic on that
dicom_character_set = self._character_set
# Shortcut to the decode function in pydicom.charset
decode_data_element = pydicom.charset.decode_element
# Callback for walk(), to decode the chr strings if necessary
# This simply calls the pydicom.charset.decode_element function
def decode_callback(ds: "Dataset", data_element: DataElement) -> None:
"""Callback to decode `data_element`."""
if data_element.VR == VR_.SQ:
for dset in data_element.value:
dset._parent_encoding = dicom_character_set
dset.decode()
else:
decode_data_element(data_element, dicom_character_set)
self.walk(decode_callback, recursive=False)
def copy(self) -> "Dataset":
"""Return a shallow copy of the dataset."""
return copy.copy(self)
def __delattr__(self, name: str) -> None:
"""Intercept requests to delete an attribute by `name`.
Examples
--------
>>> ds = Dataset()
>>> ds.PatientName = 'foo'
>>> ds.some_attribute = True
If `name` is a DICOM keyword - delete the corresponding
:class:`~pydicom.dataelem.DataElement`
>>> del ds.PatientName
>>> 'PatientName' in ds
False
If `name` is another attribute - delete it
>>> del ds.some_attribute
>>> hasattr(ds, 'some_attribute')
False
Parameters
----------
name : str
The keyword for the DICOM element or the class attribute to delete.
"""
# First check if a valid DICOM keyword and if we have that data element
tag = cast(BaseTag, tag_for_keyword(name))
if tag is not None and tag in self._dict:
del self._dict[tag]
# If not a DICOM name in this dataset, check for regular instance name
# can't do delete directly, that will call __delattr__ again
elif name in self.__dict__:
del self.__dict__[name]
# Not found, raise an error in same style as python does
else:
raise AttributeError(name)
def __delitem__(self, key: Union[slice, BaseTag, TagType]) -> None:
"""Intercept requests to delete an attribute by key.
Examples
--------
Indexing using :class:`~pydicom.dataelem.DataElement` tag
>>> ds = Dataset()
>>> ds.CommandGroupLength = 100
>>> ds.PatientName = 'CITIZEN^Jan'
>>> del ds[0x00000000]
>>> ds
(0010, 0010) Patient's Name PN: 'CITIZEN^Jan'
Slicing using :class:`~pydicom.dataelem.DataElement` tag
>>> ds = Dataset()
>>> ds.CommandGroupLength = 100
>>> ds.SOPInstanceUID = '1.2.3'
>>> ds.PatientName = 'CITIZEN^Jan'
>>> del ds[:0x00100000]
>>> ds
(0010, 0010) Patient's Name PN: 'CITIZEN^Jan'
Parameters
----------
key
The key for the attribute to be deleted. If a ``slice`` is used
then the tags matching the slice conditions will be deleted.
"""
# If passed a slice, delete the corresponding DataElements
if isinstance(key, slice):
for tag in self._slice_dataset(key.start, key.stop, key.step):
del self._dict[tag]
# invalidate private blocks in case a private creator is
# deleted - will be re-created on next access
if self._private_blocks and BaseTag(tag).is_private_creator:
self._private_blocks = {}
elif isinstance(key, BaseTag):
del self._dict[key]
if self._private_blocks and key.is_private_creator:
self._private_blocks = {}
else:
# If not a standard tag, than convert to Tag and try again
tag = Tag(key)
del self._dict[tag]
if self._private_blocks and tag.is_private_creator:
self._private_blocks = {}
def __dir__(self) -> List[str]:
"""Return a list of methods, properties, attributes and element
keywords available in the :class:`Dataset`.
List of attributes is used, for example, in auto-completion in editors
or command-line environments.
"""
names = set(super().__dir__())
keywords = set(self.dir())
return sorted(names | keywords)
def dir(self, *filters: str) -> List[str]:
"""Return an alphabetical list of element keywords in the
:class:`Dataset`.
Intended mainly for use in interactive Python sessions. Only lists the
element keywords in the current level of the :class:`Dataset` (i.e.
the contents of any sequence elements are ignored).
Parameters
----------
filters : str
Zero or more string arguments to the function. Used for
case-insensitive match to any part of the DICOM keyword.
Returns
-------
list of str
The matching element keywords in the dataset. If no
filters are used then all element keywords are returned.
"""
allnames = [keyword_for_tag(tag) for tag in self._dict.keys()]
# remove blanks - tags without valid names (e.g. private tags)
allnames = [x for x in allnames if x]
# Store found names in a dict, so duplicate names appear only once
matches = {}
for filter_ in filters:
filter_ = filter_.lower()
match = [x for x in allnames if x.lower().find(filter_) != -1]
matches.update({x: 1 for x in match})
if filters:
return sorted(matches.keys())
return sorted(allnames)
def __eq__(self, other: Any) -> bool:
"""Compare `self` and `other` for equality.
Returns
-------
bool
The result if `self` and `other` are the same class
NotImplemented
If `other` is not the same class as `self` then returning
:class:`NotImplemented` delegates the result to
``superclass.__eq__(subclass)``.
"""
# When comparing against self this will be faster
if other is self:
return True
if isinstance(other, self.__class__):
return _dict_equal(self, other)
return NotImplemented
@overload
def get(self, key: str, default: Optional[Any] = None) -> Any:
pass # pragma: no cover
@overload
def get(
self,
key: Union[int, Tuple[int, int], BaseTag],
default: Optional[Any] = None
) -> DataElement:
pass # pragma: no cover
def get(
self,
key: Union[str, Union[int, Tuple[int, int], BaseTag]],
default: Optional[Any] = None
) -> Union[Any, DataElement]:
"""Simulate ``dict.get()`` to handle element tags and keywords.
Parameters
----------
key : str or int or Tuple[int, int] or BaseTag
The element keyword or tag or the class attribute name to get.
default : obj or None, optional
If the element or class attribute is not present, return
`default` (default ``None``).
Returns
-------
value
If `key` is the keyword for an element in the :class:`Dataset`
then return the element's value.
dataelem.DataElement
If `key` is a tag for a element in the :class:`Dataset` then
return the :class:`~pydicom.dataelem.DataElement`
instance.
value
If `key` is a class attribute then return its value.
"""
if isinstance(key, str):
try:
return getattr(self, key)
except AttributeError:
return default
# is not a string, try to make it into a tag and then hand it
# off to the underlying dict
try:
key = Tag(key)
except Exception as exc:
raise TypeError("Dataset.get key must be a string or tag") from exc
try:
return self.__getitem__(key)
except KeyError:
return default
def items(self) -> AbstractSet[Tuple[BaseTag, _DatasetValue]]:
"""Return the :class:`Dataset` items to simulate :meth:`dict.items`.
Returns
-------
dict_items
The top-level (:class:`~pydicom.tag.BaseTag`,
:class:`~pydicom.dataelem.DataElement`) items for the
:class:`Dataset`.
"""
return self._dict.items()
def keys(self) -> AbstractSet[BaseTag]:
"""Return the :class:`Dataset` keys to simulate :meth:`dict.keys`.
Returns
-------
dict_keys
The :class:`~pydicom.tag.BaseTag` of all the elements in
the :class:`Dataset`.
"""
return self._dict.keys()
def values(self) -> ValuesView[_DatasetValue]:
"""Return the :class:`Dataset` values to simulate :meth:`dict.values`.
Returns
-------
dict_keys
The :class:`DataElements<pydicom.dataelem.DataElement>` that make
up the values of the :class:`Dataset`.
"""
return self._dict.values()
def __getattr__(self, name: str) -> Any:
"""Intercept requests for :class:`Dataset` attribute names.
If `name` matches a DICOM keyword, return the value for the
element with the corresponding tag.
Parameters
----------
name : str
An element keyword or a class attribute name.
Returns
-------
value
If `name` matches a DICOM keyword, returns the corresponding
element's value. Otherwise returns the class attribute's
value (if present).
"""
tag = tag_for_keyword(name)
if tag is not None: # `name` isn't a DICOM element keyword
tag = Tag(tag)
if tag in self._dict: # DICOM DataElement not in the Dataset
return self[tag].value
# no tag or tag not contained in the dataset
if name == '_dict':
# special handling for contained dict, needed for pickle
return {}
# Try the base class attribute getter (fix for issue 332)
return object.__getattribute__(self, name)
@property
def _character_set(self) -> List[str]:
"""The character set used to encode text values."""
char_set = self.get(BaseTag(0x00080005), None)
if not char_set:
return self._parent_encoding
return convert_encodings(char_set.value)
@overload
def __getitem__(self, key: slice) -> "Dataset":
pass # pragma: no cover
@overload
def __getitem__(self, key: TagType) -> DataElement:
pass # pragma: no cover
def __getitem__(
self, key: Union[slice, TagType]
) -> Union["Dataset", DataElement]:
"""Operator for ``Dataset[key]`` request.
Any deferred data elements will be read in and an attempt will be made
to correct any elements with ambiguous VRs.
Examples
--------
Indexing using :class:`~pydicom.dataelem.DataElement` tag
>>> ds = Dataset()
>>> ds.SOPInstanceUID = '1.2.3'
>>> ds.PatientName = 'CITIZEN^Jan'
>>> ds.PatientID = '12345'
>>> ds[0x00100010].value
'CITIZEN^Jan'
Slicing using element tags; all group ``0x0010`` elements in
the dataset
>>> ds[0x00100000:0x00110000]
(0010, 0010) Patient's Name PN: 'CITIZEN^Jan'
(0010, 0020) Patient ID LO: '12345'
All group ``0x0002`` elements in the dataset
>>> ds[(0x0002, 0x0000):(0x0003, 0x0000)]
<BLANKLINE>
Parameters
----------
key
The DICOM (group, element) tag in any form accepted by
:func:`~pydicom.tag.Tag` such as ``[0x0010, 0x0010]``,
``(0x10, 0x10)``, ``0x00100010``, etc. May also be a :class:`slice`
made up of DICOM tags.
Returns
-------
dataelem.DataElement or Dataset
If a single DICOM element tag is used then returns the
corresponding :class:`~pydicom.dataelem.DataElement`.
If a :class:`slice` is used then returns a :class:`Dataset` object
containing the corresponding
:class:`DataElements<pydicom.dataelem.DataElement>`.
"""
# If passed a slice, return a Dataset containing the corresponding
# DataElements
if isinstance(key, slice):
return self._dataset_slice(key)
if isinstance(key, BaseTag):
tag = key
else:
try:
tag = Tag(key)
except Exception as exc:
raise KeyError(f"'{key}'") from exc
elem = self._dict[tag]
if isinstance(elem, DataElement):
if elem.VR == VR_.SQ and elem.value:
# let a sequence know its parent dataset, as sequence items
# may need parent dataset tags to resolve ambiguous tags
elem.value.parent = self
return elem
if isinstance(elem, RawDataElement):
# If a deferred read, then go get the value now
if elem.value is None and elem.length != 0:
from pydicom.filereader import read_deferred_data_element
elem = read_deferred_data_element(
self.fileobj_type,
self.filename,
self.timestamp,
elem
)
if tag != BaseTag(0x00080005):
character_set = self.read_encoding or self._character_set
else:
character_set = default_encoding
# Not converted from raw form read from file yet; do so now
self[tag] = DataElement_from_raw(elem, character_set, self)
# If the Element has an ambiguous VR, try to correct it
if self[tag].VR in AMBIGUOUS_VR:
from pydicom.filewriter import correct_ambiguous_vr_element
self[tag] = correct_ambiguous_vr_element(
self[tag], self, elem[6]
)
return cast(DataElement, self._dict.get(tag))
def private_block(
self, group: int, private_creator: str, create: bool = False
) -> PrivateBlock:
"""Return the block for the given tag `group` and `private_creator`.
.. versionadded:: 1.3
If `create` is ``True`` and the `private_creator` does not exist,
the private creator tag is added.
Notes
-----
We ignore the unrealistic case that no free block is available.
Parameters
----------
group : int
The group of the private tag to be found as a 32-bit :class:`int`.
Must be an odd number (e.g. a private group).
private_creator : str
The private creator string associated with the tag.
create : bool, optional
If ``True`` and `private_creator` does not exist, a new private
creator tag is added at the next free block. If ``False``
(the default) and `private_creator` does not exist,
:class:`KeyError` is raised instead.
Returns
-------
PrivateBlock
The existing or newly created private block.
Raises
------
ValueError
If `group` doesn't belong to a private tag or `private_creator`
is empty.
KeyError
If the private creator tag is not found in the given group and
the `create` parameter is ``False``.
"""
def new_block(element: int) -> PrivateBlock:
block = PrivateBlock(key, self, element)
self._private_blocks[key] = block
return block
key = (group, private_creator)
if key in self._private_blocks:
return self._private_blocks[key]
if not private_creator: