"Fossies" - the Fresh Open Source Software Archive

Member "scikit-image-0.19.3/skimage/_shared/testing.py" (12 Jun 2022, 10526 Bytes) of package /linux/misc/scikit-image-0.19.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "testing.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.19.2_vs_0.19.3.

    1 """
    2 Testing utilities.
    3 """
    4 
    5 import os
    6 import re
    7 import struct
    8 import threading
    9 import functools
   10 from tempfile import NamedTemporaryFile
   11 
   12 import numpy as np
   13 from numpy import testing
   14 from numpy.testing import (assert_array_equal, assert_array_almost_equal,
   15                            assert_array_less, assert_array_almost_equal_nulp,
   16                            assert_equal, TestCase, assert_allclose,
   17                            assert_almost_equal, assert_, assert_warns,
   18                            assert_no_warnings)
   19 
   20 import warnings
   21 
   22 from .. import data, io
   23 from ..data._fetchers import _fetch
   24 from ..util import img_as_uint, img_as_float, img_as_int, img_as_ubyte
   25 from ._warnings import expected_warnings
   26 
   27 
   28 SKIP_RE = re.compile(r"(\s*>>>.*?)(\s*)#\s*skip\s+if\s+(.*)$")
   29 
   30 import pytest
   31 skipif = pytest.mark.skipif
   32 xfail = pytest.mark.xfail
   33 parametrize = pytest.mark.parametrize
   34 raises = pytest.raises
   35 fixture = pytest.fixture
   36 
   37 # true if python is running in 32bit mode
   38 # Calculate the size of a void * pointer in bits
   39 # https://docs.python.org/3/library/struct.html
   40 arch32 = struct.calcsize("P") * 8 == 32
   41 
   42 
   43 _error_on_warnings = os.environ.get('SKIMAGE_TEST_STRICT_WARNINGS_GLOBAL', '0')
   44 if _error_on_warnings.lower() == 'true':
   45     _error_on_warnings = True
   46 elif _error_on_warnings.lower() == 'false':
   47     _error_on_warnings = False
   48 else:
   49     try:
   50         _error_on_warnings = bool(int(_error_on_warnings))
   51     except ValueError:
   52         _error_on_warnings = False
   53 
   54 def assert_less(a, b, msg=None):
   55     message = "%r is not lower than %r" % (a, b)
   56     if msg is not None:
   57         message += ": " + msg
   58     assert a < b, message
   59 
   60 
   61 def assert_greater(a, b, msg=None):
   62     message = "%r is not greater than %r" % (a, b)
   63     if msg is not None:
   64         message += ": " + msg
   65     assert a > b, message
   66 
   67 
   68 def doctest_skip_parser(func):
   69     """ Decorator replaces custom skip test markup in doctests
   70 
   71     Say a function has a docstring::
   72 
   73         >>> something, HAVE_AMODULE, HAVE_BMODULE = 0, False, False
   74         >>> something # skip if not HAVE_AMODULE
   75         0
   76         >>> something # skip if HAVE_BMODULE
   77         0
   78 
   79     This decorator will evaluate the expression after ``skip if``.  If this
   80     evaluates to True, then the comment is replaced by ``# doctest: +SKIP``. If
   81     False, then the comment is just removed. The expression is evaluated in the
   82     ``globals`` scope of `func`.
   83 
   84     For example, if the module global ``HAVE_AMODULE`` is False, and module
   85     global ``HAVE_BMODULE`` is False, the returned function will have docstring::
   86 
   87         >>> something # doctest: +SKIP
   88         >>> something + else # doctest: +SKIP
   89         >>> something # doctest: +SKIP
   90 
   91     """
   92     lines = func.__doc__.split('\n')
   93     new_lines = []
   94     for line in lines:
   95         match = SKIP_RE.match(line)
   96         if match is None:
   97             new_lines.append(line)
   98             continue
   99         code, space, expr = match.groups()
  100 
  101         try:
  102             # Works as a function decorator
  103             if eval(expr, func.__globals__):
  104                 code = code + space + "# doctest: +SKIP"
  105         except AttributeError:
  106             # Works as a class decorator
  107             if eval(expr, func.__init__.__globals__):
  108                 code = code + space + "# doctest: +SKIP"
  109 
  110         new_lines.append(code)
  111     func.__doc__ = "\n".join(new_lines)
  112     return func
  113 
  114 
  115 def roundtrip(image, plugin, suffix):
  116     """Save and read an image using a specified plugin"""
  117     if '.' not in suffix:
  118         suffix = '.' + suffix
  119     temp_file = NamedTemporaryFile(suffix=suffix, delete=False)
  120     fname = temp_file.name
  121     temp_file.close()
  122     io.imsave(fname, image, plugin=plugin)
  123     new = io.imread(fname, plugin=plugin)
  124     try:
  125         os.remove(fname)
  126     except Exception:
  127         pass
  128     return new
  129 
  130 
  131 def color_check(plugin, fmt='png'):
  132     """Check roundtrip behavior for color images.
  133 
  134     All major input types should be handled as ubytes and read
  135     back correctly.
  136     """
  137     img = img_as_ubyte(data.chelsea())
  138     r1 = roundtrip(img, plugin, fmt)
  139     testing.assert_allclose(img, r1)
  140 
  141     img2 = img > 128
  142     r2 = roundtrip(img2, plugin, fmt)
  143     testing.assert_allclose(img2, r2.astype(bool))
  144 
  145     img3 = img_as_float(img)
  146     r3 = roundtrip(img3, plugin, fmt)
  147     testing.assert_allclose(r3, img)
  148 
  149     img4 = img_as_int(img)
  150     if fmt.lower() in (('tif', 'tiff')):
  151         img4 -= 100
  152         r4 = roundtrip(img4, plugin, fmt)
  153         testing.assert_allclose(r4, img4)
  154     else:
  155         r4 = roundtrip(img4, plugin, fmt)
  156         testing.assert_allclose(r4, img_as_ubyte(img4))
  157 
  158     img5 = img_as_uint(img)
  159     r5 = roundtrip(img5, plugin, fmt)
  160     testing.assert_allclose(r5, img)
  161 
  162 
  163 def mono_check(plugin, fmt='png'):
  164     """Check the roundtrip behavior for images that support most types.
  165 
  166     All major input types should be handled.
  167     """
  168 
  169     img = img_as_ubyte(data.moon())
  170     r1 = roundtrip(img, plugin, fmt)
  171     testing.assert_allclose(img, r1)
  172 
  173     img2 = img > 128
  174     r2 = roundtrip(img2, plugin, fmt)
  175     testing.assert_allclose(img2, r2.astype(bool))
  176 
  177     img3 = img_as_float(img)
  178     r3 = roundtrip(img3, plugin, fmt)
  179     if r3.dtype.kind == 'f':
  180         testing.assert_allclose(img3, r3)
  181     else:
  182         testing.assert_allclose(r3, img_as_uint(img))
  183 
  184     img4 = img_as_int(img)
  185     if fmt.lower() in (('tif', 'tiff')):
  186         img4 -= 100
  187         r4 = roundtrip(img4, plugin, fmt)
  188         testing.assert_allclose(r4, img4)
  189     else:
  190         r4 = roundtrip(img4, plugin, fmt)
  191         testing.assert_allclose(r4, img_as_uint(img4))
  192 
  193     img5 = img_as_uint(img)
  194     r5 = roundtrip(img5, plugin, fmt)
  195     testing.assert_allclose(r5, img5)
  196 
  197 
  198 def setup_test():
  199     """Default package level setup routine for skimage tests.
  200 
  201     Import packages known to raise warnings, and then
  202     force warnings to raise errors.
  203 
  204     Also set the random seed to zero.
  205     """
  206     warnings.simplefilter('default')
  207 
  208     if _error_on_warnings:
  209         from scipy import signal, ndimage, special, optimize, linalg
  210         from scipy.io import loadmat
  211         from skimage import viewer
  212 
  213         np.random.seed(0)
  214 
  215         warnings.simplefilter('error')
  216 
  217         # do not error on specific warnings from the skimage.io module
  218         # https://github.com/scikit-image/scikit-image/issues/5337
  219         warnings.filterwarnings(
  220             'default', message='TiffFile:', category=DeprecationWarning
  221         )
  222 
  223         warnings.filterwarnings(
  224             'default', message='TiffWriter:', category=DeprecationWarning
  225         )
  226         # newer tifffile change the start of the warning string
  227         # e.g. <tifffile.TiffWriter.write> data with shape ...
  228         warnings.filterwarnings(
  229             'default',
  230             message='<tifffile.',
  231             category=DeprecationWarning
  232         )
  233 
  234         warnings.filterwarnings(
  235             'default', message='unclosed file', category=ResourceWarning
  236         )
  237 
  238         # ignore known FutureWarnings from viewer module
  239         warnings.filterwarnings(
  240             'ignore', category=FutureWarning, module='skimage.viewer'
  241         )
  242 
  243         # Ignore other warnings only seen when using older versions of
  244         # dependencies.
  245         warnings.filterwarnings(
  246             'default',
  247             message='Conversion of the second argument of issubdtype',
  248             category=FutureWarning
  249         )
  250 
  251         warnings.filterwarnings(
  252             'default',
  253             message='the matrix subclass is not the recommended way',
  254             category=PendingDeprecationWarning, module='numpy'
  255         )
  256 
  257         warnings.filterwarnings(
  258             'default',
  259             message='Your installed pillow version',
  260             category=UserWarning,
  261             module='skimage.io'
  262         )
  263 
  264         # match both "viewer requires Qt" and "viewer requires matplotlib"
  265         warnings.filterwarnings(
  266             'default', message='viewer requires ', category=UserWarning
  267         )
  268 
  269         # ignore warning from cycle_spin about Dask not being installed
  270         warnings.filterwarnings(
  271             'default',
  272             message='The optional dask dependency is not installed.',
  273             category=UserWarning
  274         )
  275 
  276         warnings.filterwarnings(
  277             'default',
  278             message='numpy.ufunc size changed',
  279             category=RuntimeWarning
  280         )
  281 
  282         warnings.filterwarnings(
  283             'default',
  284             message='\n\nThe scipy.sparse array containers',
  285             category=DeprecationWarning
  286         )
  287 
  288 
  289 def teardown_test():
  290     """Default package level teardown routine for skimage tests.
  291 
  292     Restore warnings to default behavior
  293     """
  294     if _error_on_warnings:
  295         warnings.resetwarnings()
  296         warnings.simplefilter('default')
  297 
  298 
  299 def fetch(data_filename):
  300     """Attempt to fetch data, but if unavailable, skip the tests."""
  301     try:
  302         return _fetch(data_filename)
  303     except (ConnectionError, ModuleNotFoundError):
  304         pytest.skip(f'Unable to download {data_filename}',
  305                     allow_module_level=True)
  306 
  307 
  308 def test_parallel(num_threads=2, warnings_matching=None):
  309     """Decorator to run the same function multiple times in parallel.
  310 
  311     This decorator is useful to ensure that separate threads execute
  312     concurrently and correctly while releasing the GIL.
  313 
  314     Parameters
  315     ----------
  316     num_threads : int, optional
  317         The number of times the function is run in parallel.
  318 
  319     warnings_matching: list or None
  320         This parameter is passed on to `expected_warnings` so as not to have
  321         race conditions with the warnings filters. A single
  322         `expected_warnings` context manager is used for all threads.
  323         If None, then no warnings are checked.
  324 
  325     """
  326 
  327     assert num_threads > 0
  328 
  329     def wrapper(func):
  330         @functools.wraps(func)
  331         def inner(*args, **kwargs):
  332             with expected_warnings(warnings_matching):
  333                 threads = []
  334                 for i in range(num_threads - 1):
  335                     thread = threading.Thread(target=func, args=args,
  336                                               kwargs=kwargs)
  337                     threads.append(thread)
  338                 for thread in threads:
  339                     thread.start()
  340 
  341                 result = func(*args, **kwargs)
  342 
  343                 for thread in threads:
  344                     thread.join()
  345 
  346                 return result
  347 
  348         return inner
  349 
  350     return wrapper
  351 
  352 
  353 if __name__ == '__main__':
  354     color_check('pil')
  355     mono_check('pil')
  356     mono_check('pil', 'bmp')
  357     mono_check('pil', 'tiff')