Repository: pydicom/pydicom

Issue:
Feature: to/from DICOM JSON Model
For DICOMweb [client code](https://github.com/clindatsci/dicomweb-client), dataset information comes in ["DICOM JSON Model"](http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_F.html) form and it would be convenient to convert these to/from pydicom datasets.  The back and forth functionality exists already in [JavaScript](https://github.com/dcmjs-org/dcmjs) and the dicom to json part exists in [C++](https://support.dcmtk.org/docs/dcm2json.html).  It would be great to have similar functionality in Python and it probably belongs in pydicom.

@hackermd and I have python implementations that could be used as the basis for a pull request.



Discussion:
pep8speaks: Hello @pieper! Thanks for opening 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/dataset.py`](https://github.com/pydicom/pydicom/blob/dd94587283efab5ef722f8e72376bd0578e8da6b/pydicom/dataset.py):

> [Line 1872:80](https://github.com/pydicom/pydicom/blob/dd94587283efab5ef722f8e72376bd0578e8da6b/pydicom/dataset.py#L1872): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (91 > 79 characters)
> [Line 1940:80](https://github.com/pydicom/pydicom/blob/dd94587283efab5ef722f8e72376bd0578e8da6b/pydicom/dataset.py#L1940): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (91 > 79 characters)




pieper: @hackermd could you have a look through this?  I'd like help with the TODO items... 😄 
hackermd: @pieper Thanks Steve for moving this forward. I've started reviewing the code. Can you please give me push access to your fork?
hackermd: The following program now completes successfully:

```python
import pydicom
import dicomweb_client

if __name__ == '__main__':
    url = 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs'
    client = dicomweb_client.DICOMwebClient(url)

    metadata = client.search_for_instances()
    dataset = pydicom.dataset.Dataset.from_json(metadata[0])
    print('Result of "from_json()"\n')
    print(dataset)
    print('\n---\n')
    print('Result of "to_json()"\n')
    print(dataset.to_json())

```

```
Result of "from_json()"

(0008, 0005) Specific Character Set              CS: ['ISO_IR 100']
(0008, 0016) SOP Class UID                       UI: VL Whole Slide Microscopy Image Storage
(0008, 0018) SOP Instance UID                    UI: 1.2.392.200140.2.1.1.1.4.799008771.2448.1519719572.520
(0008, 0020) Study Date                          DA: '20180227'
(0008, 0030) Study Time                          TM: '091932'
(0008, 0050) Accession Number                    SH: '2015031299'
(0008, 0054) Retrieve AE Title                   AE: ['DCM4CHEE']
(0008, 0056) Instance Availability               CS: 'ONLINE'
(0008, 0060) Modality                            CS: 'SM'
(0008, 0061) Modalities in Study                 CS: ['SM', 'SR']
(0008, 0090) Referring Physician's Name          PN: ''
(0008, 1190) Retrieve URL                        UR: 'http://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518/series/1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519/instances/1.2.392.200140.2.1.1.1.4.799008771.2448.1519719572.520'
(0010, 0010) Patient's Name                      PN: ''
(0010, 0020) Patient ID                          LO: 'PID_08154711'
(0010, 0030) Patient's Birth Date                DA: None
(0010, 0040) Patient's Sex                       CS: None
(0020, 000d) Study Instance UID                  UI: 1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518
(0020, 000e) Series Instance UID                 UI: 1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519
(0020, 0010) Study ID                            SH: 'SID_1001'
(0020, 0011) Series Number                       IS: "1"
(0020, 0013) Instance Number                     IS: "1"
(0020, 1206) Number of Study Related Series      IS: "25"
(0020, 1208) Number of Study Related Instances   IS: "30"
(0020, 1209) Number of Series Related Instances  IS: "6"
(0028, 0008) Number of Frames                    IS: "1"
(0028, 0010) Rows                                US: 1892
(0028, 0011) Columns                             US: 2048
(0028, 0100) Bits Allocated                      US: 8

---

Result of "to_json()"

{'00080005': {'vr': 'CS', 'Value': [['ISO_IR 100']]}, '00080016': {'vr': 'UI', 'Value': ['1.2.840.10008.5.1.4.1.1.77.1.6']}, '00080018': {'vr': 'UI', 'Value': ['1.2.392.200140.2.1.1.1.4.799008771.2448.1519719572.520']}, '00080020': {'vr': 'DA', 'Value': ['20180227']}, '00080030': {'vr': 'TM', 'Value': ['091932']}, '00080050': {'vr': 'SH', 'Value': ['2015031299']}, '00080054': {'vr': 'AE', 'Value': [['DCM4CHEE']]}, '00080056': {'vr': 'CS', 'Value': ['ONLINE']}, '00080060': {'vr': 'CS', 'Value': ['SM']}, '00080061': {'vr': 'CS', 'Value': ['SM', 'SR']}, '00080090': {'vr': 'PN', 'Value': [{'Alphabetic': ''}]}, '00081190': {'vr': 'UR', 'Value': ['http://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518/series/1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519/instances/1.2.392.200140.2.1.1.1.4.799008771.2448.1519719572.520']}, '00100010': {'vr': 'PN', 'Value': [{'Alphabetic': ''}]}, '00100020': {'vr': 'LO', 'Value': ['PID_08154711']}, '00100030': {'vr': 'DA'}, '00100040': {'vr': 'CS'}, '0020000D': {'vr': 'UI', 'Value': ['1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518']}, '0020000E': {'vr': 'UI', 'Value': ['1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519']}, '00200010': {'vr': 'SH', 'Value': ['SID_1001']}, '00200011': {'vr': 'IS', 'Value': ["1"]}, '00200013': {'vr': 'IS', 'Value': ["1"]}, '00201206': {'vr': 'IS', 'Value': ["25"]}, '00201208': {'vr': 'IS', 'Value': ["30"]}, '00201209': {'vr': 'IS', 'Value': ["6"]}, '00280008': {'vr': 'IS', 'Value': ["1"]}, '00280010': {'vr': 'US', 'Value': [1892]}, '00280011': {'vr': 'US', 'Value': [2048]}, '00280100': {'vr': 'US', 'Value': [8]}}
```

Note that `from_json()` now accepts a `dict` instead of a `str` and `to_json()` returns a `dict` instead of a `str`. Thereby users have more flexibility in encoding JSON strings (indents, etc.).

We need to add more unit tests for the `to_json()` and `from_json()` methods. For `from_json()`, we can reuse tests from the `dicomweb_cient` package: https://github.com/clindatsci/dicomweb-client/blob/master/src/dicomweb_client/tests/test_api.py#L519-L591 
darcymason: @pieper, @hackermd would it be possible to add unit tests in time for 1.3 release, i.e. next couple of weeks?
pieper: Hi @darcymason yes, I think it's pretty close.  Both @hackermd and I have been using the base code in other contexts so it should be fairly solid.  We might end up with a list of caveats but I don't think any would be show stoppers for a release (certainly they shouldn't break anything or introduce incompatible changes).  Markus and I plan to talk today and will report back.

Patch:
diff --git a/.gitignore b/.gitignore
index 0d0419e2c..10288e20d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,9 @@ doc/_build/*
 *.ipynb
 .ipynb_checkpoints/*
 tests/test_pixel.py
+
+# mypy
+pydicom/.mypy_cache/*
+
+# vscode
+.vscode/*
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index a2976a1bb..7db9f5f31 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -33,7 +33,7 @@ install:
 
   # Installed prebuilt dependencies from conda
   - "conda install pip numpy nose wheel pytest matplotlib -y -q"
-  - "conda install -y -q setuptools"
+  - "conda --version"
   - "pip install nose-timer nose-exclude"
 
   # Install other pydicom dependencies
diff --git a/build_tools/travis/install.sh b/build_tools/travis/install.sh
index 1e2b19ea2..76850e53d 100755
--- a/build_tools/travis/install.sh
+++ b/build_tools/travis/install.sh
@@ -32,7 +32,8 @@ if [[ "$DISTRIB" == "conda" ]]; then
     MINICONDA_PATH=/home/travis/miniconda
     chmod +x miniconda.sh && ./miniconda.sh -b -p $MINICONDA_PATH
     export PATH=$MINICONDA_PATH/bin:$PATH
-    conda update --yes conda
+    conda --version
+    # conda update --yes conda
 
     # Configure the conda environment and put it in the path using the
     # provided versions
diff --git a/doc/base_element.rst b/doc/base_element.rst
index b8b4fb426..bdb5b6099 100644
--- a/doc/base_element.rst
+++ b/doc/base_element.rst
@@ -61,6 +61,13 @@ number::
 In the latter case (using the tag number directly) a DataElement instance is
 returned, so the ``.value`` must be used to get the value.
 
+.. note::
+
+    In pydicom, private data elements are displayed with square brackets
+    around the name (if the name is known to pydicom).  These are shown for convenience only;
+    the descriptive name in brackets cannot be used to retrieve data elements.
+    See details in :doc:`private_data_elements`.
+
 You can also set values by name (DICOM keyword) or tag number::
 
   >>> ds.PatientID = "12345"
@@ -95,22 +102,22 @@ convention::
 
   >>> ds.BeamSequence[0].BeamName
   'Field 1'
+  >>> # Or, set an intermediate variable to a dataset in the list
+  >>> beam1 = ds.BeamSequence[0]  # First dataset in the sequence
+  >>> beam1.BeamName
+  'Field 1'
 
 See :ref:`sphx_glr_auto_examples_metadata_processing_plot_sequences.py`.
 
 Using DICOM keywords is the recommended way to access data elements, but you
 can also use the tag numbers directly, such as::
 
-  >>> # Same thing with tag numbers:
+  >>> # Same thing with tag numbers - much harder to read:
+  >>> # Really should only be used if DICOM keyword not in pydicom dictionary
   >>> ds[0x300a,0xb0][0][0x300a,0xc2].value
   'Field 1'
-  >>> # yet another way, using another variable
-  >>> beam1=ds[0x300a,0xb0][0]
-  >>> beam1.BeamName, beam1[0x300a,0xc2].value
-  ('Field 1', 'Field 1')
 
-
-If you don't remember or know the exact tag name, :class:`dataset.Dataset`
+If you don't remember or know the exact tag name (aka DICOM keyword), :class:`dataset.Dataset`
 provides a handy :func:`dataset.Dataset.dir` method, useful during interactive
 sessions at the Python prompt::
 
@@ -126,8 +133,11 @@ have the specified string anywhere in the name (case insensitive).
    available in the dataset.
 
 You can also see all the names that pydicom knows about by viewing the
-``_dicom_dict.py`` file. You could modify that file to add tags that pydicom
-doesn't already know about.
+``_dicom_dict.py`` file. It should not normally be necessary, but you can add
+your own entries to the DICOM dictionary at run time using :func:`datadict.add_dict_entries`
+or :func:`datadict.add_dict_entry`.  Similarly, you can add private data elements
+to the private dictionary using :func:`datadict.add_private_dict_entries` or
+:func:`datadict.add_private_dict_entries`.
 
 Under the hood, :class:`dataset.Dataset` stores a DataElement object for each
 item, but when accessed by name (e.g. ``ds.PatientName``) only the ``value`` of
@@ -152,7 +162,7 @@ use the `in` keyword::
   >>> "PatientName" in ds
   True
 
-To remove a data element from the dataset,  use :func:`dataset.Dataset.del`::
+To remove a data element from the dataset,  use python's `del` statement::
 
   >>> del ds.SoftwareVersions   # or del ds[0x0018, 0x1020]
 
@@ -163,7 +173,7 @@ To work with pixel data, the raw bytes are available through the usual tag::
   >>> ds = pydicom.dcmread(filename)
   >>> pixel_bytes = ds.PixelData
 
-but to work with them in a more intelligent way, use ``pixel_array``
+but to work with them in a more intelligent way, use :func:`Dataset.pixel_array`
 (requires the `NumPy library <http://numpy.org>`_)::
 
   >>> pix = ds.pixel_array
@@ -197,21 +207,26 @@ which stores the following things:
 Tag
 ---
 
+Tag is not generally used directly in user code, as Tags are automatically created 
+when you assign or read data elements using the DICOM keywords as illustrated in
+sections above.
+
 The Tag class is derived from Python's ``int``, so in effect, it is just
 a number with some extra behaviour:
 
   * Tag enforces that the DICOM tag fits in the expected 4-byte (group,element)
-  * A Tag instance can be created from an int or from a tuple containing
-    the (group,element) separately::
+  * A Tag instance can be created from an int or a tuple containing
+    the (group,element), or from the DICOM keyword::
 
       >>> from pydicom.tag import Tag
-      >>> t1=Tag(0x00100010) # all of these are equivalent
-      >>> t2=Tag(0x10,0x10)
-      >>> t3=Tag((0x10, 0x10))
+      >>> t1 = Tag(0x00100010) # all of these are equivalent
+      >>> t2 = Tag(0x10,0x10)
+      >>> t3 = Tag((0x10, 0x10))
+      >>> t4 = Tag("PatientName")
       >>> t1
       (0010, 0010)
-      >>> t1==t2, t1==t3
-      (True, True)
+      >>> t1==t2, t1==t3, t1==t4
+      (True, True, True)
 
   * Tag has properties group and element (or elem) to return the group and
     element portions
@@ -224,3 +239,6 @@ Sequence
 Sequence is derived from Python's ``list``. The only added functionality is
 to make string representations prettier. Otherwise all the usual methods of
 ``list`` like item selection, append, etc. are available.
+
+For examples of accessing data nested in sequences, see
+:ref:`sphx_glr_auto_examples_metadata_processing_plot_sequences.py`.
diff --git a/doc/conf.py b/doc/conf.py
index f756d1a9a..2b3426c62 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -104,7 +104,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'pydicom'
-copyright = u'2008-2018, Darcy Mason and pydicom contributors'
+copyright = u'2008-2019, Darcy Mason and pydicom contributors'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
diff --git a/doc/getting_started.rst b/doc/getting_started.rst
index 1a13961e9..b451657bb 100644
--- a/doc/getting_started.rst
+++ b/doc/getting_started.rst
@@ -77,7 +77,7 @@ Prerequisites
    * gdcm
    * jpeg_ls
    * jpeg2000
-   * pytest (if running pydicom's test suite)
+   * pytest (if running pydicom's test suite). pytest<5 if in Python 2.
 
 
 Installing pydicom
@@ -139,6 +139,8 @@ pydicom's setup.py file and::
 
 This will install `pytest <https://pytest.org>`_ if it is not 
 already installed.
+
+In v1.3 run under Python 2, if pytest is not found, please `python2 -m pip install "pytest<5"`
   
 Or, in linux you can also use::
 
diff --git a/doc/private_data_elements.rst b/doc/private_data_elements.rst
new file mode 100644
index 000000000..ce8dadd4e
--- /dev/null
+++ b/doc/private_data_elements.rst
@@ -0,0 +1,115 @@
+.. _private_data_elements:
+
+Private Data Elements
+=====================
+
+.. rubric:: Accessing or creating private data elements
+
+.. currentmodule:: pydicom.dataset
+
+Introduction
+------------
+
+The DICOM standard allows DICOM file creators to use `private data elements` to store
+information that is not defined by the DICOM standard itself.
+
+Private data elements are stored in Datasets just like other data elements.
+When reading files with pydicom, they will automatically be read
+and available for display.  Pydicom knows descriptive names for some 'well-known'
+private data elements, but for others it may not be able to show anything except
+the tag and the value.
+
+When writing your own private data elements, the DICOM standard requires the use
+of 'private creator blocks'.  Pydicom (since v1.3) has some convenience functions
+to make creating private blocks and data elements easier.
+
+The sections below outlines accessing and creating private blocks and data elements
+using pydicom.
+
+Displaying Private Data Elements in pydicom
+-------------------------------------------
+
+Here is an example of some private tags displayed for pydicom's test file 'CT_small.dcm'::
+
+    >>> from pydicom import dcmread
+    >>> from pydicom.data import get_testdata_files
+    >>> ct_filename = get_testdata_files("CT_small")[0]
+    >>> ds = dcmread(ct_filename)
+    >>> ds
+    (0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
+    (0008, 0008) Image Type                          CS: ['ORIGINAL', 'PRIMARY', 'AX
+    IAL']
+    ...
+    ...
+    (0009, 0010) Private Creator                     LO: 'GEMS_IDEN_01'
+    (0009, 1001) [Full fidelity]                     LO: 'GE_GENESIS_FF'
+    (0009, 1002) [Suite id]                          SH: 'CT01'
+    ...
+
+The last two lines in the example above show pydicom's display of two private data
+elements.  The line preceding those shows the private creator data element
+that reserves a section of tag element numbers for that creator's use.
+
+Since the descriptions for private data elements are not part of the DICOM standard,
+and are thus not necessarily unique,
+pydicom does not allow you to access data elements using those names. This is indicated
+by enclosing the text in square brackets, to make it clear it is different from DICOM 
+standard descriptors.
+
+You can still access the private data elements using the tag, remembering that 
+data elements access by tag number return a full DataElement instance, and the `value` 
+attribute is needed to get the value::
+
+    >>> ds[0x00091001].value
+    'GE_GENESIS_FF'
+
+You can also create a :class:`PrivateBlock` instance and access elements through it::
+
+    >>> block = ds.private_block(0x0009, 'GEMS_IDEN_01')
+    >>> block[0x01]
+    (0009, 1001) [Full fidelity]                     LO: 'GE_GENESIS_FF'
+    >>> block[0x01].value
+    'GE_GENESIS_FF'
+
+Using the private block like this is even more useful when creating your own private data
+elements, as shown in the next section.
+
+Setting Private Data Elements with pydicom
+------------------------------------------
+
+The DICOM standard requires a private creator data element to identify and reserve a section
+of private tags. That name should be unique, and usually has the company name as the first part 
+to accomplish that.  Pydicom (since v1.3) provides convenient functions to manage this::
+
+    >>> block = ds.private_block(0x000b, "My company 001", create=True)
+    >>> block.add_new(0x01, "SH", "my value")
+    >>> ds
+    ...
+    (000b, 0010) Private Creator                     LO: 'My company 001'
+    (000b, 1001) Private tag data                    SH: 'my value'
+    ...
+
+Standard python operations like `in` and `del` can also be used when working 
+with block object::
+
+    >>> 0x01 in block
+    True
+    >>> 0x02 in block
+    False
+    >>> del block[0x01]
+    >>> 0x01 in block
+    False
+
+Removing All Private Data Elements
+-----------------------------------------------
+
+One part of anonymizing a DICOM file is to ensure that private data elements
+have been removed, as there is no guarantee as to what kind of information
+might be contained in them.  Pydicom provides a convenient function 
+:func:`Dataset.remove_private_tags` to recursively remove private elements::
+
+    >>> ds.remove_private_tags()
+
+This can also be helpful during interactive sessions when exploring DICOM files,
+to remove a large number of lines from the display of a dataset --
+lines which may not provide useful information.
diff --git a/doc/pydicom_user_guide.rst b/doc/pydicom_user_guide.rst
index b9e07a803..d252b0724 100644
--- a/doc/pydicom_user_guide.rst
+++ b/doc/pydicom_user_guide.rst
@@ -6,9 +6,12 @@ Pydicom User Guide
 
 .. toctree::
 
-   transition_to_pydicom1.rst
+   warn_transition_to_pydicom1.rst
    base_element.rst
    writing_files.rst
    working_with_pixel_data.rst
    image_data_handlers.rst
-   viewing_images.rst
\ No newline at end of file
+   viewing_images.rst
+   private_data_elements.rst
+   transition_to_pydicom1.rst
+   python2_support.rst
\ No newline at end of file
diff --git a/doc/python2_support.rst b/doc/python2_support.rst
new file mode 100644
index 000000000..072e43050
--- /dev/null
+++ b/doc/python2_support.rst
@@ -0,0 +1,36 @@
+.. _Python2_support:
+
+Python 2 Support Plan for Pydicom
+=================================
+
+.. rubric:: Timeline of support for Python 2
+
+The Python developers have stated that Python 2 will no longer be supported starting Jan 1, 2020.
+Numpy is also dropping support for Python 2 at that time. Pytest, which pydicom uses for testing,
+will support only Python 3.5+
+starting with the pytest 5.0 release, but are supporting the 4.6 version until mid 2020.
+
+It is clear -- Python 2 will become a thing of the past starting Jan 2020.
+All packages, including pydicom, need to think of transitioning away from Python 2 and
+supporting only Python 3.
+
+Pydicom code was written with common code for both Python 2.7 and Python 3 as much as possible.
+Where necessary, checks for Python 2 have been used to create small blocks of distinct
+code.  When the time comes, it should be relatively easy to remove the Python 2 blocks and leave
+a Python-3-only version of pydicom.
+
+After some discussion on github, the proposed plan for pydicom and Python 2 support is as follows
+
+* pydicom v1.3 (July 2019) - no changes to Python versions supported. Adds a deprecation
+  warning (to be deprecated in v1.5) when run under Python 2
+
+* pydicom v1.4 (planned for release ~December 2019) will support Python 2.7,
+  with deprecation warning as above.
+
+* pydicom v1.5 (planned for ~April 2020) will be Python 3.5+ only
+
+* pydicom v1.6 (no date set) will be Python 3.6+
+
+We may consider the possibility of backporting some fixes to pydicom v1.4 for very serious issues,
+if users make the case for it.  Generally speaking, however, we encourage all pydicom users to make
+the transition to Python 3 by early 2020.
diff --git a/doc/ref_guide.rst b/doc/ref_guide.rst
index 02fee3f6e..c58dfa0ac 100644
--- a/doc/ref_guide.rst
+++ b/doc/ref_guide.rst
@@ -27,10 +27,10 @@ File Writing
 ============
 
 DICOM files can also be written using pydicom. There are two ways to do this.
-The first is to use ``write_file`` with a prexisting FileDataset (derived from Dataset) instance.
+The first is to use ``dcmwrite`` with a prexisting FileDataset (derived from Dataset) instance.
 The second is to use the ``save_as`` method on an Dataset instance.
 
-.. autofunction:: pydicom.filewriter.write_file
+.. autofunction:: pydicom.filewriter.dcmwrite
 
 .. automethod:: pydicom.dataset.Dataset.save_as
 
diff --git a/doc/warn_transition_to_pydicom1.rst b/doc/warn_transition_to_pydicom1.rst
new file mode 100644
index 000000000..2030b5604
--- /dev/null
+++ b/doc/warn_transition_to_pydicom1.rst
@@ -0,0 +1,12 @@
+For users of pydicom < 1.0
+========================
+
+.. rubric:: Warning about pydicom < 1.0 and reference to Transition to Pydicom 1 section
+
+Pydicom < 1
+-------
+
+.. warning::
+
+        Pydicom versions 1.0 and higher introduce backwards-incompatible changes.
+        If you have used earlier versions of pydicom, see :doc:`transition_to_pydicom1` for details.
diff --git a/doc/whatsnew/v1.3.0.rst b/doc/whatsnew/v1.3.0.rst
index 4f642ed99..60436117d 100644
--- a/doc/whatsnew/v1.3.0.rst
+++ b/doc/whatsnew/v1.3.0.rst
@@ -4,14 +4,24 @@ Version 1.3.0
 Changelog
 ---------
 
+Documentation
+-------------
+* New User Guide page for Python 2 support timeline
+* New User Guide page for working with private data elements
+* example loading set of CT slices and plotting axial, sagittal
+  and coronal (:pull_request:`789`)
+
 Changes
 .......
+* Removed deprecated uid variables, config.image_handlers and DeferredDataElement (:pull_request:`760`)
 * ``dataelem.isMultiValue`` is deprecated and will be removed in v1.4.
   Use ``dataelem.DataElement.VM`` instead.
 * ``dataelem.isStringOrStringList`` and ``dataelem.isString`` functions are
   removed
 * ``datadict.add_dict_entry`` and ``datadict.add_dict_entries`` now raise if
   trying to add a private tag
+* ``dataset.Dataset.maxBytesToDisplay`` also limits display length for
+  non-binary VRs (by number of items) (:issue:`666`)
 
 Enhancements
 ............
@@ -19,13 +29,21 @@ Enhancements
 * Added ``datadict.add_private_dict_entry`` and
   ``datadict.add_private_dict_entries`` to add custom private tags
   (:issue:`799`)
-* Added possibility to write into zip file using gzip (:issue:`753`)
+* Added possibility to write into zip file using gzip, by avoiding seek (:issue:`753`)
+* Added RLE encoding (:pull_request:`730`)
 * Added handling of incorrect transfer syntax (explicit vs implicit)
   (:issue:`820`)
 * Added creation of Tag instances by DICOM keyword, e.g Tag("PatientName")
 * Added possibility to get and add private tags without adding them to the
   private dictionary
 * Added possibility to use a ``Dataset`` in a ``NumPy`` array
+* Allow missing padding byte in Pixel Data, issue a warning in this case
+  (:issue:`864`)
+* Add in-memory image decoding with GDCM
+* Check really used implicit/explicit VR before reading a
+  data set (:issue:`819`, :issue:`820`)
+* Added alpha conversion of Datasets to/from JSON format (Python 3 only)
+
 
 Fixes
 .....
@@ -38,8 +56,6 @@ Fixes
   not present (:issue:`838`)
 * Raise exception with specific message if value is too large to be written
   in explicit transfer syntax (:issue:`757`)
-* Do not derive `Dataset` from `dict` - fixes behavior in newer Python versions
-  (:issue:`765`)
 * Make hash for `PersonName3` behave as expected, make `PersonName` objects
   immutable (:issue:`785`)
 * Fixed `generate_uid()` returning non-conformant UIDs when `prefix=None`
@@ -48,3 +64,10 @@ Fixes
 * An invalid encoding is now replaced by the default encoding, if
   ``config.enforce_valid_values`` is not set (:issue:`815`)
 * Correctly handle elements with ambiguous VR in sequence items (:issue:`804`)
+* Fix bug where new DicomDir objects always have is_implicit_VR
+* Fix dataset equality for mixed raw vs converted data elements (:issue:`835`)
+* Remove excess padding in Pixel Data 
+* Fix wrong date format in anonymize example
+* Fix unknown VR exception message when VR isn't ASCII (:issue:`791`)
+* Fix jis-x-0201 characters encoding (:issue:`856`)
+
diff --git a/pydicom/__init__.py b/pydicom/__init__.py
index de10167e1..e00e000d7 100644
--- a/pydicom/__init__.py
+++ b/pydicom/__init__.py
@@ -47,3 +47,9 @@ __all__ = ['DataElement',
            'write_file',
            '__version__',
            '__version_info__']
+
+from pydicom.compat import in_py2
+if in_py2:
+    import warnings
+    msg = 'Python 2 will no longer be supported after the pydicom v1.4 release'
+    warnings.warn(msg, DeprecationWarning)
diff --git a/pydicom/charset.py b/pydicom/charset.py
index f85bff7a7..676730c22 100644
--- a/pydicom/charset.py
+++ b/pydicom/charset.py
@@ -86,6 +86,7 @@ CODES_TO_ENCODINGS = {
 }
 
 ENCODINGS_TO_CODES = {v: k for k, v in CODES_TO_ENCODINGS.items()}
+ENCODINGS_TO_CODES['shift_jis'] = ESC + b')I'
 
 # Multi-byte character sets except Korean are handled by Python.
 # To decode them, the escape sequence shall be preserved in the input byte
@@ -95,6 +96,182 @@ handled_encodings = ('iso2022_jp',
                      'iso_ir_58')
 
 
+def _encode_to_jis_x_0201(value, errors='strict'):
+    """Convert a unicode string into JIS X 0201 byte string using shift_jis
+    encodings.
+    shift_jis is a superset of jis_x_0201. So we can regard the encoded value
+    as jis_x_0201 if it is single byte character.
+
+    Parameters
+    ----------
+    value : text type
+        The unicode string as presented to the user.
+    errors : str
+        The behavior of a character which could not be encoded. If 'strict' is
+        passed, raise an UnicodeEncodeError. If any other value is passed,
+        non ISO IR 14 characters are replaced by the ASCII '?'.
+
+    Returns
+    -------
+    byte string
+        The encoded string. If some characters in value could not be encoded to
+        JIS X 0201, and `errors` is not set to 'strict', they are replaced to
+        '?'.
+
+    Raises
+    ------
+    UnicodeEncodeError
+        If errors is set to 'strict' and `value` could not be encoded with
+        JIS X 0201.
+    """
+
+    Encoder = codecs.getincrementalencoder('shift_jis')
+    encoder = Encoder()
+
+    # If errors is not strict, this function is used as fallback.
+    # In this case, we use only ISO IR 14 to encode given value
+    # without escape sequence.
+    if errors != 'strict' or value == '':
+        encoded = b''
+        for c in value:
+            try:
+                b = encoder.encode(c)
+            except UnicodeEncodeError as e:
+                b = b'?'
+
+            if len(b) != 1 or 0x80 <= ord(b):
+                b = b'?'
+            encoded += b
+        return encoded
+
+    encoded = encoder.encode(value[0])
+    if len(encoded) != 1:
+        raise UnicodeEncodeError(
+            'shift_jis', value, 0, len(value), 'illegal multibyte sequence')
+
+    msb = ord(encoded) & 0x80  # msb is 1 for ISO IR 13, 0 for ISO IR 14
+    for i, c in enumerate(value[1:], 1):
+        try:
+            b = encoder.encode(c)
+        except UnicodeEncodeError as e:
+            e.start = i
+            e.end = len(value)
+            raise e
+        if len(b) != 1 or ((ord(b) & 0x80) ^ msb) != 0:
+            character_set = 'ISO IR 14' if msb == 0 else 'ISO IR 13'
+            msg = 'Given character is out of {}'.format(character_set)
+            raise UnicodeEncodeError('shift_jis', value, i, len(value), msg)
+        encoded += b
+
+    return encoded
+
+
+def _encode_to_jis_x_0208(value, errors='strict'):
+    """Convert a unicode string into JIS X 0208 byte string using iso2022_jp
+    encodings.
+    The escape sequence which is located at the end of the encoded value has
+    to vary depending on the value 1 of SpecificCharacterSet. So we have to
+    trim it and append the correct escape sequence manually.
+
+    Parameters
+    ----------
+    value : text type
+        The unicode string as presented to the user.
+    errors : str
+        The behavior of a character which could not be encoded. This value
+        is passed to errors argument of str.encode().
+
+    Returns
+    -------
+    byte string
+        The encoded string. If some characters in value could not be encoded to
+        JIS X 0208, it depends on the behavior of iso2022_jp encoder.
+
+    Raises
+    ------
+    UnicodeEncodeError
+        If errors is set to 'strict' and `value` could not be encoded with
+        JIS X 0208.
+    """
+
+    # If errors is not strict, this function is used as fallback.
+    # So keep the tail escape sequence of encoded for backward compatibility.
+    if errors != 'strict':
+        return value.encode('iso2022_jp', errors=errors)
+
+    Encoder = codecs.getincrementalencoder('iso2022-jp')
+    encoder = Encoder()
+
+    encoded = encoder.encode(value[0])
+    if encoded[:3] != ENCODINGS_TO_CODES['iso2022_jp']:
+        raise UnicodeEncodeError(
+            'iso2022_jp', value, 0, len(value),
+            'Given character is out of ISO IR 87')
+
+    for i, c in enumerate(value[1:], 1):
+        try:
+            b = encoder.encode(c)
+        except UnicodeEncodeError as e:
+            e.start = i
+            e.end = len(value)
+            raise e
+        if b[:3] == ENCODINGS_TO_CODES['iso8859']:
+            raise UnicodeEncodeError(
+                'iso2022_jp', value, i, len(value),
+                'Given character is out of ISO IR 87')
+        encoded += b
+    return encoded
+
+
+def _get_escape_sequence_for_encoding(encoding, encoded=None):
+    """ Return an escape sequence corresponding to the given encoding. If
+    encoding is 'shift_jis', return 'ESC)I' or 'ESC(J' depending on the first
+    byte of encoded.
+
+    Parameters
+    ----------
+    encoding : str
+        An encoding is used to specify  an escape sequence.
+
+    encoded : bytes or str
+        The encoded value is used to chose an escape sequence if encoding is
+        'shift_jis'
+
+    Returns
+    -------
+    string
+        Escape sequence for encoded value.
+    """
+
+    ESC_ISO_IR_14 = ESC + b'(J'
+    ESC_ISO_IR_13 = ESC + b')I'
+
+    if encoding == 'shift_jis':
+        if encoded is None:
+            return ESC_ISO_IR_14
+
+        if not in_py2:
+            first_byte = encoded[0]
+        else:
+            first_byte = ord(encoded[0])
+        if 0x80 <= first_byte:
+            return ESC_ISO_IR_13
+
+        return ESC_ISO_IR_14
+    return ENCODINGS_TO_CODES.get(encoding, b'')
+
+
+# These encodings need escape sequence to handle alphanumeric characters.
+need_tail_escape_sequence_encodings = ('iso2022_jp', 'iso-2022-jp')
+
+
+custom_encoders = {
+    'shift_jis': _encode_to_jis_x_0201,
+    'iso2022_jp': _encode_to_jis_x_0208,
+    'iso-2022-jp': _encode_to_jis_x_0208
+}
+
+
 def decode_string(value, encodings, delimiters):
     """Convert a raw byte string into a unicode string using the given
     list of encodings.
@@ -281,9 +458,14 @@ def encode_string(value, encodings):
     """
     for i, encoding in enumerate(encodings):
         try:
-            encoded = value.encode(encoding)
+            encoded = _encode_string_impl(value, encoding)
+
             if i > 0 and encoding not in handled_encodings:
-                return ENCODINGS_TO_CODES.get(encoding, b'') + encoded
+                escape_sequence = _get_escape_sequence_for_encoding(
+                        encoding, encoded=encoded)
+                encoded = escape_sequence + encoded
+            if encoding in need_tail_escape_sequence_encodings:
+                encoded += _get_escape_sequence_for_encoding(encodings[0])
             return encoded
         except UnicodeError:
             continue
@@ -304,7 +486,7 @@ def encode_string(value, encodings):
         warnings.warn("Failed to encode value with encodings: {} - using "
                       "replacement characters in encoded string"
                       .format(', '.join(encodings)))
-        return value.encode(encodings[0], errors='replace')
+        return _encode_string_impl(value, encodings[0], errors='replace')
 
 
 def _encode_string_parts(value, encodings):
@@ -337,14 +519,14 @@ def _encode_string_parts(value, encodings):
     """
     encoded = bytearray()
     unencoded_part = value
+    best_encoding = None
     while unencoded_part:
         # find the encoding that can encode the longest part of the rest
         # of the string still to be encoded
         max_index = 0
-        best_encoding = None
         for encoding in encodings:
             try:
-                unencoded_part.encode(encoding)
+                _encode_string_impl(unencoded_part, encoding)
                 # if we get here, the whole rest of the value can be encoded
                 best_encoding = encoding
                 max_index = len(unencoded_part)
@@ -355,20 +537,36 @@ def _encode_string_parts(value, encodings):
                     max_index = e.start
                     best_encoding = encoding
         # none of the given encodings can encode the first character - give up
-        if best_encoding is None:
-            raise ValueError()
+        if max_index == 0:
+            raise ValueError("None of the given encodings can encode the "
+                             "first character")
 
         # encode the part that can be encoded with the found encoding
-        encoded_part = unencoded_part[:max_index].encode(best_encoding)
+        encoded_part = _encode_string_impl(unencoded_part[:max_index],
+                                           best_encoding)
         if best_encoding not in handled_encodings:
-            encoded += ENCODINGS_TO_CODES.get(best_encoding, b'')
+            encoded += _get_escape_sequence_for_encoding(
+                    best_encoding, encoded=encoded_part)
         encoded += encoded_part
         # set remaining unencoded part of the string and handle that
         unencoded_part = unencoded_part[max_index:]
     # unencoded_part is empty - we are done, return the encoded string
+    if best_encoding in need_tail_escape_sequence_encodings:
+        encoded += _get_escape_sequence_for_encoding(encodings[0])
     return encoded
 
 
+def _encode_string_impl(value, encoding, errors='strict'):
+    """Convert a unicode string into a byte string. If given encoding is in
+    custom_encoders, use a corresponding custom_encoder. If given encoding
+    is not in custom_encoders, use a corresponding python handled encoder.
+    """
+    if encoding in custom_encoders:
+        return custom_encoders[encoding](value, errors=errors)
+    else:
+        return value.encode(encoding, errors=errors)
+
+
 # DICOM PS3.5-2008 6.1.1 (p 18) says:
 #   default is ISO-IR 6 G0, equiv to common chr set of ISO 8859 (PS3.5 6.1.2.1)
 #    (0008,0005)  value 1 can *replace* the default encoding...
diff --git a/pydicom/dataelem.py b/pydicom/dataelem.py
index d9c1609e9..4d633f3af 100644
--- a/pydicom/dataelem.py
+++ b/pydicom/dataelem.py
@@ -9,6 +9,7 @@ A DataElement has a tag,
 
 from __future__ import absolute_import
 
+import base64
 import warnings
 from collections import namedtuple
 
@@ -16,15 +17,18 @@ from pydicom import config  # don't import datetime_conversion directly
 from pydicom import compat
 from pydicom.charset import default_encoding
 from pydicom.compat import in_py2
+from pydicom.config import logger
 from pydicom.datadict import (dictionary_has_tag, dictionary_description,
                               dictionary_keyword, dictionary_is_retired,
                               private_dictionary_description, dictionary_VR,
-                              repeater_has_tag)
+                              dictionary_VM, repeater_has_tag)
 from pydicom.multival import MultiValue
 from pydicom.tag import Tag, BaseTag
 from pydicom.uid import UID
+from pydicom import jsonrep
 import pydicom.valuerep  # don't import DS directly as can be changed by config
 
+from pydicom.valuerep import PersonNameUnicode
 if not in_py2:
     from pydicom.valuerep import PersonName3 as PersonNameUnicode
     PersonName = PersonNameUnicode
@@ -169,6 +173,133 @@ class DataElement(object):
         self.file_tell = file_value_tell
         self.is_undefined_length = is_undefined_length
 
+    @classmethod
+    def from_json(cls, dataset_class, tag, vr, value, value_key,
+                                bulk_data_uri_handler=None,
+                                encodings=None):
+        """Creates a DataElement from JSON.
+
+        Parameters
+        ----------
+        tag: pydicom.tag.Tag
+            data element tag
+        vr: str
+            data element value representation
+        value: list
+            data element value(s)
+        value_key: Union[str, None]
+            key of the data element that contains the value
+            (options: ``{"Value", "InlineBinary", "BulkDataURI"}``)
+        bulk_data_uri_handler: Union[Callable, None]
+            callable that accepts the "BulkDataURI" of the JSON representation
+            of a data element and returns the actual value of that data element
+            (retrieved via DICOMweb WADO-RS)
+
+        Returns
+        -------
+        pydicom.dataelem.DataElement
+
+        """
+        # TODO: test wado-rs retrieve wrapper
+        try:
+            vm = dictionary_VM(tag)
+        except KeyError:
+            # Private tag
+            vm = str(len(value))
+        if value_key == 'Value':
+            if not(isinstance(value, list)):
+                fmt = '"{}" of data element "{}" must be a list.'
+                raise TypeError(fmt.format(value_key, tag))
+        elif value_key in {'InlineBinary', 'BulkDataURI'}:
+            if isinstance(value, list):
+                fmt = '"{}" of data element "{}" must be a string.'
+                raise TypeError(fmt.format(value_key, tag))
+        if vr == 'SQ':
+            elem_value = []
+            for value_item in value:
+                ds = dataset_class()
+                if value_item:
+                    for key, val in value_item.items():
+                        if 'vr' not in val:
+                            fmt = 'Data element "{}" must have key "vr".'
+                            raise KeyError(fmt.format(tag))
+                        unique_value_keys = tuple(
+                            set(val.keys()) & set(jsonrep.JSON_VALUE_KEYS)
+                        )
+                        if len(unique_value_keys) == 0:
+                            logger.debug(
+                                'data element has neither key "{}".'.format(
+                                    '" nor "'.join(supported_keys)
+                                )
+                            )
+                            elem = DataElement(tag=tag, value='', VR=vr)
+                        else:
+                            value_key = unique_value_keys[0]
+                            elem = cls.from_json(
+                                dataset_class, key, val['vr'],
+                                val[value_key], value_key
+                            )
+                        ds.add(elem)
+                elem_value.append(ds)
+        elif vr == 'PN':
+            # Special case, see DICOM Part 18 Annex F2.2
+            elem_value = []
+            for v in value:
+                if not isinstance(v, dict):
+                    # Some DICOMweb services get this wrong, so we
+                    # workaround the issue and warn the user
+                    # rather than raising an error.
+                    logger.error(
+                        'value of data element "{}" with VR Person Name (PN) '
+                        'is not formatted correctly'.format(tag)
+                    )
+                    elem_value.append(v)
+                else:
+                    elem_value.extend(list(v.values()))
+            if vm == '1':
+                try:
+                    elem_value = elem_value[0]
+                except IndexError:
+                    elem_value = ''
+        else:
+            if vm == '1':
+                if value_key == 'InlineBinary':
+                    elem_value = base64.b64decode(value)
+                elif value_key == 'BulkDataURI':
+                    if bulk_data_uri_handler is None:
+                        logger.warning(
+                            'no bulk data URI handler provided for retrieval '
+                            'of value of data element "{}"'.format(tag)
+                        )
+                        elem_value = ''
+                    else:
+                        elem_value = bulk_data_uri_handler(value)
+                else:
+                    if value:
+                        elem_value = value[0]
+                    else:
+                        elem_value = value
+            else:
+                elem_value = value
+        if not value:
+            logger.warning('missing value for data element "{}"'.format(tag))
+            elem_value = ''
+
+        elem_value = jsonrep._convert_to_python_number(elem_value, vr)
+
+        try:
+            if compat.in_py2 and vr == "PN":
+
+                elem_value = PersonNameUnicode(elem_value, 'UTF8')
+            return DataElement(tag=tag, value=elem_value, VR=vr)
+        except Exception:
+            raise
+            raise ValueError(
+                'Data element "{}" could not be loaded from JSON: {}'.format(
+                    tag, elem_value
+                    )
+            )
+
     @property
     def value(self):
         """Return the element's `value`."""
@@ -302,10 +433,17 @@ class DataElement(object):
     @property
     def repval(self):
         """Return a str representation of the element's `value`."""
-        byte_VRs = ['OB', 'OW', 'OW/OB', 'OW or OB', 'OB or OW',
-                    'US or SS or OW', 'US or SS']
-        if (self.VR in byte_VRs and len(self.value) > self.maxBytesToDisplay):
-            repVal = "Array of %d bytes" % len(self.value)
+        long_VRs = {"OB", "OD", "OF", "OW", "UN", "UT"}
+        if set(self.VR.split(" or ")) & long_VRs:
+            try:
+                length = len(self.value)
+            except TypeError:
+                pass
+            else:
+                if length > self.maxBytesToDisplay:
+                    return "Array of %d elements" % length
+        if self.VM > self.maxBytesToDisplay:
+            repVal = "Array of %d elements" % self.VM
         elif isinstance(self.value, UID):
             repVal = self.value.name
         else:
diff --git a/pydicom/dataset.py b/pydicom/dataset.py
index c30ada8db..80a8f3d3c 100644
--- a/pydicom/dataset.py
+++ b/pydicom/dataset.py
@@ -15,8 +15,10 @@ Dataset (dict subclass)
             contains its own DataElements, and so on in a recursive manner.
 """
 
+import base64
 import inspect  # for __dir__
 import io
+import json
 import os
 import os.path
 from bisect import bisect_left
@@ -33,14 +35,17 @@ from pydicom.datadict import dictionary_VR
 from pydicom.datadict import (tag_for_keyword, keyword_for_tag,
                               repeater_has_keyword)
 from pydicom.dataelem import DataElement, DataElement_from_raw, RawDataElement
+from pydicom.multival import MultiValue
 from pydicom.pixel_data_handlers.util import (convert_color_space,
                                               reshape_pixel_array)
 from pydicom.tag import Tag, BaseTag, tag_in_exception
 from pydicom.uid import (ExplicitVRLittleEndian, ImplicitVRLittleEndian,
                          ExplicitVRBigEndian, PYDICOM_IMPLEMENTATION_UID)
 
+
 if compat.in_py2:
     from pkgutil import find_loader as have_package
+    from pydicom.valuerep import PersonNameUnicode
 else:
     from importlib.util import find_spec as have_package
 
@@ -179,7 +184,6 @@ class PrivateBlock(object):
         self.dataset.add_new(self.get_tag(element_offset), VR, value)
 
 
-
 def _dict_equal(a, b, exclude=None):
     """Common method for Dataset.__eq__ and FileDataset.__eq__
 
@@ -308,6 +312,15 @@ class Dataset(dict):
     ...         else:
     ...             # Do something useful with each DataElement
 
+    Converting a dataset to and from json:
+
+    >>> ds = Dataset()
+    >>> ds.PatientName = "Some^Name"
+    >>> jsonmodel = ds.to_json()
+    >>> ds2 = Dataset()
+    >>> ds2.from_json(jsonmodel)
+    (0010, 0010) Patient's Name                      PN: u'Some^Name'
+
     Attributes
     ----------
     default_element_format : str
@@ -330,6 +343,16 @@ class Dataset(dict):
     """
     indent_chars = "   "
 
+    # TODO: this the correct list?
+    _BINARY_VR_VALUES = ['OW', 'OB', 'OD', 'OF', 'OL', 'UN',
+                         'OW/OB', 'OW or OB', 'OB or OW', 'US or SS']
+
+    _VRs_TO_BE_FLOATS = ["DS", "FL", "FD", ]
+    _VRs_TO_BE_INTS = ["IS", "SL", "SS", "UL", "US", ]
+
+    # Order of keys is significant!
+    _JSON_VALUE_KEYS = ('Value', 'BulkDataURI', 'InlineBinary', )
+
     # Python 2: Classes defining __eq__ should flag themselves as unhashable
     __hash__ = None
 
@@ -1378,7 +1401,8 @@ class Dataset(dict):
                                if callable(getattr(data_element, x)) else
                                getattr(data_element, x))
                               for x in dir(data_element)
-                              if not x.startswith("_")])
+                              if not x.startswith("_")
+                              and x != "from_json"])
             if data_element.VR == "SQ":
                 yield sequence_element_format % elem_dict
             else:
@@ -1754,6 +1778,232 @@ class Dataset(dict):
                     for dataset in sequence:
                         dataset.walk(callback)
 
+    @classmethod
+    def _convert_to_python_number(cls, value, vr):
+        """Makes sure that values are either ints or floats
+        based on their value representation.
+
+        Parameters
+        ----------
+        value: Union[Union[str, int, float], List[Union[str, int, float]]]
+            value of data element
+        vr: str
+            value representation of data element
+
+        Returns
+        -------
+        Union[Union[str, int, float], List[Union[str, int, float]]]
+
+        """
+        if value is None:
+            return None
+        number_type = None
+        if vr in cls._VRs_TO_BE_INTS:
+            number_type = int
+        if vr in cls._VRs_TO_BE_FLOATS:
+            number_type = float
+        if number_type is not None:
+            if isinstance(value, (list, tuple, )):
+                value = [number_type(e) for e in value]
+            else:
+                value = number_type(value)
+        return value
+
+    @classmethod
+    def from_json(cls, json_dataset, bulk_data_uri_handler=None,
+                  encodings=None):
+        """Loads DICOM Data Set in DICOM JSON format.
+        See:
+        http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_F.html
+
+        Parameters
+        ----------
+        json_dataset: Union[dict, str]
+            dictionary or string representing a DICOM Data Set formatted based
+            on the DICOM JSON Model (Annex F)
+        bulk_data_uri_handler: Union[Callable, None]
+            callable that accepts the "BulkDataURI" of the JSON representation
+            of a data element and returns the actual value of data element
+            (retrieved via DICOMweb WADO-RS)
+        encodings: Union[list, None]
+            encodings from SpecificCharacterSet, or None for default
+        Returns
+        -------
+        pydicom.dataset.Dataset
+
+        """
+        if not isinstance(json_dataset, dict):
+            json_dataset = json.loads(json_dataset)
+        dataset = cls()
+        for tag, mapping in json_dataset.items():
+            vr = mapping['vr']
+            unique_value_keys = tuple(
+                set(mapping.keys()) & set(cls._JSON_VALUE_KEYS)
+            )
+            if len(unique_value_keys) == 0:
+                value_key = None
+                value = ['']
+            else:
+                value_key = unique_value_keys[0]
+                value = mapping[value_key]
+            data_element = DataElement.from_json(
+                cls, tag, vr, value, value_key
+            )
+            dataset.add(data_element)
+        return dataset
+
+    def _data_element_to_json(self, data_element,
+                              bulk_data_element_handler,
+                              bulk_data_threshold, dump_handler):
+        """Converts a DataElement to JSON representation.
+
+        Parameters
+        ----------
+        data_element: pydicom.dataelem.DataElement
+            data element
+        bulk_data_element_handler: Union[Callable, None]
+            callable that accepts a bulk data element and returns the
+            "BulkDataURI" for retrieving the value of the data element
+            via DICOMweb WADO-RS
+        bulk_data_threshold: int
+            size of base64 encoded data element above which a value will be
+            provided in form of a "BulkDataURI" rather than "InlineBinary"
+
+        Returns
+        -------
+        dict
+            mapping representing a JSON encoded data element
+
+        Raises
+        ------
+        TypeError
+            when size of encoded data element exceeds `bulk_data_threshold`
+            but `bulk_data_element_handler` is ``None`` and hence not callable
+
+        """
+        # TODO: Determine whether more VRs need to be converted to strings
+        _VRs_TO_QUOTE = ['AT', ]
+        json_element = {'vr': data_element.VR, }
+        if data_element.VR in Dataset._BINARY_VR_VALUES:
+            if data_element.value is not None:
+                binary_value = data_element.value.encode('utf-8')
+                encoded_value = base64.b64encode(binary_value).decode('utf-8')
+                if len(encoded_value) > bulk_data_threshold:
+                    if bulk_data_element_handler is None:
+                        raise TypeError(
+                            'No bulk data element handler provided to generate '
+                            'URL for value of data element "{}".'.format(
+                                data_element.name
+                            )
+                        )
+                    json_element['BulkDataURI'] = bulk_data_element_handler(
+                        data_element
+                    )
+                else:
+                    logger.info(
+                        'encode bulk data element "{}" inline'.format(
+                            data_element.name
+                        )
+                    )
+                    json_element['InlineBinary'] = encoded_value
+        elif data_element.VR == 'SQ':
+            # recursive call to co-routine to format sequence contents
+            value = [
+                json.loads(e.to_json(
+                    bulk_data_element_handler=bulk_data_element_handler,
+                    bulk_data_threshold=bulk_data_threshold,
+                    dump_handler=dump_handler
+                ))
+                for e in data_element
+            ]
+            json_element['Value'] = value
+        elif data_element.VR == 'PN':
+            if data_element.value is not None:
+                if len(data_element.value.components) > 2:
+                    json_element['Value'] = [
+                        {'Phonetic': data_element.value.components[2], },
+                    ]
+                elif len(data_element.value.components) > 1:
+                    json_element['Value'] = [
+                        {'Ideographic': data_element.value.components[1], },
+                    ]
+                else:
+                    json_element['Value'] = [
+                        {'Alphabetic': data_element.value.components[0], },
+                    ]
+        else:
+            if data_element.value is not None:
+                is_multivalue = isinstance(data_element.value, MultiValue)
+                if data_element.VM > 1 or is_multivalue:
+                    value = data_element.value
+                else:
+                    value = [data_element.value]
+                # ensure it's a list and not another iterable
+                # (e.g. tuple), which would not be JSON serializable
+                if data_element.VR in _VRs_TO_QUOTE:
+                    json_element['Value'] = [str(v) for v in value]
+                else:
+                    json_element['Value'] = [v for v in value]
+        if hasattr(json_element, 'Value'):
+            json_element['Value'] = self._convert_to_python_number(
+                json_element['Value'], data_element.VR
+            )
+        return json_element
+
+    def to_json(self, bulk_data_threshold=1, bulk_data_element_handler=None,
+                dump_handler=None):
+        """Converts the data set into JSON representation based on the
+        DICOM JSON Model
+        http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_F.html.
+
+        Parameters
+        ----------
+        bulk_data_threshold: int, optional
+            threshold for the length of a base64-encoded binary data element
+            above which the element should be considered bulk data and the value
+            provided as a URI rather than included inline (default: ``1``)
+        bulk_data_element_handler: Union[Callable, None], optional
+            callable that accepts a bulk data element and returns a JSON
+            representation of the data element (dictionary including the "vr"
+            key and either the "InlineBinary" or the "BulkDataURI" key)
+        dump_handler: Union[Callable, None], optional
+            callable that accepts a dict and returns the serialized (dumped)
+            JSON string (by default uses ``json.dumps()``)
+
+        Returns
+        -------
+        str
+            data set serialized into a string based on the DICOM JSON Model
+
+        Examples
+        --------
+        >>> def my_json_dumps(data):
+        ...     return json.dumps(data, indent=4)
+        >>> dataset.to_json(dump_handler=my_json_dumps)
+
+        """
+        if dump_handler is None:
+            logger.debug('using default json.dumps function')
+            dump_handler = json.dumps
+        json_dataset = {}
+        for key in self.keys():
+            json_key = '{0:04x}{1:04x}'.format(key.group, key.element).upper()
+            # FIXME: with pydicom 1.x the referenced image sequence
+            # causes a recursion error
+            if json_key == '00081140':
+                logger.warning(
+                    'currently can\'t serialize data element "{}"'.format(key)
+                )
+                continue
+            data_element = self[key]
+            json_dataset[json_key] = self._data_element_to_json(
+                data_element,
+                bulk_data_element_handler=bulk_data_element_handler,
+                bulk_data_threshold=bulk_data_threshold,
+                dump_handler=dump_handler
+            )
+        return dump_handler(json_dataset)
+
     __repr__ = __str__
 
 
diff --git a/pydicom/jsonrep.py b/pydicom/jsonrep.py
new file mode 100644
index 000000000..665d709c1
--- /dev/null
+++ b/pydicom/jsonrep.py
@@ -0,0 +1,37 @@
+# Copyright 2008-2019 pydicom authors. See LICENSE file for details.
+"""Methods for converting Datasets and DataElements to/from json"""
+
+# Order of keys is significant!
+JSON_VALUE_KEYS = ('Value', 'BulkDataURI', 'InlineBinary', )
+VRs_TO_BE_FLOATS = ["DS", "FL", "FD", ]
+VRs_TO_BE_INTS = ["IS", "SL", "SS", "UL", "US", ]
+
+def _convert_to_python_number(value, vr):
+    """Makes sure that values are either ints or floats
+    based on their value representation.
+
+    Parameters
+    ----------
+    value: Union[Union[str, int, float], List[Union[str, int, float]]]
+        value of data element
+    vr: str
+        value representation of data element
+
+    Returns
+    -------
+    Union[Union[str, int, float], List[Union[str, int, float]]]
+
+    """
+    if value is None:
+        return None
+    number_type = None
+    if vr in VRs_TO_BE_INTS:
+        number_type = int
+    if vr in VRs_TO_BE_FLOATS:
+        number_type = float
+    if number_type is not None:
+        if isinstance(value, (list, tuple, )):
+            value = [number_type(e) for e in value]
+        else:
+            value = number_type(value)
+    return value
diff --git a/pydicom/pixel_data_handlers/numpy_handler.py b/pydicom/pixel_data_handlers/numpy_handler.py
index ae3cd4a3d..46fc4446e 100644
--- a/pydicom/pixel_data_handlers/numpy_handler.py
+++ b/pydicom/pixel_data_handlers/numpy_handler.py
@@ -242,14 +242,20 @@ def get_pixeldata(ds, read_only=False):
     # Check that the actual length of the pixel data is as expected
     actual_length = len(ds.PixelData)
     # Correct for the trailing NULL byte padding for odd length data
+
     padded_expected_len = expected_len + expected_len % 2
     if actual_length < padded_expected_len:
-        raise ValueError(
-            "The length of the pixel data in the dataset doesn't match the "
-            "expected amount ({0} vs. {1} bytes). The dataset may be "
-            "corrupted or there may be an issue with the pixel data handler."
-            .format(actual_length, padded_expected_len)
-        )
+        if actual_length == expected_len:
+            warnings.warn(
+                "The pixel data length is odd and misses a padding byte.")
+        else:
+            raise ValueError(
+                "The length of the pixel data in the dataset ({} bytes) "
+                "doesn't match the expected length ({} bytes). "
+                "The dataset may be corrupted or there may be an issue "
+                "with the pixel data handler."
+                .format(actual_length, padded_expected_len)
+            )
     elif actual_length > padded_expected_len:
         # PS 3.5, Section 8.1.1
         msg = (
diff --git a/setup.py b/setup.py
index a6dcc0b4a..c6f151ff7 100755
--- a/setup.py
+++ b/setup.py
@@ -22,6 +22,9 @@ description = "Pure python package for DICOM medical file reading and writing"
 
 needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
 pytest_runner = ['pytest-runner'] if needs_pytest else []
+
+# in_py2 check in next line - pytest>=5 requires Python 3
+TESTS_REQUIRE = ['pytest<5'] if sys.version_info[0] == 2 else ['pytest']
 _py_modules = []
 if not have_dicom:
     _py_modules = ['dicom']
@@ -56,7 +59,6 @@ LICENSE = "MIT"
 VERSION = __version__
 REQUIRES = []
 SETUP_REQUIRES = pytest_runner
-TESTS_REQUIRE = ['pytest']
 
 # get long description from README.md
 BASE_PATH = os.path.abspath(os.path.dirname(__file__))


Test patch:
diff --git a/pydicom/data/test_files/test1.json b/pydicom/data/test_files/test1.json
new file mode 100644
index 000000000..960b89fc3
--- /dev/null
+++ b/pydicom/data/test_files/test1.json
@@ -0,0 +1,1008 @@
+{
+    "00080016": {
+        "vr": "UI",
+        "Value": [
+            "1.2.840.10008.5.1.4.1.1.4"
+        ]
+    },
+    "00081010": {
+        "vr": "SH",
+        "Value": [
+            "L1BAY3_GE3T"
+        ]
+    },
+    "00402017": {
+        "vr": "LO",
+        "Value": [
+            ""
+        ]
+    },
+    "00201002": {
+        "vr": "IS",
+        "Value": [
+            714
+        ]
+    },
+    "00250010": {
+        "vr": "LO",
+        "Value": [
+            "GEMS_SERS_01"
+        ]
+    },
+    "00080090": {
+        "vr": "PN",
+        "Value": [
+            {
+                "Alphabetic": ""
+            }
+        ]
+    },
+    "00190010": {
+        "vr": "LO",
+        "Value": [
+            "GEMS_ACQU_01"
+        ]
+    },
+    "00430010": {
+        "vr": "LO",
+        "Value": [
+            "GEMS_PARM_01"
+        ]
+    },
+    "09030010": {
+        "vr": "LO",
+        "Value": [
+            "GEIIS PACS"
+        ]
+    },
+    "00080018": {
+        "vr": "UI",
+        "Value": [
+            "1.3.6.1.4.1.5962.99.1.1516012269.2092621929.1509049565933.2991.0"
+        ]
+    },
+    "00080022": {
+        "vr": "DA",
+        "Value": [
+            "20141210"
+        ]
+    },
+    "00080023": {
+        "vr": "DA",
+        "Value": [
+            "20141210"
+        ]
+    },
+    "00080020": {
+        "vr": "DA",
+        "Value": [
+            "20141210"
+        ]
+    },
+    "00080021": {
+        "vr": "DA",
+        "Value": [
+            "20141210"
+        ]
+    },
+    "00101030": {
+        "vr": "DS",
+        "Value": [
+            87.09
+        ]
+    },
+    "00181088": {
+        "vr": "IS",
+        "Value": [
+            0
+        ]
+    },
+    "00120064": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "113100"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "DCM"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Basic Application Confidentiality Profile"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "210005"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "99PMP"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain all descriptors unchanged"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "113108"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "DCM"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain Patient Characteristics Option"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "113109"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "DCM"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain Device Identity Option"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "210006"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "99PMP"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain institution identity"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "113106"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "DCM"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain Longitudinal Temporal Information Full Dates Option"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "113111"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "DCM"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Retain Safe Private Option"
+                    ]
+                }
+            },
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "210001"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "99PMP"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "Remap UIDs"
+                    ]
+                }
+            }
+        ]
+    },
+    "00120063": {
+        "vr": "LO",
+        "Value": [
+            "Deidentified",
+            "Descriptors retained",
+            "Patient Characteristics retained",
+            "Device identity retained",
+            "Institution identity retained",
+            "Dates retained",
+            "Unsafe private removed",
+            "UIDs remapped"
+        ]
+    },
+    "00120062": {
+        "vr": "CS",
+        "Value": [
+            "YES"
+        ]
+    },
+    "00100030": {
+        "vr": "DA",
+        "Value": [
+            ""
+        ]
+    },
+    "09031012": {
+        "vr": "US",
+        "Value": [
+            0
+        ]
+    },
+    "09031010": {
+        "vr": "US",
+        "Value": [
+            0
+        ]
+    },
+    "09031011": {
+        "vr": "US",
+        "Value": [
+            0
+        ]
+    },
+    "0019109E": {
+        "vr": "LO",
+        "Value": [
+            "EPI2"
+        ]
+    },
+    "00181000": {
+        "vr": "LO",
+        "Value": [
+            "00Brigham750wMR1"
+        ]
+    },
+    "00081090": {
+        "vr": "LO",
+        "Value": [
+            "DISCOVERY MR750w"
+        ]
+    },
+    "00081032": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "00080100": {
+                    "vr": "SH",
+                    "Value": [
+                        "M2197"
+                    ]
+                },
+                "00080103": {
+                    "vr": "SH",
+                    "Value": [
+                        "0"
+                    ]
+                },
+                "00080102": {
+                    "vr": "SH",
+                    "Value": [
+                        "GEIIS"
+                    ]
+                },
+                "00080104": {
+                    "vr": "LO",
+                    "Value": [
+                        "BWH MR PELVIS WWO CONTRAST M2197"
+                    ]
+                }
+            }
+        ]
+    },
+    "00081030": {
+        "vr": "LO",
+        "Value": [
+            "M2197/PRO/BD Pelvis w"
+        ]
+    },
+    "00181310": {
+        "vr": "US",
+        "Value": [
+            128,
+            0,
+            0,
+            32
+        ]
+    },
+    "00100020": {
+        "vr": "LO",
+        "Value": [
+            "NOID"
+        ]
+    },
+    "00181312": {
+        "vr": "CS",
+        "Value": [
+            "COL"
+        ]
+    },
+    "00181090": {
+        "vr": "IS",
+        "Value": [
+            0
+        ]
+    },
+    "00181314": {
+        "vr": "DS",
+        "Value": [
+            90.0
+        ]
+    },
+    "00181315": {
+        "vr": "CS",
+        "Value": [
+            "N"
+        ]
+    },
+    "00181316": {
+        "vr": "DS",
+        "Value": [
+            0.603909
+        ]
+    },
+    "00181094": {
+        "vr": "IS",
+        "Value": [
+            0
+        ]
+    },
+    "00200052": {
+        "vr": "UI",
+        "Value": [
+            "1.3.6.1.4.1.5962.99.1.1516012269.2092621929.1509049565933.1722.0"
+        ]
+    },
+    "00400242": {
+        "vr": "SH",
+        "Value": [
+            "L1BAY3_GE3T"
+        ]
+    },
+    "00181250": {
+        "vr": "SH",
+        "Value": [
+            "EIS_eCoil only"
+        ]
+    },
+    "00400244": {
+        "vr": "DA",
+        "Value": [
+            "20141210"
+        ]
+    },
+    "00400245": {
+        "vr": "TM",
+        "Value": [
+            "125903"
+        ]
+    },
+    "00281050": {
+        "vr": "DS",
+        "Value": [
+            5888.0
+        ]
+    },
+    "00281051": {
+        "vr": "DS",
+        "Value": [
+            11776.0
+        ]
+    },
+    "00185100": {
+        "vr": "CS",
+        "Value": [
+            "FFS"
+        ]
+    },
+    "00400254": {
+        "vr": "LO",
+        "Value": [
+            "M2197/MR3 Pelvis w"
+        ]
+    },
+    "00180091": {
+        "vr": "IS",
+        "Value": [
+            1
+        ]
+    },
+    "00180093": {
+        "vr": "DS",
+        "Value": [
+            100.0
+        ]
+    },
+    "00180094": {
+        "vr": "DS",
+        "Value": [
+            100.0
+        ]
+    },
+    "00180095": {
+        "vr": "DS",
+        "Value": [
+            1304.69
+        ]
+    },
+    "00181020": {
+        "vr": "LO",
+        "Value": [
+            "24",
+            "LX",
+            "MR Software release:DV24.0_R01_1344.a"
+        ]
+    },
+    "00080031": {
+        "vr": "TM",
+        "Value": [
+            "134344"
+        ]
+    },
+    "00080030": {
+        "vr": "TM",
+        "Value": [
+            "130727"
+        ]
+    },
+    "00080033": {
+        "vr": "TM",
+        "Value": [
+            "134345"
+        ]
+    },
+    "0008103E": {
+        "vr": "LO",
+        "Value": [
+            "Opt2 Ax Diff Nb8 Bmax=500"
+        ]
+    },
+    "0043106F": {
+        "vr": "DS",
+        "Value": [
+            0.0,
+            0.0,
+            0.0,
+            0.0
+        ]
+    },
+    "00201041": {
+        "vr": "DS",
+        "Value": [
+            -91.90546417
+        ]
+    },
+    "00201040": {
+        "vr": "LO",
+        "Value": [
+            ""
+        ]
+    },
+    "00200037": {
+        "vr": "DS",
+        "Value": [
+            1.0,
+            -0.0,
+            0.0,
+            -0.0,
+            0.982071,
+            0.188509
+        ]
+    },
+    "00280011": {
+        "vr": "US",
+        "Value": [
+            256
+        ]
+    },
+    "00100040": {
+        "vr": "CS",
+        "Value": [
+            "M"
+        ]
+    },
+    "00280107": {
+        "vr": "SS",
+        "Value": [
+            11776
+        ]
+    },
+    "00280106": {
+        "vr": "SS",
+        "Value": [
+            0
+        ]
+    },
+    "0020000D": {
+        "vr": "UI",
+        "Value": [
+            "1.3.6.1.4.1.5962.99.1.1516012269.2092621929.1509049565933.1720.0"
+        ]
+    },
+    "0020000E": {
+        "vr": "UI",
+        "Value": [
+            "1.3.6.1.4.1.5962.99.1.1516012269.2092621929.1509049565933.2967.0"
+        ]
+    },
+    "00280103": {
+        "vr": "US",
+        "Value": [
+            1
+        ]
+    },
+    "00280102": {
+        "vr": "US",
+        "Value": [
+            15
+        ]
+    },
+    "00280101": {
+        "vr": "US",
+        "Value": [
+            16
+        ]
+    },
+    "00280100": {
+        "vr": "US",
+        "Value": [
+            16
+        ]
+    },
+    "00181030": {
+        "vr": "LO",
+        "Value": [
+            "Prostate With e-coil C-/"
+        ]
+    },
+    "00180088": {
+        "vr": "DS",
+        "Value": [
+            4.0
+        ]
+    },
+    "00180083": {
+        "vr": "DS",
+        "Value": [
+            1.0
+        ]
+    },
+    "00200032": {
+        "vr": "DS",
+        "Value": [
+            -129.995,
+            -56.8856,
+            -108.805
+        ]
+    },
+    "00180081": {
+        "vr": "DS",
+        "Value": [
+            67.0
+        ]
+    },
+    "00180080": {
+        "vr": "DS",
+        "Value": [
+            3500.0
+        ]
+    },
+    "00180087": {
+        "vr": "DS",
+        "Value": [
+            3.0
+        ]
+    },
+    "00180086": {
+        "vr": "IS",
+        "Value": [
+            1
+        ]
+    },
+    "00180085": {
+        "vr": "SH",
+        "Value": [
+            "1H"
+        ]
+    },
+    "00180084": {
+        "vr": "DS",
+        "Value": [
+            127.767001
+        ]
+    },
+    "00100010": {
+        "vr": "PN",
+        "Value": [
+            {
+                "Alphabetic": "Prostate^Volunteer"
+            }
+        ]
+    },
+    "7FE00010": {
+        "vr": "OW",
+        "BulkDataURI": ""
+    },
+    "04000561": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "04000565": {
+                    "vr": "CS",
+                    "Value": [
+                        "COERCE"
+                    ]
+                },
+                "04000550": {
+                    "vr": "SQ",
+                    "Value": [
+                        {
+                            "00100010": {
+                                "vr": "PN",
+                                "Value": [
+                                    {
+                                        "Alphabetic": ""
+                                    }
+                                ]
+                            },
+                            "00100020": {
+                                "vr": "LO",
+                                "Value": [
+                                    ""
+                                ]
+                            },
+                            "00101010": {
+                                "vr": "AS",
+                                "Value": [
+                                    "051Y"
+                                ]
+                            },
+                            "00100030": {
+                                "vr": "DA",
+                                "Value": [
+                                    ""
+                                ]
+                            },
+                            "00100040": {
+                                "vr": "CS",
+                                "Value": [
+                                    "M"
+                                ]
+                            }
+                        }
+                    ]
+                },
+                "04000564": {
+                    "vr": "LO",
+                    "Value": [
+                        "Transfer Framework"
+                    ]
+                },
+                "04000563": {
+                    "vr": "LO",
+                    "Value": [
+                        "Siemens Healthcare syngo Transfer Framework"
+                    ]
+                },
+                "04000562": {
+                    "vr": "DT",
+                    "Value": [
+                        "20141222104657.146916"
+                    ]
+                }
+            }
+        ]
+    },
+    "00209056": {
+        "vr": "SH",
+        "Value": [
+            "1"
+        ]
+    },
+    "00280030": {
+        "vr": "DS",
+        "Value": [
+            0.7031,
+            0.7031
+        ]
+    },
+    "0018A001": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "00081010": {
+                    "vr": "SH",
+                    "Value": [
+                        "CORE_11112"
+                    ]
+                },
+                "00181020": {
+                    "vr": "LO",
+                    "Value": [
+                        "Wed May 24 09:06:52 EDT 2017"
+                    ]
+                },
+                "00081090": {
+                    "vr": "LO",
+                    "Value": [
+                        "DicomCleaner"
+                    ]
+                },
+                "00080070": {
+                    "vr": "LO",
+                    "Value": [
+                        "PixelMed"
+                    ]
+                },
+                "0018A002": {
+                    "vr": "DT",
+                    "Value": [
+                        "20171026182546.399-0400"
+                    ]
+                },
+                "0018A003": {
+                    "vr": "ST",
+                    "Value": [
+                        "Cleaned"
+                    ]
+                },
+                "0040A170": {
+                    "vr": "SQ",
+                    "Value": [
+                        {
+                            "00080100": {
+                                "vr": "SH",
+                                "Value": [
+                                    "109104"
+                                ]
+                            },
+                            "00080102": {
+                                "vr": "SH",
+                                "Value": [
+                                    "DCM"
+                                ]
+                            },
+                            "00080104": {
+                                "vr": "LO",
+                                "Value": [
+                                    "De-identifying Equipment"
+                                ]
+                            }
+                        }
+                    ]
+                }
+            }
+        ]
+    },
+    "00080050": {
+        "vr": "SH",
+        "Value": [
+            "1"
+        ]
+    },
+    "00180021": {
+        "vr": "CS",
+        "Value": [
+            "NONE"
+        ]
+    },
+    "00180020": {
+        "vr": "CS",
+        "Value": [
+            "EP",
+            "RM"
+        ]
+    },
+    "00180023": {
+        "vr": "CS",
+        "Value": [
+            "2D"
+        ]
+    },
+    "00180022": {
+        "vr": "CS",
+        "Value": [
+            "EDR_GEMS",
+            "EPI_GEMS"
+        ]
+    },
+    "00180025": {
+        "vr": "CS",
+        "Value": [
+            "N"
+        ]
+    },
+    "00209057": {
+        "vr": "UL",
+        "Value": [
+            13
+        ]
+    },
+    "00200011": {
+        "vr": "IS",
+        "Value": [
+            10
+        ]
+    },
+    "00200010": {
+        "vr": "SH",
+        "Value": [
+            ""
+        ]
+    },
+    "00280004": {
+        "vr": "CS",
+        "Value": [
+            "MONOCHROME2"
+        ]
+    },
+    "00200012": {
+        "vr": "IS",
+        "Value": [
+            1
+        ]
+    },
+    "00280002": {
+        "vr": "US",
+        "Value": [
+            1
+        ]
+    },
+    "00080060": {
+        "vr": "CS",
+        "Value": [
+            "MR"
+        ]
+    },
+    "00081111": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "00081150": {
+                    "vr": "UI",
+                    "Value": [
+                        "1.2.840.10008.3.1.2.3.3"
+                    ]
+                },
+                "00081155": {
+                    "vr": "UI",
+                    "Value": [
+                        "1.3.6.1.4.1.5962.99.1.1516012269.2092621929.1509049565933.1718.0"
+                    ]
+                }
+            }
+        ]
+    },
+    "00080008": {
+        "vr": "CS",
+        "Value": [
+            "ORIGINAL",
+            "PRIMARY",
+            "OTHER"
+        ]
+    },
+    "00180050": {
+        "vr": "DS",
+        "Value": [
+            4.0
+        ]
+    },
+    "00200013": {
+        "vr": "IS",
+        "Value": [
+            151
+        ]
+    },
+    "00101010": {
+        "vr": "AS",
+        "Value": [
+            "051Y"
+        ]
+    },
+    "00251007": {
+        "vr": "SL",
+        "Value": [
+            714
+        ]
+    },
+    "00080080": {
+        "vr": "LO",
+        "Value": [
+            "Brigham and Womens Hospital"
+        ]
+    },
+    "00280010": {
+        "vr": "US",
+        "Value": [
+            256
+        ]
+    },
+    "00181100": {
+        "vr": "DS",
+        "Value": [
+            180.0
+        ]
+    },
+    "00080032": {
+        "vr": "TM",
+        "Value": [
+            "134345"
+        ]
+    },
+    "00200060": {
+        "vr": "CS",
+        "Value": [
+            ""
+        ]
+    },
+    "00080070": {
+        "vr": "LO",
+        "Value": [
+            "GE MEDICAL SYSTEMS"
+        ]
+    }
+}
diff --git a/pydicom/data/test_files/test_PN.json b/pydicom/data/test_files/test_PN.json
new file mode 100644
index 000000000..92f3a966c
--- /dev/null
+++ b/pydicom/data/test_files/test_PN.json
@@ -0,0 +1,64 @@
+{
+    "00080090": {
+        "vr": "PN",
+        "Value": [
+            {
+                "Alphabetic": ""
+            }
+        ]
+    },
+    "00100010": {
+        "vr": "PN",
+        "Value": [
+            {
+                "Alphabetic": "Prostate^Volunteer"
+            }
+        ]
+    },
+    "04000561": {
+        "vr": "SQ",
+        "Value": [
+            {
+                "04000550": {
+                    "vr": "SQ",
+                    "Value": [
+                        {
+                            "00100010": {
+                                "vr": "PN",
+                                "Value": [
+                                    {
+                                        "Alphabetic": ""
+                                    }
+                                ]
+                            },
+                            "00100020": {
+                                "vr": "LO",
+                                "Value": [
+                                    ""
+                                ]
+                            },
+                            "00101010": {
+                                "vr": "AS",
+                                "Value": [
+                                    "051Y"
+                                ]
+                            },
+                            "00100030": {
+                                "vr": "DA",
+                                "Value": [
+                                    ""
+                                ]
+                            },
+                            "00100040": {
+                                "vr": "CS",
+                                "Value": [
+                                    "M"
+                                ]
+                            }
+                        }
+                    ]
+                }
+            }
+        ]
+    }
+}
diff --git a/pydicom/tests/test_charset.py b/pydicom/tests/test_charset.py
index f482b924e..007ab8e53 100644
--- a/pydicom/tests/test_charset.py
+++ b/pydicom/tests/test_charset.py
@@ -404,3 +404,36 @@ class TestCharset(object):
                                 u"- using default encoding instead"):
             pydicom.charset.decode(
                 elem, ['ISO 2022 IR 100', 'ISO 2022 IR 146'])
+
+    def test_japanese_multi_byte_personname(self):
+        """Test japanese person name which has multi byte strings are
+        correctly encoded."""
+        file_path = get_charset_files('chrH32.dcm')[0]
+        ds = dcmread(file_path)
+        ds.decode()
+
+        if hasattr(ds.PatientName, 'original_string'):
+            original_string = ds.PatientName.original_string
+            ds.PatientName.original_string = None
+            fp = DicomBytesIO()
+            fp.is_implicit_VR = False
+            fp.is_little_endian = True
+            ds.save_as(fp, write_like_original=False)
+            fp.seek(0)
+            ds_out = dcmread(fp)
+            assert original_string == ds_out.PatientName.original_string
+
+    def test_japanese_multi_byte_encoding(self):
+        """Test japanese multi byte strings are correctly encoded."""
+        encoded = pydicom.charset.encode_string(u'あaｱア',
+                                                ['shift_jis', 'iso2022_jp'])
+        assert b'\x1b$B$"\x1b(Ja\x1b)I\xb1\x1b$B%"\x1b(J' == encoded
+
+    def test_bad_japanese_encoding(self):
+        """Test japanese multi byte strings are not correctly encoded."""
+        with pytest.warns(UserWarning,
+                          match=u"Failed to encode value with encodings"
+                                u": shift_jis - using replacement character"
+                                u"s in encoded string"):
+            encoded = pydicom.charset.encode_string(u'あaｱア', ['shift_jis'])
+            assert b'?a??' == encoded
diff --git a/pydicom/tests/test_dataelem.py b/pydicom/tests/test_dataelem.py
index 4b1df7a49..a5011c867 100644
--- a/pydicom/tests/test_dataelem.py
+++ b/pydicom/tests/test_dataelem.py
@@ -317,6 +317,23 @@ class DataElementTests(unittest.TestCase):
         with pytest.raises(TypeError):
             elem[0]
 
+    def test_repval_large_elem(self):
+        """Test DataElement.repval doesn't return a huge string for a large
+        value"""
+        elem = DataElement(0x00820003, 'UT', 'a'*1000)
+        assert len(elem.repval) < 100
+
+    def test_repval_large_vm(self):
+        """Test DataElement.repval doesn't return a huge string for a large
+        vm"""
+        elem = DataElement(0x00080054, 'AE', 'a\\'*1000+'a')
+        assert len(elem.repval) < 100
+
+    def test_repval_strange_type(self):
+        """Test DataElement.repval doesn't break with bad types"""
+        elem = DataElement(0x00020001, 'OB', 0)
+        assert len(elem.repval) < 100
+
     def test_private_tag_in_repeater_range(self):
         """Test that an unknown private tag (e.g. a tag not in the private
         dictionary) in the repeater range is not handled as a repeater tag
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index 932551bbf..daad03c97 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -138,30 +138,22 @@ class DatasetTests(unittest.TestCase):
         # messages
         ds = Dataset()
         ds.PatientID = "123456"  # Valid value
-        ds.SmallestImagePixelValue = 0  # Invalid value
+        ds.SmallestImagePixelValue = BadRepr()  # Invalid value
 
-        if compat.in_PyPy:
-            expected_msg = ("With tag (0028, 0106) got exception: "
-                            "'int' has no length")
-        else:
-            expected_msg = ("With tag (0028, 0106) got exception: "
-                            "object of type 'int' has no len()")
+        expected_msg = ("With tag (0028, 0106) got exception: "
+                        "bad repr")
 
-        self.failUnlessExceptionArgs(expected_msg, TypeError, lambda: str(ds))
+        self.failUnlessExceptionArgs(expected_msg, ValueError, lambda: str(ds))
 
     def testTagExceptionWalk(self):
         # When recursing through dataset, a tag number should appear in
         # error messages
         ds = Dataset()
         ds.PatientID = "123456"  # Valid value
-        ds.SmallestImagePixelValue = 0  # Invalid value
+        ds.SmallestImagePixelValue = BadRepr()  # Invalid value
 
-        if compat.in_PyPy:
-            expected_msg = ("With tag (0028, 0106) got exception: "
-                            "'int' has no length")
-        else:
-            expected_msg = ("With tag (0028, 0106) got exception: "
-                            "object of type 'int' has no len()")
+        expected_msg = ("With tag (0028, 0106) got exception: "
+                        "bad repr")
 
         def callback(dataset, data_element):
             return str(data_element)
@@ -169,7 +161,7 @@ class DatasetTests(unittest.TestCase):
         def func(dataset=ds):
             return dataset.walk(callback)
 
-        self.failUnlessExceptionArgs(expected_msg, TypeError, func)
+        self.failUnlessExceptionArgs(expected_msg, ValueError, func)
 
     def dummy_dataset(self):
         # This dataset is used by many of the tests
@@ -1411,6 +1403,33 @@ class DatasetTests(unittest.TestCase):
         ds2.update(ds)
         assert ds2.PatientName == 'TestC'
 
+    def test_json_roundtrip(self):
+        ds = Dataset()
+        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)
+
+        jsonmodel = ds.to_json()
+        ds2 = Dataset.from_json(jsonmodel)
+
+        assert ds2.SpecificCharacterSet == ['ISO_IR 100']
+
+    def test_json_private_DS_VM(self):
+        test1_json = get_testdata_files("test1.json")[0]
+        if compat.in_py2:
+            jsonmodel = open(test1_json, 'r').read().encode("utf8")
+        else:
+            jsonmodel = open(test1_json, 'r').read()
+        ds = Dataset.from_json(jsonmodel)
+        import json
+        jsonmodel2 = ds.to_json(dump_handler=lambda d: json.dumps(d, indent=2))
+        ds2 = Dataset.from_json(jsonmodel2)
+
+        assert ds.PatientIdentityRemoved == 'YES'
+        assert ds2.PatientIdentityRemoved == 'YES'
 
 class DatasetElementsTests(unittest.TestCase):
     """Test valid assignments of data elements"""
@@ -1571,3 +1590,8 @@ class FileDatasetTests(unittest.TestCase):
         di = dict()
         expected_diff = {'__class__', '__doc__', '__hash__'}
         assert expected_diff == set(dir(di)) - set(dir(ds))
+
+
+class BadRepr(object):
+    def __repr__(self):
+        raise ValueError("bad repr")
diff --git a/pydicom/tests/test_json.py b/pydicom/tests/test_json.py
new file mode 100644
index 000000000..ef27f5c3d
--- /dev/null
+++ b/pydicom/tests/test_json.py
@@ -0,0 +1,50 @@
+import pytest
+from pydicom.data import get_testdata_files
+from pydicom.dataelem import DataElement
+from pydicom.dataset import Dataset
+from pydicom import compat
+
+from pydicom.valuerep import PersonNameUnicode, PersonName3
+
+def test_json_PN():
+    s = open(get_testdata_files("test_PN.json")[0], "r").read()
+    ds = Dataset.from_json(s)
+    assert isinstance(ds[0x00080090].value, (PersonNameUnicode, PersonName3))
+    assert isinstance(ds[0x00100010].value, (PersonNameUnicode, PersonName3))
+    inner_seq = ds[0x04000561].value[0][0x04000550]
+    dataelem = inner_seq[0][0x00100010]
+    assert isinstance(dataelem.value, (PersonNameUnicode, PersonName3))
+
+@pytest.mark.skipif(compat.in_py2,
+                    reason='JSON conversion not yet working in Python 2')
+def test_dataelem_from_json():
+    tag = 0x0080090
+    vr = "PN"
+    value = [{"Alphabetic":""}]
+    dataelem = DataElement.from_json(Dataset, tag, vr, value, "Value")
+    assert isinstance(dataelem.value, (PersonNameUnicode, PersonName3))
+
+def test_json_roundtrip():
+    ds = Dataset()
+    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)
+
+    jsonmodel = ds.to_json()
+    ds2 = Dataset.from_json(jsonmodel)
+
+    assert ds2.SpecificCharacterSet == ['ISO_IR 100']
+
+def test_json_private_DS_VM():
+    test1_json = get_testdata_files("test1.json")[0]
+    jsonmodel = open(test1_json, 'r').read()
+    ds = Dataset.from_json(jsonmodel)
+    import json
+    jsonmodel2 = ds.to_json(dump_handler=lambda d: json.dumps(d, indent=2))
+    ds2 = Dataset.from_json(jsonmodel2)
+
+    assert ds.PatientIdentityRemoved == 'YES'
+    assert ds2.PatientIdentityRemoved == 'YES'
diff --git a/pydicom/tests/test_numpy_pixel_data.py b/pydicom/tests/test_numpy_pixel_data.py
index dcafe5555..51a13a67f 100644
--- a/pydicom/tests/test_numpy_pixel_data.py
+++ b/pydicom/tests/test_numpy_pixel_data.py
@@ -986,13 +986,23 @@ class TestNumpy_GetPixelData(object):
         # Too short
         ds.PixelData = ds.PixelData[:-1]
         msg = (
-            r"The length of the pixel data in the dataset doesn't match the "
-            r"expected amount \(479999 vs. 480000 bytes\). The dataset may be "
-            r"corrupted or there may be an issue with the pixel data handler."
+            r"The length of the pixel data in the dataset \(479999 bytes\) "
+            r"doesn't match the expected length \(480000 bytes\). "
+            r"The dataset may be corrupted or there may be an issue "
+            r"with the pixel data handler."
         )
         with pytest.raises(ValueError, match=msg):
             get_pixeldata(ds)
 
+    def test_missing_padding_warns(self):
+        """A warning shall be issued if the padding for odd data is missing."""
+        ds = dcmread(EXPL_8_3_1F_ODD)
+        # remove the padding byte
+        ds.PixelData = ds.PixelData[:-1]
+        msg = "The pixel data length is odd and misses a padding byte."
+        with pytest.warns(UserWarning, match=msg):
+            get_pixeldata(ds)
+
     def test_change_photometric_interpretation(self):
         """Test get_pixeldata changes PhotometricInterpretation if required."""
         def to_rgb(ds):