Skip to content

Commit

Permalink
expose zoom_to_selected in catalogs plugin api
Browse files Browse the repository at this point in the history
  • Loading branch information
cshanahan1 committed Dec 20, 2024
1 parent 57848c2 commit 06b20aa
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ Imviz
- Catalog Search plugin now exposes a maximum sources limit for all catalogs and resolves an edge case
when loading a catalog from a file that only contains one source. [#3337]

- Catalog Search plugin ``zoom_to_selected`` is now in the public API. The default
zoom level changed from a fixed 50 pixels to a zoom window that is a fraction of
the image size (default 2%) to address and issue with zooming when using a small
image or WCS linked. [#3369]

Mosviz
^^^^^^

Expand Down
58 changes: 49 additions & 9 deletions jdaviz/configs/imviz/plugins/catalogs/catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ 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:`zoom_to_selected`
"""
template_file = __file__, "catalogs.vue"
uses_active_status = Bool(True).tag(sync=True)
Expand All @@ -49,7 +50,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)
Expand Down Expand Up @@ -324,23 +326,61 @@ 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=0.02, return_bounding_box=False):
"""
Zoom to a region containing the currently selected points in the catalog.
Parameters
----------
padding : float, optional
A fractional value representing the padding around the bounding box
of the selected points. It is applied as a proportion of the largest
dimension of the current extent of loaded data. Defaults to 0.02.
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 or None
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.
"""

viewer = self.app._jdaviz_helper._default_viewer

selected_rows = self.table.selected_rows

if not len(selected_rows):
return

if padding <= 0 or padding > 1:
raise ValueError("`padding` must be between 0 and 1.")

x = [float(coord['x_coord']) for coord in selected_rows]
y = [float(coord['y_coord']) for coord in selected_rows]

limits = viewer.state._get_reset_limits()
max_dim = max((limits[1] - limits[0]), (limits[3] - limits[2]))
padding = max_dim * padding

# 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)
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):
"""
Expand Down
91 changes: 84 additions & 7 deletions jdaviz/configs/imviz/tests/test_catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -157,7 +158,9 @@ def test_plugin_image_with_result(self, imviz_helper, tmp_path):
assert imviz_helper.viewers['imviz-0']._obj.state.y_min == -0.5
assert imviz_helper.viewers['imviz-0']._obj.state.y_max == 1488.5

catalogs_plugin.vue_zoom_in()
# set 'padding' to reproduce original hard-coded 50 pixel window
# so test results don't change
catalogs_plugin.zoom_to_selected(padding=50 / 2048)

assert imviz_helper.viewers['imviz-0']._obj.state.x_min == 858.24969
assert imviz_helper.viewers['imviz-0']._obj.state.x_max == 958.38461
Expand Down Expand Up @@ -251,9 +254,83 @@ def test_offline_ecsv_catalog(imviz_helper, image_2d_wcs, tmp_path):
assert imviz_helper.viewers['imviz-0']._obj.state.y_min == -0.5
assert imviz_helper.viewers['imviz-0']._obj.state.y_max == 9.5

catalogs_plugin.vue_zoom_in()
# test the zooming using the default 'padding' of 2% of the viewer size
# around selected points
catalogs_plugin.zoom_to_selected()
assert imviz_helper.viewers['imviz-0']._obj.state.x_min == -0.19966
assert imviz_helper.viewers['imviz-0']._obj.state.x_max == 0.20034000000000002
assert imviz_helper.viewers['imviz-0']._obj.state.y_min == 0.8000100000000001
assert imviz_helper.viewers['imviz-0']._obj.state.y_max == 1.20001


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 2% of the image
# size around the bounding box containing the source(s), which in this case is
# 10 pixels around
assert_allclose(xmin, 100 - 10, atol=0.1) # min x of selected sources minus pad
assert_allclose(xmax, 200 + 10, atol=0.1) # max x of selected sources plus pad
assert_allclose(ymin, 100 - 10, atol=0.1) # min y of selected sources minus pad
assert_allclose(ymax, 200 + 10, atol=0.1) # max y of selected sources plus pad

# 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=0.05)

# check that zoom window is centered correctly on the source at 100, 100
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 the zoom box size should reflect the default padding of 5% of the image
# size around the bounding box containing the source(s), which in this case is
# 25 pixels around
assert_allclose(xmin, 100 - 25, atol=0.1) # min x of selected source minus pad
assert_allclose(xmax, 100 + 25, atol=0.1) # max x of selected source plus pad
assert_allclose(ymin, 100 - 25, atol=0.1) # min y of selected source minus pad
assert_allclose(ymax, 100 + 25, atol=0.1) # max y of selected source plus pad

assert imviz_helper.viewers['imviz-0']._obj.state.x_min == -49.99966
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
# test that appropriate error is raised when padding is not a valud percentage
with pytest.raises(ValueError, match="`padding` must be between 0 and 1."):
catalogs_plugin.zoom_to_selected(padding=5)

0 comments on commit 06b20aa

Please sign in to comment.