Repository: pydicom/pydicom

Issue:
"Printing" of certain dicom files fails once, but works the second time
<!-- Instructions For Filing a Bug: https://github.com/pydicom/pydicom/blob/master/CONTRIBUTING.md#filing-bugs -->

#### Description
"Printing" of certain dicom files (see [example](https://github.com/pydicom/pydicom/files/2865551/dicom_exception.zip)) fails once, but not the second time

#### Steps/Code to Reproduce
```python
from pydicom import read_file

a = read_file('...')
print(a)
# triggers exception: AttributeError: With tag (0028, 3000) got exception: Failed to resolve ambiguous VR for tag (0028, 3002): 'Dataset' object has no attribute 'PixelRepresentation'

# try same thing again...
print(a)
# just works...
```

#### Versions
Behaviour as described above at least on:
```
Linux-4.18.0-15-generic-x86_64-with-Ubuntu-18.10-cosmic
('Python', '2.7.15+ (default, Oct  2 2018, 22:12:08) \n[GCC 8.2.0]')
('numpy', '1.14.5')
('pydicom', '1.3.0.dev0')
```
and


```
('pydicom', '1.2.2')
```

Works as expected on:
```
Linux-4.18.0-15-generic-x86_64-with-Ubuntu-18.10-cosmic
('Python', '2.7.15+ (default, Oct  2 2018, 22:12:08) \n[GCC 8.2.0]')
('pydicom', '1.0.1')
```


Discussion:
Occurs because Pixel Representation is in the top level-dataset while the ambiguous element is in a sequence.

Regression test:
```python
from pydicom.dataset import Dataset

ds = Dataset()
ds.PixelRepresentation = 0
ds.ModalityLUTSequence = [Dataset()]
ds.ModalityLUTSequence[0].LUTDescriptor = [0, 0, 16]
ds.ModalityLUTSequence[0].LUTExplanation = None
ds.ModalityLUTSequence[0].ModalityLUTType = 'US'  # US = unspecified
ds.ModalityLUTSequence[0].LUTData = b'\x0000\x149a\x1f1c\c2637'

ds.is_little_endian= True
ds.is_implicit_VR = False
ds.save_as('test.dcm')
```

The reason it works the second time is the ambiguous VR correction only gets used during the initial decoding (pydicom uses deferred decoding which is triggered by the first `print()`).

This might be a bit tricky to fix elegantly...
One thing we should probably change is to warn rather than raise if ambiguous correction fails during decoding. Should still raise if occurs during encoding
> This might be a bit tricky to fix elegantly...

Yes... we have to support the cases where the tag needed to resolve the ambiguity is in the sequence item, or in any parent dataset (for nested sequences). Having the parent dataset as a member in the dataset would allow this, but this would also mean, that it has always to be set on creating a sequence item... not sure if this is a good idea. 
Another option is to pass the function a dict/Dataset containing elements required for resolving ambiguity (if they're present)

Patch:
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -197,6 +197,9 @@ def __init__(self, *args, **kwargs):
         self.is_little_endian = None
         self.is_implicit_VR = None
 
+        # the parent data set, if this dataset is a sequence item
+        self.parent = None
+
     def __enter__(self):
         """Method invoked on entry to a with statement."""
         return self
@@ -555,7 +558,13 @@ def __getattr__(self, name):
             # Try the base class attribute getter (fix for issue 332)
             return super(Dataset, self).__getattribute__(name)
         else:
-            return self[tag].value
+            data_elem = self[tag]
+            value = data_elem.value
+            if data_elem.VR == 'SQ':
+                # let a sequence know its parent dataset, as sequence items
+                # may need parent dataset tags to resolve ambiguous tags
+                value.parent = self
+            return value
 
     @property
     def _character_set(self):
@@ -1165,6 +1174,11 @@ def __setattr__(self, name, value):
                 # don't have this tag yet->create the data_element instance
                 VR = dictionary_VR(tag)
                 data_element = DataElement(tag, VR, value)
+                if VR == 'SQ':
+                    # let a sequence know its parent dataset to pass it
+                    # to its items, who may need parent dataset tags
+                    # to resolve ambiguous tags
+                    data_element.parent = self
             else:
                 # already have this data_element, just changing its value
                 data_element = self[tag]
diff --git a/pydicom/filewriter.py b/pydicom/filewriter.py
--- a/pydicom/filewriter.py
+++ b/pydicom/filewriter.py
@@ -69,6 +69,9 @@ def _correct_ambiguous_vr_element(elem, ds, is_little_endian):
         # US if PixelRepresentation value is 0x0000, else SS
         #   For references, see the list at
         #   https://github.com/darcymason/pydicom/pull/298
+        # PixelRepresentation is usually set in the root dataset
+        while 'PixelRepresentation' not in ds and ds.parent:
+            ds = ds.parent
         if ds.PixelRepresentation == 0:
             elem.VR = 'US'
             byte_type = 'H'
diff --git a/pydicom/sequence.py b/pydicom/sequence.py
--- a/pydicom/sequence.py
+++ b/pydicom/sequence.py
@@ -43,6 +43,9 @@ def __init__(self, iterable=None):
         if isinstance(iterable, Dataset):
             raise TypeError('The Sequence constructor requires an iterable')
 
+        # the parent dataset
+        self._parent = None
+
         # If no inputs are provided, we create an empty Sequence
         if not iterable:
             iterable = list()
@@ -50,6 +53,24 @@ def __init__(self, iterable=None):
         # validate_dataset is used as a pseudo type_constructor
         super(Sequence, self).__init__(validate_dataset, iterable)
 
+    @property
+    def parent(self):
+        """Return the parent dataset."""
+        return self._parent
+
+    @parent.setter
+    def parent(self, value):
+        """Set the parent dataset and pass it to all items."""
+        if value != self._parent:
+            self._parent = value
+            for item in self._list:
+                item.parent = self._parent
+
+    def __setitem__(self, i, val):
+        """Set the parent dataset to the new sequence item"""
+        super(Sequence, self).__setitem__(i, val)
+        val.parent = self._parent
+
     def __str__(self):
         """String description of the Sequence."""
         lines = [str(x) for x in self]


Test patch:
diff --git a/pydicom/tests/test_filewriter.py b/pydicom/tests/test_filewriter.py
--- a/pydicom/tests/test_filewriter.py
+++ b/pydicom/tests/test_filewriter.py
@@ -864,6 +864,47 @@ def test_write_new_ambiguous(self):
         assert ds[0x00283002].VR == 'US'
         assert ds.LUTDescriptor == [1, 0]
 
+    def test_ambiguous_element_in_sequence_explicit(self):
+        """Test that writing a sequence with an ambiguous element
+        as explicit transfer syntax works."""
+        # regression test for #804
+        ds = Dataset()
+        ds.PixelRepresentation = 0
+        ds.ModalityLUTSequence = [Dataset()]
+        ds.ModalityLUTSequence[0].LUTDescriptor = [0, 0, 16]
+        ds.ModalityLUTSequence[0].LUTExplanation = None
+        ds.ModalityLUTSequence[0].ModalityLUTType = 'US'  # US = unspecified
+        ds.ModalityLUTSequence[0].LUTData = b'\x0000\x149a\x1f1c\xc2637'
+
+        ds.is_little_endian = True
+        ds.is_implicit_VR = False
+        fp = BytesIO()
+        ds.save_as(fp, write_like_original=True)
+
+        ds = dcmread(fp, force=True)
+        assert 'US' == ds.ModalityLUTSequence[0][0x00283002].VR
+
+    def test_ambiguous_element_in_sequence_implicit(self):
+        """Test that reading a sequence with an ambiguous element
+        from a file with implicit transfer syntax works."""
+        # regression test for #804
+        ds = Dataset()
+        ds.PixelRepresentation = 0
+        ds.ModalityLUTSequence = [Dataset()]
+        ds.ModalityLUTSequence[0].LUTDescriptor = [0, 0, 16]
+        ds.ModalityLUTSequence[0].LUTExplanation = None
+        ds.ModalityLUTSequence[0].ModalityLUTType = 'US'  # US = unspecified
+        ds.ModalityLUTSequence[0].LUTData = b'\x0000\x149a\x1f1c\xc2637'
+
+        ds.is_little_endian = True
+        ds.is_implicit_VR = True
+        fp = BytesIO()
+        ds.save_as(fp, write_like_original=True)
+        ds = dcmread(fp, force=True)
+        # we first have to access the value to trigger correcting the VR
+        assert 16 == ds.ModalityLUTSequence[0].LUTDescriptor[2]
+        assert 'US' == ds.ModalityLUTSequence[0][0x00283002].VR
+
 
 class TestCorrectAmbiguousVRElement(object):
     """Test filewriter.correct_ambiguous_vr_element"""