Skip to content

Commit

Permalink
Implement additional deprecations carried out for scikit-image 0.20 (#…
Browse files Browse the repository at this point in the history
…451)

related to #419

This PR removes most of the deprecated parameters that are being removed in scikit-image 0.20.

There are a few exceptions (e.g. `selem`) where they will be removed in scikit-image 0.20, but we had previously advertised a specific removal date like `2022.03.02`. For those, I will delay the removal until the previously advertised release.

It will be nice to be rid of all the deprecated `multichannel` code! The first, large commit here is just the multichannel deprecation. The rest of the commits are much smaller.

Authors:
  - Gregory Lee (https://github.com/grlee77)

Approvers:
  - Gigon Bae (https://github.com/gigony)

URL: #451
  • Loading branch information
grlee77 authored Nov 30, 2022
1 parent 35f0792 commit a7490ca
Show file tree
Hide file tree
Showing 28 changed files with 120 additions and 517 deletions.
60 changes: 42 additions & 18 deletions python/cucim/src/cucim/skimage/_shared/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,30 @@

import cucim.skimage._vendored.ndimage as ndi

from .._shared import utils
from .._shared.utils import _supported_float_type, convert_to_float
from .._shared.utils import _supported_float_type, convert_to_float, warn


class _PatchClassRepr(type):
"""Control class representations in rendered signatures."""
def __repr__(cls):
return f"<{cls.__name__}>"


class ChannelAxisNotSet(metaclass=_PatchClassRepr):
"""Signal that the `channel_axis` parameter is not set.
This is a proxy object, used to signal to `skimage.filters.gaussian` that
the `channel_axis` parameter has not been set, in which case the function
will determine whether a color channel is present. We cannot use ``None``
for this purpose as it has its own meaning which indicates that the given
image is grayscale.
This automatic behavior was broken in v0.19, recovered but deprecated in
v0.20 and will be removed in v0.21.
"""


@utils.deprecate_multichannel_kwarg(multichannel_position=5)
def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
multichannel=None, preserve_range=False, truncate=4.0, *,
channel_axis=None):
preserve_range=False, truncate=4.0, *,
channel_axis=ChannelAxisNotSet):
"""Multi-dimensional Gaussian filter.
Parameters
Expand All @@ -39,13 +55,6 @@ def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
cval : scalar, optional
Value to fill past edges of input if ``mode`` is 'constant'. Default
is 0.0
multichannel : bool, optional (default: None)
Whether the last axis of the image is to be interpreted as multiple
channels. If True, each channel is filtered separately (channels are
not mixed together). Only 3 channels are supported. If ``None``,
the function will attempt to guess this, and raise a warning if
ambiguous, when the array has shape (M, N, 3).
This argument is deprecated: specify `channel_axis` instead.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of ``img_as_float``.
Expand All @@ -58,6 +67,14 @@ def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
Otherwise, this parameter indicates which axis of the array corresponds
to channels.
.. warning::
Automatic detection of the color channel based on the old deprecated
``multichannel=None`` was broken in version 0.19. In 0.20 this
behavior is recovered. The last axis of an `image` with dimensions
(M, N, 3) is interpreted as a color channel if `channel_axis` is
not set. Starting with 2023.03.06, ``channel_axis=None`` will be
used as the new default value.
Returns
-------
filtered_image : ndarray
Expand Down Expand Up @@ -109,12 +126,19 @@ def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
>>> filtered_img = gaussian(image, sigma=1, channel_axis=-1)
"""
if image.ndim == 3 and image.shape[-1] == 3 and channel_axis is None:
msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB "
"by default. Use `multichannel=False` to interpret as "
"3D image with last dimension of length 3.")
utils.warn(RuntimeWarning(msg))
channel_axis = -1
if channel_axis is ChannelAxisNotSet:
if image.ndim == 3 and image.shape[-1] == 3:
warn(
"Automatic detection of the color channel was deprecated in "
"v0.19, and `channel_axis=None` will be the new default in "
"v0.21. Set `channel_axis=-1` explicitly to silence this "
"warning.",
FutureWarning,
stacklevel=2,
)
channel_axis = -1
else:
channel_axis = None

# CuPy Backend: refactor to avoid overhead of cp.any(cp.asarray(sigma))
sigma_msg = "Sigma values less than zero are not valid"
Expand Down
62 changes: 0 additions & 62 deletions python/cucim/src/cucim/skimage/_shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,68 +238,6 @@ def fixed_func(*args, **kwargs):
return fixed_func


class deprecate_multichannel_kwarg(deprecate_kwarg):
"""Decorator for deprecating multichannel keyword in favor of channel_axis.
Parameters
----------
removed_version : str
The package version in which the deprecated argument will be
removed.
"""

def __init__(self, removed_version='2023.02.00',
multichannel_position=None):
super().__init__(
kwarg_mapping={'multichannel': 'channel_axis'},
deprecated_version='22.02.00',
warning_msg=None,
removed_version=removed_version)
self.position = multichannel_position

def __call__(self, func):
@functools.wraps(func)
def fixed_func(*args, **kwargs):

if self.position is not None and len(args) > self.position:
warning_msg = (
"Providing the `multichannel` argument positionally to "
"{func_name} is deprecated. Use the `channel_axis` kwarg "
"instead."
)
warnings.warn(warning_msg.format(func_name=func.__name__),
FutureWarning,
stacklevel=2)
if 'channel_axis' in kwargs:
raise ValueError(
"Cannot provide both a `channel_axis` kwarg and a "
"positional `multichannel` value."
)
else:
channel_axis = -1 if args[self.position] else None
kwargs['channel_axis'] = channel_axis

if 'multichannel' in kwargs:
# warn that the function interface has changed:
warnings.warn(self.warning_msg.format(
old_arg='multichannel', func_name=func.__name__,
new_arg='channel_axis'), FutureWarning, stacklevel=2)

# multichannel = True -> last axis corresponds to channels
convert = {True: -1, False: None}
kwargs['channel_axis'] = convert[kwargs.pop('multichannel')]

# Call the function with the fixed arguments
return func(*args, **kwargs)

if func.__doc__ is not None:
newdoc = docstring_add_deprecated(
func, {'multichannel': 'channel_axis'}, '22.02.00')
fixed_func.__doc__ = newdoc
return fixed_func


class channel_as_last_axis():
"""Decorator for automatically making channels axis last for all arrays.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ def _match_cumulative_cdf(source, template):


@utils.channel_as_last_axis(channel_arg_positions=(0, 1))
@utils.deprecate_multichannel_kwarg()
def match_histograms(image, reference, *, channel_axis=None,
multichannel=False):
def match_histograms(image, reference, *, channel_axis=None):
"""Adjust an image so that its cumulative histogram matches that of another.
The adjustment is applied separately for each channel.
Expand All @@ -50,9 +48,6 @@ def match_histograms(image, reference, *, channel_axis=None,
If None, the image is assumed to be a grayscale (single channel) image.
Otherwise, this parameter indicates which axis of the array corresponds
to channels.
multichannel : bool, optional
Apply the matching separately for each channel. This argument is
deprecated: specify `channel_axis` instead.
Returns
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
import numpy as np
import pytest
from cupy.testing import assert_array_almost_equal
from numpy.testing import assert_almost_equal
from skimage import data

from cucim.skimage import exposure
from cucim.skimage._shared.testing import expected_warnings
from cucim.skimage._shared.utils import _supported_float_type
from cucim.skimage.exposure import histogram_matching

Expand All @@ -28,36 +26,6 @@ class TestMatchHistogram:
image_rgb = cp.asarray(data.chelsea())
template_rgb = cp.asarray(data.astronaut())

@pytest.mark.parametrize('image, reference, multichannel', [
(image_rgb, template_rgb, True),
(image_rgb[:, :, 0], template_rgb[:, :, 0], False)
])
def test_match_histograms(self, image, reference, multichannel):
"""Assert that pdf of matched image is close to the reference's pdf for
all channels and all values of matched"""

with expected_warnings(["`multichannel` is a deprecated argument"]):
matched = exposure.match_histograms(image, reference,
multichannel=multichannel)

matched = cp.asnumpy(matched)
matched_pdf = self._calculate_image_empirical_pdf(matched)
reference_pdf = self._calculate_image_empirical_pdf(
cp.asnumpy(reference))

# then
for channel in range(len(matched_pdf)):
reference_values, reference_quantiles = reference_pdf[channel]
matched_values, matched_quantiles = matched_pdf[channel]

for i, matched_value in enumerate(matched_values):
closest_id = (
np.abs(reference_values - matched_value)
).argmin()
assert_almost_equal(matched_quantiles[i],
reference_quantiles[closest_id],
decimal=1)

@pytest.mark.parametrize('channel_axis', (0, 1, -1))
def test_match_histograms_channel_axis(self, channel_axis):
"""Assert that pdf of matched image is close to the reference's pdf for
Expand Down
6 changes: 0 additions & 6 deletions python/cucim/src/cucim/skimage/feature/_basic_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import numpy as np

from cucim.skimage import feature, filters
from cucim.skimage._shared import utils
from cucim.skimage.util import img_as_float32

from .._shared._gradient import gradient
Expand Down Expand Up @@ -100,10 +99,8 @@ def _mutiscale_basic_features_singlechannel(
return features


@utils.deprecate_multichannel_kwarg(multichannel_position=1)
def multiscale_basic_features(
image,
multichannel=False,
intensity=True,
edges=True,
texture=True,
Expand All @@ -123,9 +120,6 @@ def multiscale_basic_features(
----------
image : ndarray
Input image, which can be grayscale or multichannel.
multichannel : bool, default False
True if the last dimension corresponds to color channels.
This argument is deprecated: specify `channel_axis` instead.
intensity : bool, default True
If True, pixel intensities averaged over the different scales
are added to the feature set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import numpy as np
import pytest

from cucim.skimage._shared.testing import expected_warnings
from cucim.skimage.feature import multiscale_basic_features


Expand All @@ -23,49 +22,6 @@ def test_multiscale_basic_features_gray(edges, texture):
assert features.shape[:-1] == img.shape[:]


@pytest.mark.parametrize('edges', (False, True))
@pytest.mark.parametrize('texture', (False, True))
def test_multiscale_basic_features_rgb(edges, texture):
img = np.zeros((20, 20, 3))
img[:10] = 1
img += 0.05 * np.random.randn(*img.shape)
img = cp.asarray(img)
with expected_warnings(["`multichannel` is a deprecated argument"]):
features = multiscale_basic_features(img, edges=edges, texture=texture,
multichannel=True)

n_sigmas = 6
intensity = True
assert features.shape[-1] == (
3 * n_sigmas * (int(intensity) + int(edges) + 2 * int(texture))
)
assert features.shape[:-1] == img.shape[:-1]


def test_multiscale_basic_features_deprecated_multichannel():
img = np.zeros((10, 10, 5))
img[:10] = 1
img += 0.05 * np.random.randn(*img.shape)
img = cp.asarray(img)
n_sigmas = 2
with expected_warnings(["`multichannel` is a deprecated argument"]):
features = multiscale_basic_features(img, sigma_min=1, sigma_max=2,
multichannel=True)
assert features.shape[-1] == 5 * n_sigmas * 4
assert features.shape[:-1] == img.shape[:-1]

# repeat prior test, but check for positional multichannel warning
with expected_warnings(["Providing the `multichannel` argument"]):
multiscale_basic_features(img, True, sigma_min=1, sigma_max=2)
assert features.shape[-1] == 5 * n_sigmas * 4
assert features.shape[:-1] == img.shape[:-1]

# Consider last axis as spatial dimension
features = multiscale_basic_features(img, sigma_min=1, sigma_max=2)
assert features.shape[-1] == n_sigmas * 5
assert features.shape[:-1] == img.shape


@pytest.mark.parametrize('channel_axis', [0, 1, 2, -1, -2])
def test_multiscale_basic_features_channel_axis(channel_axis):
num_channels = 5
Expand Down
13 changes: 3 additions & 10 deletions python/cucim/src/cucim/skimage/filters/_gaussian.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import numpy as np

from .._shared import utils
from .._shared.filters import gaussian
from ..util import img_as_float

__all__ = ['gaussian', 'difference_of_gaussians']


@utils.deprecate_multichannel_kwarg()
def difference_of_gaussians(image, low_sigma, high_sigma=None, *,
mode='nearest', cval=0, channel_axis=None,
truncate=4.0, multichannel=False):
truncate=4.0):
"""Find features between ``low_sigma`` and ``high_sigma`` in size.
This function uses the Difference of Gaussians method for applying
Expand Down Expand Up @@ -50,11 +48,6 @@ def difference_of_gaussians(image, low_sigma, high_sigma=None, *,
to channels.
truncate : float, optional (default is 4.0)
Truncate the filter at this many standard deviations.
multichannel : bool, optional (default: False)
Whether the last axis of the image is to be interpreted as multiple
channels. If True, each channel is filtered separately (channels are
not mixed together). This argument is deprecated: specify
`channel_axis` instead.
Returns
-------
Expand Down Expand Up @@ -91,13 +84,13 @@ def difference_of_gaussians(image, low_sigma, high_sigma=None, *,
>>> from cucim.skimage.filters import difference_of_gaussians
>>> astro = cp.asarray(astronaut())
>>> filtered_image = difference_of_gaussians(astro, 2, 10,
... multichannel=True)
... channel_axis=-1)
Apply a Laplacian of Gaussian filter as approximated by the Difference
of Gaussians filter:
>>> filtered_image = difference_of_gaussians(astro, 2,
... multichannel=True)
... channel_axis=-1)
Apply a Difference of Gaussians filter to a grayscale image using different
sigma values for each axis:
Expand Down
9 changes: 2 additions & 7 deletions python/cucim/src/cucim/skimage/filters/_unsharp_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ def _unsharp_mask_single_channel(image, radius, amount, vrange):
return result


@utils.deprecate_multichannel_kwarg(multichannel_position=3)
def unsharp_mask(image, radius=1.0, amount=1.0, multichannel=False,
preserve_range=False, *, channel_axis=None):
def unsharp_mask(image, radius=1.0, amount=1.0, preserve_range=False, *,
channel_axis=None):
"""Unsharp masking filter.
The sharp details are identified as the difference between the original
Expand All @@ -38,10 +37,6 @@ def unsharp_mask(image, radius=1.0, amount=1.0, multichannel=False,
amount : scalar, optional
The details will be amplified with this factor. The factor could be 0
or negative. Typically, it is a small positive number, e.g. 1.0.
multichannel : bool, optional
If True, the last ``image`` dimension is considered as a color channel,
otherwise as spatial. Color channels are processed individually.
This argument is deprecated: specify `channel_axis` instead.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of ``img_as_float``.
Expand Down
Loading

0 comments on commit a7490ca

Please sign in to comment.