spacetelescope / jdaviz

JWST astronomical data analysis tools in the Jupyter platform
https://jdaviz.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
134 stars 72 forks source link

[BUG] Specviz: Adding a spectrum after subset creation is broken #1123

Closed rosteen closed 2 years ago

rosteen commented 2 years ago

Describe the bug It looks like adding an additional spectrum to the spectrum-viewer after having created one or more subsets is broken.

To Reproduce Steps to reproduce the behavior:

  1. Load the data in the SpecvizExample notebook.
  2. Select a subset
  3. Run the code snippet below and see that it's broken:
    specs = specviz.get_spectra()
    new_spec = specs['myfile'] - 55*specs['myfile'].unit
    specviz.load_spectrum(new_spec, data_label = "SimpleContinuumSub2")

The problem appears to be in mask retrieval in app.get_subsets_from_viewer() as seen in the trace below. Note that you can add more spectra just fine as long as no subsets have been defined, and can even add subsets after the additional spectrum has been added. But once a spectrum is added post-subset-creation, subset retrieval breaks. It's unclear to me when this was introduced, I've tested with a slightly older version of Glue (1.2.2) and glue-jupyter 0.10.1 but haven't gone back further than that.

---------------------------------------------------------------------------
IncompatibleAttribute                     Traceback (most recent call last)
File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/data.py:1385, in Data.get_mask(self, subset_state, view)
   1384 try:
-> 1385     return subset_state.to_mask(self, view=view)
   1386 except IncompatibleAttribute:

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/subset.py:773, in RangeSubsetState.to_mask(self, data, view)
    771 @contract(data='isinstance(Data)', view='array_view')
    772 def to_mask(self, data, view=None):
--> 773     x = data[self.att, view]
    774     result = (x >= self.lo) & (x <= self.hi)

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/data.py:571, in BaseCartesianData.__getitem__(self, key)
    569         raise IncompatibleAttribute(_k)
--> 571 return self.get_data(key, view=view)

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/data.py:1361, in Data.get_data(self, cid, view)
   1360 else:
-> 1361     raise IncompatibleAttribute(cid)
   1363 if view is not None:

IncompatibleAttribute: World 0

During handling of the above exception, another exception occurred:

IncompatibleAttribute                     Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 specviz.load_spectrum(new_spec, data_label = "SimpleContinuumSub")

File ~/projects/jdaviz/jdaviz/configs/specviz/helper.py:41, in Specviz.load_spectrum(self, data, data_label, format, show_in_viewer)
     40 def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True):
---> 41     super().load_data(data,
     42                       'specviz-spectrum1d-parser',
     43                       data_label=data_label,
     44                       format=format,
     45                       show_in_viewer=show_in_viewer)

File ~/projects/jdaviz/jdaviz/core/helpers.py:69, in ConfigHelper.load_data(self, data, parser_reference, **kwargs)
     68 def load_data(self, data, parser_reference=None, **kwargs):
---> 69     self.app.load_data(data, parser_reference=parser_reference, **kwargs)

File ~/projects/jdaviz/jdaviz/app.py:370, in Application.load_data(self, file_obj, parser_reference, **kwargs)
    365         parser = data_parser_registry.members.get(data_parser)
    367 if parser is not None:
    368     # If the parser returns something other than known, assume it's
    369     #  a message we want to make the user aware of.
--> 370     msg = parser(self, file_obj, **kwargs)
    372     if msg is not None:
    373         snackbar_message = SnackbarMessage(
    374             msg, color='error', sender=self)

File ~/projects/jdaviz/jdaviz/configs/specviz/plugins/parsers.py:83, in specviz_spectrum1d_parser(app, data, data_label, format, show_in_viewer)
     81 # Only auto-show the first spectrum in a list
     82 if i == 0 and show_in_viewer:
---> 83     app.add_data_to_viewer("spectrum-viewer", data_label[i])

File ~/projects/jdaviz/jdaviz/app.py:810, in Application.add_data_to_viewer(self, viewer_reference, data_path, clear_other_data, ext)
    808 if data_id is not None:
    809     data_ids.append(data_id)
--> 810     self._update_selected_data_items(viewer_item['id'], data_ids)
    811 else:
    812     raise ValueError(
    813         f"No data item found with label '{data_label}'. Label must be one "
    814         "of:\n\t" + "\n\t".join([
    815             data_item['name'] for data_item in self.state.data_items]))

File ~/projects/jdaviz/jdaviz/app.py:1144, in Application._update_selected_data_items(self, viewer_id, selected_items)
   1139     viewer.add_data(data)
   1141     add_data_message = AddDataMessage(data, viewer,
   1142                                       viewer_id=viewer_id,
   1143                                       sender=self)
-> 1144     self.hub.broadcast(add_data_message)
   1146 # Remove any deselected data objects from viewer
   1147 viewer_data = [layer_state.layer
   1148                for layer_state in viewer.state.layers
   1149                if hasattr(layer_state, 'layer') and
   1150                isinstance(layer_state.layer, BaseData)]

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/hub.py:215, in Hub.broadcast(self, message)
    213 logging.getLogger(__name__).info("Broadcasting %s", message)
    214 for subscriber, handler in self._find_handlers(message):
--> 215     handler(message)

File ~/projects/jdaviz/jdaviz/configs/specviz/plugins/line_analysis/line_analysis.py:87, in LineAnalysis._on_viewer_data_changed(self, msg)
     84 viewer = self.app.get_viewer('spectrum-viewer')
     86 try:
---> 87     self._spectral_subsets = self.app.get_subsets_from_viewer("spectrum-viewer",
     88                                                               subset_type="spectral")
     89 except ValueError:
     90     pass

File ~/projects/jdaviz/jdaviz/app.py:650, in Application.get_subsets_from_viewer(self, viewer_reference, data_label, subset_type)
    640 # There is a special case for 1d data (which is also not
    641 #  supported currently). We now eschew the use of the
    642 #  translation machinery entirely and construct the astropy
    643 #  region ourselves.
    644 elif value.data.ndim == 1:
    645     # Grab the data units from the glue-astronomy spectral axis
    646     # TODO: this needs to be much simpler; i.e. data units in
    647     #  the glue component objects
    648     # Cases where there is a single subset
    649     subregions_in_subset = _get_all_subregions(
--> 650             np.where(value.to_mask() == True)[0], # noqa
    651             value.data.coords.spectral_axis)
    653     regions[key] = subregions_in_subset
    654     continue

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/subset.py:182, in Subset.to_mask(self, view)
    169 @contract(view='array_view', returns='array')
    170 def to_mask(self, view=None):
    171     """
    172     Convert the current subset to a mask.
    173 
   (...)
    180 
    181     """
--> 182     return self.data.get_mask(self.subset_state, view=view)

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/data.py:1387, in Data.get_mask(self, subset_state, view)
   1385     return subset_state.to_mask(self, view=view)
   1386 except IncompatibleAttribute:
-> 1387     return get_mask_with_key_joins(self, self._key_joins, subset_state, view=view)

File ~/opt/anaconda3/envs/test/lib/python3.9/site-packages/glue/core/joins.py:105, in get_mask_with_key_joins(data, key_joins, subset_state, view)
     99     else:
    101         raise Exception("Either the number of components in the key join sets "
    102                         "should match, or one of the component sets should ",
    103                         "contain a single component.")
--> 105 raise IncompatibleAttribute

IncompatibleAttribute: 

Package versions (please complete the following information):

macOS-10.15.7-x86_64-i386-64bit Python 3.9.7 (default, Sep 16 2021, 08:50:36) [Clang 10.0.0 ] Numpy 1.22.2 astropy 5.0.1 specutils 1.7.0 spectral-cube 0.6.0 pyyaml 6.0 click 8.0.4 asteval 0.9.26 idna 3.3 traitlets 5.1.1 bqplot 0.12.33 bqplot-image-gl 1.4.5 glue-core 1.2.2 glue-jupyter 0.10.1 glue-astronomy 0.3.2 echo 0.6 ipyvue 1.7.0 ipyvuetify 1.8.2 ipysplitpanes 0.2.0 ipygoldenlayout 0.4.0 voila 0.3.2 vispy 0.9.6 Jdaviz 2.2.1.dev226+g7392da3b.d20220225

pllim commented 2 years ago

The data is still added though. You see it in specviz.app.data_collection after the crash still. This boils down to the following:

>>> specviz.app.data_collection[0].get_mask(specviz.app.data_collection[0].subsets[0].subset_state)  # ok
>>> specviz.app.data_collection[1].get_mask(specviz.app.data_collection[1].subsets[0].subset_state)  # crash

Wow, what is going on here? If you drill down:

>>> print(specviz.app.data_collection[0].subsets[0].subset_state)
<glue.core.subset.RangeSubsetState object at 0x7f7859b24bb0>
>>> print(specviz.app.data_collection[1].subsets[0].subset_state)  # Same object
<glue.core.subset.RangeSubsetState object at 0x7f7859b24bb0>
>>> cid = specviz.app.data_collection[0].subsets[0].subset_state.att
>>> type(cid), cid.label
(glue.core.component_id.ComponentID, 'World 0')
>>> key = list(specviz.app.data_collection[1]._components.keys())[1]
>>> type(key), key.label
(glue.core.component_id.ComponentID, 'World 0')
>>> cid in specviz.app.data_collection[0]._components
True
>>> cid in specviz.app.data_collection[1]._components  # ?????
False
>>> cid = specviz.app.data_collection[1].subsets[0].subset_state.att
>>> cid in specviz.app.data_collection[1]._components  # ?????????
False
>>> specviz.app.data_collection[0]._components
OrderedDict([(Pixel Axis 0 [x], <glue.core.component.CoordinateComponent at 0x7f785a394550>),
             (World 0, <glue.core.component.CoordinateComponent at 0x7f785a3945b0>),
             (flux, <glue.core.component.Component at 0x7f785a394220>),
             (uncertainty, <glue.core.component.Component at 0x7f785a394670>),
             (mask, <glue.core.component.Component at 0x7f785a394790>)])
>>> specviz.app.data_collection[1]._components
OrderedDict([(Pixel Axis 0 [x], <glue.core.component.CoordinateComponent at 0x7f7859ae9ca0>),
             (World 0, <glue.core.component.CoordinateComponent at 0x7f7859ae9b50>),
             (flux, <glue.core.component.Component at 0x7f7859a87a30>),
             (uncertainty, <glue.core.component.Component at 0x7f7859aebe80>)])

@astrofrog , any advice?