def create_new_uids(folder, dicom_tag, dry_run=True): """Search folder (and subfolders) for DICOM files and create new UIDs for specified DICOM tag Parameters ---------- folder : Folder to (recursively) search for DICOM objects dicom_tag : DICOM tag specifying which UID to create new UIDs for (format as "0x0020000D") dry_run : Dry run, (True)/False """ # find dcm files dcm_files = glob.glob(os.path.join(folder,"**","*.dcm"), recursive=True) dcm_dict = dict() # loop over dcm files for dcm_file in dcm_files: dcm = pydicom.read_file(dcm_file) # if dicom tag exists in current dcm file if int(dicom_tag, 16) in dcm: # record replaceed uid and set new uid uid = dcm[dicom_tag].value if uid not in dcm_dict: dcm_dict[uid] = pydicom.uid.generate_uid() dcm[dicom_tag] = DataElement(int(dicom_tag,16), "UI", dcm_dict[uid]) else: # add dicom tag with new uid uid = pydicom.uid.generate_uid() dcm[dicom_tag] = DataElement(int(dicom_tag,16), "UI", uid) if not dry_run: # write dcm file to disk pydicom.write_file(dcm_file, dcm, write_like_original=False) if dry_run: print("Uids to update") print(dcm_file)
def datasetFromJSON(data: dict): #return Dataset.from_json(data) ds = Dataset() for key in data.keys(): tag = Tag(key) try: if 'Value' in data[key].keys(): if data[key]['vr'] == 'SQ': tempds = [] for subdata in data[key]['Value']: tempds.append(datasetFromJSON(subdata)) seq = Sequence(tempds) ds[key] = DataElement(tag, data[key]['vr'], seq) elif type( data[key]['Value'][0] ) == dict and 'Alphabetic' in data[key]['Value'][0].keys(): ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value'][0]['Alphabetic']) else: if len(data[key]['Value']) > 1: ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value']) else: ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value'][0]) else: ds[key] = DataElement(tag, data[key]['vr'], '') except: from IPython import embed embed() return ds
def test_add_private_entries(self): """dicom_dictionary: add and use a dict of new dictionary entries""" new_dict_items = { 0x10011001: ('SH', '1', "Test One",), 0x10011002: ('DS', '3', "Test Two", '', 'TestTwo'), } add_private_dict_entries('ACME 3.1', new_dict_items) ds = Dataset() ds[0x10010010] = DataElement(0x10010010, 'LO', 'ACME 3.1') ds[0x10011001] = DataElement(0x10011001, 'SH', 'Test') ds[0x10011002] = DataElement(0x10011002, 'DS', '1\\2\\3') assert 'Test' == ds[0x10011001].value assert [1, 2, 3] == ds[0x10011002].value
def set_dicom_attribute(self, keyword, value): """Sets specified DICOM attribute according to the keyword value pair Parameters ---------- keyword : Name of DICOM tag to set value for value : Value to set """ if tag_for_keyword(keyword): if keyword in self.dataset: if dictionary_VR( tag_for_keyword(keyword)) == "SQ" and isinstance( value, list): value = generate_sequence(keyword, value) self.dataset[tag_for_keyword(keyword)].value = value else: if dictionary_VR( tag_for_keyword(keyword)) == "SQ" and isinstance( value, list): value = generate_sequence(keyword, value) de = DataElement(tag_for_keyword(keyword), dictionary_VR(tag_for_keyword(keyword)), value) self.dataset[tag_for_keyword(keyword)] = de else: print("Keyword", keyword, "is an unknown DICOM attribute")
def test_default_for_writing_validation_mode(self): ds = Dataset() ds.is_implicit_VR = True ds.is_little_endian = True ds.SpecificCharacterSet = "ISO_IR 192" ds.add(DataElement(0x00080050, "SH", "洪^吉洞=홍^길동")) with pytest.warns(UserWarning): ds.save_as(DicomBytesIO())
def test_write_invalid_values(self, future_setter): ds = Dataset() ds.is_implicit_VR = True ds.is_little_endian = True ds.SpecificCharacterSet = "ISO_IR 192" ds.add(DataElement(0x00080050, "SH", "洪^吉洞=홍^길동")) with pytest.raises(ValueError): ds.save_as(DicomBytesIO())
def add_patient_age(dcm: Dicom, age: Optional[int] = None) -> Optional[int]: r"""Attempts to add the specified patient age to a DICOM object. An age can be determined using the following order of precedence: 1. Reading an existing age tag in the DICOM metadata 2. Computing an age using the date of birth and capture date metadata 3. Adding the age passed as an ``age`` parameter. If ``age`` is given and ``age`` doesn't match the age determined via DICOM metadata, the age will not be updated. Args: dcm (:class:`pydicom.FileDataset`): The DICOM object to add an age to age (int, optional): The age to attempt to add to ``dcm``. Returns: The determined patient age in years, or ``None`` if no age could be determined. """ capture_date = get_date(dcm) if AGE_TAG in dcm: dcm_age = int(re.sub("[^0-9]", "", dcm[AGE_TAG].value)) elif DOB in dcm and capture_date is not None: dob = dcm[DOB].value try: dob = dt.datetime.strptime(dob, "%m%d%Y") delta = (capture_date - dob).days / 365.25 dcm_age = round(delta) except ValueError: dcm_age = age else: dcm_age = age if dcm_age is not None: if dcm_age != age and age is not None: print(f"Computed age {dcm_age} doesn't match given age {age}. " "Not updating patient age.") else: dcm[AGE_TAG] = DataElement(AGE_TAG, "AS", f"{dcm_age:03d}Y") elif age is not None: dcm[AGE_TAG] = DataElement(AGE_TAG, "AS", f"{age:03d}Y") return dcm_age
def test_from_dicom(self, dicom_object, val, expected): if val is not None: de = DataElement(Tag.PhotometricInterpretation, "CS", val) dicom_object[Tag.PhotometricInterpretation] = de else: del dicom_object[Tag.PhotometricInterpretation] pm = PhotometricInterpretation.from_dicom(dicom_object) assert pm == expected
def copy_required_dicom_attributes(self, dataset_to_copy_from, dataset_to_copy_to): """Copies required DICOM attributes for this module from one dataset to another Parameters ---------- dataset_to_copy_from : Dataset to copy DICOM attributes from dataset_to_copy_to : Dataset to copy DICOM attributes to """ for dicom_attribute in self.required_dicom_attributes: tag = tag_for_keyword(dicom_attribute) if dicom_attribute in dataset_to_copy_from: dataset_to_copy_to[tag] = dataset_to_copy_from[tag] elif dicom_attribute in dataset_to_copy_to: pass else: de = DataElement(tag, dictionary_VR(tag), "") dataset_to_copy_to[tag] = de
def copy_additional_dicom_attributes(self, dataset_to_copy_from, dataset_to_copy_to, additional_dicom_attributes): """Copies additional DICOM attributes for this module from one dataset to another Parameters ---------- dataset_to_copy_from : Dataset to copy DICOM attributes from dataset_to_copy_to : Dataset to copy DICOM attributes to additional_dicom_attributes : List of additional DICOM attributes to copy """ for dicom_attribute in additional_dicom_attributes: tag = tag_for_keyword(dicom_attribute) if dicom_attribute in dataset_to_copy_from: dataset_to_copy_to[tag] = dataset_to_copy_from[tag] else: de = DataElement(tag, dictionary_VR(tag), "") dataset_to_copy_to[tag] = de
def get_sequence_item(self, value: SQValueType) -> "Dataset": """Return a sequence item for the JSON dict `value`. Parameters ---------- value : dict or None The sequence item from the JSON entry. Returns ------- dataset_class The decoded dataset item. Raises ------ KeyError If the "vr" key is missing for a contained element """ from pydicom import DataElement from pydicom.dataelem import empty_value_for_VR ds = self.dataset_class() value = {} if value is None else value for key, val in value.items(): if 'vr' not in val: raise KeyError(f"Data element '{self.tag}' must have key 'vr'") vr = val['vr'] unique_value_keys = tuple(set(val.keys()) & set(JSON_VALUE_KEYS)) if not unique_value_keys: # data element with no value elem = DataElement(tag=int(key, 16), value=empty_value_for_VR(vr), VR=vr) else: value_key = unique_value_keys[0] elem = DataElement.from_json(self.dataset_class, key, vr, val[value_key], value_key, self.bulk_data_element_handler) ds.add(elem) return ds
def get_sequence_item(self, value): """Return a sequence item for the JSON dict `value`. Parameters ---------- value : dict or None The sequence item from the JSON entry. Returns ------- dataset_class The decoded dataset item. Raises ------ KeyError If the "vr" key is missing for a contained element """ ds = self.dataset_class() if value: for key, val in value.items(): if 'vr' not in val: fmt = 'Data element "{}" must have key "vr".' raise KeyError(fmt.format(self.tag)) vr = val['vr'] unique_value_keys = tuple( set(val.keys()) & set(JSON_VALUE_KEYS)) from pydicom import DataElement from pydicom.dataelem import empty_value_for_VR if not unique_value_keys: # data element with no value elem = DataElement(tag=int(key, 16), value=empty_value_for_VR(vr), VR=vr) else: value_key = unique_value_keys[0] elem = DataElement.from_json(self.dataset_class, key, vr, val[value_key], value_key) ds.add(elem) return ds
def update_and_insert_additional_DICOM_attributes_in_ds( ds, keyword_and_value_dict): # For every keyword for keyword in keyword_and_value_dict: # Get corresponding tag and value tag = tag_for_keyword(keyword) # Verify that it is a valid keyword if tag is None: print("Unknown DICOM attribute:", keyword) continue # Get corresponding value value = None if dictionary_VR(tag) == "SQ": value = generate_sequence(keyword, keyword_and_value_dict[keyword]) else: value = keyword_and_value_dict[keyword] # If keyword already set, update its value, otherwise, create a new data element if keyword in ds: ds[tag].value = value else: ds[tag] = DataElement(tag, dictionary_VR(tag), value) # Return edited dataset return ds
class TestReadDicomImage: def test_shape(self, dicom_object): array = read_dicom_image(dicom_object) assert isinstance(array, np.ndarray) assert array.ndim == 3, "dims C x H x W" assert array.shape[0] == 1, "channel dim size == 1" assert array.shape[1] == 128, "height dim size == 128" assert array.shape[2] == 128, "width dim size == 128" def test_array_dtype(self, dicom_object): array = read_dicom_image(dicom_object) assert isinstance(array, np.ndarray) assert array.dtype == np.int16 def test_min_max_values(self, dicom_object): array = read_dicom_image(dicom_object) assert isinstance(array, np.ndarray) assert array.min() == 128, "min pixel value 128" assert array.max() == 2191, "max pixel value 2191" def test_invalid_TransferSyntaxUID_loose_interpretation(self, dicom_object): dicom_object.file_meta.TransferSyntaxUID = "1.2.840.10008.1.2.4.90" # Assign random invalid TransferSyntaxUID array = read_dicom_image(dicom_object) assert isinstance(array, np.ndarray) assert array.min() == 128, "min pixel value 128" assert array.max() == 2191, "max pixel value 2191" def test_invalid_TransferSyntaxUID_exception(self, dicom_object): dicom_object.file_meta.TransferSyntaxUID = "1.2.840.10008.1.2.4.90" # Assign random invalid TransferSyntaxUID with pytest.raises(ValueError) as e: read_dicom_image(dicom_object, strict_interp=True) assert "does not appear to be correct" in str(e), "The expected exception message was not returned." def test_invalid_PixelData(self, dicom_object): dicom_object.PixelData = b"" with pytest.raises(ValueError) as e: read_dicom_image(dicom_object) expected_msg = "Unable to parse the pixel array after trying all possible TransferSyntaxUIDs." assert expected_msg in str(e), "The expected exception message was not returned." @pytest.mark.parametrize("shape_override", [None, (32, 32), (32, 32, 32)]) def test_stop_before_pixels(self, dicom_object, shape_override): np.random.seed(42) array1 = read_dicom_image(dicom_object) array2 = read_dicom_image(dicom_object, stop_before_pixels=True, shape=shape_override) assert isinstance(array1, np.ndarray) assert isinstance(array2, np.ndarray) if shape_override is None: assert not (array1 == array2).all() assert array1.shape == array2.shape else: assert array2.shape == (1,) + shape_override @pytest.mark.parametrize( "handler", [ KeepVolume(), SliceAtLocation(4), UniformSample(4, method="count"), ], ) def test_volume_handling(self, dicom_object_3d, handler, mocker, transfer_syntax): spy = mocker.spy(handler, "__call__") F = 8 dcm = dicom_object_3d(num_frames=F, syntax=transfer_syntax) array1 = read_dicom_image(dcm, volume_handler=spy, strict_interp=True) spy.assert_called_once() assert spy.mock_calls[0].args[0] == dcm, "handler should be called with DICOM object" assert array1.ndim < 4 or array1.shape[1] != 1, "3D dim should be squeezed when D=1" @pytest.mark.parametrize( "apply,center,width", [ pytest.param(True, None, None), pytest.param(True, DataElement(WINDOW_CENTER, "DS", 512), None), pytest.param(True, None, DataElement(WINDOW_WIDTH, "DS", 512)), pytest.param(True, DataElement(WINDOW_CENTER, "DS", 512), DataElement(WINDOW_WIDTH, "DS", 256)), pytest.param(True, DataElement(WINDOW_CENTER, "DS", 256), DataElement(WINDOW_WIDTH, "DS", 512)), pytest.param( True, DataElement(WINDOW_CENTER, "DS", [100, 200, 300]), DataElement(WINDOW_WIDTH, "DS", [200, 300, 400]), ), pytest.param(False, DataElement(WINDOW_CENTER, "DS", 512), DataElement(WINDOW_WIDTH, "DS", 256)), pytest.param(False, DataElement(WINDOW_CENTER, "DS", 256), DataElement(WINDOW_WIDTH, "DS", 512)), pytest.param( False, DataElement(WINDOW_CENTER, "DS", [100, 200, 300]), DataElement(WINDOW_WIDTH, "DS", [200, 300, 400]), ), ], ) def test_apply_window(self, dicom_object, apply, center, width): # set metadata if center is not None: dicom_object[WINDOW_CENTER] = center if width is not None: dicom_object[WINDOW_WIDTH] = width window = Window.from_dicom(dicom_object) pixels = read_dicom_image(dicom_object, apply_window=False) window_pixels = read_dicom_image(dicom_object, apply_window=apply) if center is not None and width is not None and apply: assert (window_pixels >= 0).all() assert (window_pixels <= window.width).all() assert (window_pixels[pixels <= window.lower_bound] == 0).all() assert (window_pixels[pixels >= window.upper_bound] == window.upper_bound - window.lower_bound).all() elif apply: pixels = dicom_object.pixel_array assert window_pixels.min() == 0 # tolerance of 1 here for rounding errors assert window_pixels.max() - (pixels.max() - pixels.min()) <= 1 else: assert (window_pixels == pixels).all() window = Window.from_dicom(dicom_object)
def add_density(dcm: Dicom, density: float) -> str: if DENSITY not in dcm.keys(): dcm[DENSITY] = DataElement(DENSITY, "FL", density) return str(density) else: return str(dcm[DENSITY].value)
class TestWindow: @pytest.mark.parametrize( "center,width", [ pytest.param(None, None), pytest.param(DataElement(WINDOW_CENTER, "DS", 512), None), pytest.param(None, DataElement(WINDOW_WIDTH, "DS", 512)), pytest.param(DataElement(WINDOW_CENTER, "DS", 512), DataElement(WINDOW_WIDTH, "DS", 256)), pytest.param(DataElement(WINDOW_CENTER, "DS", 256), DataElement(WINDOW_WIDTH, "DS", 512)), pytest.param( DataElement(WINDOW_CENTER, "DS", [100, 200, 300]), DataElement(WINDOW_WIDTH, "DS", [200, 300, 400]) ), ], ) def test_from_dicom(self, dicom_object, center, width): if center is not None: dicom_object[WINDOW_CENTER] = center if width is not None: dicom_object[WINDOW_WIDTH] = width window = Window.from_dicom(dicom_object) if center is not None and width is not None: assert window.center == center.value if not isinstance(center.value, Iterable) else center[0] assert window.width == width.value if not isinstance(width.value, Iterable) else width[0] else: pixels = dicom_object.pixel_array assert window.center == (pixels.max() - pixels.min()) // 2 + pixels.min() assert window.width == pixels.max() - pixels.min() def test_repr(self): window = Window(100, 300) print(window) @pytest.mark.parametrize( "center,width,expected", [ pytest.param(200, 100, 150), pytest.param(100, 200, 0), pytest.param(100, 300, 0), ], ) def test_lower_bound(self, center, width, expected): window = Window(center, width) assert window.lower_bound == expected @pytest.mark.parametrize( "center,width,expected", [ pytest.param(200, 100, 250), pytest.param(100, 200, 200), pytest.param(100, 300, 250), ], ) def test_upper_bound(self, center, width, expected): window = Window(center, width) assert window.upper_bound == expected @pytest.mark.parametrize( "center,width", [ pytest.param(200, 100), pytest.param(100, 200), pytest.param(100, 300), ], ) def test_apply(self, center, width): np.random.seed(42) pixels = np.random.random(100).reshape(10, 10) pixels = (pixels * 1024).astype(np.uint16) window = Window(center, width) window_pixels = window.apply(pixels) assert (window_pixels >= 0).all() assert (window_pixels <= window.width).all() assert (window_pixels[pixels <= window.lower_bound] == 0).all() assert (window_pixels[pixels >= window.upper_bound] == window.upper_bound - window.lower_bound).all()
def create_attribute_list(attr): ds = Dataset() for item in attr: elem = DataElement(item, dictionary_VR(item), "UIH") ds.add(elem) return ds