Repository: pydicom/pydicom

Issue:
dir(FileDataset) and dir(DicomDir) return only Dataset methods
#### Description
`dir(FileDataset)` and `dir(DicomDir)` don't return their specific methods.

#### Steps/Code to Reproduce
```python
from pydicom import read_file
>>> ds = read_file('DICOMDIR')
>>> type(ds)
<class 'pydicom.dicomdir.DicomDir'>
>>> hasattr(ds, 'parse_records')
True
>>> callable(ds.parse_records)
True
>>> 'parse_records' in dir(ds)
False
```
Occurs because `Dataset.__dir__()` only gets methods/properties from the `Dataset` class.

Discussion:
pep8speaks: Hello @scaramallion! Thanks for submitting the PR.

 - In the file [`pydicom/dataset.py`](https://github.com/pydicom/pydicom/blob/201f53ec6ffe632f4875418ee6cd3a2375e88ffa/pydicom/dataset.py), following are the PEP8 issues :

> [Line 339:80](https://github.com/pydicom/pydicom/blob/201f53ec6ffe632f4875418ee6cd3a2375e88ffa/pydicom/dataset.py#L339): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (81 > 79 characters)
> [Line 341:80](https://github.com/pydicom/pydicom/blob/201f53ec6ffe632f4875418ee6cd3a2375e88ffa/pydicom/dataset.py#L341): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (85 > 79 characters)





Patch:
diff --git a/README.md b/README.md
index 13667dbc0..457d83603 100644
--- a/README.md
+++ b/README.md
@@ -27,4 +27,4 @@ Documentation
 
 pydicom [documentation](https://pydicom.readthedocs.org/en/stable/) is available on Read The Docs.
 
-See [Getting Started](https://pydicom.readthedocs.org/en/stable/getting_started.html) for installation and basic information, and the [User Guide](https://pydicom.readthedocs.org/en/stable/pydicom_user_guide.html) for an overview of how to use the pydicom library.
+See [Getting Started](https://pydicom.readthedocs.org/en/stable/getting_started.html) for installation and basic information, and the [User Guide](https://pydicom.readthedocs.org/en/stable/pydicom_user_guide.html) for an overview of how to use the pydicom library. To contribute to pydicom, read our [contribution guide](CONTRIBUTING.md). To contribute an example or extension of pydicom that does not belong with the core software, see our contribution repository, [contrib-pydicom](https://www.github.com/pydicom/contrib-pydicom).
diff --git a/doc/pydicom_user_guide.rst b/doc/pydicom_user_guide.rst
index 6d85c4a62..5f16ce768 100644
--- a/doc/pydicom_user_guide.rst
+++ b/doc/pydicom_user_guide.rst
@@ -9,4 +9,4 @@ Pydicom User Guide
    transition_to_pydicom1.rst
    base_element.rst
    working_with_pixel_data.rst
-   viewing_images.rst
+   viewing_images.rst
\ No newline at end of file
diff --git a/doc/viewing_images.rst b/doc/viewing_images.rst
index 3f6f1a66e..5132c4e36 100644
--- a/doc/viewing_images.rst
+++ b/doc/viewing_images.rst
@@ -47,8 +47,8 @@ Using pydicom with Tkinter
 --------------------------
 
 The program `pydicom_Tkinter.py
-<https://github.com/pydicom/pydicom/blob/master/pydicom/contrib/pydicom_Tkinter.py>`_
-in the ``contrib`` folder demonstrates how to show an image using the
+<https://github.com/pydicom/contrib-pydicom/blob/master/viewers/pydicom_Tkinter.py>`_
+in the ``contrib-pydicom`` repository demonstrates how to show an image using the
 Tkinter graphics system, which comes standard with most python installs.
 It creates a Tkinter PhotoImage in a Label widget or a user-supplied widget.
 
@@ -56,8 +56,8 @@ Using pydicom with Python Imaging Library (PIL)
 -----------------------------------------------
 
 The module `pydicom_PIL.py
-<https://github.com/pydicom/pydicom/blob/master/pydicom/contrib/pydicom_PIL.py>`_
-in the ``contrib`` folder
+<https://github.com/pydicom/contrib-pydicom/blob/master/viewers/pydicom_PIL.py>`_
+in the ``contrib-pydicom`` repository
 uses PIL's ``Image.show()`` method after creating an Image instance
 from the pixel data and some basic information about it (bit depth, LUTs, etc)
 
@@ -65,6 +65,6 @@ Using pydicom with wxPython
 ---------------------------
 
 The module `imViewer-Simple.py
-<https://github.com/pydicom/pydicom/blob/master/pydicom/contrib/imViewer_Simple.py>`_
-in the ``contrib`` folder uses wxPython (also PIL, but it notes that it
-may not be strictly necessary) to display an image from a pydicom dataset.
+<https://github.com/pydicom/contrib-pydicom/blob/master/viewers/imViewer_Simple.py>`
+in the ``contrib-pydicom`` repository uses wxPython (also PIL, but it notes that it
+may not be strictly necessary) to display an image from a pydicom dataset.
\ No newline at end of file
diff --git a/pydicom/contrib/__init__.py b/pydicom/contrib/__init__.py
deleted file mode 100644
index c8438e83e..000000000
--- a/pydicom/contrib/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# __init__.py
-# Mark the folder as a python package
diff --git a/pydicom/contrib/dcm_qt_tree.py b/pydicom/contrib/dcm_qt_tree.py
deleted file mode 100644
index aaa70c98c..000000000
--- a/pydicom/contrib/dcm_qt_tree.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# dcm_qt_tree.py
-"""View DICOM files in a tree using Qt and PySide"""
-# Copyright (c) 2013 Padraig Looney
-# This file is released under the
-# pydicom (https://github.com/pydicom/pydicom)
-# license, see the file LICENSE available at
-# (https://github.com/pydicom/pydicom)
-
-import pydicom
-import sys
-from PySide import QtGui
-import collections
-
-
-class DicomTree(object):
-    def __init__(self, filename):
-        self.filename = filename
-
-    def show_tree(self):
-        ds = self.dicom_to_dataset(self.filename)
-        dic = self.dataset_to_dic(ds)
-        model = self.dic_to_model(dic)
-        self.display(model)
-
-    def array_to_model(self, array):
-        model = QtGui.QStandardItemModel()
-        parentItem = model.invisibleRootItem()
-        for ntuple in array:
-            tag = ntuple[0]
-            value = ntuple[1]
-            if isinstance(value, dict):
-                self.recurse_dic_to_item(value, parentItem)
-            else:
-                item = QtGui.QStandardItem(tag + str(value))
-                parentItem.appendRow(item)
-        return parentItem
-
-    def dic_to_model(self, dic):
-        model = QtGui.QStandardItemModel()
-        parentItem = model.invisibleRootItem()
-        self.recurse_dic_to_item(dic, parentItem)
-        return model
-
-    def dataset_to_array(self, dataset):
-        array = []
-        for data_element in dataset:
-            array.append(self.data_element_to_dic(data_element))
-        return array
-
-    def recurse_dic_to_item(self, dic, parent):
-        for k in dic:
-            v = dic[k]
-            if isinstance(v, dict):
-                item = QtGui.QStandardItem(k + ':' + str(v))
-                parent.appendRow(self.recurse_dic_to_item(v, item))
-            else:
-                item = QtGui.QStandardItem(k + ': ' + str(v))
-                parent.appendRow(item)
-        return parent
-
-    def dicom_to_dataset(self, filename):
-        dataset = pydicom.read_file(filename, force=True)
-        return dataset
-
-    def data_element_to_dic(self, data_element):
-        dic = collections.OrderedDict()
-        if data_element.VR == "SQ":
-            items = collections.OrderedDict()
-            dic[data_element.name] = items
-            i = 0
-            for dataset_item in data_element:
-                items['item ' + str(i)] = self.dataset_to_dic(dataset_item)
-                i += 1
-        elif data_element.name != 'Pixel Data':
-            dic[data_element.name] = data_element.value
-        return dic
-
-    def dataset_to_dic(self, dataset):
-        dic = collections.OrderedDict()
-        for data_element in dataset:
-            dic.update(self.data_element_to_dic(data_element))
-        return dic
-
-    def display(self, model):
-        app = QtGui.QApplication.instance()
-
-        # create QApplication if it doesnt exist
-        if not app:
-            app = QtGui.QApplication(sys.argv)
-        tree = QtGui.QTreeView()
-        tree.setModel(model)
-        tree.show()
-        app.exec_()
-        return tree
-
-
-def main():
-    filename = sys.argv[1]
-    dicomTree = DicomTree(filename)
-    dicomTree.show_tree()
-
-
-if __name__ == "__main__":
-    main()
diff --git a/pydicom/contrib/dicom_dao.py b/pydicom/contrib/dicom_dao.py
deleted file mode 100644
index 549e0888d..000000000
--- a/pydicom/contrib/dicom_dao.py
+++ /dev/null
@@ -1,460 +0,0 @@
-#!/usr/bin/python
-""" dicom_dao
-
-Data Access Objects for persisting PyDicom DataSet objects.
-
-Currently we support couchdb through the DicomCouch class.
-
-Limitations:
- - Private tags are discarded
-
-TODO:
- - Unit tests with multiple objects open at a time
- - Unit tests with rtstruct objects
- - Support for mongodb (mongo has more direct support for binary data)
-
-Dependencies:
- - PyDicom
- - python-couchdb
-
-Tested with:
- - PyDicom 0.9.4-1
- - python-couchdb 0.6
- - couchdb 0.10.1
-
-"""
-
-#
-# Copyright (c) 2010 Michael Wallace
-# This file is released under the pydicom license.
-#    See the file LICENSE included with the pydicom distribution, also
-#    available at https://github.com/pydicom/pydicom
-#
-
-import hashlib
-import os
-import string
-import couchdb
-import pydicom
-
-
-def uid2str(uid):
-    """ Convert PyDicom uid to a string """
-    return repr(uid).strip("'")
-
-
-# When reading files a VR of 'US or SS' is left as binary,
-# because we don't know how to interpret the values
-# different numbers. We therefore treat it as binary
-# and will continue to until either pydicom works it out
-# for us, or we figure out a test.
-
-BINARY_VR_VALUES = ['OW', 'OB', 'OW/OB', 'US or SS']
-
-
-class DicomCouch(dict):
-    """ A Data Access Object for persisting
-        PyDicom objects into CouchDB
-
-    We follow the same pattern as the python-couchdb
-    library for getting and setting documents, for
-        example storing pydicom.dataset.Dataset object dcm:
-        db = DicomCouch('http://localhost:5984/', 'dbname')
-        db[dcm.SeriesInstanceUID] = dcm
-
-    The only constraints on the key are that it must be
-    json-serializable and unique within the database instance.
-    In theory it should be possible to use any DICOM UID.
-    Unfortunately I have written this code under the assumption
-    that SeriesInstanceUID will always be used.
-    This will be fixed.
-
-    Retrieving object with key 'foo':
-        dcm = db['foo']
-
-    Deleting object with key 'foo':
-        dcm = db['foo']
-        db.delete(dcm)
-
-    TODO:
-     - It is possible to have couchdb assign a uid
-       when adding objects. This
-       should be supported.
-    """
-
-    def __init__(self, server, db):
-        """ Create connection to couchdb server/db """
-        super(DicomCouch, self).__init__()
-        self._meta = {}
-        server = couchdb.Server(server)
-        try:
-            self._db = server[db]
-        except couchdb.client.ResourceNotFound:
-            self._db = server.create(db)
-
-    def __getitem__(self, key):
-        """ Retrieve DICOM object with
-            specified SeriesInstanceUID """
-        doc = self._db[key]
-        dcm = json2pydicom(doc)
-
-        if dcm.SeriesInstanceUID not in self._meta:
-            self._meta[dcm.SeriesInstanceUID] = {}
-            self._meta[dcm.SeriesInstanceUID]['hashes'] = {}
-
-        if '_attachments' in doc:
-            self.__get_attachments(dcm, doc)
-        _set_meta_info_dcm(dcm)
-        # Keep a copy of the couch doc for use in DELETE operations
-        self._meta[dcm.SeriesInstanceUID]['doc'] = doc
-        return dcm
-
-    def __setitem__(self, key, dcm):
-        """ Write the supplied DICOM object to the database """
-        try:
-            dcm.PixelData = dcm.pixel_array.tostring()
-
-        # Silently ignore errors due to pixel_array not existing
-        except AttributeError:
-            pass
-
-        # Silently ignore attempts to modify compressed pixel data
-        except NotImplementedError:
-            pass
-
-        # Silently ignore errors due to PixelData not existing
-        except TypeError:
-            pass
-
-        jsn,
-        binary_elements,
-        file_meta_binary_elements = pydicom2json(dcm)
-
-        _strip_elements(jsn, binary_elements)
-        _strip_elements(jsn['file_meta'], file_meta_binary_elements)
-        if dcm.SeriesInstanceUID in self._meta:
-            self.__set_meta_info_jsn(jsn, dcm)
-
-        try:  # Actually write to the db
-            self._db[key] = jsn
-        except TypeError as type_error:
-            if str(type_error) == 'string indices must be integers, not str':
-                pass
-
-        if dcm.SeriesInstanceUID not in self._meta:
-            self._meta[dcm.SeriesInstanceUID] = {}
-            self._meta[dcm.SeriesInstanceUID]['hashes'] = {}
-
-        self.__put_attachments(dcm, binary_elements, jsn)
-        # Get a local copy of the document
-        # We get this from couch because we get the _id,
-        # _rev and _attachments keys which will ensure
-        # we don't overwrite the attachments we just uploaded.
-        # I don't really like the extra HTTP GET and
-        # I think we can generate what we need without doing
-        # it. Don't have time to work out how yet.
-        self._meta[dcm.SeriesInstanceUID]['doc'] = \
-            self._db[dcm.SeriesInstanceUID]
-
-    def __str__(self):
-        """ Return the string representation of the
-            couchdb client """
-        return str(self._db)
-
-    def __repr__(self):
-        """ Return the canonical string representation
-            of the couchdb client """
-        return repr(self._db)
-
-    def __get_attachments(self, dcm, doc):
-        """ Set binary tags by retrieving attachments
-            from couchdb.
-
-        Values are hashed so they are only updated
-        if they have changed.
-
-        """
-        for id in doc['_attachments'].keys():
-            tagstack = id.split(':')
-            value = self._db.get_attachment(doc['_id'], id)
-            _add_element(dcm, tagstack, value)
-            value = hashlib.md5(value)
-            self._meta[dcm.SeriesInstanceUID]['hashes'][id] = value
-
-    def __put_attachments(self, dcm, binary_elements, jsn):
-        """ Upload all new and modified attachments """
-        func = self.__attachment_update_needed
-        elements_to_update = \
-            [(tagstack, item)
-             for tagstack, item in binary_elements
-             if func(dcm,
-                     _tagstack2id(tagstack + [item.tag]),
-                     item)
-            ]  # nopep8
-        for tagstack, element in elements_to_update:
-            id = _tagstack2id(tagstack + [element.tag])
-            self._db.put_attachment(jsn, element.value, id)
-            self._meta[dcm.SeriesInstanceUID]['hashes'][id] = \
-                hashlib.md5(element.value)
-
-    def delete(self, dcm):
-        """ Delete from database and remove meta info from the DAO """
-        self._db.delete(self._meta[dcm.SeriesInstanceUID]['doc'])
-        self._meta.pop(dcm.SeriesInstanceUID)
-
-    def __set_meta_info_jsn(self, jsn, dcm):
-        """ Set the couch-specific meta data for supplied dict """
-        jsn['_rev'] = self._meta[dcm.SeriesInstanceUID]['doc']['_rev']
-        if '_attachments' in self._meta[dcm.SeriesInstanceUID]['doc']:
-            jsn['_attachments'] = \
-                self._meta[dcm.SeriesInstanceUID]['doc']['_attachments']
-
-    def __attachment_update_needed(self, dcm, id, binary_element):
-        """ Compare hashes for binary element and return true if different """
-        try:
-            hashes = self._meta[dcm.SeriesInstanceUID]['hashes']
-        except KeyError:
-            return True  # If no hashes dict then attachments do not exist
-
-        if id not in hashes or hashes[id].digest() != \
-                hashlib.md5(binary_element.value).digest():
-            return True
-        else:
-            return False
-
-
-def _add_element(dcm, tagstack, value):
-    """ Add element with tag, vr and value to dcm
-        at location tagstack """
-    current_node = dcm
-    for item in tagstack[:-1]:
-        try:
-            address = int(item)
-        except ValueError:
-            address = pydicom.tag.Tag(__str2tag(item))
-        current_node = current_node[address]
-    tag = __str2tag(tagstack[-1])
-    vr = pydicom.datadict.dictionary_VR(tag)
-    current_node[tag] = pydicom.dataelem.DataElement(tag, vr, value)
-
-
-def _tagstack2id(tagstack):
-    """ Convert a list of tags to a unique
-        (within document) attachment id """
-    return string.join([str(tag) for tag in tagstack], ':')
-
-
-def _strip_elements(jsn, elements):
-    """ Remove supplied elements from the dict object
-
-    We use this with a list of binary elements so that
-    we don't store empty tags in couchdb when we are
-    already storing the binary data as
-    attachments.
-
-    """
-    for tagstack, element in elements:
-        if len(tagstack) == 0:
-            jsn.pop(element.tag)
-        else:
-            current_node = jsn
-            for tag in tagstack:
-                current_node = current_node[tag]
-            current_node.pop(element.tag)
-
-
-def _set_meta_info_dcm(dcm):
-    """ Set the file metadata DataSet attributes
-
-    This is done by PyDicom when we pydicom.read_file(foo)
-    but we need to do it ourselves when creating
-    a DataSet from scratch, otherwise we cannot use
-    foo.pixel_array or pydicom.write_file(foo).
-
-    This code is lifted from PyDicom.
-
-    """
-    TransferSyntax = dcm.file_meta.TransferSyntaxUID
-    if TransferSyntax == pydicom.uid.ExplicitVRLittleEndian:
-        dcm.is_implicit_vr = False
-
-        # This line not in PyDicom
-        dcm.is_little_endian = True
-
-    elif TransferSyntax == pydicom.uid.ImplicitVRLittleEndian:
-        dcm.is_implicit_vr = True
-        dcm.is_little_endian = True
-
-    elif TransferSyntax == pydicom.uid.ExplicitVRBigEndian:
-        dcm.is_implicit_vr = False
-        dcm.is_little_endian = False
-
-    elif TransferSyntax == pydicom.uid.DeflatedExplicitVRLittleEndian:
-
-        # Deleted lines above as it relates
-        dcm.is_implicit_vr = False
-
-        # to reading compressed file data.
-        dcm.is_little_endian = True
-
-    else:
-        # Any other syntax should be Explicit VR Little Endian,
-        #   e.g. all Encapsulated (JPEG etc) are ExplVR-LE by
-        #   Standard PS 3.5-2008 A.4 (p63)
-        dcm.is_implicit_vr = False
-        dcm.is_little_endian = True
-
-
-def pydicom2json(dcm):
-    """ Convert the supplied PyDicom object into a
-        json-serializable dict
-
-    Binary elements cannot be represented in json
-    so we return these as as separate list of the
-    tuple (tagstack, element), where:
-     - element  = pydicom.dataelem.DataElement
-     - tagstack = list of tags/sequence IDs that address
-                  the element
-
-    The tagstack variable means we know the absolute
-    address of each binary element. We then use this as
-    the attachment id in couchdb - when we retrieve
-    the attachment we can then insert it at the
-    appropriate point in the tree.
-
-    """
-    dcm.remove_private_tags()  # No support for now
-    dcm.decode()               # Convert to unicode
-    binary_elements = []
-    tagstack = []
-    jsn = dict((key, __jsonify(dcm[key], binary_elements, tagstack))
-               for key in dcm.keys())
-    file_meta_binary_elements = []
-    jsn['file_meta'] = dict((key, __jsonify(dcm.file_meta[key],
-                            file_meta_binary_elements, tagstack))
-                            for key in dcm.file_meta.keys())
-    return jsn, binary_elements, file_meta_binary_elements
-
-
-def __jsonify(element, binary_elements, tagstack):
-    """ Convert key, value to json-serializable types
-
-    Recursive, so if value is key/value pairs then
-    all children will get converted
-
-    """
-    value = element.value
-    if element.VR in BINARY_VR_VALUES:
-        binary_elements.append((tagstack[:], element))
-        return ''
-    elif type(value) == list:
-        new_list = [__typemap(listvalue) for listvalue in value]
-        return new_list
-    elif type(value) == pydicom.sequence.Sequence:
-        tagstack.append(element.tag)
-        nested_data = []
-        for i in range(0, len(value)):
-            tagstack.append(i)
-            nested_data.append(
-                dict((subkey, __jsonify(value[i][subkey],
-                                        binary_elements, tagstack))
-                     for subkey in value[i].keys()))
-            tagstack.pop()
-        tagstack.pop()
-        return nested_data
-    else:
-        return __typemap(value)
-
-
-def __typemap(value):
-    """ Map PyDicom types that won't serialize
-        to JSON types """
-    if type(value) == pydicom.uid.UID:
-        return uid2str(value)
-    elif isinstance(value, pydicom.tag.BaseTag):
-        return int(value)
-    else:
-        return value
-
-
-def json2pydicom(jsn):
-    """ Convert the supplied json dict into
-        a PyDicom object """
-    dataset = pydicom.dataset.Dataset()
-    # Don't try to convert couch specific tags
-    dicom_keys = [
-        key for key in jsn.keys()
-        if key not in ['_rev', '_id', '_attachments', 'file_meta']
-    ]
-    for key in dicom_keys:
-        dataset.add(__dicomify(key, jsn[key]))
-    file_meta = pydicom.dataset.Dataset()
-    for key in jsn['file_meta']:
-        file_meta.add(__dicomify(key, jsn['file_meta'][key]))
-    dataset.file_meta = file_meta
-    return dataset
-
-
-def __dicomify(key, value):
-    """ Convert a json key, value to a PyDicom DataElement """
-    tag = __str2tag(key)
-
-    # 0 tag implies group length (filreader.py pydicom)
-    if tag.element == 0:
-        vr = 'UL'
-    else:
-        vr = pydicom.datadict.dictionary_VR(tag)
-
-    if vr == 'OW/OB':  # Always write pixel data as bytes
-        vr = 'OB'      # rather than words
-
-    if vr == 'US or SS':    # US or SS is up to us as the data is already
-        if value < 0:       # decoded. We therefore choose US, unless we
-            vr = 'SS'       # need a signed value.
-        else:
-            vr = 'US'
-
-    if vr == 'SQ':  # We have a sequence of datasets, so we recurse
-        seq_list = [__make_dataset([__dicomify(subkey, listvalue[subkey])
-                                    for subkey in listvalue.keys()])
-                    for listvalue in value
-                    ]
-        seq = pydicom.sequence.Sequence(seq_list)
-        return pydicom.dataelem.DataElement(tag, vr, seq)
-    else:
-        return pydicom.dataelem.DataElement(tag, vr, value)
-
-
-def __make_dataset(data_elements):
-    """ Create a Dataset from a list of DataElement objects """
-    dataset = pydicom.dataset.Dataset()
-    for element in data_elements:
-        dataset.add(element)
-    return dataset
-
-
-def __str2tag(key):
-    """ Convert string representation of a tag into a Tag """
-    return pydicom.tag.Tag((int(key[1:5], 16), int(key[7:-1], 16)))
-
-
-if __name__ == '__main__':
-    TESTDB = 'dicom_test'
-    SERVER = 'http://127.0.0.1:5984'
-    # Delete test database if it already exists
-    couch = couchdb.Server(SERVER)
-    try:
-        couch.delete(TESTDB)
-    except couchdb.client.ResourceNotFound:
-        pass  # Don't worry if it didn't exist
-
-    db = DicomCouch(SERVER, TESTDB)
-
-    testfiles_dir = '../testfiles'
-    testfiles = os.listdir('../testfiles')
-    testfiles = [x for x in testfiles if x.endswith('dcm')]
-    testfiles = [os.path.join('../testfiles', x) for x in testfiles]
-
-    for dcmfile in testfiles:
-        dcm = pydicom.read_file(dcmfile)
-        db[dcm.SeriesInstanceUID] = dcm
diff --git a/pydicom/contrib/imViewer_Simple.py b/pydicom/contrib/imViewer_Simple.py
deleted file mode 100644
index ae98aea9c..000000000
--- a/pydicom/contrib/imViewer_Simple.py
+++ /dev/null
@@ -1,347 +0,0 @@
-# ===================================================================
-# imViewer-Simple.py
-#
-#    An example program that opens uncompressed DICOM images and
-# converts them via numPy and PIL to be viewed in wxWidgets GUI
-# apps.  The conversion is currently:
-#
-#    pydicom->NumPy->PIL->wxPython.Image->wxPython.Bitmap
-#
-# Gruesome but it mostly works.  Surely there is at least one
-# of these steps that could be eliminated (probably PIL) but
-# haven't tried that yet and I may want some of the PIL manipulation
-# functions.
-#
-#    This won't handle RLE, embedded JPEG-Lossy, JPEG-lossless,
-# JPEG2000, old ACR/NEMA files, or anything wierd.  Also doesn't
-# handle some RGB images that I tried.
-#
-#    Have added Adit Panchal's LUT code.  It helps a lot, but needs
-# to be further generalized.  Added test for window and/or level
-# as 'list' type - crude, but it worked for a bunch of old MR and
-# CT slices I have.
-#
-# Testing:      minimal
-#               Tried only on WinXP sp2 using numpy 1.3.0
-#               and PIL 1.1.7b1, Python 2.6.4, and wxPython 2.8.10.1
-#
-# Dave Witten:  Nov. 11, 2009
-# ===================================================================
-
-import pydicom
-import wx
-from pydicom import compat
-have_PIL = True
-try:
-    import PIL.Image
-except ImportError:
-    have_PIL = False
-have_numpy = True
-try:
-    import numpy as np
-except ImportError:
-    have_numpy = False
-
-# ----------------------------------------------------------------
-#  Initialize image capabilities.
-# ----------------------------------------------------------------
-
-wx.InitAllImageHandlers()
-
-
-def MsgDlg(window, string, caption='OFAImage', style=wx.YES_NO | wx.CANCEL):
-    """Common MessageDialog."""
-    dlg = wx.MessageDialog(window, string, caption, style)
-    result = dlg.ShowModal()
-    dlg.Destroy()
-    return result
-
-
-class ImFrame(wx.Frame):
-    """Class for main window."""
-
-    def __init__(self, parent, title):
-        """Create the pydicom image example's main frame window."""
-
-        style = wx.DEFAULT_FRAME_STYLE | wx.SUNKEN_BORDER | wx.CLIP_CHILDREN
-
-        wx.Frame.__init__(
-            self,
-            parent,
-            id=-1,
-            title="",
-            pos=wx.DefaultPosition,
-            size=wx.Size(w=1024, h=768),
-            style=style)
-
-        # --------------------------------------------------------
-        # Set up the menubar.
-        # --------------------------------------------------------
-        self.mainmenu = wx.MenuBar()
-
-        # Make the 'File' menu.
-        menu = wx.Menu()
-        item = menu.Append(wx.ID_ANY, '&Open', 'Open file for editing')
-        self.Bind(wx.EVT_MENU, self.OnFileOpen, item)
-        item = menu.Append(wx.ID_ANY, 'E&xit', 'Exit Program')
-        self.Bind(wx.EVT_MENU, self.OnFileExit, item)
-        self.mainmenu.Append(menu, '&File')
-
-        # Attach the menu bar to the window.
-        self.SetMenuBar(self.mainmenu)
-
-        # --------------------------------------------------------
-        # Set up the main splitter window.
-        # --------------------------------------------------------
-        self.mainSplitter = wx.SplitterWindow(self, style=wx.NO_3D | wx.SP_3D)
-        self.mainSplitter.SetMinimumPaneSize(1)
-
-        # -------------------------------------------------------------
-        # Create the folderTreeView on the left.
-        # -------------------------------------------------------------
-        dsstyle = wx.TR_LINES_AT_ROOT | wx.TR_HAS_BUTTONS
-        self.dsTreeView = wx.TreeCtrl(self.mainSplitter, style=dsstyle)
-
-        # --------------------------------------------------------
-        # Create the ImageView on the right pane.
-        # --------------------------------------------------------
-        imstyle = wx.VSCROLL | wx.HSCROLL | wx.CLIP_CHILDREN
-        self.imView = wx.Panel(self.mainSplitter, style=imstyle)
-
-        self.imView.Bind(wx.EVT_PAINT, self.OnPaint)
-        self.imView.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
-
-        self.imView.Bind(wx.EVT_SIZE, self.OnSize)
-
-        # --------------------------------------------------------
-        # Install the splitter panes.
-        # --------------------------------------------------------
-        self.mainSplitter.SplitVertically(self.dsTreeView, self.imView)
-        self.mainSplitter.SetSashPosition(300, True)
-
-        # --------------------------------------------------------
-        # Initialize some values
-        # --------------------------------------------------------
-        self.dcmdsRoot = False
-        self.foldersRoot = False
-        self.loadCentered = True
-        self.bitmap = None
-        self.Show(True)
-
-    def OnFileExit(self, event):
-        """Exits the program."""
-        self.Destroy()
-        event.Skip()
-
-    def OnSize(self, event):
-        "Window 'size' event."
-        self.Refresh()
-
-    def OnEraseBackground(self, event):
-        "Window 'erase background' event."
-        pass
-
-    def populateTree(self, ds):
-        """ Populate the tree in the left window with the [desired]
-        dataset values"""
-        if not self.dcmdsRoot:
-            self.dcmdsRoot = self.dsTreeView.AddRoot(text="DICOM Objects")
-        else:
-            self.dsTreeView.DeleteChildren(self.dcmdsRoot)
-        self.recurse_tree(ds, self.dcmdsRoot)
-        self.dsTreeView.ExpandAll()
-
-    def recurse_tree(self, ds, parent, hide=False):
-        """ order the dicom tags """
-        for data_element in ds:
-            if isinstance(data_element.value, compat.text_type):
-                text = compat.text_type(data_element)
-                ip = self.dsTreeView.AppendItem(parent, text=text)
-            else:
-                ip = self.dsTreeView.AppendItem(parent, text=str(data_element))
-
-            if data_element.VR == "SQ":
-                for i, ds in enumerate(data_element.value):
-                    item_describe = data_element.name.replace(" Sequence", "")
-                    item_text = "%s %d" % (item_describe, i + 1)
-                    rjust = item_text.rjust(128)
-                    parentNodeID = self.dsTreeView.AppendItem(ip, text=rjust)
-                    self.recurse_tree(ds, parentNodeID)
-
-# --- Most of what is important happens below this line ---------------------
-
-    def OnFileOpen(self, event):
-        """Opens a selected file."""
-        msg = 'Choose a file to add.'
-        dlg = wx.FileDialog(self, msg, '', '', '*.*', wx.OPEN)
-        if dlg.ShowModal() == wx.ID_OK:
-            fullPath = dlg.GetPath()
-            imageFile = dlg.GetFilename()
-            # checkDICMHeader()
-            self.show_file(imageFile, fullPath)
-
-    def OnPaint(self, event):
-        "Window 'paint' event."
-        dc = wx.PaintDC(self.imView)
-        dc = wx.BufferedDC(dc)
-
-        # paint a background just so it isn't *so* boring.
-        dc.SetBackground(wx.Brush("WHITE"))
-        dc.Clear()
-        dc.SetBrush(wx.Brush("GREY", wx.CROSSDIAG_HATCH))
-        windowsize = self.imView.GetSizeTuple()
-        dc.DrawRectangle(0, 0, windowsize[0], windowsize[1])
-        bmpX0 = 0
-        bmpY0 = 0
-        if self.bitmap is not None:
-            if self.loadCentered:
-                bmpX0 = (windowsize[0] - self.bitmap.Width) / 2
-                bmpY0 = (windowsize[1] - self.bitmap.Height) / 2
-            dc.DrawBitmap(self.bitmap, bmpX0, bmpY0, False)
-
-    # ------------------------------------------------------------
-    #  ImFrame.ConvertWXToPIL()
-    #  Expropriated from Andrea Gavana's
-    #  ShapedButton.py in the wxPython dist
-    # ------------------------------------------------------------
-    def ConvertWXToPIL(self, bmp):
-        """ Convert wx.Image Into PIL Image. """
-        width = bmp.GetWidth()
-        height = bmp.GetHeight()
-        im = wx.EmptyImage(width, height)
-        im.fromarray("RGBA", (width, height), bmp.GetData())
-        return im
-
-    # ------------------------------------------------------------
-    #  ImFrame.ConvertPILToWX()
-    #  Expropriated from Andrea Gavana's
-    #  ShapedButton.py in the wxPython dist
-    # ------------------------------------------------------------
-    def ConvertPILToWX(self, pil, alpha=True):
-        """ Convert PIL Image Into wx.Image. """
-        if alpha:
-            image = wx.EmptyImage(*pil.size)
-            image.SetData(pil.convert("RGB").tostring())
-            image.SetAlphaData(pil.convert("RGBA").tostring()[3::4])
-        else:
-            image = wx.EmptyImage(pil.size[0], pil.size[1])
-            new_image = pil.convert('RGB')
-            data = new_image.tostring()
-            image.SetData(data)
-        return image
-
-    def get_LUT_value(self, data, window, level):
-        """Apply the RGB Look-Up Table for the given
-           data and window/level value."""
-        if not have_numpy:
-            raise ImportError("Numpy is not available. "
-                              "See http://numpy.scipy.org/ "
-                              "to download and install")
-
-        if isinstance(window, list):
-            window = window[0]
-        if isinstance(level, list):
-            level = level[0]
-
-        win = window
-        lvl = level
-
-        e = [
-            0, 255,
-            lambda data: ((data - (lvl - 0.5)) / (win - 1) + 0.5) * (255 - 0)
-        ]
-        return np.piecewise(data, [
-            data <= (lvl - 0.5 - (win - 1) / 2),
-            data > (lvl - 0.5 + (win - 1) / 2)
-        ], e)
-
-    # -----------------------------------------------------------
-    # ImFrame.loadPIL_LUT(dataset)
-    # Display an image using the Python Imaging Library (PIL)
-    # -----------------------------------------------------------
-    def loadPIL_LUT(self, dataset):
-        if not have_PIL:
-            raise ImportError("Python Imaging Library is not available."
-                              " See http://www.pythonware.com/products/pil/"
-                              " to download and install")
-        if 'PixelData' not in dataset:
-            raise TypeError("Cannot show image -- "
-                            "DICOM dataset does not have pixel data")
-
-        # can only apply LUT if these values exist
-        if ('WindowWidth' not in dataset) or ('WindowCenter' not in dataset):
-            bits = dataset.BitsAllocated
-            samples = dataset.SamplesPerPixel
-            if bits == 8 and samples == 1:
-                mode = "L"
-            elif bits == 8 and samples == 3:
-                mode = "RGB"
-            # not sure about this -- PIL source says is
-            # 'experimental' and no documentation.
-            elif bits == 16:
-                # Also, should bytes swap depending
-                # on endian of file and system??
-                mode = "I;16"
-            else:
-                msg = "Don't know PIL mode for %d BitsAllocated" % (bits)
-                msg += " and %d SamplesPerPixel" % (samples)
-                raise TypeError(msg)
-            size = (dataset.Columns, dataset.Rows)
-
-            # Recommended to specify all details by
-            # http://www.pythonware.com/library/pil/handbook/image.htm
-            im = PIL.Image.frombuffer(mode, size, dataset.PixelData, "raw",
-                                      mode, 0, 1)
-        else:
-
-            image = self.get_LUT_value(
-                dataset.pixel_array, dataset.WindowWidth, dataset.WindowCenter)
-
-            # Convert mode to L since LUT has only 256 values:
-            # http://www.pythonware.com/library/pil/handbook/image.htm
-            im = PIL.Image.fromarray(image).convert('L')
-        return im
-
-    def show_file(self, imageFile, fullPath):
-        """ Load the DICOM file, make sure it contains at least one
-        image, and set it up for display by OnPaint().  ** be
-        careful not to pass a unicode string to read_file or it will
-        give you 'fp object does not have a defer_size attribute,
-        or some such."""
-        ds = pydicom.read_file(str(fullPath))
-
-        # change strings to unicode
-        ds.decode()
-        self.populateTree(ds)
-        if 'PixelData' in ds:
-            self.dImage = self.loadPIL_LUT(ds)
-            if self.dImage is not None:
-                tmpImage = self.ConvertPILToWX(self.dImage, False)
-                self.bitmap = wx.BitmapFromImage(tmpImage)
-                self.Refresh()
-
-
-# ------ This is just the initialization of the App  ----
-
-# =======================================================
-# The main App Class.
-# =======================================================
-
-
-class App(wx.App):
-    """Image Application."""
-
-    def OnInit(self):
-        """Create the Image Application."""
-        ImFrame(None, 'wxImage Example')
-        return True
-
-
-# ------------------------------------------------------
-# If this file is running as main or a
-# standalone test, begin execution here.
-# ------------------------------------------------------
-
-if __name__ == '__main__':
-    app = App(0)
-    app.MainLoop()
diff --git a/pydicom/contrib/pydicom_PIL.py b/pydicom/contrib/pydicom_PIL.py
deleted file mode 100644
index 867cfe4bb..000000000
--- a/pydicom/contrib/pydicom_PIL.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# pydicom_PIL.py
-
-"""View DICOM images using Python image Library (PIL)
-
-Usage:
->>> import pydicom
->>> from pydicom.contrib.pydicom_PIL import show_PIL
->>> ds = pydicom.read_file("filename")
->>> show_PIL(ds)
-
-Requires Numpy:
-    http://numpy.scipy.org/
-
-and Python Imaging Library:
-    http://www.pythonware.com/products/pil/
-
-"""
-# Copyright (c) 2009 Darcy Mason, Adit Panchal
-# This file is part of pydicom, relased under an MIT license.
-#    See the file LICENSE included with this distribution, also
-#    available at https://github.com/pydicom/pydicom
-
-# Based on image.py from pydicom version 0.9.3,
-#    LUT code added by Adit Panchal
-# Tested on Python 2.5.4 (32-bit) on Mac OS X 10.6
-#    using numpy 1.3.0 and PIL 1.1.7b1
-
-have_PIL = True
-try:
-    import PIL.Image
-except ImportError:
-    have_PIL = False
-
-have_numpy = True
-try:
-    import numpy as np
-except ImportError:
-    have_numpy = False
-
-
-def get_LUT_value(data, window, level):
-    """Apply the RGB Look-Up Table for the given
-       data and window/level value."""
-    if not have_numpy:
-        raise ImportError("Numpy is not available."
-                          "See http://numpy.scipy.org/"
-                          "to download and install")
-
-    return np.piecewise(data,
-                        [data <= (level - 0.5 - (window - 1) / 2),
-                         data > (level - 0.5 + (window - 1) / 2)],
-                        [0, 255, lambda data: ((data - (level - 0.5)) /
-                         (window - 1) + 0.5) * (255 - 0)])
-
-
-def get_PIL_image(dataset):
-    """Get Image object from Python Imaging Library(PIL)"""
-    if not have_PIL:
-        raise ImportError("Python Imaging Library is not available. "
-                          "See http://www.pythonware.com/products/pil/ "
-                          "to download and install")
-
-    if ('PixelData' not in dataset):
-        raise TypeError("Cannot show image -- DICOM dataset does not have "
-                        "pixel data")
-    # can only apply LUT if these window info exists
-    if ('WindowWidth' not in dataset) or ('WindowCenter' not in dataset):
-        bits = dataset.BitsAllocated
-        samples = dataset.SamplesPerPixel
-        if bits == 8 and samples == 1:
-            mode = "L"
-        elif bits == 8 and samples == 3:
-            mode = "RGB"
-        elif bits == 16:
-            # not sure about this -- PIL source says is 'experimental'
-            # and no documentation. Also, should bytes swap depending
-            # on endian of file and system??
-            mode = "I;16"
-        else:
-            raise TypeError("Don't know PIL mode for %d BitsAllocated "
-                            "and %d SamplesPerPixel" % (bits, samples))
-
-        # PIL size = (width, height)
-        size = (dataset.Columns, dataset.Rows)
-
-        # Recommended to specify all details
-        # by http://www.pythonware.com/library/pil/handbook/image.htm
-        im = PIL.Image.frombuffer(mode, size, dataset.PixelData,
-                                  "raw", mode, 0, 1)
-
-    else:
-        image = get_LUT_value(dataset.pixel_array, dataset.WindowWidth,
-                              dataset.WindowCenter)
-        # Convert mode to L since LUT has only 256 values:
-        #   http://www.pythonware.com/library/pil/handbook/image.htm
-        im = PIL.Image.fromarray(image).convert('L')
-
-    return im
-
-
-def show_PIL(dataset):
-    """Display an image using the Python Imaging Library (PIL)"""
-    im = get_PIL_image(dataset)
-    im.show()
diff --git a/pydicom/contrib/pydicom_Tkinter.py b/pydicom/contrib/pydicom_Tkinter.py
deleted file mode 100644
index a2232310b..000000000
--- a/pydicom/contrib/pydicom_Tkinter.py
+++ /dev/null
@@ -1,221 +0,0 @@
-# pydicom_Tkinter.py
-#
-# Copyright (c) 2009 Daniel Nanz
-# This file is released under the pydicom
-# (https://github.com/pydicom/pydicom)
-# license, see the file LICENSE available at
-# (https://github.com/pydicom/pydicom)
-#
-# revision history:
-# Dec-08-2009: version 0.1
-#
-# 0.1:   tested with pydicom version 0.9.3, Python version 2.6.2 (32-bit)
-#        under Windows XP Professional 2002, and Mac OS X 10.5.5,
-#        using numpy 1.3.0 and a small random selection of MRI and
-#        CT images.
-'''
-View DICOM images from pydicom
-
-requires numpy:  http://numpy.scipy.org/
-
-Usage:
-------
->>> import pydicom              # pydicom
->>> import pydicom.contrib.pydicom_Tkinter as pydicom_Tkinter    # this module
-
->>> df = pydicom.read_file(filename)
->>> pydicom_Tkinter.show_image(df)
-'''
-
-import tempfile
-import os
-
-from pydicom.compat import in_py2
-if in_py2:
-    import Tkinter as tkinter
-
-have_numpy = True
-try:
-    import numpy as np
-except ImportError:
-    # will not work...
-    have_numpy = False
-
-
-def get_PGM_bytedata_string(arr):
-    '''Given a 2D numpy array as input write
-    gray-value image data in the PGM
-    format into a byte string and return it.
-
-    arr: single-byte unsigned int numpy array
-    note: Tkinter's PhotoImage object seems to
-    accept only single-byte data
-    '''
-
-    if arr.dtype != np.uint8:
-        raise ValueError
-    if len(arr.shape) != 2:
-        raise ValueError
-
-    # array.shape is (#rows, #cols) tuple; PGM input needs this reversed
-    col_row_string = ' '.join(reversed([str(x) for x in arr.shape]))
-
-    bytedata_string = '\n'.join(('P5', col_row_string, str(arr.max()),
-                                 arr.tostring()))
-    return bytedata_string
-
-
-def get_PGM_from_numpy_arr(arr,
-                           window_center,
-                           window_width,
-                           lut_min=0,
-                           lut_max=255):
-    '''real-valued numpy input  ->  PGM-image formatted byte string
-
-    arr: real-valued numpy array to display as grayscale image
-    window_center, window_width: to define max/min values to be mapped to the
-                                 lookup-table range. WC/WW scaling is done
-                                 according to DICOM-3 specifications.
-    lut_min, lut_max: min/max values of (PGM-) grayscale table: do not change
-    '''
-
-    if np.isreal(arr).sum() != arr.size:
-        raise ValueError
-
-    # currently only support 8-bit colors
-    if lut_max != 255:
-        raise ValueError
-
-    if arr.dtype != np.float64:
-        arr = arr.astype(np.float64)
-
-    # LUT-specific array scaling
-    # width >= 1 (DICOM standard)
-    window_width = max(1, window_width)
-
-    wc, ww = np.float64(window_center), np.float64(window_width)
-    lut_range = np.float64(lut_max) - lut_min
-
-    minval = wc - 0.5 - (ww - 1.0) / 2.0
-    maxval = wc - 0.5 + (ww - 1.0) / 2.0
-
-    min_mask = (minval >= arr)
-    to_scale = (arr > minval) & (arr < maxval)
-    max_mask = (arr >= maxval)
-
-    if min_mask.any():
-        arr[min_mask] = lut_min
-    if to_scale.any():
-        arr[to_scale] = ((arr[to_scale] - (wc - 0.5)) /
-                         (ww - 1.0) + 0.5) * lut_range + lut_min
-    if max_mask.any():
-        arr[max_mask] = lut_max
-
-    # round to next integer values and convert to unsigned int
-    arr = np.rint(arr).astype(np.uint8)
-
-    # return PGM byte-data string
-    return get_PGM_bytedata_string(arr)
-
-
-def get_tkinter_photoimage_from_pydicom_image(data):
-    '''
-    Wrap data.pixel_array in a Tkinter PhotoImage instance,
-    after conversion into a PGM grayscale image.
-
-    This will fail if the "numpy" module is not
-    installed in the attempt of creating the data.pixel_array.
-
-    data:  object returned from pydicom.read_file()
-    side effect: may leave a temporary .pgm file on disk
-    '''
-
-    # get numpy array as representation of image data
-    arr = data.pixel_array.astype(np.float64)
-
-    # pixel_array seems to be the original, non-rescaled array.
-    # If present, window center and width refer to rescaled array
-    # -> do rescaling if possible.
-    if ('RescaleIntercept' in data) and ('RescaleSlope' in data):
-        intercept = data.RescaleIntercept  # single value
-        slope = data.RescaleSlope
-        arr = slope * arr + intercept
-
-    # get default window_center and window_width values
-    wc = (arr.max() + arr.min()) / 2.0
-    ww = arr.max() - arr.min() + 1.0
-
-    # overwrite with specific values from data, if available
-    if ('WindowCenter' in data) and ('WindowWidth' in data):
-        wc = data.WindowCenter
-        ww = data.WindowWidth
-        try:
-            wc = wc[0]  # can be multiple values
-        except Exception:
-            pass
-        try:
-            ww = ww[0]
-        except Exception:
-            pass
-
-    # scale array to account for center, width and PGM grayscale range,
-    # and wrap into PGM formatted ((byte-) string
-    pgm = get_PGM_from_numpy_arr(arr, wc, ww)
-
-    # create a PhotoImage
-    # for as yet unidentified reasons the following fails for certain
-    # window center/width values:
-    #         photo_image = Tkinter.PhotoImage(data=pgm, gamma=1.0)
-    #    Error with Python 2.6.2 under Windows XP:
-    #          (self.tk.call(('image', 'create', imgtype, name,) + options)
-    #          _tkinter.TclError: truncated PPM data
-    #    OsX: distorted images
-    # while all seems perfectly OK for other values of center/width or when
-    # the PGM is first written to a temporary file and read again
-
-    # write PGM file into temp dir
-    (os_id, abs_path) = tempfile.mkstemp(suffix='.pgm')
-    with open(abs_path, 'wb') as fd:
-        fd.write(pgm)
-
-    photo_image = tkinter.PhotoImage(file=abs_path, gamma=1.0)
-
-    # close and remove temporary file on disk
-    # os.close is needed under windows for os.remove not to fail
-    try:
-        os.close(os_id)
-        os.remove(abs_path)
-    except Exception:
-        pass  # silently leave file on disk in temp-like directory
-
-    return photo_image
-
-
-def show_image(data, block=True, master=None):
-    '''
-    Get minimal Tkinter GUI and display a pydicom data.pixel_array
-
-    data: object returned from pydicom.read_file()
-    block: if True run Tk mainloop() to show the image
-    master: use with block==False and an existing
-    Tk widget as parent widget
-
-    side effects: may leave a temporary .pgm file on disk
-    '''
-    frame = tkinter.Frame(master=master, background='#000')
-    if 'SeriesDescription' in data and 'InstanceNumber' in data:
-        title = ', '.join(('Ser: ' + data.SeriesDescription,
-                           'Img: ' + str(data.InstanceNumber)))
-    else:
-        title = 'pydicom image'
-    frame.master.title(title)
-    photo_image = get_tkinter_photoimage_from_pydicom_image(data)
-    label = tkinter.Label(frame, image=photo_image, background='#000')
-
-    # keep a reference to avoid disappearance upon garbage collection
-    label.photo_reference = photo_image
-    label.grid()
-    frame.grid()
-
-    if block:
-        frame.mainloop()
diff --git a/pydicom/contrib/pydicom_series.py b/pydicom/contrib/pydicom_series.py
deleted file mode 100644
index 23ce8c658..000000000
--- a/pydicom/contrib/pydicom_series.py
+++ /dev/null
@@ -1,686 +0,0 @@
-# dicom_series.py
-"""
-By calling the function read_files with a directory name or list
-of files as an argument, a list of DicomSeries instances can be
-obtained. A DicomSeries object has some attributes that give
-information about the serie (such as shape, sampling, suid) and
-has an info attribute, which is a pydicom.DataSet instance containing
-information about the first dicom file in the serie. The data can
-be obtained using the get_pixel_array() method, which produces a
-3D numpy array if there a multiple files in the serie.
-
-This module can deal with gated data, in which case a DicomSeries
-instance is created for each 3D volume.
-
-"""
-from __future__ import print_function
-#
-# Copyright (c) 2010 Almar Klein
-# This file is released under the pydicom license.
-#    See the file LICENSE included with the pydicom distribution, also
-#    available at https://github.com/pydicom/pydicom
-#
-
-# I (Almar) performed some test to loading a series of data
-# in two different ways: loading all data, and deferring loading
-# the data. Both ways seem equally fast on my system. I have to
-# note that results can differ quite a lot depending on the system,
-# but still I think this suggests that deferred reading is in
-# general not slower. I think deferred loading of the pixel data
-# can be advantageous because maybe not all data of all series
-# is needed. Also it simply saves memory, because the data is
-# removed from the Dataset instances.
-# In the few result below, cold means reading for the first time,
-# warm means reading 2nd/3d/etc time.
-# - Full loading of data, cold: 9 sec
-# - Full loading of data, warm: 3 sec
-# - Deferred loading of data, cold: 9 sec
-# - Deferred loading of data, warm: 3 sec
-
-import os
-import time
-import gc
-
-import pydicom
-from pydicom.sequence import Sequence
-from pydicom import compat
-
-# Try importing numpy
-try:
-    import numpy as np
-    have_numpy = True
-except ImportError:
-    np = None  # NOQA
-    have_numpy = False
-
-
-# Helper functions and classes
-class ProgressBar(object):
-    """ To print progress to the screen.
-    """
-
-    def __init__(self, char='-', length=20):
-        self.char = char
-        self.length = length
-        self.progress = 0.0
-        self.nbits = 0
-        self.what = ''
-
-    def Start(self, what=''):
-        """ Start(what='')
-        Start the progress bar, displaying the given text first.
-        Make sure not to print anything untill after calling
-        Finish(). Messages can be printed while displaying
-        progess by using printMessage().
-        """
-        self.what = what
-        self.progress = 0.0
-        self.nbits = 0
-        sys.stdout.write(what + " [")
-
-    def Stop(self, message=""):
-        """ Stop the progress bar where it is now.
-        Optionally print a message behind it."""
-        delta = int(self.length - self.nbits)
-        sys.stdout.write(" " * delta + "] " + message + "\n")
-
-    def Finish(self, message=""):
-        """ Finish the progress bar, setting it to 100% if it
-        was not already. Optionally print a message behind the bar.
-        """
-        delta = int(self.length - self.nbits)
-        sys.stdout.write(self.char * delta + "] " + message + "\n")
-
-    def Update(self, newProgress):
-        """ Update progress. Progress is given as a number
-        between 0 and 1.
-        """
-        self.progress = newProgress
-        required = self.length * (newProgress)
-        delta = int(required - self.nbits)
-        if delta > 0:
-            sys.stdout.write(self.char * delta)
-            self.nbits += delta
-
-    def PrintMessage(self, message):
-        """ Print a message (for example a warning).
-        The message is printed behind the progress bar,
-        and a new bar is started.
-        """
-        self.Stop(message)
-        self.Start(self.what)
-
-
-def _dummyProgressCallback(progress):
-    """ A callback to indicate progress that does nothing. """
-    pass
-
-
-_progressBar = ProgressBar()
-
-
-def _progressCallback(progress):
-    """ The default callback for displaying progress. """
-    if isinstance(progress, compat.string_types):
-        _progressBar.Start(progress)
-        _progressBar._t0 = time.time()
-    elif progress is None:
-        dt = time.time() - _progressBar._t0
-        _progressBar.Finish('%2.2f seconds' % dt)
-    else:
-        _progressBar.Update(progress)
-
-
-def _listFiles(files, path):
-    """List all files in the directory, recursively. """
-
-    for item in os.listdir(path):
-        item = os.path.join(path, item)
-        if os.path.isdir(item):
-            _listFiles(files, item)
-        else:
-            files.append(item)
-
-
-def _splitSerieIfRequired(serie, series):
-    """ _splitSerieIfRequired(serie, series)
-    Split the serie in multiple series if this is required.
-    The choice is based on examing the image position relative to
-    the previous image. If it differs too much, it is assumed
-    that there is a new dataset. This can happen for example in
-    unspitted gated CT data.
-    """
-
-    # Sort the original list and get local name
-    serie._sort()
-    L = serie._datasets
-
-    # Init previous slice
-    ds1 = L[0]
-
-    # Check whether we can do this
-    if "ImagePositionPatient" not in ds1:
-        return
-
-    # Initialize a list of new lists
-    L2 = [[ds1]]
-
-    # Init slice distance estimate
-    distance = 0
-
-    for index in range(1, len(L)):
-
-        # Get current slice
-        ds2 = L[index]
-
-        # Get positions
-        pos1 = float(ds1.ImagePositionPatient[2])
-        pos2 = float(ds2.ImagePositionPatient[2])
-
-        # Get distances
-        newDist = abs(pos1 - pos2)
-        # deltaDist = abs(firstPos-pos2)
-
-        # If the distance deviates more than 2x from what we've seen,
-        # we can agree it's a new dataset.
-        if distance and newDist > 2.1 * distance:
-            L2.append([])
-            distance = 0
-        else:
-            # Test missing file
-            if distance and newDist > 1.5 * distance:
-                print('Warning: missing file after "%s"' % ds1.filename)
-            distance = newDist
-
-        # Add to last list
-        L2[-1].append(ds2)
-
-        # Store previous
-        ds1 = ds2
-
-    # Split if we should
-    if len(L2) > 1:
-
-        # At what position are we now?
-        i = series.index(serie)
-
-        # Create new series
-        series2insert = []
-        for L in L2:
-            newSerie = DicomSeries(serie.suid, serie._showProgress)
-            newSerie._datasets = Sequence(L)
-            series2insert.append(newSerie)
-
-        # Insert series and remove self
-        for newSerie in reversed(series2insert):
-            series.insert(i, newSerie)
-        series.remove(serie)
-
-
-pixelDataTag = pydicom.tag.Tag(0x7fe0, 0x0010)
-
-
-def _getPixelDataFromDataset(ds):
-    """ Get the pixel data from the given dataset. If the data
-    was deferred, make it deferred again, so that memory is
-    preserved. Also applies RescaleSlope and RescaleIntercept
-    if available. """
-
-    # Get original element
-    el = dict.__getitem__(ds, pixelDataTag)
-
-    # Get data
-    data = ds.pixel_array
-
-    # Remove data (mark as deferred)
-    dict.__setitem__(ds, pixelDataTag, el)
-    del ds._pixel_array
-
-    # Obtain slope and offset
-    slope = 1
-    offset = 0
-    needFloats = False
-    needApplySlopeOffset = False
-    if 'RescaleSlope' in ds:
-        needApplySlopeOffset = True
-        slope = ds.RescaleSlope
-    if 'RescaleIntercept' in ds:
-        needApplySlopeOffset = True
-        offset = ds.RescaleIntercept
-    if int(slope) != slope or int(offset) != offset:
-        needFloats = True
-    if not needFloats:
-        slope, offset = int(slope), int(offset)
-
-    # Apply slope and offset
-    if needApplySlopeOffset:
-
-        # Maybe we need to change the datatype?
-        if data.dtype in [np.float32, np.float64]:
-            pass
-        elif needFloats:
-            data = data.astype(np.float32)
-        else:
-            # Determine required range
-            minReq, maxReq = data.min(), data.max()
-            minReq = min(
-                [minReq, minReq * slope + offset, maxReq * slope + offset])
-            maxReq = max(
-                [maxReq, minReq * slope + offset, maxReq * slope + offset])
-
-            # Determine required datatype from that
-            dtype = None
-            if minReq < 0:
-                # Signed integer type
-                maxReq = max([-minReq, maxReq])
-                if maxReq < 2**7:
-                    dtype = np.int8
-                elif maxReq < 2**15:
-                    dtype = np.int16
-                elif maxReq < 2**31:
-                    dtype = np.int32
-                else:
-                    dtype = np.float32
-            else:
-                # Unsigned integer type
-                if maxReq < 2**8:
-                    dtype = np.uint8
-                elif maxReq < 2**16:
-                    dtype = np.uint16
-                elif maxReq < 2**32:
-                    dtype = np.uint32
-                else:
-                    dtype = np.float32
-
-            # Change datatype
-            if dtype != data.dtype:
-                data = data.astype(dtype)
-
-        # Apply slope and offset
-        data *= slope
-        data += offset
-
-    # Done
-    return data
-
-
-# The public functions and classes
-
-
-def read_files(path, showProgress=False, readPixelData=False, force=False):
-    """ read_files(path, showProgress=False, readPixelData=False)
-
-    Reads dicom files and returns a list of DicomSeries objects, which
-    contain information about the data, and can be used to load the
-    image or volume data.
-
-    The parameter "path" can also be a list of files or directories.
-
-    If the callable "showProgress" is given, it is called with a single
-    argument to indicate the progress. The argument is a string when a
-    progress is started (indicating what is processed). A float indicates
-    progress updates. The paremeter is None when the progress is finished.
-    When "showProgress" is True, a default callback is used that writes
-    to stdout. By default, no progress is shown.
-
-    if readPixelData is True, the pixel data of all series is read. By
-    default the loading of pixeldata is deferred until it is requested
-    using the DicomSeries.get_pixel_array() method. In general, both
-    methods should be equally fast.
-    """
-
-    # Init list of files
-    files = []
-
-    # Obtain data from the given path
-    if isinstance(path, compat.string_types):
-        # Make dir nice
-        basedir = os.path.abspath(path)
-        # Check whether it exists
-        if not os.path.isdir(basedir):
-            raise ValueError('The given path is not a valid directory.')
-        # Find files recursively
-        _listFiles(files, basedir)
-
-    elif isinstance(path, (tuple, list)):
-        # Iterate over all elements, which can be files or directories
-        for p in path:
-            if os.path.isdir(p):
-                _listFiles(files, os.path.abspath(p))
-            elif os.path.isfile(p):
-                files.append(p)
-            else:
-                print("Warning, the path '%s' is not valid." % p)
-    else:
-        raise ValueError('The path argument must be a string or list.')
-
-    # Set default progress callback?
-    if showProgress is True:
-        showProgress = _progressCallback
-    if not hasattr(showProgress, '__call__'):
-        showProgress = _dummyProgressCallback
-
-    # Set defer size
-    deferSize = 16383  # 128**2-1
-    if readPixelData:
-        deferSize = None
-
-    # Gather file data and put in DicomSeries
-    series = {}
-    count = 0
-    showProgress('Loading series information:')
-    for filename in files:
-
-        # Skip DICOMDIR files
-        if filename.count("DICOMDIR"):
-            continue
-
-        # Try loading dicom ...
-        try:
-            dcm = pydicom.read_file(filename, deferSize, force=force)
-        except pydicom.filereader.InvalidDicomError:
-            continue  # skip non-dicom file
-        except Exception as why:
-            if showProgress is _progressCallback:
-                _progressBar.PrintMessage(str(why))
-            else:
-                print('Warning:', why)
-            continue
-
-        # Get SUID and register the file with an existing or new series object
-        try:
-            suid = dcm.SeriesInstanceUID
-        except AttributeError:
-            continue  # some other kind of dicom file
-        if suid not in series:
-            series[suid] = DicomSeries(suid, showProgress)
-        series[suid]._append(dcm)
-
-        # Show progress (note that we always start with a 0.0)
-        showProgress(float(count) / len(files))
-        count += 1
-
-    # Finish progress
-    showProgress(None)
-
-    # Make a list and sort, so that the order is deterministic
-    series = list(series.values())
-    series.sort(key=lambda x: x.suid)
-
-    # Split series if necessary
-    for serie in reversed([serie for serie in series]):
-        _splitSerieIfRequired(serie, series)
-
-    # Finish all series
-    showProgress('Analysing series')
-    series_ = []
-    for i in range(len(series)):
-        try:
-            series[i]._finish()
-            series_.append(series[i])
-        except Exception:
-            pass  # Skip serie (probably report-like file without pixels)
-        showProgress(float(i + 1) / len(series))
-    showProgress(None)
-
-    return series_
-
-
-class DicomSeries(object):
-    """ DicomSeries
-    This class represents a serie of dicom files that belong together.
-    If these are multiple files, they represent the slices of a volume
-    (like for CT or MRI). The actual volume can be obtained using loadData().
-    Information about the data can be obtained using the info attribute.
-    """
-
-    # To create a DicomSeries object, start by making an instance and
-    # append files using the "_append" method. When all files are
-    # added, call "_sort" to sort the files, and then "_finish" to evaluate
-    # the data, perform some checks, and set the shape and sampling
-    # attributes of the instance.
-
-    def __init__(self, suid, showProgress):
-        # Init dataset list and the callback
-        self._datasets = Sequence()
-        self._showProgress = showProgress
-
-        # Init props
-        self._suid = suid
-        self._info = None
-        self._shape = None
-        self._sampling = None
-
-    @property
-    def suid(self):
-        """ The Series Instance UID. """
-        return self._suid
-
-    @property
-    def shape(self):
-        """ The shape of the data (nz, ny, nx).
-        If None, the serie contains a single dicom file. """
-        return self._shape
-
-    @property
-    def sampling(self):
-        """ The sampling (voxel distances) of the data (dz, dy, dx).
-        If None, the serie contains a single dicom file. """
-        return self._sampling
-
-    @property
-    def info(self):
-        """ A DataSet instance containing the information as present in the
-        first dicomfile of this serie. """
-        return self._info
-
-    @property
-    def description(self):
-        """ A description of the dicom series. Used fields are
-        PatientName, shape of the data, SeriesDescription,
-        and ImageComments.
-        """
-
-        info = self.info
-
-        # If no info available, return simple description
-        if info is None:
-            return "DicomSeries containing %i images" % len(self._datasets)
-
-        fields = []
-
-        # Give patient name
-        if 'PatientName' in info:
-            fields.append("" + info.PatientName)
-
-        # Also add dimensions
-        if self.shape:
-            tmp = [str(d) for d in self.shape]
-            fields.append('x'.join(tmp))
-
-        # Try adding more fields
-        if 'SeriesDescription' in info:
-            fields.append("'" + info.SeriesDescription + "'")
-        if 'ImageComments' in info:
-            fields.append("'" + info.ImageComments + "'")
-
-        # Combine
-        return ' '.join(fields)
-
-    def __repr__(self):
-        adr = hex(id(self)).upper()
-        data_len = len(self._datasets)
-        return "<DicomSeries with %i images at %s>" % (data_len, adr)
-
-    def get_pixel_array(self):
-        """ get_pixel_array()
-
-        Get (load) the data that this DicomSeries represents, and return
-        it as a numpy array. If this serie contains multiple images, the
-        resulting array is 3D, otherwise it's 2D.
-
-        If RescaleSlope and RescaleIntercept are present in the dicom info,
-        the data is rescaled using these parameters. The data type is chosen
-        depending on the range of the (rescaled) data.
-
-        """
-
-        # Can we do this?
-        if not have_numpy:
-            msg = "The Numpy package is required to use get_pixel_array.\n"
-            raise ImportError(msg)
-
-        # It's easy if no file or if just a single file
-        if len(self._datasets) == 0:
-            raise ValueError('Serie does not contain any files.')
-        elif len(self._datasets) == 1:
-            ds = self._datasets[0]
-            slice = _getPixelDataFromDataset(ds)
-            return slice
-
-        # Check info
-        if self.info is None:
-            raise RuntimeError("Cannot return volume if series not finished.")
-
-        # Set callback to update progress
-        showProgress = self._showProgress
-
-        # Init data (using what the dicom packaged produces as a reference)
-        ds = self._datasets[0]
-        slice = _getPixelDataFromDataset(ds)
-        # vol = Aarray(self.shape, self.sampling, fill=0, dtype=slice.dtype)
-        vol = np.zeros(self.shape, dtype=slice.dtype)
-        vol[0] = slice
-
-        # Fill volume
-        showProgress('Loading data:')
-        ll = self.shape[0]
-        for z in range(1, ll):
-            ds = self._datasets[z]
-            vol[z] = _getPixelDataFromDataset(ds)
-            showProgress(float(z) / ll)
-
-        # Finish
-        showProgress(None)
-
-        # Done
-        gc.collect()
-        return vol
-
-    def _append(self, dcm):
-        """ _append(dcm)
-        Append a dicomfile (as a pydicom.dataset.FileDataset) to the series.
-        """
-        self._datasets.append(dcm)
-
-    def _sort(self):
-        """ sort()
-        Sort the datasets by instance number.
-        """
-        self._datasets.sort(key=lambda k: k.InstanceNumber)
-
-    def _finish(self):
-        """ _finish()
-
-        Evaluate the series of dicom files. Together they should make up
-        a volumetric dataset. This means the files should meet certain
-        conditions. Also some additional information has to be calculated,
-        such as the distance between the slices. This method sets the
-        attributes for "shape", "sampling" and "info".
-
-        This method checks:
-          * that there are no missing files
-          * that the dimensions of all images match
-          * that the pixel spacing of all images match
-
-        """
-
-        # The datasets list should be sorted by instance number
-        L = self._datasets
-        if len(L) == 0:
-            return
-        elif len(L) < 2:
-            # Set attributes
-            ds = self._datasets[0]
-            self._info = self._datasets[0]
-            self._shape = [ds.Rows, ds.Columns]
-            self._sampling = [
-                float(ds.PixelSpacing[0]), float(ds.PixelSpacing[1])
-            ]
-            return
-
-        # Get previous
-        ds1 = L[0]
-
-        # Init measures to calculate average of
-        distance_sum = 0.0
-
-        # Init measures to check (these are in 2D)
-        dimensions = ds1.Rows, ds1.Columns
-
-        # row, column
-        sampling = float(ds1.PixelSpacing[0]), float(ds1.PixelSpacing[1])
-
-        for index in range(len(L)):
-            # The first round ds1 and ds2 will be the same, for the
-            # distance calculation this does not matter
-
-            # Get current
-            ds2 = L[index]
-
-            # Get positions
-            pos1 = float(ds1.ImagePositionPatient[2])
-            pos2 = float(ds2.ImagePositionPatient[2])
-
-            # Update distance_sum to calculate distance later
-            distance_sum += abs(pos1 - pos2)
-
-            # Test measures
-            dimensions2 = ds2.Rows, ds2.Columns
-            sampling2 = float(ds2.PixelSpacing[0]), float(ds2.PixelSpacing[1])
-            if dimensions != dimensions2:
-                # We cannot produce a volume if the dimensions match
-                raise ValueError('Dimensions of slices does not match.')
-            if sampling != sampling2:
-                # We can still produce a volume, but we should notify the user
-                msg = 'Warning: sampling does not match.'
-                if self._showProgress is _progressCallback:
-                    _progressBar.PrintMessage(msg)
-                else:
-                    print(msg)
-            # Store previous
-            ds1 = ds2
-
-        # Create new dataset by making a deep copy of the first
-        info = pydicom.dataset.Dataset()
-        firstDs = self._datasets[0]
-        for key in firstDs.keys():
-            if key != (0x7fe0, 0x0010):
-                el = firstDs[key]
-                info.add_new(el.tag, el.VR, el.value)
-
-        # Finish calculating average distance
-        # (Note that there are len(L)-1 distances)
-        distance_mean = distance_sum / (len(L) - 1)
-
-        # Store information that is specific for the serie
-        self._shape = [len(L), ds2.Rows, ds2.Columns]
-        self._sampling = [distance_mean, float(ds2.PixelSpacing[0]),
-                          float(ds2.PixelSpacing[1])]
-
-        # Store
-        self._info = info
-
-
-if __name__ == '__main__':
-    import sys
-
-    if len(sys.argv) != 2:
-        print("Expected a single argument: a directory with dicom files in it")
-    else:
-        adir = sys.argv[1]
-        t0 = time.time()
-        all_series = read_files(adir, None, False)
-        print("Summary of each series:")
-        for series in all_series:
-            print(series.description)
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
index 8c7605a54..664cc62cc 100644
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -335,11 +335,10 @@ class Dataset(dict):
         """
         # Force zip object into a list in case of python3. Also backwards
         # compatible
-        meths = set(
-            list(zip(*inspect.getmembers(Dataset, inspect.isroutine)))[0])
-        props = set(
-            list(zip(*inspect.getmembers(Dataset, inspect.isdatadescriptor)))[
-                0])
+        meths = set(list(zip(
+            *inspect.getmembers(self.__class__, inspect.isroutine)))[0])
+        props = set(list(zip(
+            *inspect.getmembers(self.__class__, inspect.isdatadescriptor)))[0])
         dicom_names = set(self.dir())
         alldir = sorted(props | meths | dicom_names)
         return alldir
diff --git a/pydicom/valuerep.py b/pydicom/valuerep.py
index 655fa821e..f217e3eee 100644
--- a/pydicom/valuerep.py
+++ b/pydicom/valuerep.py
@@ -754,7 +754,7 @@ class PersonNameUnicode(PersonNameBase, compat.text_type):
         return new_person
 
     def __deepcopy__(self, memo):
-        """Make a correct deep copy of the object.
+        """Make correctly a deep copy of the object.
         Needed because of the overwritten __new__.
         """
         name = compat.text_type(self).encode('utf8')


Test patch:
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index bce2660bf..87b4f19b0 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -313,6 +313,22 @@ class DatasetTests(unittest.TestCase):
             'Johnny',
             "set by tag failed")
 
+    def test_dir_subclass(self):
+        """Dataset.__dir__ returns class specific dir"""
+        class DSP(Dataset):
+            def test_func(self):
+                pass
+
+        ds = DSP()
+        assert hasattr(ds, 'test_func')
+        assert callable(ds.test_func)
+        assert 'test_func' in dir(ds)
+
+        ds = Dataset()
+        assert hasattr(ds, 'group_dataset')
+        assert callable(ds.group_dataset)
+        assert 'group_dataset' in dir(ds)
+
     def test_dir(self):
         """Dataset.dir() returns sorted list of named data_elements."""
         ds = self.dummy_dataset()