From 8042dc2cd9df32cf33fa271e3c5261490c2fc187 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 21 Jul 2014 19:48:27 -0500 Subject: [PATCH 1/3] Fix holes in lab conversions and add tests Fix holes in lab conversions and add tests Fix holes in lab conversions and add tests Add saturation protection and error raising by default Trigger travis rebuild after error Raise warning by default, change parameter name --- skimage/color/colorconv.py | 17 ++++++++++++++++- skimage/color/tests/test_colorconv.py | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 8e8c3b41..938bb849 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -53,6 +53,7 @@ References from __future__ import division +from warnings import warn import numpy as np from scipy import linalg from ..util import dtype @@ -552,6 +553,8 @@ def xyz2rgb(xyz): mask = arr > 0.0031308 arr[mask] = 1.055 * np.power(arr[mask], 1 / 2.4) - 0.055 arr[~mask] *= 12.92 + arr[arr < 0] = 0 + arr[arr > 1] = 1 return arr @@ -808,7 +811,7 @@ def xyz2lab(xyz, illuminant="D65", observer="2"): return np.concatenate([x[..., np.newaxis] for x in [L, a, b]], axis=-1) -def lab2xyz(lab, illuminant="D65", observer="2"): +def lab2xyz(lab, illuminant="D65", observer="2", error_on_invalid=False): """CIE-LAB to XYZcolor space conversion. Parameters @@ -819,6 +822,9 @@ def lab2xyz(lab, illuminant="D65", observer="2"): The name of the illuminant (the function is NOT case sensitive). observer : {"2", "10"}, optional The aperture angle of the observer. + error_on_invalid: optional, bool + If true, raise ValueError when pixels are outside the valid gamut. + Otherwise, raise a warning. Returns ------- @@ -854,6 +860,15 @@ def lab2xyz(lab, illuminant="D65", observer="2"): x = (a / 500.) + y z = y - (b / 200.) + if np.any(z < 0): + invalid = np.nonzero(z < 0) + msg = 'Color data out of range: Z < 0 in %s pixels' % invalid[0].size + if error_on_invalid: + raise ValueError(msg) + else: + warn(msg) + z[invalid] = 0 + out = np.dstack([x, y, z]) mask = out > 0.2068966 diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 094873ae..2b435f67 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -11,6 +11,7 @@ Authors :license: modified BSD """ +from __future__ import division import os.path import numpy as np @@ -372,6 +373,29 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(luv2rgb(rgb2luv(img_rgb)), img_rgb) + def test_lab_rgb_outlier(self): + lab_array = np.ones((3, 1, 3)) + lab_array[0] = [50, -12, 85] + lab_array[1] = [50, 12, -85] + lab_array[2] = [90, -4, -47] + rgb_array = np.array([[[0.501, 0.481, 0]], + [[0, 0.482, 1.]], + [[0.578, 0.914, 1.]], + ]) + assert_almost_equal(lab2rgb(lab_array), rgb_array, decimal=3) + + def test_lab_full_gamut(self): + a, b = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100)) + L = np.ones(a.shape) + lab = np.dstack((L, a, b)) + assert_raises(ValueError, lambda: lab2xyz(lab)) + multipliers = [0, 10, 50, 100] + nan_percents = [12, 9, 0, 0] + for (l, perc) in zip(multipliers, nan_percents): + lab[:, :, 0] = l + out = lab2xyz(lab) + assert_equal(int(np.isnan(out).sum() / out.size * 100), perc) + def test_lab_lch_roundtrip(self): rgb = img_as_float(self.img_rgb) lab = rgb2lab(rgb) From 1af0edbb02fff4779aec66fb9a10be6c6e0b3b1a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 5 Feb 2015 21:53:57 -0600 Subject: [PATCH 2/3] Update test to reflect new error behaviour --- skimage/color/tests/test_colorconv.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 2b435f67..a80ef43e 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -388,13 +388,11 @@ class TestColorconv(TestCase): a, b = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100)) L = np.ones(a.shape) lab = np.dstack((L, a, b)) - assert_raises(ValueError, lambda: lab2xyz(lab)) - multipliers = [0, 10, 50, 100] - nan_percents = [12, 9, 0, 0] - for (l, perc) in zip(multipliers, nan_percents): - lab[:, :, 0] = l - out = lab2xyz(lab) - assert_equal(int(np.isnan(out).sum() / out.size * 100), perc) + assert_raises(ValueError, lambda: lab2xyz(lab, error_on_invalid=True)) + for value in [0, 10, 20]: + lab[:, :, 0] = value + with expected_warnings(['Color data out of range']): + lab2xyz(lab) def test_lab_lch_roundtrip(self): rgb = img_as_float(self.img_rgb) From 371d46ecf56c238761e9f7baf2bd36e915015b21 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2015 14:37:11 -0600 Subject: [PATCH 3/3] Remove unnecessary option and update test --- skimage/color/colorconv.py | 15 +++++---------- skimage/color/tests/test_colorconv.py | 1 - 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 938bb849..74a9a520 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -811,7 +811,7 @@ def xyz2lab(xyz, illuminant="D65", observer="2"): return np.concatenate([x[..., np.newaxis] for x in [L, a, b]], axis=-1) -def lab2xyz(lab, illuminant="D65", observer="2", error_on_invalid=False): +def lab2xyz(lab, illuminant="D65", observer="2"): """CIE-LAB to XYZcolor space conversion. Parameters @@ -822,9 +822,6 @@ def lab2xyz(lab, illuminant="D65", observer="2", error_on_invalid=False): The name of the illuminant (the function is NOT case sensitive). observer : {"2", "10"}, optional The aperture angle of the observer. - error_on_invalid: optional, bool - If true, raise ValueError when pixels are outside the valid gamut. - Otherwise, raise a warning. Returns ------- @@ -838,6 +835,8 @@ def lab2xyz(lab, illuminant="D65", observer="2", error_on_invalid=False): ValueError If either the illuminant or the observer angle are not supported or unknown. + UserWarning + If any of the pixels are invalid (Z < 0). Notes @@ -862,12 +861,8 @@ def lab2xyz(lab, illuminant="D65", observer="2", error_on_invalid=False): if np.any(z < 0): invalid = np.nonzero(z < 0) - msg = 'Color data out of range: Z < 0 in %s pixels' % invalid[0].size - if error_on_invalid: - raise ValueError(msg) - else: - warn(msg) - z[invalid] = 0 + warn('Color data out of range: Z < 0 in %s pixels' % invalid[0].size) + z[invalid] = 0 out = np.dstack([x, y, z]) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index a80ef43e..c9febb5f 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -388,7 +388,6 @@ class TestColorconv(TestCase): a, b = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100)) L = np.ones(a.shape) lab = np.dstack((L, a, b)) - assert_raises(ValueError, lambda: lab2xyz(lab, error_on_invalid=True)) for value in [0, 10, 20]: lab[:, :, 0] = value with expected_warnings(['Color data out of range']):