From a1127d4601c10e71122a0f96d86e2c2afc73e402 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 2 Feb 2014 17:40:52 -0600 Subject: [PATCH 1/7] Add shaded polygon to lineprofile output --- skimage/viewer/plugins/lineprofile.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index ea44981a..03d83277 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -147,13 +147,28 @@ class LineProfile(PlotPlugin): ------- line_image : (M, N) uint8 array, same shape as image An array of 0s with the scanned line set to 255. + If the linewidth of the line tool is greater than 1, + sets the values within the profiled polygon to 128. scan : (P,) or (P, 3) array of int or float The line scan values across the image. """ (x1, y1), (x2, y2) = self.line_tool.end_points line_image = np.zeros(self.image_viewer.original_image.shape[:2], np.uint8) - rr, cc = draw.line(y1, x1, y2, x2) + w = self.line_tool.linewidth + if w > 1: + a = np.arctan2((y2 - y1), (x2 - x1)) + xinc = abs(int(np.cos(a) * w / 2)) + 1 + yinc = abs(int(np.sin(a) * w / 2)) + 1 + if ((x2 > x1) and (y2 > y1)) or ((x2 < x1) and (y2 < y1)): + xinc *= -1 + xx = np.array([x1 + xinc, x1 - xinc, x2 - xinc, x2 + xinc], + dtype=np.int) + yy = np.array([y1 + yinc, y1 - yinc, y2 - yinc, y2 + yinc], + dtype=np.int) + rrp, ccp = draw.polygon(yy, xx, line_image.shape) + line_image[rrp, ccp] = 128 + rr, cc = draw.line(int(y1), int(x1), int(y2), int(x2)) line_image[rr, cc] = 255 return line_image, self.scan_data From 2893fbae0e227820db4dbe92393c7103ce52ad09 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 2 Feb 2014 21:52:50 -0600 Subject: [PATCH 2/7] Refactor perp_lines behavior --- skimage/measure/profile.py | 52 +++++++++++++++++++-------- skimage/viewer/plugins/lineprofile.py | 23 +++++------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/skimage/measure/profile.py b/skimage/measure/profile.py index 98038e06..7cb6a135 100644 --- a/skimage/measure/profile.py +++ b/skimage/measure/profile.py @@ -50,6 +50,43 @@ def profile_line(img, src, dst, linewidth=1, The destination point is included in the profile, in contrast to standard numpy indexing. """ + perp_lines = _line_profile_coordinates(src, dst, linewidth=linewidth) + if img.ndim == 3: + pixels = [nd.map_coordinates(img[..., i], perp_lines, + order=order, mode=mode, cval=cval) + for i in range(img.shape[2])] + pixels = np.transpose(np.asarray(pixels), (1, 2, 0)) + else: + pixels = nd.map_coordinates(img, perp_lines, + order=order, mode=mode, cval=cval) + intensities = pixels.mean(axis=1) + + return intensities + + +def _line_profile_coordinates(src, dst, linewidth=1): + """Return the coordinates of the profile of an image along a scan line. + + Parameters + ---------- + src : 2-tuple of numeric scalar (float or int) + The start point of the scan line. + dst : 2-tuple of numeric scalar (float or int) + The end point of the scan line. + linewidth : int, optional + Width of the scan, perpendicular to the line + + Returns + ------- + return_value : array + The coordinates of the profile along the scan line. The length of the profile is the ceil of the computed length of the scan line. The array is of the form (2, length, linewidth). + + Notes + ----- + This is a utility method meant to be used internally by skimage functions. + The destination point is included in the profile, in contrast to + standard numpy indexing. + """ src_row, src_col = src = np.asarray(src, dtype=float) dst_row, dst_col = dst = np.asarray(dst, dtype=float) d_row, d_col = dst - src @@ -70,18 +107,5 @@ def profile_line(img, src, dst, linewidth=1, linewidth) for row_i in line_row]) perp_cols = np.array([np.linspace(col_i - col_width, col_i + col_width, linewidth) for col_i in line_col]) - perp_lines = np.array([perp_rows, perp_cols]) - - if img.ndim == 3: - pixels = [nd.map_coordinates(img[..., i], perp_lines, - order=order, mode=mode, cval=cval) - for i in range(img.shape[2])] - pixels = np.transpose(np.asarray(pixels), (1, 2, 0)) - else: - pixels = nd.map_coordinates(img, perp_lines, - order=order, mode=mode, cval=cval) - - intensities = pixels.mean(axis=1) - - return intensities + return np.array([perp_rows, perp_cols]) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 03d83277..03b5eaaf 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -152,23 +152,16 @@ class LineProfile(PlotPlugin): scan : (P,) or (P, 3) array of int or float The line scan values across the image. """ - (x1, y1), (x2, y2) = self.line_tool.end_points + end_points = self.line_tool.end_points line_image = np.zeros(self.image_viewer.original_image.shape[:2], np.uint8) - w = self.line_tool.linewidth - if w > 1: - a = np.arctan2((y2 - y1), (x2 - x1)) - xinc = abs(int(np.cos(a) * w / 2)) + 1 - yinc = abs(int(np.sin(a) * w / 2)) + 1 - if ((x2 > x1) and (y2 > y1)) or ((x2 < x1) and (y2 < y1)): - xinc *= -1 - xx = np.array([x1 + xinc, x1 - xinc, x2 - xinc, x2 + xinc], - dtype=np.int) - yy = np.array([y1 + yinc, y1 - yinc, y2 - yinc, y2 + yinc], - dtype=np.int) - rrp, ccp = draw.polygon(yy, xx, line_image.shape) - line_image[rrp, ccp] = 128 - rr, cc = draw.line(int(y1), int(x1), int(y2), int(x2)) + width = self.line_tool.linewidth + if width > 1: + rp, cp = measure.profile._line_profile_coordinates( + *end_points[:, ::-1], linewidth=width) + line_image[rp.astype(int), cp.astype(int)] = 128 + (x1, y1), (x2, y2) = end_points.astype(int) + rr, cc = draw.line(y1, x1, y2, x2) line_image[rr, cc] = 255 return line_image, self.scan_data From 07f64b443ad10b0bbb3740c1eb3e7a0a7bcfd001 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 2 Feb 2014 22:22:04 -0600 Subject: [PATCH 3/7] Add test for LineProfiler tool --- skimage/viewer/tests/test_viewer.py | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 skimage/viewer/tests/test_viewer.py diff --git a/skimage/viewer/tests/test_viewer.py b/skimage/viewer/tests/test_viewer.py new file mode 100644 index 00000000..5e95daab --- /dev/null +++ b/skimage/viewer/tests/test_viewer.py @@ -0,0 +1,43 @@ +import skimage.data as data +from skimage.viewer import ImageViewer +from numpy.testing import assert_equal, assert_allclose + + +def setup_line_profile(image): + from skimage.viewer.plugins.lineprofile import LineProfile + viewer = ImageViewer(image) + plugin = LineProfile() + viewer += plugin + return plugin + + +def test_line_profile(): + """ Test a line profile using an ndim=2 image""" + plugin = setup_line_profile(data.camera()) + line_image, scan_data = plugin.output() + for inp in [line_image.nonzero()[0].size, + line_image.sum() / line_image.max(), + scan_data.size]: + assert_equal(inp, 172) + assert_equal(line_image.shape, (512, 512)) + assert_equal(scan_data.max(), 234.0) + assert_allclose(scan_data.mean(), 71.726744186046517) + + +def test_line_profile_rgb(): + """ Test a line profile using an ndim=3 image""" + plugin = setup_line_profile(data.chelsea()) + for i in range(6): + plugin.line_tool._thicken_scan_line() + line_image, scan_data = plugin.output() + assert_equal(line_image[line_image == 128].size, 906) + assert_equal(line_image[line_image == 255].size, 151) + assert_equal(line_image.shape, (300, 451)) + assert_equal(scan_data.shape, (151, 3)) + assert_allclose(scan_data.max(), 196.85714285714286) + assert_allclose(scan_data.mean(), 111.17029328287606) + + +if __name__ == "__main__": + from numpy.testing import run_module_suite + run_module_suite() From c2fd471c5589c3a94c4477ffae1a8befef6b93fc Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 4 Feb 2014 21:59:39 -0600 Subject: [PATCH 4/7] Fix aliasing and update docstring --- skimage/measure/profile.py | 5 +++-- skimage/viewer/plugins/lineprofile.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/skimage/measure/profile.py b/skimage/measure/profile.py index 7cb6a135..fef3fac8 100644 --- a/skimage/measure/profile.py +++ b/skimage/measure/profile.py @@ -78,8 +78,9 @@ def _line_profile_coordinates(src, dst, linewidth=1): Returns ------- - return_value : array - The coordinates of the profile along the scan line. The length of the profile is the ceil of the computed length of the scan line. The array is of the form (2, length, linewidth). + coords : array, shape (2, N, C), float + The coordinates of the profile along the scan line. The length of the + profile is the ceil of the computed length of the scan line. Notes ----- diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 03b5eaaf..2affc444 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -159,7 +159,11 @@ class LineProfile(PlotPlugin): if width > 1: rp, cp = measure.profile._line_profile_coordinates( *end_points[:, ::-1], linewidth=width) - line_image[rp.astype(int), cp.astype(int)] = 128 + # the points are aliased, so create a polygon using the corners + yp = np.rint(rp[[0, 0, -1, -1],[0, -1, -1, 0]]).astype(int) + xp = np.rint(cp[[0, 0, -1, -1],[0, -1, -1, 0]]).astype(int) + rp, cp = draw.polygon(yp, xp, line_image.shape) + line_image[rp, cp] = 128 (x1, y1), (x2, y2) = end_points.astype(int) rr, cc = draw.line(y1, x1, y2, x2) line_image[rr, cc] = 255 From 8a261242c18db89c326f94989f762435c2b92233 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 4 Feb 2014 22:04:37 -0600 Subject: [PATCH 5/7] Update test now that aliasing has been removed --- skimage/viewer/tests/test_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/tests/test_viewer.py b/skimage/viewer/tests/test_viewer.py index 5e95daab..84bae6bb 100644 --- a/skimage/viewer/tests/test_viewer.py +++ b/skimage/viewer/tests/test_viewer.py @@ -30,7 +30,7 @@ def test_line_profile_rgb(): for i in range(6): plugin.line_tool._thicken_scan_line() line_image, scan_data = plugin.output() - assert_equal(line_image[line_image == 128].size, 906) + assert_equal(line_image[line_image == 128].size, 750) assert_equal(line_image[line_image == 255].size, 151) assert_equal(line_image.shape, (300, 451)) assert_equal(scan_data.shape, (151, 3)) From 864a1897211b4e3219cc00810dc31761daa45850 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 8 Feb 2014 12:24:06 -0600 Subject: [PATCH 6/7] Use division consistently between PY2 and PY3 --- skimage/viewer/plugins/lineprofile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 2affc444..e7c04d87 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -1,3 +1,4 @@ +from __future__ import division import warnings import numpy as np From df4746d4f993d59afdda974d9cd7a97a56741228 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 8 Feb 2014 12:24:32 -0600 Subject: [PATCH 7/7] Update test to use floating point images and with updated values --- skimage/viewer/tests/test_viewer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/skimage/viewer/tests/test_viewer.py b/skimage/viewer/tests/test_viewer.py index 84bae6bb..6b0fd48f 100644 --- a/skimage/viewer/tests/test_viewer.py +++ b/skimage/viewer/tests/test_viewer.py @@ -1,3 +1,4 @@ +import skimage import skimage.data as data from skimage.viewer import ImageViewer from numpy.testing import assert_equal, assert_allclose @@ -5,7 +6,7 @@ from numpy.testing import assert_equal, assert_allclose def setup_line_profile(image): from skimage.viewer.plugins.lineprofile import LineProfile - viewer = ImageViewer(image) + viewer = ImageViewer(skimage.img_as_float(image)) plugin = LineProfile() viewer += plugin return plugin @@ -20,8 +21,8 @@ def test_line_profile(): scan_data.size]: assert_equal(inp, 172) assert_equal(line_image.shape, (512, 512)) - assert_equal(scan_data.max(), 234.0) - assert_allclose(scan_data.mean(), 71.726744186046517) + assert_allclose(scan_data.max(), 0.9139, rtol=1e-3) + assert_allclose(scan_data.mean(), 0.2828, rtol=1e-3) def test_line_profile_rgb(): @@ -30,12 +31,12 @@ def test_line_profile_rgb(): for i in range(6): plugin.line_tool._thicken_scan_line() line_image, scan_data = plugin.output() - assert_equal(line_image[line_image == 128].size, 750) + assert_equal(line_image[line_image == 128].size, 755) assert_equal(line_image[line_image == 255].size, 151) assert_equal(line_image.shape, (300, 451)) - assert_equal(scan_data.shape, (151, 3)) - assert_allclose(scan_data.max(), 196.85714285714286) - assert_allclose(scan_data.mean(), 111.17029328287606) + assert_equal(scan_data.shape, (152, 3)) + assert_allclose(scan_data.max(), 0.772, rtol=1e-3) + assert_allclose(scan_data.mean(), 0.4355, rtol=1e-3) if __name__ == "__main__":