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

Show path to file #2896

Merged
merged 11 commits into from
Jul 8, 2024
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ Other Changes and Additions
Bug Fixes
---------

- Display default filepath in Export plugin, re-enable API exporting, enable relative and absolute
path exports from the UI. [#2896]

Cubeviz
^^^^^^^

Expand Down
80 changes: 50 additions & 30 deletions jdaviz/configs/default/plugins/export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
filename_auto = Bool(True).tag(sync=True)
filename_invalid_msg = Unicode('').tag(sync=True)

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

# if selected subset is spectral or composite, display message and disable export
subset_invalid_msg = Unicode().tag(sync=True)
data_invalid_msg = Unicode().tag(sync=True)
Expand Down Expand Up @@ -177,7 +179,7 @@
# TODO: backwards compat for save_figure, save_movie,
# i_start, i_end, movie_fps, movie_filename
# TODO: expose export method once API is finalized

# is the above comment still needed or can it be removed?
expose = ['viewer', 'viewer_format',
'dataset', 'dataset_format',
'subset', 'subset_format',
Expand Down Expand Up @@ -254,8 +256,20 @@
else:
self.filename_default = ''

# Call to initially set the default filepath
if hasattr(self, 'viewer_format') and self.filename_default:
self._normalize_filename(filename=self.filename_default,
filetype=self.viewer_format.selected,
default_path=True)

@observe('filename_value')
def _is_filename_changed(self, event):
# Update the UI Filepath if relative or absolute paths are provided
# by user via self.filename_value
expanded_path = os.path.expanduser(self.filename_value)
abs_path = Path(expanded_path).resolve()
self.default_filepath = str(abs_path)

# Clear overwrite warning when user changes filename
self.overwrite_warn = False

Expand Down Expand Up @@ -333,7 +347,7 @@
else:
self.data_invalid_msg = ''

def _normalize_filename(self, filename=None, filetype=None, overwrite=False):
def _normalize_filename(self, filename=None, filetype=None, overwrite=False, default_path=False): # noqa: E501
# Make sure filename is valid and file does not end up in weird places in standalone mode.
if not filename:
raise ValueError("Invalid filename")
Expand All @@ -349,12 +363,21 @@
elif ((not filepath or str(filepath).startswith(".")) and os.environ.get("JDAVIZ_START_DIR", "")): # noqa: E501 # pragma: no cover
filename = os.environ["JDAVIZ_START_DIR"] / filename

if filename.exists() and not overwrite:
# Set the default filepath to inform user where file will be exported to
if default_path:
if not filepath.is_absolute():
abs_path = filepath.resolve()
self.default_filepath = str(abs_path)
# set default path for standalone
if os.environ.get("JDAVIZ_START_DIR", ""):
self.default_filepath = os.environ["JDAVIZ_START_DIR"]

Check warning on line 373 in jdaviz/configs/default/plugins/export/export.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/export/export.py#L373

Added line #L373 was not covered by tests

if filename.exists() and not overwrite and not default_path:
self.overwrite_warn = True
else:
self.overwrite_warn = False

return str(filename)
return filename

@with_spinner()
def export(self, filename=None, show_dialog=None, overwrite=False,
Expand Down Expand Up @@ -509,31 +532,28 @@
f"Exported to {filename} (overwrite)", 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)
else:
# support writing without save dialog
# https://github.com/bqplot/bqplot/pull/1397
def on_img_received(data):
try:
with filename.open(mode='bw') as f:
f.write(data)
except Exception as e:
self.hub.broadcast(SnackbarMessage(
f"{self.viewer.selected} failed to export to {str(filename)}: {e}",
sender=self, color="error"))
finally:
self.hub.broadcast(SnackbarMessage(
f"{self.viewer.selected} exported to {str(filename)}",
sender=self, color="success"))

if viewer.figure._upload_png_callback is not None:
raise ValueError("previous png export is still in progress. Wait to complete "
"before making another call to save_figure")

viewer.figure.get_png_data(on_img_received)

if filename is None:
filename = self.filename_default

Check warning on line 538 in jdaviz/configs/default/plugins/export/export.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/export/export.py#L538

Added line #L538 was not covered by tests

def on_img_received(data):
try:
with filename.open(mode='bw') as f:
f.write(data)
except Exception as e:
self.hub.broadcast(SnackbarMessage(

Check warning on line 545 in jdaviz/configs/default/plugins/export/export.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/export/export.py#L541-L545

Added lines #L541 - L545 were not covered by tests
f"{self.viewer.selected} failed to export to {str(filename)}: {e}",
sender=self, color="error"))
finally:
self.hub.broadcast(SnackbarMessage(

Check warning on line 549 in jdaviz/configs/default/plugins/export/export.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/export/export.py#L549

Added line #L549 was not covered by tests
f"{self.viewer.selected} exported to {str(filename)}",
sender=self, color="success"))

if viewer.figure._upload_png_callback is not None:
raise ValueError("previous png export is still in progress. Wait to complete before making another call to save_figure") # noqa: E501 # pragma: no cover

viewer.figure.get_png_data(on_img_received)

elif filetype == "svg":
viewer.figure.save_svg(str(filename) if filename is not None else None)
Expand Down Expand Up @@ -698,11 +718,11 @@

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

region.write(filename, overwrite=True)
region.write(str(filename), overwrite=True)

def save_subset_as_table(self, filename):
region = self.app.get_subsets(subset_name=self.subset.selected)
region.write(filename)
region.write(str(filename))

def vue_interrupt_recording(self, *args): # pragma: no cover
self.movie_interrupt = True
Expand Down
16 changes: 14 additions & 2 deletions jdaviz/configs/default/plugins/export/export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@
</v-row>
</div>

<v-row class="row-no-outside-padding row-min-bottom-padding">
<v-col>
<v-text-field
:value="default_filepath"
label="Filepath"
hint="Filepath export location."
persistent-hint
disabled
></v-text-field>
</v-col>
</v-row>

<div style="display: grid; position: relative"> <!-- overlay container -->
<div style="grid-area: 1/1">

Expand All @@ -231,7 +243,7 @@
:auto.sync="filename_auto"
:invalid_msg="filename_invalid_msg"
label="Filename"
hint="Export to a file on disk"
hint="Export to a file on disk."
></plugin-auto-label>

<v-row justify="end">
Expand Down Expand Up @@ -261,7 +273,7 @@

<v-overlay
absolute
opacity=1.0
opacity=0.5
:value="overwrite_warn"
:zIndex=3
style="grid-area: 1/1;
Expand Down
38 changes: 38 additions & 0 deletions jdaviz/configs/default/plugins/export/tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from glue.core.roi import CircularROI, XRangeROI
from regions import Regions, CircleSkyRegion
from specutils import Spectrum1D
from pathlib import Path


@pytest.mark.usefixtures('_jail')
Expand Down Expand Up @@ -348,3 +349,40 @@ def test_ap_phot_plot_export(self, imviz_helper):

# just check that it doesn't crash, since we can't download
export_plugin.export()

def test_figure_export(self, imviz_helper):

data = NDData(np.ones((500, 500)) * u.nJy)

imviz_helper.load_data(data)

export_plugin = imviz_helper.plugins['Export']._obj

export_plugin.export(filename=None)

# attempt to save a figure back to back
try:
export_plugin.export(filename='img.png')
except ValueError as e:
assert str(e) == "previous png export is still in progress. Wait to complete before making another call to save_figure" # noqa: E501

def test_filepath_convention(self, imviz_helper):
data = NDData(np.ones((500, 500)) * u.nJy)
imviz_helper.load_data(data)
export_plugin = imviz_helper.plugins['Export']._obj

# Set filename value using OS-independent Path methods
export_plugin.filename_value = str(Path('/') / 'img.png')
assert os.path.abspath(export_plugin.default_filepath) == os.path.abspath(export_plugin.filename_value) # noqa: E501

export_plugin.filename_value = str(Path('~') / 'img.png')
expected_path = str(Path('~').expanduser() / 'img.png')
assert export_plugin.default_filepath == expected_path

export_plugin.filename_value = str(Path('..') / 'img.png')
expected_path = str((Path('..') / 'img.png').resolve())
assert export_plugin.default_filepath == expected_path

export_plugin.filename_value = str(Path('.') / 'img.png')
expected_path = str((Path('.') / 'img.png').resolve())
assert export_plugin.default_filepath == expected_path