Repository: pydicom/pydicom

Issue:
Wrong encoding occurs if the value 1 of SpecificCharacterSets is ISO 2022 IR 13.
<!-- Instructions For Filing a Bug: https://github.com/pydicom/pydicom/blob/master/CONTRIBUTING.md#filing-bugs -->

#### Description
All Japanese characters are encoded into shift_jis if the value 1 of SpecificCharacterSets (0x0008, 0x0005) is  ISO 2022 IR 13.

#### Steps/Code to Reproduce
The japanese_pn and expect_encoded in the following code came from 
[H.3.2 Value 1 of Attribute Specific Character Set (0008,0005) is ISO 2022 IR 13.](http://dicom.nema.org/medical/dicom/2015b/output/chtml/part05/sect_H.3.2.html)

```py
import pydicom

japanese_pn = u"ﾔﾏﾀﾞ^ﾀﾛｳ=山田^太郎=やまだ^たろう"
specific_character_sets = ["ISO 2022 IR 13", "ISO 2022 IR 87"]
expect_encoded = (
    b"\xd4\xcf\xc0\xde\x5e\xc0\xdb\xb3\x3d\x1b\x24\x42\x3b\x33"
    b"\x45\x44\x1b\x28\x4a\x5e\x1b\x24\x42\x42\x40\x4f\x3a\x1b"
    b"\x28\x4a\x3d\x1b\x24\x42\x24\x64\x24\x5e\x24\x40\x1b\x28"
    b"\x4a\x5e\x1b\x24\x42\x24\x3f\x24\x6d\x24\x26\x1b\x28\x4a"
)

python_encodings = pydicom.charset.convert_encodings(specific_character_sets)
actual_encoded = pydicom.charset.encode_string(japanese_pn, python_encodings)

print("actual:{}".format(actual_encoded))
print("expect:{}".format(expect_encoded))
```
<!--
Example:
```py
from io import BytesIO
from pydicom import dcmread

bytestream = b'\x02\x00\x02\x00\x55\x49\x16\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31' \
             b'\x30\x30\x30\x38\x2e\x35\x2e\x31\x2e\x31\x2e\x39\x00\x02\x00\x10\x00' \
             b'\x55\x49\x12\x00\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30\x30\x30\x38' \
             b'\x2e\x31\x2e\x32\x00\x20\x20\x10\x00\x02\x00\x00\x00\x01\x00\x20\x20' \
             b'\x20\x00\x06\x00\x00\x00\x4e\x4f\x52\x4d\x41\x4c'

fp = BytesIO(bytestream)
ds = dcmread(fp, force=True)

print(ds.PatientID)
```
If the code is too long, feel free to put it in a public gist and link
it in the issue: https://gist.github.com

When possible use pydicom testing examples to reproduce the errors. Otherwise, provide
an anonymous version of the data in order to replicate the errors.
-->

#### Expected Results
<!-- Please paste or describe the expected results.
Example: No error is thrown and the name of the patient is printed.-->
```
b'\xd4\xcf\xc0\xde^\xc0\xdb\xb3=\x1b$B;3ED\x1b(J^\x1b$BB@O:\x1b(J=\x1b$B$d$^$@\x1b(J^\x1b$B$?$m$&\x1b(J'
```

#### Actual Results
<!-- Please paste or specifically describe the actual output or traceback.
(Use %xmode to deactivate ipython's trace beautifier)
Example: ```AttributeError: 'FileDataset' object has no attribute 'PatientID'```
-->
```
b'\xd4\xcf\xc0\xde^\xc0\xdb\xb3=\x8eR\x93c^\x91\xbe\x98Y=\x82\xe2\x82\xdc\x82\xbe^\x82\xbd\x82\xeb\x82\xa4'
```

#### Versions
<!--
Please run the following snippet and paste the output below.
import platform; print(platform.platform())
import sys; print("Python", sys.version)
import pydicom; print("pydicom", pydicom.__version__)
-->
```
Linux-4.15.0-50-generic-x86_64-with-debian-buster-sid
Python 3.6.8 |Anaconda, Inc.| (default, Dec 30 2018, 01:22:34)
[GCC 7.3.0]
pydicom 1.2.2
```

<!-- Thanks for contributing! -->


Discussion:
pep8speaks: Hello @ar90n! 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/charset.py`](https://github.com/pydicom/pydicom/blob/11636c89a5faf83b3cea66c8ac8307d06da1eb89/pydicom/charset.py):

> [Line 112:80](https://github.com/pydicom/pydicom/blob/11636c89a5faf83b3cea66c8ac8307d06da1eb89/pydicom/charset.py#L112): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (94 > 79 characters)




mrbean-bremen: There is a test (`TestWriteTest.test_invalid_encoding`) that runs into an infinite loop now, but you probably noticed this already...
ar90n: I fixed the infinite loop you said above. But it seems that there are still issues. I'm trying to fix them.

Patch:
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/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..5c2c14a3a
--- /dev/null
+++ b/doc/python2_support.rst
@@ -0,0 +1,42 @@
+.. _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 in December 2019) will support Python 2.7,
+  with deprecation warning as above.
+
+* pydicom v1.5 (planned for ~April 2020) officially will not support Python 2.7,
+  but no compatibility code will be removed from pydicom; it will include a stronger 
+  'deprecated' (past tense) message.  It will not install using pip, but those who
+  choose to do so could download and install themselves; however, we make no guarantees
+  that it will function correctly.
+
+* pydicom v1.6 (no date set) will remove Python 2 code from pydicom, and officially
+  drop Python 3.4 support as well. Any new pydicom code afterwards can target
+  Python 3.5+ features. 
+
+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..5b06b5147 100644
--- a/doc/whatsnew/v1.3.0.rst
+++ b/doc/whatsnew/v1.3.0.rst
@@ -26,6 +26,8 @@ Enhancements
 * 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`)
 
 Fixes
 .....
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/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 = (


Test patch:
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_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):