Skip to content

Commit

Permalink
Jdaviz Launcher: Identify compatible configs and request user to sele…
Browse files Browse the repository at this point in the history
…ct config (#2267)

* Prep launch method without autodetect

* Identify config before launching

* Codestyle

* Support "empty" filepaths

* Filepath fallback when autoconfig doesn't need to open file

* Move open to launcher module

* Missing list encapsulation

* Changelog

* Combine changelogs
  • Loading branch information
duytnguyendtn authored Jul 7, 2023
1 parent f99c213 commit 683d989
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 84 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ New Features

- The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273]

- Add first-pass launcher to select config and auto-identify data. [#2257]
- Add launcher to select and identify compatible configurations,
and require --layout argument when launching standalone. [#2257, #2267]

Cubeviz
^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from jdaviz.configs.cubeviz import Cubeviz # noqa: F401
from jdaviz.configs.imviz import Imviz # noqa: F401
from jdaviz.utils import enable_hot_reloading # noqa: F401
from jdaviz.core.data_formats import open # noqa: F401
from jdaviz.core.launcher import open # noqa: F401

# Clean up namespace.
del os
Expand Down
4 changes: 2 additions & 2 deletions jdaviz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
JDAVIZ_DIR = pathlib.Path(__file__).parent.resolve()
DEFAULT_VERBOSITY = 'warning'
DEFAULT_HISTORY_VERBOSITY = 'info'
ALL_JDAVIZ_CONFIGS = ['cubeviz', 'specviz', 'specviz2d', 'mosviz', 'imviz']


def main(filepaths=None, layout='default', instrument=None, browser='default',
Expand Down Expand Up @@ -119,8 +120,7 @@ def _main(config=None):
'loaded from FILENAME.')
filepaths_nargs = '*'
if config is None:
parser.add_argument('--layout', default='', choices=['cubeviz', 'specviz', 'specviz2d',
'mosviz', 'imviz'],
parser.add_argument('--layout', default='', choices=ALL_JDAVIZ_CONFIGS,
help='Configuration to use.')
if (config == "mosviz") or ("mosviz" in sys.argv):
filepaths_nargs = 1
Expand Down
70 changes: 14 additions & 56 deletions jdaviz/core/data_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
from stdatamodels import asdf_in_fits

from jdaviz.core.config import list_configurations
from jdaviz import configs as jdaviz_configs
from jdaviz.cli import DEFAULT_VERBOSITY, DEFAULT_HISTORY_VERBOSITY

__all__ = [
'guess_dimensionality',
Expand Down Expand Up @@ -156,8 +154,8 @@ def identify_helper(filename, ext=1):
Returns
-------
helper_name : str
Name of the best-guess helper for ``filename``.
helper_name : list of str
Name of the best-guess compatible helpers for ``filename``.
Fits HDUList : astropy.io.fits.HDUList
The HDUList of the file opened to identify the helper
Expand All @@ -172,7 +170,7 @@ def identify_helper(filename, ext=1):
if filename.lower().endswith('asdf'):
# ASDF files are only supported in jdaviz for
# Roman WFI 2D images, so suggest imviz:
return ('imviz', None)
return (['imviz'], None)

# Must use memmap=False to force close all handles and allow file overwrite
hdul = fits.open(filename, memmap=False)
Expand Down Expand Up @@ -208,10 +206,10 @@ def identify_helper(filename, ext=1):
# could be 2D spectrum or 2D image. break tie with WCS:
if has_spectral_axis:
if n_axes > 1:
return ('specviz2d', hdul)
return ('specviz', hdul)
return (['specviz2d'], hdul)
return (['specviz'], hdul)
elif not isinstance(data, fits.BinTableHDU):
return ('imviz', hdul)
return (['imviz'], hdul)

# Ensure specviz is chosen when ``data`` is a table or recarray
# and there's a "known" spectral column name:
Expand All @@ -237,7 +235,7 @@ def identify_helper(filename, ext=1):

# if at least one spectral column is found:
if sum(found_spectral_columns):
return ('specviz', hdul)
return (['specviz'], hdul)

# If the data could be spectral:
for cls in [Spectrum1D, SpectrumList]:
Expand All @@ -247,10 +245,10 @@ def identify_helper(filename, ext=1):
# first catch known JWST spectrum types:
if (n_axes == 3 and
recognized_spectrum_format.find('s3d') > -1):
return ('cubeviz', hdul)
return (['cubeviz'], hdul)
elif (n_axes == 2 and
recognized_spectrum_format.find('x1d') > -1):
return ('specviz', hdul)
return (['specviz'], hdul)

# we intentionally don't choose specviz2d for
# data recognized as 's2d' as we did with the cases above,
Expand All @@ -260,62 +258,22 @@ def identify_helper(filename, ext=1):
# Use WCS to break the tie below:
elif n_axes == 2:
if has_spectral_axis:
return ('specviz2d', hdul)
return ('imviz', hdul)
return (['specviz2d'], hdul)
return (['imviz'], hdul)

elif n_axes == 1:
return ('specviz', hdul)
return (['specviz'], hdul)

try:
# try using the specutils registry:
valid_format, config = identify_data(filename)
return (config, hdul)
return ([config], hdul)
except ValueError:
# if file type not recognized:
pass

if n_axes == 2 and not has_spectral_axis:
# at this point, non-spectral 2D data are likely images:
return ('imviz', hdul)
return (['imviz'], hdul)

raise ValueError(f"No helper could be auto-identified for {filename}.")


def open(filename, show=True, **kwargs):
'''
Automatically detect the correct configuration based on a given file,
load the data, and display the configuration
Parameters
----------
filename : str (path-like)
Name for a local data file.
show : bool
Determines whether to immediately show the application
All other arguments are interpreted as load_data arguments for
the autoidentified configuration class
Returns
-------
Jdaviz ConfigHelper : jdaviz.core.helpers.ConfigHelper
The autoidentified ConfigHelper for the given data
'''
# Identify the correct config
helper_str, hdul = identify_helper(filename)
viz_class = getattr(jdaviz_configs, helper_str.capitalize())

# Create config instance
verbosity = kwargs.pop('verbosity', DEFAULT_VERBOSITY)
history_verbosity = kwargs.pop('history_verbosity', DEFAULT_HISTORY_VERBOSITY)
viz_helper = viz_class(verbosity=verbosity, history_verbosity=history_verbosity)

# Load data
data = hdul if (hdul is not None) else filename
viz_helper.load_data(data, **kwargs)

# Display app
if show:
viz_helper.show()

return viz_helper
129 changes: 105 additions & 24 deletions jdaviz/core/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,76 @@
from ipywidgets import jslink

from jdaviz import configs as jdaviz_configs
from jdaviz.core.data_formats import open as jdaviz_open
from jdaviz.cli import DEFAULT_VERBOSITY, DEFAULT_HISTORY_VERBOSITY, ALL_JDAVIZ_CONFIGS
from jdaviz.core.data_formats import identify_helper


def open(filename, show=True, **kwargs):
'''
Automatically detect the correct configuration based on a given file,
load the data, and display the configuration
Parameters
----------
filename : str (path-like)
Name for a local data file.
show : bool
Determines whether to immediately show the application
All other arguments are interpreted as load_data/load_spectrum arguments for
the autoidentified configuration class
Returns
-------
Jdaviz ConfigHelper : jdaviz.core.helpers.ConfigHelper
The autoidentified ConfigHelper for the given data
'''
# Identify the correct config
compatible_helpers, hdul = identify_helper(filename)
if len(compatible_helpers) > 1:
raise NotImplementedError(f"Multiple helpers provided: {compatible_helpers}."
"Unsure which to launch")
else:
return _launch_config_with_data(compatible_helpers[0], hdul, show, **kwargs)


def _launch_config_with_data(config, data=None, show=True, **kwargs):
'''
Launch jdaviz with a specific, known configuration and data
Parameters
----------
config : str (path-like)
Name for a local data file.
data : str or any Jdaviz-compatible data
A filepath or Jdaviz-compatible data object (such as Spectrum1D or CCDData)
show : bool
Determines whether to immediately show the application
All other arguments are interpreted as load_data/load_spectrum arguments for
the autoidentified configuration class
Returns
-------
Jdaviz ConfigHelper : jdaviz.core.helpers.ConfigHelper
The loaded ConfigHelper with data loaded
'''
viz_class = getattr(jdaviz_configs, config.capitalize())

# Create config instance
verbosity = kwargs.pop('verbosity', DEFAULT_VERBOSITY)
history_verbosity = kwargs.pop('history_verbosity', DEFAULT_HISTORY_VERBOSITY)
viz_helper = viz_class(verbosity=verbosity, history_verbosity=history_verbosity)

# Load data
if data not in (None, ''):
viz_helper.load_data(data, **kwargs)

# Display app
if show:
viz_helper.show()

return viz_helper


def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']):
Expand All @@ -15,37 +84,49 @@ def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']
children=['Welcome to Jdaviz'])
intro_row.children = [welcome_text]

# Filepath row
filepath_row = v.Row()
text_field = v.TextField(label="File Path", v_model=None)

def load_file(filepath):
if filepath:
helper = jdaviz_open(filepath, show=False)
main.children = [helper.app]

open_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary",
children=[v.Icon(children=["mdi-upload"])])
open_data_btn.on_event('click', lambda btn, event, data: load_file(btn.value))
jslink((text_field, 'v_model'), (open_data_btn, 'value'))

filepath_row.children = [text_field, open_data_btn]

# Config buttons
def create_config(config):
viz_class = getattr(jdaviz_configs, config.capitalize())
main.children = [viz_class().app]
def create_config(config, data=None):
helper = _launch_config_with_data(config, data, show=False)
main.children = [helper.app]

btns = []
btns = {}
loaded_data = None
for config in configs:
config_btn = v.Btn(class_="ma-2", outlined=True, color="primary",
children=[config.capitalize()])
config_btn.on_event('click', lambda btn, event, data: create_config(btn.children[0]))
btns.append(config_btn)
config_btn.on_event('click', lambda btn, event, data: create_config(btn.children[0],
loaded_data))
btns[config] = config_btn

# Create button row
btn_row = v.Row()
btn_row.children = btns
btn_row.children = list(btns.values())

# Filepath row
filepath_row = v.Row()
text_field = v.TextField(label="File Path", v_model=None)

def enable_compatible_configs(filepath):
nonlocal loaded_data
if filepath in (None, ''):
compatible_helpers = ALL_JDAVIZ_CONFIGS
loaded_data = None
else:
compatible_helpers, loaded_data = identify_helper(filepath)
if len(compatible_helpers) > 0 and loaded_data is None:
loaded_data = filepath

for config, btn in btns.items():
btn.disabled = not (config in compatible_helpers)

id_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary",
children=[v.Icon(children=["mdi-magnify"])])
id_data_btn.on_event('click', lambda btn, event, data: enable_compatible_configs(btn.value))
jslink((text_field, 'v_model'), (id_data_btn, 'value'))

filepath_row.children = [text_field, id_data_btn]

# Create Launcher
main.children = [intro_row, filepath_row, btn_row]

return main

0 comments on commit 683d989

Please sign in to comment.