From 2082f324a3ecd4e230ab0b2c9c82da9ce28fdc01 Mon Sep 17 00:00:00 2001 From: Clare Shanahan Date: Thu, 19 Dec 2024 21:06:02 -0500 Subject: [PATCH] expose zoom_to_selected in catalogs plugin api --- .../imviz/plugins/catalogs/catalogs.py | 48 ++++++++++++-- jdaviz/configs/imviz/tests/test_catalogs.py | 65 ++++++++++++++++++- 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/catalogs/catalogs.py b/jdaviz/configs/imviz/plugins/catalogs/catalogs.py index 13ec7beda8..5a34c2a86a 100644 --- a/jdaviz/configs/imviz/plugins/catalogs/catalogs.py +++ b/jdaviz/configs/imviz/plugins/catalogs/catalogs.py @@ -29,6 +29,9 @@ class Catalogs(PluginTemplateMixin, ViewerSelectMixin, HasFileImportSelect, Tabl * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.clear_table` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.export_table` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.zoom_to_selected` """ template_file = __file__, "catalogs.vue" uses_active_status = Bool(True).tag(sync=True) @@ -49,7 +52,8 @@ class Catalogs(PluginTemplateMixin, ViewerSelectMixin, HasFileImportSelect, Tabl @property def user_api(self): - return PluginUserApi(self, expose=('clear_table', 'export_table',)) + return PluginUserApi(self, expose=('clear_table', 'export_table', + 'zoom_to_selected')) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -324,23 +328,53 @@ def plot_selected_points(self): getattr(y, 'value', y)) def vue_zoom_in(self, *args, **kwargs): - """This function will zoom into the image based on the selected points""" + + self.zoom_to_selected(return_bounding_box=True) + + def zoom_to_selected(self, padding=50, return_bounding_box=False): + """ + Zoom to a region containing the currently selected points in the catalog. + + Parameters + ---------- + padding : int, optional + The total padding added to the bounding box of the selected points + to define the zoom region, in pixels. This value is added to both + the minimum and maximum coordinates along each axis. Defaults to 50. + return_bounding_box : bool, optional + If True, returns the bounding box of the zoomed region as + ((x_min, x_max), (y_min, y_max)). Defaults to False. + + Returns + ------- + tuple of tuple, optional + If there are activley selected rows, and `return_bounding_box` is + True, returns a tuple containing the bounding + box coordinates: ((x_min, x_max), (y_min, y_max)). + Otherwise, returns None. + + """ + selected_rows = self.table.selected_rows + if not len(selected_rows): + return + x = [float(coord['x_coord']) for coord in selected_rows] y = [float(coord['y_coord']) for coord in selected_rows] # this works with single selected points # zooming when the range is too large is not performing correctly - x_min = min(x) - 50 - x_max = max(x) + 50 - y_min = min(y) - 50 - y_max = max(y) + 50 + x_min = min(x) - padding + x_max = max(x) + padding + y_min = min(y) - padding + y_max = max(y) + padding self.app._jdaviz_helper._default_viewer.set_limits( x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max) - return (x_min, x_max), (y_min, y_max) + if return_bounding_box: + return (x_min, x_max), (y_min, y_max) def import_catalog(self, catalog): """ diff --git a/jdaviz/configs/imviz/tests/test_catalogs.py b/jdaviz/configs/imviz/tests/test_catalogs.py index 0e6e960ddc..34853d56b8 100644 --- a/jdaviz/configs/imviz/tests/test_catalogs.py +++ b/jdaviz/configs/imviz/tests/test_catalogs.py @@ -22,11 +22,12 @@ ''' import numpy as np +from numpy.testing import assert_allclose import pytest +from astropy.coordinates import SkyCoord from astropy.io import fits from astropy.nddata import NDData -from astropy.coordinates import SkyCoord from astropy.table import Table, QTable @@ -257,3 +258,65 @@ def test_offline_ecsv_catalog(imviz_helper, image_2d_wcs, tmp_path): assert imviz_helper.viewers['imviz-0']._obj.state.x_max == 50.00034 assert imviz_helper.viewers['imviz-0']._obj.state.y_min == -48.99999 assert imviz_helper.viewers['imviz-0']._obj.state.y_max == 51.00001 + + +def test_zoom_to_selected(imviz_helper, image_2d_wcs, tmp_path): + + arr = np.ones((500, 500)) + ndd = NDData(arr, wcs=image_2d_wcs) + imviz_helper.load_data(ndd) + + # write out catalog to file so we can read it back in + # todo: if tables can be loaded directly at some point, do that + + # sources at pixel coords ~(100, 100), ~(200, 200) + sky_coord = SkyCoord(ra=[337.49056532, 337.46086081], + dec=[-20.80555273, -20.7777673], unit='deg') + tbl = Table({'sky_centroid': [sky_coord], + 'label': ['Source_1', 'Source_2']}) + tbl_file = str(tmp_path / 'test_catalog.ecsv') + tbl.write(tbl_file, overwrite=True) + + catalogs_plugin = imviz_helper.plugins['Catalog Search'] + + catalogs_plugin._obj.from_file = tbl_file + + catalogs_plugin._obj.search() + + # select both sources + catalogs_plugin._obj.table.selected_rows = catalogs_plugin._obj.table.items + + # check viewer limits before zoom + xmin, xmax, ymin, ymax = imviz_helper.app._jdaviz_helper._default_viewer.get_limits() + assert xmin == ymin == -0.5 + assert xmax == ymax == 499.5 + + # zoom to selected sources + catalogs_plugin.zoom_to_selected() + + # make sure the viewer bounds reflect the zoom, which, in pixel coords, + # should be centered at roughly pixel coords (150, 150) + xmin, xmax, ymin, ymax = imviz_helper.app._jdaviz_helper._default_viewer.get_limits() + + assert_allclose((xmin + xmax) / 2, 150., atol=0.1) + assert_allclose((ymin + ymax) / 2, 150., atol=0.1) + + # and the zoom box size should reflect the default padding of 50 pixels + # added to/subtracted from x(y) min(max) + assert_allclose(xmax - xmin, 200., atol=0.1) + assert_allclose(ymax - ymin, 200., atol=0.1) + + # select one source now + catalogs_plugin._obj.table.selected_rows = catalogs_plugin._obj.table.items[0:1] + + # zoom to single selected source, using a new value for 'padding' + catalogs_plugin.zoom_to_selected(padding=25) + + # check that zoom window is centered correctly + xmin, xmax, ymin, ymax = imviz_helper.app._jdaviz_helper._default_viewer.get_limits() + assert_allclose((xmin + xmax) / 2, 100., atol=0.1) + assert_allclose((ymin + ymax) / 2, 100., atol=0.1) + + # and that the the window size corresponds to the 'padding' chosen + assert_allclose(xmax - xmin, 50., atol=0.1) + assert_allclose(ymax - ymin, 50., atol=0.1)