diff --git a/chaco/tools/image_inspector_tool.py b/chaco/tools/image_inspector_tool.py index f13c58ebd..02a02582a 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 @@ -100,10 +100,35 @@ 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, event): + """ Create a formatted string to display from the mouse event data. + """ + newstring = "" + if 'indices' in event: + newstring += '(%d, %d)' % event['indices'] + if 'color_value' in event: + try: + newstring += "\n(%d, %d, %d)" % tuple( + map(int, event['color_value'][:3])) + except IndexError: + # color value is an integer, for example if gray scale image + newstring += "\n%d" % event['color_value'] + + if 'data_value' in event: + newstring += "\n{}".format(event['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 +148,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): diff --git a/chaco/tools/tests/test_image_inspector.py b/chaco/tools/tests/test_image_inspector.py new file mode 100644 index 000000000..61474b612 --- /dev/null +++ b/chaco/tools/tests/test_image_inspector.py @@ -0,0 +1,209 @@ +""" Tests for the ImageInspectorTool and ImageInspectorOverlay tools. +""" + +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 CustomImageInspectorOverlay(ImageInspectorOverlay): + def _build_text_from_event(self, event): + return 'Position: ({}, {})'.format(*event['indices']) + + +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.overlay2 = CustomImageInspectorOverlay(component=renderer, + image_inspector=self.tool) + self.plot.active_tool = self.tool + self.plot.do_layout() + + self.insp_event = None + + 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)) + + 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): + 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) 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)