From e358644271224bd82201e7e657a63924b94e1055 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 14:10:29 -0600 Subject: [PATCH 1/9] Add flexibility to the ImageInspectorToolOverlay so it's easier to control what is displayed. --- chaco/tools/image_inspector_tool.py | 35 +++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/chaco/tools/image_inspector_tool.py b/chaco/tools/image_inspector_tool.py index f13c58ebd..79f25ebf3 100644 --- a/chaco/tools/image_inspector_tool.py +++ b/chaco/tools/image_inspector_tool.py @@ -100,10 +100,30 @@ class ImageInspectorOverlay(TextBoxOverlay): # tool's location, or whether it should be forced to be hidden or visible. visibility = Enum("auto", True, False) + # Private interface ------------------------------------------------------- + + def _build_text_from_event(self, d): + """ Create a formatted string to display from the mouse event data. + """ + newstring = "" + if 'indices' in d: + newstring += '(%d, %d)\n' % d['indices'] + if 'color_value' in d: + newstring += "(%d, %d, %d)\n" % tuple( + map(int, d['color_value'][:3])) + if 'data_value' in d: + newstring += str(d['data_value']) + + return newstring + + # Traits listeners -------------------------------------------------------- + def _image_inspector_changed(self, old, new): if old: - old.on_trait_event(self._new_value_updated, 'new_value', remove=True) - old.on_trait_change(self._tool_visible_changed, "visible", remove=True) + old.on_trait_event(self._new_value_updated, 'new_value', + remove=True) + old.on_trait_change(self._tool_visible_changed, "visible", + remove=True) if new: new.on_trait_event(self._new_value_updated, 'new_value') new.on_trait_change(self._tool_visible_changed, "visible") @@ -123,16 +143,7 @@ def _new_value_updated(self, event): else: self.alternate_position = None - d = event - newstring = "" - if 'indices' in d: - newstring += '(%d, %d)' % d['indices'] + '\n' - if 'color_value' in d: - newstring += "(%d, %d, %d)" % tuple(map(int,d['color_value'][:3])) + "\n" - if 'data_value' in d: - newstring += str(d['data_value']) - - self.text = newstring + self.text = self._build_text_from_event(event) self.component.request_redraw() def _visible_changed(self): From 9350c2bdf659e8605e5cc94acc70dfbd44bf5989 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 14:10:50 -0600 Subject: [PATCH 2/9] Example tweak. --- examples/demo/basic/image_inspector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/basic/image_inspector.py b/examples/demo/basic/image_inspector.py index a04ef4ca0..bf9fd12dc 100644 --- a/examples/demo/basic/image_inspector.py +++ b/examples/demo/basic/image_inspector.py @@ -57,7 +57,7 @@ def _create_plot_component():# Create a scalar field to colormap plot.tools.append(PanTool(plot)) zoom = ZoomTool(component=plot, tool_mode="box", always_on=False) plot.overlays.append(zoom) - imgtool = ImageInspectorTool(img_plot) + imgtool = ImageInspectorTool(component=img_plot) img_plot.tools.append(imgtool) overlay = ImageInspectorOverlay(component=img_plot, image_inspector=imgtool, bgcolor="white", border_visible=True) From fd5d9a5ce0f20e4b22e8304b3c1639385d9a0729 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 21:14:56 -0600 Subject: [PATCH 3/9] Handle color_value being an int. --- chaco/tools/image_inspector_tool.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/chaco/tools/image_inspector_tool.py b/chaco/tools/image_inspector_tool.py index 79f25ebf3..f1b04a63b 100644 --- a/chaco/tools/image_inspector_tool.py +++ b/chaco/tools/image_inspector_tool.py @@ -67,15 +67,15 @@ def normal_mouse_move(self, event): if hasattr(plot, "_cached_mapped_image") and \ plot._cached_mapped_image is not None: self.new_value = \ - dict(indices=ndx, - data_value=image_data.data[y_index, x_index], - color_value=plot._cached_mapped_image[y_index, - x_index]) + {"indices": ndx, + "data_value": image_data.data[y_index, x_index], + "color_value": plot._cached_mapped_image[y_index, + x_index]} else: self.new_value = \ - dict(indices=ndx, - color_value=image_data.data[y_index, x_index]) + {"indices": ndx, + "color_value": image_data.data[y_index, x_index]} self.last_mouse_position = (event.x, event.y) return @@ -107,12 +107,18 @@ def _build_text_from_event(self, d): """ newstring = "" if 'indices' in d: - newstring += '(%d, %d)\n' % d['indices'] + newstring += '(%d, %d)' % d['indices'] if 'color_value' in d: - newstring += "(%d, %d, %d)\n" % tuple( - map(int, d['color_value'][:3])) + if isinstance(d['color_value'], int): + # Gray scale image: + newstring += "\n%d" % d['color_value'] + else: + # RGB(A) image: + newstring += "\n(%d, %d, %d)" % tuple( + map(int, d['color_value'][:3])) + if 'data_value' in d: - newstring += str(d['data_value']) + newstring += "\n{}".format(d['data_value']) return newstring From bc3484139a06492b8182e47f318a76d57534b967 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 21:15:11 -0600 Subject: [PATCH 4/9] Add tests. --- .../tools/tests/image_inspector_test_case.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 chaco/tools/tests/image_inspector_test_case.py diff --git a/chaco/tools/tests/image_inspector_test_case.py b/chaco/tools/tests/image_inspector_test_case.py new file mode 100644 index 000000000..1c5aeef3c --- /dev/null +++ b/chaco/tools/tests/image_inspector_test_case.py @@ -0,0 +1,189 @@ +""" Tests for the ScatterInspector Chaco tool +""" + +from unittest import TestCase +import numpy as np +from numpy.testing import assert_array_equal + +from chaco.api import ArrayPlotData, Plot +from chaco.tools.api import ImageInspectorTool, ImageInspectorOverlay +from enable.testing import EnableTestAssistant +from traits.testing.unittest_tools import UnittestTools + + +def create_image_plot(img_values, **kwargs): + data = ArrayPlotData(img=img_values) + plot = Plot(data) + plot.img_plot("img", **kwargs) + return plot + + +class BaseImageInspectorTool(EnableTestAssistant, UnittestTools): + def setUp(self): + # Control the pixel size of the plot to know where the tiles are: + self.plot.bounds = [100, 100] + self.plot._window = self.create_mock_window() + renderer = self.plot.plots["plot0"][0] + self.tool = ImageInspectorTool(component=renderer) + self.overlay = ImageInspectorOverlay(component=renderer, + image_inspector=self.tool) + self.plot.active_tool = self.tool + self.plot.do_layout() + + self.insp_event = None + + def tearDown(self): + del self.tool + del self.plot + + def test_mouse_move_records_last_position(self): + tool = self.tool + + self.assertEqual(tool.last_mouse_position, ()) + + self.mouse_move(tool, 0, 0) + self.assertEqual(tool.last_mouse_position, (0, 0)) + + self.mouse_move(tool, 10, 10) + self.assertEqual(tool.last_mouse_position, (10, 10)) + + self.mouse_leave(tool, 1000, 1000) + self.assertEqual(tool.last_mouse_position, (10, 10)) + + # Helper methods ---------------------------------------------------------- + + def store_inspector_event(self, event): + self.insp_event = event + + +class TestImageInspectorToolGray(BaseImageInspectorTool, TestCase): + """ Tests for the ImageInspector tool with a gray scale image + """ + + def setUp(self): + values = np.arange(4).reshape(2, 2) + self.plot = create_image_plot(values) + super(TestImageInspectorToolGray, self).setUp() + + def test_mouse_move_collect_image_info(self): + tool = self.tool + + # Add a listener to catch the emitted event: + tool.on_trait_change(self.store_inspector_event, "new_value") + try: + self.assertIsNone(self.insp_event) + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + self.mouse_move(tool, 0, 0) + self.assertEqual(self.insp_event["color_value"], 0) + self.assertEqual(self.insp_event["indices"], (0, 0)) + + self.assertEqual(self.overlay.text, '(0, 0)\n0') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move within the same tile: + self.mouse_move(tool, 90, 0) + self.assertEqual(self.insp_event["color_value"], 1) + self.assertEqual(self.insp_event["indices"], (1, 0)) + + self.assertEqual(self.overlay.text, '(1, 0)\n1') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitDoesNotChange(self.overlay, "text"): + # Move within the same tile: + self.mouse_move(tool, 91, 0) + self.assertEqual(self.insp_event["color_value"], 1) + self.assertEqual(self.insp_event["indices"], (1, 0)) + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move to another value in the image: + self.mouse_move(tool, 0, 90) + self.assertEqual(self.insp_event["color_value"], 2) + self.assertEqual(self.insp_event["indices"], (0, 1)) + + self.assertEqual(self.overlay.text, '(0, 1)\n2') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move within the same tile: + self.mouse_move(tool, 90, 90) + self.assertEqual(self.insp_event["color_value"], 3) + self.assertEqual(self.insp_event["indices"], (1, 1)) + + self.assertEqual(self.overlay.text, '(1, 1)\n3') + + finally: + tool.on_trait_change(self.store_inspector_event, "new_value", + remove=True) + + +class TestImageInspectorToolRGB(BaseImageInspectorTool, TestCase): + """ Tests for the ImageInspector tool with an RGB image. + """ + + def setUp(self): + values = np.arange(12).reshape(2, 2, 3) + self.plot = create_image_plot(values) + super(TestImageInspectorToolRGB, self).setUp() + + def test_mouse_move_collect_image_info(self): + tool = self.tool + + # Add a listener to catch the emitted event: + tool.on_trait_change(self.store_inspector_event, "new_value") + try: + self.assertIsNone(self.insp_event) + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + self.mouse_move(tool, 0, 0) + assert_array_equal(self.insp_event["color_value"], + np.array([0, 1, 2])) + self.assertEqual(self.insp_event["indices"], (0, 0)) + + self.assertEqual(self.overlay.text, '(0, 0)\n(0, 1, 2)') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move within the same tile: + self.mouse_move(tool, 90, 0) + assert_array_equal(self.insp_event["color_value"], + np.array([3, 4, 5])) + self.assertEqual(self.insp_event["indices"], (1, 0)) + + self.assertEqual(self.overlay.text, '(1, 0)\n(3, 4, 5)') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitDoesNotChange(self.overlay, "text"): + # Move within the same tile: + self.mouse_move(tool, 91, 0) + assert_array_equal(self.insp_event["color_value"], + np.array([3, 4, 5])) + self.assertEqual(self.insp_event["indices"], (1, 0)) + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move to another value in the image: + self.mouse_move(tool, 0, 90) + assert_array_equal(self.insp_event["color_value"], + np.array([6, 7, 8])) + self.assertEqual(self.insp_event["indices"], (0, 1)) + + self.assertEqual(self.overlay.text, '(0, 1)\n(6, 7, 8)') + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay, "text", 1): + # Move within the same tile: + self.mouse_move(tool, 90, 90) + assert_array_equal(self.insp_event["color_value"], + np.array([9, 10, 11])) + self.assertEqual(self.insp_event["indices"], (1, 1)) + + self.assertEqual(self.overlay.text, '(1, 1)\n(9, 10, 11)') + + finally: + tool.on_trait_change(self.store_inspector_event, "new_value", + remove=True) From 601d416d9dc4950967823843a12fc92c74cb6afd Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 21:29:52 -0600 Subject: [PATCH 5/9] Add test for custom overlay text building. --- .../tools/tests/image_inspector_test_case.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/chaco/tools/tests/image_inspector_test_case.py b/chaco/tools/tests/image_inspector_test_case.py index 1c5aeef3c..c38eb6b3c 100644 --- a/chaco/tools/tests/image_inspector_test_case.py +++ b/chaco/tools/tests/image_inspector_test_case.py @@ -18,6 +18,11 @@ def create_image_plot(img_values, **kwargs): return plot +class CustomImageInspectorOverlay(ImageInspectorOverlay): + def _build_text_from_event(self, event): + return 'Position: (%d, %d)' % event['indices'] + + class BaseImageInspectorTool(EnableTestAssistant, UnittestTools): def setUp(self): # Control the pixel size of the plot to know where the tiles are: @@ -27,6 +32,8 @@ def setUp(self): self.tool = ImageInspectorTool(component=renderer) self.overlay = ImageInspectorOverlay(component=renderer, image_inspector=self.tool) + self.overlay2 = CustomImageInspectorOverlay(component=renderer, + image_inspector=self.tool) self.plot.active_tool = self.tool self.plot.do_layout() @@ -50,6 +57,23 @@ def test_mouse_move_records_last_position(self): self.mouse_leave(tool, 1000, 1000) self.assertEqual(tool.last_mouse_position, (10, 10)) + def test_mouse_move_custom_overlay(self): + tool = self.tool + + # Add a listener to catch the emitted event: + tool.on_trait_change(self.store_inspector_event, "new_value") + try: + self.assertIsNone(self.insp_event) + + with self.assertTraitChanges(tool, "new_value", 1): + with self.assertTraitChanges(self.overlay2, "text", 1): + self.mouse_move(tool, 0, 0) + + self.assertEqual(self.overlay2.text, 'Position: (0, 0)') + finally: + tool.on_trait_change(self.store_inspector_event, "new_value", + remove=True) + # Helper methods ---------------------------------------------------------- def store_inspector_event(self, event): From 6a9c03b2bc41e3f5e8dcc142ef935730c6f94749 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Tue, 19 Feb 2019 21:37:48 -0600 Subject: [PATCH 6/9] Better handling of integer case when building color value string. --- chaco/tools/image_inspector_tool.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/chaco/tools/image_inspector_tool.py b/chaco/tools/image_inspector_tool.py index f1b04a63b..ee15d7a56 100644 --- a/chaco/tools/image_inspector_tool.py +++ b/chaco/tools/image_inspector_tool.py @@ -109,13 +109,12 @@ def _build_text_from_event(self, d): if 'indices' in d: newstring += '(%d, %d)' % d['indices'] if 'color_value' in d: - if isinstance(d['color_value'], int): - # Gray scale image: - newstring += "\n%d" % d['color_value'] - else: - # RGB(A) image: + try: newstring += "\n(%d, %d, %d)" % tuple( map(int, d['color_value'][:3])) + except IndexError: + # color value is an integer, for example if gray scale image + newstring += "\n%d" % d['color_value'] if 'data_value' in d: newstring += "\n{}".format(d['data_value']) From 6ab22806d3c3840dc79f7a5fbce00134736612af Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Fri, 22 Feb 2019 09:54:25 -0600 Subject: [PATCH 7/9] Rename argument name. --- chaco/tools/image_inspector_tool.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chaco/tools/image_inspector_tool.py b/chaco/tools/image_inspector_tool.py index ee15d7a56..02a02582a 100644 --- a/chaco/tools/image_inspector_tool.py +++ b/chaco/tools/image_inspector_tool.py @@ -102,22 +102,22 @@ class ImageInspectorOverlay(TextBoxOverlay): # Private interface ------------------------------------------------------- - def _build_text_from_event(self, d): + def _build_text_from_event(self, event): """ Create a formatted string to display from the mouse event data. """ newstring = "" - if 'indices' in d: - newstring += '(%d, %d)' % d['indices'] - if 'color_value' in d: + if 'indices' in event: + newstring += '(%d, %d)' % event['indices'] + if 'color_value' in event: try: newstring += "\n(%d, %d, %d)" % tuple( - map(int, d['color_value'][:3])) + map(int, event['color_value'][:3])) except IndexError: # color value is an integer, for example if gray scale image - newstring += "\n%d" % d['color_value'] + newstring += "\n%d" % event['color_value'] - if 'data_value' in d: - newstring += "\n{}".format(d['data_value']) + if 'data_value' in event: + newstring += "\n{}".format(event['data_value']) return newstring From 2c0c463337ef675b21069d4b747439374e3d144e Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Sun, 9 Jun 2019 10:43:34 -0500 Subject: [PATCH 8/9] Rename test file --- .../{image_inspector_test_case.py => test_image_inspector.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename chaco/tools/tests/{image_inspector_test_case.py => test_image_inspector.py} (100%) diff --git a/chaco/tools/tests/image_inspector_test_case.py b/chaco/tools/tests/test_image_inspector.py similarity index 100% rename from chaco/tools/tests/image_inspector_test_case.py rename to chaco/tools/tests/test_image_inspector.py From 20ace01f3c1a164cf0cc01dc9ddf7ca32d4498b7 Mon Sep 17 00:00:00 2001 From: Jonathan Rocher Date: Sun, 9 Jun 2019 10:58:55 -0500 Subject: [PATCH 9/9] Tweak test file docstring and code quality. --- chaco/tools/tests/test_image_inspector.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/chaco/tools/tests/test_image_inspector.py b/chaco/tools/tests/test_image_inspector.py index c38eb6b3c..61474b612 100644 --- a/chaco/tools/tests/test_image_inspector.py +++ b/chaco/tools/tests/test_image_inspector.py @@ -1,4 +1,4 @@ -""" Tests for the ScatterInspector Chaco tool +""" Tests for the ImageInspectorTool and ImageInspectorOverlay tools. """ from unittest import TestCase @@ -20,7 +20,7 @@ def create_image_plot(img_values, **kwargs): class CustomImageInspectorOverlay(ImageInspectorOverlay): def _build_text_from_event(self, event): - return 'Position: (%d, %d)' % event['indices'] + return 'Position: ({}, {})'.format(*event['indices']) class BaseImageInspectorTool(EnableTestAssistant, UnittestTools): @@ -39,10 +39,6 @@ def setUp(self): self.insp_event = None - def tearDown(self): - del self.tool - del self.plot - def test_mouse_move_records_last_position(self): tool = self.tool