mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-19 11:27:45 +08:00
Merge remote-tracking branch 'origin/master' into v0.11.x
Conflicts: bento.info setup.py
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/>`__
|
||||
|
||||
@@ -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.2
|
||||
Version: 0.12.dev0
|
||||
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,41 @@ 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.2'
|
||||
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 +72,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 +105,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.12dev'
|
||||
|
||||
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