Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to export subsets #2760

Merged
merged 4 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ New Features

- "Export Plot" plugin is now replaced with the more general "Export" plugin. [#2722]

- "Export" plugin supports exporting plugin tables. [#2755]
- "Export" plugin supports exporting plugin tables and non non-composite spatial subsets.[#2755, #2760]

Cubeviz
^^^^^^^
Expand Down
6 changes: 5 additions & 1 deletion docs/cubeviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,11 @@ have valid flux units. For 3D data, the current :ref:`slice` is used.
Export
======

This plugin allows exporting the plot in a given viewer to various image formats.
This plugin allows exporting:

* the plot in a given viewer to a PNG or SVG file,
* a table in a plugin to ecsv
* subsets as a region to .fits or .reg file.

.. _cubeviz-export-video:

Expand Down
1 change: 1 addition & 0 deletions docs/imviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,4 @@ This plugin allows exporting:

* the plot in a given viewer to a PNG or SVG file,
* a table in a plugin to ecsv
* subsets as a region to .fits or .reg file.
85 changes: 80 additions & 5 deletions jdaviz/configs/default/plugins/export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
ViewerSelectMixin, DatasetMultiSelectMixin,
SubsetSelectMixin, PluginTableSelectMixin,
MultiselectMixin, with_spinner)
from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage

from jdaviz.core.events import AddDataMessage, SnackbarMessage
from jdaviz.core.user_api import PluginUserApi

Expand Down Expand Up @@ -46,7 +48,7 @@ class Export(PluginTemplateMixin, ViewerSelectMixin, SubsetSelectMixin,

# feature flag for cone support
dev_dataset_support = Bool(False).tag(sync=True) # when enabling: add entries to docstring
dev_subset_support = Bool(False).tag(sync=True) # when enabling: add entries to docstring

dev_plot_support = Bool(False).tag(sync=True) # when enabling: add entries to docstring
dev_multi_support = Bool(False).tag(sync=True) # when enabling: add entries to docstring

Expand All @@ -59,8 +61,14 @@ class Export(PluginTemplateMixin, ViewerSelectMixin, SubsetSelectMixin,
table_format_items = List().tag(sync=True)
table_format_selected = Unicode().tag(sync=True)

subset_format_items = List().tag(sync=True)
subset_format_selected = Unicode().tag(sync=True)

filename = Unicode().tag(sync=True)

# if selected subset is spectral or composite, display message and disable export
subset_invalid_msg = Unicode().tag(sync=True)

# For Cubeviz movie.
movie_enabled = Bool(False).tag(sync=True)
i_start = IntHandleEmpty(0).tag(sync=True)
Expand Down Expand Up @@ -100,6 +108,12 @@ def __init__(self, *args, **kwargs):
selected='table_format_selected',
manual_options=table_format_options)

subset_format_options = ['fits', 'reg']
self.subset_format = SelectPluginComponent(self,
items='subset_format_items',
selected='subset_format_selected',
manual_options=subset_format_options)

# default selection:
self.dataset._default_mode = 'empty'
self.table._default_mode = 'empty'
Expand All @@ -110,19 +124,26 @@ def __init__(self, *args, **kwargs):
if self.config == "cubeviz":
self.session.hub.subscribe(self, AddDataMessage, handler=self._on_cubeviz_data_added) # noqa: E501

self.session.hub.subscribe(self, SubsetCreateMessage,
handler=self._set_subset_not_supported_msg)
self.session.hub.subscribe(self, SubsetUpdateMessage,
handler=self._set_subset_not_supported_msg)
self.session.hub.subscribe(self, SubsetDeleteMessage,
handler=self._set_subset_not_supported_msg)

@property
def user_api(self):
# TODO: backwards compat for save_figure, save_movie,
# i_start, i_end, movie_fps, movie_filename
# TODO: expose export method once API is finalized

expose = ['viewer', 'viewer_format',
'subset', 'subset_format',
'table', 'table_format',
'filename', 'export']

if self.dev_dataset_support:
expose += ['dataset']
if self.dev_subset_support:
expose += ['subset']
if self.dev_plot_support:
expose += ['plot']
if self.dev_multi_support:
Expand Down Expand Up @@ -150,6 +171,7 @@ def _sync_multiselect_traitlets(self, event):
@observe('viewer_selected', 'dataset_selected', 'subset_selected',
'table_selected', 'plot_selected')
def _sync_singleselect(self, event):

if not hasattr(self, 'dataset') or not hasattr(self, 'viewer'):
# plugin not fully intialized
return
Expand All @@ -163,6 +185,27 @@ def _sync_singleselect(self, event):
'table_selected', 'plot_selected'):
if name != attr:
setattr(self, attr, '')
if attr == 'subset_selected':
self._set_subset_not_supported_msg()

def _set_subset_not_supported_msg(self, msg=None):
"""
Check if selected subset is spectral or composite, and warn and
disable Export button until these are supported.
"""

if self.subset.selected is not None:
subset = self.app.get_subsets(self.subset.selected)
if self.subset.selected == '':
self.subset_invalid_msg = ''
elif self.app._is_subset_spectral(subset[0]):
self.subset_invalid_msg = 'Export for spectral subsets not supported.'
elif len(subset) > 1:
self.subset_invalid_msg = 'Export for composite subsets not supported.'
else:
self.subset_invalid_msg = ''
else: # no subset selected (can be '' instead of None if previous selection made)
self.subset_invalid_msg = ''

@with_spinner()
def export(self, filename=None, show_dialog=None):
Expand All @@ -176,8 +219,7 @@ def export(self, filename=None, show_dialog=None):
"""
if self.dataset.selected is not None and len(self.dataset.selected):
raise NotImplementedError("dataset export not yet supported")
if self.subset.selected is not None and len(self.subset.selected):
raise NotImplementedError("subset export not yet supported")

if self.plot.selected is not None and len(self.plot.selected):
raise NotImplementedError("plot export not yet supported")
if self.multiselect:
Expand All @@ -200,11 +242,23 @@ def export(self, filename=None, show_dialog=None):
self.save_movie(viewer, filename, filetype)
else:
self.save_figure(viewer, filename, filetype, show_dialog=show_dialog)

elif len(self.table.selected):
filetype = self.table_format.selected
if not filename.endswith(filetype):
filename += f".{filetype}"
self.table.selected_obj.export_table(filename, overwrite=True)

elif len(self.subset.selected):
selected_subset_label = self.subset.selected
kecnry marked this conversation as resolved.
Show resolved Hide resolved
filetype = self.subset_format.selected
if len(filename):
if not filename.endswith(filetype):
filename += f".{filetype}"
if self.subset_invalid_msg != '':
raise NotImplementedError(f'Subset can not be exported - {self.subset_invalid_msg}')
self.save_subset_as_region(selected_subset_label, filename)

else:
raise ValueError("nothing selected for export")
return filename
Expand All @@ -221,6 +275,7 @@ def vue_export_from_ui(self, *args, **kwargs):
f"Exported to {filename}", sender=self, color="success"))

def save_figure(self, viewer, filename=None, filetype="png", show_dialog=False):

if filetype == "png":
if filename is None or show_dialog:
viewer.figure.save_png(str(filename) if filename is not None else None)
Expand Down Expand Up @@ -403,6 +458,26 @@ def save_movie(self, viewer, filename, filetype, i_start=None, i_end=None, fps=N

return filename

def save_subset_as_region(self, selected_subset_label, filename):
"""
Save a subset to file as a Region object in the working directory.
Currently only enabled for non-composite spatial subsets. Can be saved
as a .fits or .reg file. If link type is currently set to 'pixel',
then a pixel region will be saved. If link type is 'wcs', then a sky
region will be saved. If a file with the same name already exists in the
working directory, it will be overwriten.
"""

# type of region saved depends on link type
link_type = getattr(self.app, '_link_type', None)

region = self.app.get_subsets(subset_name=selected_subset_label,
include_sky_region=link_type == 'wcs')

region = region[0][f'{"sky_" if link_type == "wcs" else ""}region']

region.write(filename, overwrite=True)

def vue_interrupt_recording(self, *args): # pragma: no cover
self.movie_interrupt = True
# TODO: this will need updating when batch/multiselect support is added
Expand Down
47 changes: 35 additions & 12 deletions jdaviz/configs/default/plugins/export/export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,39 @@
</plugin-inline-select>
</div>

<div v-if="dev_subset_support && subset_items.length > 0">
<j-plugin-section-header style="margin-top: 12px">Subsets</j-plugin-section-header>
<plugin-inline-select
:items="subset_items"
:selected.sync="subset_selected"
:multiselect="multiselect"
:single_select_allow_blank="true"
>
</plugin-inline-select>
</div>
<div v-if="subset_items.length > 0">
<j-plugin-section-header style="margin-top: 12px">Subsets</j-plugin-section-header>
<v-row>
<span class="v-messages v-messages__message text--secondary"> Save subset as astropy region. </span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. 👌🏻

</v-row>
<div class="section-description">
<plugin-inline-select
:items="subset_items"
:selected.sync="subset_selected"
:multiselect="multiselect"
:single_select_allow_blank="true"
>
</plugin-inline-select>
</div>

<v-row v-if="subset_invalid_msg.length > 0">
<span class="v-messages v-messages__message text--secondary" style="color: red !important">
{{subset_invalid_msg}}
</span>
</v-row>

<v-row v-if="subset_selected" class="row-min-bottom-padding">
<v-select
:menu-props="{ left: true }"
attach
v-model="subset_format_selected"
:items="subset_format_items.map(i => i.label)"
label="Format"
hint="Format for exporting subsets."
persistent-hint
>
</v-select>
</v-row>

<div v-if="table_items.length > 0">
<j-plugin-section-header style="margin-top: 12px">Plugin Tables</j-plugin-section-header>
Expand Down Expand Up @@ -162,13 +185,13 @@
@click="export_from_ui"
:spinner="spinner"
:disabled="filename.length === 0 ||
movie_recording ||
movie_recording ||
subset_invalid_msg.length > 0 ||
(viewer_selected.length > 0 && viewer_format_selected == 'mp4' && !movie_enabled)"
>
Export
</plugin-action-button>
</v-row>


</j-tray-plugin>
</template>
Loading