mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-19 11:27:45 +08:00
Merge tag 'v0.11.3' into releases
Bugfix release (addresses pip package dependency issues) * tag 'v0.11.3': (42 commits) Set version to 0.11.3 Do not read in the whole array Fix handling of non-gif and tiff files Only index tif and gif files in collection Another random warning guard Fix setup to allow installing from PyPI ≈ Fix setup to allow installing from PyPi Another attempt at fixing the setuptools problem Fix for broken setuptools close conda install code string Fix freeimage collection test Do not attempt to use _frame_index for exotic load_patterns More specific test in freeimage Always use _find_images() Remove debug error raise Add a note to the docstring Fix handling of ImageCollection indexing and add test TST: Barn-door testing of SaveButtons clicks add some more magic install sphinx before running tests in travis install_requires from requirements.txt Update See Also to References ...
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
+27
-26
@@ -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 <http://travis-ci.org/>`__, 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 <http://travis-ci.org/>`__, 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 <http://travis-ci.org/>`__ and follow the Sign In link at the top
|
||||
* Go to `Travis-CI <http://travis-ci.org/>`__ and follow the Sign In link at
|
||||
the top
|
||||
|
||||
* Go to your `profile page <https://travis-ci.org/profile>`__ and switch
|
||||
on your scikit-image fork
|
||||
* Go to your `profile page <https://travis-ci.org/profile>`__ and switch on
|
||||
your scikit-image fork
|
||||
|
||||
It corresponds to steps one and two in
|
||||
`Travis-CI documentation <http://about.travis-ci.org/docs/user/getting-started/>`__
|
||||
|
||||
+2
-2
@@ -45,11 +45,11 @@ functionality is only available with the following installed:
|
||||
|
||||
* `Astropy <http://www.astropy.org>`__ provides FITS io capability.
|
||||
|
||||
* `SimpleITK <http://www.simpleitk.org/>`
|
||||
* `SimpleITK <http://www.simpleitk.org/>`__
|
||||
Optional io plugin providing a wide variety of `formats <http://www.itk.org/Wiki/ITK_File_Formats>`__.
|
||||
including specialized formats using in medical imaging.
|
||||
|
||||
* `imread <http://pythonhosted.org/imread/>`
|
||||
* `imread <http://pythonhosted.org/imread/>`__
|
||||
Optional io plugin providing most standard `formats <http://pythonhosted.org//imread/formats.html>`__.
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-2
@@ -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::
|
||||
|
||||
|
||||
+10
-9
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+20
-2
@@ -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...""
|
||||
|
||||
+9
-3
@@ -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 \
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-7
@@ -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')
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
+23
-23
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
+49
-14
@@ -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,
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user