Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update cucim.skimage API to match scikit-image 0.19 #190

Merged
merged 79 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
7eefa7b
ENH: add channel_axis support to cucim.skimage.color module
grlee77 Dec 23, 2021
4f19966
ENH: add channel_axis support to cucim.skimage.exposure module
grlee77 Dec 23, 2021
0dc0d13
ENH: add channel_axis support to cucim.skimage.feature module
grlee77 Dec 23, 2021
e81be51
ENH: add channel_axis support to cucim.skimage.filters module
grlee77 Dec 23, 2021
84873a4
ENH: add channel_axis support to cucim.skimage.metrics module
grlee77 Dec 23, 2021
7a83810
ENH: add channel_axis support to cucim.skimage.transform module
grlee77 Dec 23, 2021
c66ac69
ENH: add channel_axis support to cucim.skimage.restoration module
grlee77 Dec 23, 2021
c707354
ENH: add channel_axis support to cucim.skimage.segmentation module
grlee77 Dec 24, 2021
3748a26
residual fixes to test suite
grlee77 Dec 24, 2021
72150c6
update benchmarks to use channel_axis rather than multichannel
grlee77 Dec 24, 2021
d7019ab
update conda environments to use scikit-image 0.19.1
grlee77 Dec 24, 2021
b360bf7
improve float32 support in skimage.color
grlee77 Dec 24, 2021
eb710ee
improve float32 support in skimage.exposure
grlee77 Dec 24, 2021
a0aad25
improve float32 support in skimage.feature
grlee77 Dec 24, 2021
ef04c9f
improve float32 support in skimage.filters
grlee77 Dec 24, 2021
02cf5f0
update float32 support in cucim.skimage.measure
grlee77 Dec 26, 2021
e335782
update tests for float32 support in cucim.skimage.metrics
grlee77 Dec 27, 2021
fa4993e
update float32 support in cucim.skimage.registration
grlee77 Dec 27, 2021
d84a63d
fix channel_axis support in pyramid functions
grlee77 Dec 28, 2021
fdc8276
Update _geometric.py to match scikit-image v0.19.1 implementation
grlee77 Dec 28, 2021
1a120ea
Update float32 dtype handling in cucim.skimage.transform
grlee77 Dec 28, 2021
50cf112
update float32 support in cucim.skimage.restoration
grlee77 Dec 28, 2021
cefcd7a
update float32 support in cucim.skimage.segmentation
grlee77 Dec 28, 2021
3ffc48f
flake8 fixes
grlee77 Dec 28, 2021
ce64c27
flake8
grlee77 Dec 28, 2021
5ef5f52
update tests in test_utils.py
grlee77 Dec 28, 2021
5c4a08f
fix test_moments.py
grlee77 Dec 28, 2021
2a89581
apply isort
grlee77 Dec 29, 2021
508991b
flake8
grlee77 Dec 29, 2021
b875f2c
deprecate selem in favor of footprint
grlee77 Dec 29, 2021
e0983cb
deprecate in_place in favor of out
grlee77 Dec 29, 2021
c451c94
iterations -> num_iter, max_iter -> max_num_iter deprecations
grlee77 Dec 29, 2021
69f05a2
update to non-deprecated names in benchmarks
grlee77 Dec 29, 2021
3fb09d8
update to non-deprecated names in benchmarks
grlee77 Dec 29, 2021
9b4ce30
ENH: Support lazy loading of cucim.skimage submodules
grlee77 Jan 16, 2022
a8d9065
add cucim.skimage.lookfor utility
grlee77 Jan 17, 2022
4a76752
clip output of separate stains
grlee77 Jan 17, 2022
5e34b5e
ENH: add additional color illuminants
grlee77 Jan 17, 2022
9ce2328
fix
grlee77 Jan 17, 2022
ae7c3df
use default_rng in binary_blobs
grlee77 Jan 17, 2022
9dffaf2
match_histogram dtype fix
grlee77 Jan 17, 2022
404e600
ENH: add mode and cval to canny
grlee77 Jan 17, 2022
bc18b79
ENH: update peak_local_max as in scikit-image 0.19
grlee77 Jan 17, 2022
5503f1f
dtype fix in filters/edges.py
grlee77 Jan 17, 2022
c23748f
move gaussian filter definition as in skimage 0.19
grlee77 Jan 17, 2022
4cfd561
fix circular imports when EAGER_IMPORT=1 is defined
grlee77 Jan 17, 2022
975caae
BUG: fix broken doctests
grlee77 Jan 17, 2022
28e0add
ENH: regionprops updates
grlee77 Jan 17, 2022
6213841
remove legacy regionprops code path
grlee77 Jan 17, 2022
fd313e5
ENH: add normalized_mutual_information metric
grlee77 Jan 17, 2022
a7a574b
update regionprop name in peaks.py
grlee77 Jan 17, 2022
8002e5c
n_iter_max->max_num_iter for denoise_tv_chambolle
grlee77 Jan 17, 2022
ea60076
fix max_num_iter
grlee77 Jan 17, 2022
5ba509c
apply isort
grlee77 Jan 17, 2022
12a1d13
flake8
grlee77 Jan 17, 2022
65e564e
ENH: add cucim.skimage.transform.resize_local_mean
grlee77 Jan 17, 2022
c381d95
ENH: add skimage.filter.butterworth
grlee77 Jan 17, 2022
51f8f8e
ENH: add cucim.skimage.metrics.blur_effect
grlee77 Jan 17, 2022
68c4d60
fix resize_local_mean doctest
grlee77 Jan 17, 2022
1b977c7
fix butterworth doctest
grlee77 Jan 17, 2022
d3349b6
remove structure_tensor_eigvals
grlee77 Jan 17, 2022
df827e6
fix imports for compatibility with scikit-image 0.18
grlee77 Jan 17, 2022
59bf508
fix import path in conftest.py
grlee77 Jan 17, 2022
90275ec
add benchmark for resize_local_mean
grlee77 Jan 17, 2022
19ed79e
try reverting conda environments back to scikit-image 0.18.1
grlee77 Jan 18, 2022
da9e8bc
remove version number from cucim/skimage/__init__.py
grlee77 Jan 18, 2022
177775d
remove test for removed function, structure_tensor_eigvals
grlee77 Jan 19, 2022
b9b62a6
make j_invariant tests compatible with both scikit-image 0.18.x and 0…
grlee77 Jan 19, 2022
7d6eaea
backport skimage gh-6191: fix pyramid channel_axis default
grlee77 Jan 19, 2022
e63a6bf
multichannel warning check is optional (will not occur for scikit-ima…
grlee77 Jan 19, 2022
efec6c7
allow skimage tests to run with only matplotlib-base installed
grlee77 Jan 20, 2022
07b4783
allow scikit-image 0.19 again
grlee77 Jan 20, 2022
1611f69
flake8 fix
grlee77 Jan 20, 2022
68c1fe5
revert addition of conftest.py and option for error on warning
grlee77 Jan 20, 2022
3a69d03
clean up cucim/skimage/__init__.py
grlee77 Jan 20, 2022
c8177c0
flake8
grlee77 Jan 20, 2022
8438d64
use cupy dtypes more consistently
grlee77 Jan 24, 2022
a63e083
update error message
grlee77 Jan 24, 2022
e363752
style fix
grlee77 Jan 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmarks/skimage/cucim_exposure_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ def set_args(self, dtype):
for shape in [(512, 512), (3840, 2160), (3840, 2160, 3), (192, 192, 192)]:
ndim = len(shape)

multichannel = shape[-1] in [3, 4]
channel_axis = -1 if shape[-1] in [3, 4] else None

B = MatchHistogramBench(
function_name="match_histograms",
shape=shape,
dtypes=dtypes,
fixed_kwargs=dict(multichannel=multichannel),
fixed_kwargs=dict(channel_axis=channel_axis),
var_kwargs=dict(),
module_cpu=skimage.exposure,
module_gpu=cucim.skimage.exposure,
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/skimage/cucim_feature_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def set_args(self, dtype):
continue

if function_name == "multiscale_basic_features":
fixed_kwargs["multichannel"] = shape[-1] == 3
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None
if ndim == 3 and shape[-1] != 3:
# Omit texture=True case to avoid excessive GPU memory usage
var_kwargs["texture"] = [False]
Expand Down
14 changes: 8 additions & 6 deletions benchmarks/skimage/cucim_filters_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,17 @@
var_kwargs["frequency"] = [f for f in var_kwargs["frequency"] if f >= 0.1]

if function_name == "median":
selems = []
footprints = []
ndim = len(shape)
selem_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for selem_size in [3, 5, 7, 9]:
selems.append(np.ones((selem_size,) * ndim, dtype=bool))
var_kwargs["selem"] = selems
footprint_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for footprint_size in [3, 5, 7, 9]:
footprints.append(
np.ones((footprint_size,) * ndim, dtype=bool)
)
var_kwargs["footprint"] = footprints

if function_name in ["gaussian", "unsharp_mask"]:
fixed_kwargs["multichannel"] = True if shape[-1] == 3 else False
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None

B = ImageBench(
function_name=function_name,
Expand Down
14 changes: 8 additions & 6 deletions benchmarks/skimage/cucim_measure_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,17 @@ def set_args(self, dtype):
fixed_kwargs["dst"] = (shape[0] - 32, shape[1] + 9)

if function_name == "median":
selems = []
footprints = []
ndim = len(shape)
selem_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for selem_size in [3, 5, 7, 9]:
selems.append(np.ones((selem_sizes,) * ndim, dtype=bool))
var_kwargs["selem"] = selems
footprint_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for footprint_size in [3, 5, 7, 9]:
footprints.append(
np.ones((footprint_sizes,) * ndim, dtype=bool)
)
var_kwargs["footprint"] = footprints

if function_name in ["gaussian", "unsharp_mask"]:
fixed_kwargs["multichannel"] = True if shape[-1] == 3 else False
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None

B = FiltersBench(
function_name=function_name,
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/skimage/cucim_metrics_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def set_args(self, dtype):
continue

if function_name in ["structural_similarity"]:
fixed_kwargs["multichannel"] = True if shape[-1] == 3 else False
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None

B = MetricsBench(
function_name=function_name,
Expand Down
32 changes: 17 additions & 15 deletions benchmarks/skimage/cucim_morphology_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(
self,
function_name,
shape,
selem=None,
footprint=None,
dtypes=[bool],
fixed_kwargs={},
index_str="",
Expand All @@ -32,9 +32,9 @@ def __init__(
module_gpu=cucim.skimage.morphology,
):

array_kwargs = dict(selem=selem)
if "selem" in fixed_kwargs:
raise ValueError("fixed_kwargs cannot contain 'selem'")
array_kwargs = dict(footprint=footprint)
if "footprint" in fixed_kwargs:
raise ValueError("fixed_kwargs cannot contain 'footprint'")
fixed_kwargs = copy.deepcopy(fixed_kwargs)
fixed_kwargs.update(array_kwargs)

Expand Down Expand Up @@ -110,7 +110,7 @@ def set_args(self, dtype):
all_results = pickle.load(f)
else:
all_results = pd.DataFrame()
dtypes_grey = [np.float32]
dtypes_gray = [np.float32]


for function_name, fixed_kwargs, var_kwargs, allow_nd in [
Expand Down Expand Up @@ -155,13 +155,13 @@ def set_args(self, dtype):

for connectivity in range(1, ndim + 1):
index_str = f"conn={connectivity}"
selem = ndi.generate_binary_structure(ndim, connectivity)
footprint = ndi.generate_binary_structure(ndim, connectivity)

B = BinaryMorphologyBench(
function_name=function_name,
shape=shape,
dtypes=[bool],
selem=selem,
footprint=footprint,
fixed_kwargs={},
var_kwargs=var_kwargs,
index_str=index_str,
Expand Down Expand Up @@ -213,7 +213,7 @@ def set_args(self, dtype):
("black_tophat", dict(), dict(), False, True),
# greyreconstruct.py
("reconstruction", dict(), dict(), False, True),
# selem.py
# footprints.py
# OMIT the functions from this file (each creates a structuring element)
]:

Expand All @@ -235,15 +235,17 @@ def set_args(self, dtype):
var_kwargs["frequency"] = [f for f in var_kwargs["frequency"] if f >= 0.1]

if function_name == "median":
selems = []
footprints = []
ndim = len(shape)
selem_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for selem_size in [3, 5, 7, 9]:
selems.append(np.ones((selem_sizes,) * ndim, dtype=bool))
var_kwargs["selem"] = selems
footprint_sizes = [3, 5, 7, 9] if ndim == 2 else [3, 5, 7]
for footprint_size in [3, 5, 7, 9]:
footprints.append(
np.ones((footprint_sizes,) * ndim, dtype=bool)
)
var_kwargs["footprint"] = footprints

if function_name in ["gaussian", "unsharp_mask"]:
fixed_kwargs["multichannel"] = True if shape[-1] == 3 else False
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None

if function_name == "reconstruction":
TestClass = ReconstructionBench
Expand All @@ -253,7 +255,7 @@ def set_args(self, dtype):
B = TestClass(
function_name=function_name,
shape=shape,
dtypes=dtypes_grey,
dtypes=dtypes_gray,
fixed_kwargs=fixed_kwargs,
var_kwargs=var_kwargs,
module_cpu=skimage.morphology,
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/skimage/cucim_restoration_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def set_args(self, dtype):
continue

if function_name == "denoise_tv_chambolle":
fixed_kwargs["multichannel"] = shape[-1] == 3
fixed_kwargs["channel_axis"] = -1 if shape[-1] == 3 else None

if function_name == "calibrate_denoiser":
denoise_class = CalibratedDenoiseBench
Expand All @@ -152,7 +152,7 @@ def set_args(self, dtype):
# deconvolution.py
("wiener", dict(balance=100.0), dict(), False, False),
("unsupervised_wiener", dict(), dict(), False, False),
("richardson_lucy", dict(), dict(iterations=[5]), False, True),
("richardson_lucy", dict(), dict(num_iter=[5]), False, True),
]:

for shape in [(512, 512), (3840, 2160), (3840, 2160, 3), (192, 192, 192)]:
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/skimage/cucim_segmentation_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,14 @@ def set_args(self, dtype):
(
"morphological_geodesic_active_contour",
dict(),
dict(iterations=[16], init_level_set=["checkerboard", "disk"]),
dict(num_iter=[16], init_level_set=["checkerboard", "disk"]),
False,
False,
),
(
"morphological_chan_vese",
dict(),
dict(iterations=[16], init_level_set=["checkerboard", "disk"]),
dict(num_iter=[16], init_level_set=["checkerboard", "disk"]),
False,
False,
),
Expand Down
13 changes: 10 additions & 3 deletions benchmarks/skimage/cucim_transform_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
True,
True,
), # scale handled in loop below
(
"resize_local_mean",
dict(preserve_range=True),
{},
True,
True,
), # scale handled in loop below
(
"rescale",
dict(preserve_range=True),
Expand Down Expand Up @@ -87,15 +94,15 @@
ndim_spatial = ndim - 1 if shape[-1] == 3 else ndim

if function_name in ["rescale", "warp_polar", "pyramid_gaussian", "pyramid_laplacian"]:
fixed_kwargs["multichannel"] = ndim_spatial < ndim
fixed_kwargs["channel_axis"] = -1 if ndim_spatial < ndim else None

function_is_generator = function_name in ["pyramid_gaussian", "pyramid_laplacian"]

if function_name in ["rescale", "resize"]:
if function_name in ["rescale", "resize", "resize_local_mean"]:
scales = [0.75, 1.25]
if function_name == "rescale":
var_kwargs["scale"] = [(s,) * ndim_spatial for s in scales]
elif function_name == "resize":
elif function_name.startswith("resize"):
out_shapes = [[int(s_ * s) for s_ in shape] for s in scales]
if ndim_spatial < ndim:
# don't resize along channels dimension
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ channels:
- conda-forge
dependencies:
- cupy>=9
- scikit-image=0.18.1
- scikit-image>=0.18.1
- openslide
- zlib
- jpeg
Expand Down
4 changes: 2 additions & 2 deletions conda/recipes/cucim/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ requirements:
- cupy >=9,<11.0.0a0
- numpy 1.18
- scipy
- scikit-image 0.18.1
- scikit-image >=0.18.1,<0.20.0a0
run:
- {{ pin_compatible('cudatoolkit', max_pin='x', min_pin='x') }}
- python {{ python_version }}.*
Expand All @@ -45,7 +45,7 @@ requirements:
- cupy >=9,<11.0.0a0
- {{ pin_compatible('numpy') }}
- scipy
- scikit-image 0.18.1
- scikit-image >=0.18.1,<0.20.0a0
# - openslide # skipping here but benchmark binary would needs openslide library

tests: # [linux64]
Expand Down
6 changes: 4 additions & 2 deletions python/cucim/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ ignore =
W504
# whitespace before :
E203
# f-string is missing placeholders
F541
exclude = .tox,.eggs,ci/templates,build,dist,.git,__pycache__,doc/conf.py,doc/sphinxext,build,dist,__init__.py
per-file-ignores =
setup.py:F821
versioneer.py:W605
src/localtest.py:E127
src/cucim/skimage/__init__.py:F401
src/cucim/skimage/util/tests/test_shape.py:E201,E202,E241
src/cucim/skimage/measure/tests/test_block.py:E201,E202,E241
src/cucim/skimage/transform/tests/test_warps.py:E201,E202,E241,W605
src/cucim/skimage/transform/_geometric.py:E201,E202,E241
src/cucim/skimage/transform/tests/test_warps.py:E201,E202,E241,W605
src/cucim/skimage/util/tests/test_shape.py:E201,E202,E241
src/cucim/core/operations/expose/transform.py:F401

[tool:pytest]
Expand Down
19 changes: 19 additions & 0 deletions python/cucim/src/cucim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,41 @@
# If cucim.clara package is imported first, you may see the following error when running on CUDA 10.x (#44)
# python3: Relink `/usr/lib/x86_64-linux-gnu/libnccl.so.2.8.3' with `/lib/x86_64-linux-gnu/librt.so.1' for IFUNC symbol `clock_gettime'
# Segmentation fault
submodules = []
submod_attrs = {}

try:
import cupy
_is_cupy_available = True
submodules += ['core', 'skimage']
except ImportError:
pass

try:
from .clara import CuImage, __version__, cli
_is_clara_available = True
submodules += ['clara']
submod_attrs['clara'] = ['CuImage', 'cli']
except ImportError:
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
del _version


from .skimage._shared import lazy

__getattr__, __lazy_dir__, _ = lazy.attach(
__name__,
submodules,
submod_attrs,
)


def __dir__():
return __lazy_dir__() + ['__version__', 'is_available']


def is_available(module_name: str = "") -> bool:
"""Check if a specific module is available.

Expand Down
38 changes: 34 additions & 4 deletions python/cucim/src/cucim/skimage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
-----------
color
Color space conversion.
data
Test images and example data.
exposure
Image intensity adjustment, e.g., histogram equalization, etc.
feature
Expand Down Expand Up @@ -58,7 +60,35 @@

"""

# All skimage root imports go here
from .util.dtype import (dtype_limits, img_as_bool, img_as_float,
img_as_float32, img_as_float64, img_as_int,
img_as_ubyte, img_as_uint)
from ._shared import lazy

submodules = [
'color',
'data',
'exposure',
'feature',
'filters',
'measure',
'metrics',
'morphology',
'registration',
'restoration',
'segmentation',
'transform',
'util',
]


__getattr__, __lazy_dir__, _ = lazy.attach(
__name__,
submodules,
{'util.dtype': ['dtype_limits', 'img_as_bool', 'img_as_float',
'img_as_float32', 'img_as_float64', 'img_as_int',
'img_as_ubyte', 'img_as_uint'],
'util.lookfor': ['lookfor'],
}
)


def __dir__():
return __lazy_dir__()
11 changes: 11 additions & 0 deletions python/cucim/src/cucim/skimage/_shared/_dependency_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .version_requirements import is_installed

has_mpl = is_installed("matplotlib", ">=3.0.3")
if has_mpl:
try:
# will fail with
# ImportError: Failed to import any qt binding
# if only matplotlib-base is installed
from matplotlib import pyplot # noqa
except ImportError:
has_mpl = False
Loading