Repository: pydicom/pydicom

Issue:
Error when using pickle on a dataset
Hi,
I am using python 3.6.8, with Pydicom version 1.3.0, on Windows 10.
I have an error when I try to save and load a dataset using the pickle format.
I manage (no error) to save the dataset using pickle.dump() function, but I get an error when I try to load it. Here is the code:

````python
import pydicom
import pickle

dcmname='C:\\toto\IM-0511-0001.dcm' # path to the DICOM file
ds=pydicom.dcmread(dcmname) # load the DICOM file and obtain a dataset

filename='C:\\toto\dcm.pkl' # Name of the file where the pickle data will be saved
f=open(filename,'wb'); # open the file
pickle.dump({'ds': ds}, f); # save the dataset
f.close(); # close the file

f=open(filename,'rb'); # open the file
obj=pickle.load(f); # load the pickle file
f.close(); # close the file
````
I get the following error
````python
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\toto\.conda\envs\marvelous\lib\site-packages\pydicom\dataset.py", line 1649, in __setitem__
    self._dict[tag] = data_element
  File "C:\toto\.conda\envs\my_env\lib\site-packages\pydicom\dataset.py", line 741, in __getattr__
    return object.__getattribute__(self, name)
AttributeError: 'Dataset' object has no attribute '_dict'
````
I found a workaround by setting the [protocol ](https://docs.python.org/3.6/library/pickle.html#pickle.DEFAULT_PROTOCOL) option of the `pickle.dump()` function to a value lower or equal than 1:
````python
import pydicom
import pickle

dcmname='C:\\toto\IM-0511-0001.dcm' # path to the DICOM file
ds=pydicom.dcmread(dcmname) # load the DICOM file and obtain a dataset

filename='C:\\toto\dcm.pkl' # Name of the file where the pickle data will be saved
f=open(filename,'wb'); # open the file
pickle.dump({'ds': ds}, f, protocol=1); # save the dataset
f.close(); # close the file

f=open(filename,'rb'); # open the file
obj=pickle.load(f); # load the pickle file
f.close(); # close the file
````
Other protocol value does not work (3, the default value, and 4), while I would like to continue to be able to use the default value of the protocol.

I think that the problem was not existing on previous release of Pydicom, so I guess that it appeared with version 1.3.0. 

I am sorry that I cannot share a DICOM file with you, but I have the problem with all my dicom files. I would be interested to know if someone else experienced the same problem.

Thanks!



Discussion:

Patch:
diff --git a/doc/release_notes/v1.4.0.rst b/doc/release_notes/v1.4.0.rst
index 34d665f06..948b19df5 100644
--- a/doc/release_notes/v1.4.0.rst
+++ b/doc/release_notes/v1.4.0.rst
@@ -14,6 +14,7 @@ Fixes
 * Fixed handling of data too large to written in explicit transfer syntax
 * Fixed handling of known tags with VR UN (:issue:`899`)
 * Fixed assigning of empty values to data elements (:issue:`896`)
+* Fixed error in unpickling dataset (:issue:`947`)
 
 Enhancements
 ............
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
index 3c5c6d2f2..2c631c8e2 100644
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -766,21 +766,23 @@ class Dataset(dict):
               value (if present).
         """
         tag = tag_for_keyword(name)
-        if tag is None:  # `name` isn't a DICOM element keyword
-            # Try the base class attribute getter (fix for issue 332)
-            return object.__getattribute__(self, name)
-        tag = Tag(tag)
-        if tag not in self._dict:  # DICOM DataElement not in the Dataset
-            # Try the base class attribute getter (fix for issue 332)
-            return object.__getattribute__(self, name)
-        else:
-            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
+        if tag is not None:  # `name` isn't a DICOM element keyword
+            tag = Tag(tag)
+            if tag in self._dict:  # DICOM DataElement not in the Dataset
+                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
+
+        # no tag or tag not contained in the dataset
+        if name == '_dict':
+            # special handling for contained dict, needed for pickle
+            return {}
+        # Try the base class attribute getter (fix for issue 332)
+        return object.__getattribute__(self, name)
 
     @property
     def _character_set(self):


Test patch:
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index 491f0b5e2..2ac9f066d 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -1382,6 +1382,14 @@ class TestFileDataset(object):
     def setup(self):
         self.test_file = get_testdata_files('CT_small.dcm')[0]
 
+    def test_pickle(self):
+        ds = pydicom.dcmread(self.test_file)
+        import pickle
+        s = pickle.dumps({'ds': ds})
+        ds1 = pickle.loads(s)['ds']
+        assert ds == ds1
+        assert ds1.Modality == 'CT'
+
     def test_equality_file_meta(self):
         """Dataset: equality returns correct value if with metadata"""
         d = dcmread(self.test_file)