Skip to content

Commit d476db5

Browse files
committed
Merge branch 'docstub-fixes-III' of github.com:mkcor/scikit-image into docstub-fixes-III
2 parents 664dc33 + 858a513 commit d476db5

File tree

5 files changed

+90
-54
lines changed

5 files changed

+90
-54
lines changed

.github/workflows/typing.yml

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,12 @@ jobs:
3737
cache: "pip"
3838
cache-dependency-path: "requirements/*.txt"
3939

40-
- name: Install build dependencies
41-
env:
42-
PIP_FLAGS: ${{ matrix.PIP_FLAGS }}
43-
run: |
44-
source .github/scripts/setup-build-env.sh
45-
46-
- name: Build and install from source
47-
run: |
48-
pip install -v --no-build-isolation .
49-
5040
- name: Install docstub
5141
run: |
5242
pip install docstub~=0.6.0
5343
docstub --version
5444
55-
- name: Create stubs with docstub
45+
- name: Create skimage-stubs with docstub
5646
shell: bash
5747
run: |
5848
echo -e "## docstub output\n\`\`\`" >> $GITHUB_STEP_SUMMARY
@@ -62,6 +52,29 @@ jobs:
6252
2>&1 | tee -a $GITHUB_STEP_SUMMARY)
6353
echo -e "\`\`\`" >> $GITHUB_STEP_SUMMARY
6454
55+
- name: Create skimage2-stubs with docstub
56+
shell: bash
57+
run: |
58+
echo -e "## docstub output\n\`\`\`" >> $GITHUB_STEP_SUMMARY
59+
(set -o pipefail && \
60+
docstub run --verbose --group-errors \
61+
--out-dir ${MYPYPATH}/skimage2 src/skimage2/ \
62+
2>&1 | tee -a $GITHUB_STEP_SUMMARY)
63+
echo -e "\`\`\`" >> $GITHUB_STEP_SUMMARY
64+
65+
# Not needed yet, since we don't run mypy.stubtest or similar
66+
# which would require importing scikit-image.
67+
#
68+
# - name: Install build dependencies
69+
# env:
70+
# PIP_FLAGS: ${{ matrix.PIP_FLAGS }}
71+
# run: |
72+
# source .github/scripts/setup-build-env.sh
73+
#
74+
# - name: Build and install from source
75+
# run: |
76+
# pip install -v --no-build-isolation .
77+
6578
- uses: actions/upload-artifact@v4
6679
with:
6780
name: skimage-stubs

src/skimage/_shared/utils.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,23 @@ def __repr__(cls):
205205

206206

207207
class DEPRECATED(metaclass=PatchClassRepr):
208-
"""Signal value to help with deprecating parameters that use None.
208+
"""Signal that a deprecated parameter has not been set.
209209
210-
This is a proxy object, used to signal that a parameter has not been set.
211210
This is useful if ``None`` is already used for a different purpose or just
212211
to highlight a deprecated parameter in the signature.
212+
213+
This is a sentinel and not meant to be initialized.
214+
"""
215+
216+
217+
class DEPRECATED_GOT_VALUE(metaclass=PatchClassRepr):
218+
"""Signal that a value was passed to a deprecated parameter.
219+
220+
Used by :class:`deprecate_parameter` to replace values that were passed to
221+
deprecated parameters. This allows further handling of the deprecated case
222+
inside the decorated function.
223+
224+
This is a sentinel and not meant to be initialized.
213225
"""
214226

215227

@@ -228,9 +240,13 @@ class deprecate_parameter:
228240
template : str, optional
229241
If given, this message template is used instead of the default one.
230242
new_name : str, optional
231-
If given, the default message will recommend the new parameter name and an
232-
error will be raised if the user uses both old and new names for the
233-
same parameter.
243+
The name of a new parameter replacing the deprecated one. If given:
244+
- The default message will recommend the new parameter name.
245+
- Values passed to `deprecated_name` are replaced with
246+
:class:`DEPRECATED_GOT_VALUE`, and the replaced value is passed to
247+
`new_name` instead.
248+
- An error will be raised if the user uses both `deprecated_name` and
249+
`new_name`.
234250
modify_docstring : bool, optional
235251
If the wrapped function has a docstring, add the deprecated parameters
236252
to the "Other Parameters" section.
@@ -267,6 +283,7 @@ class deprecate_parameter:
267283
"""
268284

269285
DEPRECATED = DEPRECATED # Make signal value accessible for convenience
286+
DEPRECATED_GOT_VALUE = DEPRECATED_GOT_VALUE
270287

271288
remove_parameter_template = (
272289
"Parameter `{deprecated_name}` is deprecated since version "
@@ -341,21 +358,20 @@ def fixed_func(*args, **kwargs):
341358
deprecated_value = DEPRECATED
342359
new_value = DEPRECATED
343360

344-
# Extract value of deprecated parameter
361+
# Extract value of deprecated parameter and overwrite with
362+
# DEPRECATED_GOT_VALUE if replacement exists
345363
if len(args) > deprecated_idx:
346364
deprecated_value = args[deprecated_idx]
347-
# Overwrite old with DEPRECATED if replacement exists
348365
if self.new_name is not None:
349366
args = (
350367
args[:deprecated_idx]
351-
+ (DEPRECATED,)
368+
+ (DEPRECATED_GOT_VALUE,)
352369
+ args[deprecated_idx + 1 :]
353370
)
354371
if self.deprecated_name in kwargs.keys():
355372
deprecated_value = kwargs[self.deprecated_name]
356-
# Overwrite old with DEPRECATED if replacement exists
357373
if self.new_name is not None:
358-
kwargs[self.deprecated_name] = DEPRECATED
374+
kwargs[self.deprecated_name] = DEPRECATED_GOT_VALUE
359375

360376
# Extract value of new parameter (if present)
361377
if new_idx is not False and len(args) > new_idx:

src/skimage/morphology/misc.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import numpy as np
44
import functools
5-
import warnings
65
from scipy import ndimage as ndi
76
from scipy.spatial import cKDTree
87

@@ -67,7 +66,7 @@ def _check_dtype_supported(ar):
6766
"its value, while the previous parameter only removed smaller ones.",
6867
)
6968
def remove_small_objects(
70-
ar, min_size=DEPRECATED, connectivity=1, *, max_size=64, out=None
69+
ar, min_size=DEPRECATED, connectivity=1, *, max_size=63, out=None
7170
):
7271
"""Remove objects smaller than the specified size.
7372
@@ -81,7 +80,7 @@ def remove_small_objects(
8180
ar : ndarray (arbitrary shape, int or bool type)
8281
The array containing the objects of interest. If the array type is
8382
int, the ints must be non-negative.
84-
max_size : int, optional (default: 64)
83+
max_size : int, optional
8584
Remove objects whose contiguous area (or volume, in N-D) contains this
8685
number of pixels or fewer.
8786
@@ -167,12 +166,12 @@ def remove_small_objects(
167166
"Did you mean to use a boolean array?"
168167
)
169168

170-
if min_size is not DEPRECATED:
171-
# Exclusive threshold is deprecated behavior
172-
too_small = component_sizes < min_size
173-
else:
174-
# New behavior uses inclusive threshold
175-
too_small = component_sizes <= max_size
169+
if min_size is deprecate_parameter.DEPRECATED_GOT_VALUE:
170+
# Old parameter with exclusive threshold (< instead of <=) was used and
171+
# forwarded to `max_size`, correct for this
172+
max_size -= 1
173+
174+
too_small = component_sizes <= max_size
176175
too_small_mask = too_small[ccs]
177176
out[too_small_mask] = 0
178177

@@ -189,15 +188,15 @@ def remove_small_objects(
189188
"its value, while the previous parameter only removed smaller ones.",
190189
)
191190
def remove_small_holes(
192-
ar, area_threshold=DEPRECATED, connectivity=1, *, max_size=64, out=None
191+
ar, area_threshold=DEPRECATED, connectivity=1, *, max_size=63, out=None
193192
):
194193
"""Remove contiguous holes smaller than the specified size.
195194
196195
Parameters
197196
----------
198197
ar : ndarray (arbitrary shape, int or bool type)
199198
The array containing the connected components of interest.
200-
max_size : int, optional (default: 64)
199+
max_size : int, optional
201200
Remove holes whose contiguous area (or volume, in N-D) contains this
202201
number of pixels or fewer.
203202
@@ -278,20 +277,18 @@ def remove_small_holes(
278277
# Creating the inverse of ar
279278
np.logical_not(ar, out=out)
280279

280+
if area_threshold is deprecate_parameter.DEPRECATED_GOT_VALUE:
281+
# Old parameter with exclusive threshold (< instead of <=) was used and
282+
# forwarded to `max_size`, correct for this
283+
max_size -= 1
284+
281285
# removing small objects from the inverse of ar
282-
with warnings.catch_warnings():
283-
warnings.filterwarnings(
284-
"ignore",
285-
message="Parameter `min_size` is deprecated",
286-
category=FutureWarning,
287-
)
288-
out = remove_small_objects(
289-
out,
290-
min_size=area_threshold,
291-
max_size=max_size,
292-
connectivity=connectivity,
293-
out=out,
294-
)
286+
out = remove_small_objects(
287+
out,
288+
max_size=max_size,
289+
connectivity=connectivity,
290+
out=out,
291+
)
295292

296293
np.logical_not(out, out=out)
297294

tests/skimage/_shared/test_utils.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
deprecate_func,
1616
deprecate_parameter,
1717
DEPRECATED,
18+
DEPRECATED_GOT_VALUE,
1819
FailedEstimationAccessError,
1920
FailedEstimation,
2021
)
@@ -356,7 +357,7 @@ def test_warning_replaced_param(self):
356357
with pytest.warns(FutureWarning, match=match):
357358
assert _func_replace_params(1, 2) == (
358359
1,
359-
DEPRECATED,
360+
DEPRECATED_GOT_VALUE,
360361
DEPRECATED,
361362
None,
362363
2,
@@ -366,8 +367,8 @@ def test_warning_replaced_param(self):
366367
with pytest.warns(FutureWarning, match=match) as records:
367368
assert _func_replace_params(1, 2, 3) == (
368369
1,
369-
DEPRECATED,
370-
DEPRECATED,
370+
DEPRECATED_GOT_VALUE,
371+
DEPRECATED_GOT_VALUE,
371372
3,
372373
2,
373374
None,
@@ -379,7 +380,7 @@ def test_warning_replaced_param(self):
379380
with pytest.warns(FutureWarning, match=match):
380381
assert _func_replace_params(1, old0=2) == (
381382
1,
382-
DEPRECATED,
383+
DEPRECATED_GOT_VALUE,
383384
DEPRECATED,
384385
None,
385386
2,
@@ -390,7 +391,7 @@ def test_warning_replaced_param(self):
390391
assert _func_replace_params(1, old1=3) == (
391392
1,
392393
DEPRECATED,
393-
DEPRECATED,
394+
DEPRECATED_GOT_VALUE,
394395
3,
395396
None,
396397
None,

tests/skimage/morphology/test_misc.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,24 @@ def test_negative_input():
122122

123123
def test_remove_small_objects_deprecated_min_size():
124124
expected = np.array([[0, 0, 0, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0]], dtype=bool)
125+
zeros = np.zeros_like(expected)
125126

126-
# This is fine
127-
observed = remove_small_objects(test_object_image, max_size=3)
127+
# New max_size=6 filters all objects without warning
128+
observed = remove_small_objects(test_object_image, max_size=6)
129+
assert_array_equal(observed, zeros)
130+
# New max_size=5 leaves on object of size 6 without warning
131+
observed = remove_small_objects(test_object_image, max_size=5)
128132
assert_array_equal(observed, expected)
129133

130-
# Using area_threshold= warns
134+
# Deprecated min_size=7 warns and filters all objects
131135
regex = "Parameter `min_size` is deprecated"
132136
with pytest.warns(FutureWarning, match=regex) as record:
133-
observed = remove_small_objects(test_object_image, min_size=5)
137+
observed = remove_small_objects(test_object_image, min_size=7)
138+
assert_stacklevel(record)
139+
assert_array_equal(observed, zeros)
140+
# Deprecated min_size=6 warns leaves on object of size 6
141+
with pytest.warns(FutureWarning, match=regex) as record:
142+
observed = remove_small_objects(test_object_image, min_size=6)
134143
assert_stacklevel(record)
135144
assert_array_equal(observed, expected)
136145

0 commit comments

Comments
 (0)