# Copyright 2008-2018 pydicom authors. See LICENSE file for details.
# -*- coding: utf-8 -*-
"""Unit tests for the pydicom.filereader module."""

import gzip
import io
from io import BytesIO
import os
import shutil
from pathlib import Path
from struct import unpack
import sys
import tempfile

import pytest

import pydicom.config
from pydicom import config
from pydicom.dataset import Dataset, FileDataset, FileMetaDataset
from pydicom.data import get_testdata_file
from pydicom.datadict import add_dict_entries
from pydicom.filereader import (
    dcmread, read_dataset, read_dicomdir, data_element_generator
)
from pydicom.dataelem import DataElement, DataElement_from_raw
from pydicom.errors import InvalidDicomError
from pydicom.filebase import DicomBytesIO
from pydicom.multival import MultiValue
from pydicom.sequence import Sequence
from pydicom.tag import Tag, TupleTag
from pydicom.uid import ImplicitVRLittleEndian
import pydicom.valuerep
from pydicom import values


from pydicom.pixel_data_handlers import gdcm_handler

have_gdcm_handler = gdcm_handler.is_available()

have_numpy = pydicom.config.have_numpy
if have_numpy:
    import numpy  # NOQA

try:
    import jpeg_ls
except ImportError:
    jpeg_ls = None

try:
    from PIL import Image as PILImg
except ImportError:
    # If that failed, try the alternate import syntax for PIL.
    try:
        import Image as PILImg
    except ImportError:
        # Neither worked, so it's likely not installed.
        PILImg = None

have_jpeg_ls = jpeg_ls is not None
have_pillow = PILImg is not None

empty_number_tags_name = get_testdata_file(
    "reportsi_with_empty_number_tags.dcm"
)
rtplan_name = get_testdata_file("rtplan.dcm")
rtdose_name = get_testdata_file("rtdose.dcm")
ct_name = get_testdata_file("CT_small.dcm")
mr_name = get_testdata_file("MR_small.dcm")
truncated_mr_name = get_testdata_file("MR_truncated.dcm")
jpeg2000_name = get_testdata_file("JPEG2000.dcm")
jpeg2000_embedded_sequence_delimeter_name = get_testdata_file(
    "JPEG2000-embedded-sequence-delimiter.dcm"
)
jpeg2000_lossless_name = get_testdata_file("MR_small_jp2klossless.dcm")
jpeg_ls_lossless_name = get_testdata_file("MR_small_jpeg_ls_lossless.dcm")
jpeg_lossy_name = get_testdata_file("JPEG-lossy.dcm")
jpeg_lossless_name = get_testdata_file("JPEG-LL.dcm")
deflate_name = get_testdata_file("image_dfl.dcm")
rtstruct_name = get_testdata_file("rtstruct.dcm")
priv_SQ_name = get_testdata_file("priv_SQ.dcm")
nested_priv_SQ_name = get_testdata_file("nested_priv_SQ.dcm")
meta_missing_tsyntax_name = get_testdata_file("meta_missing_tsyntax.dcm")
no_meta_group_length = get_testdata_file("no_meta_group_length.dcm")
gzip_name = get_testdata_file("zipMR.gz")
color_px_name = get_testdata_file("color-px.dcm")
color_pl_name = get_testdata_file("color-pl.dcm")
explicit_vr_le_no_meta = get_testdata_file("ExplVR_LitEndNoMeta.dcm")
explicit_vr_be_no_meta = get_testdata_file("ExplVR_BigEndNoMeta.dcm")
emri_name = get_testdata_file("emri_small.dcm")
emri_big_endian_name = get_testdata_file("emri_small_big_endian.dcm")
emri_jpeg_ls_lossless = get_testdata_file("emri_small_jpeg_ls_lossless.dcm")
emri_jpeg_2k_lossless = get_testdata_file("emri_small_jpeg_2k_lossless.dcm")
emri_jpeg_2k_lossless_too_short = get_testdata_file(
    "emri_small_jpeg_2k_lossless_too_short.dcm"
)
color_3d_jpeg_baseline = get_testdata_file("color3d_jpeg_baseline.dcm")
dir_name = os.path.dirname(sys.argv[0])
save_dir = os.getcwd()


class TestReader:
    def test_empty_numbers_tag(self):
        """Test that an empty tag with a number VR (FL, UL, SL, US,
        SS, FL, FD, OF) reads as ``None``."""
        empty_number_tags_ds = dcmread(empty_number_tags_name)
        assert empty_number_tags_ds.ExaminedBodyThickness is None
        assert empty_number_tags_ds.SimpleFrameList is None
        assert empty_number_tags_ds.ReferencePixelX0 is None
        assert empty_number_tags_ds.PhysicalUnitsXDirection is None
        assert empty_number_tags_ds.TagAngleSecondAxis is None
        assert empty_number_tags_ds.TagSpacingSecondDimension is None
        assert empty_number_tags_ds.VectorGridData is None

    def test_UTF8_filename(self):
        utf8_filename = os.path.join(tempfile.gettempdir(), "ДИКОМ.dcm")
        shutil.copyfile(rtdose_name, utf8_filename)
        ds = dcmread(utf8_filename)
        os.remove(utf8_filename)
        assert ds is not None

    def test_pathlib_path_filename(self):
        """Check that file can be read using pathlib.Path"""
        dcmread(Path(priv_SQ_name))

    def test_RTPlan(self):
        """Returns correct values for sample data elements in test
        RT Plan file.
        """
        plan = dcmread(rtplan_name)
        beam = plan.BeamSequence[0]
        # if not two controlpoints, then this would raise exception
        cp0, cp1 = beam.ControlPointSequence

        assert "unit001" == beam.TreatmentMachineName
        assert beam[0x300A, 0x00B2].value == beam.TreatmentMachineName

        got = cp1.ReferencedDoseReferenceSequence[
            0
        ].CumulativeDoseReferenceCoefficient
        DS = pydicom.valuerep.DS
        expected = DS("0.9990268")
        assert expected == got
        got = cp0.BeamLimitingDevicePositionSequence[0].LeafJawPositions
        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([DS("-100"), DS("100.0")])
            assert numpy.allclose(got, expected)
        else:
            expected = [DS("-100"), DS("100.0")]
            assert got == expected

    def test_RTDose(self):
        """Returns correct values for sample data elements in test
        RT Dose file"""
        dose = dcmread(rtdose_name)
        assert Tag((0x3004, 0x000C)) == dose.FrameIncrementPointer
        assert dose[0x28, 9].value == dose.FrameIncrementPointer

        # try a value that is nested the deepest
        # (so deep I break it into two steps!)
        fract = dose.ReferencedRTPlanSequence[
            0
        ].ReferencedFractionGroupSequence[0]
        assert 1 == fract.ReferencedBeamSequence[0].ReferencedBeamNumber

    def test_CT(self):
        """Returns correct values for sample data elements in test CT file."""
        ct = dcmread(ct_name)
        assert "1.3.6.1.4.1.5962.2" == ct.file_meta.ImplementationClassUID
        value = ct.file_meta[0x2, 0x12].value
        assert value == ct.file_meta.ImplementationClassUID

        # (0020, 0032) Image Position (Patient)
        # [-158.13580300000001, -179.035797, -75.699996999999996]
        got = ct.ImagePositionPatient
        DS = pydicom.valuerep.DS
        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([-158.135803, -179.035797, -75.699997])
            assert numpy.allclose(got, expected)
        else:
            expected = [DS("-158.135803"), DS("-179.035797"), DS("-75.699997")]
            assert got == expected

        assert 128 == ct.Rows
        assert 128 == ct.Columns
        assert 16 == ct.BitsStored
        assert 128 * 128 * 2 == len(ct.PixelData)

        # Also test private elements name can be resolved:
        got = ct[(0x0043, 0x104E)].name
        assert "[Duration of X-ray on]" == got

    @pytest.mark.skipif(not have_numpy, reason="Numpy not installed")
    def test_CT_PixelData(self):
        """Check that we can read pixel data.
        Tests that we get last one in array.
        """
        ct = dcmread(ct_name)
        assert 909 == ct.pixel_array[-1][-1]

    def test_no_force(self):
        """Raises exception if missing DICOM header and force==False."""
        with pytest.raises(InvalidDicomError):
            dcmread(rtstruct_name)

    def test_RTStruct(self):
        """Returns correct values for sample elements in test RTSTRUCT file."""
        # RTSTRUCT test file has complex nested sequences
        # -- see rtstruct.dump file
        # Also has no DICOM header ... so tests 'force' argument of dcmread

        rtss = dcmread(rtstruct_name, force=True)
        frame_of_ref = rtss.ReferencedFrameOfReferenceSequence[0]
        study = frame_of_ref.RTReferencedStudySequence[0]
        uid = study.RTReferencedSeriesSequence[0].SeriesInstanceUID
        assert "1.2.826.0.1.3680043.8.498.2010020400001.2.1.1" == uid

        got = rtss.ROIContourSequence[0].ContourSequence[2].ContourNumber
        assert 3 == got

        obs_seq0 = rtss.RTROIObservationsSequence[0]
        got = obs_seq0.ROIPhysicalPropertiesSequence[0].ROIPhysicalProperty
        assert "REL_ELEC_DENSITY" == got

    def test_dir(self):
        """Returns correct dir attributes for both Dataset and DICOM names
        (python >= 2.6).."""
        # Only python >= 2.6 calls __dir__ for dir() call
        rtss = dcmread(rtstruct_name, force=True)
        # sample some expected 'dir' values
        got_dir = dir(rtss)
        expect_in_dir = [
            "pixel_array",
            "add_new",
            "ROIContourSequence",
            "StructureSetDate",
        ]
        for name in expect_in_dir:
            assert name in got_dir

        # Now check for some items in dir() of a nested item
        roi0 = rtss.ROIContourSequence[0]
        got_dir = dir(roi0)
        expect_in_dir = [
            "pixel_array",
            "add_new",
            "ReferencedROINumber",
            "ROIDisplayColor",
        ]
        for name in expect_in_dir:
            assert name in got_dir

    def test_MR(self):
        """Returns correct values for sample data elements in test MR file."""
        mr = dcmread(mr_name)
        # (0010, 0010) Patient's Name           'CompressedSamples^MR1'
        mr.decode()
        assert "CompressedSamples^MR1" == mr.PatientName
        assert mr[0x10, 0x10].value == mr.PatientName

        DS = pydicom.valuerep.DS

        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([0.3125, 0.3125])
            assert numpy.allclose(mr.PixelSpacing, expected)
        else:
            assert [DS("0.3125"), DS("0.3125")] == mr.PixelSpacing

    def test_deflate(self):
        """Returns correct values for sample data elements in test compressed
         (zlib deflate) file
         """
        # Everything after group 2 is compressed.
        # If we can read anything else, the decompression must have been ok.
        ds = dcmread(deflate_name)
        assert "WSD" == ds.ConversionType

    def test_sequence_with_implicit_vr(self):
        """Test that reading a UN sequence with unknown length and implicit VR
        in a dataset with explicit VR is read regardless of the value of
        the assume_implicit_vr_switch option."""
        replace_un_with_known_vr = config.replace_un_with_known_vr
        assume_implicit_vr_switch = config.assume_implicit_vr_switch

        config.replace_un_with_known_vr = True
        config.assume_implicit_vr_switch = True
        ds = dcmread(get_testdata_file("bad_sequence.dcm"))
        str(ds.CTDIPhantomTypeCodeSequence)

        config.assume_implicit_vr_switch = False
        ds = dcmread(get_testdata_file("bad_sequence.dcm"))
        str(ds.CTDIPhantomTypeCodeSequence)

        config.replace_un_with_known_vr = False
        ds = dcmread(get_testdata_file("bad_sequence.dcm"))
        str(ds.CTDIPhantomTypeCodeSequence)

        config.replace_un_with_known_vr = replace_un_with_known_vr
        config.assume_implicit_vr_switch = assume_implicit_vr_switch

    def test_no_pixels_read(self):
        """Returns all data elements before pixels using
        stop_before_pixels=False.
        """
        # Just check the tags, and a couple of values
        ctpartial = dcmread(ct_name, stop_before_pixels=True)
        ctpartial_tags = sorted(ctpartial.keys())
        ctfull = dcmread(ct_name)
        ctfull_tags = sorted(ctfull.keys())
        missing = [Tag(0x7FE0, 0x10), Tag(0xFFFC, 0xFFFC)]
        assert ctfull_tags == ctpartial_tags + missing

    @pytest.mark.skipif(not have_numpy, reason="Numpy not available")
    def test_no_float_pixels_read(self):
        """Returns all data elements before pixels using
        stop_before_pixels=True.
        """
        ds = Dataset()
        ds.InstanceNumber = 1
        ds.FloatPixelData = numpy.random.random((3, 3)).tobytes()

        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        file_ds.is_implicit_VR = True
        file_ds.is_little_endian = True
        file_ds.save_as(fp, write_like_original=True)

        test_ds = dcmread(fp, force=True, stop_before_pixels=True)
        ds_tags = sorted(ds.keys())
        test_ds_tags = sorted(test_ds.keys())
        assert ds_tags == test_ds_tags + [Tag(0x7FE0, 0x08)]

    def test_specific_tags(self):
        """Returns only tags specified by user."""
        ctspecific = dcmread(
            ct_name,
            specific_tags=[
                Tag(0x0010, 0x0010),
                "PatientID",
                "ImageType",
                "ViewName",
            ],
        )
        ctspecific_tags = sorted(ctspecific.keys())
        expected = [
            # SpecificCharacterSet is always added
            # ViewName does not exist in the data set
            Tag(0x0008, 0x0005),
            Tag(0x0008, 0x0008),
            Tag(0x0010, 0x0010),
            Tag(0x0010, 0x0020),
        ]
        assert expected == ctspecific_tags

    def test_specific_tags_with_other_unkonwn_length_tags(self):
        rtstruct_specific = dcmread(
            rtstruct_name,
            force=True,
            specific_tags=[
                "PatientName",
                "PatientID",
            ],
        )
        rtstruct_specific_tags = sorted(rtstruct_specific.keys())
        expected = [
            # SpecificCharacterSet is always added
            Tag(0x0008, 0x0005),
            Tag(0x0010, 0x0010),
            Tag(0x0010, 0x0020),
        ]
        assert expected == rtstruct_specific_tags

    def test_specific_tags_with_unknown_length_SQ(self):
        """Returns only tags specified by user."""
        unknown_len_sq_tag = Tag(0x3F03, 0x1001)
        tags = dcmread(priv_SQ_name, specific_tags=[unknown_len_sq_tag])
        tags = sorted(tags.keys())
        assert [unknown_len_sq_tag] == tags

        tags = dcmread(priv_SQ_name, specific_tags=["PatientName"])
        tags = sorted(tags.keys())
        assert [] == tags

    def test_specific_tags_with_unknown_length_tag(self):
        """Returns only tags specified by user."""
        unknown_len_tag = Tag(0x7FE0, 0x0010)  # Pixel Data
        tags = dcmread(emri_jpeg_2k_lossless, specific_tags=[unknown_len_tag])
        tags = sorted(tags.keys())
        # SpecificCharacterSet is always added
        assert [Tag(0x08, 0x05), unknown_len_tag] == tags

        tags = dcmread(
            emri_jpeg_2k_lossless, specific_tags=["SpecificCharacterSet"]
        )
        tags = sorted(tags.keys())
        assert [Tag(0x08, 0x05)] == tags

    def test_tag_with_unknown_length_tag_too_short(self, allow_invalid_values):
        """Tests handling of incomplete sequence value."""
        # the data set is the same as emri_jpeg_2k_lossless,
        # with the last 8 bytes removed to provoke the EOF error
        unknown_len_tag = Tag(0x7FE0, 0x0010)  # Pixel Data
        with pytest.warns(UserWarning, match="End of file reached*"):
            dcmread(
                emri_jpeg_2k_lossless_too_short,
                specific_tags=[unknown_len_tag],
            )

    def test_tag_with_unknown_length_tag_too_short_strict(
            self, enforce_valid_values):
        """Tests handling of incomplete sequence value in strict mode."""
        unknown_len_tag = Tag(0x7FE0, 0x0010)  # Pixel Data
        with pytest.raises(EOFError, match="End of file reached*"):
            dcmread(
                emri_jpeg_2k_lossless_too_short,
                specific_tags=[unknown_len_tag],
            )

    def test_private_SQ(self):
        """Can read private undefined length SQ without error."""
        # From issues 91, 97, 98. Bug introduced by fast reading, due to
        #    VR=None in raw data elements, then an undefined length private
        #    item VR is looked up, and there is no such tag,
        #    generating an exception

        # Simply read the file, in 0.9.5 this generated an exception
        dcmread(priv_SQ_name)

    def test_nested_private_SQ(self):
        """Can successfully read a private SQ which contains additional SQs."""
        # From issue 113. When a private SQ of undefined length is used, the
        #   sequence is read in and the length of the SQ is determined upon
        #   identification of the SQ termination sequence. When using nested
        #   Sequences, the first termination sequence encountered actually
        #   belongs to the nested Sequence not the parent, therefore the
        #   remainder of the file is not read in properly
        ds = dcmread(nested_priv_SQ_name)

        # Make sure that the entire dataset was read in
        pixel_data_tag = TupleTag((0x7FE0, 0x10))
        assert pixel_data_tag in ds

        # Check that the DataElement is indeed a Sequence
        tag = TupleTag((0x01, 0x01))
        seq0 = ds[tag]
        assert "SQ" == seq0.VR

        # Now verify the presence of the nested private SQ
        seq1 = seq0[0][tag]
        assert "SQ" == seq1.VR

        # Now make sure the values that are parsed are correct
        assert b"Double Nested SQ" == seq1[0][tag].value
        assert b"Nested SQ" == seq0[0][0x01, 0x02].value

    def test_un_sequence(self, dont_replace_un_with_known_vr):
        ds = dcmread(get_testdata_file("UN_sequence.dcm"))
        seq_element = ds[0x4453100c]
        assert seq_element.VR == "SQ"
        assert len(seq_element.value) == 1
        assert len(seq_element.value[0].ReferencedSeriesSequence) == 1

    def test_no_meta_group_length(self, no_datetime_conversion):
        """Read file with no group length in file meta."""
        # Issue 108 -- iView example file with no group length (0002,0002)
        # Originally crashed, now check no exception, but also check one item
        #     in file_meta, and second one in followinsg dataset
        ds = dcmread(no_meta_group_length)
        assert "20111130" == ds.InstanceCreationDate

    def test_no_transfer_syntax_in_meta(self):
        """Read file with file_meta, but has no TransferSyntaxUID in it."""
        # From issue 258: if file has file_meta but no TransferSyntaxUID in it,
        #   should assume default transfer syntax
        ds = dcmread(meta_missing_tsyntax_name)  # is default transfer syntax

        # Repeat one test from nested private sequence test to maker sure
        #    file was read correctly
        pixel_data_tag = TupleTag((0x7FE0, 0x10))
        assert pixel_data_tag in ds

    def test_explicit_VR_little_endian_no_meta(self, no_datetime_conversion):
        """Read file without file meta with Little Endian Explicit VR dataset.
        """
        # Example file from CMS XiO 5.0 and above
        # Still need to force read data since there is no 'DICM' marker present
        ds = dcmread(explicit_vr_le_no_meta, force=True)
        assert "20150529" == ds.InstanceCreationDate

    def test_explicit_VR_big_endian_no_meta(self, no_datetime_conversion):
        """Read file without file meta with Big Endian Explicit VR dataset."""
        # Example file from CMS XiO 5.0 and above
        # Still need to force read data since there is no 'DICM' marker present
        ds = dcmread(explicit_vr_be_no_meta, force=True)
        assert "20150529" == ds.InstanceCreationDate

    def test_planar_config(self):
        px_data_ds = dcmread(color_px_name)
        pl_data_ds = dcmread(color_pl_name)
        assert px_data_ds.PlanarConfiguration != pl_data_ds.PlanarConfiguration
        if have_numpy:
            px_data = px_data_ds.pixel_array
            pl_data = pl_data_ds.pixel_array
            assert numpy.all(px_data == pl_data)

    def test_correct_ambiguous_vr(self):
        """Test correcting ambiguous VR elements read from file"""
        ds = Dataset()
        ds.PixelRepresentation = 0
        ds.add(DataElement(0x00280108, "US", 10))
        ds.add(DataElement(0x00280109, "US", 500))

        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        file_ds.is_implicit_VR = True
        file_ds.is_little_endian = True
        file_ds.save_as(fp, write_like_original=True)

        ds = dcmread(fp, force=True)
        assert "US" == ds[0x00280108].VR
        assert 10 == ds.SmallestPixelValueInSeries

    def test_correct_ambiguous_explicit_vr(self):
        """Test correcting ambiguous VR elements read from file"""
        ds = Dataset()
        ds.PixelRepresentation = 0
        ds.add(DataElement(0x00280108, "US", 10))
        ds.add(DataElement(0x00280109, "US", 500))

        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        file_ds.is_implicit_VR = False
        file_ds.is_little_endian = True
        file_ds.save_as(fp, write_like_original=True)

        ds = dcmread(fp, force=True)
        assert "US" == ds[0x00280108].VR
        assert 10 == ds.SmallestPixelValueInSeries

    def test_correct_ambiguous_vr_compressed(self):
        """Test correcting compressed Pixel Data read from file"""
        # Create an implicit VR compressed dataset
        ds = dcmread(jpeg_lossless_name)
        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        file_ds.is_implicit_VR = True
        file_ds.is_little_endian = True
        file_ds.save_as(fp, write_like_original=True)

        ds = dcmread(fp, force=True)
        assert "OB" == ds[0x7FE00010].VR

    def test_read_encoded_pixel_data_without_embedded_sequence_delimiter(self):
        ds = dcmread(jpeg2000_name)
        assert "OB" == ds[0x7FE00010].VR
        assert 266 == len(ds[0x7FE00010].value)

    def test_read_encoded_pixel_data_with_embedded_sequence_delimiter(self):
        """Test ignoring embedded sequence delimiter in encoded pixel
        data fragment. Reproduces #1140.
        """
        ds = dcmread(jpeg2000_embedded_sequence_delimeter_name)
        assert "OB" == ds[0x7FE00010].VR
        assert 266 == len(ds[0x7FE00010].value)

    def test_long_specific_char_set(self, allow_invalid_values):
        """Test that specific character set is read even if it is longer
         than defer_size"""
        ds = Dataset()

        long_specific_char_set_value = ["ISO 2022IR 100"] * 9
        ds.add(DataElement(0x00080005, "CS", long_specific_char_set_value))

        msg = (
            r"Unknown encoding 'ISO 2022IR 100' - using default encoding "
            r"instead"
        )

        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        with pytest.warns(UserWarning, match=msg):
            file_ds.save_as(fp, write_like_original=True)

        with pytest.warns(UserWarning, match=msg):
            ds = dcmread(fp, defer_size=65, force=True)
            assert long_specific_char_set_value == ds[0x00080005].value

    def test_long_specific_char_set_strict(self, enforce_valid_values):
        ds = Dataset()

        long_specific_char_set_value = ["ISO 2022IR 100"] * 9
        ds.add(DataElement(0x00080005, "CS", long_specific_char_set_value))

        fp = BytesIO()
        file_ds = FileDataset(fp, ds)
        with pytest.raises(LookupError,
                           match="Unknown encoding 'ISO 2022IR 100'"):
            file_ds.save_as(fp, write_like_original=True)

    def test_no_preamble_file_meta_dataset(self):
        """Test correct read of group 2 elements with no preamble."""
        bytestream = (
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00\x20\x20\x10\x00"
            b"\x02\x00\x00\x00\x01\x00\x20\x20\x20\x00\x06\x00"
            b"\x00\x00\x4e\x4f\x52\x4d\x41\x4c"
        )

        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "MediaStorageSOPClassUID" in ds.file_meta
        assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
        assert "NORMAL" == ds.Polarity
        assert 1 == ds.ImageBoxPosition

    def test_no_preamble_command_group_dataset(self):
        """Test correct read of group 0 and 2 elements with no preamble."""
        bytestream = (
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00"
            b"\x20\x20\x10\x00\x02\x00\x00\x00\x01\x00\x20\x20"
            b"\x20\x00\x06\x00\x00\x00\x4e\x4f\x52\x4d\x41\x4c"
            b"\x00\x00\x10\x01\x02\x00\x00\x00\x03\x00"
        )

        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "MediaStorageSOPClassUID" in ds.file_meta
        assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
        assert "NORMAL" == ds.Polarity
        assert 1 == ds.ImageBoxPosition
        assert 3 == ds.MessageID

    def test_group_length_wrong(self):
        """Test file is read correctly even if FileMetaInformationGroupLength
        is incorrect.
        """
        bytestream = (
            b"\x02\x00\x00\x00\x55\x4C\x04\x00\x0A\x00\x00\x00"
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00"
            b"\x20\x20\x10\x00\x02\x00\x00\x00\x01\x00\x20\x20"
            b"\x20\x00\x06\x00\x00\x00\x4e\x4f\x52\x4d\x41\x4c"
        )
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        value = ds.file_meta.FileMetaInformationGroupLength
        assert not len(bytestream) - 12 == value
        assert 10 == ds.file_meta.FileMetaInformationGroupLength
        assert "MediaStorageSOPClassUID" in ds.file_meta
        assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
        assert "NORMAL" == ds.Polarity
        assert 1 == ds.ImageBoxPosition

    def test_preamble_command_meta_no_dataset(self):
        """Test reading only preamble, command and meta elements"""
        preamble = b"\x00" * 128
        prefix = b"DICM"
        command = (
            b"\x00\x00\x00\x00\x04\x00\x00\x00\x38"
            b"\x00\x00\x00\x00\x00\x02\x00\x12\x00\x00"
            b"\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31"
            b"\x30\x30\x30\x38\x2e\x31\x2e\x31\x00\x00"
            b"\x00\x00\x01\x02\x00\x00\x00\x30\x00\x00"
            b"\x00\x10\x01\x02\x00\x00\x00\x07\x00\x00"
            b"\x00\x00\x08\x02\x00\x00\x00\x01\x01"
        )
        meta = (
            b"\x02\x00\x00\x00\x55\x4C\x04\x00\x0A\x00\x00\x00"
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00"
        )

        bytestream = preamble + prefix + meta + command
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "TransferSyntaxUID" in ds.file_meta
        assert "MessageID" in ds

    def test_preamble_meta_no_dataset(self):
        """Test reading only preamble and meta elements"""
        preamble = b"\x00" * 128
        prefix = b"DICM"
        meta = (
            b"\x02\x00\x00\x00\x55\x4C\x04\x00\x0A\x00\x00\x00"
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00"
        )

        bytestream = preamble + prefix + meta
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert b"\x00" * 128 == ds.preamble
        assert "TransferSyntaxUID" in ds.file_meta
        assert Dataset() == ds[:]

    def test_preamble_commandset_no_dataset(self):
        """Test reading only preamble and command set"""
        preamble = b"\x00" * 128
        prefix = b"DICM"
        command = (
            b"\x00\x00\x00\x00\x04\x00\x00\x00\x38"
            b"\x00\x00\x00\x00\x00\x02\x00\x12\x00\x00"
            b"\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31"
            b"\x30\x30\x30\x38\x2e\x31\x2e\x31\x00\x00"
            b"\x00\x00\x01\x02\x00\x00\x00\x30\x00\x00"
            b"\x00\x10\x01\x02\x00\x00\x00\x07\x00\x00"
            b"\x00\x00\x08\x02\x00\x00\x00\x01\x01"
        )
        bytestream = preamble + prefix + command

        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "MessageID" in ds
        assert Dataset() == ds.file_meta

    def test_meta_no_dataset(self):
        """Test reading only meta elements"""
        bytestream = (
            b"\x02\x00\x00\x00\x55\x4C\x04\x00\x0A\x00\x00\x00"
            b"\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e"
            b"\x38\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x35\x2e"
            b"\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00\x55\x49"
            b"\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30"
            b"\x30\x30\x38\x2e\x31\x2e\x32\x00"
        )
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "TransferSyntaxUID" in ds.file_meta
        assert Dataset() == ds[:]

    def test_commandset_no_dataset(self):
        """Test reading only command set elements"""
        bytestream = (
            b"\x00\x00\x00\x00\x04\x00\x00\x00\x38"
            b"\x00\x00\x00\x00\x00\x02\x00\x12\x00\x00"
            b"\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31"
            b"\x30\x30\x30\x38\x2e\x31\x2e\x31\x00\x00"
            b"\x00\x00\x01\x02\x00\x00\x00\x30\x00\x00"
            b"\x00\x10\x01\x02\x00\x00\x00\x07\x00\x00"
            b"\x00\x00\x08\x02\x00\x00\x00\x01\x01"
        )
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert "MessageID" in ds
        assert ds.preamble is None
        assert Dataset() == ds.file_meta

    def test_file_meta_dataset_implicit_vr(self, allow_invalid_values):
        """Test reading a file meta dataset that is implicit VR"""

        bytestream = (
            b"\x02\x00\x10\x00\x12\x00\x00\x00"
            b"\x31\x2e\x32\x2e\x38\x34\x30\x2e"
            b"\x31\x30\x30\x30\x38\x2e\x31\x2e"
            b"\x32\x00"
        )
        fp = BytesIO(bytestream)
        with pytest.warns(UserWarning):
            ds = dcmread(fp, force=True)
        assert "TransferSyntaxUID" in ds.file_meta

    def test_file_meta_dataset_implicit_vr_strict(self, enforce_valid_values):
        """Test reading a file meta dataset that is implicit VR"""

        bytestream = (
            b"\x02\x00\x10\x00\x12\x00\x00\x00"
            b"\x31\x2e\x32\x2e\x38\x34\x30\x2e"
            b"\x31\x30\x30\x30\x38\x2e\x31\x2e"
            b"\x32\x00"
        )
        fp = BytesIO(bytestream)
        with pytest.raises(InvalidDicomError,
                           match="Expected explicit VR, "
                                 "but found implicit VR"):
            dcmread(fp, force=True)

    def test_no_dataset(self):
        """Test reading no elements or preamble produces empty Dataset"""
        bytestream = b""
        fp = BytesIO(bytestream)
        ds = dcmread(fp, force=True)
        assert ds.preamble is None
        assert Dataset() == ds.file_meta
        assert Dataset() == ds[:]

    def test_empty_file(self):
        """Test reading no elements from file produces empty Dataset"""
        with tempfile.NamedTemporaryFile() as f:
            ds = dcmread(f, force=True)
            assert ds.preamble is None
            assert Dataset() == ds.file_meta
            assert Dataset() == ds[:]

    def test_bad_filename(self):
        """Test reading from non-existing file raises."""
        with pytest.raises(FileNotFoundError):
            dcmread("InvalidFilePath")
        with pytest.raises(TypeError, match="dcmread: Expected a file path or "
                                            "a file-like, but got None"):
            dcmread(None)
        with pytest.raises(TypeError, match="dcmread: Expected a file path or "
                                            "a file-like, but got int"):
            dcmread(42)

    def test_empty_specific_character_set(self):
        """Test that an empty Specific Character Set is handled correctly.
        Regression test for #1038"""
        ds = dcmread(get_testdata_file("empty_charset_LEI.dcm"))
        assert ds.read_encoding == ["iso8859"]

    def test_dcmread_does_not_raise(self):
        """Test that reading from DicomBytesIO does not raise on EOF.
        Regression test for #358."""
        ds = dcmread(mr_name)
        fp = DicomBytesIO()
        ds.save_as(fp, write_like_original=True)
        fp.seek(0)
        de_gen = data_element_generator(fp, False, True)
        try:
            while True:
                next(de_gen)
        except StopIteration:
            pass
        except EOFError:
            self.fail("Unexpected EOFError raised")

    def test_lut_descriptor(self):
        """Regression test for #942: incorrect first value"""
        prefixes = [
            b"\x28\x00\x01\x11",
            b"\x28\x00\x02\x11",
            b"\x28\x00\x03\x11",
            b"\x28\x00\x02\x30",
        ]
        suffix = b"\x53\x53\x06\x00\x00\xf5\x00\xf8\x10\x00"

        for raw_tag in prefixes:
            tag = unpack("<2H", raw_tag)
            bs = DicomBytesIO(raw_tag + suffix)
            bs.is_little_endian = True
            bs.is_implicit_VR = False

            ds = dcmread(bs, force=True)
            elem = ds[tag]
            assert elem.VR == "SS"
            assert elem.value == [62720, -2048, 16]

    def test_lut_descriptor_empty(self):
        """Regression test for #1049: LUT empty raises."""
        bs = DicomBytesIO(b"\x28\x00\x01\x11\x53\x53\x00\x00")
        bs.is_little_endian = True
        bs.is_implicit_VR = False
        ds = dcmread(bs, force=True)
        elem = ds[0x00281101]
        assert elem.value is None
        assert elem.VR == "SS"

    def test_lut_descriptor_singleton(self):
        """Test LUT Descriptor with VM = 1"""
        bs = DicomBytesIO(b"\x28\x00\x01\x11\x53\x53\x02\x00\x00\xf5")
        bs.is_little_endian = True
        bs.is_implicit_VR = False
        ds = dcmread(bs, force=True)
        elem = ds[0x00281101]
        # No conversion to US if not a triplet
        assert elem.value == -2816
        assert elem.VR == "SS"

    def test_reading_of(self):
        """Test reading a dataset with OF element."""
        bs = DicomBytesIO(
            b"\x28\x00\x01\x11\x53\x53\x06\x00\x00\xf5\x00\xf8\x10\x00"
            b"\xe0\x7f\x08\x00\x4F\x46\x00\x00\x04\x00\x00\x00\x00\x01\x02\x03"
        )
        bs.is_little_endian = True
        bs.is_implicit_VR = False

        ds = dcmread(bs, force=True)
        elem = ds["FloatPixelData"]
        assert "OF" == elem.VR
        assert b"\x00\x01\x02\x03" == elem.value

    def test_empty_pn(self):
        """Test correct type for an empty PN element."""
        # Test for 1338
        ds = Dataset()
        ds.is_little_endian = True
        ds.is_implicit_VR = True
        ds.PatientName = ''
        assert isinstance(ds.PatientName, pydicom.valuerep.PersonName)

        bs = DicomBytesIO()
        ds.save_as(bs)

        out = dcmread(bs, force=True)
        assert isinstance(ds[0x00100010].value, pydicom.valuerep.PersonName)


class TestIncorrectVR:
    def setup(self):
        self.ds_explicit = BytesIO(
            b"\x08\x00\x05\x00CS\x0a\x00ISO_IR 100"  # SpecificCharacterSet
            b"\x08\x00\x20\x00DA\x08\x0020000101"  # StudyDate
        )
        self.ds_implicit = BytesIO(
            b"\x08\x00\x05\x00\x0a\x00\x00\x00ISO_IR 100"
            b"\x08\x00\x20\x00\x08\x00\x00\x0020000101"
        )

    def test_implicit_vr_expected_explicit_used(self, allow_invalid_values,
                                                no_datetime_conversion):
        msg = (
            "Expected implicit VR, but found explicit VR - "
            "using explicit VR for reading"
        )

        with pytest.warns(UserWarning, match=msg):
            ds = read_dataset(
                self.ds_explicit, is_implicit_VR=True, is_little_endian=True
            )
        assert "ISO_IR 100" == ds.SpecificCharacterSet
        assert "20000101" == ds.StudyDate

    def test_implicit_vr_expected_explicit_used_strict(
            self, enforce_valid_values):
        msg = "Expected implicit VR, but found explicit VR"
        with pytest.raises(InvalidDicomError, match=msg):
            read_dataset(
                self.ds_explicit, is_implicit_VR=True, is_little_endian=True
            )

    def test_explicit_vr_expected_implicit_used(self, allow_invalid_values,
                                                no_datetime_conversion):
        msg = (
            "Expected explicit VR, but found implicit VR - "
            "using implicit VR for reading"
        )

        with pytest.warns(UserWarning, match=msg):
            ds = read_dataset(
                self.ds_implicit, is_implicit_VR=False, is_little_endian=True
            )
        assert "ISO_IR 100" == ds.SpecificCharacterSet
        assert "20000101" == ds.StudyDate

    def test_explicit_vr_expected_implicit_used_strict(
            self, enforce_valid_values):
        msg = "Expected explicit VR, but found implicit VR"
        with pytest.raises(InvalidDicomError, match=msg):
            read_dataset(
                self.ds_implicit, is_implicit_VR=False, is_little_endian=True
            )

    def test_seq_item_looks_like_explicit_VR(self):
        # For issue 999.

        # Set up an implicit VR dataset with a "normal" group 8 tag,
        # followed by a sequence with an item (dataset) having
        # a data element length that looks like a potential valid VR
        ds = Dataset()
        ds.file_meta = FileMetaDataset()
        ds.file_meta.MediaStorageSOPClassUID = "1.1.1"
        ds.file_meta.MediaStorageSOPInstanceUID = "2.2.2"
        ds.is_implicit_VR = True
        ds.is_little_endian = True
        ds.SOPClassUID = "9.9.9"  # First item group 8 in top-level dataset
        seq = Sequence()
        seq_ds = Dataset()
        seq_ds.BadPixelImage = b"\3" * 0x5244  # length looks like "DR"
        seq.append(seq_ds)
        ds.ReferencedImageSequence = seq

        dbio = DicomBytesIO()
        ds.save_as(dbio, write_like_original=False)

        # Now read the constructed dataset back in
        # In original issue, shows warning that has detected what appears
        # to be Explicit VR, then throws NotImplemented for the unknown VR
        dbio.seek(0)
        ds = dcmread(dbio)
        ds.remove_private_tags()  # forces it to actually parse SQ


class TestUnknownVR:
    @pytest.fixture(autouse=True)
    def restore_config_values(self):
        orig_impl_VR_switch = config.assume_implicit_vr_switch
        config.assume_implicit_vr_switch = False
        yield
        config.assume_implicit_vr_switch = orig_impl_VR_switch

    @pytest.mark.parametrize(
        "vr_bytes, str_output",
        [
            # Test limits of char values
            (b"\x00\x41", "0x00 0x41"),  # 000/A
            (b"\x40\x41", "0x40 0x41"),  # 064/A
            (b"\x5B\x41", "0x5b 0x41"),  # 091/A
            (b"\x60\x41", "0x60 0x41"),  # 096/A
            (b"\x7B\x41", "0x7b 0x41"),  # 123/A
            (b"\xFF\x41", "0xff 0x41"),  # 255/A
            # Test good/bad
            (b"\x41\x00", "0x41 0x00"),  # A/-
            (b"\x5A\x00", "0x5a 0x00"),  # Z/-
            # Test not quite good/bad
            (b"\x61\x00", "0x61 0x00"),  # a/-
            (b"\x7A\x00", "0x7a 0x00"),  # z/-
            # Test bad/good
            (b"\x00\x41", "0x00 0x41"),  # -/A
            (b"\x00\x5A", "0x00 0x5a"),  # -/Z
            # Test bad/not quite good
            (b"\x00\x61", "0x00 0x61"),  # -/a
            (b"\x00\x7A", "0x00 0x7a"),  # -/z
            # Test good/good
            (b"\x41\x41", "AA"),  # A/A
            (b"\x41\x5A", "AZ"),  # A/Z
            (b"\x5A\x41", "ZA"),  # Z/A
            (b"\x5A\x5A", "ZZ"),  # Z/Z
            # Test not quite good
            (b"\x41\x61", "Aa"),  # A/a
            (b"\x41\x7A", "Az"),  # A/z
            (b"\x61\x41", "aA"),  # a/A
            (b"\x61\x5A", "aZ"),  # a/Z
            (b"\x61\x61", "aa"),  # a/a
            (b"\x61\x7A", "az"),  # a/z
            (b"\x5A\x61", "Za"),  # Z/a
            (b"\x5A\x7A", "Zz"),  # Z/z
            (b"\x7A\x41", "zA"),  # z/A
            (b"\x7A\x5A", "zZ"),  # z/Z
            (b"\x7A\x61", "za"),  # z/a
            (b"\x7A\x7A", "zz"),  # z/z
        ],
    )
    def test_fail_decode_msg(self, vr_bytes, str_output):
        """Regression test for #791."""
        # start the dataset with a valid tag (SpecificCharacterSet),
        # as the first tag is used to check the VR
        ds = read_dataset(
            BytesIO(
                b"\x08\x00\x05\x00CS\x0a\x00ISO_IR 100"
                b"\x08\x00\x06\x00" + vr_bytes + b"\x00\x00\x00\x08\x00\x49"
            ),
            False,
            True,
        )
        msg = r"Unknown Value Representation '{}' in tag \(0008, 0006\)"
        msg = msg.format(str_output)
        with pytest.raises(NotImplementedError, match=msg):
            print(ds)


class TestReadDataElement:
    def setup(self):
        ds = Dataset()
        ds.DoubleFloatPixelData = (
            b"\x00\x01\x02\x03\x04\x05\x06\x07"
            b"\x01\x01\x02\x03\x04\x05\x06\x07"
        )  # OD
        ds.SelectorOLValue = (
            b"\x00\x01\x02\x03\x04\x05\x06\x07" b"\x01\x01\x02\x03"
        )  # VR of OL
        ds.PotentialReasonsForProcedure = [
            "A",
            "B",
            "C",
        ]  # VR of UC, odd length
        ds.StrainDescription = "Test"  # Even length
        ds.URNCodeValue = "http://test.com"  # VR of UR
        ds.RetrieveURL = "ftp://test.com  "  # Test trailing spaces ignored
        ds.DestinationAE = "    TEST  12    "  # 16 characters max for AE
        # 8-byte values
        ds.ExtendedOffsetTable = (  # VR of OV
            b"\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x01\x02\x03\x04\x05\x06\x07\x08"
        )

        # No public elements with VR of SV or UV yet...
        add_dict_entries(
            {
                0xFFFE0001: (
                    "SV",
                    "1",
                    "SV Element Minimum",
                    "",
                    "SVElementMinimum",
                ),
                0xFFFE0002: (
                    "SV",
                    "1",
                    "SV Element Maximum",
                    "",
                    "SVElementMaximum",
                ),
                0xFFFE0003: (
                    "UV",
                    "1",
                    "UV Element Minimum",
                    "",
                    "UVElementMinimum",
                ),
                0xFFFE0004: (
                    "UV",
                    "1",
                    "UV Element Maximum",
                    "",
                    "UVElementMaximum",
                ),
            }
        )
        ds.SVElementMinimum = -(2 ** 63)
        ds.SVElementMaximum = 2 ** 63 - 1
        ds.UVElementMinimum = 0
        ds.UVElementMaximum = 2 ** 64 - 1

        self.fp = BytesIO()  # Implicit little
        file_ds = FileDataset(self.fp, ds)
        file_ds.is_implicit_VR = True
        file_ds.is_little_endian = True
        file_ds.save_as(self.fp, write_like_original=True)

        self.fp_ex = BytesIO()  # Explicit little
        file_ds = FileDataset(self.fp_ex, ds)
        file_ds.is_implicit_VR = False
        file_ds.is_little_endian = True
        file_ds.save_as(self.fp_ex, write_like_original=True)

    def test_read_OD_implicit_little(self):
        """Check creation of OD DataElement from byte data works correctly."""
        ds = dcmread(self.fp, force=True)
        ref_elem = ds.get(0x7FE00009)
        elem = DataElement(
            0x7FE00009,
            "OD",
            b"\x00\x01\x02\x03\x04\x05\x06\x07"
            b"\x01\x01\x02\x03\x04\x05\x06\x07",
        )
        assert ref_elem == elem

    def test_read_OD_explicit_little(self):
        """Check creation of OD DataElement from byte data works correctly."""
        ds = dcmread(self.fp_ex, force=True)
        ref_elem = ds.get(0x7FE00009)
        elem = DataElement(
            0x7FE00009,
            "OD",
            b"\x00\x01\x02\x03\x04\x05\x06\x07"
            b"\x01\x01\x02\x03\x04\x05\x06\x07",
        )
        assert ref_elem == elem

    def test_read_OL_implicit_little(self):
        """Check creation of OL DataElement from byte data works correctly."""
        ds = dcmread(self.fp, force=True)
        ref_elem = ds.get(0x00720075)
        elem = DataElement(
            0x00720075,
            "OL",
            b"\x00\x01\x02\x03\x04\x05\x06\x07" b"\x01\x01\x02\x03",
        )
        assert ref_elem == elem

    def test_read_OL_explicit_little(self):
        """Check creation of OL DataElement from byte data works correctly."""
        ds = dcmread(self.fp_ex, force=True)
        ref_elem = ds.get(0x00720075)
        elem = DataElement(
            0x00720075,
            "OL",
            b"\x00\x01\x02\x03\x04\x05\x06\x07" b"\x01\x01\x02\x03",
        )
        assert ref_elem == elem

    def test_read_UC_implicit_little(self):
        """Check creation of DataElement from byte data works correctly."""
        ds = dcmread(self.fp, force=True)
        ref_elem = ds.get(0x00189908)
        elem = DataElement(0x00189908, "UC", ["A", "B", "C"])
        assert ref_elem == elem

        ds = dcmread(self.fp, force=True)
        ref_elem = ds.get(0x00100212)
        elem = DataElement(0x00100212, "UC", "Test")
        assert ref_elem == elem

    def test_read_UC_explicit_little(self):
        """Check creation of DataElement from byte data works correctly."""
        ds = dcmread(self.fp_ex, force=True)
        ref_elem = ds.get(0x00189908)
        elem = DataElement(0x00189908, "UC", ["A", "B", "C"])
        assert ref_elem == elem

        ds = dcmread(self.fp_ex, force=True)
        ref_elem = ds.get(0x00100212)
        elem = DataElement(0x00100212, "UC", "Test")
        assert ref_elem == elem

    def test_read_UR_implicit_little(self):
        """Check creation of DataElement from byte data works correctly."""
        ds = dcmread(self.fp, force=True)
        ref_elem = ds.get(0x00080120)  # URNCodeValue
        elem = DataElement(0x00080120, "UR", "http://test.com")
        assert ref_elem == elem

        # Test trailing spaces ignored
        ref_elem = ds.get(0x00081190)  # RetrieveURL
        elem = DataElement(0x00081190, "UR", "ftp://test.com")
        assert ref_elem == elem

    def test_read_UR_explicit_little(self):
        """Check creation of DataElement from byte data works correctly."""
        ds = dcmread(self.fp_ex, force=True)
        ref_elem = ds.get(0x00080120)  # URNCodeValue
        elem = DataElement(0x00080120, "UR", "http://test.com")
        assert ref_elem == elem

        # Test trailing spaces ignored
        ref_elem = ds.get(0x00081190)  # RetrieveURL
        elem = DataElement(0x00081190, "UR", "ftp://test.com")
        assert ref_elem == elem

    def test_read_AE(self):
        """Check creation of AE DataElement from byte data works correctly."""
        ds = dcmread(self.fp, force=True)
        assert "TEST  12" == ds.DestinationAE

        # Test multivalue read correctly
        ds.DestinationAE = ["TEST  12  ", "  TEST2", "   TEST 3  "]

        fp = BytesIO()
        ds.save_as(fp, write_like_original=True)
        fp.seek(0)
        ds = dcmread(fp, force=True)
        assert ["TEST  12", "TEST2", "TEST 3"] == ds.DestinationAE

    def test_read_OV_implicit_little(self):
        """Check reading element with VR of OV encoded as implicit"""
        ds = dcmread(self.fp, force=True)
        val = (
            b"\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x01\x02\x03\x04\x05\x06\x07\x08"
        )
        elem = ds["ExtendedOffsetTable"]
        assert "OV" == elem.VR
        assert 0x7FE00001 == elem.tag
        assert val == elem.value

        new = DataElement(0x7FE00001, "OV", val)
        assert elem == new

    def test_read_OV_explicit_little(self):
        """Check reading element with VR of OV encoded as explicit"""
        ds = dcmread(self.fp_ex, force=True)
        val = (
            b"\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x01\x02\x03\x04\x05\x06\x07\x08"
        )
        elem = ds["ExtendedOffsetTable"]
        assert "OV" == elem.VR
        assert 0x7FE00001 == elem.tag
        assert val == elem.value

        new = DataElement(0x7FE00001, "OV", val)
        assert elem == new

    def test_read_SV_implicit_little(self):
        """Check reading element with VR of SV encoded as implicit"""
        ds = dcmread(self.fp, force=True)
        elem = ds["SVElementMinimum"]
        assert "SV" == elem.VR
        assert 0xFFFE0001 == elem.tag
        assert -(2 ** 63) == elem.value

        new = DataElement(0xFFFE0001, "SV", -(2 ** 63))
        assert elem == new

        elem = ds["SVElementMaximum"]
        assert "SV" == elem.VR
        assert 0xFFFE0002 == elem.tag
        assert 2 ** 63 - 1 == elem.value

        new = DataElement(0xFFFE0002, "SV", 2 ** 63 - 1)
        assert elem == new

    @pytest.mark.skip("No public elements with VR of SV")
    def test_read_SV_explicit_little(self):
        """Check reading element with VR of SV encoded as explicit"""
        ds = dcmread(self.fp_ex, force=True)
        elem = ds["SVElementMinimum"]
        assert "SV" == elem.VR
        assert 0xFFFE0001 == elem.tag
        assert -(2 ** 63) == elem.value

        new = DataElement(0xFFFE0001, "SV", -(2 ** 63))
        assert elem == new

        elem = ds["SVElementMaximum"]
        assert "SV" == elem.VR
        assert 0xFFFE0002 == elem.tag
        assert 2 ** 63 - 1 == elem.value

        new = DataElement(0xFFFE0002, "SV", 2 ** 63 - 1)
        assert elem == new

    def test_read_UV_implicit_little(self):
        """Check reading element with VR of UV encoded as implicit"""
        ds = dcmread(self.fp, force=True)
        elem = ds["UVElementMinimum"]
        assert "UV" == elem.VR
        assert 0xFFFE0003 == elem.tag
        assert 0 == elem.value

        new = DataElement(0xFFFE0003, "UV", 0)
        assert elem == new

        elem = ds["UVElementMaximum"]
        assert "UV" == elem.VR
        assert 0xFFFE0004 == elem.tag
        assert 2 ** 64 - 1 == elem.value

        new = DataElement(0xFFFE0004, "UV", 2 ** 64 - 1)
        assert elem == new

    def test_read_UV_explicit_little(self):
        """Check reading element with VR of UV encoded as explicit"""
        ds = dcmread(self.fp_ex, force=True)
        elem = ds["UVElementMinimum"]
        assert "UV" == elem.VR
        assert 0xFFFE0003 == elem.tag
        assert 0 == elem.value

        new = DataElement(0xFFFE0003, "UV", 0)
        assert elem == new

        elem = ds["UVElementMaximum"]
        assert "UV" == elem.VR
        assert 0xFFFE0004 == elem.tag
        assert 2 ** 64 - 1 == elem.value

        new = DataElement(0xFFFE0004, "UV", 2 ** 64 - 1)
        assert elem == new


class TestDSISnumpy:
    @pytest.fixture(autouse=True)
    def restore_config_values(self):
        orig_IS_numpy = config.use_IS_numpy
        orig_DS_numpy = config.use_DS_numpy
        orig_DS_decimal = config.use_DS_decimal
        yield
        config.use_IS_numpy = orig_IS_numpy
        config.DS_decimal(orig_DS_decimal)
        config.DS_numpy(orig_DS_numpy)

    @pytest.mark.skipif(have_numpy, reason="Testing import error")
    def test_IS_numpy_import_error(self):
        config.use_IS_numpy = True
        rtss = dcmread(rtstruct_name, force=True)
        # no numpy, then trying to use numpy raises error
        with pytest.raises(ImportError):
            rtss.ROIContourSequence[0].ROIDisplayColor  # VR is IS

    @pytest.mark.skipif(not have_numpy, reason="Testing with numpy only")
    def test_IS_numpy_class(self):
        config.use_IS_numpy = True
        rtss = dcmread(rtstruct_name, force=True)
        col = rtss.ROIContourSequence[0].ROIDisplayColor  # VR is IS
        assert isinstance(col, numpy.ndarray)
        assert "int64" == col.dtype

        # Check a conversion with only a single value
        roi_num = rtss.ROIContourSequence[0].ReferencedROINumber
        assert isinstance(roi_num, numpy.int64)

    def test_IS_not_numpy(self):
        """Test class of the object matches the config,
        when the config is changed"""
        config.use_IS_numpy = False
        rtss = dcmread(rtstruct_name, force=True)
        col = rtss.ROIContourSequence[0].ROIDisplayColor  # VR is IS
        assert isinstance(col, MultiValue)

    @pytest.mark.skipif(have_numpy, reason="Testing import error")
    def test_DS_numpy_import_error(self):
        config.use_DS_numpy = True
        rtss = dcmread(rtstruct_name, force=True)
        # no numpy, then trying to use numpy raises error
        with pytest.raises(ImportError):
            rtss.ROIContourSequence[0].ContourSequence[0].ContourData

    @pytest.mark.skipif(not have_numpy, reason="Testing with numpy only")
    def test_DS_numpy_class(self):
        config.use_DS_numpy = True
        rtss = dcmread(rtstruct_name, force=True)
        # ContourData has VR of DS
        cd = rtss.ROIContourSequence[0].ContourSequence[0].ContourData
        assert isinstance(cd, numpy.ndarray)
        assert "float64" == cd.dtype

        # Check conversion with only a single value
        roi_vol = rtss.StructureSetROISequence[0].ROIVolume
        assert isinstance(roi_vol, numpy.float64)

    def test_DS_not_numpy(self):
        """Test class of the object matches the config."""
        config.use_DS_numpy = False
        rtss = dcmread(rtstruct_name, force=True)
        # ContourData has VR of DS
        cd = rtss.ROIContourSequence[0].ContourSequence[0].ContourData
        assert isinstance(cd, MultiValue)

    @pytest.mark.skipif(not have_numpy, reason="numpy not installed")
    def test_DS_conflict_config(self):
        config.DS_numpy(False)
        config.DS_decimal(True)
        with pytest.raises(ValueError):
            config.DS_numpy(True)

    @pytest.mark.skipif(not have_numpy, reason="numpy not installed")
    def test_DS_conflict_config2(self):
        config.DS_numpy(True)
        with pytest.raises(ValueError):
            config.DS_decimal(True)

    @pytest.mark.skipif(not have_numpy, reason="numpy not installed")
    def test_DS_bad_chars(self):
        config.DS_numpy(True)
        with pytest.raises(ValueError):
            values.convert_DS_string(b"123.1b", True)

    @pytest.mark.skipif(not have_numpy, reason="numpy not installed")
    def test_IS_bad_chars(self):
        config.use_IS_numpy = True
        with pytest.raises(ValueError):
            values.convert_IS_string(b"123b", True)

    @pytest.mark.skipif(have_numpy, reason="testing numpy ImportError")
    def test_numpy_import_warning(self):
        config.DS_numpy(True)
        config.use_IS_numpy = True
        with pytest.raises(ImportError):
            values.convert_DS_string(b"123.1", True)
        with pytest.raises(ImportError):
            values.convert_IS_string(b"123", True)


class TestDeferredRead:
    """Test that deferred data element reading (for large size)
    works as expected
    """

    # Copy one of test files and use temporarily, then later remove.
    def setup(self):
        self.testfile_name = ct_name + ".tmp"
        shutil.copyfile(ct_name, self.testfile_name)

    def teardown(self):
        if os.path.exists(self.testfile_name):
            os.remove(self.testfile_name)

    def test_time_check(self):
        """Deferred read warns if file has been modified"""
        ds = dcmread(self.testfile_name, defer_size="2 kB")
        from time import sleep

        sleep(0.1)
        with open(self.testfile_name, "r+") as f:
            f.write("\0")  # "touch" the file

        msg = r"Deferred read warning -- file modification time has changed"
        with pytest.warns(UserWarning, match=msg):
            ds.PixelData

    def test_file_exists(self):
        """Deferred read raises error if file no longer exists."""
        ds = dcmread(self.testfile_name, defer_size=2000)
        os.remove(self.testfile_name)
        with pytest.raises(IOError):
            ds.PixelData

    def test_values_identical(self):
        """Deferred values exactly matches normal read."""
        ds_norm = dcmread(self.testfile_name)
        ds_defer = dcmread(self.testfile_name, defer_size=2000)
        for data_elem in ds_norm:
            tag = data_elem.tag

            if have_numpy and isinstance(data_elem.value, numpy.ndarray):
                assert numpy.allclose(data_elem.value, ds_defer[tag].value)
            else:
                assert data_elem.value == ds_defer[tag].value

    def test_zipped_deferred(self):
        """Deferred values from a gzipped file works."""
        # Arose from issue 103 "Error for defer_size read of gzip file object"
        fobj = gzip.open(gzip_name)
        ds = dcmread(fobj, defer_size=1)
        fobj.close()
        # before the fix, this threw an error as file reading was not in
        # the right place, it was re-opened as a normal file, not a zip file
        ds.InstanceNumber

    def test_filelike_deferred(self):
        """Deferred values work with file-like objects."""
        with open(ct_name, "rb") as fp:
            data = fp.read()
        filelike = io.BytesIO(data)
        dataset = pydicom.dcmread(filelike, defer_size=1024)
        assert 32768 == len(dataset.PixelData)


class TestReadTruncatedFile:
    def testReadFileWithMissingPixelData(self):
        mr = dcmread(truncated_mr_name)
        mr.decode()
        assert "CompressedSamples^MR1" == mr.PatientName
        assert mr.PatientName == mr[0x10, 0x10].value
        DS = pydicom.valuerep.DS

        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([0.3125, 0.3125])
            assert numpy.allclose(mr.PixelSpacing, expected)
        else:
            assert [DS("0.3125"), DS("0.3125")] == mr.PixelSpacing

    @pytest.mark.skipif(
        not have_numpy or have_gdcm_handler,
        reason="Missing numpy or GDCM present",
    )
    def testReadFileWithMissingPixelDataArray(self):
        mr = dcmread(truncated_mr_name)
        mr.decode()
        # Need to escape brackets
        msg = (
            r"The length of the pixel data in the dataset \(8130 bytes\) "
            r"doesn't match the expected length \(8192 bytes\). "
            r"The dataset may be corrupted or there may be an issue with "
            r"the pixel data handler."
        )
        with pytest.raises(ValueError, match=msg):
            mr.pixel_array


class TestFileLike:
    """Test that can read DICOM files with file-like object rather than
    filename
    """

    def test_read_file_given_file_object(self):
        """filereader: can read using already opened file............"""
        f = open(ct_name, "rb")
        ct = dcmread(f)
        # XXX Tests here simply repeat testCT -- perhaps should collapse
        # the code together?
        DS = pydicom.valuerep.DS

        got = ct.ImagePositionPatient
        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([-158.135803, -179.035797, -75.699997])
            assert numpy.allclose(got, expected)
        else:
            expected = [DS("-158.135803"), DS("-179.035797"), DS("-75.699997")]
            assert expected == got

        assert "1.3.6.1.4.1.5962.2" == ct.file_meta.ImplementationClassUID
        value = ct.file_meta[0x2, 0x12].value
        assert ct.file_meta.ImplementationClassUID == value

        assert 128 == ct.Rows
        assert 128 == ct.Columns
        assert 16 == ct.BitsStored
        assert 128 * 128 * 2 == len(ct.PixelData)

        # Should also be able to close the file ourselves without
        # exception raised:
        f.close()

    def test_read_file_given_file_like_object(self):
        """filereader: can read using a file-like (BytesIO) file...."""
        with open(ct_name, "rb") as f:
            file_like = BytesIO(f.read())
        ct = dcmread(file_like)
        # Tests here simply repeat some of testCT test
        got = ct.ImagePositionPatient
        DS = pydicom.valuerep.DS

        if have_numpy and config.use_DS_numpy:
            expected = numpy.array([-158.135803, -179.035797, -75.699997])
            assert numpy.allclose(got, expected)
        else:
            expected = [DS("-158.135803"), DS("-179.035797"), DS("-75.699997")]
            assert expected == got

        assert 128 * 128 * 2 == len(ct.PixelData)

        # Should also be able to close the file ourselves without
        # exception raised:
        file_like.close()


class TestDataElementGenerator:
    """Test filereader.data_element_generator"""

    def test_little_endian_explicit(self):
        """Test reading little endian explicit VR data"""
        # (0010, 0010) PatientName PN 6 ABCDEF
        bytestream = b"\x10\x00\x10\x00" b"PN" b"\x06\x00" b"ABCDEF"
        fp = BytesIO(bytestream)
        # fp, is_implicit_VR, is_little_endian,
        gen = data_element_generator(fp, False, True)
        elem = DataElement(0x00100010, "PN", "ABCDEF")
        assert elem == DataElement_from_raw(next(gen), "ISO_IR 100")

    def test_little_endian_implicit(self):
        """Test reading little endian implicit VR data"""
        # (0010, 0010) PatientName PN 6 ABCDEF
        bytestream = b"\x10\x00\x10\x00" b"\x06\x00\x00\x00" b"ABCDEF"
        fp = BytesIO(bytestream)
        gen = data_element_generator(
            fp, is_implicit_VR=True, is_little_endian=True
        )
        elem = DataElement(0x00100010, "PN", "ABCDEF")
        assert elem == DataElement_from_raw(next(gen), "ISO_IR 100")

    def test_big_endian_explicit(self):
        """Test reading big endian explicit VR data"""
        # (0010, 0010) PatientName PN 6 ABCDEF
        bytestream = b"\x00\x10\x00\x10" b"PN" b"\x00\x06" b"ABCDEF"
        fp = BytesIO(bytestream)
        # fp, is_implicit_VR, is_little_endian,
        gen = data_element_generator(fp, False, False)
        elem = DataElement(0x00100010, "PN", "ABCDEF")
        assert elem == DataElement_from_raw(next(gen), "ISO_IR 100")


def test_read_dicomdir_deprecated():
    """Test deprecation warning for read_dicomdir()."""
    msg = (
        r"'read_dicomdir\(\)' is deprecated and will be removed in v3.0, use "
        r"'dcmread\(\)' instead"
    )
    with pytest.warns(DeprecationWarning, match=msg):
        ds = read_dicomdir(get_testdata_file("DICOMDIR"))


def test_read_file_deprecated():
    """Test deprecation warning for read_file()."""
    if sys.version_info[:2] < (3, 7):
        from pydicom.filereader import read_file
    else:
        msg = (
            r"'read_file' is deprecated and will be removed in v3.0, use "
            r"'dcmread' instead"
        )
        with pytest.warns(DeprecationWarning, match=msg):
            from pydicom.filereader import read_file

    assert read_file == dcmread
