diff --git a/jdaviz/app.py b/jdaviz/app.py index 9e33007a6d..4f894727ca 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -15,6 +15,7 @@ from regions import RectanglePixelRegion, PixCoord from specutils import Spectrum1D +from glue.core.exceptions import IncompatibleAttribute from glue.config import data_translator from glue.config import settings as glue_settings from glue.core import BaseData, HubListener, Data, DataCollection @@ -148,6 +149,10 @@ def __init__(self, configuration=None, *args, **kwargs): # Parse the yaml configuration file used to compose the front-end UI self.load_configuration(configuration) + # If true, link data on load. If false, do not link data to speed up + # data loading + self.auto_link = kwargs.pop('auto_link', True) + # Subscribe to messages indicating that a new viewer needs to be # created. When received, information is passed to the application # handler to generate the appropriate viewer instance. @@ -244,6 +249,11 @@ def _link_new_data(self): any components are compatible with already loaded data. If so, link them so that they can be displayed on the same profile1D plot. """ + # Allow for batch linking of data in the parser rather than on + # data load + if not self.auto_link: + return + new_len = len(self.data_collection) # Can't link if there's no world_component_ids wc_new = self.data_collection[new_len-1].world_component_ids @@ -442,8 +452,11 @@ def get_data_from_viewer(self, viewer_reference, data_label=None, if cls is not None: handler, _ = data_translator.get_handler_for(cls) - layer_data = handler.to_object(layer_data, - statistic=statistic) + try: + layer_data = handler.to_object(layer_data, + statistic=statistic) + except IncompatibleAttribute: + continue data[label] = layer_data diff --git a/jdaviz/configs/mosviz/helper.py b/jdaviz/configs/mosviz/helper.py index 1530b9a85e..87feb9e30f 100644 --- a/jdaviz/configs/mosviz/helper.py +++ b/jdaviz/configs/mosviz/helper.py @@ -20,8 +20,8 @@ class MosViz(ConfigHelper): _default_configuration = "mosviz" - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) spec1d = self.app.get_viewer("spectrum-viewer") spec1d.scales['x'].observe(self._update_spec2d_x_axis) @@ -211,6 +211,8 @@ def load_data(self, spectra_1d=None, spectra_2d=None, images=None, ``images``. Can be a list of strings representing data labels for each item in ``data_obj`` if ``data_obj`` is a list. """ + # Link data after everything is loaded + self.app.auto_link = False directory = kwargs.pop('directory', None) instrument = kwargs.pop('instrument', None) @@ -258,9 +260,27 @@ def load_data(self, spectra_1d=None, spectra_2d=None, images=None, msg = SnackbarMessage(msg, color='warning', sender=self) self.app.hub.broadcast(msg) + self.link_table_data(None) + + # Any subsequently added data will automatically be linked + # with data already loaded in the app + self.app.auto_link = True + # Load the first object into the viewers automatically self.app.get_viewer("table-viewer").figure_widget.highlighted = 0 + def link_table_data(self, data_obj): + """ + Batch link data in the Mosviz table rather than doing it on + data load. + + Parameters + ---------- + data_obj : obj + Input for Mosviz data parsers. + """ + super().load_data(data_obj, parser_reference="mosviz-link-data") + def load_spectra(self, spectra_1d, spectra_2d): """ Load 1D and 2D spectra using lists or strings to represent each. @@ -343,8 +363,14 @@ def load_2d_spectra(self, data_obj, data_labels=None): data_labels=data_labels) def load_niriss_data(self, data_obj, data_labels=None): + self.app.auto_link = False + super().load_data(data_obj, parser_reference="mosviz-niriss-parser") + self.link_table_data(data_obj) + + self.app.auto_link = True + def load_images(self, data_obj, data_labels=None, share_image=0): """ Load and parse a set of image objects. If providing a file path, it diff --git a/jdaviz/configs/mosviz/plugins/parsers.py b/jdaviz/configs/mosviz/plugins/parsers.py index 5ae7f4dc70..695dbaaa05 100644 --- a/jdaviz/configs/mosviz/plugins/parsers.py +++ b/jdaviz/configs/mosviz/plugins/parsers.py @@ -14,6 +14,8 @@ from astropy.wcs import WCS from asdf.fits_embed import AsdfInFits from pathlib import Path +from glue.core.link_helpers import LinkSame + import glob __all__ = ['mos_spec1d_parser', 'mos_spec2d_parser', 'mos_image_parser'] @@ -116,6 +118,38 @@ def _fields_from_ecsv(fname, fields, delimiter=","): return parsed_fields +@data_parser_registry("mosviz-link-data") +def link_data_in_table(app, data_obj=None): + """ + Batch links data in the mosviz table viewer. + + Parameters + ---------- + app : `~jdaviz.app.Application` + The application-level object used to reference the viewers. + data_obj : None + Passed in in order to use the data_parser_registry, otherwise + not used. + """ + mos_data = app.session.data_collection['MOS Table'] + wc_spec_ids = [] + + # Optimize linking speed through a) delaying link manager updates with a + # context manager, b) handling intra-row linkage of 1D and 2D spectra in a + # loop, and c) handling inter-row linkage after that in one fell swoop. + with app.data_collection.delay_link_manager_update(): + for index in range(len(mos_data.get_component('1D Spectra').data)): + spec_1d = mos_data.get_component('1D Spectra').data[index] + spec_2d = mos_data.get_component('2D Spectra').data[index] + + wc_spec_1d = app.session.data_collection[spec_1d].world_component_ids + wc_spec_2d = app.session.data_collection[spec_2d].world_component_ids + + wc_spec_ids.append(LinkSame(wc_spec_1d[0], wc_spec_2d[0])) + + app.session.data_collection.add_link(wc_spec_ids) + + @data_parser_registry("mosviz-nirspec-directory-parser") def mos_nirspec_directory_parser(app, data_obj, data_labels=None): diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py index 3d96e64c20..55dc241185 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py @@ -74,7 +74,8 @@ def _on_viewer_data_changed(self, msg=None): if msg is not None and msg.viewer_id != self._viewer_id: return - self._viewer_data = self.app.get_data_from_viewer('spectrum-viewer') + self._viewer_data = self.app.get_data_from_viewer('spectrum-viewer', + include_subsets=False) self.dc_items = [data.label for data in self.app.data_collection diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py index 1450244bda..22737d66fc 100644 --- a/jdaviz/configs/specviz/plugins/viewers.py +++ b/jdaviz/configs/specviz/plugins/viewers.py @@ -6,6 +6,7 @@ from glue.core.subset import Subset from glue.config import data_translator from glue_jupyter.bqplot.profile import BqplotProfileView +from glue.core.exceptions import IncompatibleAttribute import astropy from astropy.utils.introspection import minversion @@ -66,8 +67,11 @@ def data(self, cls=None): if _class is not None: handler, _ = data_translator.get_handler_for(_class) - layer_data = handler.to_object(layer_data, - statistic=statistic) + try: + layer_data = handler.to_object(layer_data, + statistic=statistic) + except IncompatibleAttribute: + continue data.append(layer_data) return data diff --git a/notebooks/MosvizExample.ipynb b/notebooks/MosvizExample.ipynb index b091f91527..90c0b8d27e 100644 --- a/notebooks/MosvizExample.ipynb +++ b/notebooks/MosvizExample.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# MOSViz example notebook" + "# Mosviz example notebook" ] }, { @@ -28,7 +28,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This starts MOSViz." + "Next, start Mosviz." ] }, { @@ -51,7 +51,7 @@ "source": [ "But before we can use it, we need some data.\n", "\n", - "The MOSViz parsers accept lists of `Spectrum1D`, `SpectralCube`, and `CCDData` for 1D, 2D, and image data, respectively. Alternatively, users can also provide lists of file paths and MOSViz will internally attempt to parse them as their respective data types." + "The Mosviz parsers accept lists of `Spectrum1D`, `SpectralCube`, and `CCDData` for 1D, 2D, and image data, respectively. Alternatively, users can also provide lists of file paths and Mosviz will internally attempt to parse them as their respective data types." ] }, { @@ -210,7 +210,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If no images are provided, MOSViz can still display the spectra." + "If no images are provided, Mosviz can still display the spectra." ] }, { diff --git a/notebooks/MosvizNIRISSExample.ipynb b/notebooks/MosvizNIRISSExample.ipynb index ae647501fa..91c21bca3e 100644 --- a/notebooks/MosvizNIRISSExample.ipynb +++ b/notebooks/MosvizNIRISSExample.ipynb @@ -5,7 +5,7 @@ "id": "2b2fcea7", "metadata": {}, "source": [ - "# MOSViz NIRISS example notebook" + "# Mosviz NIRISS example notebook" ] }, { @@ -32,7 +32,7 @@ "id": "e3d6f12f", "metadata": {}, "source": [ - "This starts MOSViz." + "Next, start Mosviz." ] }, { @@ -44,9 +44,9 @@ }, "outputs": [], "source": [ - "from jdaviz.configs.mosviz.helper import MosViz\n", + "from jdaviz.configs.mosviz.helper import MosViz as Mosviz\n", "\n", - "mosviz = MosViz()\n", + "mosviz = Mosviz()\n", "mosviz.app" ] },