Source code for jdaviz.core.launcher

import os
from pathlib import Path

from astropy.io.registry.base import IORegistryError
from glue_jupyter.common.toolbar_vuetify import read_icon
import ipyvuetify as v
from traitlets import List, Unicode, Dict, Bool, observe, Any
from ipywidgets import widget_serialization
from solara import FileBrowser, reactive
import reacton

import jdaviz as jd
from jdaviz import configs as jdaviz_configs
from jdaviz import __version__
from jdaviz.cli import DEFAULT_VERBOSITY, DEFAULT_HISTORY_VERBOSITY, ALL_JDAVIZ_CONFIGS
from jdaviz.core.data_formats import identify_helper
from jdaviz.core.tools import ICON_DIR
from jdaviz.core.template_mixin import show_widget
from jdaviz.utils import download_uri_to_path

STATUS_HINTS = {
    'idle': "Provide a file path, or pick which viz tool to open",
    'identifying': "Identifying which tool is best to visualize your file...",
    'invalid path': "Error: Cannot find file. Please check your file path.",
    'id ok': "The below tools can best visualize your file. Pick which one you want to use.",
    'id failed': "We couldn’t identify which tool is best for your file. Pick a tool below to use."

}


[docs] 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 ''' # first catch URIs and download them, or return filename unchanged: if "local_path" in kwargs: fn_dl_kw = {"local_path": kwargs["local_path"]} else: fn_dl_kw = {} filename = download_uri_to_path(filename, cache=True, **fn_dl_kw) # 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, filepath=filename, show=show, **kwargs)
def _launch_config_with_data(config, data=None, show=True, filepath=None, **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 Spectrum or CCDData) show : bool Determines whether to immediately show the application filepath : str Filepath to use as fallback if ``data`` fails to load. All other arguments are interpreted as load_data arguments for the autoidentified configuration class Returns ------- Jdaviz ConfigHelper : jdaviz.core.helpers.ConfigHelper The loaded ConfigHelper with data loaded ''' # Deprecate non-mosviz configs deprecated_configs = ['cubeviz', 'imviz', 'rampviz', 'specviz', 'specviz2d'] if config.lower() in deprecated_configs: import warnings warnings.warn( f"{config.capitalize()} is deprecated and will be removed in version 5.2. " "Please use the top-level App instead.", DeprecationWarning, stacklevel=3 ) 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, ''): try: viz_helper.load_data(data, **kwargs) except IORegistryError: if filepath is None: raise viz_helper.load_data(filepath, **kwargs) # Display app if show: viz_helper.show() return viz_helper class Launcher(v.VuetifyTemplate): template_file = __file__, "launcher.vue" configs = List().tag(sync=True) filepath = Unicode().tag(sync=True) compatible_configs = List().tag(sync=True) config_icons = Dict().tag(sync=True) hint = Unicode().tag(sync=True) vdocs = Unicode("").tag(sync=True) # App not available yet, so we need to recompute it here # File picker Traitlets file_browser_widget = Any(None).tag(sync=True, **widget_serialization) file_browser_visible = Bool(False).tag(sync=True) # Define Icons cubeviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'cubeviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa specviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'specviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa specviz2d_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'specviz2d_icon.svg'), 'svg+xml')).tag(sync=True) # noqa mosviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'mosviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa imviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'imviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa # General Jdaviz logo for deconfigged button jdaviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'jdaviz.svg'), 'svg+xml')).tag(sync=True) # noqa def __init__(self, main=None, configs=ALL_JDAVIZ_CONFIGS, filepath='', height=None, *args, **kwargs): self.vdocs = 'latest' if 'dev' in __version__ else 'v'+__version__ if main is None: main = v.Sheet(class_="mx-25", attributes={"id": "popout-widget-container"}, color="#00212C", height=height) self.main = main self.configs = configs self.height = f"{height}px" if isinstance(height, int) else height # Set all configs to compatible at first load (for loading blank config) self.compatible_configs = configs self.config_icons = { 'cubeviz': self.cubeviz_icon, 'specviz': self.specviz_icon, 'specviz2d': self.specviz2d_icon, 'mosviz': self.mosviz_icon, 'imviz': self.imviz_icon, 'jdaviz': self.jdaviz_icon } self.file_browser_widget = None self.file_browser_dir = None self.selected_file = None self.components = {} self.loaded_data = None self.hint = STATUS_HINTS['idle'] self.filepath = filepath super().__init__(*args, **kwargs) @observe('filepath') def _filepath_changed(self, *args): self.hint = STATUS_HINTS['identifying'] self.compatible_configs = [] if self.filepath in (None, ''): self.compatible_configs = self.configs self.loaded_data = None self.hint = STATUS_HINTS['idle'] else: path = Path(self.filepath) if not path.is_file(): self.loaded_data = None self.hint = STATUS_HINTS['invalid path'] else: try: self.compatible_configs, self.loaded_data = identify_helper(self.filepath) self.hint = STATUS_HINTS['id ok'] except Exception: self.hint = STATUS_HINTS['id failed'] self.compatible_configs = self.configs self.loaded_data = self.filepath finally: if len(self.compatible_configs) > 0 and self.loaded_data is None: self.loaded_data = self.filepath # Clear hint if it's still stuck on "Identifying". We're in an ambiguous state self.hint = ('' if self.hint == STATUS_HINTS['identifying'] else self.hint) def _create_file_browser(self): if self.file_browser_widget is None: # Ensure JDAVIZ_START_DIR is set to absolute path of current working directory if 'JDAVIZ_START_DIR' not in os.environ: os.environ['JDAVIZ_START_DIR'] = os.path.abspath(os.getcwd()) start_path = Path(os.path.abspath(os.environ['JDAVIZ_START_DIR'])) self.file_browser_dir = reactive(start_path) self.selected_file = reactive('') self.file_browser_widget_el = FileBrowser( directory=self.file_browser_dir, selected=self.selected_file, on_path_select=self._on_file_select, can_select=True ) self.file_browser_widget, rc = reacton.render(self.file_browser_widget_el) def _on_file_select(self, path): # don't set filepath here, only set it when user clicks Import button pass def vue_open_file_dialog(self, *args, **kwargs): self._create_file_browser() self.file_browser_visible = True def vue_choose_file(self, *args, **kwargs): if self.selected_file is not None and self.selected_file.value: full_path = os.path.join(self.file_browser_dir.value, self.selected_file.value) self.filepath = full_path self.file_browser_visible = False def vue_launch_config(self, event): config = event.get('config') helper = _launch_config_with_data(config, self.loaded_data, filepath=self.filepath, show=False) if self.height not in ['100%', '100vh']: # We're in jupyter mode. Set to default height default_height = helper._app.state.settings['context']['notebook']['max_height'] helper._app.layout.height = default_height self.main.height = default_height self.main.color = 'transparent' self.main.children = [helper._app] def vue_launch_jdaviz(self, _): # Launch the deconfigged version of Jdaviz jdaviz_app = jd.gca()._app jd.loaders['file'].open_in_tray() if self.height not in ['100%', '100vh']: # We're in jupyter mode. Set to default height default_height = jdaviz_app.state.settings['context']['notebook']['max_height'] jdaviz_app.layout.height = default_height self.main.height = default_height self.main.color = 'transparent' self.main.children = [jdaviz_app] @property def main_with_launcher(self): self.main.children = [self] return self.main def show_launcher(configs=ALL_JDAVIZ_CONFIGS, filepath='', height='450px'): '''Display an interactive Jdaviz launcher to select your data and compatible configuration Parameters ---------- filepath: str, optional The path to the file to load into Jdaviz. Will autopopulate the File Path field height: int, optional The height of the top-level application widget, in pixels. Will be passed and processed by the selected configuration. Applies to the launcher and all instances of the same application in the notebook. ''' # Color defined manually due to the custom theme not being defined yet (in main_styles.vue) height = f"{height}px" if isinstance(height, int) else height launcher = Launcher(None, configs, filepath, height) show_widget(launcher.main_with_launcher, loc='inline', title=None)