def testExtend(self): """MultiValue: Extending a list converts all to required type""" multival = MultiValue(IS, [1, 5, 10]) multival.extend(['7', 42]) assert isinstance(multival[-2], IS) assert isinstance(multival[-1], IS) assert 7 == multival[-2]
def testAppend(self): """MultiValue: Append of item converts it to required type...""" multival = MultiValue(IS, [1, 5, 10]) multival.append('5') self.assertTrue(isinstance(multival[-1], IS)) self.assertEqual(multival[-1], 5, "Item set by append is not correct value")
def test_multivalue_DA(self): """Write DA/DT/TM data elements..........""" multi_DA_expected = (date(1961, 8, 4), date(1963, 11, 22)) DA_expected = date(1961, 8, 4) tzinfo = tzoffset('-0600', -21600) multi_DT_expected = (datetime(1961, 8, 4), datetime(1963, 11, 22, 12, 30, 0, 0, tzoffset('-0600', -21600))) multi_TM_expected = (time(1, 23, 45), time(11, 11, 11)) TM_expected = time(11, 11, 11, 1) ds = read_file(datetime_name) # Add date/time data elements ds.CalibrationDate = MultiValue(DA, multi_DA_expected) ds.DateOfLastCalibration = DA(DA_expected) ds.ReferencedDateTime = MultiValue(DT, multi_DT_expected) ds.CalibrationTime = MultiValue(TM, multi_TM_expected) ds.TimeOfLastCalibration = TM(TM_expected) ds.save_as(datetime_out) # Now read it back in and check the values are as expected ds = read_file(datetime_out) self.assertSequenceEqual( multi_DA_expected, ds.CalibrationDate, "Multiple dates not written correctly (VR=DA)") self.assertEqual(DA_expected, ds.DateOfLastCalibration, "Date not written correctly (VR=DA)") self.assertSequenceEqual( multi_DT_expected, ds.ReferencedDateTime, "Multiple datetimes not written correctly (VR=DT)") self.assertSequenceEqual( multi_TM_expected, ds.CalibrationTime, "Multiple times not written correctly (VR=TM)") self.assertEqual(TM_expected, ds.TimeOfLastCalibration, "Time not written correctly (VR=DA)") if os.path.exists(datetime_out): os.remove(datetime_out) # get rid of the file
def testExtend(self): """MultiValue: Extending a list converts all to required type""" multival = MultiValue(IS, [1, 5, 10]) multival.extend(['7', 42]) self.assertTrue(isinstance(multival[-2], IS)) self.assertTrue(isinstance(multival[-1], IS)) self.assertEqual(multival[-2], 7, "Item set by extend not correct value")
def testIssue236DeepCopy(self): """MultiValue: deepcopy of MultiValue does not generate an error""" multival = MultiValue(IS, range(7)) deepcopy(multival) multival = MultiValue(DS, range(7)) deepcopy(multival) multival = MultiValue(DSfloat, range(7)) deepcopy(multival)
def test_str_rep(self): """MultiValue: test print output""" multival = MultiValue(IS, []) assert '' == str(multival) multival = MultiValue(str, [1, 2, 3]) assert "['1', '2', '3']" == str(multival) multival = MultiValue(int, [1, 2, 3]) assert '[1, 2, 3]' == str(multival) multival = MultiValue(float, [1.1, 2.2, 3.3]) assert '[1.1, 2.2, 3.3]' == str(multival)
def generate_dicom_scans(dst, num_scans=10, intercept=0, slope=1): spacing = (0.4 + 0.4 * np.random.rand(num_scans, 3) + np.array([1 + 0.5 * np.random.rand(), 0, 0])) origin = np.random.randint(-200, 200, (num_scans, 3)) for i in range(num_scans): num_slices = np.random.randint(128, 169) scan_id = np.random.randint(2**16) scan_data = np.random.randint(0, 256, (num_slices, 128, 128)) folder = os.path.join(dst, hex(scan_id).replace('x', '').upper().zfill(8)) if not os.path.exists(folder): os.makedirs(folder) for k in range(num_slices): slice_name = (hex(scan_id + k).replace('x', '').upper().zfill(8)) filename = os.path.join(folder, slice_name) pixel_array = (scan_data[k, ...] - intercept) / slope locZ = float(origin[i, 0] + spacing[i, 0] * k) locY, locX = float(origin[i, 1]), float(origin[i, 2]) file_meta = DicomDataset() file_meta.MediaStorageSOPClassUID = "Secondary Capture Image Storage" file_meta.MediaStorateSOPInstanceUID = (hex(scan_id).replace( 'x', '').upper().zfill(8)) file_meta.ImplementationClassUID = slice_name dataset = DicomFileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) dataset.PixelData = pixel_array.astype(np.uint16).tostring() dataset.RescaleSlope = slope dataset.RescaleIntercept = intercept dataset.ImagePositionPatient = MultiValue( type_constructor=float, iterable=[locZ, locY, locX]) dataset.PixelSpacing = MultiValue( type_constructor=float, iterable=[float(spacing[i, 1]), float(spacing[i, 2])]) dataset.SliceThickness = float(spacing[i, 0]) dataset.Modality = 'WSD' dataset.Columns = pixel_array.shape[0] dataset.Rows = pixel_array.shape[1] dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian dataset.PixelRepresentation = 1 dataset.BitsAllocated = 16 dataset.BitsStored = 16 dataset.SamplesPerPixel = 1 write_file(filename, dataset)
def testEmptyElements(self): """MultiValue: Empty number string elements are not converted...""" multival = MultiValue(DSfloat, ['1.0', '']) self.assertEqual(1.0, multival[0]) self.assertEqual('', multival[1]) multival = MultiValue(IS, ['1', '']) self.assertEqual(1, multival[0]) self.assertEqual('', multival[1]) multival = MultiValue(DSdecimal, ['1', '']) self.assertEqual(1, multival[0]) self.assertEqual('', multival[1])
def testEmptyElements(self): """MultiValue: Empty number string elements are not converted...""" multival = MultiValue(DSfloat, ['1.0', '']) assert 1.0 == multival[0] assert '' == multival[1] multival = MultiValue(IS, ['1', '']) assert 1 == multival[0] assert '' == multival[1] multival = MultiValue(DSdecimal, ['1', '']) assert 1 == multival[0] assert '' == multival[1] multival = MultiValue(IS, []) assert not multival assert 0 == len(multival)
def test_empty_binary_values(self): """Test that assigning an empty value behaves as expected for non-text VRs.""" def check_empty_binary_element(value): setattr(ds, tag_name, value) elem = ds[tag_name] assert bool(elem.value) is False assert 0 == elem.VM assert elem.value == value fp = DicomBytesIO() filewriter.write_dataset(fp, ds) ds_read = dcmread(fp, force=True) assert ds_read[tag_name].value is None non_text_vrs = { 'AT': 'OffendingElement', 'DS': 'PatientWeight', 'IS': 'BeamNumber', 'SL': 'RationalNumeratorValue', 'SS': 'SelectorSSValue', 'UL': 'SimpleFrameList', 'US': 'SourceAcquisitionBeamNumber', 'FD': 'RealWorldValueLUTData', 'FL': 'VectorAccuracy', 'OB': 'FillPattern', 'OD': 'DoubleFloatPixelData', 'OF': 'UValueData', 'OL': 'TrackPointIndexList', 'OW': 'TrianglePointIndexList', 'UN': 'SelectorUNValue', } ds = Dataset() ds.is_little_endian = True # set value to new element for tag_name in non_text_vrs.values(): check_empty_binary_element(None) del ds[tag_name] check_empty_binary_element([]) del ds[tag_name] check_empty_binary_element(MultiValue(int, [])) del ds[tag_name] # set value to existing element for tag_name in non_text_vrs.values(): check_empty_binary_element(None) check_empty_binary_element([]) check_empty_binary_element(MultiValue(int, [])) check_empty_binary_element(None)
def convert_ATvalue( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[BaseTag, MutableSequence[BaseTag]]: """Return a decoded 'AT' value. Parameters ---------- byte_string : bytes The encoded 'AT' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- BaseTag or MultiValue of BaseTag The decoded value(s). """ length = len(byte_string) if length == 4: return convert_tag(byte_string, is_little_endian) # length > 4 if length % 4 != 0: logger.warning("Expected length to be multiple of 4 for VR 'AT', " f"got length {length}") return MultiValue(Tag, [ convert_tag(byte_string, is_little_endian, offset=x) for x in range(0, length, 4) ])
def convert_PN(byte_string, is_little_endian, struct_format=None, encoding=None): """Read and return string(s) as PersonName instance(s)""" def get_valtype(x): if not in_py2: if encoding: return PersonName(x, encoding).decode() return PersonName(x).decode() return PersonName(x) # XXX - We have to replicate MultiString functionality # here because we can't decode easily here since that # is performed in PersonNameUnicode ends_with1 = byte_string.endswith(b' ') ends_with2 = byte_string.endswith(b'\x00') if byte_string and (ends_with1 or ends_with2): byte_string = byte_string[:-1] splitup = byte_string.split(b"\\") if len(splitup) == 1: return get_valtype(splitup[0]) else: return MultiValue(get_valtype, splitup)
def testSetIndex(self): """MultiValue: Setting list item converts it to required type""" multival = MultiValue(IS, [1, 5, 10]) multival[1] = '7' self.assertTrue(isinstance(multival[1], IS)) self.assertEqual(multival[1], 7, "Item set by index is not correct value")
def testDeleteIndex(self): """MultiValue: Deleting item at index behaves as expected...""" multival = MultiValue(IS, [1, 5, 10]) del multival[1] self.assertEqual(2, len(multival)) self.assertEqual(multival[0], 1) self.assertEqual(multival[1], 10)
def MultiString(val, valtype=str): """Split a bytestring by delimiters if there are any Parameters ---------- val : bytes or str DICOM byte string to split up. valtype Default :class:`str`, but can be e.g. :class:`~pydicom.uid.UID` to overwrite to a specific type. Returns ------- valtype or list of valtype The split value as `valtype` or a :class:`list` of `valtype`. """ # Remove trailing blank used to pad to even length # 2005.05.25: also check for trailing 0, error made # in PET files we are converting while val and (val.endswith(' ') or val.endswith('\x00')): val = val[:-1] splitup = val.split("\\") if len(splitup) == 1: val = splitup[0] return valtype(val) if val else val else: return MultiValue(valtype, splitup)
def convert_DT_string( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[str, DT, SequenceType[Union[str, DT]]]: """Return a decoded 'DT' value. Parameters ---------- byte_string : bytes The encoded 'DT' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- str or MultiValue of str or valuerep.DT or MultiValue of DT If :attr:`~pydicom.config.datetime_conversion` is ``True`` then returns :class:`~pydicom.valuerep.DT` or a :class:`list` of ``DT``, otherwise returns :class:`str` or ``list`` of ``str``. """ if config.datetime_conversion: splitup = byte_string.decode(default_encoding).split("\\") if len(splitup) == 1: return _DT_from_str(splitup[0]) return MultiValue(_DT_from_str, splitup) return convert_string(byte_string, is_little_endian, struct_format)
def convert_PN(byte_string, encodings=None): """Return a decoded 'PN' value. Parameters ---------- byte_string : bytes or str The encoded 'IS' element value. encodings : list of str, optional A list of the character encoding schemes used to encode the 'PN' value. Returns ------- valuerep.PersonName or list of PersonName The decoded 'PN' value(s). """ def get_valtype(x): return PersonName(x, encodings).decode() if byte_string.endswith((b' ', b'\x00')): byte_string = byte_string[:-1] splitup = byte_string.split(b"\\") if len(splitup) == 1: return get_valtype(splitup[0]) else: return MultiValue(get_valtype, splitup)
def convert_text(byte_string, encodings=None): """Return a decoded text VR value, ignoring backslashes. Text VRs are 'SH', 'LO' and 'UC'. Parameters ---------- byte_string : bytes or str The encoded text VR element value. encodings : list of str, optional A list of the character encoding schemes used to encode the value. Returns ------- unicode or list of unicode The decoded value(s) if in Python 2. str or list of str The decoded value(s) if in Python 3. """ values = byte_string.split(b'\\') values = [convert_single_string(value, encodings) for value in values] if len(values) == 1: return values[0] else: return MultiValue(compat.text_type, values)
def convert_PN( byte_string: bytes, encodings: Optional[List[str]] = None ) -> Union[PersonName, MutableSequence[PersonName]]: """Return a decoded 'PN' value. Parameters ---------- byte_string : bytes The encoded 'PN' element value. encodings : list of str, optional A list of the character encoding schemes used to encode the 'PN' value. Returns ------- valuerep.PersonName or MultiValue of PersonName The decoded 'PN' value(s). """ def get_valtype(x: bytes) -> PersonName: return PersonName(x, encodings).decode() b_split = byte_string.rstrip(b'\x00 ').split(b'\\') if len(b_split) == 1: return get_valtype(b_split[0]) return MultiValue(get_valtype, b_split)
def testSlice(self): """MultiValue: Setting slice converts items to required type.""" multival = MultiValue(IS, range(7)) multival[2:7:2] = [4, 16, 36] for val in multival: assert isinstance(val, IS) assert 16 == multival[4]
def convert_text( byte_string: bytes, encodings: Optional[List[str]] = None ) -> Union[str, MutableSequence[str]]: """Return a decoded text VR value, ignoring backslashes. Text VRs are 'SH', 'LO' and 'UC'. Parameters ---------- byte_string : bytes The encoded text VR element value. encodings : list of str, optional A list of the character encoding schemes used to encode the value. Returns ------- str or list of str The decoded value(s). """ values = byte_string.split(b'\\') as_strings = [convert_single_string(value, encodings) for value in values] if len(as_strings) == 1: return as_strings[0] return MultiValue(str, as_strings)
def convert_ATvalue(byte_string, is_little_endian, struct_format=None): """Return a decoded 'AT' value. Parameters ---------- byte_string : bytes or str The encoded 'AT' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- BaseTag or list of BaseTag The decoded value(s). """ length = len(byte_string) if length == 4: return convert_tag(byte_string, is_little_endian) # length > 4 if length % 4 != 0: logger.warn( "Expected length to be multiple of 4 for VR 'AT', " "got length %d", length) return MultiValue(Tag, [ convert_tag(byte_string, is_little_endian, offset=x) for x in range(0, length, 4) ])
def convert_TM_string( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[str, TM, MutableSequence[str], MutableSequence[TM]]: """Return a decoded 'TM' value. Parameters ---------- byte_string : bytes The encoded 'TM' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- str or list of str or valuerep.TM or list of valuerep.TM If :attr:`~pydicom.config.datetime_conversion` is ``True`` then returns either :class:`~pydicom.valuerep.TM` or a :class:`list` of ``TM``, otherwise returns :class:`str` or ``list`` of ``str``. """ if config.datetime_conversion: splitup = byte_string.decode(default_encoding).split("\\") if len(splitup) == 1: return _TM_from_str(splitup[0]) return MultiValue(_TM_from_str, splitup) return convert_string(byte_string, is_little_endian)
def convert_text(byte_string: bytes, encodings: Optional[List[str]] = None, vr: str = None) -> Union[str, MutableSequence[str]]: """Return a decoded text VR value. Text VRs are 'SH', 'LO' and 'UC'. Parameters ---------- byte_string : bytes The encoded text VR element value. encodings : list of str, optional A list of the character encoding schemes used to encode the value. vr : str The value representation of the element. Needed for validation. Returns ------- str or list of str The decoded value(s). """ values = byte_string.split(b'\\') as_strings = [ convert_single_string(value, encodings, vr) for value in values ] if len(as_strings) == 1: return as_strings[0] return MultiValue(str, as_strings, validation_mode=config.settings.reading_validation_mode)
def convert_PN(byte_string, is_little_endian, struct_format=None, encoding=None): """Read and return string(s) as PersonName instance(s)""" # XXX - We have to replicate MultiString functionality here because we can't decode # easily here since that is performed in PersonNameUnicode if byte_string and (byte_string.endswith(b' ') or byte_string.endswith(b'\x00')): byte_string = byte_string[:-1] splitup = byte_string.split(b"\\") if encoding and not in_py2: args = (encoding,) else: args = () # We would like to return string literals if not in_py2: valtype = lambda x: PersonName(x, *args).decode() else: valtype = lambda x: PersonName(x, *args) if len(splitup) == 1: return valtype(splitup[0]) else: return MultiValue(valtype, splitup)
def convert_PN(byte_string, encodings=None): """Return a decoded 'PN' value. Parameters ---------- byte_string : bytes or str The encoded 'IS' element value. encodings : list of str, optional A list of the character encoding schemes used to encode the 'PN' value. Returns ------- valuerep.PersonName3 or list of PersonName3 The decoded 'PN' value(s) if using Python 3. valuerep.PersonNameUnicode or list of PersonNameUnicode The decoded 'PN' value(s) if using Python 2. """ def get_valtype(x): if not in_py2: return PersonName(x, encodings).decode() return PersonName(x, encodings) # XXX - We have to replicate MultiString functionality # here because we can't decode easily here since that # is performed in PersonNameUnicode if byte_string.endswith((b' ', b'\x00')): byte_string = byte_string[:-1] splitup = byte_string.split(b"\\") if len(splitup) == 1: return get_valtype(splitup[0]) else: return MultiValue(get_valtype, splitup)
def MultiString( val: str, valtype: Optional[Union[Type[_T], Callable[[object], _T]]] = None ) -> Union[_T, SequenceType[_T]]: """Split a bytestring by delimiters if there are any Parameters ---------- val : str The string to split up. valtype : type or callable, optional Default :class:`str`, but can be e.g. :class:`~pydicom.uid.UID` to overwrite to a specific type. Returns ------- valtype or MultiValue of valtype The split value as `valtype` or a :class:`list` of `valtype`. """ valtype = str if valtype is None else valtype # Remove trailing blank used to pad to even length # 2005.05.25: also check for trailing 0, error made # in PET files we are converting while val and val.endswith((' ', '\x00')): val = val[:-1] splitup = val.split("\\") if len(splitup) == 1: val = splitup[0] return valtype(val) if val else val return MultiValue(valtype, splitup)
def convert_TM_string(byte_string, is_little_endian, struct_format=None): """Return a decoded 'TM' value. Parameters ---------- byte_string : bytes or str The encoded 'TM' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- str or list of str or valuerep.TM or list of valuerep.TM If :attr:`~pydicom.config.datetime_conversion` is ``True`` then returns either :class:`~pydicom.valuerep.TM` or a :class:`list` of ``TM``, otherwise returns :class:`str` or ``list`` of ``str``. """ if config.datetime_conversion: if not in_py2: byte_string = byte_string.decode(default_encoding) splitup = byte_string.split("\\") if len(splitup) == 1: return _TM_from_byte_string(splitup[0]) else: return MultiValue(_TM_from_byte_string, splitup) else: return convert_string(byte_string, is_little_endian, struct_format)
def convert_AE_string( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[str, MutableSequence[str]]: """Return a decoded 'AE' value. Elements with VR of 'AE' have non-significant leading and trailing spaces. Parameters ---------- byte_string : bytes The encoded 'AE' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- str The decoded 'AE' value without non-significant spaces. """ # Differs from convert_string because leading spaces are non-significant values = byte_string.decode(default_encoding).split('\\') values = [s.strip() for s in values] if len(values) == 1: return values[0] return MultiValue(str, values)
def testDeleteIndex(self): """MultiValue: Deleting item at index behaves as expected...""" multival = MultiValue(IS, [1, 5, 10]) del multival[1] assert 2 == len(multival) assert 1 == multival[0] assert 10 == multival[1]
def testSorting(self): """MultiValue: allow inline sort.""" multival = MultiValue(DS, [12, 33, 5, 7, 1]) multival.sort() self.assertEqual([1, 5, 7, 12, 33], multival) multival.sort(reverse=True) self.assertEqual([33, 12, 7, 5, 1], multival) multival.sort(key=str) self.assertEqual([1, 12, 33, 5, 7], multival)