Skip to content

Commit

Permalink
Reprojected image will be reference.
Browse files Browse the repository at this point in the history
Added tests and updated doc.
  • Loading branch information
pllim committed Feb 8, 2023
1 parent aacd804 commit 5b8285d
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 17 deletions.
10 changes: 8 additions & 2 deletions docs/imviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,14 @@ Use the `reproject <https://reproject.readthedocs.io/>`_ package to create a new
that is the input image reprojected to its optimal celestial WCS.

Choose the desired image from the data selection menu, if applicable.
Then click on the :guilabel:`REPROJECT` button.
If successful, a new reprojected image will be displayed in Imviz.
Then enter the desired data label for the result.
Next, click on the :guilabel:`REPROJECT` button
(this might take a while to complete, please be patient).

If successful, a new reprojected image with the given label will be
displayed in Imviz's default viewer and is now the viewer's reference data.
The original image before reprojection is also removed from the viewer
(but still available in the application's underlying data collection).

.. _imviz-export-plot:

Expand Down
46 changes: 36 additions & 10 deletions jdaviz/configs/imviz/plugins/reproject/reproject.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from traitlets import Bool
import numpy as np
import time
from glue.core.data import Data
from traitlets import Bool, Unicode

from jdaviz.core.events import SnackbarMessage
from jdaviz.core.registries import tray_registry
Expand All @@ -19,6 +22,7 @@
class Reproject(PluginTemplateMixin, DatasetSelectMixin):
template_file = __file__, "reproject.vue"

results_label = Unicode("Reprojected").tag(sync=True)
reproject_in_progress = Bool(False).tag(sync=True)

def __init__(self, *args, **kwargs):
Expand All @@ -33,15 +37,21 @@ def vue_do_reproject(self, *args, **kwargs):
or self.reproject_in_progress):
return

import reproject

data = self.data_collection[self.dataset_selected]
wcs_in = data.coords
if wcs_in is None:
return

from astropy.nddata import NDData

viewer_reference = f"{self.app.config}-0"
self.reproject_in_progress = True
t_start = time.time()
try:
if self.results_label in self.app.data_collection.labels:
raise ValueError(
f'{self.results_label} is already used, choose another label name.')

# Find WCS where North is pointing up.
wcs_out, shape_out = find_optimal_celestial_wcs([(data.shape, wcs_in)], frame='icrs')

Expand All @@ -50,19 +60,35 @@ def vue_do_reproject(self, *args, **kwargs):
new_arr = reproject_interp((comp.data, wcs_in), wcs_out, shape_out=shape_out,
return_footprint=False)

# TODO: Let user customize new label; have default label not be so ugly.
new_label = f'{self.dataset_selected} (reprojected)'

# Stuff it back into Imviz.
# Stuff it back into Imviz and show in default viewer.
# We don't want to inherit input metadata because it might have wrong (unrotated)
# WCS info in the header metadata.
ndd = NDData(new_arr, wcs=wcs_out)
self.app._jdaviz_helper.load_data(ndd, data_label=new_label)
new_data = Data(label=self.results_label,
coords=wcs_out,
data=np.nan_to_num(new_arr, copy=False))
new_data.meta.update({'orig_label': data.label,
'reproject_version': reproject.__version__})
self.app.add_data(new_data, self.results_label)
self.app.add_data_to_viewer(viewer_reference, self.results_label)

# We unload the unrotated image from default viewer.
# Only do this after we add the reprojected data to avoid JSON warning.
self.app.remove_data_from_viewer(viewer_reference, data.label)

# Make the reprojected image the reference data for the default viewer.
viewer = self.app.get_viewer(viewer_reference)
viewer.state.reference_data = new_data

except Exception as e:
self.hub.broadcast(SnackbarMessage(
f"Failed to reproject {self.dataset_selected}: {repr(e)}",
f"Failed to reproject {data.label}: {repr(e)}",
color='error', sender=self))

else:
t_end = time.time()
self.hub.broadcast(SnackbarMessage(
f"Reprojection of {data.label} took {t_end - t_start:.1f} seconds.",
color='info', sender=self))

finally:
self.reproject_in_progress = False
11 changes: 10 additions & 1 deletion jdaviz/configs/imviz/plugins/reproject/reproject.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<j-tray-plugin
description='Perform reprojection for a single image.'
description='Perform reprojection for a single image to align display to celestial axes.'
:link="'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#reproject'"
:disabled_msg="disabled_msg"
:popout_button="popout_button">
Expand All @@ -14,6 +14,15 @@
/>

<div v-if='dataset_selected'>
<v-row>
<v-col>
<v-text-field
v-model='results_label'
hint="Data label for reprojected data"
persistent-hint
></v-text-field>
</v-col>
</v-row>
<v-row justify="end">
<v-btn color="primary" text @click="do_reproject">Reproject</v-btn>
</v-row>
Expand Down
51 changes: 47 additions & 4 deletions jdaviz/configs/imviz/tests/test_reproject.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
import pytest

reproject = pytest.importorskip("reproject")
from jdaviz.configs.imviz.plugins.reproject.reproject import HAS_REPROJECT
from jdaviz.configs.imviz.tests.utils import BaseImviz_WCS_GWCS


def test_reproject_plugin():
# FIXME
raise NotImplementedError
@pytest.mark.skipif(not HAS_REPROJECT, reason='reproject not installed')
class TestReproject_WCS_GWCS(BaseImviz_WCS_GWCS):
def test_reproject_fits_wcs(self):
self.imviz.link_data(link_type='wcs', error_on_fail=True)

plg = self.imviz.plugins["Reproject"]
assert not plg._obj.disabled_msg

# Attempt to reproject without WCS should be silent no-op.
plg._obj.dataset_selected = "no_wcs"
plg._obj.vue_do_reproject()
assert self.imviz.app.data_collection.labels == ['fits_wcs[DATA]', 'gwcs[DATA]', 'no_wcs']

# Reproject FITS WCS. We do not test the actual reprojection algorithm.
plg._obj.dataset_selected = 'fits_wcs[DATA]'
plg._obj.vue_do_reproject()
assert self.imviz.app.data_collection.labels == ['fits_wcs[DATA]', 'gwcs[DATA]', 'no_wcs',
'Reprojected']
assert self.imviz.app.data_collection['Reprojected'].meta['orig_label'] == 'fits_wcs[DATA]'
# Original data should not be loaded in the viewer anymore.
assert [data.label for data in self.viewer.data()] == ['gwcs[DATA]', 'no_wcs', 'Reprojected']
# Reprojected data now is the viewer reference.
assert self.viewer.state.reference_data.label == 'Reprojected'

# Reproject again using existing label is not allowed.
# Only snackbar message is shown, so that is not tested here. Result should be unchanged.
plg._obj.dataset_selected = 'gwcs[DATA]'
plg._obj.vue_do_reproject()
assert self.imviz.app.data_collection.labels == ['fits_wcs[DATA]', 'gwcs[DATA]', 'no_wcs',
'Reprojected']
assert self.imviz.app.data_collection['Reprojected'].meta['orig_label'] == 'fits_wcs[DATA]'


@pytest.mark.skipif(not HAS_REPROJECT, reason='reproject not installed')
def test_reproject_no_data(imviz_helper):
"""This should be silent no-op."""
plg = imviz_helper.plugins["Reproject"]
plg._obj.vue_do_reproject()
assert len(imviz_helper.app.data_collection) == 0


@pytest.mark.skipif(HAS_REPROJECT, reason='reproject is installed')
def test_reproject_no_reproject(imviz_helper):
plg = imviz_helper.plugins["Reproject"]
assert "Please install reproject" in plg._obj.disabled_msg

0 comments on commit 5b8285d

Please sign in to comment.