diff --git a/.travis.yml b/.travis.yml
index a46b7171..14dd7c30 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,7 +23,7 @@ python:
- 3.4
before_install:
- - sudo apt-get update
+ - sudo apt-get update; true
- source tools/travis_before_install.sh
- which python; python --version
diff --git a/CONTRIBUTING.txt b/CONTRIBUTING.txt
index 58b83296..0fa66ddd 100644
--- a/CONTRIBUTING.txt
+++ b/CONTRIBUTING.txt
@@ -19,8 +19,8 @@ Here's the long and short of it:
* Now, you have remote repositories named:
- - ``upstream``, which refers to the ``scikit-image`` repository
- - ``origin``, which refers to your personal fork
+ - ``upstream``, which refers to the ``scikit-image`` repository
+ - ``origin``, which refers to your personal fork
2. Develop your contribution:
@@ -56,32 +56,32 @@ For a more detailed discussion, read these :doc:`detailed documents
4. Review process:
- * Reviewers (the other developers and interested community members) will
- write inline and/or general comments on your Pull Request (PR) to help
- you improve its implementation, documentation and style. Every single
- developer working on the project has their code reviewed, and we've come
- to see it as friendly conversation from which we all learn and the
- overall code quality benefits. Therefore, please don't let the review
- discourage you from contributing: its only aim is to improve the quality
- of project, not to criticize (we are, after all, very grateful for the
- time you're donating!).
+ * Reviewers (the other developers and interested community members) will
+ write inline and/or general comments on your Pull Request (PR) to help
+ you improve its implementation, documentation and style. Every single
+ developer working on the project has their code reviewed, and we've come
+ to see it as friendly conversation from which we all learn and the
+ overall code quality benefits. Therefore, please don't let the review
+ discourage you from contributing: its only aim is to improve the quality
+ of project, not to criticize (we are, after all, very grateful for the
+ time you're donating!).
- * To update your pull request, make your changes on your local repository
- and commit. As soon as those changes are pushed up (to the same branch as
- before) the pull request will update automatically.
+ * To update your pull request, make your changes on your local repository
+ and commit. As soon as those changes are pushed up (to the same branch as
+ before) the pull request will update automatically.
- * `Travis-CI `__, a continuous integration service,
- is triggered after each Pull Request update to build the code, run unit
- tests, measure code coverage and check coding style (PEP8) of your
- branch. The Travis tests must pass before your PR can be merged. If
- Travis fails, you can find out why by clicking on the "failed" icon (red
- cross) and inspecting the build and test log.
+ * `Travis-CI `__, a continuous integration service,
+ is triggered after each Pull Request update to build the code, run unit
+ tests, measure code coverage and check coding style (PEP8) of your
+ branch. The Travis tests must pass before your PR can be merged. If
+ Travis fails, you can find out why by clicking on the "failed" icon (red
+ cross) and inspecting the build and test log.
5. Document changes
- Before merging your commits, you must add a description of your changes
- to the release notes of the upcoming version in
- ``doc/release/release_dev.txt``.
+ Before merging your commits, you must add a description of your changes
+ to the release notes of the upcoming version in
+ ``doc/release/release_dev.txt``.
.. note::
@@ -191,10 +191,11 @@ Travis-CI checks all unittests in the project to prevent breakage.
Before sending a pull request, you may want to check that Travis-CI
successfully passes all tests. To do so,
- * Go to `Travis-CI `__ and follow the Sign In link at the top
+* Go to `Travis-CI `__ and follow the Sign In link at
+ the top
- * Go to your `profile page `__ and switch
- on your scikit-image fork
+* Go to your `profile page `__ and switch on
+ your scikit-image fork
It corresponds to steps one and two in
`Travis-CI documentation `__
diff --git a/DEPENDS.txt b/DEPENDS.txt
index 5cf37564..c7f91df5 100644
--- a/DEPENDS.txt
+++ b/DEPENDS.txt
@@ -45,11 +45,11 @@ functionality is only available with the following installed:
* `Astropy `__ provides FITS io capability.
-* `SimpleITK `
+* `SimpleITK `__
Optional io plugin providing a wide variety of `formats `__.
including specialized formats using in medical imaging.
-* `imread `
+* `imread `__
Optional io plugin providing most standard `formats `__.
diff --git a/README.md b/README.md
index b7ae96d1..f1338716 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
- **Debian/Ubuntu:** ``sudo apt-get install python-skimage``
- **OSX:** ``pip install scikit-image``
-- **Anaconda:** ``conda install scikit-image
+- **Anaconda:** ``conda install scikit-image``
- **Windows:** Download [Windows binaries](http://www.lfd.uci.edu/~gohlke/pythonlibs/#scikits.image)
Also see
diff --git a/RELEASE.txt b/RELEASE.txt
index 66860662..915134ad 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -15,7 +15,7 @@ How to make a new release of ``skimage``
3. Copy ``doc/release/release_template.txt`` to
``doc/release/release_dev.txt`` for the next release.
-- Update the version number in ``setup.py`` and ``bento.info`` and commit
+- Update the version number in ``skimage/__init__.py`` and ``bento.info`` and commit
- Update the docs:
@@ -31,7 +31,10 @@ How to make a new release of ``skimage``
- Add the version number as a tag in git::
- git tag v0.X.0
+ git tag -s v0.X.0
+
+ (If you do not have a gpg key, use -m instead; it is important for
+ Debian packaging that the tags are annotated)
- Push the new meta-data to github::
diff --git a/appveyor.yml b/appveyor.yml
index df1ffd3e..0bd10075 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -13,17 +13,18 @@ environment:
PYTHON_VERSION: "2.7"
PYTHON_ARCH: "32"
- - PYTHON: "C:\\Python27_64"
- PYTHON_VERSION: "2.7"
- PYTHON_ARCH: "64"
+ # disable other builds until they can be run in parallel
+ #- PYTHON: "C:\\Python27_64"
+ # PYTHON_VERSION: "2.7"
+ # PYTHON_ARCH: "64"
- - PYTHON: "C:\\Python34_32"
- PYTHON_VERSION: "3.4.2"
- PYTHON_ARCH: "32"
+ #- PYTHON: "C:\\Python34_32"
+ # PYTHON_VERSION: "3.4.2"
+ # PYTHON_ARCH: "32"
- - PYTHON: "C:\\Python24_64"
- PYTHON_VERSION: "3.4.2"
- PYTHON_ARCH: "64"
+ #- PYTHON: "C:\\Python24_64"
+ # PYTHON_VERSION: "3.4.2"
+ # PYTHON_ARCH: "64"
install:
# Install Python (from the official .msi of http://python.org) and pip when
diff --git a/bento.info b/bento.info
index 0af161d4..8587db3f 100644
--- a/bento.info
+++ b/bento.info
@@ -1,5 +1,5 @@
Name: scikit-image
-Version: 0.11.0
+Version: 0.11.3
Summary: Image processing routines for SciPy
Url: http://scikit-image.org
DownloadUrl: http://github.com/scikit-image/scikit-image
diff --git a/doc/README.md b/doc/README.md
index 50deb4e2..c89500e7 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,13 +2,31 @@
To build docs, run `make` in this directory. `make help` lists all targets.
## Requirements ##
-Sphinx is needed to build doc. Install with `pip install sphinx`.
+Sphinx and Latex is needed to build doc.
+
+**Spinx:**
+```sh
+pip install sphinx
+```
+
+**Latex Ubuntu:**
+```sh
+sudo apt-get install -qq texlive texlive-latex-extra dvipng
+```
+
+**Latex Mac:**
+
+Install the full [MacTex](http://www.tug.org/mactex/) installation or install the smaller [BasicTex](http://www.tug.org/mactex/morepackages.html) and add *ucs* and *dvipng* packages:
+```sh
+sudo tlmgr install ucs dvipng
+```
+
## Fixing Warnings ##
- "citation not found: R###"
$ cd doc/build; grep -rin R### .
- There is probably an underscore after a reference
+ There is probably an underscore after a reference
in the first line of a docstring (e.g. [1]_)
- "Duplicate citation R###, other instance in...""
diff --git a/doc/ext/plot2rst.py b/doc/ext/plot2rst.py
index 99fd9a3f..32811deb 100644
--- a/doc/ext/plot2rst.py
+++ b/doc/ext/plot2rst.py
@@ -186,6 +186,11 @@ def setup(app):
def generate_example_galleries(app):
cfg = app.builder.config
+ if isinstance(cfg.source_suffix, list):
+ cfg.source_suffix_str = cfg.source_suffix[0]
+ else:
+ cfg.source_suffix_str = cfg.source_suffix
+
doc_src = Path(os.path.abspath(app.builder.srcdir)) # path/to/doc/source
if isinstance(cfg.plot2rst_paths, tuple):
@@ -205,7 +210,7 @@ def generate_examples_and_gallery(example_dir, rst_dir, cfg):
rst_dir.makedirs()
# we create an index.rst with all examples
- gallery_index = open(rst_dir.pjoin('index'+cfg.source_suffix), 'w')
+ gallery_index = open(rst_dir.pjoin('index'+cfg.source_suffix_str), 'w')
# Here we don't use an os.walk, but we recurse only twice: flat is
# better than nested.
@@ -235,7 +240,7 @@ def write_gallery(gallery_index, src_dir, rst_dir, cfg, depth=0):
cfg : config object
Sphinx config object created by Sphinx.
"""
- index_name = cfg.plot2rst_index_name + cfg.source_suffix
+ index_name = cfg.plot2rst_index_name + cfg.source_suffix_str
gallery_template = src_dir.pjoin(index_name)
if not os.path.exists(gallery_template):
print(src_dir)
@@ -328,7 +333,8 @@ def write_example(src_name, src_dir, rst_dir, cfg):
image_path = image_dir.pjoin(base_image_name + '_{0}.png')
basename, py_ext = os.path.splitext(src_name)
- rst_path = rst_dir.pjoin(basename + cfg.source_suffix)
+
+ rst_path = rst_dir.pjoin(basename + cfg.source_suffix_str)
notebook_path = notebook_dir.pjoin(basename + '.ipynb')
if _plots_are_current(src_path, image_path) and rst_path.exists and \
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 94904305..794df662 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -68,10 +68,10 @@ copyright = '2013, the scikit-image team'
#
# The short X.Y version.
-setup_lines = open('../../setup.py').readlines()
+setup_lines = open('../../skimage/__init__.py').readlines()
version = 'vUndefined'
for l in setup_lines:
- if l.startswith('VERSION'):
+ if l.startswith('__version__'):
version = l.split("'")[1]
break
diff --git a/doc/tools/build_modref_templates.py b/doc/tools/build_modref_templates.py
index e83ce980..2d959252 100644
--- a/doc/tools/build_modref_templates.py
+++ b/doc/tools/build_modref_templates.py
@@ -37,10 +37,10 @@ if __name__ == '__main__':
installed_version = V(module.__version__)
- setup_lines = open('../setup.py').readlines()
+ source_lines = open('../skimage/__init__.py').readlines()
version = 'vUndefined'
- for l in setup_lines:
- if l.startswith('VERSION'):
+ for l in source_lines:
+ if l.startswith('__version__'):
source_version = V(l.split("'")[1])
break
diff --git a/setup.py b/setup.py
old mode 100755
new mode 100644
index 8ba529e5..9e5d1444
--- a/setup.py
+++ b/setup.py
@@ -17,28 +17,40 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za'
URL = 'http://scikit-image.org'
LICENSE = 'Modified BSD'
DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image'
-VERSION = '0.11.0'
-PYTHON_VERSION = (2, 6)
import os
-import sys
import setuptools
from distutils.command.build_py import build_py
-from distutils.version import LooseVersion
-# These are manually checked.
-# These packages are sometimes installed outside of the setuptools scope
-DEPENDENCIES = {}
-with open('requirements.txt', 'rb') as fid:
- data = fid.read().decode('utf-8', 'replace')
-for line in data.splitlines():
- pkg, _, version_info = line.replace('==', '>=').partition('>=')
- # Only require Cython if we have a developer checkout
- if pkg.lower() == 'cython' and not VERSION.endswith('dev'):
- continue
- DEPENDENCIES[str(pkg).lower()] = str(version_info)
+with open('skimage/__init__.py') as fid:
+ for line in fid:
+ if line.startswith('__version__'):
+ VERSION = line.strip().split()[-1][1:-1]
+ break
+
+with open('requirements.txt') as fid:
+ INSTALL_REQUIRES = [l.strip() for l in fid.readlines() if l]
+
+# development versions do not have the cythonized files
+if VERSION.endswith('dev'):
+ SETUP_REQUIRES = [r for r in INSTALL_REQUIRES if r.startswith('cython')]
+else:
+ INSTALL_REQUIRES = [r for r in INSTALL_REQUIRES
+ if not r.startswith('cython')]
+ SETUP_REQUIRES = []
+
+
+# list requirements for PyPI
+REQUIRES = [r.replace('>=', ' (>= ') + ')'
+ for r in INSTALL_REQUIRES + SETUP_REQUIRES]
+REQUIRES = [r.replace('==', ' (== ') for r in REQUIRES]
+
+
+# do not attempt to install numpy and scipy until they have eggs available
+INSTALL_REQUIRES = [r for r in INSTALL_REQUIRES
+ if not r.startswith(('scipy', 'numpy'))]
def configuration(parent_package='', top_path=None):
@@ -59,72 +71,11 @@ def configuration(parent_package='', top_path=None):
return config
-def write_version_py(filename='skimage/version.py'):
- template = """# THIS FILE IS GENERATED FROM THE SKIMAGE SETUP.PY
-version='%s'
-"""
-
- try:
- vfile = open(os.path.join(os.path.dirname(__file__),
- filename), 'w')
- vfile.write(template % VERSION)
-
- except IOError:
- raise IOError("Could not open/write to skimage/version.py - did you "
- "install using sudo in the past? If so, run\n"
- "sudo chown -R your_username ./*\n"
- "from package root to fix permissions, and try again.")
-
- finally:
- vfile.close()
-
-
-def get_package_version(package):
- for version_attr in ('__version__', 'VERSION', 'version'):
- version_info = getattr(package, version_attr, None)
- if version_info and str(version_attr) == version_attr:
- return str(version_info)
-
-
-def check_requirements():
- if sys.version_info < PYTHON_VERSION:
- raise SystemExit('You need Python version %d.%d or later.' \
- % PYTHON_VERSION)
- for (package_name, min_version) in DEPENDENCIES.items():
- if package_name == 'cython':
- package_name = 'Cython'
- dep_error = ''
- if package_name.lower() == 'pillow':
- package_name = 'PIL.Image'
- min_version = '1.1.7'
- try:
- package = __import__(package_name,
- fromlist=[package_name.rpartition('.')[0]])
- except ImportError:
- dep_error = ('You need `%s` version %s or later.'
- % (package_name, min_version))
- else:
- if package_name == 'PIL':
- package_version = package.PILLOW_VERSION
- else:
- package_version = get_package_version(package)
-
- if LooseVersion(min_version) > LooseVersion(package_version):
- dep_error = ('You need `%s` version %s or later,'
- 'found version %s.'
- % (package_name, min_version,
- package_version))
- if dep_error:
- raise ImportError(dep_error)
-
-
if __name__ == "__main__":
-
- check_requirements()
-
- write_version_py()
-
+ # purposely fail loudly if numpy or scipy are not available
from numpy.distutils.core import setup
+ import scipy
+
setup(
name=DISTNAME,
description=DESCRIPTION,
@@ -153,9 +104,9 @@ if __name__ == "__main__":
],
configuration=configuration,
- install_requires=[
- "six>=%s" % DEPENDENCIES['six']
- ],
+ setup_requires=SETUP_REQUIRES,
+ install_requires=INSTALL_REQUIRES,
+ requires=REQUIRES,
packages=setuptools.find_packages(exclude=['doc']),
include_package_data=True,
zip_safe=False, # the package can run out of an .egg file
diff --git a/skimage/__init__.py b/skimage/__init__.py
index 8f386682..2ec70944 100644
--- a/skimage/__init__.py
+++ b/skimage/__init__.py
@@ -64,13 +64,7 @@ import warnings as _warnings
pkg_dir = _osp.abspath(_osp.dirname(__file__))
data_dir = _osp.join(pkg_dir, 'data')
-try:
- from .version import version as __version__
-except ImportError:
- __version__ = "unbuilt-dev"
-else:
- del version
-
+__version__ = '0.11.3'
try:
_imp.find_module('nose')
diff --git a/skimage/_shared/testing.py b/skimage/_shared/testing.py
index 1c41221b..f202fbff 100644
--- a/skimage/_shared/testing.py
+++ b/skimage/_shared/testing.py
@@ -161,7 +161,7 @@ def mono_check(plugin, fmt='png'):
else:
testing.assert_allclose(r3, img_as_uint(img))
- with expected_warnings(['precision loss']):
+ with expected_warnings(['precision loss|unclosed file']):
img4 = img_as_int(img)
if fmt.lower() in (('tif', 'tiff')):
img4 -= 100
@@ -182,7 +182,7 @@ def setup_test():
"""Default package level setup routine for skimage tests.
Import packages known to raise errors, and then
- force warnings to raise errors.
+ force warnings to raise errors.
Set a random seed
"""
warnings.simplefilter('default')
@@ -190,7 +190,7 @@ def setup_test():
from scipy.io import loadmat
from skimage import viewer, filter
np.random.seed(0)
- warnings.simplefilter('error')
+ warnings.simplefilter('error')
def teardown_test():
diff --git a/skimage/exposure/__init__.py b/skimage/exposure/__init__.py
index d78cf927..1950f37a 100644
--- a/skimage/exposure/__init__.py
+++ b/skimage/exposure/__init__.py
@@ -1,6 +1,7 @@
from .exposure import histogram, equalize_hist, \
rescale_intensity, cumulative_distribution, \
- adjust_gamma, adjust_sigmoid, adjust_log
+ adjust_gamma, adjust_sigmoid, adjust_log, \
+ is_low_contrast
from ._adapthist import equalize_adapthist
@@ -12,4 +13,5 @@ __all__ = ['histogram',
'cumulative_distribution',
'adjust_gamma',
'adjust_sigmoid',
- 'adjust_log']
+ 'adjust_log',
+ 'is_low_contrast']
diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py
index 8774cf9b..0cad7bbe 100644
--- a/skimage/exposure/exposure.py
+++ b/skimage/exposure/exposure.py
@@ -1,7 +1,8 @@
+from __future__ import division
import warnings
import numpy as np
-from .. import img_as_float
+from ..color import rgb2gray
from ..util.dtype import dtype_range, dtype_limits
@@ -463,3 +464,54 @@ def adjust_sigmoid(image, cutoff=0.5, gain=10, inv=False):
out = (1 / (1 + np.exp(gain * (cutoff - image / scale)))) * scale
return dtype(out)
+
+
+def is_low_contrast(image, fraction_threshold=0.05, lower_percentile=1,
+ upper_percentile=99, method='linear'):
+ """Detemine if an image is low contrast.
+
+ Parameters
+ ----------
+ image : array-like
+ The image under test.
+ fraction_threshold : float, optional
+ The low contrast fraction threshold. An image is considered low-
+ contrast when its range of brightness spans less than this
+ fraction of its data type's full range. [1]_
+ lower_bound : float, optional
+ Disregard values below this percentile when computing image contrast.
+ upper_bound : float, optional
+ Disregard values above this percentile when computing image contrast.
+ method : str, optional
+ The contrast determination method. Right now the only available
+ option is "linear".
+
+ Returns
+ -------
+ out : bool
+ True when the image is determined to be low contrast.
+
+ References
+ ----------
+ .. [1] http://scikit-image.org/docs/dev/user_guide/data_types.html
+
+ Examples
+ --------
+ >>> image = np.linspace(0, 0.04, 100)
+ >>> is_low_contrast(image)
+ True
+ >>> image[-1] = 1
+ >>> is_low_contrast(image)
+ True
+ >>> is_low_contrast(image, upper_percentile=100)
+ False
+ """
+ image = np.asanyarray(image)
+ if image.ndim == 3 and image.shape[2] in [3, 4]:
+ image = rgb2gray(image)
+
+ dlimits = dtype_limits(image)
+ limits = np.percentile(image, [lower_percentile, upper_percentile])
+ ratio = (limits[1] - limits[0]) / (dlimits[1] - dlimits[0])
+
+ return ratio < fraction_threshold
diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py
index 1316ea4e..265ce246 100644
--- a/skimage/exposure/tests/test_exposure.py
+++ b/skimage/exposure/tests/test_exposure.py
@@ -472,6 +472,22 @@ def test_negative():
assert_raises(ValueError, exposure.adjust_gamma, image)
+def test_is_low_contrast():
+ image = np.linspace(0, 0.04, 100)
+ assert exposure.is_low_contrast(image)
+ image[-1] = 1
+ assert exposure.is_low_contrast(image)
+ assert not exposure.is_low_contrast(image, upper_percentile=100)
+
+ image = (image * 255).astype(np.uint8)
+ assert exposure.is_low_contrast(image)
+ assert not exposure.is_low_contrast(image, upper_percentile=100)
+
+ image = (image.astype(np.uint16)) * 2**8
+ assert exposure.is_low_contrast(image)
+ assert not exposure.is_low_contrast(image, upper_percentile=100)
+
+
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
diff --git a/skimage/io/_io.py b/skimage/io/_io.py
index f44bfd8b..e76c36f6 100644
--- a/skimage/io/_io.py
+++ b/skimage/io/_io.py
@@ -1,4 +1,5 @@
from io import BytesIO
+import warnings
import numpy as np
import six
@@ -6,6 +7,8 @@ import six
from ..io.manage_plugins import call_plugin
from ..color import rgb2grey
from .util import file_or_url_context
+from ..exposure import is_low_contrast
+from .._shared._warnings import all_warnings
__all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show']
@@ -152,6 +155,8 @@ def imsave(fname, arr, plugin=None, **plugin_args):
Passed to the given plugin.
"""
+ if is_low_contrast(arr):
+ warnings.warn('%s is a low contrast image' % fname)
return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
diff --git a/skimage/io/_plugins/gtk_plugin.py b/skimage/io/_plugins/gtk_plugin.py
index 6130289c..327d762d 100644
--- a/skimage/io/_plugins/gtk_plugin.py
+++ b/skimage/io/_plugins/gtk_plugin.py
@@ -1,4 +1,4 @@
-from util import prepare_for_display, window_manager, GuiLockError
+from .util import prepare_for_display, window_manager, GuiLockError
try:
# we try to aquire the gui lock first
diff --git a/skimage/io/_plugins/matplotlib_plugin.py b/skimage/io/_plugins/matplotlib_plugin.py
index 2de61ecf..32ebf2fd 100644
--- a/skimage/io/_plugins/matplotlib_plugin.py
+++ b/skimage/io/_plugins/matplotlib_plugin.py
@@ -2,7 +2,9 @@ from collections import namedtuple
import numpy as np
import warnings
import matplotlib.pyplot as plt
-from skimage.util import dtype as dtypes
+from ...util import dtype as dtypes
+from ...exposure import is_low_contrast
+from ..._shared._warnings import all_warnings
_default_colormap = 'gray'
@@ -49,7 +51,7 @@ def _get_image_properties(image):
out_of_range_float = (np.issubdtype(image.dtype, np.float) and
(immin < lo or immax > hi))
low_dynamic_range = (immin != immax and
- (float(immax - immin) / (hi - lo)) < (1. / 255))
+ is_low_contrast(image))
unsupported_dtype = image.dtype not in dtypes._supported_types
return ImageProperties(signed, out_of_range_float,
@@ -72,8 +74,8 @@ def _raise_warnings(image_properties):
warnings.warn("Low image dynamic range; displaying image with "
"stretched contrast.")
if ip.out_of_range_float:
- warnings.warn("Float image out of standard range; displaying image "
- "with stretched contrast.")
+ warnings.warn("Float image out of standard range; displaying "
+ "image with stretched contrast.")
def _get_display_range(image):
diff --git a/skimage/io/_plugins/q_color_mixer.py b/skimage/io/_plugins/q_color_mixer.py
index 3fe9e29c..87402a37 100644
--- a/skimage/io/_plugins/q_color_mixer.py
+++ b/skimage/io/_plugins/q_color_mixer.py
@@ -2,7 +2,7 @@
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import (QWidget, QStackedWidget, QSlider, QGridLayout, QLabel)
-from util import ColorMixer
+from .util import ColorMixer
class IntelligentSlider(QWidget):
diff --git a/skimage/io/_plugins/q_histogram.py b/skimage/io/_plugins/q_histogram.py
index 0a60ce9f..4b1b545b 100644
--- a/skimage/io/_plugins/q_histogram.py
+++ b/skimage/io/_plugins/q_histogram.py
@@ -2,7 +2,7 @@ import numpy as np
from PyQt4.QtGui import QWidget, QPainter, QGridLayout, QColor, QFrame
-from util import histograms
+from .util import histograms
class ColorHistogram(QWidget):
diff --git a/skimage/io/collection.py b/skimage/io/collection.py
index 49d6aa25..8812740e 100644
--- a/skimage/io/collection.py
+++ b/skimage/io/collection.py
@@ -135,7 +135,8 @@ class ImageCollection(object):
ic = ImageCollection('/tmp/*.png', load_func=imread_convert)
For files with multiple images, the images will be flattened into a list
- and added to the list of available images.
+ and added to the list of available images. In this case, ``load_func``
+ should accept the keyword argument ``img_num``.
Examples
--------
@@ -165,7 +166,7 @@ class ImageCollection(object):
self._numframes = self._find_images()
else:
self._files = load_pattern
- self._numframes = len(load_pattern)
+ self._numframes = len(self._files)
self._frame_index = None
if conserve_memory:
@@ -201,26 +202,22 @@ class ImageCollection(object):
img = TiffFile(fname)
index += [(fname, i) for i in range(len(img.pages))]
else:
- im = Image.open(fname)
try:
- # this will raise an IOError if the file is not readable
- im.getdata()[0]
- except IOError:
- site = "http://pillow.readthedocs.org/en/latest/installation.html#external-libraries"
- raise ValueError(
- 'Could not load "%s"\nPlease see documentation at: %s' % (fname, site))
- else:
- i = 0
- while True:
- try:
- im.seek(i)
- except EOFError:
- break
- index.append((fname, i))
- i += 1
+ im = Image.open(fname)
+ im.seek(0)
+ except (IOError, OSError):
+ index.append([fname, i])
+ continue
+ i = 0
+ while True:
+ try:
+ im.seek(i)
+ except EOFError:
+ break
+ index.append((fname, i))
+ i += 1
if hasattr(im, 'fp') and im.fp:
im.fp.close()
-
self._frame_index = index
return len(index)
@@ -254,13 +251,16 @@ class ImageCollection(object):
if ((self.conserve_memory and n != self._cached) or
(self.data[idx] is None)):
+ kwargs = self.load_func_kwargs
if self._frame_index:
fname, img_num = self._frame_index[n]
- self.data[idx] = self.load_func(fname, img_num=img_num,
- **self.load_func_kwargs)
+ if img_num > 0:
+ self.data[idx] = self.load_func(fname, img_num=img_num,
+ **kwargs)
+ else:
+ self.data[idx] = self.load_func(fname, **kwargs)
else:
- self.data[idx] = self.load_func(self.files[n],
- **self.load_func_kwargs)
+ self.data[idx] = self.load_func(self.files[n], **kwargs)
self._cached = n
return self.data[idx]
diff --git a/skimage/io/tests/test_freeimage.py b/skimage/io/tests/test_freeimage.py
index 81f8058a..238229d1 100644
--- a/skimage/io/tests/test_freeimage.py
+++ b/skimage/io/tests/test_freeimage.py
@@ -1,6 +1,7 @@
import os
import skimage as si
import skimage.io as sio
+from skimage import data_dir
import numpy as np
from numpy.testing import *
@@ -112,5 +113,17 @@ def test_metadata():
assert meta[1][('EXIF_MAIN', 'Software')].startswith('I')
+@skipif(not FI_available)
+def test_collection():
+ pattern = [os.path.join(data_dir, pic)
+ for pic in ['camera.png', 'color.png', 'multipage.tif']]
+ images = sio.ImageCollection(pattern[:-1])
+ assert len(images) == 2
+ assert len(images[:]) == 2
+
+ images = sio.ImageCollection(pattern)
+ assert len(images) == 3
+ assert len(images[:]) == 3
+
if __name__ == "__main__":
run_module_suite()
diff --git a/skimage/io/tests/test_mpl_imshow.py b/skimage/io/tests/test_mpl_imshow.py
index aad51048..166c8cbc 100644
--- a/skimage/io/tests/test_mpl_imshow.py
+++ b/skimage/io/tests/test_mpl_imshow.py
@@ -87,7 +87,8 @@ def test_outside_standard_range():
def test_nonstandard_type():
plt.figure()
- with expected_warnings(["Non-standard image type"]):
+ with expected_warnings(["Non-standard image type",
+ "Low image dynamic range"]):
ax_im = io.imshow(im64)
assert ax_im.get_clim() == (im64.min(), im64.max())
assert n_subplots(ax_im) == 2
diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py
index fc28ce89..e8909503 100644
--- a/skimage/io/tests/test_pil.py
+++ b/skimage/io/tests/test_pil.py
@@ -147,7 +147,8 @@ def test_imsave_filelike():
s = BytesIO()
# save to file-like object
- with expected_warnings(['precision loss|unclosed file']):
+ with expected_warnings(['precision loss|unclosed file',
+ 'is a low contrast image']):
imsave(s, image)
# read from file-like object
diff --git a/skimage/measure/__init__.py b/skimage/measure/__init__.py
index e2a4a51a..9731d6da 100755
--- a/skimage/measure/__init__.py
+++ b/skimage/measure/__init__.py
@@ -9,7 +9,7 @@ from ._moments import moments, moments_central, moments_normalized, moments_hu
from .profile import profile_line
from .fit import LineModel, CircleModel, EllipseModel, ransac
from .block import block_reduce
-from ._ccomp import label
+from ._label import label
__all__ = ['find_contours',
diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py
index 7de90291..f8e65117 100644
--- a/skimage/measure/fit.py
+++ b/skimage/measure/fit.py
@@ -4,9 +4,6 @@ import numpy as np
from scipy import optimize
-_EPSILON = np.spacing(1)
-
-
def _check_data_dim(data, dim):
if data.ndim != 2 or data.shape[1] != dim:
raise ValueError('Input data must have shape (N, %d).' % dim)
@@ -57,6 +54,11 @@ class LineModel(BaseModel):
data : (N, 2) array
N points with ``(x, y)`` coordinates, respectively.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
_check_data_dim(data, dim=2)
@@ -81,6 +83,8 @@ class LineModel(BaseModel):
self.params = (dist, theta)
+ return True
+
def residuals(self, data):
"""Determine residuals of data to model.
@@ -182,6 +186,11 @@ class CircleModel(BaseModel):
data : (N, 2) array
N points with ``(x, y)`` coordinates, respectively.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
_check_data_dim(data, dim=2)
@@ -217,6 +226,8 @@ class CircleModel(BaseModel):
self.params = params
+ return True
+
def residuals(self, data):
"""Determine residuals of data to model.
@@ -313,6 +324,11 @@ class EllipseModel(BaseModel):
data : (N, 2) array
N points with ``(x, y)`` coordinates, respectively.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
_check_data_dim(data, dim=2)
@@ -373,6 +389,8 @@ class EllipseModel(BaseModel):
self.params = params[:5]
+ return True
+
def residuals(self, data):
"""Determine residuals of data to model.
@@ -471,7 +489,6 @@ class EllipseModel(BaseModel):
def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability):
"""Determine number trials such that at least one outlier-free subset is
sampled for the given inlier/outlier ratio.
-
Parameters
----------
n_inliers : int
@@ -482,21 +499,31 @@ def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability):
Minimum number of samples chosen randomly from original data.
probability : float
Probability (confidence) that one outlier-free sample is generated.
-
Returns
-------
trials : int
Number of trials.
-
"""
+ if n_inliers == 0:
+ return np.inf
+
+ nom = 1 - probability
+ if nom == 0:
+ return np.inf
+
inlier_ratio = n_inliers / float(n_samples)
- nom = max(_EPSILON, 1 - probability)
- denom = max(_EPSILON, 1 - inlier_ratio ** min_samples)
- if nom == 1:
+ denom = 1 - inlier_ratio ** min_samples
+ if denom == 0:
+ return 1
+ elif denom == 1:
+ return np.inf
+
+ nom = np.log(nom)
+ denom = np.log(denom)
+ if denom == 0:
return 0
- if denom == 1:
- return float('inf')
- return abs(float(np.ceil(np.log(nom) / np.log(denom))))
+
+ return int(np.ceil(nom / denom))
def ransac(data, model_class, min_samples, residual_threshold,
@@ -542,9 +569,11 @@ def ransac(data, model_class, min_samples, residual_threshold,
model_class : object
Object with the following object methods:
- * ``estimate(*data)``
+ * ``success = estimate(*data)``
* ``residuals(*data)``
+ where `success` indicates whether the model estimation succeeded
+ (`True` or `None` for success, `False` for failure).
min_samples : int
The minimum number of data points to fit a model to.
residual_threshold : float
@@ -612,6 +641,7 @@ def ransac(data, model_class, min_samples, residual_threshold,
>>> model = EllipseModel()
>>> model.estimate(data)
+ True
>>> model.params # doctest: +SKIP
array([ -3.30354146e+03, -2.87791160e+03, 5.59062118e+03,
7.84365066e+00, 7.19203152e-01])
@@ -688,7 +718,12 @@ def ransac(data, model_class, min_samples, residual_threshold,
# estimate model for current random sample set
sample_model = model_class()
- sample_model.estimate(*samples)
+
+ success = sample_model.estimate(*samples)
+
+ if success is not None: # backwards compatibility
+ if not success:
+ continue
# check if estimated model is valid
if is_model_valid is not None and not is_model_valid(sample_model,
diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py
index 1b960d4b..7f98c971 100644
--- a/skimage/measure/tests/test_fit.py
+++ b/skimage/measure/tests/test_fit.py
@@ -233,9 +233,9 @@ def test_ransac_dynamic_max_trials():
# e = 50%, min_samples = 8
assert_equal(_dynamic_max_trials(50, 100, 8, 0.99), 1177)
- # e = 0%, min_samples = 10
- assert_equal(_dynamic_max_trials(1, 100, 10, 0), 0)
- assert_equal(_dynamic_max_trials(1, 100, 10, 1), float('inf'))
+ # e = 0%, min_samples = 5
+ assert_equal(_dynamic_max_trials(1, 100, 5, 0), 0)
+ assert_equal(_dynamic_max_trials(1, 100, 5, 1), np.inf)
def test_ransac_invalid_input():
diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py
index 3ac735c7..487d782e 100644
--- a/skimage/measure/tests/test_regionprops.py
+++ b/skimage/measure/tests/test_regionprops.py
@@ -26,7 +26,7 @@ INTENSITY_SAMPLE[1, 9:11] = 2
def test_all_props():
region = regionprops(SAMPLE, INTENSITY_SAMPLE)[0]
for prop in PROPS:
- assert_equal(region[prop], getattr(region, PROPS[prop]))
+ assert_almost_equal(region[prop], getattr(region, PROPS[prop]))
def test_dtype():
diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py
index c94e3b76..33448353 100644
--- a/skimage/transform/_geometric.py
+++ b/skimage/transform/_geometric.py
@@ -263,6 +263,11 @@ class ProjectiveTransform(GeometricTransform):
dst : (N, 2) array
Destination coordinates.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
try:
@@ -270,7 +275,7 @@ class ProjectiveTransform(GeometricTransform):
dst_matrix, dst = _center_and_normalize_points(dst)
except ZeroDivisionError:
self.params = np.nan * np.empty((3, 3))
- return
+ return False
xs = src[:, 0]
ys = src[:, 1]
@@ -309,6 +314,8 @@ class ProjectiveTransform(GeometricTransform):
self.params = H
+ return True
+
def __add__(self, other):
"""Combine this transformation with another.
@@ -459,6 +466,11 @@ class PiecewiseAffineTransform(GeometricTransform):
dst : (N, 2) array
Destination coordinates.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
# forward piecewise affine
@@ -481,6 +493,8 @@ class PiecewiseAffineTransform(GeometricTransform):
affine.estimate(dst[tri, :], src[tri, :])
self.inverse_affines.append(affine)
+ return True
+
def __call__(self, coords):
"""Apply forward transformation.
@@ -658,6 +672,11 @@ class SimilarityTransform(ProjectiveTransform):
dst : (N, 2) array
Destination coordinates.
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
try:
@@ -665,7 +684,7 @@ class SimilarityTransform(ProjectiveTransform):
dst_matrix, dst = _center_and_normalize_points(dst)
except ZeroDivisionError:
self.params = np.nan * np.empty((3, 3))
- return
+ return False
xs = src[:, 0]
ys = src[:, 1]
@@ -699,6 +718,7 @@ class SimilarityTransform(ProjectiveTransform):
self.params = S
+ return True
@property
def scale(self):
@@ -798,6 +818,11 @@ class PolynomialTransform(GeometricTransform):
order : int, optional
Polynomial order (number of coefficients is order + 1).
+ Returns
+ -------
+ success : bool
+ True, if model estimation succeeds.
+
"""
xs = src[:, 0]
ys = src[:, 1]
@@ -828,6 +853,8 @@ class PolynomialTransform(GeometricTransform):
self.params = params.reshape((2, u // 2))
+ return True
+
def __call__(self, coords):
"""Apply forward transformation.
diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py
index 0e7bc060..1c594534 100644
--- a/skimage/util/dtype.py
+++ b/skimage/util/dtype.py
@@ -11,6 +11,10 @@ dtype_range = {np.bool_: (False, True),
np.uint16: (0, 65535),
np.int8: (-128, 127),
np.int16: (-32768, 32767),
+ np.int64: (-2**63, 2**63 - 1),
+ np.uint64: (0, 2**64 - 1),
+ np.int32: (-2**31, 2**31 - 1),
+ np.uint32: (0, 2**32 - 1),
np.float32: (-1, 1),
np.float64: (-1, 1)}
diff --git a/skimage/viewer/tests/test_widgets.py b/skimage/viewer/tests/test_widgets.py
index 66b86fa4..8d0e05e0 100644
--- a/skimage/viewer/tests/test_widgets.py
+++ b/skimage/viewer/tests/test_widgets.py
@@ -101,6 +101,11 @@ def test_save_buttons():
timer = QtCore.QTimer()
timer.singleShot(100, QtGui.QApplication.quit)
+ # exercise the button clicks
+ sv.save_stack.click()
+ sv.save_file.click()
+
+ # call the save functions directly
sv.save_to_stack()
with expected_warnings(['precision loss']):
sv.save_to_file(filename)
diff --git a/skimage/viewer/widgets/__init__.py b/skimage/viewer/widgets/__init__.py
index efa9fe9d..2705f371 100644
--- a/skimage/viewer/widgets/__init__.py
+++ b/skimage/viewer/widgets/__init__.py
@@ -1,2 +1,20 @@
+"""
+Widgets for interacting with ImageViewer.
+
+These widgets should be added to a Plugin subclass using its `add_widget`
+method or calling::
+
+ plugin += Widget(...)
+
+on a Plugin instance. The Plugin will delegate action based on the widget's
+parameter type specified by its `ptype` attribute, which can be::
+
+ 'arg' : positional argument passed to Plugin's `filter_image` method.
+ 'kwarg' : keyword argument passed to Plugin's `filter_image` method.
+ 'plugin' : attribute of Plugin. You'll probably need to add a class
+ property of the same name that updates the display.
+
+"""
+
from .core import *
from .history import *
diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py
index ae8be4e7..71f0bc50 100644
--- a/skimage/viewer/widgets/core.py
+++ b/skimage/viewer/widgets/core.py
@@ -1,20 +1,3 @@
-"""
-Widgets for interacting with ImageViewer.
-
-These widgets should be added to a Plugin subclass using its `add_widget`
-method or calling::
-
- plugin += Widget(...)
-
-on a Plugin instance. The Plugin will delegate action based on the widget's
-parameter type specified by its `ptype` attribute, which can be:
-
- 'arg' : positional argument passed to Plugin's `filter_image` method.
- 'kwarg' : keyword argument passed to Plugin's `filter_image` method.
- 'plugin' : attribute of Plugin. You'll probably need to add a class
- property of the same name that updates the display.
-
-"""
from ..qt import QtWidgets, QtCore, Qt
from ..utils import RequiredAttr
@@ -275,7 +258,7 @@ class CheckBox(BaseWidget):
added to a plugin.
"""
- def __init__(self, name, value=False, alignment='center', ptype='kwarg',
+ def __init__(self, name, value=False, alignment='center', ptype='kwarg',
callback=None):
super(CheckBox, self).__init__(name, ptype, callback)
diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py
index 3b7f58b0..6229e525 100644
--- a/skimage/viewer/widgets/history.py
+++ b/skimage/viewer/widgets/history.py
@@ -80,9 +80,9 @@ class SaveButtons(BaseWidget):
notify(msg)
def save_to_file(self, filename=None):
- if filename is None:
+ if not filename:
filename = dialogs.save_file_dialog()
- if filename is None:
+ if not filename:
return
image = self.plugin.filtered_image
if image.dtype == np.bool: