Repository: scipy/scipy
Pull request: Add warning to medfilt when array size < kernel_size
Warn when array size is lesser than kernel size along any axis



<!-- 
Thanks for contributing a pull request! Please ensure that
your PR satisfies the checklist before submitting:
http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#checklist-before-submitting-a-pr

Also, please name and describe your PR as you would write a
commit message:
https://docs.scipy.org/doc/numpy/dev/development_workflow.html#writing-the-commit-message

Note that we are a team of volunteers; we appreciate your
patience during the review process.

Again, thanks for contributing!
-->

#### Reference issue
<!--Example: Closes gh-WXYZ.-->
Closes #11503 

#### What does this implement/fix?
<!--Please explain your changes.-->
After this change user will be notified in case when provided array should be zero-padded

#### Additional information
<!--Any additional information you think is important.-->
As far as I can see, failing tests do not depend on changes which have been made.

Problem statement:
medfilt produces 0 on input of length 1
scipy.signal.medfilt returns zero when filtering a singleton list, rather than the the value in the singleton.  This feels wrong, but if it's as intended, could the documentation be clearer?

```
>>> for i in range(5, -1, -1): print(scipy.signal.medfilt([99]*i))

[ 99.  99.  99.  99.  99.]
[ 99.  99.  99.  99.]
[ 99.  99.  99.]
[ 99.  99.]
[ 0.]           <---- !!
[]
```

#### Scipy/Numpy/Python version information:
```
>>> import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
0.19.1 1.13.3 sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
```

Hints from issue discussion:
Hi! I would like to work on this issue. I'll  share some thoughts soon
@aardvarkkrill  it's said in documentation that "default size is 3 for each dimension" and "array will be automatically 0-padded", so when You have 1-element array it should add 2 zeros, and then return 0 as median value. I think it maybe be helpful to add such special case for documentation, but this is definitely not an issue, so might be closed.
I agree with @erheron that it is not a bug for the reason explained. Setting `kernel_size=1` gives the result @aardvarkkrill was expecting. 

One possibility would be to raise a `UserWarning` for cases when the signal size is less than the `kernel_size`.
I'm happy to accept that the function agrees with the documentation.  

However, I feel that the filtered list should lie in the same "range of values" as the unfiltered list in some way.  For example, a law like:  `medianfilter(k*xs+m) = medianfilter(k*xs)+m `  (where xs is a numpy array, k and m are any floats.)  To achieve this I imagine the kernel would have to shrink for shorter inputs, or pad with a median value, or similar)

Anyway, thank you for looking in to it.
I'm working on it now to implement suggestion from @grlee77 . PR will come soon :p

Commit message: Add pytest.warns to test suite

Patch to review:
diff --git a/scipy/signal/signaltools.py b/scipy/signal/signaltools.py
index 0ee89b13fe68..59c96873d81f 100644
--- a/scipy/signal/signaltools.py
+++ b/scipy/signal/signaltools.py
@@ -19,6 +19,7 @@
 from .filter_design import cheby1, _validate_sos
 from .fir_filter_design import firwin
 from ._sosfilt import _sosfilt
+import warnings
 
 
 __all__ = ['correlate', 'correlate2d',
@@ -1378,6 +1379,11 @@ def medfilt(volume, kernel_size=None):
         An array the same size as input containing the median filtered
         result.
 
+    Raises
+    -------
+        UserWarning
+            If array size is lesser than kernel size along any dimension
+
     See also
     --------
     scipy.ndimage.median_filter
@@ -1397,6 +1403,11 @@ def medfilt(volume, kernel_size=None):
     for k in range(volume.ndim):
         if (kernel_size[k] % 2) != 1:
             raise ValueError("Each element of kernel_size should be odd.")
+    shape = volume.shape
+    for k in range(volume.ndim):
+        if (kernel_size[k] > shape[k]):
+            warnings.warn(f'Array size is less then kernel size '
+                          f'along axis nr. {k}, input will be zero-padded', UserWarning)
 
     domain = np.ones(kernel_size)
 
diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py
index 8d9d489b337e..d33e06f869de 100644
--- a/scipy/signal/tests/test_signaltools.py
+++ b/scipy/signal/tests/test_signaltools.py
@@ -1034,7 +1034,8 @@ def test_basic(self):
 
     def test_none(self):
         # Ticket #1124. Ensure this does not segfault.
-        signal.medfilt(None)
+        with pytest.warns(UserWarning):
+            signal.medfilt(None)
         # Expand on this test to avoid a regression with possible contiguous
         # numpy arrays that have odd strides. The stride value below gets
         # us into wrong memory if used (but it does not need to be used)
@@ -1052,8 +1053,9 @@ def test_refcounting(self):
         else:
             n = 10
         # Shouldn't segfault:
-        for j in range(n):
-            signal.medfilt(x)
+        with pytest.warns(UserWarning):
+            for j in range(n):
+                signal.medfilt(x)
         if hasattr(sys, 'getrefcount'):
             assert_(sys.getrefcount(a) < n)
         assert_equal(x, [a, a])


Code review comments:
diff_hunk: @@ -1378,6 +1379,11 @@ def medfilt(volume, kernel_size=None):
         An array the same size as input containing the median filtered
         result.
 
+    Raises
+    -------
+        UserWarning
path: scipy/signal/signaltools.py
text: @user1:
I just grepped through our code base and couldn't find any example of a `Warning` being mentioned in a `Raises` section of a docstring. The identation is a little strange here too, though it doesn't seem to affect the render.

@user1:
I don't know if it might be better to place in a `Notes` section or something like that--let's wait for another person to chime in on that.

@user2:
Yeah, this would go under a `Warns` section rather than `Raises` one.

As you noted, we do not seem to have been documenting such warnings in SciPy, so not sure if we want to do it here or not. NumPy itself does seem to use it occasionally, but not consistently.

**edit**: On second look, SciPy does have a `Warns` section in a small number of functions (e.g. `scipy.signal.peak_prominences`)

@author:
@user2, it will go to `Warns` section then

diff_hunk: @@ -1397,6 +1403,11 @@ def medfilt(volume, kernel_size=None):
     for k in range(volume.ndim):
         if (kernel_size[k] % 2) != 1:
             raise ValueError("Each element of kernel_size should be odd.")
+    shape = volume.shape
+    for k in range(volume.ndim):
+        if (kernel_size[k] > shape[k]):
+            warnings.warn(f'Array size is less then kernel size '
+                          f'along axis nr. {k}, input will be zero-padded', UserWarning)
path: scipy/signal/signaltools.py
text: @user1:
```suggestion
    if any(k > s for k, s in zip(kernel_size, volume.shape)):
        warnings.warn('kernel_size exceeds volume extent: the volume will be zero-padded.')
```

@user1:
I deleted my prior suggestion (decided it is probably preferable to just warn once)