Repository: glue-viz/glue-astronomy

Issue:
Uncertainties failing to round-trip properly from Spectrum1D
Using the latest version throws an error when retrieving a Spectrum1D in `jdaviz` (the file is https://data.sdss.org/sas/dr14/sdss/spectro/redux/26/spectra/0751/spec-0751-52251-0160.fits):

```
---------------------------------------------------------------------------
UnitConversionError                       Traceback (most recent call last)
~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/nddata/nduncertainty.py in parent_nddata(self, value)
    232                 try:
--> 233                     unit_from_data.to(self.unit)
    234                 except UnitConversionError:

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/quantity.py in to(self, unit, equivalencies)
    688         unit = Unit(unit)
--> 689         return self._new_view(self._to_value(unit, equivalencies), unit)
    690 

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/quantity.py in _to_value(self, unit, equivalencies)
    659             equivalencies = self._equivalencies
--> 660         return self.unit.to(unit, self.view(np.ndarray),
    661                             equivalencies=equivalencies)

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/core.py in to(self, other, value, equivalencies)
    986         else:
--> 987             return self._get_converter(other, equivalencies=equivalencies)(value)
    988 

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/core.py in _get_converter(self, other, equivalencies)
    917 
--> 918             raise exc
    919 

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/core.py in _get_converter(self, other, equivalencies)
    902         try:
--> 903             return self._apply_equivalencies(
    904                 self, other, self._normalize_equivalencies(equivalencies))

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/units/core.py in _apply_equivalencies(self, unit, other, equivalencies)
    885 
--> 886         raise UnitConversionError(
    887             "{} and {} are not convertible".format(

UnitConversionError: 'Angstrom2 cm4 s2 / erg2' and 'erg / (Angstrom cm2 s)' (spectral flux density wav) are not convertible

During handling of the above exception, another exception occurred:

UnitConversionError                       Traceback (most recent call last)
<ipython-input-2-a4b364c1fa48> in <module>
      1 from astropy.utils.data import download_file
      2 fn = download_file('https://data.sdss.org/sas/dr14/sdss/spectro/redux/26/spectra/0751/spec-0751-52251-0160.fits', cache=True)
----> 3 specviz.load_spectrum(fn, "myfile", format="SDSS-III/IV spec")

~/projects/jdaviz/jdaviz/configs/specviz/helper.py in load_spectrum(self, data, data_label, format, show_in_viewer)
     67         self.app.add_data(data, data_label)
     68         if show_in_viewer:
---> 69             self.app.add_data_to_viewer("spectrum-viewer", data_label)
     70 
     71     def get_spectra(self, data_label=None):

~/projects/jdaviz/jdaviz/app.py in add_data_to_viewer(self, viewer_reference, data_label, clear_other_data)
    563         if data_id is not None:
    564             data_ids.append(data_id)
--> 565             self._update_selected_data_items(viewer_item['id'], data_ids)
    566         else:
    567             raise ValueError(

~/projects/jdaviz/jdaviz/app.py in _update_selected_data_items(self, viewer_id, selected_items)
    833                                               viewer_id=viewer_id,
    834                                               sender=self)
--> 835             self.hub.broadcast(add_data_message)
    836 
    837         # Remove any deselected data objects from viewer

~/projects/glue/glue/core/hub.py in broadcast(self, message)
    213             logging.getLogger(__name__).info("Broadcasting %s", message)
    214             for subscriber, handler in self._find_handlers(message):
--> 215                 handler(message)
    216 
    217     def __getstate__(self):

~/projects/jdaviz/jdaviz/configs/specviz/plugins/unit_conversion/unit_conversion.py in _on_viewer_data_changed(self, msg)
     72         viewer = self.app.get_viewer('spectrum-viewer')
     73 
---> 74         self._viewer_data = self.app.get_data_from_viewer('spectrum-viewer')
     75 
     76         self.dc_items = [layer_state.layer.label

~/projects/jdaviz/jdaviz/app.py in get_data_from_viewer(self, viewer_reference, data_label, cls, include_subsets)
    372 
    373                     if cls is not None:
--> 374                         layer_data = layer_data.get_object(cls=cls,
    375                                                            statistic=statistic)
    376                     # If the shape of the data is 2d, then use CCDData as the

~/projects/glue/glue/core/data.py in get_object(self, cls, **kwargs)
    287         handler, _ = data_translator.get_handler_for(cls)
    288 
--> 289         return handler.to_object(self, **kwargs)
    290 
    291     @property

~/projects/glue-astronomy/glue_astronomy/translators/spectrum1d.py in to_object(self, data_or_subset, attribute, statistic)
    145             [attribute] if not hasattr(attribute, '__len__') else attribute)
    146 
--> 147         return Spectrum1D(**data_kwargs, **kwargs)

~/projects/specutils/specutils/spectra/spectrum1d.py in __init__(self, flux, spectral_axis, wcs, velocity_convention, rest_value, redshift, radial_velocity, bin_specification, **kwargs)
    187             wcs = gwcs_from_array(np.arange(size) * u.Unit(""))
    188 
--> 189         super(Spectrum1D, self).__init__(
    190             data=flux.value if isinstance(flux, u.Quantity) else flux,
    191             wcs=wcs, **kwargs)

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/nddata/nddata.py in __init__(self, data, uncertainty, mask, wcs, meta, unit, copy)
    232         self._unit = unit
    233         # Call the setter for uncertainty to further check the uncertainty
--> 234         self.uncertainty = uncertainty
    235 
    236     def __str__(self):

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/nddata/nddata.py in uncertainty(self, value)
    324                 # to be saved as weakref but that's done by NDUncertainty
    325                 # setter).
--> 326                 value.parent_nddata = self
    327         self._uncertainty = value

~/opt/anaconda3/envs/viz_dev/lib/python3.8/site-packages/astropy/nddata/nduncertainty.py in parent_nddata(self, value)
    233                     unit_from_data.to(self.unit)
    234                 except UnitConversionError:
--> 235                     raise UnitConversionError("Unit {} of uncertainty "
    236                                               "incompatible with unit {} of "
    237                                               "data".format(self.unit,

UnitConversionError: Unit 1e-17 erg / (Angstrom cm2 s) of uncertainty incompatible with unit 1e-17 erg / (Angstrom cm2 s) of data
```

Comments:

Patch:
diff --git a/glue_astronomy/translators/ccddata.py b/glue_astronomy/translators/ccddata.py
index c862c6a..52a7d78 100644
--- a/glue_astronomy/translators/ccddata.py
+++ b/glue_astronomy/translators/ccddata.py
@@ -1,6 +1,5 @@
-import numpy as np
-
 from astropy.wcs import WCS
+from astropy.nddata import CCDData
 
 from glue.config import data_translator
 from glue.core import Data, Subset
diff --git a/glue_astronomy/translators/spectral_cube.py b/glue_astronomy/translators/spectral_cube.py
index 8fa161f..c0baff7 100644
--- a/glue_astronomy/translators/spectral_cube.py
+++ b/glue_astronomy/translators/spectral_cube.py
@@ -1,5 +1,3 @@
-import numpy as np
-
 from astropy.wcs.wcsapi import BaseLowLevelWCS
 
 from glue.config import data_translator
diff --git a/glue_astronomy/translators/spectrum1d.py b/glue_astronomy/translators/spectrum1d.py
index db64c40..42c22a9 100644
--- a/glue_astronomy/translators/spectrum1d.py
+++ b/glue_astronomy/translators/spectrum1d.py
@@ -29,7 +29,7 @@ class Specutils1DHandler:
         # Include uncertainties if they exist
         if obj.uncertainty is not None:
             data['uncertainty'] = obj.uncertainty.quantity
-            data.get_component('uncertainty').units = str(obj.unit)
+            data.get_component('uncertainty').units = str(obj.uncertainty.unit)
             data.meta.update({'uncertainty_type': obj.uncertainty.uncertainty_type})
 
         # Include mask if it exists


Test patch:
diff --git a/glue_astronomy/translators/tests/test_spectrum1d.py b/glue_astronomy/translators/tests/test_spectrum1d.py
index c5d894d..4ccfc61 100644
--- a/glue_astronomy/translators/tests/test_spectrum1d.py
+++ b/glue_astronomy/translators/tests/test_spectrum1d.py
@@ -7,7 +7,7 @@ from specutils import Spectrum1D
 from astropy import units as u
 from astropy.wcs import WCS
 from astropy.tests.helper import assert_quantity_allclose
-from astropy.nddata import StdDevUncertainty
+from astropy.nddata import VarianceUncertainty
 
 from glue.core import Data, DataCollection
 from glue.core.component import Component
@@ -136,8 +136,8 @@ def test_from_spectrum1d(mode):
         kwargs = {'spectral_axis': [1, 2, 3, 4] * u.Hz}
 
     spec = Spectrum1D([2, 3, 4, 5] * u.Jy,
-                      uncertainty=StdDevUncertainty(
-                          [0.1, 0.1, 0.1, 0.1] * u.Jy),
+                      uncertainty=VarianceUncertainty(
+                          [0.1, 0.1, 0.1, 0.1] * u.Jy**2),
                       mask=[False, False, False, False],
                       **kwargs)
 
@@ -158,7 +158,7 @@ def test_from_spectrum1d(mode):
     assert data.main_components[1].label == 'uncertainty'
     assert_allclose(data['uncertainty'], [0.1, 0.1, 0.1, 0.1])
     component = data.get_component('uncertainty')
-    assert component.units == 'Jy'
+    assert component.units == 'Jy2'
 
     # Check round-tripping via single attribute reference
     spec_new = data.get_object(attribute='flux')
@@ -173,4 +173,4 @@ def test_from_spectrum1d(mode):
     assert_quantity_allclose(spec_new.spectral_axis, [1, 2, 3, 4] * u.Hz)
     assert_quantity_allclose(spec_new.flux, [2, 3, 4, 5] * u.Jy)
     assert spec_new.uncertainty is not None
-    assert_quantity_allclose(spec_new.uncertainty.quantity, [0.1, 0.1, 0.1, 0.1] * u.Jy)
+    assert_quantity_allclose(spec_new.uncertainty.quantity, [0.1, 0.1, 0.1, 0.1] * u.Jy**2)