Repository: pydicom/pydicom

Issue:
Speed up processing DS when handling structure sets.
code to extract all the coordinates from a structure set.
```python
import pydicom
import numpy as np
import cProfile
import pstats


def parse_coords(contour):
    """Given a contour from a DICOM ROIContourSequence, returns coordinates
    [loop][[x0, x1, x2, ...][y0, y1, y2, ...][z0, z1, z2, ...]]"""
    if not hasattr(contour, "ContourSequence"):
        return [] # empty structure
    def _reshape_contour_data(loop):
        return np.reshape(np.array(loop.ContourData),
                          (3, len(loop.ContourData) // 3),
                          order='F')
    return list(map(_reshape_contour_data,contour.ContourSequence))


def profile_load_contours():
    rs = pydicom.dcmread('RS.gyn1.dcm')
    structs = [parse_coords(contour) for contour in rs.ROIContourSequence]


cProfile.run('profile_load_contours()','prof.stats')
p = pstats.Stats('prof.stats')
p.sort_stats('cumulative').print_stats(30)
```
Run using a real structure set from Varian Eclipse.
```
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   12.165   12.165 {built-in method builtins.exec}
        1    0.151    0.151   12.165   12.165 <string>:1(<module>)
        1    0.000    0.000   12.014   12.014 load_contour_time.py:19(profile_load_contours)
        1    0.000    0.000   11.983   11.983 load_contour_time.py:21(<listcomp>)
       56    0.009    0.000   11.983    0.214 load_contour_time.py:7(parse_coords)
50745/33837    0.129    0.000   11.422    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataset.py:455(__getattr__)
50741/33825    0.152    0.000   10.938    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataset.py:496(__getitem__)
    16864    0.069    0.000    9.839    0.001 load_contour_time.py:12(_reshape_contour_data)
    16915    0.101    0.000    9.780    0.001 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataelem.py:439(DataElement_from_raw)
    16915    0.052    0.000    9.300    0.001 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/values.py:320(convert_value)
    16864    0.038    0.000    7.099    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/values.py:89(convert_DS_string)
    16870    0.042    0.000    7.010    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/valuerep.py:495(MultiString)
    16908    1.013    0.000    6.826    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/multival.py:29(__init__)
  3004437    3.013    0.000    5.577    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/multival.py:42(number_string_type_constructor)
3038317/3038231    1.037    0.000    3.171    0.000 {built-in method builtins.hasattr}

```
Getting the raw DS from the `Dataset` parent class and parsing with `numpy.fromstring` gives better than 4 times speedup.
```python
import pydicom
import numpy as np
import cProfile
import pstats


def parse_coords(contour):
    """Given a contour from a DICOM ROIContourSequence, returns coordinates
    [loop][[x0, x1, x2, ...][y0, y1, y2, ...][z0, z1, z2, ...]]"""
    if not hasattr(contour, "ContourSequence"):
        return [] # empty structure
    cd_tag = pydicom.tag.Tag(0x3006, 0x0050) # ContourData tag
    def _reshape_contour_data(loop):
        val = super(loop.__class__, loop).__getitem__(cd_tag).value
        try:
            double_string = val.decode(encoding='utf-8')
            double_vec = np.fromstring(double_string, dtype=float, sep=chr(92)) # 92 is '/'
        except AttributeError: # 'MultiValue'  has no 'decode' (bytes does)
            # It's already been converted to doubles and cached
            double_vec = loop.ContourData
        return np.reshape(np.array(double_vec),
                          (3, len(double_vec) // 3),
                          order='F')
    return list(map(_reshape_contour_data, contour.ContourSequence))


def profile_load_contours():
    rs = pydicom.dcmread('RS.gyn1.dcm')
    structs = [parse_coords(contour) for contour in rs.ROIContourSequence]


profile_load_contours()
cProfile.run('profile_load_contours()','prof.stats')
p = pstats.Stats('prof.stats')
p.sort_stats('cumulative').print_stats(15)
```
Profile result
```
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.800    2.800 {built-in method builtins.exec}
        1    0.017    0.017    2.800    2.800 <string>:1(<module>)
        1    0.000    0.000    2.783    2.783 load_contour_time3.py:29(profile_load_contours)
        1    0.000    0.000    2.761    2.761 load_contour_time3.py:31(<listcomp>)
       56    0.006    0.000    2.760    0.049 load_contour_time3.py:9(parse_coords)
  153/109    0.001    0.000    2.184    0.020 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataset.py:455(__getattr__)
   149/97    0.001    0.000    2.182    0.022 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataset.py:496(__getitem__)
       51    0.000    0.000    2.178    0.043 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataelem.py:439(DataElement_from_raw)
       51    0.000    0.000    2.177    0.043 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/values.py:320(convert_value)
       44    0.000    0.000    2.176    0.049 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/values.py:255(convert_SQ)
       44    0.035    0.001    2.176    0.049 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/filereader.py:427(read_sequence)
   152/66    0.000    0.000    2.171    0.033 {built-in method builtins.hasattr}
    16920    0.147    0.000    1.993    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/filereader.py:452(read_sequence_item)
    16923    0.116    0.000    1.267    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/filereader.py:365(read_dataset)
    84616    0.113    0.000    0.699    0.000 /home/cf/python/venv/lib/python3.5/site-packages/pydicom/dataset.py:960(__setattr__)

```
If there is interest I could do a pull request. Perhaps use this principle in `Dataset` with a method specifically for DS.


Discussion:
pep8speaks: Hello @darcymason! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:











* In the file [`pydicom/tests/test_filereader.py`](https://github.com/pydicom/pydicom/blob/b391cf1719b0ad5b813b06265449dd7c7cd88b5a/pydicom/tests/test_filereader.py):

> [Line 1245:35](https://github.com/pydicom/pydicom/blob/b391cf1719b0ad5b813b06265449dd7c7cd88b5a/pydicom/tests/test_filereader.py#L1245): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 1269:35](https://github.com/pydicom/pydicom/blob/b391cf1719b0ad5b813b06265449dd7c7cd88b5a/pydicom/tests/test_filereader.py#L1269): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 1274:37](https://github.com/pydicom/pydicom/blob/b391cf1719b0ad5b813b06265449dd7c7cd88b5a/pydicom/tests/test_filereader.py#L1274): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 1275:1](https://github.com/pydicom/pydicom/blob/b391cf1719b0ad5b813b06265449dd7c7cd88b5a/pydicom/tests/test_filereader.py#L1275): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace






darcymason: > Defaulted conversion to False in config to start with - many existing tests failed with errors with numpy arrays. Checking a result vs. a list, e.g. `ds.PixelSpacing == [1.0, 1.0]` fails due to numpy creating a boolean array, so have to be wrapped in `all(...)`

@mrbean-bremen, @scaramallion, @bastula, any others: I'm interested in your thoughts about defaulting to False for using numpy, given the breaking code mentioned above.  The only real advantage of numpy is for fairly large arrays.  For users with large arrays and need for more speed, they can turn on the config option.
mrbean-bremen: About defaulting to False - I completely agree. If not working with pixel data (where you always need NumPy), this is quite a special case that not so many people will need. I have seen the need for this with large RTSTRUCT images, and there may be a few more cases, but it is a small part of use cases nontheless.
bastula: I also agree with defaulting to False. This is a code path that should be explicitly be turned on by user land code if one chooses to enable speedups. For the majority of cases, it's not worth breaking historical pydicom conventions.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 9420d4cae..86db59e9a 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -19,9 +19,19 @@ How to reproduce the issue. Please include a minimum working code sample, the
 traceback (if any) and the anonymized DICOM dataset (if relevant).
 
 **Your environment**
-Please run the following and paste the output.
+If you're using **pydicom 2 or later**, please use the `pydicom.env_info`
+module to gather information about your environment and paste it in the issue:
+
 ```bash
-$ python -c "import platform; print(platform.platform())"
-$ python -c "import sys; print('Python ', sys.version)"
-$ python -c "import pydicom; print('pydicom ', pydicom.__version__)"
+$ python -m pydicom.env_info
 ```
+
+For **pydicom 1.x**, please run the following code snippet and paste the
+output.
+
+```python
+import platform, sys, pydicom
+print(platform.platform(),
+      "\nPython", sys.version,
+      "\npydicom", pydicom.__version__)
+```
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/pixel_issue.md b/.github/ISSUE_TEMPLATE/pixel_issue.md
index 4467dd4cb..adad148c2 100644
--- a/.github/ISSUE_TEMPLATE/pixel_issue.md
+++ b/.github/ISSUE_TEMPLATE/pixel_issue.md
@@ -26,9 +26,19 @@ How to reproduce the issue. Please include:
 4. The anonymized DICOM dataset (if possible).
 
 **Your environment**
-Please run the following and paste the output.
+If you're using **pydicom 2 or later**, please use the `pydicom.env_info`
+module to gather information about your environment and paste it in the issue:
+
 ```bash
-$ python -c "import platform; print(platform.platform())"
-$ python -c "import sys; print('Python ', sys.version)"
-$ python -c "import pydicom; print('pydicom ', pydicom.__version__)"
+$ python -m pydicom.env_info
+```
+
+For **pydicom 1.x**, please run the following code snippet and paste the
+output.
+
+```python
+import platform, sys, pydicom
+print(platform.platform(),
+      "\nPython", sys.version,
+      "\npydicom", pydicom.__version__)
 ```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c84ced61e..884d89512 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -183,14 +183,23 @@ following rules before submitting:
    See [Creating and highlighting code blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks).
 
 -  Please include your operating system type and version number, as well
-   as your Python, pydicom and numpy versions. This information
-   can be found by running the following code snippet:
+   as your Python and pydicom versions.
 
-  ```python
-  import platform; print(platform.platform())
-  import sys; print("Python", sys.version)
-  import pydicom; print("pydicom", pydicom.__version__)
-  ```
+   If you're using **pydicom 2 or later**, please use the `pydicom_env_info`
+   module to gather this information :
+
+   ```bash
+   $ python -m pydicom.env_info
+   ```
+
+   For **pydicom 1.x**, please run the following code snippet instead.
+
+   ```python
+   import platform, sys, pydicom
+   print(platform.platform(),
+         "\nPython", sys.version,
+         "\npydicom", pydicom.__version__)
+   ```
 
 -  please include a [reproducible](http://stackoverflow.com/help/mcve) code
    snippet or link to a [gist](https://gist.github.com). If an exception is
diff --git a/doc/reference/config.rst b/doc/reference/config.rst
index d308ba0e4..b67ee4b8f 100644
--- a/doc/reference/config.rst
+++ b/doc/reference/config.rst
@@ -19,5 +19,8 @@ Configuration Options (:mod:`pydicom.config`)
    overlay_data_handlers
    pixel_data_handlers
    reset_data_element_callback
-   use_DS_decimal
    DS_decimal
+   DS_numpy
+   use_DS_decimal
+   use_IS_numpy
+   use_DS_numpy
diff --git a/doc/release_notes/v2.0.0.rst b/doc/release_notes/v2.0.0.rst
index ae211abd9..5905300a2 100644
--- a/doc/release_notes/v2.0.0.rst
+++ b/doc/release_notes/v2.0.0.rst
@@ -16,6 +16,10 @@ Enhancements
   Deflated Explicit VR Little Endian transfer syntax UID (:issue:`1086`)
 * Added `config.replace_un_with_known_vr` to be able to switch off automatic
   VR conversion for known tags with VR "UN" (see :issue:`1067`)
+* Added `config.use_DS_numpy` and `config.use_IS_numpy` to have multi-valued
+  data elements with VR of **DS** or **IS** return a numpy array (:issue:`623`)
+  (much faster for bigger arrays).  Both default to False to preserve previous
+  behavior
 
 Fixes
 .....
@@ -36,5 +40,6 @@ Fixes
   (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`)
+* Fixed update of private blocks after deleting private creator (:issue:`1097`)
 * Fixed bug in updating `pydicom.config.use_DS_Decimal` flag
   in :func:`~pydicom.config.DS_decimal`
diff --git a/pydicom/config.py b/pydicom/config.py
index d9e332aae..beccd68c4 100644
--- a/pydicom/config.py
+++ b/pydicom/config.py
@@ -5,6 +5,14 @@
 
 import logging
 
+
+have_numpy = True
+try:
+    import numpy
+except ImportError:
+    have_numpy = False
+
+
 # Set the type used to hold DS values
 #    default False; was decimal-based in pydicom 0.9.7
 use_DS_decimal = False
@@ -38,6 +46,36 @@ def reset_data_element_callback():
     data_element_callback_kwargs = {}
 
 
+def DS_numpy(use_numpy=True):
+    """Set whether multi-valued elements with VR of **DS** will be numpy arrays
+
+    .. versionadded:: 2.0
+
+    Parameters
+    ----------
+    use_numpy : bool, optional
+        ``True`` (default) to read multi-value **DS** elements
+        as :class:`~numpy.ndarray`, ``False`` to read multi-valued **DS**
+        data elements as type :class:`~python.mulitval.MultiValue`
+
+        Note: once a value has been accessed, changing this setting will
+        no longer change its type
+
+    Raises
+    ------
+    ValueError
+        If :data:`use_DS_decimal` and `use_numpy` are both True.
+
+    """
+
+    global use_DS_numpy
+
+    if use_DS_decimal and use_numpy:
+        raise ValueError("Cannot use numpy arrays to read DS elements"
+                         "if `use_DS_decimal` is True")
+    use_DS_numpy = use_numpy
+
+
 def DS_decimal(use_Decimal_boolean=True):
     """Set DS class to be derived from :class:`decimal.Decimal` or
     :class:`float`.
@@ -47,13 +85,24 @@ def DS_decimal(use_Decimal_boolean=True):
 
     Parameters
     ----------
-    use_Decimal_boolean : bool
-        ``True`` to derive :class:`~pydicom.valuerep.DS` from
+    use_Decimal_boolean : bool, optional
+        ``True`` (default) to derive :class:`~pydicom.valuerep.DS` from
         :class:`decimal.Decimal`, ``False`` to derive it from :class:`float`.
+
+    Raises
+    ------
+    ValueError
+        If `use_Decimal_boolean` and :data:`use_DS_numpy` are
+        both ``True``.
     """
     global use_DS_decimal
 
     use_DS_decimal = use_Decimal_boolean
+
+    if use_DS_decimal and use_DS_numpy:
+        raise ValueError("Cannot set use_DS_decimal True "
+                         "if use_DS_numpy is True")
+
     import pydicom.valuerep
     if use_DS_decimal:
         pydicom.valuerep.DSclass = pydicom.valuerep.DSdecimal
@@ -62,6 +111,21 @@ def DS_decimal(use_Decimal_boolean=True):
 
 
 # Configuration flags
+use_DS_numpy = False
+"""Set using the function :func:`~pydicom.config.DS_numpy` to control
+whether arrays of VR **DS** are returned as numpy arrays.
+Default: ``False``.
+
+.. versionadded:: 2.0
+"""
+
+use_IS_numpy = False
+"""Set to False to avoid IS values being returned as numpy ndarray objects.
+Default: ``False``.
+
+.. versionadded:: 2.0
+"""
+
 allow_DS_float = False
 """Set to ``True`` to allow :class:`~pydicom.valuerep.DSdecimal`
 instances to be created using :class:`floats<float>`; otherwise, they must be
diff --git a/pydicom/dataelem.py b/pydicom/dataelem.py
index 7edc6745b..735911e96 100644
--- a/pydicom/dataelem.py
+++ b/pydicom/dataelem.py
@@ -15,6 +15,7 @@ from collections import namedtuple
 
 from pydicom import config  # don't import datetime_conversion directly
 from pydicom.config import logger
+from pydicom import config
 from pydicom.datadict import (dictionary_has_tag, dictionary_description,
                               dictionary_keyword, dictionary_is_retired,
                               private_dictionary_description, dictionary_VR,
@@ -27,6 +28,9 @@ from pydicom import jsonrep
 import pydicom.valuerep  # don't import DS directly as can be changed by config
 from pydicom.valuerep import PersonName
 
+if config.have_numpy:
+    import numpy
+
 BINARY_VR_VALUES = [
     'US', 'SS', 'UL', 'SL', 'OW', 'OB', 'OL', 'UN',
     'OB or OW', 'US or OW', 'US or SS or OW', 'FL', 'FD', 'OF', 'OD'
@@ -535,8 +539,15 @@ class DataElement(object):
             return True
 
         if isinstance(other, self.__class__):
-            return (self.tag == other.tag and self.VR == other.VR
-                    and self.value == other.value)
+            if self.tag != other.tag or self.VR != other.VR:
+                return False
+
+            # tag and VR match, now check the value
+            if config.have_numpy and isinstance(self.value, numpy.ndarray):
+                return (len(self.value) == len(other.value)
+                        and numpy.allclose(self.value, other.value))
+            else:
+                return self.value == other.value
 
         return NotImplemented
 
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
index ec8f68196..0da3c44e1 100644
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -581,14 +581,22 @@ class Dataset(dict):
         if isinstance(key, slice):
             for tag in self._slice_dataset(key.start, key.stop, key.step):
                 del self._dict[tag]
+                # invalidate private blocks in case a private creator is
+                # deleted - will be re-created on next access
+                if self._private_blocks and BaseTag(tag).is_private_creator:
+                    self._private_blocks = {}
         else:
             # Assume is a standard tag (for speed in common case)
             try:
                 del self._dict[key]
+                if self._private_blocks and BaseTag(key).is_private_creator:
+                    self._private_blocks = {}
             # If not a standard tag, than convert to Tag and try again
             except KeyError:
                 tag = Tag(key)
                 del self._dict[tag]
+                if self._private_blocks and tag.is_private_creator:
+                    self._private_blocks = {}
 
     def __dir__(self):
         """Give a list of attributes available in the :class:`Dataset`.
@@ -776,7 +784,7 @@ class Dataset(dict):
         if not char_set:
             char_set = self._parent_encoding
         else:
-            char_set = convert_encodings(char_set)
+            char_set = convert_encodings(char_set.value)
 
         return char_set
 
@@ -905,7 +913,7 @@ class Dataset(dict):
             If the private creator tag is not found in the given group and
             the `create` parameter is ``False``.
         """
-        def new_block():
+        def new_block(element):
             block = PrivateBlock(key, self, element)
             self._private_blocks[key] = block
             return block
@@ -921,19 +929,23 @@ class Dataset(dict):
             raise ValueError(
                 'Tag must be private if private creator is given')
 
-        for element in range(0x10, 0x100):
-            private_creator_tag = Tag(group, element)
-            if private_creator_tag not in self._dict:
-                if create:
-                    self.add_new(private_creator_tag, 'LO', private_creator)
-                    return new_block()
-                else:
-                    break
-            if self[private_creator_tag].value == private_creator:
-                return new_block()
+        # find block with matching private creator
+        data_el = next((el for el in self[(group, 0x10):(group, 0x100)]
+                        if el.value == private_creator), None)
+        if data_el is not None:
+            return new_block(data_el.tag.element)
+
+        if not create:
+            # not found and shall not be created - raise
+            raise KeyError(
+                "Private creator '{}' not found".format(private_creator))
 
-        raise KeyError(
-            "Private creator '{}' not found".format(private_creator))
+        # private creator not existing - find first unused private block
+        # and add the private creator
+        first_free_el = next(el for el in range(0x10, 0x100)
+                             if Tag(group, el) not in self._dict)
+        self.add_new(Tag(group, first_free_el), 'LO', private_creator)
+        return new_block(first_free_el)
 
     def private_creators(self, group):
         """Return a list of private creator names in the given group.
@@ -967,13 +979,7 @@ class Dataset(dict):
         if group % 2 == 0:
             raise ValueError('Group must be an odd number')
 
-        private_creators = []
-        for element in range(0x10, 0x100):
-            private_creator_tag = Tag(group, element)
-            if private_creator_tag not in self._dict:
-                break
-            private_creators.append(self[private_creator_tag].value)
-        return private_creators
+        return [x.value for x in self[(group, 0x10):(group, 0x100)]]
 
     def get_private_item(self, group, element_offset, private_creator):
         """Return the data element for the given private tag `group`.
diff --git a/pydicom/env_info.py b/pydicom/env_info.py
new file mode 100644
index 000000000..a0eb60c95
--- /dev/null
+++ b/pydicom/env_info.py
@@ -0,0 +1,50 @@
+# Copyright 2020 pydicom authors. See LICENSE file for details.
+"""
+Gather system information and version information for pydicom and auxiliary
+modules.
+
+The output is a GitHub-flavoured markdown table whose contents can help
+diagnose any perceived bugs in pydicom. This can be pasted directly into a new
+GitHub bug report.
+
+This file is intended to be run as an executable module.
+"""
+
+import platform
+import sys
+import importlib
+
+
+def main():
+    version_rows = [("platform", platform.platform()), ("Python", sys.version)]
+
+    for module in ("pydicom", "gdcm", "jpeg_ls", "numpy", "PIL"):
+        try:
+            m = importlib.import_module(module)
+        except ImportError:
+            version = "_module not found_"
+        else:
+            version = extract_version(m) or "**cannot determine version**"
+
+        version_rows.append((module, version))
+
+    print_table(version_rows)
+
+
+def print_table(version_rows):
+    row_format = "{:12} | {}"
+    print(row_format.format("module", "version"))
+    print(row_format.format("------", "-------"))
+    for module, version in version_rows:
+        # Some version strings have multiple lines and need to be squashed
+        print(row_format.format(module, version.replace("\n", " ")))
+
+
+def extract_version(module):
+    if module.__name__ == "gdcm":
+        return getattr(module, "GDCM_VERSION", None)
+    return getattr(module, "__version__", None)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/pydicom/filereader.py b/pydicom/filereader.py
index 45245ff28..3d875d5b0 100644
--- a/pydicom/filereader.py
+++ b/pydicom/filereader.py
@@ -379,7 +379,7 @@ def read_dataset(fp, is_implicit_VR, is_little_endian, bytelength=None,
 
     ds = Dataset(raw_data_elements)
     if 0x00080005 in raw_data_elements:
-        char_set = DataElement_from_raw(raw_data_elements[0x00080005])
+        char_set = DataElement_from_raw(raw_data_elements[0x00080005]).value
         encoding = convert_encodings(char_set)
     else:
         encoding = parent_encoding
diff --git a/pydicom/filewriter.py b/pydicom/filewriter.py
index ab047b20b..93d59279e 100644
--- a/pydicom/filewriter.py
+++ b/pydicom/filewriter.py
@@ -9,6 +9,7 @@ from struct import pack
 from pydicom.charset import (
     default_encoding, text_VRs, convert_encodings, encode_string
 )
+from pydicom.config import have_numpy
 from pydicom.dataelem import DataElement_from_raw
 from pydicom.dataset import Dataset, validate_file_meta
 from pydicom.filebase import DicomFile, DicomFileLike, DicomBytesIO
@@ -22,6 +23,10 @@ from pydicom.valuerep import extra_length_VRs
 from pydicom.values import convert_numbers
 
 
+if have_numpy:
+    import numpy
+
+
 def _correct_ambiguous_vr_element(elem, ds, is_little_endian):
     """Implementation for `correct_ambiguous_vr_element`.
     See `correct_ambiguous_vr_element` for description.
@@ -259,6 +264,8 @@ def write_UI(fp, data_element):
 
 def _is_multi_value(val):
     """Return True if `val` is a multi-value container."""
+    if have_numpy and isinstance(val, numpy.ndarray):
+        return True
     return isinstance(val, (MultiValue, list, tuple))
 
 
@@ -328,8 +335,8 @@ def write_number_string(fp, data_element):
 
     if _is_multi_value(val):
         val = "\\".join((x.original_string
-                         if hasattr(x, 'original_string') else str(x)
-                         for x in val))
+                         if hasattr(x, 'original_string')
+                         else str(x) for x in val))
     else:
         if hasattr(val, 'original_string'):
             val = val.original_string
diff --git a/pydicom/values.py b/pydicom/values.py
index a3e17445f..7515bef36 100644
--- a/pydicom/values.py
+++ b/pydicom/values.py
@@ -3,13 +3,14 @@
    data elements to proper python types
 """
 
+import re
 from io import BytesIO
 from struct import (unpack, calcsize)
 
 # don't import datetime_conversion directly
 from pydicom import config
 from pydicom.charset import (default_encoding, text_VRs, decode_string)
-from pydicom.config import logger
+from pydicom.config import logger, have_numpy
 from pydicom.dataelem import empty_value_for_VR
 from pydicom.filereader import read_sequence
 from pydicom.multival import MultiValue
@@ -18,7 +19,14 @@ import pydicom.uid
 import pydicom.valuerep  # don't import DS directly as can be changed by config
 from pydicom.valuerep import (MultiString, DA, DT, TM, TEXT_VR_DELIMS)
 
-from pydicom.valuerep import PersonName
+
+have_numpy = True
+try:
+    import numpy
+except ImportError:
+    have_numpy = False
+
+from pydicom.valuerep import PersonName  # NOQA
 
 
 def convert_tag(byte_string, is_little_endian, offset=0):
@@ -139,6 +147,10 @@ def convert_DA_string(byte_string, is_little_endian, struct_format=None):
 def convert_DS_string(byte_string, is_little_endian, struct_format=None):
     """Return a decoded 'DS' value.
 
+    .. versionchanged:: 2.0
+
+        The option to return numpy values was added.
+
     Parameters
     ----------
     byte_string : bytes or str
@@ -150,18 +162,45 @@ def convert_DS_string(byte_string, is_little_endian, struct_format=None):
 
     Returns
     -------
-    valuerep.DSfloat or valuerep.DSdecimal or list of DSfloat/DSdecimal
-        If :attr:`~pydicom.config.use_DS_decimal` is ``True`` then returns
-        :class:`~pydicom.valuerep.DSdecimal` or a :class:`list` of
-        ``DSdecimal``, otherwise returns :class:`~pydicom.valuerep.DSfloat` or
-        a ``list`` of ``DSfloat``.
+    :class:`~pydicom.valuerep.DSfloat`, :class:`~pydicom.valuerep.DSdecimal`, :class:`numpy.float64`, list of DSfloat/DSdecimal or :class:`numpy.ndarray`   of :class:`numpy.float64`
+
+        If :attr:`~pydicom.config.use_DS_decimal` is ``False`` (default),
+        returns a :class:`~pydicom.valuerep.DSfloat` or list of them
+
+        If :attr:`~pydicom.config.use_DS_decimal` is ``True``,
+        returns a :class:`~pydicom.valuerep.DSdecimal` or list of them
+
+        If :data:`~pydicom.config.use_DS_numpy` is ``True``,
+        returns a :class:`numpy.float64` or a :class:`numpy.ndarray` of them
+
+    Raises
+    ------
+    ValueError
+        If :data:`~pydicom.config.use_DS_numpy` is ``True`` and the string
+        contains non-valid characters
+
+    ImportError
+        If :data:`~pydicom.config.use_DS_numpy` is ``True`` and numpy is not
+        available
     """
-    byte_string = byte_string.decode(default_encoding)
+    num_string = byte_string.decode(default_encoding)
     # Below, go directly to DS class instance
     # rather than factory DS, but need to
     # ensure last string doesn't have
     # blank padding (use strip())
-    return MultiString(byte_string.strip(), valtype=pydicom.valuerep.DSclass)
+    if config.use_DS_numpy:
+        if not have_numpy:
+            raise ImportError("use_DS_numpy set but numpy not installed")
+        # Check for valid characters. Numpy ignores many
+        regex = r'[ \\0-9\.+eE-]*\Z'
+        if re.match(regex, num_string) is None:
+            raise ValueError("DS: char(s) not in repertoire: '{}'".
+                             format(re.sub(regex[:-2], '', num_string)))
+        value = numpy.fromstring(num_string, dtype='f8', sep="\\")
+        if len(value) == 1:  # Don't use array for one number
+            value = value[0]
+        return value
+    return MultiString(num_string.strip(), valtype=pydicom.valuerep.DSclass)
 
 
 def _DT_from_byte_string(byte_string):
@@ -187,7 +226,7 @@ def convert_DT_string(byte_string, is_little_endian, struct_format=None):
     Returns
     -------
     str or list of str or valuerep.DT or list of DT
-        if
+        If
         :attr:`~pydicom.config.datetime_conversion` is ``True`` then returns
         :class:`~pydicom.valuerep.DT` or a :class:`list` of ``DT``, otherwise
         returns :class:`str` or ``list`` of ``str``.
@@ -206,6 +245,10 @@ def convert_DT_string(byte_string, is_little_endian, struct_format=None):
 def convert_IS_string(byte_string, is_little_endian, struct_format=None):
     """Return a decoded 'IS' value.
 
+    .. versionchanged:: 2.0
+
+        The option to return numpy values was added.
+
     Parameters
     ----------
     byte_string : bytes or str
@@ -217,11 +260,40 @@ def convert_IS_string(byte_string, is_little_endian, struct_format=None):
 
     Returns
     -------
-    valuerep.IS or list of IS
-        The decoded value(s).
+    :class:`~pydicom.valuerep.IS` or list of them, or :class:`numpy.int64` or :class:`~numpy.ndarray` of them
+
+        If :data:`~pydicom.config.use_IS_numpy` is ``False`` (default), returns
+        a single :class:`~pydicom.valuerep.IS` or a list of them
+
+        If :data:`~pydicom.config.use_IS_numpy` is ``True``, returns
+        a single :class:`numpy.int64` or a :class:`~numpy.ndarray` of them
+
+    Raises
+    ------
+    ValueError
+        If :data:`~pydicom.config.use_IS_numpy` is ``True`` and the string
+        contains non-valid characters
+
+    ImportError
+        If :data:`~pydicom.config.use_IS_numpy` is ``True`` and numpy is not
+        available
     """
-    byte_string = byte_string.decode(default_encoding)
-    return MultiString(byte_string, valtype=pydicom.valuerep.IS)
+    num_string = byte_string.decode(default_encoding)
+
+    if config.use_IS_numpy:
+        if not have_numpy:
+            raise ImportError("use_IS_numpy set but numpy not installed")
+        # Check for valid characters. Numpy ignores many
+        regex = r'[ \\0-9\.+-]*\Z'
+        if re.match(regex, num_string) is None:
+            raise ValueError("IS: char(s) not in repertoire: '{}'".
+                             format(re.sub(regex[:-2], '', num_string)))
+        value = numpy.fromstring(num_string, dtype='i8', sep=chr(92))  # 92:'\'
+        if len(value) == 1:  # Don't use array for one number
+            value = value[0]
+        return value
+
+    return MultiString(num_string, valtype=pydicom.valuerep.IS)
 
 
 def convert_numbers(byte_string, is_little_endian, struct_format):
@@ -540,6 +612,7 @@ def convert_value(VR, raw_data_element, encodings=None):
     type or list of type
         The element value decoded using the appropriate decoder.
     """
+
     if VR not in converters:
         # `VR` characters are in the ascii alphabet ranges 65 - 90, 97 - 122
         char_range = list(range(65, 91)) + list(range(97, 123))


Test patch:
diff --git a/pydicom/tests/test_codedict.py b/pydicom/tests/test_codedict.py
index 9a9751f84..889cc56bd 100644
--- a/pydicom/tests/test_codedict.py
+++ b/pydicom/tests/test_codedict.py
@@ -1,15 +1,10 @@
-import unittest
-
 import pytest
 
 from pydicom.sr.codedict import codes
 from pydicom.sr.coding import Code
 
 
-class TestCodeDict(unittest.TestCase):
-    def setUp(self):
-        super(TestCodeDict, self).setUp()
-
+class TestCodeDict:
     def test_dcm_1(self):
         assert codes.DCM.Modality == Code(
             value="121139", scheme_designator="DCM", meaning="Modality"
diff --git a/pydicom/tests/test_coding.py b/pydicom/tests/test_coding.py
index 98f18b693..9de543afe 100644
--- a/pydicom/tests/test_coding.py
+++ b/pydicom/tests/test_coding.py
@@ -1,14 +1,11 @@
-import unittest
-
 import pytest
 
 from pydicom.sr.coding import Code
 from pydicom.uid import UID
 
 
-class TestCode(unittest.TestCase):
-    def setUp(self):
-        super(TestCode, self).setUp()
+class TestCode:
+    def setup(self):
         self._value = "373098007"
         self._meaning = "Mean Value of population"
         self._scheme_designator = "SCT"
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index 78e2083d1..d3efc2079 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -917,9 +917,10 @@ class TestDataset(object):
         ds.add_new(0x00080005, 'CS', 'ISO_IR 100')
         ds.add_new(0x00090010, 'LO', 'Creator 1.0')
         ds.add_new(0x00091001, 'SH', 'Version1')
-        ds.add_new(0x00090011, 'LO', 'Creator 2.0')
-        ds.add_new(0x00091101, 'SH', 'Version2')
-        ds.add_new(0x00091102, 'US', 2)
+        # make sure it works with non-contiguous blocks
+        ds.add_new(0x00090020, 'LO', 'Creator 2.0')
+        ds.add_new(0x00092001, 'SH', 'Version2')
+        ds.add_new(0x00092002, 'US', 2)
 
         # Dataset.private_block
         with pytest.raises(ValueError, match='Tag must be private'):
@@ -1019,6 +1020,68 @@ class TestDataset(object):
         assert ['Creator 1.0', 'Creator 2.0'] == ds.private_creators(0x0009)
         assert not ds.private_creators(0x0011)
 
+    def test_non_contiguous_private_creators(self):
+        ds = Dataset()
+        ds.add_new(0x00080005, 'CS', 'ISO_IR 100')
+        ds.add_new(0x00090010, 'LO', 'Creator 1.0')
+        ds.add_new(0x00090020, 'LO', 'Creator 2.0')
+        ds.add_new(0x000900ff, 'LO', 'Creator 3.0')
+
+        assert (['Creator 1.0', 'Creator 2.0', 'Creator 3.0'] ==
+                ds.private_creators(0x0009))
+
+    def test_create_private_tag_after_removing_all(self):
+        # regression test for #1097 - make sure private blocks are updated
+        # after removing all private tags
+        ds = Dataset()
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x01, "SH", "Border Collie")
+        block = ds.private_block(0x000b, 'dog^2', create=True)
+        block.add_new(0x01, "SH", "Poodle")
+
+        ds.remove_private_tags()
+        block = ds.private_block(0x000b, 'dog^2', create=True)
+        block.add_new(0x01, "SH", "Poodle")
+        assert len(ds) == 2
+        assert (0x000b0010) in ds
+        assert ds[0x000b0010].value == 'dog^2'
+
+    def test_create_private_tag_after_removing_private_creator(self):
+        ds = Dataset()
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x01, "SH", "Border Collie")
+
+        del ds[0x000b0010]
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x02, "SH", "Poodle")
+        assert len(ds) == 3
+        assert ds[0x000b0010].value == 'dog^1'
+
+        del ds[Tag(0x000b, 0x0010)]
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x01, "SH", "Pug")
+        assert len(ds) == 3
+        assert ds[0x000b0010].value == 'dog^1'
+
+        del ds['0x000b0010']
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x01, "SH", "Pug")
+        assert len(ds) == 3
+        assert ds[0x000b0010].value == 'dog^1'
+
+    def test_create_private_tag_after_removing_slice(self):
+        ds = Dataset()
+        block = ds.private_block(0x000b, 'dog^1', create=True)
+        block.add_new(0x01, "SH", "Border Collie")
+        block = ds.private_block(0x000b, 'dog^2', create=True)
+        block.add_new(0x01, "SH", "Poodle")
+
+        del ds[0x000b0010:0x000b1110]
+        block = ds.private_block(0x000b, 'dog^2', create=True)
+        block.add_new(0x01, "SH", "Poodle")
+        assert len(ds) == 2
+        assert ds[0x000b0010].value == 'dog^2'
+
     def test_is_original_encoding(self):
         """Test Dataset.write_like_original"""
         ds = Dataset()
diff --git a/pydicom/tests/test_env_info.py b/pydicom/tests/test_env_info.py
new file mode 100644
index 000000000..81a067bc6
--- /dev/null
+++ b/pydicom/tests/test_env_info.py
@@ -0,0 +1,38 @@
+# Copyright 2020 pydicom authors. See LICENSE file for details.
+# -*- coding: utf-8 -*-
+"""Unit tests for the env_info module."""
+
+import pydicom.env_info
+
+
+class TestEnvInfo(object):
+    """Test the env_info module"""
+
+    def test_report_looks_like_a_table(self, capsys):
+        """Test that the report looks like a table"""
+        pydicom.env_info.main()
+
+        out, err = capsys.readouterr()
+        table_start = """
+module       | version
+------       | -------
+platform     |""".lstrip()
+        assert out.startswith(table_start)
+
+    def test_all_modules_reported(self, capsys):
+        """Test that all modules are reported"""
+        pydicom.env_info.main()
+
+        out, err = capsys.readouterr()
+        lines = out.split("\n")
+        modules = [line.split("|")[0].strip() for line in lines[2:] if line]
+
+        assert modules == [
+            "platform",
+            "Python",
+            "pydicom",
+            "gdcm",
+            "jpeg_ls",
+            "numpy",
+            "PIL",
+        ]
diff --git a/pydicom/tests/test_filereader.py b/pydicom/tests/test_filereader.py
index 5a3a578cf..5aa239d9e 100644
--- a/pydicom/tests/test_filereader.py
+++ b/pydicom/tests/test_filereader.py
@@ -24,19 +24,21 @@ from pydicom.dataelem import DataElement, DataElement_from_raw
 from pydicom.errors import InvalidDicomError
 from pydicom.filebase import DicomBytesIO
 from pydicom.filereader import data_element_generator
+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()
 
-try:
+have_numpy = pydicom.config.have_numpy
+if have_numpy:
     import numpy  # NOQA
-except ImportError:
-    numpy = None
 
 try:
     import jpeg_ls
@@ -53,12 +55,12 @@ except ImportError:
         # Neither worked, so it's likely not installed.
         PILImg = None
 
-have_numpy = numpy is not 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")
+    "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")
@@ -82,11 +84,11 @@ 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_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")
+    "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()
@@ -124,40 +126,44 @@ class TestReader(object):
         """Returns correct values for sample data elements in test
         RT Plan file.
         """
+        orig_use_numpy = config.use_DS_numpy
+        config.use_DS_numpy = False
         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
+        assert beam[0x300A, 0x00B2].value == beam.TreatmentMachineName
 
         got = cp1.ReferencedDoseReferenceSequence[
-            0].CumulativeDoseReferenceCoefficient
+            0
+        ].CumulativeDoseReferenceCoefficient
         DS = pydicom.valuerep.DS
-        expected = DS('0.9990268')
+        expected = DS("0.9990268")
         assert expected == got
         got = cp0.BeamLimitingDevicePositionSequence[0].LeafJawPositions
-        assert [DS('-100'), DS('100.0')] == got
+        assert [DS("-100"), DS("100.0")] == got
+        config.use_DS_numpy = orig_use_numpy
 
     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 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]
-        )
+        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
+        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
 
@@ -165,8 +171,12 @@ class TestReader(object):
         # [-158.13580300000001, -179.035797, -75.699996999999996]
         got = ct.ImagePositionPatient
         DS = pydicom.valuerep.DS
-        expected = [DS('-158.135803'), DS('-179.035797'), DS('-75.699997')]
-        assert expected == got
+        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
@@ -174,7 +184,7 @@ class TestReader(object):
         assert 128 * 128 * 2 == len(ct.PixelData)
 
         # Also test private elements name can be resolved:
-        got = ct[(0x0043, 0x104e)].name
+        got = ct[(0x0043, 0x104E)].name
         assert "[Duration of X-ray on]" == got
 
     @pytest.mark.skipif(not have_numpy, reason="Numpy not installed")
@@ -207,7 +217,7 @@ class TestReader(object):
 
         obs_seq0 = rtss.RTROIObservationsSequence[0]
         got = obs_seq0.ROIPhysicalPropertiesSequence[0].ROIPhysicalProperty
-        assert 'REL_ELEC_DENSITY' == got
+        assert "REL_ELEC_DENSITY" == got
 
     def test_dir(self):
         """Returns correct dir attributes for both Dataset and DICOM names
@@ -216,16 +226,24 @@ class TestReader(object):
         rtss = dcmread(rtstruct_name, force=True)
         # sample some expected 'dir' values
         got_dir = dir(rtss)
-        expect_in_dir = ['pixel_array', 'add_new', 'ROIContourSequence',
-                         'StructureSetDate']
+        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']
+        expect_in_dir = [
+            "pixel_array",
+            "add_new",
+            "ReferencedROINumber",
+            "ROIDisplayColor",
+        ]
         for name in expect_in_dir:
             assert name in got_dir
 
@@ -234,11 +252,16 @@ class TestReader(object):
         mr = dcmread(mr_name)
         # (0010, 0010) Patient's Name           'CompressedSamples^MR1'
         mr.decode()
-        assert 'CompressedSamples^MR1' == mr.PatientName
+        assert "CompressedSamples^MR1" == mr.PatientName
         assert mr[0x10, 0x10].value == mr.PatientName
 
         DS = pydicom.valuerep.DS
-        assert [DS('0.3125'), DS('0.3125')] == mr.PixelSpacing
+
+        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
@@ -269,43 +292,52 @@ class TestReader(object):
         ctpartial_tags = sorted(ctpartial.keys())
         ctfull = dcmread(ct_name)
         ctfull_tags = sorted(ctfull.keys())
-        missing = [Tag(0x7fe0, 0x10), Tag(0xfffc, 0xfffc)]
+        missing = [Tag(0x7FE0, 0x10), Tag(0xFFFC, 0xFFFC)]
         assert ctfull_tags == ctpartial_tags + missing
 
     def test_specific_tags(self):
         """Returns only tags specified by user."""
-        ctspecific = dcmread(ct_name, specific_tags=[
-            Tag(0x0010, 0x0010), 'PatientID', 'ImageType', 'ViewName'])
+        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)
+            Tag(0x0008, 0x0005),
+            Tag(0x0008, 0x0008),
+            Tag(0x0010, 0x0010),
+            Tag(0x0010, 0x0020),
         ]
         assert expected == ctspecific_tags
 
     def test_specific_tags_with_unknown_length_SQ(self):
         """Returns only tags specified by user."""
-        unknown_len_sq_tag = Tag(0x3f03, 0x1001)
+        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 = 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
+        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']
+            emri_jpeg_2k_lossless, specific_tags=["SpecificCharacterSet"]
         )
         tags = sorted(tags.keys())
         assert [Tag(0x08, 0x05)] == tags
@@ -314,15 +346,19 @@ class TestReader(object):
         """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])
+        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],
+            )
 
         config.enforce_valid_values = True
-        with pytest.raises(EOFError, match='End of file reached*'):
-            dcmread(emri_jpeg_2k_lossless_too_short,
-                    specific_tags=[unknown_len_tag])
+        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."""
@@ -345,21 +381,21 @@ class TestReader(object):
         ds = dcmread(nested_priv_SQ_name)
 
         # Make sure that the entire dataset was read in
-        pixel_data_tag = TupleTag((0x7fe0, 0x10))
+        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
+        assert "SQ" == seq0.VR
 
         # Now verify the presence of the nested private SQ
         seq1 = seq0[0][tag]
-        assert 'SQ' == seq1.VR
+        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
+        assert b"Double Nested SQ" == seq1[0][tag].value
+        assert b"Nested SQ" == seq0[0][0x01, 0x02].value
 
     def test_no_meta_group_length(self):
         """Read file with no group length in file meta."""
@@ -377,7 +413,7 @@ class TestReader(object):
 
         # Repeat one test from nested private sequence test to maker sure
         #    file was read correctly
-        pixel_data_tag = TupleTag((0x7fe0, 0x10))
+        pixel_data_tag = TupleTag((0x7FE0, 0x10))
         assert pixel_data_tag in ds
 
     def test_explicit_VR_little_endian_no_meta(self):
@@ -408,8 +444,8 @@ class TestReader(object):
         """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))
+        ds.add(DataElement(0x00280108, "US", 10))
+        ds.add(DataElement(0x00280109, "US", 500))
 
         fp = BytesIO()
         file_ds = FileDataset(fp, ds)
@@ -418,15 +454,15 @@ class TestReader(object):
         file_ds.save_as(fp, write_like_original=True)
 
         ds = dcmread(fp, force=True)
-        assert 'US' == ds[0x00280108].VR
+        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))
+        ds.add(DataElement(0x00280108, "US", 10))
+        ds.add(DataElement(0x00280109, "US", 500))
 
         fp = BytesIO()
         file_ds = FileDataset(fp, ds)
@@ -435,7 +471,7 @@ class TestReader(object):
         file_ds.save_as(fp, write_like_original=True)
 
         ds = dcmread(fp, force=True)
-        assert 'US' == ds[0x00280108].VR
+        assert "US" == ds[0x00280108].VR
         assert 10 == ds.SmallestPixelValueInSeries
 
     def test_correct_ambiguous_vr_compressed(self):
@@ -449,15 +485,15 @@ class TestReader(object):
         file_ds.save_as(fp, write_like_original=True)
 
         ds = dcmread(fp, force=True)
-        assert 'OB' == ds[0x7fe00010].VR
+        assert "OB" == ds[0x7FE00010].VR
 
     def test_long_specific_char_set(self):
         """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))
+        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 "
@@ -475,37 +511,41 @@ class TestReader(object):
 
     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')
+        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 "MediaStorageSOPClassUID" in ds.file_meta
         assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
-        assert 'NORMAL' == ds.Polarity
+        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')
+        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 "MediaStorageSOPClassUID" in ds.file_meta
         assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
-        assert 'NORMAL' == ds.Polarity
+        assert "NORMAL" == ds.Polarity
         assert 1 == ds.ImageBoxPosition
         assert 3 == ds.MessageID
 
@@ -513,127 +553,143 @@ class TestReader(object):
         """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')
+        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 "MediaStorageSOPClassUID" in ds.file_meta
         assert ImplicitVRLittleEndian == ds.file_meta.TransferSyntaxUID
-        assert 'NORMAL' == ds.Polarity
+        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')
+        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
+        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')
+        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 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')
+        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 "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')
+        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 "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')
+        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 "MessageID" in ds
         assert ds.preamble is None
         assert Dataset() == ds.file_meta
 
     def test_file_meta_dataset_implicit_vr(self):
         """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')
+        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
+        assert "TransferSyntaxUID" in ds.file_meta
 
     def test_no_dataset(self):
         """Test reading no elements or preamble produces empty Dataset"""
-        bytestream = b''
+        bytestream = b""
         fp = BytesIO(bytestream)
         ds = dcmread(fp, force=True)
         assert ds.preamble is None
@@ -652,7 +708,7 @@ class TestReader(object):
         """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']
+        assert ds.read_encoding == ["iso8859"]
 
     def test_dcmread_does_not_raise(self):
         """Test that reading from DicomBytesIO does not raise on EOF.
@@ -668,95 +724,99 @@ class TestReader(object):
         except StopIteration:
             pass
         except EOFError:
-            self.fail('Unexpected EOFError raised')
+            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'
+            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'
+        suffix = b"\x53\x53\x06\x00\x00\xf5\x00\xf8\x10\x00"
 
         for raw_tag in prefixes:
-            tag = unpack('<2H', raw_tag)
+            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.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 = 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'
+        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 = 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'
+        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'
+            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
+        elem = ds["FloatPixelData"]
+        assert "OF" == elem.VR
+        assert b"\x00\x01\x02\x03" == elem.value
 
 
 class TestIncorrectVR(object):
     def setup(self):
         config.enforce_valid_values = False
         self.ds_explicit = BytesIO(
-            b'\x08\x00\x05\x00CS\x0a\x00ISO_IR 100'  # SpecificCharacterSet
-            b'\x08\x00\x20\x00DA\x08\x0020000101'  # StudyDate
+            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'
+            b"\x08\x00\x05\x00\x0a\x00\x00\x00ISO_IR 100"
+            b"\x08\x00\x20\x00\x08\x00\x00\x0020000101"
         )
 
     def teardown(self):
         config.enforce_valid_values = False
 
     def test_implicit_vr_expected_explicit_used(self):
-        msg = ('Expected implicit VR, but found explicit VR - '
-               'using explicit VR for reading')
+        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
+        assert "ISO_IR 100" == ds.SpecificCharacterSet
+        assert "20000101" == ds.StudyDate
 
     def test_implicit_vr_expected_explicit_used_strict(self):
         config.enforce_valid_values = True
-        msg = ('Expected implicit VR, but found explicit VR - '
-               'using explicit VR for reading')
+        msg = (
+            "Expected implicit VR, but found explicit VR - "
+            "using explicit VR for reading"
+        )
 
         with pytest.raises(InvalidDicomError, match=msg):
             read_dataset(
@@ -764,20 +824,24 @@ class TestIncorrectVR(object):
             )
 
     def test_explicit_vr_expected_implicit_used(self):
-        msg = ('Expected explicit VR, but found implicit VR - '
-               'using implicit VR for reading')
+        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
+        assert "ISO_IR 100" == ds.SpecificCharacterSet
+        assert "20000101" == ds.StudyDate
 
     def test_explicit_vr_expected_implicit_used_strict(self):
         config.enforce_valid_values = True
-        msg = ('Expected explicit VR, but found implicit VR - '
-               'using implicit VR for reading')
+        msg = (
+            "Expected explicit VR, but found implicit VR - "
+            "using implicit VR for reading"
+        )
         with pytest.raises(InvalidDicomError, match=msg):
             read_dataset(
                 self.ds_implicit, is_implicit_VR=False, is_little_endian=True
@@ -795,7 +859,7 @@ class TestIncorrectVR(object):
         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
+        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"
@@ -815,46 +879,46 @@ class TestIncorrectVR(object):
 
 class TestUnknownVR(object):
     @pytest.mark.parametrize(
-        'vr_bytes, str_output',
+        "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
+            (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/-
+            (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/-
+            (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
+            (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
+            (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
+            (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
-        ]
+            (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."""
@@ -862,59 +926,80 @@ class TestUnknownVR(object):
         # 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'
+                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\)"
-            .format(str_output)
+            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(object):
+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
+        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'
+            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
+        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
+        ds.UVElementMaximum = 2 ** 64 - 1
 
         self.fp = BytesIO()  # Implicit little
         file_ds = FileDataset(self.fp, ds)
@@ -931,204 +1016,317 @@ class TestReadDataElement(object):
     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')
+        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')
+        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')
+        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')
+        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'])
+        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')
+        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'])
+        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')
+        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')
+        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')
+        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')
+        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')
+        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
+        assert "TEST  12" == 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'
+            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
+        elem = ds["ExtendedOffsetTable"]
+        assert "OV" == elem.VR
         assert 0x7FE00001 == elem.tag
         assert val == elem.value
 
-        new = DataElement(0x7FE00001, 'OV', val)
+        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'
+            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
+        elem = ds["ExtendedOffsetTable"]
+        assert "OV" == elem.VR
         assert 0x7FE00001 == elem.tag
         assert val == elem.value
 
-        new = DataElement(0x7FE00001, 'OV', val)
+        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
+        elem = ds["SVElementMinimum"]
+        assert "SV" == elem.VR
         assert 0xFFFE0001 == elem.tag
-        assert -2**63 == elem.value
+        assert -(2 ** 63) == elem.value
 
-        new = DataElement(0xFFFE0001, 'SV', -2**63)
+        new = DataElement(0xFFFE0001, "SV", -(2 ** 63))
         assert elem == new
 
-        elem = ds['SVElementMaximum']
-        assert 'SV' == elem.VR
+        elem = ds["SVElementMaximum"]
+        assert "SV" == elem.VR
         assert 0xFFFE0002 == elem.tag
-        assert 2**63 - 1 == elem.value
+        assert 2 ** 63 - 1 == elem.value
 
-        new = DataElement(0xFFFE0002, 'SV', 2**63 - 1)
+        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
+        elem = ds["SVElementMinimum"]
+        assert "SV" == elem.VR
         assert 0xFFFE0001 == elem.tag
-        assert -2**63 == elem.value
+        assert -(2 ** 63) == elem.value
 
-        new = DataElement(0xFFFE0001, 'SV', -2**63)
+        new = DataElement(0xFFFE0001, "SV", -(2 ** 63))
         assert elem == new
 
-        elem = ds['SVElementMaximum']
-        assert 'SV' == elem.VR
+        elem = ds["SVElementMaximum"]
+        assert "SV" == elem.VR
         assert 0xFFFE0002 == elem.tag
-        assert 2**63 - 1 == elem.value
+        assert 2 ** 63 - 1 == elem.value
 
-        new = DataElement(0xFFFE0002, 'SV', 2**63 - 1)
+        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
+        elem = ds["UVElementMinimum"]
+        assert "UV" == elem.VR
         assert 0xFFFE0003 == elem.tag
         assert 0 == elem.value
 
-        new = DataElement(0xFFFE0003, 'UV', 0)
+        new = DataElement(0xFFFE0003, "UV", 0)
         assert elem == new
 
-        elem = ds['UVElementMaximum']
-        assert 'UV' == elem.VR
+        elem = ds["UVElementMaximum"]
+        assert "UV" == elem.VR
         assert 0xFFFE0004 == elem.tag
-        assert 2**64 - 1 == elem.value
+        assert 2 ** 64 - 1 == elem.value
 
-        new = DataElement(0xFFFE0004, 'UV', 2**64 - 1)
+        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
+        elem = ds["UVElementMinimum"]
+        assert "UV" == elem.VR
         assert 0xFFFE0003 == elem.tag
         assert 0 == elem.value
 
-        new = DataElement(0xFFFE0003, 'UV', 0)
+        new = DataElement(0xFFFE0003, "UV", 0)
         assert elem == new
 
-        elem = ds['UVElementMaximum']
-        assert 'UV' == elem.VR
+        elem = ds["UVElementMaximum"]
+        assert "UV" == elem.VR
         assert 0xFFFE0004 == elem.tag
-        assert 2**64 - 1 == elem.value
+        assert 2 ** 64 - 1 == elem.value
 
-        new = DataElement(0xFFFE0004, 'UV', 2**64 - 1)
+        new = DataElement(0xFFFE0004, "UV", 2 ** 64 - 1)
         assert elem == new
 
 
+class TestDSISnumpy:
+    def setup(self):
+        self.orig_IS_numpy = config.use_IS_numpy
+        self.orig_DS_numpy = config.use_DS_numpy
+        self.orig_DS_decimal = config.use_DS_decimal
+
+    def teardown(self):
+        config.use_IS_numpy = self.orig_IS_numpy
+        config.DS_decimal(self.orig_DS_decimal)
+        config.DS_numpy(self.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):
+            col = 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):
+            cd = 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_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(object):
     """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"
@@ -1140,11 +1338,12 @@ class TestDeferredRead(object):
 
     def test_time_check(self):
         """Deferred read warns if file has been modified"""
-        ds = dcmread(self.testfile_name, defer_size='2 kB')
+        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
+            f.write("\0")  # "touch" the file
 
         msg = r"Deferred read warning -- file modification time has changed"
         with pytest.warns(UserWarning, match=msg):
@@ -1163,7 +1362,11 @@ class TestDeferredRead(object):
         ds_defer = dcmread(self.testfile_name, defer_size=2000)
         for data_elem in ds_norm:
             tag = data_elem.tag
-            assert data_elem.value == ds_defer[tag].value
+
+            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."""
@@ -1177,7 +1380,7 @@ class TestDeferredRead(object):
 
     def test_filelike_deferred(self):
         """Deferred values work with file-like objects."""
-        with open(ct_name, 'rb') as fp:
+        with open(ct_name, "rb") as fp:
             data = fp.read()
         filelike = io.BytesIO(data)
         dataset = pydicom.dcmread(filelike, defer_size=1024)
@@ -1188,13 +1391,20 @@ class TestReadTruncatedFile(object):
     def testReadFileWithMissingPixelData(self):
         mr = dcmread(truncated_mr_name)
         mr.decode()
-        assert 'CompressedSamples^MR1' == mr.PatientName
+        assert "CompressedSamples^MR1" == mr.PatientName
         assert mr.PatientName == mr[0x10, 0x10].value
         DS = pydicom.valuerep.DS
-        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")
+        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()
@@ -1213,25 +1423,27 @@ class TestFileLike(object):
     """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')
+        f = open(ct_name, "rb")
         ct = dcmread(f)
-        # Tests here simply repeat testCT -- perhaps should collapse
+        # XXX Tests here simply repeat testCT -- perhaps should collapse
         # the code together?
-        got = ct.ImagePositionPatient
         DS = pydicom.valuerep.DS
-        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
+
+        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
 
-        # (0020, 0032) Image Position (Patient)
-        # [-158.13580300000001, -179.035797, -75.699996999999996]
-        got = ct.ImagePositionPatient
-        expected = [DS('-158.135803'), DS('-179.035797'), DS('-75.699997')]
-        assert expected == got
         assert 128 == ct.Rows
         assert 128 == ct.Columns
         assert 16 == ct.BitsStored
@@ -1243,15 +1455,22 @@ class TestFileLike(object):
 
     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:
+        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
-        expected = [DS('-158.135803'), DS('-179.035797'), DS('-75.699997')]
-        assert expected == got
+
+        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()
@@ -1259,40 +1478,34 @@ class TestFileLike(object):
 
 class TestDataElementGenerator(object):
     """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')
+        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')
+        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'
+        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')
+        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'
+        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')
+        elem = DataElement(0x00100010, "PN", "ABCDEF")
+        assert elem == DataElement_from_raw(next(gen), "ISO_IR 100")
diff --git a/pydicom/tests/test_util.py b/pydicom/tests/test_util.py
index 2c6cfeab0..2d909865b 100644
--- a/pydicom/tests/test_util.py
+++ b/pydicom/tests/test_util.py
@@ -21,6 +21,11 @@ from pydicom.util.dump import *
 from pydicom.util.hexutil import hex2bytes, bytes2hex
 from pydicom.data import get_testdata_files
 
+have_numpy = True
+try:
+    import numpy
+except ImportError:
+    have_numpy = False
 
 test_dir = os.path.dirname(__file__)
 raw_hex_module = os.path.join(test_dir, '_write_stds.py')
@@ -253,8 +258,11 @@ class TestDataElementCallbackTests(object):
                             process_unknown_VRs=False)
         ds = filereader.read_dataset(self.bytesio, is_little_endian=True,
                                      is_implicit_VR=True)
-        expected = [valuerep.DSfloat(x) for x in ["2", "4", "8", "16"]]
         got = ds.ROIContourSequence[0].ContourSequence[0].ContourData
         config.reset_data_element_callback()
 
-        assert expected == got
+        expected = [2., 4., 8., 16.]
+        if have_numpy and config.use_DS_numpy:
+            assert numpy.allclose(expected, got)
+        else:
+            assert expected == got