Has someone found/understood how works scipy.ndimage.median_filter for even sizes?
Because I tested a lot of theories and tried to read the source code, but I haven't an explanation
(Of course it's better to use odd sizes to avoid shifts, but it's just interesting how exactly median_filter counts it for even numbers...)
Problem definition:
from scipy.ndimage import median_filter
import numpy as np
arr = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]])
print('Size 3:')
print(median_filter(arr, size=3, cval=0, mode='constant'))
print('Size 2:')
print(median_filter(arr, size=2, cval=0, mode='constant'))
#with cval=0, mode='constant' we set that input array is extended with zeros
#when window overlaps edges, just for visibility and ease of calculation
Output
Size 3
[[0. 2. 0.]
[2. 5. 3.]
[0. 5. 0.]]
Size 2
[[0. 1. 2.]
[1. 4. 5.]
[4. 7. 8.]]
As we can see for size 3 all values are like expected, but values for size 2 are not obvious.
If suppose that window has shape (2, 2) let's, for example, define and count all possible windows and medians for array element "9". We can cover "9" (or any other value from array) with window of shape (2, 2) in 4 different variants as shown below,
And corresponding median values would be: for red rectangle - 3, yellow - 7, green - 0, blue - 4... But the output value is 8. Even if imagine that afterwards some additional operations are applied to these medians (mean, median etc.), these operation are not obvious.
Interesting fact #1
It seems that for 1d median_filter just takes size-1 (so, odd) number. Example:
arr = np.array([1., 2., 3., 4., 5.])
print('1 - ', median_filter(arr, size=1, cval=0, mode='constant'))
print('2 - ', median_filter(arr, size=2, cval=0, mode='constant'))
print('3 - ', median_filter(arr, size=3, cval=0, mode='constant'))
print('4 - ', median_filter(arr, size=4, cval=0, mode='constant'))
print('5 - ', median_filter(arr, size=5, cval=0, mode='constant'))
print('6 - ', median_filter(arr, size=6, cval=0, mode='constant'))
Output
1 - [1. 2. 3. 4. 5.]
2 - [1. 2. 3. 4. 5.]
3 - [1. 2. 3. 4. 4.]
4 - [1. 2. 3. 4. 4.]
5 - [1. 2. 3. 3. 3.]
6 - [1. 2. 3. 3. 3.]
Interesting fact #2
In general median_filter with even numbers works quite well, blurs images, it's even hard to determine some difference or shifts


In scipy.ndimage.median_filter, a special case occurs when size = 2. Take a look at the source code at https://github.com/scipy/scipy/blob/v1.8.1/scipy/ndimage/_filters.py#L1351-L1396
The median filter calls the function
_rank_filter, which you can find here: https://github.com/scipy/scipy/blob/4cf21e753cf937d1c6c2d2a0e372fbc1dbbeea81/scipy/ndimage/_filters.py#L1244In the function, you'll find the code:
Additionally, earlier in that function
rank=size//2.In this case, the program is performing a minimum filter, and not a true median filter (which would involve averaging).
Both the conditions
rank==0andrank==size-1are true, but control flow prioritizesrank==0, which corresponds to the minimum filter in the code.Sizes >=4 may work differently.