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 @@
+
+
+
+
+
+
+ Perform aperture photometry for a single region.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calculate
+
+
+
+
+
+
+
+ Result
+ Value
+
+
+
+ {{ item.function }}
+
+ {{ item.result }}
+
+
+
+
+
+
+
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)