Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete deprecations targeting release 0.20 or 1.0 #6583

Merged
merged 29 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7430c41
Complete deprecation of selem parameter
lagru Oct 16, 2022
d951774
Complete deprecation of selem module
lagru Oct 16, 2022
64e0023
Complete deprecation of in_place parameter
lagru Oct 16, 2022
198566f
Complete deprecation of max_iterations parameter
lagru Oct 16, 2022
6db637d
Complete deprecation of max_iter parameter
lagru Oct 16, 2022
50cdfb0
Complete deprecation of iterations parameter
lagru Oct 16, 2022
4cb8866
Complete deprecation of n_iter_max parameter
lagru Oct 16, 2022
56f0431
Complete deprecation of compute_hessian_eigenvalues
lagru Oct 16, 2022
f77d0da
Complete deprecation of input parameter
lagru Oct 16, 2022
171d34a
Complete deprecation of greyco* functions
lagru Oct 16, 2022
9b2c8f0
Complete deprecation of multichannel kwarg
lagru Oct 18, 2022
c7337b6
Remove unused deprecate_multichannel_kwarg
lagru Oct 18, 2022
c25941e
Complete deprecation of height, width in rectangle
lagru Oct 19, 2022
1beab4a
Remove completed items in TODO.txt
lagru Oct 19, 2022
6a24f27
Complete deprecation of neighbourhood parameter
lagru Oct 19, 2022
b53b46a
Remove deprecated grey and greyreconstruct modules
lagru Oct 19, 2022
38c23c4
Remove warning about deprecated *_iter
lagru Oct 19, 2022
998ee00
Revert "Complete deprecation of compute_hessian_eigenvalues"
lagru Oct 25, 2022
a5421d5
Deprecate automatic channel detection in gaussian
lagru Oct 26, 2022
d752adf
Complete deprecation of coordinates parameter
lagru Oct 29, 2022
206e599
Remove outdated TODO for CircleModel.estimate
lagru Oct 29, 2022
bc476b7
Merge branch 'main' into complete-0.20-deprecations
lagru Oct 29, 2022
9b70ed6
Remove removed files from meson.build
lagru Oct 29, 2022
e7d8c75
Remove obsolete tests for coordinates parameter
lagru Oct 29, 2022
15638d4
Catch expected warning for deprecated color channel inference in gaus…
grlee77 Nov 18, 2022
cc32ae2
fix typo in prior commit
grlee77 Nov 18, 2022
dc843fe
Merge remote-tracking branch 'upstream/main' into complete-0.20-depre…
grlee77 Nov 18, 2022
246bc46
Update skimage/_shared/filters.py
lagru Nov 23, 2022
529c74a
Explain proxy value in more clearly
lagru Nov 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 5 additions & 8 deletions TODO.txt
Expand Up @@ -6,18 +6,15 @@ Version 0.17
* Finalize ``skimage.future.graph`` API.
* Finalize ``skimage.future.manual_segmentation`` API.

Version 1.0
-----------

Version 0.20
------------
* consider removing the argument `coordinates` in
lagru marked this conversation as resolved.
Show resolved Hide resolved
`skimage.segmentation.active_contour`, which has not effect.
* In ``skimage/morphology/misc.py`` remove the deprecated parameter
``in_place`` in remove_small_holes(), remove_small_objects() and
clear_border(), and update the tests.
* Remove the deprecation warning for `input` kwarg of the `label`
function in `skimage/measure/_label.py`
* Change the warning about poorly conditioned data to a ValueError within
`skimage.measure.CircleModel.estimate` and update the tests in `test_circle_model_insufficient_data` accordingly.

Version 0.21
------------
* In ``skimage/filters/lpi_filter.py``, remove the deprecated function
inverse().

Expand Down
8 changes: 4 additions & 4 deletions benchmarks/benchmark_rank.py
Expand Up @@ -12,10 +12,10 @@ class RankSuite:

def setup(self, filter_func, shape):
self.image = np.random.randint(0, 255, size=shape, dtype=np.uint8)
self.selem = disk(1)
self.footprint = disk(1)

def time_filter(self, filter_func, shape):
getattr(rank, filter_func)(self.image, self.selem)
getattr(rank, filter_func)(self.image, self.footprint)


class Rank3DSuite:
Expand All @@ -25,7 +25,7 @@ class Rank3DSuite:

def setup(self, filter3d, shape3d):
self.volume = np.random.randint(0, 255, size=shape3d, dtype=np.uint8)
self.selem_3d = ball(1)
self.footprint_3d = ball(1)

def time_3d_filters(self, filter3d, shape3d):
getattr(rank, filter3d)(self.volume, self.selem_3d)
getattr(rank, filter3d)(self.volume, self.footprint_3d)
11 changes: 1 addition & 10 deletions skimage/_shared/filters.py
Expand Up @@ -9,13 +9,11 @@
import numpy as np
from scipy import ndimage as ndi

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


@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, *,
preserve_range=False, truncate=4.0, *,
channel_axis=None):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to point out

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.")
warn(RuntimeWarning(msg))
channel_axis = -1

I am pretty sure that the logic makes it impossible to indicate to the function to treat an image of shape (M, N, 3) as a grayscale image as channel_axis=None is replaced with channel_axis=-1. The warning text implies, that in the absence of an explicit multichannel, the image is treated as RGB in some cases. However, with the switch to channel_axis we can no longer replicate this behavior. channel_axis=None means "no RGB" as opposed to multichannel=None which means "not set".

Pretty sure this was broken since the deprecate_multichannel_kwarg was applied as well. A that one translates multichannel=False into channel_axis=None...

My proposal is to remove this (broken) implicit RGB assumption if (M, N, 3).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! The warning message is problematic too; it should have been updated ever since multichannel became deprecated, so the user isn't invited to set a deprecated kwarg...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need another None-proxy, e.g.:

class NoChannelAxis:
		pass

def gaussian(..., channel_axis=NoChannelAxis):
	if channel_axis is NoChannelAxis:
	    ...
			
# User can do:
gaussian(..., channel_axis=None)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See a5421d5. Patching the __repr__ with a metaclass was actually a lot of fun, although it probably shouldn't be. 😅

I think the patch addresses the following cases:

  1. User neither passed multichannel nor channel_axis and relied on automatic detection of color channel for (M, N, 3). Since v0.19 we silently broke his workflow. This patch recovers the original behavior for one release and notifies the user.
  2. User continued using multichannel=False. Since v0.19 that was replaced with channel_axis=None. In case the shape was (M, N, 3) we broke his code otherwise he was fine. This user is notified as well because multichannel is no longer available.
  3. User read the misleading RuntimeWarning, and nevertheless started using channel_axis=None for a grayscale image. The warning continued. In case the shape was (M, N, 3) we broke his code otherwise he was fine. This patch recovers the original behavior but does not notify him.

All other cases using grayscale images shouldn't have been impacted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stefanv @lagru What is the status of this? It would be good to get this merged soon.

Copy link
Member Author

@lagru lagru Nov 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking about the status of this specific comment thread? It's been addressed in a5421d5.

Concerning the status of this PR, I think I addressed everything I found at the time. The only remaining suggestion was to add .. versionchanged:: directives everywhere. But that should not hold up this PR in my opinion and can be done here or in another PR if someone finds the time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for patching up this behavior @lagru, it looks great.

"""Multi-dimensional Gaussian filter.

Expand All @@ -38,13 +36,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
If True, keep the original range of values. Otherwise, the input
``image`` is converted according to the conventions of ``img_as_float``
Expand Down
24 changes: 8 additions & 16 deletions skimage/_shared/tests/test_utils.py
Expand Up @@ -11,8 +11,6 @@
change_default_value, remove_arg,
_supported_float_type,
channel_as_last_axis)
from skimage.feature import hog
from skimage.transform import pyramid_gaussian

complex_dtypes = [np.complex64, np.complex128]
if hasattr(np, 'complex256'):
Expand Down Expand Up @@ -280,23 +278,17 @@ def test_decorated_channel_axis_shape(channel_axis):
assert size == x.shape[channel_axis]


def test_decorator_warnings():
"""Assert that warning message issued by decorator points to
expected file and line number.
"""

with pytest.warns(FutureWarning) as record:
pyramid_gaussian(None, multichannel=True)
expected_lineno = inspect.currentframe().f_lineno - 1

assert record[0].lineno == expected_lineno
assert record[0].filename == __file__
@deprecate_kwarg({"old_kwarg": "new_kwarg"}, deprecated_version="x.y.z")
def _function_with_deprecated_kwarg(*, new_kwarg):
pass

img = np.random.rand(100, 100, 3)

def test_deprecate_kwarg_location():
"""Assert that warning message issued by deprecate_kwarg points to
file and line number where decorated function is called.
"""
with pytest.warns(FutureWarning) as record:
hog(img, multichannel=True)
_function_with_deprecated_kwarg(old_kwarg=True)
expected_lineno = inspect.currentframe().f_lineno - 1

assert record[0].lineno == expected_lineno
assert record[0].filename == __file__
66 changes: 0 additions & 66 deletions skimage/_shared/utils.py
Expand Up @@ -288,72 +288,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='1.0', multichannel_position=None):
super().__init__(
kwarg_mapping={'multichannel': 'channel_axis'},
deprecated_version='0.19',
warning_msg=None,
removed_version=removed_version)
self.position = multichannel_position

def __call__(self, func):

stack_rank = _get_stack_rank(func)

@functools.wraps(func)
def fixed_func(*args, **kwargs):
stacklevel = 1 + self.get_stack_length(func) - stack_rank

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=stacklevel)
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=stacklevel)

# 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'}, '0.19')
fixed_func.__doc__ = newdoc
return fixed_func


class channel_as_last_axis:
"""Decorator for automatically making channels axis last for all arrays.

Expand Down
8 changes: 1 addition & 7 deletions skimage/draw/_random_shapes.py
Expand Up @@ -3,7 +3,7 @@

from . import (polygon as draw_polygon, disk as draw_disk,
ellipse as draw_ellipse)
from .._shared.utils import deprecate_multichannel_kwarg, warn
from .._shared.utils import warn


def _generate_rectangle_mask(point, image, shape, random):
Expand Down Expand Up @@ -291,13 +291,11 @@ def _generate_random_colors(num_colors, num_channels, intensity_range, random):
return np.transpose(colors)


@deprecate_multichannel_kwarg(multichannel_position=5)
def random_shapes(image_shape,
max_shapes,
min_shapes=1,
min_size=2,
max_size=None,
multichannel=True,
num_channels=3,
shape=None,
intensity_range=None,
Expand Down Expand Up @@ -330,10 +328,6 @@ def random_shapes(image_shape,
The minimum dimension of each shape to fit into the image.
max_size : int, optional
The maximum dimension of each shape to fit into the image.
multichannel : bool, optional
If True, the generated image has ``num_channels`` color channels,
otherwise generates grayscale image. This argument is deprecated:
specify `channel_axis` instead.
num_channels : int, optional
Number of channels in the generated image. If 1, generate monochrome
images, else color images with multiple channels. Ignored if
Expand Down
15 changes: 4 additions & 11 deletions skimage/draw/tests/test_random_shapes.py
Expand Up @@ -19,14 +19,8 @@ def test_generates_gray_images_with_correct_shape():


def test_generates_gray_images_with_correct_shape_deprecated_multichannel():
with expected_warnings(["`multichannel` is a deprecated argument"]):
image, _ = random_shapes(
(4567, 123), min_shapes=3, max_shapes=20, multichannel=False)
assert image.shape == (4567, 123)

# repeat prior test, but check for positional multichannel warning
with expected_warnings(["Providing the `multichannel` argument"]):
image, _ = random_shapes((4567, 123), 20, 3, 2, None, False)
image, _ = random_shapes(
(4567, 123), min_shapes=3, max_shapes=20, channel_axis=None)
assert image.shape == (4567, 123)


Expand Down Expand Up @@ -167,9 +161,8 @@ def test_can_generate_one_by_one_rectangle():

def test_throws_when_intensity_range_out_of_range():
with testing.raises(ValueError):
with expected_warnings(["`multichannel` is a deprecated argument"]):
random_shapes((1000, 1234), max_shapes=1, multichannel=False,
intensity_range=(0, 256))
random_shapes((1000, 1234), max_shapes=1, channel_axis=None,
intensity_range=(0, 256))
with testing.raises(ValueError):
random_shapes((2, 2), max_shapes=1,
intensity_range=((-1, 255),))
Expand Down
7 changes: 1 addition & 6 deletions skimage/exposure/histogram_matching.py
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
14 changes: 6 additions & 8 deletions skimage/exposure/tests/test_histogram_matching.py
Expand Up @@ -4,7 +4,6 @@

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

Expand All @@ -26,17 +25,16 @@ class TestMatchHistogram:
image_rgb = data.chelsea()
template_rgb = data.astronaut()

@pytest.mark.parametrize('image, reference, multichannel', [
(image_rgb, template_rgb, True),
(image_rgb[:, :, 0], template_rgb[:, :, 0], False)
@pytest.mark.parametrize('image, reference, channel_axis', [
(image_rgb, template_rgb, -1),
(image_rgb[:, :, 0], template_rgb[:, :, 0], None)
])
def test_match_histograms(self, image, reference, multichannel):
def test_match_histograms(self, image, reference, channel_axis):
"""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 = exposure.match_histograms(image, reference,
channel_axis=channel_axis)

matched_pdf = self._calculate_image_empirical_pdf(matched)
reference_pdf = self._calculate_image_empirical_pdf(reference)
Expand Down
17 changes: 0 additions & 17 deletions skimage/feature/__init__.py
@@ -1,5 +1,3 @@
from .._shared.utils import deprecated

from ._canny import canny
from ._cascade import Cascade
from ._daisy import daisy
Expand Down Expand Up @@ -30,27 +28,12 @@
from ._basic_features import multiscale_basic_features


@deprecated(alt_func='skimage.feature.graycomatrix',
removed_version='1.0')
def greycomatrix(image, distances, angles, levels=None, symmetric=False,
normed=False):
return graycomatrix(image, distances, angles, levels, symmetric, normed)


@deprecated(alt_func='skimage.feature.graycoprops',
removed_version='1.0')
def greycoprops(P, prop='contrast'):
return graycoprops(P, prop)


__all__ = ['canny',
'Cascade',
'daisy',
'hog',
'graycomatrix',
'graycoprops',
'greycomatrix',
'greycoprops',
'local_binary_pattern',
'multiblock_lbp',
'draw_multiblock_lbp',
Expand Down
6 changes: 0 additions & 6 deletions skimage/feature/_basic_features.py
Expand Up @@ -3,7 +3,6 @@
import numpy as np
from skimage import filters, feature
from skimage.util.dtype import img_as_float32
from skimage._shared import utils
from concurrent.futures import ThreadPoolExecutor


Expand Down Expand Up @@ -97,10 +96,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 @@ -120,9 +117,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
7 changes: 1 addition & 6 deletions skimage/feature/_hog.py
Expand Up @@ -46,10 +46,9 @@ def _hog_channel_gradient(channel):


@utils.channel_as_last_axis(multichannel_output=False)
@utils.deprecate_multichannel_kwarg(multichannel_position=8)
def hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(3, 3),
block_norm='L2-Hys', visualize=False, transform_sqrt=False,
feature_vector=True, multichannel=None, *, channel_axis=None):
feature_vector=True, *, channel_axis=None):
"""Extract Histogram of Oriented Gradients (HOG) for a given image.

Compute a Histogram of Oriented Gradients (HOG) by
Expand Down Expand Up @@ -98,10 +97,6 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(3, 3),
feature_vector : bool, optional
Return the data as a feature vector by calling .ravel() on the result
just before returning.
multichannel : boolean, optional
If True, the last `image` dimension is considered as a color channel,
otherwise as spatial. This argument is deprecated: specify
`channel_axis` instead.
channel_axis : int or None, optional
If None, the image is assumed to be a grayscale (single channel) image.
Otherwise, this parameter indicates which axis of the array corresponds
Expand Down