From fafb6689ba6a74a7016986375f0c1eb14f0c31bd Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 15 Oct 2021 13:59:46 -0400 Subject: [PATCH 01/24] Imviz: New plugin for simple aperture photometry [ci skip] --- docs/imviz/plugins.rst | 20 +- jdaviz/configs/imviz/helper.py | 12 + jdaviz/configs/imviz/imviz.yaml | 1 + jdaviz/configs/imviz/plugins/__init__.py | 1 + .../plugins/aper_phot_simple/__init__.py | 1 + .../aper_phot_simple/aper_phot_simple.py | 158 +++++++++++++ .../aper_phot_simple/aper_phot_simple.vue | 77 +++++++ .../concepts/imviz_simple_aper_phot.ipynb | 213 ++++++++++++++++++ 8 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 jdaviz/configs/imviz/plugins/aper_phot_simple/__init__.py create mode 100644 jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py create mode 100644 jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue create mode 100644 notebooks/concepts/imviz_simple_aper_phot.ipynb diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 4e3617eb04..975c161ef0 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -4,4 +4,22 @@ Data Analysis Plugins ********************* -Currently there are no active plugins that work with Imviz. +The Imviz data analysis plugins are meant to aid quick-look analysis +of 2D image data. All plugins are accessed via the :guilabel:`plugin` +icon in the upper right corner of the Imviz application. + +.. _imviz-link-control: + +Link Control +============ + +Plugin to re-link images by pixels or WCS. + + +.. _aper-phot-simple: + +Simple Aperture Photometry +========================== + +Perform simple aperture photometry on one object within an interactively +selected region. diff --git a/jdaviz/configs/imviz/helper.py b/jdaviz/configs/imviz/helper.py index 57f9533a55..ad8b90d7c9 100644 --- a/jdaviz/configs/imviz/helper.py +++ b/jdaviz/configs/imviz/helper.py @@ -279,6 +279,18 @@ def _delete_all_regions(self): for subset_grp in self.app.data_collection.subset_groups: # should be a copy self.app.data_collection.remove_subset_group(subset_grp) + def get_aperture_photometry_results(self): + """Return aperture photometry results, if any. + Results are calculated using :ref:`aper-phot-simple` plugin. + + Returns + ------- + results : `~astropy.table.QTable` or `None` + Photometry results if available or `None` otherwise. + + """ + return getattr(self.app, '_aper_phot_results', None) + def split_filename_with_fits_ext(filename): """Split a ``filename[ext]`` input into filename and FITS extension. diff --git a/jdaviz/configs/imviz/imviz.yaml b/jdaviz/configs/imviz/imviz.yaml index 4a460577fd..8370190e44 100644 --- a/jdaviz/configs/imviz/imviz.yaml +++ b/jdaviz/configs/imviz/imviz.yaml @@ -19,6 +19,7 @@ toolbar: - g-coords-info tray: - imviz-links-control + - imviz-aper-phot-simple viewer_area: - container: col children: diff --git a/jdaviz/configs/imviz/plugins/__init__.py b/jdaviz/configs/imviz/plugins/__init__.py index fb53b9b42d..ad25112896 100644 --- a/jdaviz/configs/imviz/plugins/__init__.py +++ b/jdaviz/configs/imviz/plugins/__init__.py @@ -4,3 +4,4 @@ from .parsers import * # noqa from .coords_info import * # noqa from .links_control import * # noqa +from .aper_phot_simple import * # noqa diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/__init__.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/__init__.py new file mode 100644 index 0000000000..73a1d2f4ff --- /dev/null +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/__init__.py @@ -0,0 +1 @@ +from .aper_phot_simple import * # noqa diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py new file mode 100644 index 0000000000..bc2ddc1319 --- /dev/null +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -0,0 +1,158 @@ +import numpy as np +from astropy import units as u +from astropy.table import QTable +from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage +from glue.core.subset import Subset +from traitlets import Any, Bool, List + +from jdaviz.configs.imviz.helper import layer_is_image_data +from jdaviz.core.events import AddDataMessage, RemoveDataMessage, SnackbarMessage +from jdaviz.core.registries import tray_registry +from jdaviz.core.template_mixin import TemplateMixin +from jdaviz.utils import load_template + +__all__ = ['SimpleAperturePhotometry'] + + +@tray_registry('imviz-aper-phot-simple', label="Imviz Simple Aperture Photometry") +class SimpleAperturePhotometry(TemplateMixin): + template = load_template("aper_phot_simple.vue", __file__).tag(sync=True) + dc_items = List([]).tag(sync=True) + subset_items = List([]).tag(sync=True) + background_value = Any(0).tag(sync=True) + result_available = Bool(False).tag(sync=True) + results = List().tag(sync=True) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.hub.subscribe(self, AddDataMessage, handler=self._on_viewer_data_changed) + self.hub.subscribe(self, RemoveDataMessage, handler=self._on_viewer_data_changed) + self.hub.subscribe(self, SubsetCreateMessage, + handler=lambda x: self._on_viewer_data_changed()) + self.hub.subscribe(self, SubsetDeleteMessage, + handler=lambda x: self._on_viewer_data_changed()) + self.hub.subscribe(self, SubsetUpdateMessage, + handler=lambda x: self._on_viewer_data_changed()) + + # TODO: Allow switching viewer in the future. Need new "messages" to subscribe + # to in viewer create/destroy events. + self._selected_viewer_id = 'imviz-0' + + self._selected_data = None + self._selected_subset = None + + def _on_viewer_data_changed(self, msg=None): + # NOTE: Unlike other plugins, this does not check viewer ID because + # Imviz allows photometry from any number of open image viewers. + viewer = self.app.get_viewer_by_id(self._selected_viewer_id) + self.dc_items = [lyr.layer.label for lyr in viewer.state.layers + if layer_is_image_data(lyr.layer)] + self.subset_items = [lyr.layer.label for lyr in viewer.state.layers + if (lyr.layer.label.startswith('Subset') and + isinstance(lyr.layer, Subset) and lyr.layer.ndim == 2)] + + def vue_data_selected(self, event): + try: + self._selected_data = self.app.data_collection[ + self.app.data_collection.labels.index(event)] + except Exception as e: + self._selected_data = None + self.hub.broadcast(SnackbarMessage( + f"Failed to extract {event}: {repr(e)}", color='error', sender=self)) + + def vue_subset_selected(self, event): + subset = None + viewer = self.app.get_viewer_by_id(self._selected_viewer_id) + for lyr in viewer.state.layers: + if lyr.layer.label == event: + subset = lyr.layer + break + try: + self._selected_subset = subset.data.get_selection_definition( + subset_id=event, format='astropy-regions') + self._selected_subset.meta['label'] = subset.label + except Exception as e: + self._selected_subset = None + self.hub.broadcast(SnackbarMessage( + f"Failed to extract {event}: {repr(e)}", color='error', sender=self)) + + def vue_do_aper_phot(self, *args, **kwargs): + if self._selected_data is None or self._selected_subset is None: + self.result_available = False + self.results = [] + self.hub.broadcast(SnackbarMessage( + "No data for aperture photometry", color='error', sender=self)) + return + + data = self._selected_data + reg = self._selected_subset + + try: + comp = data.get_component(data.main_components[0]) + bg = float(self.background_value) + comp_no_bg = comp.data - bg + + # TODO: Use photutils when it supports astropy regions. + aper_mask = reg.to_mask(mode='exact') + img = aper_mask.get_values(comp_no_bg, mask=None) + if comp.units: + img_unit = u.Unit(comp.units) + img = img * img_unit + bg = bg * img_unit + d = {'id': 1, + 'xcenter': reg.center.x * u.pix, + 'ycenter': reg.center.y * u.pix, + 'aperture_sum': np.nansum(img)} + if data.coords is not None: + d['sky_center'] = data.coords.pixel_to_world(reg.center.x, reg.center.y) + else: + d['sky_center'] = None + + # Extra stats beyond photutils. + d.update({'background': bg, + 'mean': np.nanmean(img), + 'stddev': np.nanstd(img), + 'median': np.nanmedian(img), + 'min': np.nanmin(img), + 'max': np.nanmax(img), + 'data_label': data.label, + 'subset_label': reg.meta.get('label', '')}) + + # Attach to app for Python extraction. + if (not hasattr(self.app, '_aper_phot_results') or + not isinstance(self.app._aper_phot_results, QTable)): + self.app._aper_phot_results = _qtable_from_dict(d) + else: + try: + self.app._aper_phot_results.add_row(d.values()) + except Exception: # Discard incompatible QTable + self.app._aper_phot_results = _qtable_from_dict(d) + + except Exception as e: + self.result_available = False + self.results = [] + self.hub.broadcast(SnackbarMessage( + f"Aperture photometry failed: {repr(e)}", color='error', sender=self)) + + else: + # Parse results for GUI. + tmp = [] + for key, x in d.items(): + if key in ('id', 'data_label', 'subset_label', 'background'): + continue + if isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter'): + x = f'{x:.4e}' + elif not isinstance(x, str): + x = str(x) + tmp.append({'function': key, 'result': x}) + self.results = tmp + self.result_available = True + + +def _qtable_from_dict(d): + # TODO: Is there more elegant way to do this? + tmp = {} + for key, x in d.items(): + tmp[key] = [x] + return QTable(tmp) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue new file mode 100644 index 0000000000..0e60da70ea --- /dev/null +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -0,0 +1,77 @@ + diff --git a/notebooks/concepts/imviz_simple_aper_phot.ipynb b/notebooks/concepts/imviz_simple_aper_phot.ipynb new file mode 100644 index 0000000000..16e8519713 --- /dev/null +++ b/notebooks/concepts/imviz_simple_aper_phot.ipynb @@ -0,0 +1,213 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "65797402-e16b-4a78-8acc-02dd90ca5e7d", + "metadata": {}, + "source": [ + "# Imviz simple aperture photometry\n", + "\n", + "This is a proof-of-concept showing how to use Imviz to perform simple aperture photometry using hand-drawn aperture on a single object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ad56d9d-a52f-446b-9d3f-b823d930cb2e", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "from astropy.utils.data import download_file\n", + "\n", + "from jdaviz import Imviz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "393b3fe4", + "metadata": {}, + "outputs": [], + "source": [ + "with warnings.catch_warnings():\n", + " warnings.simplefilter('ignore') # Hide import warnings\n", + " imviz = Imviz(verbosity='warning') # Hide info messages" + ] + }, + { + "cell_type": "markdown", + "id": "61eee0ab", + "metadata": {}, + "source": [ + "We can load JWST images with ASDF and GWCS." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66ca7067-6f89-446c-b1ca-53c46827af1c", + "metadata": {}, + "outputs": [], + "source": [ + "jwf277w = download_file('https://stsci.box.com/shared/static/iao1zxtigyrhq7k3wtu5nchrxzlhj9kv.fits', cache=True)\n", + "jwf444w = download_file('https://stsci.box.com/shared/static/rey83o5wq6g7qd7xym6r1jq9wlsxaqnt.fits', cache=True)\n", + "my_cen = (1002, 1154)\n", + "my_radius = 20\n", + "my_zoom = 4\n", + "\n", + "imviz.load_data(jwf277w, data_label='JWST_F277W')\n", + "imviz.load_data(jwf444w, data_label='JWST_F444W')" + ] + }, + { + "cell_type": "markdown", + "id": "564c06c8", + "metadata": {}, + "source": [ + "Or we can load HST/ACS images with FITS WCS. This one has no valid unit." + ] + }, + { + "cell_type": "raw", + "id": "16a10237", + "metadata": {}, + "source": [ + "acs_47tuc_1 = download_file('https://mast.stsci.edu/api/v0.1/Download/file?uri=mast:HST/product/jbqf03gjq_flc.fits', cache=True)\n", + "acs_47tuc_2 = download_file('https://mast.stsci.edu/api/v0.1/Download/file?uri=mast:HST/product/jbqf03h1q_flc.fits', cache=True)\n", + "my_cen = (1090, 1157)\n", + "my_radius = 10\n", + "my_zoom = 6\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.simplefilter('ignore') # Hide FITS warnings\n", + " imviz.load_data(acs_47tuc_1, data_label='acs_47tuc_1')\n", + " imviz.load_data(acs_47tuc_2, data_label='acs_47tuc_2')\n", + "\n", + "# These are dithered, so we re-link by WCS.\n", + "imviz.link_data(link_type='wcs')" + ] + }, + { + "cell_type": "markdown", + "id": "7b9210d6", + "metadata": {}, + "source": [ + "Or we can load plain Numpy array without WCS or unit." + ] + }, + { + "cell_type": "raw", + "id": "ee6b76e3", + "metadata": {}, + "source": [ + "import numpy as np\n", + "from astropy.modeling.models import Gaussian2D\n", + "\n", + "gm = Gaussian2D(100, 25, 25, 5, 5)\n", + "y, x = np.mgrid[0:51, 0:51]\n", + "arr = gm(x, y)\n", + "my_cen = (25, 25)\n", + "my_radius = 5\n", + "my_zoom = 'fit'\n", + "\n", + "imviz.load_data(arr, data_label='my_gaussian')" + ] + }, + { + "cell_type": "markdown", + "id": "07d23b82", + "metadata": {}, + "source": [ + "Display the app." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "610b86ac-d6da-43df-8855-3963391dab4d", + "metadata": {}, + "outputs": [], + "source": [ + "imviz.app" + ] + }, + { + "cell_type": "markdown", + "id": "0e973760-7843-49ce-987b-1b84d5d6eb0a", + "metadata": {}, + "source": [ + "Now, we would zoom in on a star and draw an aperture." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f763388-64f5-4fbe-b910-07f52b327329", + "metadata": {}, + "outputs": [], + "source": [ + "viewer = imviz.default_viewer\n", + "viewer.cuts = '95%'\n", + "viewer.center_on(my_cen)\n", + "viewer.zoom_level = my_zoom\n", + "\n", + "# Click on image to finalize selection.\n", + "imviz._apply_interactive_region(\n", + " 'bqplot:circle', (my_cen[0] - my_radius, my_cen[1] - my_radius),\n", + " (my_cen[0] + my_radius, my_cen[1] + my_radius))" + ] + }, + { + "cell_type": "markdown", + "id": "9abeb637-dac2-4787-8087-3be5f54a5dd0", + "metadata": {}, + "source": [ + "Now, use the \"Imviz Simple Aperture Photometry\" plugin.\n", + "\n", + "Once photometry is done, we would do the following to extract the data from Imviz back to notebook for further processing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c3e6a27-bdf8-4881-8933-aa8c29e78163", + "metadata": {}, + "outputs": [], + "source": [ + "results = imviz.get_aperture_photometry_results()\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00435609", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 6f5b33d4c12955460fe520f8538b9d356d18470b Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 15 Oct 2021 16:55:51 -0400 Subject: [PATCH 02/24] Add change log [ci skip] --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7ec5253bfc..7880fc0dfc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ Imviz - New plugin to control image linking via GUI. [#909] +- New plugin to perform simple aperture photometry. [#938] + Mosviz ^^^^^^ From c85424e31d194de5212d08be5363ecf41017a237 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 15 Oct 2021 17:41:15 -0400 Subject: [PATCH 03/24] BUG: Increment ID when appending --- .../configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index bc2ddc1319..fc039b3a20 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -125,8 +125,10 @@ def vue_do_aper_phot(self, *args, **kwargs): self.app._aper_phot_results = _qtable_from_dict(d) else: try: + d['id'] = self.app._aper_phot_results['id'].max() + 1 self.app._aper_phot_results.add_row(d.values()) except Exception: # Discard incompatible QTable + d['id'] = 1 self.app._aper_phot_results = _qtable_from_dict(d) except Exception as e: From 1154c54267788fe3d47b3da9c3f1f2200a36cbca Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Tue, 19 Oct 2021 21:37:20 -0400 Subject: [PATCH 04/24] BUG: Fix stats calculation Co-authored-by: Larry Bradley --- .../plugins/aper_phot_simple/aper_phot_simple.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index fc039b3a20..51c1466446 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -96,9 +96,12 @@ def vue_do_aper_phot(self, *args, **kwargs): # TODO: Use photutils when it supports astropy regions. aper_mask = reg.to_mask(mode='exact') img = aper_mask.get_values(comp_no_bg, mask=None) + aper_mask_stat = reg.to_mask(mode='center') + img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) if comp.units: img_unit = u.Unit(comp.units) img = img * img_unit + img_stat = img_stat * img_unit bg = bg * img_unit d = {'id': 1, 'xcenter': reg.center.x * u.pix, @@ -111,11 +114,11 @@ def vue_do_aper_phot(self, *args, **kwargs): # Extra stats beyond photutils. d.update({'background': bg, - 'mean': np.nanmean(img), - 'stddev': np.nanstd(img), - 'median': np.nanmedian(img), - 'min': np.nanmin(img), - 'max': np.nanmax(img), + 'mean': np.nanmean(img_stat), + 'stddev': np.nanstd(img_stat), + 'median': np.nanmedian(img_stat), + 'min': np.nanmin(img_stat), + 'max': np.nanmax(img_stat), 'data_label': data.label, 'subset_label': reg.meta.get('label', '')}) From bd30c39fe6fcbd0cba9c999ff0e5d84d096a8d7a Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Tue, 19 Oct 2021 22:10:14 -0400 Subject: [PATCH 05/24] Add pixel scale input --- .../aper_phot_simple/aper_phot_simple.py | 18 +++++++++++++++--- .../aper_phot_simple/aper_phot_simple.vue | 11 +++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 51c1466446..39ee535ca5 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -20,6 +20,7 @@ class SimpleAperturePhotometry(TemplateMixin): dc_items = List([]).tag(sync=True) subset_items = List([]).tag(sync=True) background_value = Any(0).tag(sync=True) + pixel_scale = Any(0).tag(sync=True) result_available = Bool(False).tag(sync=True) results = List().tag(sync=True) @@ -69,6 +70,7 @@ def vue_subset_selected(self, event): subset = lyr.layer break try: + # TODO: Is this accurate for dithered images linked by WCS? self._selected_subset = subset.data.get_selection_definition( subset_id=event, format='astropy-regions') self._selected_subset.meta['label'] = subset.label @@ -95,18 +97,24 @@ def vue_do_aper_phot(self, *args, **kwargs): # TODO: Use photutils when it supports astropy regions. aper_mask = reg.to_mask(mode='exact') + npix = np.sum(aper_mask) * u.pix img = aper_mask.get_values(comp_no_bg, mask=None) aper_mask_stat = reg.to_mask(mode='center') img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) + pixscale_fac = 1.0 if comp.units: img_unit = u.Unit(comp.units) img = img * img_unit img_stat = img_stat * img_unit bg = bg * img_unit + if u.sr in img_unit.bases: # TODO: Better way to do this? + pixscale = float(self.pixel_scale) * (u.arcsec * u.arcsec / u.pix) + if not np.allclose(pixscale, 0): + pixscale_fac = npix * pixscale.to(u.sr / u.pix) d = {'id': 1, 'xcenter': reg.center.x * u.pix, 'ycenter': reg.center.y * u.pix, - 'aperture_sum': np.nansum(img)} + 'aperture_sum': np.nansum(img) * pixscale_fac} if data.coords is not None: d['sky_center'] = data.coords.pixel_to_world(reg.center.x, reg.center.y) else: @@ -114,6 +122,8 @@ def vue_do_aper_phot(self, *args, **kwargs): # Extra stats beyond photutils. d.update({'background': bg, + 'npix': npix, + 'pixscale_fac': pixscale_fac, 'mean': np.nanmean(img_stat), 'stddev': np.nanstd(img_stat), 'median': np.nanmedian(img_stat), @@ -144,10 +154,12 @@ def vue_do_aper_phot(self, *args, **kwargs): # Parse results for GUI. tmp = [] for key, x in d.items(): - if key in ('id', 'data_label', 'subset_label', 'background'): + if key in ('id', 'data_label', 'subset_label', 'background', 'pixscale_fac'): continue - if isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter'): + if isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter', 'npix'): x = f'{x:.4e}' + elif key == 'npix': + x = f'{x:.1f}' elif not isinstance(x, str): x = str(x) tmp.append({'function': key, 'result': x}) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index 0e60da70ea..2e12397d1c 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -39,6 +39,17 @@ + + + + + + From f9ad28b85f6bd7db53feb53b877b7b82f417d60b Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:17:12 -0400 Subject: [PATCH 06/24] Add counts conversion input --- .../aper_phot_simple/aper_phot_simple.py | 36 +++++++++++++++---- .../aper_phot_simple/aper_phot_simple.vue | 11 ++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 39ee535ca5..343a6727e3 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -21,6 +21,7 @@ class SimpleAperturePhotometry(TemplateMixin): subset_items = List([]).tag(sync=True) background_value = Any(0).tag(sync=True) pixel_scale = Any(0).tag(sync=True) + counts_factor = Any(0).tag(sync=True) result_available = Bool(False).tag(sync=True) results = List().tag(sync=True) @@ -102,6 +103,9 @@ def vue_do_aper_phot(self, *args, **kwargs): aper_mask_stat = reg.to_mask(mode='center') img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) pixscale_fac = 1.0 + include_pixscale_fac = False + counts_fac = 1.0 + include_counts_fac = False if comp.units: img_unit = u.Unit(comp.units) img = img * img_unit @@ -111,20 +115,36 @@ def vue_do_aper_phot(self, *args, **kwargs): pixscale = float(self.pixel_scale) * (u.arcsec * u.arcsec / u.pix) if not np.allclose(pixscale, 0): pixscale_fac = npix * pixscale.to(u.sr / u.pix) + include_pixscale_fac = True + if img_unit != u.count: + ctfac = float(self.counts_factor) + if not np.allclose(ctfac, 0): + counts_fac = ctfac * (u.count / img_unit) + include_counts_fac = True + apersum = np.nansum(img) * pixscale_fac d = {'id': 1, 'xcenter': reg.center.x * u.pix, 'ycenter': reg.center.y * u.pix, - 'aperture_sum': np.nansum(img) * pixscale_fac} + 'aperture_sum': apersum, + 'background': bg, + 'npix': npix} + if include_counts_fac: + d.update({'aperture_sum_counts': apersum * counts_fac, + 'counts_fac': counts_fac}) + else: + d.update({'aperture_sum_counts': None, + 'counts_fac': None}) + if include_pixscale_fac: + d['pixscale_fac'] = pixscale_fac + else: + d['pixscale_fac'] = None if data.coords is not None: d['sky_center'] = data.coords.pixel_to_world(reg.center.x, reg.center.y) else: d['sky_center'] = None # Extra stats beyond photutils. - d.update({'background': bg, - 'npix': npix, - 'pixscale_fac': pixscale_fac, - 'mean': np.nanmean(img_stat), + d.update({'mean': np.nanmean(img_stat), 'stddev': np.nanstd(img_stat), 'median': np.nanmedian(img_stat), 'min': np.nanmin(img_stat), @@ -154,9 +174,11 @@ def vue_do_aper_phot(self, *args, **kwargs): # Parse results for GUI. tmp = [] for key, x in d.items(): - if key in ('id', 'data_label', 'subset_label', 'background', 'pixscale_fac'): + if key in ('id', 'data_label', 'subset_label', 'background', 'pixscale_fac', + 'counts_fac'): continue - if isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter', 'npix'): + if (isinstance(x, (int, float, u.Quantity)) and + key not in ('xcenter', 'ycenter', 'npix')): x = f'{x:.4e}' elif key == 'npix': x = f'{x:.1f}' diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index 2e12397d1c..34b9839723 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -50,6 +50,17 @@ + + + + + + From db5eeb958d885fcb58f2fca31510adc5d244cd81 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 20 Oct 2021 15:17:06 -0400 Subject: [PATCH 07/24] Self-populate pixel scale and counts conversion factor when possible in photometry plugin. Improve photometry table output. Imviz parser now understands HST e- and e-/s units. Imviz parser now inherits image metadata. For FITS HDUList, this includes primary header. --- .../aper_phot_simple/aper_phot_simple.py | 53 +++++++++++++++---- .../aper_phot_simple/aper_phot_simple.vue | 4 +- jdaviz/configs/imviz/plugins/parsers.py | 35 +++++++----- jdaviz/configs/imviz/tests/test_parser.py | 21 ++++++-- .../concepts/imviz_simple_aper_phot.ipynb | 4 +- 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 343a6727e3..55769b33e3 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -58,6 +58,38 @@ def vue_data_selected(self, event): try: self._selected_data = self.app.data_collection[ self.app.data_collection.labels.index(event)] + self.counts_factor = 0 + self.pixel_scale = 0 + + # Extract telescope specific unit conversion factors, if applicable. + meta = self._selected_data.meta + telescope = meta.get('TELESCOP', '').lower() + comp = self._selected_data.get_component(self._selected_data.main_components[0]) + if telescope == 'jwst': + if 'PIXAR_A2' in meta: + self.pixel_scale = meta['PIXAR_A2'] + if (comp.units and u.sr in comp.units.bases and 'photometry' in meta and + 'conversion_megajanskys' in meta['photometry']): + self.counts_factor = meta['photometry']['conversion_megajanskys'] + elif telescope == 'hst': + # TODO: Add more HST support, as needed. + # HST pixel scales are from instrument handbooks. + # This is really not used because HST data does not have sr in unit. + # This is only for completeness. + # For counts conversion, PHOTFLAM is used to convert "counts" to flux manually, + # which is the opposite of JWST, so we just do not do it here. + instrument = meta.get('INSTRUME', '').lower() + detector = meta.get('DETECTOR', '').lower() + if instrument == 'acs': + if detector == 'wfc': + self.pixel_scale = 0.05 * 0.05 + elif detector == 'hrc': + self.pixel_scale = 0.028 * 0.025 + elif detector == 'sbc': + self.pixel_scale = 0.034 * 0.03 + elif instrument == 'wfc3' and detector == 'uvis': + self.pixel_scale = 0.04 * 0.04 + except Exception as e: self._selected_data = None self.hub.broadcast(SnackbarMessage( @@ -104,7 +136,6 @@ def vue_do_aper_phot(self, *args, **kwargs): img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) pixscale_fac = 1.0 include_pixscale_fac = False - counts_fac = 1.0 include_counts_fac = False if comp.units: img_unit = u.Unit(comp.units) @@ -119,17 +150,21 @@ def vue_do_aper_phot(self, *args, **kwargs): if img_unit != u.count: ctfac = float(self.counts_factor) if not np.allclose(ctfac, 0): - counts_fac = ctfac * (u.count / img_unit) include_counts_fac = True apersum = np.nansum(img) * pixscale_fac d = {'id': 1, 'xcenter': reg.center.x * u.pix, - 'ycenter': reg.center.y * u.pix, - 'aperture_sum': apersum, - 'background': bg, - 'npix': npix} + 'ycenter': reg.center.y * u.pix} + if data.coords is not None: + d['sky_center'] = data.coords.pixel_to_world(reg.center.x, reg.center.y) + else: + d['sky_center'] = None + d.update({'background': bg, + 'npix': npix, + 'aperture_sum': apersum}) if include_counts_fac: - d.update({'aperture_sum_counts': apersum * counts_fac, + counts_fac = ctfac * (apersum.unit / (u.count / u.s)) + d.update({'aperture_sum_counts': apersum / counts_fac, 'counts_fac': counts_fac}) else: d.update({'aperture_sum_counts': None, @@ -138,10 +173,6 @@ def vue_do_aper_phot(self, *args, **kwargs): d['pixscale_fac'] = pixscale_fac else: d['pixscale_fac'] = None - if data.coords is not None: - d['sky_center'] = data.coords.pixel_to_world(reg.center.x, reg.center.y) - else: - d['sky_center'] = None # Extra stats beyond photutils. d.update({'mean': np.nanmean(img_stat), diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index 34b9839723..af32f70101 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -44,7 +44,7 @@ @@ -55,7 +55,7 @@ diff --git a/jdaviz/configs/imviz/plugins/parsers.py b/jdaviz/configs/imviz/plugins/parsers.py index d028a834d7..b305eb4f65 100644 --- a/jdaviz/configs/imviz/plugins/parsers.py +++ b/jdaviz/configs/imviz/plugins/parsers.py @@ -171,15 +171,20 @@ def _validate_fits_image2d(hdu, raise_error=True): def _validate_bunit(bunit, raise_error=True): - # TODO: Do we want to handle weird FITS BUNIT values here? - try: - u.Unit(bunit) - except Exception: - if raise_error: - raise - valid = False + # TODO: Handle weird FITS BUNIT values here, as needed. + if bunit == 'ELECTRONS/S': + valid = 'electron/s' + elif bunit == 'ELECTRONS': + valid = 'electron' else: - valid = True + try: + u.Unit(bunit) + except Exception: + if raise_error: + raise + valid = '' + else: + valid = bunit return valid @@ -225,10 +230,10 @@ def _jwst2data(file_obj, ext, data_label): with AsdfInFits.open(file_obj) as af: dm = af.tree dm_meta = af.tree["meta"] + data.meta.update(dm_meta) - if (unit_attr in dm_meta and - _validate_bunit(dm_meta[unit_attr], raise_error=False)): - bunit = dm_meta[unit_attr] + if unit_attr in dm_meta: + bunit = _validate_bunit(dm_meta[unit_attr], raise_error=False) else: bunit = '' @@ -272,8 +277,8 @@ def _hdus_to_glue_data(file_obj, data_label): def _hdu2data(hdu, data_label, hdulist, include_wcs=True): - if 'BUNIT' in hdu.header and _validate_bunit(hdu.header['BUNIT'], raise_error=False): - bunit = hdu.header['BUNIT'] + if 'BUNIT' in hdu.header: + bunit = _validate_bunit(hdu.header['BUNIT'], raise_error=False) else: bunit = '' @@ -281,6 +286,9 @@ def _hdu2data(hdu, data_label, hdulist, include_wcs=True): new_data_label = f'{data_label}[{comp_label}]' data = Data(label=new_data_label) + if hdulist is not None and hdu.name != 'PRIMARY' and 'PRIMARY' in hdulist: + data.meta.update(dict(hdulist['PRIMARY'].header)) + data.meta.update(dict(hdu.header)) if include_wcs: data.coords = WCS(hdu.header, hdulist) component = Component.autotyped(hdu.data, units=bunit) @@ -302,6 +310,7 @@ def _nddata_to_glue_data(ndd, data_label): comp_label = attrib.upper() cur_label = f'{data_label}[{comp_label}]' cur_data = Data(label=cur_label) + cur_data.meta.update(ndd.meta) if ndd.wcs is not None: cur_data.coords = ndd.wcs raw_arr = arr diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 62aa2a1939..cd0611de22 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -50,7 +50,9 @@ def test_validate_bunit(): _validate_bunit('NOT_A_UNIT') assert not _validate_bunit('Mjy-sr', raise_error=False) # Close but not quite - assert _validate_bunit('MJy/sr') + assert _validate_bunit('MJy/sr') == 'MJy/sr' + assert _validate_bunit('ELECTRONS/S') == 'electron/s' + assert _validate_bunit('ELECTRONS') == 'electron' class TestParseImage: @@ -116,13 +118,14 @@ def test_parse_nddata_with_one_only(self, imviz_app, ndd, attributes): def test_parse_nddata_with_everything(self, imviz_app): ndd = NDData([[1, 2], [3, 4]], mask=[[True, False], [False, False]], uncertainty=StdDevUncertainty([[0.1, 0.2], [0.3, 0.4]]), - unit=u.MJy/u.sr, wcs=WCS(naxis=2)) + unit=u.MJy/u.sr, wcs=WCS(naxis=2), meta={'name': 'my_ndd'}) parse_data(imviz_app.app, ndd, data_label='some_data', show_in_viewer=False) for i, attrib in enumerate(['DATA', 'MASK', 'UNCERTAINTY']): data = imviz_app.app.data_collection[i] comp = data.get_component(attrib) assert data.label == f'some_data[{attrib}]' assert data.shape == (2, 2) + assert data.meta['name'] == 'my_ndd' assert isinstance(data.coords, WCS) assert comp.data.shape == (2, 2) if attrib == 'MASK': @@ -155,6 +158,7 @@ def test_filelist(self, imviz_app, tmp_path): fpath = tmp_path / f'myfits_{i}.fits' flist.append(str(fpath)) hdu = fits.PrimaryHDU(np.zeros((2, 2)) + i) + hdu.header['foo'] = 'bar' hdu.writeto(fpath, overwrite=True) flist = ','.join(flist) @@ -165,6 +169,7 @@ def test_filelist(self, imviz_app, tmp_path): comp = data.get_component('PRIMARY,1') assert data.label == f'myfits_{i}[PRIMARY,1]' assert data.shape == (2, 2) + assert data.meta['FOO'] == 'bar' np.testing.assert_allclose(comp.data.mean(), i) with pytest.raises(ValueError, match='Do not manually overwrite data_label'): @@ -180,6 +185,8 @@ def test_parse_jwst_nircam_level2(self, imviz_app): comp = data.get_component('DATA') assert data.label == 'contents[DATA]' # download_file returns cache loc assert data.shape == (2048, 2048) + # NOTE: jwst.datamodels.find_fits_keyword("PHOTMJSR") + assert_allclose(data.meta['photometry']['conversion_megajanskys'], 0.6250675320625305) assert isinstance(data.coords, GWCS) assert comp.units == 'MJy/sr' assert comp.data.shape == (2048, 2048) @@ -201,6 +208,7 @@ def test_parse_jwst_nircam_level2(self, imviz_app): data = imviz_app.app.data_collection[1] comp = data.get_component('DQ') assert data.label == 'jw01072001001_01101_00001_nrcb1_cal[DQ]' + assert data.meta['aperture']['name'] == 'NRCB5_FULL' assert comp.units == '' # Pass in HDUList directly + ext (name only), use given label @@ -253,6 +261,7 @@ def test_parse_jwst_niriss_grism(self, imviz_app): assert data.label == 'contents[SCI,1]' # download_file returns cache loc assert data.shape == (2048, 2048) assert data.coords is None + assert data.meta['RADESYS'] == 'ICRS' assert comp.units == 'DN/s' assert comp.data.shape == (2048, 2048) @@ -267,8 +276,9 @@ def test_parse_hst_drz(self, imviz_app): comp = data.get_component('SCI,1') assert data.label == 'contents[SCI,1]' # download_file returns cache loc assert data.shape == (4300, 4219) + assert_allclose(data.meta['PHOTFLAM'], 7.8711728E-20) assert isinstance(data.coords, WCS) - assert comp.units == '' # "ELECTRONS/S" is not valid + assert comp.units == 'electron/s' assert comp.data.shape == (4300, 4219) # Request specific extension (name only), use given label @@ -277,6 +287,7 @@ def test_parse_hst_drz(self, imviz_app): data = imviz_app.app.data_collection[1] comp = data.get_component('CTX,1') assert data.label == 'jclj01010_drz[CTX,1]' + assert data.meta['EXTNAME'] == 'CTX' assert comp.units == '' # BUNIT is not set # Request specific extension (name + ver), use given label @@ -285,6 +296,7 @@ def test_parse_hst_drz(self, imviz_app): data = imviz_app.app.data_collection[2] comp = data.get_component('WHT,1') assert data.label == 'jclj01010_drz[WHT,1]' + assert data.meta['EXTNAME'] == 'WHT' assert comp.units == '' # BUNIT is not set # Pass in file obj directly @@ -293,18 +305,21 @@ def test_parse_hst_drz(self, imviz_app): parse_data(imviz_app.app, pf, show_in_viewer=False) data = imviz_app.app.data_collection[3] assert data.label.startswith('imviz_data|') and data.label.endswith('[SCI,1]') + assert_allclose(data.meta['PHOTFLAM'], 7.8711728E-20) assert 'SCI,1' in data.components # Request specific extension (name only), use given label parse_data(imviz_app.app, pf, ext='CTX', show_in_viewer=False) data = imviz_app.app.data_collection[4] assert data.label.startswith('imviz_data|') and data.label.endswith('[CTX,1]') + assert data.meta['EXTNAME'] == 'CTX' assert 'CTX,1' in data.components # Pass in HDU directly, use given label parse_data(imviz_app.app, pf[2], data_label='foo', show_in_viewer=False) data = imviz_app.app.data_collection[5] assert data.label == 'foo[WHT,1]' + assert data.meta['EXTNAME'] == 'WHT' assert 'WHT,1' in data.components # Load all extensions diff --git a/notebooks/concepts/imviz_simple_aper_phot.ipynb b/notebooks/concepts/imviz_simple_aper_phot.ipynb index 16e8519713..28ba891890 100644 --- a/notebooks/concepts/imviz_simple_aper_phot.ipynb +++ b/notebooks/concepts/imviz_simple_aper_phot.ipynb @@ -66,12 +66,12 @@ "id": "564c06c8", "metadata": {}, "source": [ - "Or we can load HST/ACS images with FITS WCS. This one has no valid unit." + "Or we can load HST/ACS images with FITS WCS." ] }, { "cell_type": "raw", - "id": "16a10237", + "id": "2559f60a", "metadata": {}, "source": [ "acs_47tuc_1 = download_file('https://mast.stsci.edu/api/v0.1/Download/file?uri=mast:HST/product/jbqf03gjq_flc.fits', cache=True)\n", From dcf68c492bb94479a14fad2eba692420cbb32618 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 20 Oct 2021 17:57:12 -0400 Subject: [PATCH 08/24] Grab subset associated with selected data --- .../plugins/aper_phot_simple/aper_phot_simple.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 55769b33e3..a0cc279751 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -97,13 +97,14 @@ def vue_data_selected(self, event): def vue_subset_selected(self, event): subset = None - viewer = self.app.get_viewer_by_id(self._selected_viewer_id) - for lyr in viewer.state.layers: - if lyr.layer.label == event: - subset = lyr.layer - break try: - # TODO: Is this accurate for dithered images linked by WCS? + viewer = self.app.get_viewer_by_id(self._selected_viewer_id) + for lyr in viewer.layers: + if lyr.layer.label == event and lyr.layer.data.label == self._selected_data.label: + subset = lyr.layer + break + + # TODO: https://github.com/glue-viz/glue-astronomy/issues/52 self._selected_subset = subset.data.get_selection_definition( subset_id=event, format='astropy-regions') self._selected_subset.meta['label'] = subset.label From 4c74ad337fd16f03703b982bb4992ae42e777f91 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 20 Oct 2021 18:03:44 -0400 Subject: [PATCH 09/24] Update change log --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7880fc0dfc..ba76760b3b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -58,6 +58,9 @@ Imviz - ``imviz.get_interactive_regions()`` no longer produces long traceback for unsupported region shapes. [#906] +- Imviz now parses some image metadata into ``glue`` and understands + ELECTRONS and ELECTRONS/S defined in FITS BUNIT header keyword. [#938] + Mosviz ^^^^^^ From a52812045bf50b815df5e7c4920eabe3e6398c06 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 20 Oct 2021 18:27:40 -0400 Subject: [PATCH 10/24] Fix metadata extraction for JWST in aper phot --- .../plugins/aper_phot_simple/aper_phot_simple.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index a0cc279751..3d10d284e0 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -63,15 +63,18 @@ def vue_data_selected(self, event): # Extract telescope specific unit conversion factors, if applicable. meta = self._selected_data.meta - telescope = meta.get('TELESCOP', '').lower() + if 'telescope' in meta: + telescope = meta['telescope'] + else: + telescope = meta.get('TELESCOP', '') comp = self._selected_data.get_component(self._selected_data.main_components[0]) - if telescope == 'jwst': - if 'PIXAR_A2' in meta: - self.pixel_scale = meta['PIXAR_A2'] - if (comp.units and u.sr in comp.units.bases and 'photometry' in meta and + if telescope == 'JWST': + if 'photometry' in meta and 'pixelarea_arcsecsq' in meta['photometry']: + self.pixel_scale = meta['photometry']['pixelarea_arcsecsq'] + if (comp.units and 'sr' in comp.units and 'photometry' in meta and 'conversion_megajanskys' in meta['photometry']): self.counts_factor = meta['photometry']['conversion_megajanskys'] - elif telescope == 'hst': + elif telescope == 'HST': # TODO: Add more HST support, as needed. # HST pixel scales are from instrument handbooks. # This is really not used because HST data does not have sr in unit. From 4fc8962abd5332cfc21c60343137502a0788f08b Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Thu, 21 Oct 2021 17:04:59 -0400 Subject: [PATCH 11/24] WIP: Started writing plugin tests [ci skip] --- .../configs/imviz/tests/test_simple_aper_phot.py | 15 +++++++++++++++ jdaviz/configs/imviz/tests/utils.py | 14 ++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 jdaviz/configs/imviz/tests/test_simple_aper_phot.py diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py new file mode 100644 index 0000000000..bef5a5d6b9 --- /dev/null +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -0,0 +1,15 @@ +from jdaviz.configs.imviz.plugins.aper_phot_simple.aper_phot_simple import SimpleAperturePhotometry +from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_WCS + + +class TestSimpleAperPhot(BaseImviz_WCS_WCS): + def test_plugin_wcs_dithered(self): + self.imviz.link_data(link_type='wcs') # They are dithered by 1 pixel on X + self.imviz._apply_interactive_region('bqplot:circle', (0, 0), (9, 9)) # Draw a circle + + phot_plugin = SimpleAperturePhotometry(app=self.imviz.app) + + # Populate plugin menu items + phot_plugin._on_viewer_data_changed() + + # UNTIL HERE diff --git a/jdaviz/configs/imviz/tests/utils.py b/jdaviz/configs/imviz/tests/utils.py index 6178c77ca6..73df85660f 100644 --- a/jdaviz/configs/imviz/tests/utils.py +++ b/jdaviz/configs/imviz/tests/utils.py @@ -45,8 +45,10 @@ def setup_class(self, imviz_app): class BaseImviz_WCS_WCS: @pytest.fixture(autouse=True) def setup_class(self, imviz_app): + arr = np.ones((10, 10)) + # First data with WCS, same as the one in BaseImviz_WCS_NoWCS. - hdu1 = fits.ImageHDU(np.arange(100).reshape((10, 10)), name='SCI') + hdu1 = fits.ImageHDU(arr, name='SCI') hdu1.header.update({'CTYPE1': 'RA---TAN', 'CUNIT1': 'deg', 'CDELT1': -0.0002777777778, @@ -61,21 +63,21 @@ def setup_class(self, imviz_app): 'NAXIS2': 10}) imviz_app.load_data(hdu1, data_label='has_wcs_1') - # Second data with WCS, similar to above but smaller. + # Second data with WCS, similar to above but dithered by 1 pixel in X. # TODO: Use GWCS when https://github.com/spacetelescope/gwcs/issues/99 is possible. - hdu2 = fits.ImageHDU(np.arange(25).reshape((5, 5)), name='SCI') + hdu2 = fits.ImageHDU(arr, name='SCI') hdu2.header.update({'CTYPE1': 'RA---TAN', 'CUNIT1': 'deg', 'CDELT1': -0.0002777777778, - 'CRPIX1': 1, + 'CRPIX1': 2, 'CRVAL1': 337.5202808, - 'NAXIS1': 5, + 'NAXIS1': 10, 'CTYPE2': 'DEC--TAN', 'CUNIT2': 'deg', 'CDELT2': 0.0002777777778, 'CRPIX2': 1, 'CRVAL2': -20.833333059999998, - 'NAXIS2': 5}) + 'NAXIS2': 10}) imviz_app.load_data(hdu2, data_label='has_wcs_2') self.wcs_1 = WCS(hdu1.header) From b5621d110d90de553680cf48f2cab01190b9f20b Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 22 Oct 2021 14:50:33 -0400 Subject: [PATCH 12/24] Add mag unit conversion field --- .../plugins/aper_phot_simple/aper_phot_simple.py | 15 ++++++++++++++- .../plugins/aper_phot_simple/aper_phot_simple.vue | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 3d10d284e0..5cd767a84f 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -22,6 +22,7 @@ class SimpleAperturePhotometry(TemplateMixin): background_value = Any(0).tag(sync=True) pixel_scale = Any(0).tag(sync=True) counts_factor = Any(0).tag(sync=True) + magnitude_zeropoint = Any(0).tag(sync=True) result_available = Bool(False).tag(sync=True) results = List().tag(sync=True) @@ -60,6 +61,7 @@ def vue_data_selected(self, event): self.app.data_collection.labels.index(event)] self.counts_factor = 0 self.pixel_scale = 0 + self.magnitude_zeropoint = 0 # Extract telescope specific unit conversion factors, if applicable. meta = self._selected_data.meta @@ -141,6 +143,7 @@ def vue_do_aper_phot(self, *args, **kwargs): pixscale_fac = 1.0 include_pixscale_fac = False include_counts_fac = False + include_mag_zpt = False if comp.units: img_unit = u.Unit(comp.units) img = img * img_unit @@ -155,6 +158,9 @@ def vue_do_aper_phot(self, *args, **kwargs): ctfac = float(self.counts_factor) if not np.allclose(ctfac, 0): include_counts_fac = True + mag_zpt = float(self.magnitude_zeropoint) + if not np.allclose(mag_zpt, 0): + include_mag_zpt = True apersum = np.nansum(img) * pixscale_fac d = {'id': 1, 'xcenter': reg.center.x * u.pix, @@ -173,6 +179,13 @@ def vue_do_aper_phot(self, *args, **kwargs): else: d.update({'aperture_sum_counts': None, 'counts_fac': None}) + if include_mag_zpt: + mag_zpt = mag_zpt * apersum.unit + d.update({'aperture_sum_mag': -2.5 * np.log10(apersum / mag_zpt) * u.mag, + 'mag_zpt': mag_zpt}) + else: + d.update({'aperture_sum_mag': None, + 'mag_zpt': None}) if include_pixscale_fac: d['pixscale_fac'] = pixscale_fac else: @@ -210,7 +223,7 @@ def vue_do_aper_phot(self, *args, **kwargs): tmp = [] for key, x in d.items(): if key in ('id', 'data_label', 'subset_label', 'background', 'pixscale_fac', - 'counts_fac'): + 'counts_fac', 'mag_zpt'): continue if (isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter', 'npix')): diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index af32f70101..fd3b54f9db 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -61,6 +61,17 @@ + + + + + + From 0a7f2ed0015dc7d5e10f780f9e8a002e0034bea1 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:26:35 -0400 Subject: [PATCH 13/24] WIP: Started tests [ci skip] --- .../aper_phot_simple/aper_phot_simple.py | 6 +- .../imviz/tests/test_simple_aper_phot.py | 65 ++++++++++++++++++- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 5cd767a84f..7f378312f6 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -88,11 +88,11 @@ def vue_data_selected(self, event): if instrument == 'acs': if detector == 'wfc': self.pixel_scale = 0.05 * 0.05 - elif detector == 'hrc': + elif detector == 'hrc': # pragma: no cover self.pixel_scale = 0.028 * 0.025 - elif detector == 'sbc': + elif detector == 'sbc': # pragma: no cover self.pixel_scale = 0.034 * 0.03 - elif instrument == 'wfc3' and detector == 'uvis': + elif instrument == 'wfc3' and detector == 'uvis': # pragma: no cover self.pixel_scale = 0.04 * 0.04 except Exception as e: diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index bef5a5d6b9..afa06f35d8 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -1,3 +1,7 @@ +from astropy import units as u +from astropy.tests.helper import assert_quantity_allclose +from numpy.testing import assert_allclose, assert_array_equal + from jdaviz.configs.imviz.plugins.aper_phot_simple.aper_phot_simple import SimpleAperturePhotometry from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_WCS @@ -9,7 +13,64 @@ def test_plugin_wcs_dithered(self): phot_plugin = SimpleAperturePhotometry(app=self.imviz.app) - # Populate plugin menu items + # Populate plugin menu items. phot_plugin._on_viewer_data_changed() - # UNTIL HERE + # Make sure invalid Data/Subset selection does not crash plugin. + phot_plugin.vue_data_selected('no_such_data') + assert phot_plugin._selected_data is None + phot_plugin.vue_subset_selected('no_such_subset') + assert phot_plugin._selected_subset is None + phot_plugin.vue_do_aper_phot() + assert not phot_plugin.result_available + assert len(phot_plugin.results) == 0 + assert self.imviz.get_aperture_photometry_results() is None + + # Perform photometry on both images using same Subset. + phot_plugin.vue_data_selected('has_wcs_1[SCI,1]') + phot_plugin.vue_subset_selected('Subset 1') + phot_plugin.vue_do_aper_phot() + phot_plugin.vue_data_selected('has_wcs_2[SCI,1]') + phot_plugin.vue_do_aper_phot() + assert_allclose(phot_plugin.background_value, 0) + assert_allclose(phot_plugin.counts_factor, 0) + assert_allclose(phot_plugin.pixel_scale, 0) + assert_allclose(phot_plugin.magnitude_zeropoint, 0) + + # Check photometry results. + tbl = self.imviz.get_aperture_photometry_results() + assert len(tbl) == 2 + assert tbl.colnames == [ + 'id', 'xcenter', 'ycenter', 'sky_center', 'background', 'npix', 'aperture_sum', + 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'mag_zpt', 'pixscale_fac', + 'mean', 'stddev', 'median', 'min', 'max', 'data_label', 'subset_label'] + assert_array_equal(tbl['id'], [1, 2]) + assert_allclose(tbl['background'], 0) + assert_quantity_allclose(tbl['npix'], 63.617251 * u.pix) + #'aperture_sum' + #'aperture_sum_counts' + #'counts_fac' + #'aperture_sum_mag' + assert_allclose(tbl['mag_zpt'], 0) # None? + assert_allclose(tbl['pixscale_fac'], 0) # None? + assert_allclose(tbl['mean'], 1) + assert_allclose(tbl['stddev'], 0) + assert_allclose(tbl['median'], 1) + assert_allclose(tbl['min'], 1) + assert_allclose(tbl['max'], 1) + assert tbl['data_label'] == ['has_wcs_1[SCI,1]', 'has_wcs_2[SCI,1]'] + assert tbl['subset_label'] == ['Subset 1', 'Subset 1'] + + # BUG: https://github.com/glue-viz/glue-astronomy/issues/52 + # Sky should have been the same and the pix different, but not until bug is fixed. + assert_quantity_allclose(tbl['xcenter'], 4.5 * u.pix) + assert_quantity_allclose(tbl['ycenter'], 4.5 * u.pix) + sky = tbl['sky_center'] + assert_allclose(sky.ra.deg, [337.518943, 337.519241]) + assert_allclose(sky.dec.deg, [-20.832083, -20.832083]) + + # TODO: Ellipse + #self.imviz.app._aper_phot_results = None + + # TODO: Rectangle + #self.imviz.app._aper_phot_results = None From 2f427a515981c2b3d5b6494ba53a986102297de0 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Mon, 25 Oct 2021 17:40:10 -0400 Subject: [PATCH 14/24] Address demo feedback: * Rename pixel scale to pixel area to be more accurate. * Rename mag zeropoint to flux scaling to be more accurate. * Add calculation timestamp as astropy.time.Time object. [ci skip] --- .../aper_phot_simple/aper_phot_simple.py | 66 ++++++++++--------- .../aper_phot_simple/aper_phot_simple.vue | 12 ++-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 7f378312f6..c0ed2c720e 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -1,6 +1,9 @@ +from datetime import datetime + import numpy as np from astropy import units as u from astropy.table import QTable +from astropy.time import Time from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage from glue.core.subset import Subset from traitlets import Any, Bool, List @@ -20,9 +23,9 @@ class SimpleAperturePhotometry(TemplateMixin): dc_items = List([]).tag(sync=True) subset_items = List([]).tag(sync=True) background_value = Any(0).tag(sync=True) - pixel_scale = Any(0).tag(sync=True) + pixel_area = Any(0).tag(sync=True) counts_factor = Any(0).tag(sync=True) - magnitude_zeropoint = Any(0).tag(sync=True) + flux_scaling = Any(0).tag(sync=True) result_available = Bool(False).tag(sync=True) results = List().tag(sync=True) @@ -60,8 +63,8 @@ def vue_data_selected(self, event): self._selected_data = self.app.data_collection[ self.app.data_collection.labels.index(event)] self.counts_factor = 0 - self.pixel_scale = 0 - self.magnitude_zeropoint = 0 + self.pixel_area = 0 + self.flux_scaling = 0 # Extract telescope specific unit conversion factors, if applicable. meta = self._selected_data.meta @@ -72,7 +75,7 @@ def vue_data_selected(self, event): comp = self._selected_data.get_component(self._selected_data.main_components[0]) if telescope == 'JWST': if 'photometry' in meta and 'pixelarea_arcsecsq' in meta['photometry']: - self.pixel_scale = meta['photometry']['pixelarea_arcsecsq'] + self.pixel_area = meta['photometry']['pixelarea_arcsecsq'] if (comp.units and 'sr' in comp.units and 'photometry' in meta and 'conversion_megajanskys' in meta['photometry']): self.counts_factor = meta['photometry']['conversion_megajanskys'] @@ -87,13 +90,13 @@ def vue_data_selected(self, event): detector = meta.get('DETECTOR', '').lower() if instrument == 'acs': if detector == 'wfc': - self.pixel_scale = 0.05 * 0.05 + self.pixel_area = 0.05 * 0.05 elif detector == 'hrc': # pragma: no cover - self.pixel_scale = 0.028 * 0.025 + self.pixel_area = 0.028 * 0.025 elif detector == 'sbc': # pragma: no cover - self.pixel_scale = 0.034 * 0.03 + self.pixel_area = 0.034 * 0.03 elif instrument == 'wfc3' and detector == 'uvis': # pragma: no cover - self.pixel_scale = 0.04 * 0.04 + self.pixel_area = 0.04 * 0.04 except Exception as e: self._selected_data = None @@ -140,28 +143,28 @@ def vue_do_aper_phot(self, *args, **kwargs): img = aper_mask.get_values(comp_no_bg, mask=None) aper_mask_stat = reg.to_mask(mode='center') img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) - pixscale_fac = 1.0 - include_pixscale_fac = False + pixarea_fac = 1.0 + include_pixarea_fac = False include_counts_fac = False - include_mag_zpt = False + include_flux_scale = False if comp.units: img_unit = u.Unit(comp.units) img = img * img_unit img_stat = img_stat * img_unit bg = bg * img_unit if u.sr in img_unit.bases: # TODO: Better way to do this? - pixscale = float(self.pixel_scale) * (u.arcsec * u.arcsec / u.pix) - if not np.allclose(pixscale, 0): - pixscale_fac = npix * pixscale.to(u.sr / u.pix) - include_pixscale_fac = True + pixarea = float(self.pixel_area) * (u.arcsec * u.arcsec / u.pix) + if not np.allclose(pixarea, 0): + pixarea_fac = npix * pixarea.to(u.sr / u.pix) + include_pixarea_fac = True if img_unit != u.count: ctfac = float(self.counts_factor) if not np.allclose(ctfac, 0): include_counts_fac = True - mag_zpt = float(self.magnitude_zeropoint) - if not np.allclose(mag_zpt, 0): - include_mag_zpt = True - apersum = np.nansum(img) * pixscale_fac + flux_scale = float(self.flux_scaling) + if not np.allclose(flux_scale, 0): + include_flux_scale = True + apersum = np.nansum(img) * pixarea_fac d = {'id': 1, 'xcenter': reg.center.x * u.pix, 'ycenter': reg.center.y * u.pix} @@ -179,17 +182,17 @@ def vue_do_aper_phot(self, *args, **kwargs): else: d.update({'aperture_sum_counts': None, 'counts_fac': None}) - if include_mag_zpt: - mag_zpt = mag_zpt * apersum.unit - d.update({'aperture_sum_mag': -2.5 * np.log10(apersum / mag_zpt) * u.mag, - 'mag_zpt': mag_zpt}) + if include_flux_scale: + flux_scale = flux_scale * apersum.unit + d.update({'aperture_sum_mag': -2.5 * np.log10(apersum / flux_scale) * u.mag, + 'flux_scaling': flux_scale}) else: d.update({'aperture_sum_mag': None, - 'mag_zpt': None}) - if include_pixscale_fac: - d['pixscale_fac'] = pixscale_fac + 'flux_scaling': None}) + if include_pixarea_fac: + d['pixarea_tot'] = pixarea_fac else: - d['pixscale_fac'] = None + d['pixarea_tot'] = None # Extra stats beyond photutils. d.update({'mean': np.nanmean(img_stat), @@ -198,7 +201,8 @@ def vue_do_aper_phot(self, *args, **kwargs): 'min': np.nanmin(img_stat), 'max': np.nanmax(img_stat), 'data_label': data.label, - 'subset_label': reg.meta.get('label', '')}) + 'subset_label': reg.meta.get('label', ''), + 'timestamp': Time(datetime.utcnow())}) # Attach to app for Python extraction. if (not hasattr(self.app, '_aper_phot_results') or @@ -222,8 +226,8 @@ def vue_do_aper_phot(self, *args, **kwargs): # Parse results for GUI. tmp = [] for key, x in d.items(): - if key in ('id', 'data_label', 'subset_label', 'background', 'pixscale_fac', - 'counts_fac', 'mag_zpt'): + if key in ('id', 'data_label', 'subset_label', 'background', 'pixarea_tot', + 'counts_fac', 'flux_scaling', 'timestamp'): continue if (isinstance(x, (int, float, u.Quantity)) and key not in ('xcenter', 'ycenter', 'npix')): diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index fd3b54f9db..c3c3fbd22c 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -42,9 +42,9 @@ @@ -64,9 +64,9 @@ From 52f48e581e11a4eb5993ce207997b7a1c7e3a583 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Tue, 26 Oct 2021 17:14:52 -0400 Subject: [PATCH 15/24] Add support for rectangle region. Add tests for numpy array and JWST data. --- .../aper_phot_simple/aper_phot_simple.py | 7 ++- jdaviz/configs/imviz/tests/test_parser.py | 36 ++++++++++- .../imviz/tests/test_simple_aper_phot.py | 62 +++++++++++++++---- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index c0ed2c720e..41cc25c119 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -6,6 +6,7 @@ from astropy.time import Time from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage from glue.core.subset import Subset +from regions.shapes.rectangle import RectanglePixelRegion from traitlets import Any, Bool, List from jdaviz.configs.imviz.helper import layer_is_image_data @@ -138,7 +139,11 @@ def vue_do_aper_phot(self, *args, **kwargs): comp_no_bg = comp.data - bg # TODO: Use photutils when it supports astropy regions. - aper_mask = reg.to_mask(mode='exact') + if not isinstance(reg, RectanglePixelRegion): + aper_mask = reg.to_mask(mode='exact') + else: + # TODO: https://github.com/astropy/regions/issues/404 (moot if we use photutils?) + aper_mask = reg.to_mask(mode='subpixels', subpixels=32) npix = np.sum(aper_mask) * u.pix img = aper_mask.get_values(comp_no_bg, mask=None) aper_mask_stat = reg.to_mask(mode='center') diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index cd0611de22..a42446d9d0 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -3,6 +3,7 @@ from astropy import units as u from astropy.io import fits from astropy.nddata import NDData, StdDevUncertainty +from astropy.tests.helper import assert_quantity_allclose from astropy.utils.data import download_file from astropy.wcs import WCS from gwcs import WCS as GWCS @@ -11,6 +12,7 @@ from skimage.io import imsave from jdaviz.configs.imviz.helper import split_filename_with_fits_ext +from jdaviz.configs.imviz.plugins.aper_phot_simple.aper_phot_simple import SimpleAperturePhotometry from jdaviz.configs.imviz.plugins.parsers import ( parse_data, _validate_fits_image2d, _validate_bunit, _parse_image) @@ -194,11 +196,39 @@ def test_parse_jwst_nircam_level2(self, imviz_app): # --- Since download is expensive, we attach GWCS-specific tests here. --- # Ensure interactive region supports GWCS. Also see test_regions.py - imviz_app._apply_interactive_region('bqplot:circle', (0, 0), (1000, 1000)) + imviz_app._apply_interactive_region('bqplot:circle', (965, 1122), (976.9, 1110.1)) # Star subsets = imviz_app.get_interactive_regions() assert list(subsets.keys()) == ['Subset 1'], subsets assert isinstance(subsets['Subset 1'], CirclePixelRegion) + # Test simple aperture photometry plugin. + phot_plugin = SimpleAperturePhotometry(app=imviz_app.app) + phot_plugin._on_viewer_data_changed() + phot_plugin.vue_data_selected('contents[DATA]') + phot_plugin.vue_subset_selected('Subset 1') + phot_plugin.background_value = 0.22 # Median on whole array + phot_plugin.flux_scaling = 1 # Simple mag, no zeropoint + phot_plugin.vue_do_aper_phot() + tbl = imviz_app.get_aperture_photometry_results() + assert_quantity_allclose(tbl['xcenter'], 970.95 * u.pix) + assert_quantity_allclose(tbl['ycenter'], 1116.05 * u.pix) + sky = tbl['sky_center'] + assert_allclose(sky.ra.deg, 80.48419863) + assert_allclose(sky.dec.deg, -69.49460838) + assert_quantity_allclose(tbl['background'], 0.22 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['npix'], 111.22023392 * u.pix) + assert_quantity_allclose(tbl['aperture_sum'], 4.93689560e-09 * u.MJy) + assert_quantity_allclose(tbl['aperture_sum_counts'], 7.89817955e-09 * (u.count / u.s)) + assert_quantity_allclose(tbl['counts_fac'], 0.62506753 * (u.MJy * u.s / u.ct)) + assert_quantity_allclose(tbl['aperture_sum_mag'], 20.76636514207734 * u.mag) + assert_quantity_allclose(tbl['flux_scaling'], 1 * u.MJy) + assert_quantity_allclose(tbl['pixarea_tot'], 1.03843779e-11 * u.sr) + assert_quantity_allclose(tbl['mean'], 4.34584047 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['stddev'], 15.61862628 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['median'], 0.43709442 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['min'], -0.00485992 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['max'], 138.87786865 * (u.MJy / u.sr)) + # --- Back to parser testing below. --- # Request specific extension (name + ver, but ver is not used), use given label @@ -281,6 +311,10 @@ def test_parse_hst_drz(self, imviz_app): assert comp.units == 'electron/s' assert comp.data.shape == (4300, 4219) + # --- Since download is expensive, we attach FITS WCS-specific tests here. --- + + # TODO: Test simple aperture photometry plugin. + # Request specific extension (name only), use given label parse_data(imviz_app.app, filename, ext='CTX', data_label='jclj01010_drz', show_in_viewer=False) diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index afa06f35d8..be62a8d397 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -42,17 +42,16 @@ def test_plugin_wcs_dithered(self): assert len(tbl) == 2 assert tbl.colnames == [ 'id', 'xcenter', 'ycenter', 'sky_center', 'background', 'npix', 'aperture_sum', - 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'mag_zpt', 'pixscale_fac', - 'mean', 'stddev', 'median', 'min', 'max', 'data_label', 'subset_label'] + 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'flux_scaling', 'pixarea_tot', + 'mean', 'stddev', 'median', 'min', 'max', 'data_label', 'subset_label', 'timestamp'] assert_array_equal(tbl['id'], [1, 2]) assert_allclose(tbl['background'], 0) - assert_quantity_allclose(tbl['npix'], 63.617251 * u.pix) - #'aperture_sum' - #'aperture_sum_counts' - #'counts_fac' - #'aperture_sum_mag' - assert_allclose(tbl['mag_zpt'], 0) # None? - assert_allclose(tbl['pixscale_fac'], 0) # None? + assert_quantity_allclose(tbl['npix'], 63.617251235193315 * u.pix) + assert_array_equal(tbl['aperture_sum_counts'], None) + assert_array_equal(tbl['counts_fac'], None) + assert_array_equal(tbl['aperture_sum_mag'], None) + assert_array_equal(tbl['flux_scaling'], None) + assert_array_equal(tbl['pixarea_tot'], None) assert_allclose(tbl['mean'], 1) assert_allclose(tbl['stddev'], 0) assert_allclose(tbl['median'], 1) @@ -60,17 +59,54 @@ def test_plugin_wcs_dithered(self): assert_allclose(tbl['max'], 1) assert tbl['data_label'] == ['has_wcs_1[SCI,1]', 'has_wcs_2[SCI,1]'] assert tbl['subset_label'] == ['Subset 1', 'Subset 1'] + assert tbl['timestamp'].scale == 'utc' # BUG: https://github.com/glue-viz/glue-astronomy/issues/52 # Sky should have been the same and the pix different, but not until bug is fixed. + # The aperture sum might be different too if mask is off limit in second image. assert_quantity_allclose(tbl['xcenter'], 4.5 * u.pix) assert_quantity_allclose(tbl['ycenter'], 4.5 * u.pix) sky = tbl['sky_center'] assert_allclose(sky.ra.deg, [337.518943, 337.519241]) assert_allclose(sky.dec.deg, [-20.832083, -20.832083]) + assert_allclose(tbl['aperture_sum'], 63.61725123519332) - # TODO: Ellipse - #self.imviz.app._aper_phot_results = None + # Make sure it also works on an ellipse subset. + self.imviz._apply_interactive_region('bqplot:ellipse', (0, 0), (9, 4)) + phot_plugin._on_viewer_data_changed() + phot_plugin.vue_data_selected('has_wcs_1[SCI,1]') + phot_plugin.vue_subset_selected('Subset 2') + phot_plugin.vue_do_aper_phot() + tbl = self.imviz.get_aperture_photometry_results() + assert len(tbl) == 3 # New result is appended + assert tbl[-1]['id'] == 3 + assert_quantity_allclose(tbl[-1]['xcenter'], 4.5 * u.pix) + assert_quantity_allclose(tbl[-1]['ycenter'], 2 * u.pix) + sky = tbl[-1]['sky_center'] + assert_allclose(sky.ra.deg, 337.51894336144454) + assert_allclose(sky.dec.deg, -20.832777499255897) + assert_quantity_allclose(tbl[-1]['npix'], 7.068583470577035 * u.pix) + assert_allclose(tbl[-1]['aperture_sum'], 7.068583470577034) + assert_allclose(tbl[-1]['mean'], 1) + assert tbl[-1]['data_label'] == 'has_wcs_1[SCI,1]' + assert tbl[-1]['subset_label'] == 'Subset 2' - # TODO: Rectangle - #self.imviz.app._aper_phot_results = None + # Make sure it also works on a rectangle subset. + self.imviz._apply_interactive_region('bqplot:rectangle', (0, 0), (9, 9)) + phot_plugin._on_viewer_data_changed() + phot_plugin.vue_data_selected('has_wcs_1[SCI,1]') + phot_plugin.vue_subset_selected('Subset 3') + phot_plugin.vue_do_aper_phot() + tbl = self.imviz.get_aperture_photometry_results() + assert len(tbl) == 4 # New result is appended + assert tbl[-1]['id'] == 4 + assert_quantity_allclose(tbl[-1]['xcenter'], 4.5 * u.pix) + assert_quantity_allclose(tbl[-1]['ycenter'], 4.5 * u.pix) + sky = tbl[-1]['sky_center'] + assert_allclose(sky.ra.deg, 337.518943) + assert_allclose(sky.dec.deg, -20.832083) + assert_quantity_allclose(tbl[-1]['npix'], 81 * u.pix) + assert_allclose(tbl[-1]['aperture_sum'], 81) + assert_allclose(tbl[-1]['mean'], 1) + assert tbl[-1]['data_label'] == 'has_wcs_1[SCI,1]' + assert tbl[-1]['subset_label'] == 'Subset 3' From 778b98cff6ed2b3fe876c6d1af99196ef31486b6 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:46:22 -0400 Subject: [PATCH 16/24] BUG: Fix unit conversion calculations. TST: Finish writing phot plugin tests. --- .../aper_phot_simple/aper_phot_simple.py | 35 ++++++------ jdaviz/configs/imviz/tests/test_parser.py | 57 ++++++++++++++----- .../imviz/tests/test_simple_aper_phot.py | 34 ++++++++--- 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 41cc25c119..5232a37c5c 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -148,7 +148,6 @@ def vue_do_aper_phot(self, *args, **kwargs): img = aper_mask.get_values(comp_no_bg, mask=None) aper_mask_stat = reg.to_mask(mode='center') img_stat = aper_mask_stat.get_values(comp_no_bg, mask=None) - pixarea_fac = 1.0 include_pixarea_fac = False include_counts_fac = False include_flux_scale = False @@ -157,10 +156,9 @@ def vue_do_aper_phot(self, *args, **kwargs): img = img * img_unit img_stat = img_stat * img_unit bg = bg * img_unit - if u.sr in img_unit.bases: # TODO: Better way to do this? - pixarea = float(self.pixel_area) * (u.arcsec * u.arcsec / u.pix) + if u.sr in img_unit.bases: # TODO: Better way to detect surface brightness unit? + pixarea = float(self.pixel_area) if not np.allclose(pixarea, 0): - pixarea_fac = npix * pixarea.to(u.sr / u.pix) include_pixarea_fac = True if img_unit != u.count: ctfac = float(self.counts_factor) @@ -169,7 +167,7 @@ def vue_do_aper_phot(self, *args, **kwargs): flux_scale = float(self.flux_scaling) if not np.allclose(flux_scale, 0): include_flux_scale = True - apersum = np.nansum(img) * pixarea_fac + rawsum = np.nansum(img) d = {'id': 1, 'xcenter': reg.center.x * u.pix, 'ycenter': reg.center.y * u.pix} @@ -178,26 +176,29 @@ def vue_do_aper_phot(self, *args, **kwargs): else: d['sky_center'] = None d.update({'background': bg, - 'npix': npix, - 'aperture_sum': apersum}) + 'npix': npix}) + if include_pixarea_fac: + pixarea = pixarea * (u.arcsec * u.arcsec / u.pix) + pixarea_fac = npix * pixarea.to(u.sr / u.pix) + d.update({'aperture_sum': rawsum * pixarea_fac, + 'pixarea_tot': pixarea_fac}) + else: + d.update({'aperture_sum': rawsum, + 'pixarea_tot': None}) if include_counts_fac: - counts_fac = ctfac * (apersum.unit / (u.count / u.s)) - d.update({'aperture_sum_counts': apersum / counts_fac, - 'counts_fac': counts_fac}) + ctfac = ctfac * (rawsum.unit / (u.count / u.s)) + d.update({'aperture_sum_counts': rawsum / ctfac, + 'counts_fac': ctfac}) else: d.update({'aperture_sum_counts': None, 'counts_fac': None}) if include_flux_scale: - flux_scale = flux_scale * apersum.unit - d.update({'aperture_sum_mag': -2.5 * np.log10(apersum / flux_scale) * u.mag, + flux_scale = flux_scale * rawsum.unit + d.update({'aperture_sum_mag': -2.5 * np.log10(rawsum / flux_scale) * u.mag, 'flux_scaling': flux_scale}) else: d.update({'aperture_sum_mag': None, 'flux_scaling': None}) - if include_pixarea_fac: - d['pixarea_tot'] = pixarea_fac - else: - d['pixarea_tot'] = None # Extra stats beyond photutils. d.update({'mean': np.nanmean(img_stat), @@ -221,7 +222,7 @@ def vue_do_aper_phot(self, *args, **kwargs): d['id'] = 1 self.app._aper_phot_results = _qtable_from_dict(d) - except Exception as e: + except Exception as e: # pragma: no cover self.result_available = False self.results = [] self.hub.broadcast(SnackbarMessage( diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index a42446d9d0..9cc9921987 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -7,7 +7,7 @@ from astropy.utils.data import download_file from astropy.wcs import WCS from gwcs import WCS as GWCS -from numpy.testing import assert_allclose +from numpy.testing import assert_allclose, assert_array_equal from regions import CirclePixelRegion from skimage.io import imsave @@ -215,19 +215,20 @@ def test_parse_jwst_nircam_level2(self, imviz_app): sky = tbl['sky_center'] assert_allclose(sky.ra.deg, 80.48419863) assert_allclose(sky.dec.deg, -69.49460838) - assert_quantity_allclose(tbl['background'], 0.22 * (u.MJy / u.sr)) + data_unit = u.MJy / u.sr + assert_quantity_allclose(tbl['background'], 0.22 * data_unit) assert_quantity_allclose(tbl['npix'], 111.22023392 * u.pix) assert_quantity_allclose(tbl['aperture_sum'], 4.93689560e-09 * u.MJy) - assert_quantity_allclose(tbl['aperture_sum_counts'], 7.89817955e-09 * (u.count / u.s)) - assert_quantity_allclose(tbl['counts_fac'], 0.62506753 * (u.MJy * u.s / u.ct)) - assert_quantity_allclose(tbl['aperture_sum_mag'], 20.76636514207734 * u.mag) - assert_quantity_allclose(tbl['flux_scaling'], 1 * u.MJy) - assert_quantity_allclose(tbl['pixarea_tot'], 1.03843779e-11 * u.sr) - assert_quantity_allclose(tbl['mean'], 4.34584047 * (u.MJy / u.sr)) - assert_quantity_allclose(tbl['stddev'], 15.61862628 * (u.MJy / u.sr)) - assert_quantity_allclose(tbl['median'], 0.43709442 * (u.MJy / u.sr)) - assert_quantity_allclose(tbl['min'], -0.00485992 * (u.MJy / u.sr)) - assert_quantity_allclose(tbl['max'], 138.87786865 * (u.MJy / u.sr)) + assert_quantity_allclose(tbl['pixarea_tot'], 1.0384377922763469e-11 * u.sr) + assert_quantity_allclose(tbl['aperture_sum_counts'], 760.5828303030021 * (u.count / u.s)) + assert_quantity_allclose(tbl['counts_fac'], 0.62506753 * (data_unit / (u.ct / u.s))) + assert_quantity_allclose(tbl['aperture_sum_mag'], -6.692683645358997 * u.mag) + assert_quantity_allclose(tbl['flux_scaling'], 1 * data_unit) + assert_quantity_allclose(tbl['mean'], 4.34584047 * data_unit) + assert_quantity_allclose(tbl['stddev'], 15.61862628 * data_unit) + assert_quantity_allclose(tbl['median'], 0.43709442 * data_unit) + assert_quantity_allclose(tbl['min'], -0.00485992 * data_unit) + assert_quantity_allclose(tbl['max'], 138.87786865 * data_unit) # --- Back to parser testing below. --- @@ -301,7 +302,7 @@ def test_parse_hst_drz(self, imviz_app): filename = download_file(url, cache=True) # Default behavior: Load first image - parse_data(imviz_app.app, filename, show_in_viewer=False) + parse_data(imviz_app.app, filename, show_in_viewer=True) data = imviz_app.app.data_collection[0] comp = data.get_component('SCI,1') assert data.label == 'contents[SCI,1]' # download_file returns cache loc @@ -313,7 +314,35 @@ def test_parse_hst_drz(self, imviz_app): # --- Since download is expensive, we attach FITS WCS-specific tests here. --- - # TODO: Test simple aperture photometry plugin. + # Test simple aperture photometry plugin. + imviz_app._apply_interactive_region('bqplot:ellipse', (1465, 2541), (1512, 2611)) # Galaxy + phot_plugin = SimpleAperturePhotometry(app=imviz_app.app) + phot_plugin._on_viewer_data_changed() + phot_plugin.vue_data_selected('contents[SCI,1]') + phot_plugin.vue_subset_selected('Subset 1') + phot_plugin.background_value = 0.0014 # Median on whole array + assert_allclose(phot_plugin.pixel_area, 0.0025) # Not used but still auto-populated + phot_plugin.vue_do_aper_phot() + tbl = imviz_app.get_aperture_photometry_results() + assert_quantity_allclose(tbl['xcenter'], 1488.5 * u.pix) + assert_quantity_allclose(tbl['ycenter'], 2576 * u.pix) + sky = tbl['sky_center'] + assert_allclose(sky.ra.deg, 3.6840882015888323) + assert_allclose(sky.dec.deg, 10.802065746813046) + data_unit = u.electron / u.s + assert_quantity_allclose(tbl['background'], 0.0014 * data_unit) + assert_quantity_allclose(tbl['npix'], 645.98998939 * u.pix) + assert_quantity_allclose(tbl['aperture_sum'], 81.01186025 * data_unit) + assert_array_equal(tbl['pixarea_tot'], None) + assert_array_equal(tbl['aperture_sum_counts'], None) + assert_array_equal(tbl['counts_fac'], None) + assert_array_equal(tbl['aperture_sum_mag'], None) + assert_array_equal(tbl['flux_scaling'], None) + assert_quantity_allclose(tbl['mean'], 0.12466364 * data_unit) + assert_quantity_allclose(tbl['stddev'], 0.1784373 * data_unit) + assert_quantity_allclose(tbl['median'], 0.06988327 * data_unit) + assert_quantity_allclose(tbl['min'], 0.0013524 * data_unit) + assert_quantity_allclose(tbl['max'], 1.5221194 * data_unit) # Request specific extension (name only), use given label parse_data(imviz_app.app, filename, ext='CTX', diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index be62a8d397..0bc42427e3 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -3,7 +3,7 @@ from numpy.testing import assert_allclose, assert_array_equal from jdaviz.configs.imviz.plugins.aper_phot_simple.aper_phot_simple import SimpleAperturePhotometry -from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_WCS +from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_WCS, BaseImviz_WCS_NoWCS class TestSimpleAperPhot(BaseImviz_WCS_WCS): @@ -34,31 +34,31 @@ def test_plugin_wcs_dithered(self): phot_plugin.vue_do_aper_phot() assert_allclose(phot_plugin.background_value, 0) assert_allclose(phot_plugin.counts_factor, 0) - assert_allclose(phot_plugin.pixel_scale, 0) - assert_allclose(phot_plugin.magnitude_zeropoint, 0) + assert_allclose(phot_plugin.pixel_area, 0) + assert_allclose(phot_plugin.flux_scaling, 0) # Check photometry results. tbl = self.imviz.get_aperture_photometry_results() assert len(tbl) == 2 assert tbl.colnames == [ 'id', 'xcenter', 'ycenter', 'sky_center', 'background', 'npix', 'aperture_sum', - 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'flux_scaling', 'pixarea_tot', + 'pixarea_tot', 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'flux_scaling', 'mean', 'stddev', 'median', 'min', 'max', 'data_label', 'subset_label', 'timestamp'] assert_array_equal(tbl['id'], [1, 2]) assert_allclose(tbl['background'], 0) assert_quantity_allclose(tbl['npix'], 63.617251235193315 * u.pix) + assert_array_equal(tbl['pixarea_tot'], None) assert_array_equal(tbl['aperture_sum_counts'], None) assert_array_equal(tbl['counts_fac'], None) assert_array_equal(tbl['aperture_sum_mag'], None) assert_array_equal(tbl['flux_scaling'], None) - assert_array_equal(tbl['pixarea_tot'], None) assert_allclose(tbl['mean'], 1) assert_allclose(tbl['stddev'], 0) assert_allclose(tbl['median'], 1) assert_allclose(tbl['min'], 1) assert_allclose(tbl['max'], 1) - assert tbl['data_label'] == ['has_wcs_1[SCI,1]', 'has_wcs_2[SCI,1]'] - assert tbl['subset_label'] == ['Subset 1', 'Subset 1'] + assert_array_equal(tbl['data_label'], ['has_wcs_1[SCI,1]', 'has_wcs_2[SCI,1]']) + assert_array_equal(tbl['subset_label'], ['Subset 1', 'Subset 1']) assert tbl['timestamp'].scale == 'utc' # BUG: https://github.com/glue-viz/glue-astronomy/issues/52 @@ -110,3 +110,23 @@ def test_plugin_wcs_dithered(self): assert_allclose(tbl[-1]['mean'], 1) assert tbl[-1]['data_label'] == 'has_wcs_1[SCI,1]' assert tbl[-1]['subset_label'] == 'Subset 3' + + +class TestSimpleAperPhot_NoWCS(BaseImviz_WCS_NoWCS): + def test_plugin_no_wcs(self): + # Most things already tested above, so not re-tested here. + self.imviz._apply_interactive_region('bqplot:circle', (0, 0), (9, 9)) # Draw a circle + phot_plugin = SimpleAperturePhotometry(app=self.imviz.app) + phot_plugin._on_viewer_data_changed() + + phot_plugin.vue_data_selected('has_wcs[SCI,1]') + phot_plugin.vue_subset_selected('Subset 1') + phot_plugin.vue_do_aper_phot() + tbl = self.imviz.get_aperture_photometry_results() + assert len(tbl) == 1 + + phot_plugin.vue_data_selected('no_wcs[SCI,1]') + phot_plugin.vue_do_aper_phot() + tbl = self.imviz.get_aperture_photometry_results() + assert len(tbl) == 1 # Old table discarded due to incompatible column + assert_array_equal(tbl['sky_center'], None) From f058c0033111b25792ca01ed588b14800123e138 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:59:27 -0400 Subject: [PATCH 17/24] TST: Extract row explicitly --- jdaviz/configs/imviz/tests/test_parser.py | 64 +++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 9cc9921987..5ee4b0ed9e 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -210,25 +210,25 @@ def test_parse_jwst_nircam_level2(self, imviz_app): phot_plugin.flux_scaling = 1 # Simple mag, no zeropoint phot_plugin.vue_do_aper_phot() tbl = imviz_app.get_aperture_photometry_results() - assert_quantity_allclose(tbl['xcenter'], 970.95 * u.pix) - assert_quantity_allclose(tbl['ycenter'], 1116.05 * u.pix) - sky = tbl['sky_center'] + assert_quantity_allclose(tbl[0]['xcenter'], 970.95 * u.pix) + assert_quantity_allclose(tbl[0]['ycenter'], 1116.05 * u.pix) + sky = tbl[0]['sky_center'] assert_allclose(sky.ra.deg, 80.48419863) assert_allclose(sky.dec.deg, -69.49460838) data_unit = u.MJy / u.sr - assert_quantity_allclose(tbl['background'], 0.22 * data_unit) - assert_quantity_allclose(tbl['npix'], 111.22023392 * u.pix) - assert_quantity_allclose(tbl['aperture_sum'], 4.93689560e-09 * u.MJy) - assert_quantity_allclose(tbl['pixarea_tot'], 1.0384377922763469e-11 * u.sr) - assert_quantity_allclose(tbl['aperture_sum_counts'], 760.5828303030021 * (u.count / u.s)) - assert_quantity_allclose(tbl['counts_fac'], 0.62506753 * (data_unit / (u.ct / u.s))) - assert_quantity_allclose(tbl['aperture_sum_mag'], -6.692683645358997 * u.mag) - assert_quantity_allclose(tbl['flux_scaling'], 1 * data_unit) - assert_quantity_allclose(tbl['mean'], 4.34584047 * data_unit) - assert_quantity_allclose(tbl['stddev'], 15.61862628 * data_unit) - assert_quantity_allclose(tbl['median'], 0.43709442 * data_unit) - assert_quantity_allclose(tbl['min'], -0.00485992 * data_unit) - assert_quantity_allclose(tbl['max'], 138.87786865 * data_unit) + assert_quantity_allclose(tbl[0]['background'], 0.22 * data_unit) + assert_quantity_allclose(tbl[0]['npix'], 111.22023392 * u.pix) + assert_quantity_allclose(tbl[0]['aperture_sum'], 4.93689560e-09 * u.MJy) + assert_quantity_allclose(tbl[0]['pixarea_tot'], 1.0384377922763469e-11 * u.sr) + assert_quantity_allclose(tbl[0]['aperture_sum_counts'], 760.5828303030021 * (u.count / u.s)) + assert_quantity_allclose(tbl[0]['counts_fac'], 0.62506753 * (data_unit / (u.ct / u.s))) + assert_quantity_allclose(tbl[0]['aperture_sum_mag'], -6.692683645358997 * u.mag) + assert_quantity_allclose(tbl[0]['flux_scaling'], 1 * data_unit) + assert_quantity_allclose(tbl[0]['mean'], 4.34584047 * data_unit) + assert_quantity_allclose(tbl[0]['stddev'], 15.61862628 * data_unit) + assert_quantity_allclose(tbl[0]['median'], 0.43709442 * data_unit) + assert_quantity_allclose(tbl[0]['min'], -0.00485992 * data_unit) + assert_quantity_allclose(tbl[0]['max'], 138.87786865 * data_unit) # --- Back to parser testing below. --- @@ -324,25 +324,25 @@ def test_parse_hst_drz(self, imviz_app): assert_allclose(phot_plugin.pixel_area, 0.0025) # Not used but still auto-populated phot_plugin.vue_do_aper_phot() tbl = imviz_app.get_aperture_photometry_results() - assert_quantity_allclose(tbl['xcenter'], 1488.5 * u.pix) - assert_quantity_allclose(tbl['ycenter'], 2576 * u.pix) - sky = tbl['sky_center'] + assert_quantity_allclose(tbl[0]['xcenter'], 1488.5 * u.pix) + assert_quantity_allclose(tbl[0]['ycenter'], 2576 * u.pix) + sky = tbl[0]['sky_center'] assert_allclose(sky.ra.deg, 3.6840882015888323) assert_allclose(sky.dec.deg, 10.802065746813046) data_unit = u.electron / u.s - assert_quantity_allclose(tbl['background'], 0.0014 * data_unit) - assert_quantity_allclose(tbl['npix'], 645.98998939 * u.pix) - assert_quantity_allclose(tbl['aperture_sum'], 81.01186025 * data_unit) - assert_array_equal(tbl['pixarea_tot'], None) - assert_array_equal(tbl['aperture_sum_counts'], None) - assert_array_equal(tbl['counts_fac'], None) - assert_array_equal(tbl['aperture_sum_mag'], None) - assert_array_equal(tbl['flux_scaling'], None) - assert_quantity_allclose(tbl['mean'], 0.12466364 * data_unit) - assert_quantity_allclose(tbl['stddev'], 0.1784373 * data_unit) - assert_quantity_allclose(tbl['median'], 0.06988327 * data_unit) - assert_quantity_allclose(tbl['min'], 0.0013524 * data_unit) - assert_quantity_allclose(tbl['max'], 1.5221194 * data_unit) + assert_quantity_allclose(tbl[0]['background'], 0.0014 * data_unit) + assert_quantity_allclose(tbl[0]['npix'], 645.98998939 * u.pix) + assert_quantity_allclose(tbl[0]['aperture_sum'], 81.01186025 * data_unit) + assert_array_equal(tbl[0]['pixarea_tot'], None) + assert_array_equal(tbl[0]['aperture_sum_counts'], None) + assert_array_equal(tbl[0]['counts_fac'], None) + assert_array_equal(tbl[0]['aperture_sum_mag'], None) + assert_array_equal(tbl[0]['flux_scaling'], None) + assert_quantity_allclose(tbl[0]['mean'], 0.12466364 * data_unit) + assert_quantity_allclose(tbl[0]['stddev'], 0.1784373 * data_unit) + assert_quantity_allclose(tbl[0]['median'], 0.06988327 * data_unit) + assert_quantity_allclose(tbl[0]['min'], 0.0013524 * data_unit) + assert_quantity_allclose(tbl[0]['max'], 1.5221194 * data_unit) # Request specific extension (name only), use given label parse_data(imviz_app.app, filename, ext='CTX', From 146004db581db3b15eb8ebf586f2c29dc0290892 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:39:01 -0400 Subject: [PATCH 18/24] TST: Relax float comparison tolerance --- jdaviz/configs/imviz/tests/test_parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 5ee4b0ed9e..ced9d19910 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -227,8 +227,8 @@ def test_parse_jwst_nircam_level2(self, imviz_app): assert_quantity_allclose(tbl[0]['mean'], 4.34584047 * data_unit) assert_quantity_allclose(tbl[0]['stddev'], 15.61862628 * data_unit) assert_quantity_allclose(tbl[0]['median'], 0.43709442 * data_unit) - assert_quantity_allclose(tbl[0]['min'], -0.00485992 * data_unit) - assert_quantity_allclose(tbl[0]['max'], 138.87786865 * data_unit) + assert_quantity_allclose(tbl[0]['min'], -0.00485992 * data_unit, rtol=1e-5) + assert_quantity_allclose(tbl[0]['max'], 138.87786865 * data_unit, rtol=1e-5) # --- Back to parser testing below. --- @@ -341,8 +341,8 @@ def test_parse_hst_drz(self, imviz_app): assert_quantity_allclose(tbl[0]['mean'], 0.12466364 * data_unit) assert_quantity_allclose(tbl[0]['stddev'], 0.1784373 * data_unit) assert_quantity_allclose(tbl[0]['median'], 0.06988327 * data_unit) - assert_quantity_allclose(tbl[0]['min'], 0.0013524 * data_unit) - assert_quantity_allclose(tbl[0]['max'], 1.5221194 * data_unit) + assert_quantity_allclose(tbl[0]['min'], 0.0013524 * data_unit, rtol=1e-5) + assert_quantity_allclose(tbl[0]['max'], 1.5221194 * data_unit, rtol=1e-5) # Request specific extension (name only), use given label parse_data(imviz_app.app, filename, ext='CTX', From e86a76470d63596b0b2f5a1e79ddd1d28742baff Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:16:09 -0400 Subject: [PATCH 19/24] DOC: Add Imviz plugin docs. DOC: Add intersphinx to astropy regions package doc. --- docs/conf.py | 1 + docs/dev/links.rst | 2 + docs/imviz/displayimages.rst | 3 +- docs/imviz/import_data.rst | 9 ++- docs/imviz/plugins.rst | 116 ++++++++++++++++++++++++++++++++++- 5 files changed, 126 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0c045f8ee5..9b388342f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -242,6 +242,7 @@ # Extra intersphinx in addition to what is already in sphinx-astropy intersphinx_mapping['glue'] = ('http://docs.glueviz.org/en/stable/', None) intersphinx_mapping['glue_jupyter'] = ('https://glue-jupyter.readthedocs.io/en/stable/', None) +intersphinx_mapping['regions'] = ('https://astropy-regions.readthedocs.io/en/stable/', None) intersphinx_mapping['skimage'] = ('https://scikit-image.org/docs/stable/', None) intersphinx_mapping['specutils'] = ('https://specutils.readthedocs.io/en/stable/', None) intersphinx_mapping['spectral_cube'] = ('https://spectral-cube.readthedocs.io/en/stable/', None) diff --git a/docs/dev/links.rst b/docs/dev/links.rst index 3a89c2833b..db8c707d5d 100644 --- a/docs/dev/links.rst +++ b/docs/dev/links.rst @@ -1,3 +1,5 @@ +.. _dev_glue_linking: + *************************** Linking of datasets in glue *************************** diff --git a/docs/imviz/displayimages.rst b/docs/imviz/displayimages.rst index 849a52d9ae..ed7ce9ec7d 100644 --- a/docs/imviz/displayimages.rst +++ b/docs/imviz/displayimages.rst @@ -14,7 +14,6 @@ Selecting Data Set :ref:`Selecting Data Set` Documentation on selecting data sets in the Jdaviz viewers. - Adding New Viewers ================== @@ -23,6 +22,8 @@ that when clicked will add new viewers to the application. You can then select f that has been loaded into the application to be visualized in these additional viewers. You can then utilize some of the Imviz-specific features, like :ref:`imviz_pan_zoom`. +.. _imviz_defining_spatial_regions: + Defining Spatial Regions ======================== diff --git a/docs/imviz/import_data.rst b/docs/imviz/import_data.rst index fdad51b93d..9a33ffd8be 100644 --- a/docs/imviz/import_data.rst +++ b/docs/imviz/import_data.rst @@ -30,4 +30,11 @@ notebook, you have access to the Imviz helper class API. Using this API, users can load data into the application through code using the :meth:`~jdaviz.configs.imviz.helper.Imviz.load_data` method, which takes as input either the name of a local file, an :class:`~astropy.nddata.NDData`, :class:`~astropy.io.fits.HDUList`, -or :class:`~astropy.io.fits.ImageHDU` object. \ No newline at end of file +or :class:`~astropy.io.fits.ImageHDU` object. + +The example below loads the first science extension of the given FITS file into Imviz:: + + from jdaviz import Imviz + imviz = Imviz() + imviz.app + imviz.load_data("/path/to/data/image.fits") diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 975c161ef0..bfca9ca118 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -13,7 +13,19 @@ icon in the upper right corner of the Imviz application. Link Control ============ -Plugin to re-link images by pixels or WCS. +This plugin is used to re-link images by pixels or WCS using +:func:`~jdaviz.configs.imviz.helper.link_image_data`. +All images are automatically linked by pixels on load but you can use +it to re-link by pixels or WCS as needed. Once you have set your +options, click :guilabel:`LINK` to perform the linking. + +For WCS linking, the "fast approximation" option uses an affine transform +to represent the offset between images, if possible. It is much more +performant at the cost of accuracy but should be accurate to within a pixel +for most cases. If approximation fails, WCS linking still automatically +falls back to full transformation. + +For more details on linking, see :ref:`dev_glue_linking`. .. _aper-phot-simple: @@ -21,5 +33,103 @@ Plugin to re-link images by pixels or WCS. Simple Aperture Photometry ========================== -Perform simple aperture photometry on one object within an interactively -selected region. +This plugin performs simple aperture photometry on one object within +an interactively selected region. A typical workflow is as follows: + +1. Load image(s) in Imviz (see :ref:`imviz-import-data`). +2. Draw a region over the object of interest (see :ref:`imviz_defining_spatial_regions`). +3. Select the desired image using :guilabel:`Data` drop-down menu. +4. Select the desired region using :guilabel:`Subset` drop-down menu. +5. If you want to subtract background before performing photometry, enter + the background value in the :guilabel:`Background value` field. This value + must be in the same unit as display data, if applicable. + If your image is already background subtracted, leave it at 0. +6. For some JWST and HST images, pixel area in arcsec squared is automatically + populated in the :guilabel:`Pixel area` field from image metadata. If it does + not auto-populate for you, you can manually enter a value but it must be in the + unit of arcsec squared. This field is only used if per steradian is detected + in display data unit. Otherwise, it is only informational. + If this field is not applicable for you, leave it at 0. + **This field resets every time Data selection changes if auto-population not possible.** +7. For some JWST images, a counts conversion factor is also automatically + populated in the :guilabel:`Counts conversion factor` field from image metadata. + If it does not auto-populate for you, you can manually enter a value but it must + be in the unit of display data unit per counts/s. This is used to convert linear + flux unit (e.g., MJy/sr) to counts/s. If this field is not applicable for you, + leave it at 0. + **This field resets every time Data selection changes if auto-population not possible.** +8. If you also want photometry result in magnitude unit, you can enter a flux + scaling factor in the :guilabel:`Flux scaling` field. The value must be in the + same unit as display data unit. A magnitude is then calculated using + ``-2.5 * log(flux / flux_scaling)``. This calculation only makes sense if your + display data unit is already in linear flux unit. Setting this to 1 is equivalent + to not applying any scaling. If this field is not applicable for you, leave it at 0. + **This field resets every time Data selection changes.** +9. Once all inputs are populated correctly, click on the :guilabel:`CALCULATE` + button to perform simple aperture photometry. + +.. note:: + + Masking and weights by uncertainty are currently not supported. + However, if NaN exists in data, it will be treated as 0. + +When calculation is complete, the results are displayed under the +:guilabel:`CALCULATE` button. You can also retrieve the results as +`~astropy.table.QTable` as follows, assuming ``imviz`` is the instance +of your Imviz application:: + + results = imviz.get_aperture_photometry_results() + +When multiple calculations are done in the same session (e.g., calculating +aperture photometry for the same region across different images or for +different regions on the same image), ``imviz.get_aperture_photometry_results()`` +will return all the calculations in the same table, if possible. +However, if the newest result is incompatible with the existing ones (e.g., two +images have very different units), only the newest is kept in the table. +When you are unsure, save the results after each calculation as different +variables in your Python session. + +The output table contains the results you see in the plugin and then some. +The columns are as follow: + +* ``id``: ID number assigned to the row, starting from 1. +* ``xcenter``, ``ycenter``: Pixel center of the region used. No re-centering + w.r.t. flux distribution is done. +* ``sky_center``: `~astropy.coordinates.SkyCoord` associated with ``xcenter`` + and ``ycenter``. If WCS is not available, this field is `None`. +* ``background``: The value from :guilabel:`Background value`, with unit attached. +* ``npix``: The number of pixels covered by the region. Partial coverage is + reported as fraction. +* ``aperture_sum``: Sum of flux in the aperture. If per steradian is in input + data unit, total pixel area covered in steradian is already multiplied here, + if applicable, so there will be no per steradian in its unit. Otherwise, it + has the same unit as input data. To calculate this, + :meth:`regions.PixelRegion.to_mask` is used with ``mode='exact'`` except + for rectangular region, where it is used with ``mode='subpixels'`` and + ``subpixels=32``. Values from aperture mask are extracted using + :meth:`regions.RegionMask.get_values`. +* ``pixarea_tot``: If per steradian is in input data unit and pixel area is + provided, this contains the total pixel area covered by the aperture in + steradian. Otherwise, it is `None`. +* ``aperture_sum_counts``: This is the aperture sum converted to counts/s, + if :guilabel:`Counts conversion factor` was set. Otherwise, it is `None`. + This calculation is done without taking account of ``pixarea_tot``, even + when it is available. +* ``counts_fac``: The value from :guilabel:`Counts conversion factor`, with + unit attached, if applicable. Otherwise, it is `None`. +* ``aperture_sum_mag``: This is the aperture sum converted to magnitude, if + :guilabel:`Flux scaling` was set. Otherwise, it is `None`. This calculation + is done without taking account of ``pixarea_tot``, even when it is available. +* ``flux_scaling``: The value from :guilabel:`Flux scaling`, with unit attached, + if applicable. Otherwise, it is `None`. +* ``mean``, ``stddev``, ``median``, ``min``, ``max``: Basic statistics from all + the pixels in the region. These are done using :meth:`regions.PixelRegion.to_mask` + with ``mode='center'``, unlike ``aperture_sum``. They are not related to + the aperture photometry, but are only provided as supplemental information. +* ``data_label``: Data label of the image used. +* ``subset_label``: Subset label of the region used. +* ``timestamp``: Timestamp of when the photometry was performed as + `~astropy.time.Time`. + +Once you have the results in a table, you can further manipulated them as +documented in :ref:`astropy:astropy-table`. From d23ae72ef61b6c4cd433f8bbf8eecd0c9cec71af Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Thu, 11 Nov 2021 15:46:54 -0500 Subject: [PATCH 20/24] Clearer error message on blank fields --- .../aper_phot_simple/aper_phot_simple.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 5232a37c5c..ea92c593f2 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -135,7 +135,10 @@ def vue_do_aper_phot(self, *args, **kwargs): try: comp = data.get_component(data.main_components[0]) - bg = float(self.background_value) + try: + bg = float(self.background_value) + except ValueError: # Clearer error message + raise ValueError('Missing or invalid background value') comp_no_bg = comp.data - bg # TODO: Use photutils when it supports astropy regions. @@ -157,14 +160,23 @@ def vue_do_aper_phot(self, *args, **kwargs): img_stat = img_stat * img_unit bg = bg * img_unit if u.sr in img_unit.bases: # TODO: Better way to detect surface brightness unit? - pixarea = float(self.pixel_area) + try: + pixarea = float(self.pixel_area) + except ValueError: # Clearer error message + raise ValueError('Missing or invalid pixel area') if not np.allclose(pixarea, 0): include_pixarea_fac = True if img_unit != u.count: - ctfac = float(self.counts_factor) + try: + ctfac = float(self.counts_factor) + except ValueError: # Clearer error message + raise ValueError('Missing or invalid counts conversion factor') if not np.allclose(ctfac, 0): include_counts_fac = True - flux_scale = float(self.flux_scaling) + try: + flux_scale = float(self.flux_scaling) + except ValueError: # Clearer error message + raise ValueError('Missing or invalid flux scaling') if not np.allclose(flux_scale, 0): include_flux_scale = True rawsum = np.nansum(img) From c66292d0a615720c11835e2319590922ba3c6778 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Thu, 11 Nov 2021 17:52:20 -0500 Subject: [PATCH 21/24] Counts conversion to ct instead of ct/s --- docs/imviz/plugins.rst | 13 ++++++------- .../plugins/aper_phot_simple/aper_phot_simple.py | 5 +---- .../plugins/aper_phot_simple/aper_phot_simple.vue | 2 +- jdaviz/configs/imviz/tests/test_parser.py | 10 ++++++---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index bfca9ca118..2fee5187fb 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -51,13 +51,12 @@ an interactively selected region. A typical workflow is as follows: in display data unit. Otherwise, it is only informational. If this field is not applicable for you, leave it at 0. **This field resets every time Data selection changes if auto-population not possible.** -7. For some JWST images, a counts conversion factor is also automatically - populated in the :guilabel:`Counts conversion factor` field from image metadata. - If it does not auto-populate for you, you can manually enter a value but it must - be in the unit of display data unit per counts/s. This is used to convert linear - flux unit (e.g., MJy/sr) to counts/s. If this field is not applicable for you, +7. If you also want photometry result in the unit of counts, you can enter a + conversion factor in the :guilabel:`Counts conversion factor` field. The value + must be in the unit of display data unit per counts. This is used to convert linear + flux unit (e.g., MJy/sr) to counts. If this field is not applicable for you, leave it at 0. - **This field resets every time Data selection changes if auto-population not possible.** + **This field resets every time Data selection changes.** 8. If you also want photometry result in magnitude unit, you can enter a flux scaling factor in the :guilabel:`Flux scaling` field. The value must be in the same unit as display data unit. A magnitude is then calculated using @@ -111,7 +110,7 @@ The columns are as follow: * ``pixarea_tot``: If per steradian is in input data unit and pixel area is provided, this contains the total pixel area covered by the aperture in steradian. Otherwise, it is `None`. -* ``aperture_sum_counts``: This is the aperture sum converted to counts/s, +* ``aperture_sum_counts``: This is the aperture sum converted to counts, if :guilabel:`Counts conversion factor` was set. Otherwise, it is `None`. This calculation is done without taking account of ``pixarea_tot``, even when it is available. diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index ea92c593f2..e00d52f519 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -77,9 +77,6 @@ def vue_data_selected(self, event): if telescope == 'JWST': if 'photometry' in meta and 'pixelarea_arcsecsq' in meta['photometry']: self.pixel_area = meta['photometry']['pixelarea_arcsecsq'] - if (comp.units and 'sr' in comp.units and 'photometry' in meta and - 'conversion_megajanskys' in meta['photometry']): - self.counts_factor = meta['photometry']['conversion_megajanskys'] elif telescope == 'HST': # TODO: Add more HST support, as needed. # HST pixel scales are from instrument handbooks. @@ -198,7 +195,7 @@ def vue_do_aper_phot(self, *args, **kwargs): d.update({'aperture_sum': rawsum, 'pixarea_tot': None}) if include_counts_fac: - ctfac = ctfac * (rawsum.unit / (u.count / u.s)) + ctfac = ctfac * (rawsum.unit / u.count) d.update({'aperture_sum_counts': rawsum / ctfac, 'counts_fac': ctfac}) else: diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue index c3c3fbd22c..3e3a6b4d5f 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.vue @@ -55,7 +55,7 @@ diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index ced9d19910..108f9f6ef9 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -187,8 +187,6 @@ def test_parse_jwst_nircam_level2(self, imviz_app): comp = data.get_component('DATA') assert data.label == 'contents[DATA]' # download_file returns cache loc assert data.shape == (2048, 2048) - # NOTE: jwst.datamodels.find_fits_keyword("PHOTMJSR") - assert_allclose(data.meta['photometry']['conversion_megajanskys'], 0.6250675320625305) assert isinstance(data.coords, GWCS) assert comp.units == 'MJy/sr' assert comp.data.shape == (2048, 2048) @@ -207,6 +205,10 @@ def test_parse_jwst_nircam_level2(self, imviz_app): phot_plugin.vue_data_selected('contents[DATA]') phot_plugin.vue_subset_selected('Subset 1') phot_plugin.background_value = 0.22 # Median on whole array + # NOTE: jwst.datamodels.find_fits_keyword("PHOTMJSR") + phot_plugin.counts_factor = (data.meta['photometry']['conversion_megajanskys'] / + data.meta['exposure']['exposure_time']) + assert_allclose(phot_plugin.counts_factor, 0.0036385915646798953) phot_plugin.flux_scaling = 1 # Simple mag, no zeropoint phot_plugin.vue_do_aper_phot() tbl = imviz_app.get_aperture_photometry_results() @@ -220,8 +222,8 @@ def test_parse_jwst_nircam_level2(self, imviz_app): assert_quantity_allclose(tbl[0]['npix'], 111.22023392 * u.pix) assert_quantity_allclose(tbl[0]['aperture_sum'], 4.93689560e-09 * u.MJy) assert_quantity_allclose(tbl[0]['pixarea_tot'], 1.0384377922763469e-11 * u.sr) - assert_quantity_allclose(tbl[0]['aperture_sum_counts'], 760.5828303030021 * (u.count / u.s)) - assert_quantity_allclose(tbl[0]['counts_fac'], 0.62506753 * (data_unit / (u.ct / u.s))) + assert_quantity_allclose(tbl[0]['aperture_sum_counts'], 130659.2466386 * u.count) + assert_quantity_allclose(tbl[0]['counts_fac'], 0.0036385915646798953 * (data_unit / u.ct)) assert_quantity_allclose(tbl[0]['aperture_sum_mag'], -6.692683645358997 * u.mag) assert_quantity_allclose(tbl[0]['flux_scaling'], 1 * data_unit) assert_quantity_allclose(tbl[0]['mean'], 4.34584047 * data_unit) From 29e2440a8fb4dbe19e3fca3a57ee2991cdf457e5 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:56:01 -0500 Subject: [PATCH 22/24] Poisson error for aperture_sum_counts. Fix PEP 8. --- docs/imviz/plugins.rst | 4 ++++ .../plugins/aper_phot_simple/aper_phot_simple.py | 12 ++++++++---- jdaviz/configs/imviz/tests/test_parser.py | 2 ++ jdaviz/configs/imviz/tests/test_simple_aper_phot.py | 6 ++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 2fee5187fb..91690dd805 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -114,6 +114,10 @@ The columns are as follow: if :guilabel:`Counts conversion factor` was set. Otherwise, it is `None`. This calculation is done without taking account of ``pixarea_tot``, even when it is available. +* ``aperture_sum_counts_err``: This is the Poisson uncertainty (squareroot) + for ``aperture_sum_counts``. Other uncertainty factors like readnoise are + not included. In the plugin, it is displayed within parenthesis next to + the value for ``aperture_sum_counts``. * ``counts_fac``: The value from :guilabel:`Counts conversion factor`, with unit attached, if applicable. Otherwise, it is `None`. * ``aperture_sum_mag``: This is the aperture sum converted to magnitude, if diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index e00d52f519..192dd3313c 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -73,7 +73,6 @@ def vue_data_selected(self, event): telescope = meta['telescope'] else: telescope = meta.get('TELESCOP', '') - comp = self._selected_data.get_component(self._selected_data.main_components[0]) if telescope == 'JWST': if 'photometry' in meta and 'pixelarea_arcsecsq' in meta['photometry']: self.pixel_area = meta['photometry']['pixelarea_arcsecsq'] @@ -196,10 +195,13 @@ def vue_do_aper_phot(self, *args, **kwargs): 'pixarea_tot': None}) if include_counts_fac: ctfac = ctfac * (rawsum.unit / u.count) - d.update({'aperture_sum_counts': rawsum / ctfac, + sum_ct = rawsum / ctfac + d.update({'aperture_sum_counts': sum_ct, + 'aperture_sum_counts_err': np.sqrt(sum_ct.value) * sum_ct.unit, 'counts_fac': ctfac}) else: d.update({'aperture_sum_counts': None, + 'aperture_sum_counts_err': None, 'counts_fac': None}) if include_flux_scale: flux_scale = flux_scale * rawsum.unit @@ -242,13 +244,15 @@ def vue_do_aper_phot(self, *args, **kwargs): tmp = [] for key, x in d.items(): if key in ('id', 'data_label', 'subset_label', 'background', 'pixarea_tot', - 'counts_fac', 'flux_scaling', 'timestamp'): + 'counts_fac', 'aperture_sum_counts_err', 'flux_scaling', 'timestamp'): continue if (isinstance(x, (int, float, u.Quantity)) and - key not in ('xcenter', 'ycenter', 'npix')): + key not in ('xcenter', 'ycenter', 'npix', 'aperture_sum_counts')): x = f'{x:.4e}' elif key == 'npix': x = f'{x:.1f}' + elif key == 'aperture_sum_counts' and x is not None: + x = f'{x:.4e} ({d["aperture_sum_counts_err"]:.4e})' elif not isinstance(x, str): x = str(x) tmp.append({'function': key, 'result': x}) diff --git a/jdaviz/configs/imviz/tests/test_parser.py b/jdaviz/configs/imviz/tests/test_parser.py index 108f9f6ef9..1d7a92b2df 100644 --- a/jdaviz/configs/imviz/tests/test_parser.py +++ b/jdaviz/configs/imviz/tests/test_parser.py @@ -223,6 +223,7 @@ def test_parse_jwst_nircam_level2(self, imviz_app): assert_quantity_allclose(tbl[0]['aperture_sum'], 4.93689560e-09 * u.MJy) assert_quantity_allclose(tbl[0]['pixarea_tot'], 1.0384377922763469e-11 * u.sr) assert_quantity_allclose(tbl[0]['aperture_sum_counts'], 130659.2466386 * u.count) + assert_quantity_allclose(tbl[0]['aperture_sum_counts_err'], 361.46818205562715 * u.count) assert_quantity_allclose(tbl[0]['counts_fac'], 0.0036385915646798953 * (data_unit / u.ct)) assert_quantity_allclose(tbl[0]['aperture_sum_mag'], -6.692683645358997 * u.mag) assert_quantity_allclose(tbl[0]['flux_scaling'], 1 * data_unit) @@ -337,6 +338,7 @@ def test_parse_hst_drz(self, imviz_app): assert_quantity_allclose(tbl[0]['aperture_sum'], 81.01186025 * data_unit) assert_array_equal(tbl[0]['pixarea_tot'], None) assert_array_equal(tbl[0]['aperture_sum_counts'], None) + assert_array_equal(tbl[0]['aperture_sum_counts_err'], None) assert_array_equal(tbl[0]['counts_fac'], None) assert_array_equal(tbl[0]['aperture_sum_mag'], None) assert_array_equal(tbl[0]['flux_scaling'], None) diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index 0bc42427e3..81f697bde0 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -42,13 +42,15 @@ def test_plugin_wcs_dithered(self): assert len(tbl) == 2 assert tbl.colnames == [ 'id', 'xcenter', 'ycenter', 'sky_center', 'background', 'npix', 'aperture_sum', - 'pixarea_tot', 'aperture_sum_counts', 'counts_fac', 'aperture_sum_mag', 'flux_scaling', - 'mean', 'stddev', 'median', 'min', 'max', 'data_label', 'subset_label', 'timestamp'] + 'pixarea_tot', 'aperture_sum_counts', 'aperture_sum_counts_err', 'counts_fac', + 'aperture_sum_mag', 'flux_scaling', 'mean', 'stddev', 'median', 'min', 'max', + 'data_label', 'subset_label', 'timestamp'] assert_array_equal(tbl['id'], [1, 2]) assert_allclose(tbl['background'], 0) assert_quantity_allclose(tbl['npix'], 63.617251235193315 * u.pix) assert_array_equal(tbl['pixarea_tot'], None) assert_array_equal(tbl['aperture_sum_counts'], None) + assert_array_equal(tbl['aperture_sum_counts_err'], None) assert_array_equal(tbl['counts_fac'], None) assert_array_equal(tbl['aperture_sum_mag'], None) assert_array_equal(tbl['flux_scaling'], None) From 90910e5ac1cba41effbcdd7820a1545a10211fc6 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:59:34 -0500 Subject: [PATCH 23/24] DOC: Minor fixes --- docs/imviz/plugins.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 91690dd805..f78f50f368 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -114,10 +114,10 @@ The columns are as follow: if :guilabel:`Counts conversion factor` was set. Otherwise, it is `None`. This calculation is done without taking account of ``pixarea_tot``, even when it is available. -* ``aperture_sum_counts_err``: This is the Poisson uncertainty (squareroot) +* ``aperture_sum_counts_err``: This is the Poisson uncertainty (square root) for ``aperture_sum_counts``. Other uncertainty factors like readnoise are not included. In the plugin, it is displayed within parenthesis next to - the value for ``aperture_sum_counts``. + the value for ``aperture_sum_counts``, if applicable. * ``counts_fac``: The value from :guilabel:`Counts conversion factor`, with unit attached, if applicable. Otherwise, it is `None`. * ``aperture_sum_mag``: This is the aperture sum converted to magnitude, if From 8f3441b0102abc4952fd60ceadccb5d1626dfeff Mon Sep 17 00:00:00 2001 From: Ricky O'Steen Date: Fri, 19 Nov 2021 15:44:17 -0500 Subject: [PATCH 24/24] Update to new template loading mechanism --- .../configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 192dd3313c..cb21a51bab 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -13,14 +13,13 @@ from jdaviz.core.events import AddDataMessage, RemoveDataMessage, SnackbarMessage from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import TemplateMixin -from jdaviz.utils import load_template __all__ = ['SimpleAperturePhotometry'] @tray_registry('imviz-aper-phot-simple', label="Imviz Simple Aperture Photometry") class SimpleAperturePhotometry(TemplateMixin): - template = load_template("aper_phot_simple.vue", __file__).tag(sync=True) + template_file = __file__, "aper_phot_simple.vue" dc_items = List([]).tag(sync=True) subset_items = List([]).tag(sync=True) background_value = Any(0).tag(sync=True)