From 6861df7a17654ba6ce2ff9cce470d95a2d4ea51e Mon Sep 17 00:00:00 2001 From: Jesse Averbukh Date: Mon, 21 Feb 2022 15:45:07 -0500 Subject: [PATCH 1/2] Add a different radial profile approach --- docs/imviz/plugins.rst | 7 +-- .../aper_phot_simple/aper_phot_simple.py | 45 +++++++++++++++---- .../aper_phot_simple/aper_phot_simple.vue | 10 +++++ .../imviz/tests/test_simple_aper_phot.py | 4 ++ licenses/IMEXAM_LICENSE.txt | 28 ++++++++++++ 5 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 licenses/IMEXAM_LICENSE.txt diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index f8911f0b76..9706213231 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -56,8 +56,8 @@ 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. +3. Select the desired image using the :guilabel:`Data` drop-down menu. +4. Select the desired region using the :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. @@ -82,7 +82,8 @@ an interactively selected region. A typical workflow is as follows: 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` +9. Select the desired plot type using the :guilabel:`Plot Type` drop-down menu. +10. Once all inputs are populated correctly, click on the :guilabel:`CALCULATE` button to perform simple aperture photometry. .. note:: 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 6dc16779be..37284b9267 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 @@ -23,6 +23,7 @@ class SimpleAperturePhotometry(TemplateMixin): template_file = __file__, "aper_phot_simple.vue" dc_items = List([]).tag(sync=True) subset_items = List([]).tag(sync=True) + plot_types = List([]).tag(sync=True) background_value = Any(0).tag(sync=True) pixel_area = Any(0).tag(sync=True) counts_factor = Any(0).tag(sync=True) @@ -43,6 +44,8 @@ def __init__(self, *args, **kwargs): self._selected_data = None self._selected_subset = None + self.plot_types = ["Radial Profile", "Radial Profile (Raw)"] + self.current_plot_type = self.plot_types[0] def reset_results(self): self.result_available = False @@ -125,7 +128,11 @@ def vue_subset_selected(self, event): self.hub.broadcast(SnackbarMessage( f"Failed to extract {event}: {repr(e)}", color='error', sender=self)) + def vue_change_plot_type(self, event): + self.current_plot_type = event + def vue_do_aper_phot(self, *args, **kwargs): + bqplt.clear() if self._selected_data is None or self._selected_subset is None: self.reset_results() self.hub.broadcast(SnackbarMessage( @@ -241,26 +248,46 @@ def vue_do_aper_phot(self, *args, **kwargs): d['id'] = 1 self.app._aper_phot_results = _qtable_from_dict(d) - # Radial profile reg_bb = reg.bounding_box reg_ogrid = np.ogrid[reg_bb.iymin:reg_bb.iymax, reg_bb.ixmin:reg_bb.ixmax] radial_dx = reg_ogrid[1] - reg.center.x radial_dy = reg_ogrid[0] - reg.center.y radial_r = np.hypot(radial_dx, radial_dy).ravel() # pix radial_img = comp_no_bg_cutout.ravel() - if comp.units: - y_data = radial_img.value - y_label = radial_img.unit.to_string() - else: - y_data = radial_img - y_label = 'Value' + + # Radial profile (Raw) + if self.current_plot_type == "Radial Profile (Raw)": + + if comp.units: + y_data = radial_img.value + y_label = radial_img.unit.to_string() + else: + y_data = radial_img + y_label = 'Value' + x_data = radial_r + x_label = "pix" + + # Radial Profile + elif self.current_plot_type == "Radial Profile": + # This algorithm is from the imexam package, + # see licenses/IMEXAM_LICENSE.txt for more details + flux = np.bincount(list(radial_r), radial_img) + radbc = np.bincount(list(radial_r)) + flux = flux / radbc + radius = np.arange(len(flux)) + + x_data = radius + x_label = "radius" + y_data = flux + y_label = "flux" + bqplt.clear() # NOTE: default margin in bqplot is 60 in all directions fig = bqplt.figure(1, title='Radial profile from Subset center', fig_margin={'top': 60, 'bottom': 60, 'left': 40, 'right': 10}, title_style={'font-size': '12px'}) # TODO: Jenn wants title at bottom. # noqa - bqplt.plot(radial_r, y_data, 'go', figure=fig, default_size=1) - bqplt.xlabel(label='pix', mark=fig.marks[-1], figure=fig) + bqplt.plot(x_data, y_data, 'go', figure=fig, default_size=1) + bqplt.xlabel(label=x_label, mark=fig.marks[-1], figure=fig) bqplt.ylabel(label=y_label, mark=fig.marks[-1], figure=fig) except Exception as e: # pragma: no cover 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 bfe0a7f147..ba847981de 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 @@ -66,6 +66,16 @@ + + + + Calculate diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index 9f0e2ae5ff..e561e569ee 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -21,18 +21,21 @@ def test_plugin_wcs_dithered(self): 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_change_plot_type('Radial Profile (Raw)') 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 assert not phot_plugin.plot_available assert phot_plugin.radial_plot == '' + assert phot_plugin.current_plot_type == 'Radial Profile (Raw)' # 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_change_plot_type('Radial Profile') phot_plugin.vue_do_aper_phot() assert_allclose(phot_plugin.background_value, 0) assert_allclose(phot_plugin.counts_factor, 0) @@ -40,6 +43,7 @@ def test_plugin_wcs_dithered(self): assert_allclose(phot_plugin.flux_scaling, 0) assert phot_plugin.plot_available assert phot_plugin.radial_plot != '' # Does not check content + assert phot_plugin.current_plot_type == 'Radial Profile' # Check photometry results. tbl = self.imviz.get_aperture_photometry_results() diff --git a/licenses/IMEXAM_LICENSE.txt b/licenses/IMEXAM_LICENSE.txt new file mode 100644 index 0000000000..5a126fe841 --- /dev/null +++ b/licenses/IMEXAM_LICENSE.txt @@ -0,0 +1,28 @@ +Copyright (c) 2011-2021, Imexam developers + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From c412991920684aa2cd18c09f3ade075651017313 Mon Sep 17 00:00:00 2001 From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com> Date: Tue, 22 Feb 2022 15:02:28 -0500 Subject: [PATCH 2/2] Review from pllim and fix doc --- docs/imviz/plugins.rst | 2 +- .../aper_phot_simple/aper_phot_simple.py | 51 ++++++++----------- .../aper_phot_simple/aper_phot_simple.vue | 4 +- .../imviz/tests/test_simple_aper_phot.py | 7 ++- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 9706213231..eb3b5a2049 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -84,7 +84,7 @@ an interactively selected region. A typical workflow is as follows: **This field resets every time Data selection changes.** 9. Select the desired plot type using the :guilabel:`Plot Type` drop-down menu. 10. Once all inputs are populated correctly, click on the :guilabel:`CALCULATE` - button to perform simple aperture photometry. + button to perform simple aperture photometry. .. note:: 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 37284b9267..d9835155bc 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 @@ -8,7 +8,7 @@ from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage from ipywidgets import widget_serialization from regions.shapes.rectangle import RectanglePixelRegion -from traitlets import Any, Bool, List +from traitlets import Any, Bool, List, Unicode from jdaviz.configs.imviz.helper import layer_is_image_data from jdaviz.core.events import AddDataMessage, RemoveDataMessage, SnackbarMessage @@ -23,13 +23,14 @@ class SimpleAperturePhotometry(TemplateMixin): template_file = __file__, "aper_phot_simple.vue" dc_items = List([]).tag(sync=True) subset_items = List([]).tag(sync=True) - plot_types = List([]).tag(sync=True) background_value = Any(0).tag(sync=True) pixel_area = Any(0).tag(sync=True) counts_factor = Any(0).tag(sync=True) flux_scaling = Any(0).tag(sync=True) result_available = Bool(False).tag(sync=True) results = List().tag(sync=True) + plot_types = List([]).tag(sync=True) + current_plot_type = Unicode().tag(sync=True) plot_available = Bool(False).tag(sync=True) radial_plot = Any('').tag(sync=True, **widget_serialization) @@ -52,6 +53,7 @@ def reset_results(self): self.results = [] self.plot_available = False self.radial_plot = '' + bqplt.clear() def _on_viewer_data_changed(self, msg=None): # To support multiple viewers, we allow the entire data collection. @@ -128,11 +130,7 @@ def vue_subset_selected(self, event): self.hub.broadcast(SnackbarMessage( f"Failed to extract {event}: {repr(e)}", color='error', sender=self)) - def vue_change_plot_type(self, event): - self.current_plot_type = event - def vue_do_aper_phot(self, *args, **kwargs): - bqplt.clear() if self._selected_data is None or self._selected_subset is None: self.reset_results() self.hub.broadcast(SnackbarMessage( @@ -248,6 +246,7 @@ def vue_do_aper_phot(self, *args, **kwargs): d['id'] = 1 self.app._aper_phot_results = _qtable_from_dict(d) + # Radial profile (Raw) reg_bb = reg.bounding_box reg_ogrid = np.ogrid[reg_bb.iymin:reg_bb.iymax, reg_bb.ixmin:reg_bb.ixmax] radial_dx = reg_ogrid[1] - reg.center.x @@ -255,39 +254,31 @@ def vue_do_aper_phot(self, *args, **kwargs): radial_r = np.hypot(radial_dx, radial_dy).ravel() # pix radial_img = comp_no_bg_cutout.ravel() - # Radial profile (Raw) - if self.current_plot_type == "Radial Profile (Raw)": - - if comp.units: - y_data = radial_img.value - y_label = radial_img.unit.to_string() - else: - y_data = radial_img - y_label = 'Value' - x_data = radial_r - x_label = "pix" + if comp.units: + y_data = radial_img.value + y_label = radial_img.unit.to_string() + else: + y_data = radial_img + y_label = 'Value' - # Radial Profile - elif self.current_plot_type == "Radial Profile": + # Radial profile + if self.current_plot_type == "Radial Profile": # This algorithm is from the imexam package, # see licenses/IMEXAM_LICENSE.txt for more details - flux = np.bincount(list(radial_r), radial_img) - radbc = np.bincount(list(radial_r)) - flux = flux / radbc - radius = np.arange(len(flux)) - - x_data = radius - x_label = "radius" - y_data = flux - y_label = "flux" + radial_r = list(radial_r) + y_data = np.bincount(radial_r, y_data) / np.bincount(radial_r) + radial_r = np.arange(len(y_data)) + markerstyle = 'g--o' + else: + markerstyle = 'go' bqplt.clear() # NOTE: default margin in bqplot is 60 in all directions fig = bqplt.figure(1, title='Radial profile from Subset center', fig_margin={'top': 60, 'bottom': 60, 'left': 40, 'right': 10}, title_style={'font-size': '12px'}) # TODO: Jenn wants title at bottom. # noqa - bqplt.plot(x_data, y_data, 'go', figure=fig, default_size=1) - bqplt.xlabel(label=x_label, mark=fig.marks[-1], figure=fig) + bqplt.plot(radial_r, y_data, markerstyle, figure=fig, default_size=1) + bqplt.xlabel(label='pix', mark=fig.marks[-1], figure=fig) bqplt.ylabel(label=y_label, mark=fig.marks[-1], figure=fig) except Exception as e: # pragma: no cover 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 ba847981de..854ed186b3 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 @@ -69,9 +69,9 @@ diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index e561e569ee..84aa76ce7f 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -21,21 +21,20 @@ def test_plugin_wcs_dithered(self): 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_change_plot_type('Radial Profile (Raw)') 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 assert not phot_plugin.plot_available assert phot_plugin.radial_plot == '' - assert phot_plugin.current_plot_type == 'Radial Profile (Raw)' + assert phot_plugin.current_plot_type == 'Radial Profile' # Software default # 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_change_plot_type('Radial Profile') + phot_plugin.current_plot_type = 'Radial Profile (Raw)' phot_plugin.vue_do_aper_phot() assert_allclose(phot_plugin.background_value, 0) assert_allclose(phot_plugin.counts_factor, 0) @@ -43,7 +42,6 @@ def test_plugin_wcs_dithered(self): assert_allclose(phot_plugin.flux_scaling, 0) assert phot_plugin.plot_available assert phot_plugin.radial_plot != '' # Does not check content - assert phot_plugin.current_plot_type == 'Radial Profile' # Check photometry results. tbl = self.imviz.get_aperture_photometry_results() @@ -86,6 +84,7 @@ def test_plugin_wcs_dithered(self): 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.current_plot_type = 'Radial Profile' phot_plugin.vue_do_aper_phot() tbl = self.imviz.get_aperture_photometry_results() assert len(tbl) == 3 # New result is appended