From 6afcfb8cc3bdd56c34220932d813ca50066df65e Mon Sep 17 00:00:00 2001 From: "Brett M. Morris" Date: Wed, 10 May 2023 12:22:22 -0400 Subject: [PATCH] some test coverage for wcs-based rotation via glue --- jdaviz/app.py | 2 +- jdaviz/configs/imviz/plugins/parsers.py | 9 ++- jdaviz/configs/imviz/tests/test_wcs_utils.py | 59 ++++++++++++++++++++ jdaviz/configs/imviz/wcs_utils.py | 24 ++++---- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/jdaviz/app.py b/jdaviz/app.py index ffb2b3c875..b13c0b0148 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -1788,7 +1788,7 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac is_wcs_only = getattr(layer.layer, 'meta', {}).get('WCS-ONLY', False) if layer.layer.data.label == data_label and is_wcs_only: layer.visible = False - viewer_item['wcs_only_layers'].append(data_label) + viewer.state.wcs_only_layers.append(data_label) selected_items.pop(data_id) # Sets the plot axes labels to be the units of the most recently diff --git a/jdaviz/configs/imviz/plugins/parsers.py b/jdaviz/configs/imviz/plugins/parsers.py index 0d8cdec12e..4a52f513f0 100644 --- a/jdaviz/configs/imviz/plugins/parsers.py +++ b/jdaviz/configs/imviz/plugins/parsers.py @@ -373,7 +373,7 @@ def _nddata_to_glue_data(ndd, data_label): for attrib, sub_attrib in zip( ['data', 'mask', 'uncertainty'], - ['data', None, 'array'] + [None, None, 'array'] ): arr = getattr(ndd, attrib) if arr is None: @@ -385,11 +385,16 @@ def _nddata_to_glue_data(ndd, data_label): raw_arr = arr if sub_attrib is not None: + # since NDDataArray.uncertainty may be an object like + # StdDevUncertainty, we need to take another attr + # like StdDevUncertainty.array: base_arr = getattr(raw_arr, sub_attrib) else: base_arr = raw_arr wcs_only = np.all(np.isnan(base_arr)) - cur_data.meta.update({'WCS-ONLY': wcs_only}) + + if 'WCS-ONLY' not in cur_data.meta or not cur_data.meta.get('WCS-ONLY'): + cur_data.meta.update({'WCS-ONLY': wcs_only}) cur_label = f'{data_label}' comp_label = attrib.upper() diff --git a/jdaviz/configs/imviz/tests/test_wcs_utils.py b/jdaviz/configs/imviz/tests/test_wcs_utils.py index bff80391aa..0cbdad7c02 100644 --- a/jdaviz/configs/imviz/tests/test_wcs_utils.py +++ b/jdaviz/configs/imviz/tests/test_wcs_utils.py @@ -1,7 +1,9 @@ +import pytest import gwcs import numpy as np from astropy import units as u from astropy.coordinates import ICRS +from astropy.utils.data import get_pkg_data_filename from astropy.modeling import models from astropy.wcs import WCS from gwcs import coordinate_frames as cf @@ -104,3 +106,60 @@ def test_simple_gwcs(): 1262.0057201165127, 606.2863901330095, 155.2870478938214, -86.89813081941797)) assert not result[-1] + + +@pytest.mark.remote_data +@pytest.mark.filterwarnings(r"ignore::astropy.wcs.wcs.FITSFixedWarning") +def test_non_wcs_layer_labels(imviz_helper): + # load a real image w/ WCS: + real_image_path = get_pkg_data_filename('tutorials/FITS-images/HorseHead.fits') + imviz_helper.load_data(real_image_path) + + # load a WCS-only layer: + ndd = wcs_utils._get_rotated_nddata_from_label( + app=imviz_helper.app, + data_label=imviz_helper.app.data_collection[0].label, + rotation_angle=0*u.deg + ) + imviz_helper.load_data(ndd) + + # confirm that only the image is labeled: + assert len(imviz_helper.app.state.layer_icons) == 2 + + # confirm the WCS-only layer is logged: + viewer = imviz_helper.app.get_viewer('imviz-0') + assert len(viewer.state.wcs_only_layers) == 1 + + # load a second WCS-only layer: + ndd2 = wcs_utils._get_rotated_nddata_from_label( + app=imviz_helper.app, + data_label=imviz_helper.app.data_collection[0].label, + rotation_angle=45*u.deg + ) + imviz_helper.load_data(ndd2) + assert len(imviz_helper.app.state.layer_icons) == 3 + assert len(viewer.state.wcs_only_layers) == 2 + + wcs_only_refdata_icon = 'mdi-compass-outline' + wcs_only_not_refdata_icon = 'mdi-compass-off-outline' + + for i, data in enumerate(imviz_helper.app.data_collection): + viewer = imviz_helper.app.get_viewer("imviz-0") + + if i == 0: + # first entry is image data: + assert imviz_helper.app.state.layer_icons[data.label] == 'a' + assert viewer.state.reference_data.label == data.label + else: + # icon before setting as refdata: + before_icon = imviz_helper.app.state.layer_icons[data.label] + + # set as refdata: + imviz_helper.app._change_reference_data(data.label) + assert viewer.state.reference_data.label == data.label + + # icon after setting as refdata: + after_icon = imviz_helper.app.state.layer_icons[data.label] + + assert before_icon == wcs_only_not_refdata_icon + assert after_icon == wcs_only_refdata_icon diff --git a/jdaviz/configs/imviz/wcs_utils.py b/jdaviz/configs/imviz/wcs_utils.py index eca0c99e9f..64acfa684b 100644 --- a/jdaviz/configs/imviz/wcs_utils.py +++ b/jdaviz/configs/imviz/wcs_utils.py @@ -288,8 +288,8 @@ def data_outside_gwcs_bounding_box(data, x, y): return outside_bounding_box -def _get_fits_wcs_from_file(filename): - header = fits.getheader(filename) +def _get_fits_wcs_from_file(filename, ext=None): + header = fits.getheader(filename, ext=ext) with warnings.catch_warnings(): # Ignore a warning on using DATE-OBS in place of MJD-OBS warnings.filterwarnings('ignore', message="'datfix' made the change", @@ -369,9 +369,9 @@ def rotated_gwcs( return GWCS(pipeline) -def _prepare_rotated_nddata(wcs, rotation_angle, refdata_shape): +def _prepare_rotated_nddata(image_data, wcs, rotation_angle, refdata_shape): # get the world coordinates of the central pixel - real_image_shape = np.array(wcs.array_shape) + real_image_shape = np.array(np.shape(image_data)) central_pixel_coord = real_image_shape / 2 * u.pix central_world_coord = wcs.pixel_to_world(*central_pixel_coord) rotation_angle = coord.Angle(rotation_angle).wrap_at(360 * u.deg) @@ -401,7 +401,7 @@ def _prepare_rotated_nddata(wcs, rotation_angle, refdata_shape): return ndd -def _get_rotated_nddata_from_fits(filename, rotation_angle, refdata_shape=(2, 2)): +def _get_rotated_nddata_from_fits(filename, rotation_angle, refdata_shape=(2, 2), ext=None): """ Create a synthetic NDDataArray which stores GWCS that approximate the FITS WCS in ``filename`` rotated by ``rotation_angle``. @@ -425,12 +425,14 @@ def _get_rotated_nddata_from_fits(filename, rotation_angle, refdata_shape=(2, 2) Data are all NaNs, wcs are rotated. """ # get the FITS WCS from the file: - wcs = _get_fits_wcs_from_file(filename) + wcs = _get_fits_wcs_from_file(filename, ext=ext) return _prepare_rotated_nddata(wcs, rotation_angle, refdata_shape) -def _get_rotated_nddata_from_label(app, data_label, rotation_angle, refdata_shape=(2, 2)): +def _get_rotated_nddata_from_label( + app, data_label, rotation_angle, refdata_shape=(2, 2), main_component_idx=0 +): """ Create a synthetic NDDataArray which stores GWCS that approximate the WCS in the coords attr of the Data object with label ``data_label`` @@ -457,9 +459,9 @@ def _get_rotated_nddata_from_label(app, data_label, rotation_angle, refdata_shap Data are all NaNs, wcs are rotated. """ # get the WCS from the Data object's coords attribute: - [wcs] = [ - data.coords for data in app.data_collection + [data] = [ + data for data in app.data_collection if data.label == data_label ] - - return _prepare_rotated_nddata(wcs, rotation_angle, refdata_shape) + image = data[data.main_components[main_component_idx]] + return _prepare_rotated_nddata(image, data.coords, rotation_angle, refdata_shape)