spacetelescope / jdaviz

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

Specviz2d fails with jdaviz 2.5.0 #1306

Closed Jdaviz-Triage-Bot closed 2 years ago

Jdaviz-Triage-Bot commented 2 years ago

Reporter: Brian Cherinka

Specviz2d data loading fails with cal_ver 1.4.3 data. It fails with the error "AttributeError: 'NoneType' object has no attribute 'label'".

To reproduce:

s = Specviz2d()
s.app
s.load_data(spectrum_2d="jw00643-o031_s00194_nirspec_f290lp-g395h_s2d.fits")

🐱


DISCLAIMER: This issue was autocreated by the Jdaviz Issue Creation Bot on behalf of the reporter. If any information is incorrect, please contact Duy Nguyen

havok2063 commented 2 years ago

Full traceback:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [22], in <cell line: 1>()
----> 1 s.load_data(spectrum_2d=s2d[1])

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/configs/specviz2d/helper.py:126, in Specviz2d.load_data(self, spectrum_2d, spectrum_1d, spectrum_1d_label, spectrum_2d_label, show_in_viewer)
    123     spectrum_1d_label = spectrum_2d_label.replace("2D", "1D")
    125 if spectrum_2d is not None:
--> 126     self.app.load_data(spectrum_2d, parser_reference="mosviz-spec2d-parser",
    127                        data_labels=spectrum_2d_label,
    128                        show_in_viewer=show_in_viewer,
    129                        add_to_table=False)
    130     # Collapse the 2D spectrum to 1D if no 1D spectrum provided
    131     if spectrum_1d is None:

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/app.py:417, in Application.load_data(self, file_obj, parser_reference, **kwargs)
    412         parser = data_parser_registry.members.get(data_parser)
    414 if parser is not None:
    415     # If the parser returns something other than known, assume it's
    416     #  a message we want to make the user aware of.
--> 417     msg = parser(self, file_obj, **kwargs)
    419     if msg is not None:
    420         snackbar_message = SnackbarMessage(
    421             msg, color='error', sender=self)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/configs/mosviz/plugins/parsers.py:308, in mos_spec2d_parser(app, data_obj, data_labels, add_to_table, show_in_viewer)
    305 if len(data_labels) > 1:
    306     raise ValueError("More than one data label provided, unclear " +
    307                      "which to show in viewer")
--> 308 app.add_data_to_viewer("spectrum-2d-viewer", data_labels[0])

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/app.py:860, in Application.add_data_to_viewer(self, viewer_reference, data_path, clear_other_data, ext)
    858 if data_id is not None:
    859     data_ids.append(data_id)
--> 860     self._update_selected_data_items(viewer_item['id'], data_ids)
    861 else:
    862     raise ValueError(
    863         f"No data item found with label '{data_label}'. Label must be one "
    864         "of:\n\t" + "\n\t".join([
    865             data_item['name'] for data_item in self.state.data_items]))

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/app.py:1177, in Application._update_selected_data_items(self, viewer_id, selected_items)
   1173 active_data_labels.append(label)
   1175 [data] = [x for x in self.data_collection if x.label == label]
-> 1177 viewer.add_data(data)
   1179 add_data_message = AddDataMessage(data, viewer,
   1180                                   viewer_id=viewer_id,
   1181                                   sender=self)
   1182 self.hub.broadcast(add_data_message)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/view.py:117, in IPyWidgetView.add_data(self, data, color, alpha, **layer_state)
    113 def add_data(self, data, color=None, alpha=None, **layer_state):
    115     data = validate_data_argument(self._data, data)
--> 117     result = super().add_data(data)
    119     if not result:
    120         return

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/common/viewer.py:207, in Viewer.add_data(self, data)
    203     raise IncompatibleDataException("Data not in DataCollection")
    205 # Create layer artist and add to container. First check whether any
    206 # plugins want to make a custom layer artist.
--> 207 layer = get_layer_artist_from_registry(data, self) or self.get_data_layer_artist(data)
    209 if layer is None:
    210     return False

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/bqplot/image/viewer.py:87, in BqplotImageView.get_data_layer_artist(self, layer, layer_state)
     85 else:
     86     cls = BqplotImageLayerArtist
---> 87 return self.get_layer_artist(cls, layer=layer, layer_state=layer_state)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/view.py:142, in IPyWidgetView.get_layer_artist(self, cls, layer, layer_state)
    141 def get_layer_artist(self, cls, layer=None, layer_state=None):
--> 142     return cls(self, self.state, layer=layer, layer_state=layer_state)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/bqplot/image/layer_artist.py:21, in BqplotImageLayerArtist.__init__(self, view, *args, **kwargs)
     20 def __init__(self, view, *args, **kwargs):
---> 21     super().__init__(view, *args, **kwargs)
     22     # TODO: we should probably use a lru cache to avoid having a 'memleak'
     23     self._contour_line_cache = {}

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/layer_artist.py:67, in ImageLayerArtist.__init__(self, axes, viewer_state, layer_state, layer)
     65 def __init__(self, axes, viewer_state, layer_state=None, layer=None):
---> 67     super(ImageLayerArtist, self).__init__(axes, viewer_state,
     68                                            layer_state=layer_state, layer=layer)
     70     # We use a custom object to deal with the compositing of images, and we
     71     # store it as a private attribute of the axes to make sure it is
     72     # accessible for all layer artists.
     73     self.uuid = str(uuid.uuid4())

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/layer_artist.py:30, in BaseImageLayerArtist.__init__(self, axes, viewer_state, layer_state, layer)
     28 def __init__(self, axes, viewer_state, layer_state=None, layer=None):
---> 30     super(BaseImageLayerArtist, self).__init__(axes, viewer_state,
     31                                                layer_state=layer_state, layer=layer)
     33     # Watch for changes in the viewer state which would require the
     34     # layers to be redrawn
     35     self._viewer_state.add_global_callback(self._update_image)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/matplotlib/layer_artist.py:18, in MatplotlibLayerArtist.__init__(self, axes, viewer_state, layer_state, layer)
     16 def __init__(self, axes, viewer_state, layer_state=None, layer=None):
---> 18     super(MatplotlibLayerArtist, self).__init__(viewer_state,
     19                                                 layer_state=layer_state,
     20                                                 layer=layer)
     21     self.axes = axes
     22     self.mpl_artists = []

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/common/layer_artist.py:29, in LayerArtist.__init__(self, viewer_state, layer_state, layer)
     25 self.state = layer_state or self._layer_state_cls(viewer_state=viewer_state,
     26                                                   layer=self.layer)
     28 if self.state not in self._viewer_state.layers:
---> 29     self._viewer_state.layers.append(self.state)
     31 self.zorder = self.state.zorder
     32 self.visible = self.state.visible

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/containers.py:52, in CallbackList.append(self, value)
     50 def append(self, value):
     51     super(CallbackList, self).append(self._prepare_add(value))
---> 52     self.notify_all()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/containers.py:45, in CallbackList.notify_all(self, *args, **kwargs)
     43 def notify_all(self, *args, **kwargs):
     44     for callback in self.callbacks:
---> 45         callback(*args, **kwargs)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/containers.py:166, in dynamic_callback.__call__(self, *args, **kwargs)
    165 def __call__(self, *args, **kwargs):
--> 166     self.function(*args, **kwargs)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/containers.py:189, in ListCallbackProperty._default_setter.<locals>.callback(*args, **kwargs)
    188 def callback(*args, **kwargs):
--> 189     self.notify(instance, wrapped_list, wrapped_list)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:125, in CallbackProperty.notify(self, instance, old, new)
    123     return
    124 for cback in self._callbacks.get(instance, []):
--> 125     cback(new)
    126 for cback in self._2arg_callbacks.get(instance, []):
    127     cback(old, new)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/state.py:194, in ImageViewerState._layers_changed(self, *args)
    191 if layers_data == layers_data_cache:
    192     return
--> 194 self._update_combo_ref_data()
    195 self._set_reference_data()
    196 self._update_syncing()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/state.py:220, in ImageViewerState._update_combo_ref_data(self)
    219 def _update_combo_ref_data(self):
--> 220     self.ref_data_helper.set_multiple_data(self.layers_data)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/core/data_combo_helper.py:522, in ManualDataComboHelper.set_multiple_data(self, datasets)
    520 for data in unique_data_iter(datasets):
    521     self.append_data(data, refresh=False)
--> 522 self.refresh()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/core/data_combo_helper.py:445, in BaseDataComboHelper.refresh(self, *args)
    444 def refresh(self, *args):
--> 445     self.choices = [data for data in self._datasets]
    446     self.refresh_component_ids()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/core/data_combo_helper.py:86, in ComboHelper.choices(self, choices)
     84 with delay_callback(self.state, self.selection_property):
     85     prop = getattr(type(self.state), self.selection_property)
---> 86     prop.set_choices(self.state, choices)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:538, in delay_callback.__exit__(self, *args)
    535     self.instance._process_delayed_global_callbacks(resume_props)
    537 for p, args in notifications:
--> 538     p.notify(*args)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/utils/matplotlib.py:176, in defer_draw.<locals>.wrapper(*args, **kwargs)
    172 @wraps(func)
    173 def wrapper(*args, **kwargs):
    175     if len(DEFER_DRAW_BACKENDS) == 0:
--> 176         return func(*args, **kwargs)
    178     # Don't recursively defer draws. We just check the first draw_idle
    179     # method since all should be modified in sync.
    180     if isinstance(DEFER_DRAW_BACKENDS[0].draw_idle, DeferredMethod):

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/matplotlib/state.py:35, in DeferredDrawSelectionCallbackProperty.notify(self, *args, **kwargs)
     33 @defer_draw
     34 def notify(self, *args, **kwargs):
---> 35     super(DeferredDrawSelectionCallbackProperty, self).notify(*args, **kwargs)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:125, in CallbackProperty.notify(self, instance, old, new)
    123     return
    124 for cback in self._callbacks.get(instance, []):
--> 125     cback(new)
    126 for cback in self._2arg_callbacks.get(instance, []):
    127     cback(old, new)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/state.py:180, in ImageViewerState._reference_data_changed(self, *args)
    176 # We need to make sure that we update x_att and y_att
    177 # at the same time before any other callbacks get called,
    178 # so we do this here manually.
    179 self._on_xatt_world_change()
--> 180 self._on_yatt_world_change()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:538, in delay_callback.__exit__(self, *args)
    535     self.instance._process_delayed_global_callbacks(resume_props)
    537 for p, args in notifications:
--> 538     p.notify(*args)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/utils/matplotlib.py:176, in defer_draw.<locals>.wrapper(*args, **kwargs)
    172 @wraps(func)
    173 def wrapper(*args, **kwargs):
    175     if len(DEFER_DRAW_BACKENDS) == 0:
--> 176         return func(*args, **kwargs)
    178     # Don't recursively defer draws. We just check the first draw_idle
    179     # method since all should be modified in sync.
    180     if isinstance(DEFER_DRAW_BACKENDS[0].draw_idle, DeferredMethod):

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/matplotlib/state.py:24, in DeferredDrawCallbackProperty.notify(self, *args, **kwargs)
     22 @defer_draw
     23 def notify(self, *args, **kwargs):
---> 24     super(DeferredDrawCallbackProperty, self).notify(*args, **kwargs)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:125, in CallbackProperty.notify(self, instance, old, new)
    123     return
    124 for cback in self._callbacks.get(instance, []):
--> 125     cback(new)
    126 for cback in self._2arg_callbacks.get(instance, []):
    127     cback(old, new)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/bqplot/image/viewer.py:59, in BqplotImageView._reset_limits(self, *args)
     58 def _reset_limits(self, *args):
---> 59     self.state.reset_limits()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/image/state.py:146, in ImageViewerState.reset_limits(self)
    143 self.y_max = ny - 0.5
    144 # We need to adjust the limits in here to avoid triggering all
    145 # the update events then changing the limits again.
--> 146 self._adjust_limits_aspect()

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:538, in delay_callback.__exit__(self, *args)
    535     self.instance._process_delayed_global_callbacks(resume_props)
    537 for p, args in notifications:
--> 538     p.notify(*args)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/utils/matplotlib.py:176, in defer_draw.<locals>.wrapper(*args, **kwargs)
    172 @wraps(func)
    173 def wrapper(*args, **kwargs):
    175     if len(DEFER_DRAW_BACKENDS) == 0:
--> 176         return func(*args, **kwargs)
    178     # Don't recursively defer draws. We just check the first draw_idle
    179     # method since all should be modified in sync.
    180     if isinstance(DEFER_DRAW_BACKENDS[0].draw_idle, DeferredMethod):

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue/viewers/matplotlib/state.py:24, in DeferredDrawCallbackProperty.notify(self, *args, **kwargs)
     22 @defer_draw
     23 def notify(self, *args, **kwargs):
---> 24     super(DeferredDrawCallbackProperty, self).notify(*args, **kwargs)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/echo/core.py:125, in CallbackProperty.notify(self, instance, old, new)
    123     return
    124 for cback in self._callbacks.get(instance, []):
--> 125     cback(new)
    126 for cback in self._2arg_callbacks.get(instance, []):
    127     cback(old, new)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/glue_jupyter/bqplot/common/viewer.py:116, in BqplotBaseView._update_bqplot_limits(self, *args)
    110 # NOTE: in the following, the figure will still update twice. There
    111 # isn't a way around it at the moment and nesting the context managers
    112 # doesn't change this - at the end of the day, the two scales are
    113 # separate widgets so will result in two updates.
    115 with self.scale_x.hold_sync():
--> 116     self.scale_x.min = float_or_none(self.state.x_min)
    117     self.scale_x.max = float_or_none(self.state.x_max)
    119 with self.scale_y.hold_sync():

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/traitlets/traitlets.py:606, in TraitType.__set__(self, obj, value)
    604     raise TraitError('The "%s" trait is read-only.' % self.name)
    605 else:
--> 606     self.set(obj, value)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/traitlets/traitlets.py:595, in TraitType.set(self, obj, value)
    591     silent = False
    592 if silent is not True:
    593     # we explicitly compare silent to True just in case the equality
    594     # comparison above returns something other than True/False
--> 595     obj._notify_trait(self.name, old_value, new_value)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/traitlets/traitlets.py:1219, in HasTraits._notify_trait(self, name, old_value, new_value)
   1218 def _notify_trait(self, name, old_value, new_value):
-> 1219     self.notify_change(Bunch(
   1220         name=name,
   1221         old=old_value,
   1222         new=new_value,
   1223         owner=self,
   1224         type='change',
   1225     ))

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/ipywidgets/widgets/widget.py:686, in Widget.notify_change(self, change)
    683     if name in self.keys and self._should_send_property(name, getattr(self, name)):
    684         # Send new state to front-end
    685         self.send_state(key=name)
--> 686 super(Widget, self).notify_change(change)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/traitlets/traitlets.py:1229, in HasTraits.notify_change(self, change)
   1227 def notify_change(self, change):
   1228     """Notify observers of a change event"""
-> 1229     return self._notify_observers(change)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/traitlets/traitlets.py:1266, in HasTraits._notify_observers(self, event)
   1263 elif isinstance(c, EventHandler) and c.name is not None:
   1264     c = getattr(self, c.name)
-> 1266 c(event)

File ~/anaconda3/envs/vmpy3.9/lib/python3.9/site-packages/jdaviz/configs/specviz2d/helper.py:71, in Specviz2d._update_spec1d_x_axis(self, change)
     69     return
     70 new_idx = int(np.around(change['new']))
---> 71 spec1d = self.app.get_viewer('spectrum-viewer').state.reference_data.label
     72 extend_by = int(self.app.data_collection[spec1d]["World 0"].shape[0])
     73 world = self._extend_world(spec1d, extend_by)

AttributeError: 'NoneType' object has no attribute 'label'
pllim commented 2 years ago

@havok2063 said that same data successfully loaded before, so this is a regression.

pllim commented 2 years ago

If I comment out https://github.com/glue-viz/glue-jupyter/pull/306 , the traceback goes away without the need for #1307 , though the 2D spectrum still isn't showing.

astrofrog commented 2 years ago

I'll investigate tomorrow!

pllim commented 2 years ago

@astrofrog , do you think https://github.com/spacetelescope/jdaviz/pull/1307 is enough to fix this? Please review it unless you have a better solution. Thanks!

astrofrog commented 2 years ago

Yes seems ok if it works!