Repository: dicompyler/dicompyler-core

Issue:
dvh.max gives IndexError
Hello and thank you for the project.

I am using dicompyler-core 0.5.4 to extract statistics and I have been running into an exception with certain DCM files.  When calculating `dvh.max`, the following error is thrown:

```
 File "/home/nico/.local/share/virtualenvs/dcm-extractor-8Y6EYVZw/lib/python3.7/site-packages/dicompylercore/dvh.py", line 269, in max
    return diff.bins[1:][diff.counts > 0][-1]
IndexError: boolean index did not match indexed array along dimension 0; dimension is 0 but corresponding boolean dimension is 1
```

This seems to happen whenever dvh.counts is either empty, or has all same values (such that dvh.diff.counts is all 0s).

Please let me know if I can assist in troubleshooting.


Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 8de7b4f..d399453 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 language: python
 python:
     - 2.7
-    - 3.4
     - 3.5
     - 3.6
 install:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index e37af2c..048bc4b 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -101,7 +101,7 @@ Before you submit a pull request, check that it meets these guidelines:
 2. If the pull request adds functionality, the docs should be updated. Put
    your new functionality into a function with a docstring, and add the
    feature to the list in README.rst.
-3. The pull request should work for Python 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check
+3. The pull request should work for Python 2.7, 3.5, and 3.6. Check
    https://travis-ci.org/dicompyler/dicompyler-core/pull_requests
    and make sure that the tests pass for all supported Python versions.
 
diff --git a/README.rst b/README.rst
index cf7c59f..c24a703 100644
--- a/README.rst
+++ b/README.rst
@@ -15,7 +15,7 @@ Other information
 
 -  Free software: `BSD license <https://github.com/dicompyler/dicompyler-core/blob/master/LICENSE>`__
 -  Documentation: `Read the docs <https://dicompyler-core.readthedocs.io>`__
--  Tested on Python 2.7, 3.4, 3.5, 3.6
+-  Tested on Python 2.7, 3.5, 3.6
 
 Dependencies
 ------------
diff --git a/dicompylercore/dicomparser.py b/dicompylercore/dicomparser.py
index 3be8daa..2aa8346 100755
--- a/dicompylercore/dicomparser.py
+++ b/dicompylercore/dicomparser.py
@@ -410,7 +410,7 @@ class DicomParser:
             if (pixel_array.min() < wmin):
                 wmin = pixel_array.min()
             # Default window is the range of the data array
-            window = int(abs(wmax) + abs(wmin))
+            window = int(wmax - wmin)
             # Default level is the range midpoint minus the window minimum
             level = int(window / 2 - abs(wmin))
         return window, level
diff --git a/dicompylercore/dvh.py b/dicompylercore/dvh.py
index 24155a4..14c2777 100644
--- a/dicompylercore/dvh.py
+++ b/dicompylercore/dvh.py
@@ -262,7 +262,7 @@ class DVH(object):
     @property
     def max(self):
         """Return the maximum dose."""
-        if self.counts.size == 1:
+        if self.counts.size <= 1 or max(self.counts) == 0:
             return 0
         diff = self.differential
         # Find the the maximum non-zero dose bin
@@ -271,7 +271,7 @@ class DVH(object):
     @property
     def min(self):
         """Return the minimum dose."""
-        if self.counts.size == 1:
+        if self.counts.size <= 1 or max(self.counts) == 0:
             return 0
         diff = self.differential
         # Find the the minimum non-zero dose bin
@@ -280,7 +280,7 @@ class DVH(object):
     @property
     def mean(self):
         """Return the mean dose."""
-        if self.counts.size == 1:
+        if self.counts.size <= 1 or max(self.counts) == 0:
             return 0
         diff = self.differential
         # Find the area under the differential histogram
@@ -462,7 +462,7 @@ class DVH(object):
         else:
             volume_counts = self.absolute_volume(self.volume).counts
 
-        if volume > volume_counts.max():
+        if volume_counts.size == 0 or volume > volume_counts.max():
             return DVHValue(0.0, self.dose_units)
 
         # D100 case
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 8193cbd..7af08f4 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,8 +1,8 @@
 bumpversion==0.5.3
-wheel==0.32.3
-flake8==3.6.0
-tox==3.6.1
+wheel==0.33.1
+flake8==3.7.7
+tox==3.7.0
 coverage==4.5.2
-Sphinx==1.8.3
-sphinx-rtd-theme==0.4.2
-cryptography==2.4.2
+Sphinx==1.8.4
+sphinx-rtd-theme==0.4.3
+cryptography==2.6.1
diff --git a/setup.py b/setup.py
index 59ad242..82124af 100644
--- a/setup.py
+++ b/setup.py
@@ -62,8 +62,6 @@ setup(
         "Programming Language :: Python :: 2",
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Topic :: Scientific/Engineering :: Medical Science Apps.',


Test patch:
diff --git a/tests/test_dvh.py b/tests/test_dvh.py
index 6c9d403..3e64782 100644
--- a/tests/test_dvh.py
+++ b/tests/test_dvh.py
@@ -204,6 +204,27 @@ class TestDVH(unittest.TestCase):
         self.dvh.name = "test"
         self.assertEqual(self.dvh.plot(), self.dvh)
 
+    def test_dvh_statistics_with_no_counts(self):
+        subject = dvh.DVH(array([]), array([0]))
+        self.assertEqual(subject.max, 0)
+        self.assertEqual(subject.min, 0)
+        self.assertEqual(subject.mean, 0)
+
+    def test_dose_constraint_with_no_counts(self):
+        subject = dvh.DVH(array([]), array([0]))
+        subject.dose_constraint(1)
+
+    def test_dvh_statistics_with_zero_volume(self):
+        subject = dvh.DVH(array([0, 0]), array([0, 1]))
+        self.assertEqual(subject.max, 0)
+        self.assertEqual(subject.min, 0)
+        self.assertEqual(subject.mean, 0)
+
+    def test_dose_constraint_with_zero_volume(self):
+        subject = dvh.DVH(array([0, 0]), array([0, 1]))
+        subject.dose_constraint(1)
+
+
 if __name__ == '__main__':
     import sys
     sys.exit(unittest.main())