Repository: scikit-image/scikit-image

Issue:
Code audit: What are setup_test and teardown_test
They are a little everywhere in the __init__.py files in the test directories. It seems like some artifact from nose. 

Are they still needed now that the migration to pytest is done?


Discussion:
These are probably still executed? https://docs.pytest.org/en/latest/xunit_setup.html
Interesting.

Resolving patch:
diff --git a/skimage/conftest.py b/skimage/conftest.py
--- a/skimage/conftest.py
+++ b/skimage/conftest.py
@@ -1,6 +1,16 @@
+from skimage._shared.testing import setup_test, teardown_test
+
 # List of files that pytest should ignore
 collect_ignore = ["io/_plugins",]
 try:
     import visvis
 except ImportError:
     collect_ignore.append("measure/mc_meta/visual_test.py")
+
+
+def pytest_runtest_setup(item):
+    setup_test()
+
+
+def pytest_runtest_teardown(item):
+    teardown_test()
diff --git a/skimage/filters/rank/generic.py b/skimage/filters/rank/generic.py
--- a/skimage/filters/rank/generic.py
+++ b/skimage/filters/rank/generic.py
@@ -8,9 +8,9 @@
 window in order to limit redundant computation. The moving window follows a
 snake-like path:
 
-...------------------------\
-/--------------------------/
-\--------------------------...
+...------------------------↘
+↙--------------------------↙
+↘--------------------------...
 
 The local histogram is updated at each pixel as the footprint window
 moves by, i.e. only those pixels entering and leaving the footprint
@@ -48,8 +48,6 @@
 
 """
 
-import warnings
-
 import numpy as np
 from scipy import ndimage as ndi
 
diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py
--- a/skimage/morphology/misc.py
+++ b/skimage/morphology/misc.py
@@ -118,8 +118,11 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False,
 
     if in_place:
         out = ar
-    elif out is None:
-        out = ar.copy()
+    else:
+        if out is None:
+            out = ar.copy()
+        else:
+            out[:] = ar
 
     if min_size == 0:  # shortcut for efficiency
         return out


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -11,6 +11,7 @@ jobs:
     env:
         BUILD_DOCS: 1
         TEST_EXAMPLES: 0
+        SKIMAGE_TEST_STRICT_WARNINGS_GLOBAL: 0
 
     strategy:
       # Ensure that a wheel builder finishes even if another fails
@@ -45,6 +46,7 @@ jobs:
           - platform_id: manylinux_x86_64
             python-version: 3.8
             PIP_FLAGS: "--pre"
+            SKIMAGE_TEST_STRICT_WARNINGS_GLOBAL: 1
             # test pre-releases
             OPTIONS_NAME: "pre"
           - platform_id: manylinux_x86_64
diff --git a/skimage/_shared/testing.py b/skimage/_shared/testing.py
--- a/skimage/_shared/testing.py
+++ b/skimage/_shared/testing.py
@@ -39,6 +39,15 @@
 arch32 = struct.calcsize("P") * 8 == 32
 
 
+_error_on_warnings = os.environ.get('SKIMAGE_TEST_STRICT_WARNINGS_GLOBAL', '0')
+if _error_on_warnings.lower() == 'true':
+    _error_on_warnings = True
+elif _error_on_warnings.lower() == 'false':
+    _error_on_warnings = False
+else:
+    _error_on_warnings = bool(int(_error_on_warnings))
+
+
 def assert_less(a, b, msg=None):
     message = "%r is not lower than %r" % (a, b)
     if msg is not None:
@@ -193,13 +202,60 @@ def setup_test():
     """
     warnings.simplefilter('default')
 
-    from scipy import signal, ndimage, special, optimize, linalg
-    from scipy.io import loadmat
-    from skimage import viewer
-
-    np.random.seed(0)
-
-    warnings.simplefilter('error')
+    if _error_on_warnings:
+        from scipy import signal, ndimage, special, optimize, linalg
+        from scipy.io import loadmat
+        from skimage import viewer
+
+        np.random.seed(0)
+
+        warnings.simplefilter('error')
+
+        # do not error on specific warnings from the skimage.io module
+        # https://github.com/scikit-image/scikit-image/issues/5337
+        warnings.filterwarnings(
+            'default', message='TiffFile:', category=DeprecationWarning
+        )
+
+        warnings.filterwarnings(
+            'default', message='unclosed file', category=ResourceWarning
+        )
+
+        # ignore known FutureWarnings from viewer module
+        warnings.filterwarnings(
+            'ignore', category=FutureWarning, module='skimage.viewer'
+        )
+
+        # Ignore other warnings only seen when using older versions of
+        # dependencies.
+        warnings.filterwarnings(
+            'default',
+            message='Conversion of the second argument of issubdtype',
+            category=FutureWarning
+        )
+
+        warnings.filterwarnings(
+            'default',
+            message='the matrix subclass is not the recommended way',
+            category=PendingDeprecationWarning, module='numpy'
+        )
+
+        warnings.filterwarnings(
+            'default',
+            message='Your installed pillow version',
+            category=UserWarning,
+            module='skimage.io'
+        )
+
+        warnings.filterwarnings(
+            'default', message='Viewer requires Qt', category=UserWarning
+        )
+
+        warnings.filterwarnings(
+            'default',
+            message='numpy.ufunc size changed',
+            category=RuntimeWarning
+        )
 
 
 def teardown_test():
@@ -207,7 +263,10 @@ def teardown_test():
 
     Restore warnings to default behavior
     """
-    warnings.simplefilter('default')
+    if _error_on_warnings:
+        warnings.resetwarnings()
+        warnings.simplefilter('default')
+
 
 
 def fetch(data_filename):
diff --git a/skimage/_shared/tests/__init__.py b/skimage/_shared/tests/__init__.py
--- a/skimage/_shared/tests/__init__.py
+++ b/skimage/_shared/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/color/tests/__init__.py b/skimage/color/tests/__init__.py
--- a/skimage/color/tests/__init__.py
+++ b/skimage/color/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/color/tests/test_colorlabel.py b/skimage/color/tests/test_colorlabel.py
--- a/skimage/color/tests/test_colorlabel.py
+++ b/skimage/color/tests/test_colorlabel.py
@@ -6,6 +6,7 @@
                            assert_array_equal, assert_no_warnings,
                            assert_warns)
 
+from skimage._shared.testing import expected_warnings
 from skimage.color.colorconv import hsv2rgb, rgb2hsv
 from skimage.color.colorlabel import label2rgb
 
@@ -249,12 +250,12 @@ def test_overlay_custom_saturation():
     assert_array_almost_equal(saturaded_img[:3, :3] * (1 - alpha), rgb[:3, :3])
 
 
-@pytest.mark.filterwarnings("error")
 def test_saturation_warning():
     rgb_img = np.random.uniform(size=(10, 10, 3))
     labels = np.ones((10, 10), dtype=np.int64)
-    with pytest.raises(UserWarning):
+    with expected_warnings(["saturation must be in range"]):
         label2rgb(labels, image=rgb_img,
                   bg_label=0, saturation=2)
+    with expected_warnings(["saturation must be in range"]):
         label2rgb(labels, image=rgb_img,
                   bg_label=0, saturation=-1)
diff --git a/skimage/data/tests/__init__.py b/skimage/data/tests/__init__.py
--- a/skimage/data/tests/__init__.py
+++ b/skimage/data/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/draw/tests/__init__.py b/skimage/draw/tests/__init__.py
--- a/skimage/draw/tests/__init__.py
+++ b/skimage/draw/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/exposure/tests/__init__.py b/skimage/exposure/tests/__init__.py
--- a/skimage/exposure/tests/__init__.py
+++ b/skimage/exposure/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/feature/tests/__init__.py b/skimage/feature/tests/__init__.py
--- a/skimage/feature/tests/__init__.py
+++ b/skimage/feature/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py
--- a/skimage/feature/tests/test_corner.py
+++ b/skimage/feature/tests/test_corner.py
@@ -101,15 +101,15 @@ def test_structure_tensor_orders():
 def test_structure_tensor_sigma(ndim):
     img = np.zeros((5,) * ndim)
     img[[2] * ndim] = 1
-    A_default = structure_tensor(img, sigma=0.1)
-    A_tuple = structure_tensor(img, sigma=(0.1,) * ndim)
-    A_list = structure_tensor(img, sigma=[0.1] * ndim)
+    A_default = structure_tensor(img, sigma=0.1, order='rc')
+    A_tuple = structure_tensor(img, sigma=(0.1,) * ndim, order='rc')
+    A_list = structure_tensor(img, sigma=[0.1] * ndim, order='rc')
     assert_array_equal(A_tuple, A_default)
     assert_array_equal(A_list, A_default)
     with pytest.raises(ValueError):
-        structure_tensor(img, sigma=(0.1,) * (ndim - 1))
+        structure_tensor(img, sigma=(0.1,) * (ndim - 1), order='rc')
     with pytest.raises(ValueError):
-        structure_tensor(img, sigma=[0.1] * (ndim + 1))
+        structure_tensor(img, sigma=[0.1] * (ndim + 1), order='rc')
 
 
 @pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
diff --git a/skimage/filters/tests/__init__.py b/skimage/filters/tests/__init__.py
--- a/skimage/filters/tests/__init__.py
+++ b/skimage/filters/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/future/tests/test_trainable_segmentation.py b/skimage/future/tests/test_trainable_segmentation.py
--- a/skimage/future/tests/test_trainable_segmentation.py
+++ b/skimage/future/tests/test_trainable_segmentation.py
@@ -1,9 +1,12 @@
-import pytest
 from functools import partial
+
 import numpy as np
+import pytest
+from scipy import spatial
+
+from skimage._shared._warnings import expected_warnings
 from skimage.future import fit_segmenter, predict_segmenter, TrainableSegmenter
 from skimage.feature import multiscale_basic_features
-from scipy import spatial
 
 
 class DummyNNClassifier(object):
@@ -47,15 +50,14 @@ def test_trainable_segmentation_multichannel():
     labels[:2] = 1
     labels[-2:] = 2
     clf = DummyNNClassifier()
-    features_func = partial(
-        multiscale_basic_features,
+    features = multiscale_basic_features(
+        img,
         edges=False,
         texture=False,
         sigma_min=0.5,
         sigma_max=2,
         channel_axis=-1,
     )
-    features = features_func(img)
     clf = fit_segmenter(labels, features, clf)
     out = predict_segmenter(features, clf)
     assert np.all(out[:10] == 1)
diff --git a/skimage/graph/tests/__init__.py b/skimage/graph/tests/__init__.py
--- a/skimage/graph/tests/__init__.py
+++ b/skimage/graph/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/io/tests/__init__.py b/skimage/io/tests/__init__.py
--- a/skimage/io/tests/__init__.py
+++ b/skimage/io/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/measure/tests/__init__.py b/skimage/measure/tests/__init__.py
--- a/skimage/measure/tests/__init__.py
+++ b/skimage/measure/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py
--- a/skimage/measure/tests/test_fit.py
+++ b/skimage/measure/tests/test_fit.py
@@ -294,8 +294,9 @@ def test_ransac_geometric():
 def test_ransac_is_data_valid():
     def is_data_valid(data):
         return data.shape[0] > 2
-    model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
-                            is_data_valid=is_data_valid, random_state=1)
+    with expected_warnings(["No inliers found"]):
+        model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
+                                is_data_valid=is_data_valid, random_state=1)
     assert_equal(model, None)
     assert_equal(inliers, None)
 
@@ -303,8 +304,9 @@ def is_data_valid(data):
 def test_ransac_is_model_valid():
     def is_model_valid(model, data):
         return False
-    model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
-                            is_model_valid=is_model_valid, random_state=1)
+    with expected_warnings(["No inliers found"]):
+        model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
+                                is_model_valid=is_model_valid, random_state=1)
     assert_equal(model, None)
     assert_equal(inliers, None)
 
@@ -388,8 +390,9 @@ def residuals(self, data):
     # Create dataset with four unique points. Force 10 iterations
     # and check that there are no duplicated data points.
     data = np.arange(4)
-    ransac(data, DummyModel, min_samples=3, residual_threshold=0.0,
-           max_trials=10)
+    with expected_warnings(["No inliers found"]):
+        ransac(data, DummyModel, min_samples=3, residual_threshold=0.0,
+               max_trials=10)
 
 
 def test_ransac_with_no_final_inliers():
diff --git a/skimage/metrics/tests/__init__.py b/skimage/metrics/tests/__init__.py
--- a/skimage/metrics/tests/__init__.py
+++ b/skimage/metrics/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/metrics/tests/test_set_metrics.py b/skimage/metrics/tests/test_set_metrics.py
--- a/skimage/metrics/tests/test_set_metrics.py
+++ b/skimage/metrics/tests/test_set_metrics.py
@@ -5,19 +5,22 @@
 from numpy.testing import assert_almost_equal, assert_array_equal
 from scipy.spatial import distance
 
+from skimage._shared._warnings import expected_warnings
 from skimage.metrics import hausdorff_distance, hausdorff_pair
 
 
-@pytest.mark.filterwarnings("ignore:One or both")
 def test_hausdorff_empty():
     empty = np.zeros((0, 2), dtype=bool)
     non_empty = np.zeros((3, 2), dtype=bool)
     assert hausdorff_distance(empty, non_empty) == 0.
-    assert_array_equal(hausdorff_pair(empty, non_empty), [(), ()])
+    with expected_warnings(["One or both of the images is empty"]):
+        assert_array_equal(hausdorff_pair(empty, non_empty), [(), ()])
     assert hausdorff_distance(non_empty, empty) == 0.
-    assert_array_equal(hausdorff_pair(non_empty, empty), [(), ()])
+    with expected_warnings(["One or both of the images is empty"]):
+        assert_array_equal(hausdorff_pair(non_empty, empty), [(), ()])
     assert hausdorff_distance(empty, non_empty) == 0.
-    assert_array_equal(hausdorff_pair(empty, non_empty), [(), ()])
+    with expected_warnings(["One or both of the images is empty"]):
+        assert_array_equal(hausdorff_pair(empty, non_empty), [(), ()])
 
 
 def test_hausdorff_simple():
diff --git a/skimage/morphology/tests/__init__.py b/skimage/morphology/tests/__init__.py
--- a/skimage/morphology/tests/__init__.py
+++ b/skimage/morphology/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py
--- a/skimage/morphology/tests/test_misc.py
+++ b/skimage/morphology/tests/test_misc.py
@@ -1,3 +1,5 @@
+import warnings
+
 import numpy as np
 import pytest
 from skimage.morphology import remove_small_objects, remove_small_holes
@@ -41,7 +43,15 @@ def test_in_place():
 def test_out(in_dtype, out_dtype):
     image = test_image.astype(in_dtype, copy=True)
     expected_out = np.empty_like(test_image, dtype=out_dtype)
-    out = remove_small_objects(image, min_size=6, out=expected_out)
+
+    if out_dtype != bool:
+        # object with only 1 label will warn on non-bool output dtype
+        exp_warn = ["Only one label was provided"]
+    else:
+        exp_warn = []
+
+    with expected_warnings(exp_warn):
+        out = remove_small_objects(image, min_size=6, out=expected_out)
 
     assert out is expected_out
 
diff --git a/skimage/restoration/tests/__init__.py b/skimage/restoration/tests/__init__.py
--- a/skimage/restoration/tests/__init__.py
+++ b/skimage/restoration/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/segmentation/tests/__init__.py b/skimage/segmentation/tests/__init__.py
--- a/skimage/segmentation/tests/__init__.py
+++ b/skimage/segmentation/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/transform/tests/__init__.py b/skimage/transform/tests/__init__.py
--- a/skimage/transform/tests/__init__.py
+++ b/skimage/transform/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/util/tests/__init__.py b/skimage/util/tests/__init__.py
--- a/skimage/util/tests/__init__.py
+++ b/skimage/util/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/viewer/tests/__init__.py b/skimage/viewer/tests/__init__.py
--- a/skimage/viewer/tests/__init__.py
+++ b/skimage/viewer/tests/__init__.py
@@ -1,9 +0,0 @@
-from ..._shared.testing import setup_test, teardown_test
-
-
-def setup():
-    setup_test()
-
-
-def teardown():
-    teardown_test()
diff --git a/skimage/viewer/tests/test_widgets.py b/skimage/viewer/tests/test_widgets.py
--- a/skimage/viewer/tests/test_widgets.py
+++ b/skimage/viewer/tests/test_widgets.py
@@ -1,6 +1,6 @@
 import os
 
-from skimage import data, img_as_float, io, img_as_uint
+from skimage import data, img_as_float, io, img_as_ubyte
 
 from skimage.viewer import ImageViewer
 from skimage.viewer.qt import QtWidgets, QtCore, has_qt
@@ -108,9 +108,9 @@ def test_save_buttons():
     sv.save_to_stack()
     sv.save_to_file(filename)
 
-    img = data.imread(filename)
+    img = io.imread(filename)
 
-    assert_almost_equal(img, img_as_uint(viewer.image))
+    assert_almost_equal(img, img_as_ubyte(viewer.image))
 
     img = io.pop()
     assert_almost_equal(img, viewer.image)