Repository: pydicom/pydicom

Issue:
Keeping private element name during an update
Disclaimer - I'm new to dicom and pydicom and my issue could be just a rookie mistake/misuse of the package. 

Using PrivateBlock.add_new method to update an existing private element value leads to an omission of the descriptive element name in favor of a general, non descriptive name ("Private tag data", see code example below). 
The same general name is given when adding a new element (despite the fact that the proper name exists in the private dictionary after the application of add_private_dict_entries).

I do not see a reason to change element name at all during private element update. At the very least, there should be a way to update private element value without touching the name (maybe there is and I'm just not familiar with it?). 
As for new private elements, their name should fit their name in the private dictionary, if available. But I'm aware this might be far more complicated than not omitting existing element names.  

Code example:    

```
from pydicom import dcmread
from pydicom.data import get_testdata_files


ct_filename = get_testdata_files("CT_small")[0]
ds = dcmread(ct_filename)
block = ds.private_block(0x11, 'GEMS_PATI_01', create=True)
print(ds[0x11, 0x1010])
block.add_new(0x10, 'SS', 1)
print(ds[0x11, 0x1010])
```

Output:
```
(0011, 1010) [Patient Status]                    SS: 0
(0011, 1010) Private tag data                    SS: 1
```

Discussion:

Patch:
diff --git a/doc/release_notes/v2.0.0.rst b/doc/release_notes/v2.0.0.rst
index 4c137b142..466b1554c 100644
--- a/doc/release_notes/v2.0.0.rst
+++ b/doc/release_notes/v2.0.0.rst
@@ -31,3 +31,4 @@ Fixes
   :func:`~pydicom.pixel_data_handlers.util.apply_modality_lut` not handling
   (0028,3006) *LUT Data* with a VR of **OW** (:issue:`1073`)
 * Fixed access to private creator tag in raw datasets (:issue:`1078`)
+* Fixed description of newly added known private tag (:issue:`1082`)
diff --git a/pydicom/dataelem.py b/pydicom/dataelem.py
index 2387c3c2b..ee89d2996 100644
--- a/pydicom/dataelem.py
+++ b/pydicom/dataelem.py
@@ -14,7 +14,6 @@ import warnings
 from collections import namedtuple
 
 from pydicom import config  # don't import datetime_conversion directly
-from pydicom.charset import default_encoding
 from pydicom.config import logger
 from pydicom.datadict import (dictionary_has_tag, dictionary_description,
                               dictionary_keyword, dictionary_is_retired,
@@ -144,30 +143,13 @@ class DataElement(object):
     descripWidth : int
         For string display, this is the maximum width of the description
         field (default ``35``).
-    is_retired : bool
-        For officially registered DICOM Data Elements this will be ``True`` if
-        the retired status as given in the DICOM Standard, Part 6,
-        :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>` is 'RET'. For private
-        or unknown elements this will always be ``False``.
     is_undefined_length : bool
         Indicates whether the length field for the element was ``0xFFFFFFFFL``
         (ie undefined).
-    keyword : str
-        For officially registered DICOM Data Elements this will be the
-        *Keyword* as given in
-        :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>`. For private or
-        unknown elements this will return an empty string ``''``.
     maxBytesToDisplay : int
         For string display, elements with values containing data which is
         longer than this value will display ``"array of # bytes"``
         (default ``16``).
-    name : str
-        For officially registered DICOM Data Elements this will be the *Name*
-        as given in :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>`.
-        For private elements known to *pydicom*
-        this will be the *Name* in the format ``'[name]'``. For unknown
-        private elements this will be ``'Private Creator'``. For unknown
-        elements this will return an empty string ``''``.
     showVR : bool
         For string display, include the element's VR just before it's value
         (default ``True``).
@@ -175,8 +157,6 @@ class DataElement(object):
         The element's tag.
     value
         The element's stored value(s).
-    VM : int
-        The Value Multiplicity of the element's stored value(s).
     VR : str
         The element's Value Representation.
     """
@@ -244,6 +224,7 @@ class DataElement(object):
             self.value = value  # calls property setter which will convert
         self.file_tell = file_value_tell
         self.is_undefined_length = is_undefined_length
+        self.private_creator = None
 
     @classmethod
     def from_json(cls, dataset_class, tag, vr, value, value_key,
@@ -616,14 +597,22 @@ class DataElement(object):
 
     @property
     def name(self):
-        """Return the DICOM dictionary name for the element as :class:`str`."""
+        """Return the DICOM dictionary name for the element as :class:`str`.
+
+        For officially registered DICOM Data Elements this will be the *Name*
+        as given in :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>`.
+        For private elements known to *pydicom*
+        this will be the *Name* in the format ``'[name]'``. For unknown
+        private elements this will be ``'Private Creator'``. For unknown
+        elements this will return an empty string ``''``.
+        """
         return self.description()
 
     def description(self):
         """Return the DICOM dictionary name for the element as :class:`str`."""
         if self.tag.is_private:
             name = "Private tag data"  # default
-            if hasattr(self, 'private_creator'):
+            if self.private_creator:
                 try:
                     # If have name from private dictionary, use it, but
                     #   but put in square brackets so is differentiated,
@@ -633,7 +622,7 @@ class DataElement(object):
                     name = "[%s]" % (name)
                 except KeyError:
                     pass
-            elif self.tag.elem >> 8 == 0:
+            elif self.tag.element >> 8 == 0:
                 name = "Private Creator"
         elif dictionary_has_tag(self.tag) or repeater_has_tag(self.tag):
             name = dictionary_description(self.tag)
@@ -647,7 +636,13 @@ class DataElement(object):
 
     @property
     def is_retired(self):
-        """Return the element's retired status as :class:`bool`."""
+        """Return the element's retired status as :class:`bool`.
+
+        For officially registered DICOM Data Elements this will be ``True`` if
+        the retired status as given in the DICOM Standard, Part 6,
+        :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>` is 'RET'. For private
+        or unknown elements this will always be ``False``.
+        """
         if dictionary_has_tag(self.tag):
             return dictionary_is_retired(self.tag)
         else:
@@ -655,7 +650,13 @@ class DataElement(object):
 
     @property
     def keyword(self):
-        """Return the element's keyword (if known) as :class:`str`."""
+        """Return the element's keyword (if known) as :class:`str`.
+
+        For officially registered DICOM Data Elements this will be the
+        *Keyword* as given in
+        :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>`. For private or
+        unknown elements this will return an empty string ``''``.
+        """
         if dictionary_has_tag(self.tag):
             return dictionary_keyword(self.tag)
         else:
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
index de741cb89..ec8f68196 100644
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -22,7 +22,6 @@ from itertools import takewhile
 import json
 import os
 import os.path
-import warnings
 
 import pydicom  # for dcmwrite
 import pydicom.charset
@@ -181,7 +180,9 @@ class PrivateBlock(object):
             The value of the data element. See :meth:`Dataset.add_new()`
             for a description.
         """
-        self.dataset.add_new(self.get_tag(element_offset), VR, value)
+        tag = self.get_tag(element_offset)
+        self.dataset.add_new(tag, VR, value)
+        self.dataset[tag].private_creator = self.private_creator
 
 
 def _dict_equal(a, b, exclude=None):
@@ -666,7 +667,7 @@ class Dataset(dict):
 
         Parameters
         ----------
-        key : str or int or BaseTag
+        key : str or int or Tuple[int, int] or BaseTag
             The element keyword or tag or the class attribute name to get.
         default : obj or None, optional
             If the element or class attribute is not present, return
@@ -892,9 +893,8 @@ class Dataset(dict):
 
         Returns
         -------
-        int
-            Element base for the given tag (the last 2 hex digits are always 0)
-            as a 32-bit :class:`int`.
+        PrivateBlock
+            The existing or newly created private block.
 
         Raises
         ------
@@ -1243,7 +1243,7 @@ class Dataset(dict):
 
         Returns
         -------
-        type
+        DataElement or type
             The data element for `key` if it exists, or the default value if
             it is a :class:`~pydicom.dataelem.DataElement` or
             ``None``, or a :class:`~pydicom.dataelem.DataElement`
@@ -1815,7 +1815,7 @@ class Dataset(dict):
 
         Parameters
         ----------
-        key : int
+        key : int or Tuple[int, int] or str
             The tag for the element to be added to the Dataset.
         value : dataelem.DataElement or dataelem.RawDataElement
             The element to add to the :class:`Dataset`.
diff --git a/pydicom/filewriter.py b/pydicom/filewriter.py
index 63dd88cf4..dab042a7e 100644
--- a/pydicom/filewriter.py
+++ b/pydicom/filewriter.py
@@ -856,7 +856,7 @@ def dcmwrite(filename, dataset, write_like_original=True):
 
     See Also
     --------
-    pydicom.dataset.FileDataset
+    pydicom.dataset.Dataset
         Dataset class with relevant attributes and information.
     pydicom.dataset.Dataset.save_as
         Write a DICOM file from a dataset that was read in with ``dcmread()``.


Test patch:
diff --git a/pydicom/tests/test_dataelem.py b/pydicom/tests/test_dataelem.py
index 2ed1cd468..e917d9088 100644
--- a/pydicom/tests/test_dataelem.py
+++ b/pydicom/tests/test_dataelem.py
@@ -104,7 +104,7 @@ class TestDataElement(object):
         assert 'Private tag data' == elem.description()
         elem = DataElement(0x00110F00, 'LO', 12345)
         assert elem.tag.is_private
-        assert not hasattr(elem, 'private_creator')
+        assert elem.private_creator is None
         assert 'Private tag data' == elem.description()
 
     def test_description_unknown(self):
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index daad7f952..78e2083d1 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -4,7 +4,7 @@
 import pytest
 
 import pydicom
-from pydicom.data import get_testdata_files
+from pydicom.data import get_testdata_file
 from pydicom.dataelem import DataElement, RawDataElement
 from pydicom.dataset import Dataset, FileDataset, validate_file_meta
 from pydicom.encaps import encapsulate
@@ -793,7 +793,7 @@ class TestDataset(object):
         assert '1.2.3.4' == ds.get_item(0x00080018).value
 
         # Test deferred read
-        test_file = get_testdata_files('MR_small.dcm')[0]
+        test_file = get_testdata_file('MR_small.dcm')
         ds = dcmread(test_file, force=True, defer_size='0.8 kB')
         ds_ref = dcmread(test_file, force=True)
         # get_item will follow the deferred read branch
@@ -957,7 +957,7 @@ class TestDataset(object):
 
     def test_private_creator_from_raw_ds(self):
         # regression test for #1078
-        ct_filename = get_testdata_files("CT_small")[0]
+        ct_filename = get_testdata_file("CT_small.dcm")
         ds = dcmread(ct_filename)
         ds.private_block(0x11, 'GEMS_PATI_01', create=True)
         assert ['GEMS_PATI_01'] == ds.private_creators(0x11)
@@ -966,6 +966,15 @@ class TestDataset(object):
         ds.private_block(0x13, 'GEMS_PATI_01', create=True)
         assert ['GEMS_PATI_01'] == ds.private_creators(0x13)
 
+    def test_add_known_private_tag(self):
+        # regression test for #1082
+        ds = dcmread(get_testdata_file("CT_small.dcm"))
+        assert '[Patient Status]' == ds[0x11, 0x1010].name
+
+        block = ds.private_block(0x11, 'GEMS_PATI_01')
+        block.add_new(0x10, 'SS', 1)
+        assert '[Patient Status]' == ds[0x11, 0x1010].name
+
     def test_add_new_private_tag(self):
         ds = Dataset()
         ds.add_new(0x00080005, 'CS', 'ISO_IR 100')
@@ -1256,7 +1265,7 @@ class TestDataset(object):
 
     def test_with(self):
         """Test Dataset.__enter__ and __exit__."""
-        test_file = get_testdata_files('CT_small.dcm')[0]
+        test_file = get_testdata_file('CT_small.dcm')
         with dcmread(test_file) as ds:
             assert 'CompressedSamples^CT1' == ds.PatientName
 
@@ -1273,7 +1282,7 @@ class TestDataset(object):
     def test_pixel_array_already_have(self):
         """Test Dataset._get_pixel_array when we already have the array"""
         # Test that _pixel_array is returned unchanged unless required
-        fpath = get_testdata_files("CT_small.dcm")[0]
+        fpath = get_testdata_file("CT_small.dcm")
         ds = dcmread(fpath)
         ds._pixel_id = get_image_pixel_ids(ds)
         ds._pixel_array = 'Test Value'
@@ -1283,7 +1292,7 @@ class TestDataset(object):
 
     def test_pixel_array_id_changed(self):
         """Test that we try to get new pixel data if the id has changed."""
-        fpath = get_testdata_files("CT_small.dcm")[0]
+        fpath = get_testdata_file("CT_small.dcm")
         ds = dcmread(fpath)
         ds.file_meta.TransferSyntaxUID = '1.2.3.4'
         ds._pixel_id = 1234
@@ -1299,7 +1308,7 @@ class TestDataset(object):
 
     def test_pixel_array_unknown_syntax(self):
         """Test that pixel_array for an unknown syntax raises exception."""
-        ds = dcmread(get_testdata_files("CT_small.dcm")[0])
+        ds = dcmread(get_testdata_file("CT_small.dcm"))
         ds.file_meta.TransferSyntaxUID = '1.2.3.4'
         msg = (
             r"Unable to decode pixel data with a transfer syntax UID of "
@@ -1338,7 +1347,7 @@ class TestDataset(object):
 
     def test_set_convert_private_elem_from_raw(self):
         """Test Dataset.__setitem__ with a raw private element"""
-        test_file = get_testdata_files('CT_small.dcm')[0]
+        test_file = get_testdata_file('CT_small.dcm')
         ds = dcmread(test_file, force=True)
         # 'tag VR length value value_tell is_implicit_VR is_little_endian'
         elem = RawDataElement((0x0043, 0x1029), 'OB', 2, b'\x00\x01', 0,
@@ -1359,7 +1368,7 @@ class TestDataset(object):
 
     def test_trait_names(self):
         """Test Dataset.trait_names contains element keywords"""
-        test_file = get_testdata_files('CT_small.dcm')[0]
+        test_file = get_testdata_file('CT_small.dcm')
         ds = dcmread(test_file, force=True)
         names = ds.trait_names()
         assert 'PatientName' in names
@@ -1513,7 +1522,7 @@ class TestDatasetElements(object):
 
 class TestFileDataset(object):
     def setup(self):
-        self.test_file = get_testdata_files('CT_small.dcm')[0]
+        self.test_file = get_testdata_file('CT_small.dcm')
 
     def test_pickle(self):
         ds = pydicom.dcmread(self.test_file)
@@ -1598,7 +1607,7 @@ class TestDatasetOverlayArray(object):
         pydicom.config.overlay_data_handlers = [NP_HANDLER]
 
         self.ds = dcmread(
-            get_testdata_files("MR-SIEMENS-DICOM-WithOverlays.dcm")[0]
+            get_testdata_file("MR-SIEMENS-DICOM-WithOverlays.dcm")
         )
 
         class DummyHandler(object):