Skip to content

Commit

Permalink
Fix LPI filter for data with even dimensions
Browse files Browse the repository at this point in the history
Also add `filter_forward` to public API: this is the functional
interface to the LPIFilter2D class.

A stringest test is added that compares filter output with that of
`skimage.filters.gaussian`.
  • Loading branch information
stefanv committed Apr 11, 2023
1 parent 590924a commit a1af8f6
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 14 deletions.
2 changes: 2 additions & 0 deletions skimage/filters/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ __all__ = [
"farid_h",
"farid_v",
"filter_inverse",
"filter_forward",
"frangi",
"gabor",
"gabor_kernel",
Expand Down Expand Up @@ -84,6 +85,7 @@ from .edges import (
from .lpi_filter import (
LPIFilter2D,
filter_inverse,
filter_forward,
inverse,
wiener,
)
Expand Down
23 changes: 13 additions & 10 deletions skimage/filters/lpi_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def _center(x, oshape):
"""Return an array of shape ``oshape`` from the center of array ``x``.
"""
start = (np.array(x.shape) - np.array(oshape)) // 2 + 1
start = (np.array(x.shape) - np.array(oshape)) // 2
out = x[tuple(slice(s, s + n) for s, n in zip(start, oshape))]
return out

Expand Down Expand Up @@ -66,11 +66,10 @@ def __init__(self, impulse_response, **filter_params):
Examples
--------
Gaussian filter: Use a 1-D gaussian in each direction without
normalization coefficients.
Gaussian filter without normalization of coefficients:
>>> def filt_func(r, c, sigma = 1):
... return np.exp(-np.hypot(r, c)/sigma)
>>> def filt_func(r, c, sigma=1):
... return np.exp(-(r**2 + c**2)/(2 * sigma**2))
>>> filter = LPIFilter2D(filt_func)
"""
Expand All @@ -86,14 +85,18 @@ def _prepare(self, data):
"""
dshape = np.array(data.shape)
dshape += (dshape % 2 == 0) # all filter dimensions must be uneven
even_offset = (dshape % 2 == 0).astype(int)
dshape += even_offset # all filter dimensions must be uneven
oshape = np.array(data.shape) * 2 - 1

float_dtype = _supported_float_type(data.dtype)
data = data.astype(float_dtype, copy=False)

if self._cache is None or np.any(self._cache.shape != oshape):
coords = np.mgrid[[slice(0, float(n)) for n in dshape]]
coords = np.mgrid[
[slice(0 + offset, float(n + offset))
for (n, offset) in zip(dshape, even_offset)]
]
# this steps over two sets of coordinates,
# not over the coordinates individually
for k, coord in enumerate(coords):
Expand Down Expand Up @@ -152,10 +155,10 @@ def filter_forward(data, impulse_response=None, filter_params=None,
Examples
--------
Gaussian filter:
Gaussian filter without normalization:
>>> def filt_func(r, c):
... return np.exp(-np.hypot(r, c)/1)
>>> def filt_func(r, c, sigma=1):
... return np.exp(-(r**2 + c**2)/(2 * sigma**2))
>>>
>>> from skimage import data
>>> filtered = filter_forward(data.coins(), filt_func)
Expand Down
36 changes: 32 additions & 4 deletions skimage/filters/tests/test_lpi_filter.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
import numpy as np
import pytest
from numpy.testing import assert_, assert_equal
from numpy.testing import assert_, assert_equal, assert_array_almost_equal

from skimage._shared.utils import _supported_float_type
from skimage.data import camera
from skimage.filters.lpi_filter import LPIFilter2D, filter_inverse, wiener
from skimage.data import camera, coins
from skimage.filters import (
LPIFilter2D, filter_inverse, filter_forward, wiener, gaussian
)


def test_filter_forward():
def filt_func(r, c, sigma=2):
return (1 / (2 * np.pi * sigma**2)) * np.exp(-(r**2 + c**2) / (2 * sigma ** 2))

gaussian_args = {
'sigma': 2,
'preserve_range': True,
'mode': 'constant',
'truncate': 20 # LPI filtering is more precise than the truncated
# Gaussian, so don't truncate at the default of 4 sigma
}

# Odd image size
image = coins()[:303, :383]
filtered = filter_forward(image, filt_func)
filtered_gaussian = gaussian(image, **gaussian_args)
assert_array_almost_equal(filtered, filtered_gaussian)

# Even image size
image = coins()
filtered = filter_forward(image, filt_func)
filtered_gaussian = gaussian(image, **gaussian_args)
assert_array_almost_equal(filtered, filtered_gaussian)



class TestLPIFilter2D:

img = camera()[:50, :50]

def filt_func(self, r, c):
return np.exp(-np.hypot(r, c) / 1)
return np.exp(-(r**2 + c**2) / 1)

def setup_method(self):
self.f = LPIFilter2D(self.filt_func)
Expand Down

0 comments on commit a1af8f6

Please sign in to comment.