From 9fcb2bc84bbb3d43b290ada72480c780c488d683 Mon Sep 17 00:00:00 2001 From: Ricky O'Steen Date: Tue, 12 Mar 2024 10:52:27 -0400 Subject: [PATCH] Update Jdaviz for specutils 2 compatibility Fix coords mouseover when SkyCoord is before SpectralCoord More debugging Fix slice with different spectral_axis_index, add specutils_format kwarg to cubeviz parser Remove unused import Remove ESA parser from commissioning [skip ci] Generalizing coords_info to handle spectral axis first or last [ci skip] Working on debugging aperture photometry Need to pull meta from data not comp Fix aperture photometry and wrong coord order in coords_info Fix codestyle, delete defunct case in parser Change Spectrum1D to Spectrum Change class name in recent code additions Get spectral extraction working again for cylinder case Debugging wavelength dependent case Make sure spectral extraction result is linked when added to data collection Moved fix to correct place Fix indents Remove stray code from rebase Update missed Spectrum1D Debugging cubeviz parser First start on fixing testt Fix bad rebase in spectral extraction Starting to work through test failures Working through test failures Debugging spectral extraction --- docs/create_products.rst | 14 +-- docs/cubeviz/export_data.rst | 6 +- docs/cubeviz/import_data.rst | 36 +++--- docs/cubeviz/plugins.rst | 2 +- docs/mosviz/plugins.rst | 4 +- docs/specviz/export_data.rst | 8 +- docs/specviz/import_data.rst | 26 ++-- docs/specviz/plugins.rst | 4 +- docs/specviz2d/export_data.rst | 2 +- docs/specviz2d/import_data.rst | 10 +- docs/specviz2d/plugins.rst | 4 +- jdaviz/app.py | 25 ++-- jdaviz/configs/cubeviz/helper.py | 4 +- jdaviz/configs/cubeviz/plugins/mixins.py | 13 +- .../plugins/moment_maps/moment_maps.py | 6 +- .../moment_maps/tests/test_moment_maps.py | 7 +- jdaviz/configs/cubeviz/plugins/parsers.py | 113 ++++++------------ jdaviz/configs/cubeviz/plugins/slice/slice.py | 2 +- .../cubeviz/plugins/slice/tests/test_slice.py | 8 +- .../spectral_extraction.py | 28 ++++- .../tests/test_spectral_extraction.py | 6 +- .../cubeviz/plugins/tests/test_parsers.py | 12 +- .../cubeviz/plugins/tests/test_regions.py | 4 +- jdaviz/configs/cubeviz/plugins/tools.py | 6 +- .../plugins/collapse/tests/test_collapse.py | 6 +- .../configs/default/plugins/export/export.py | 15 ++- .../plugins/export/tests/test_export.py | 10 +- .../gaussian_smooth/gaussian_smooth.py | 14 +-- .../tests/test_gaussian_smooth.py | 4 +- .../default/plugins/line_lists/line_lists.py | 2 +- .../line_lists/tests/test_line_lists.py | 6 +- .../plugins/model_fitting/fitting_backend.py | 36 +++--- .../plugins/model_fitting/model_fitting.py | 9 +- .../model_fitting/tests/test_fitting.py | 18 +-- .../model_fitting/tests/test_plugin.py | 18 +-- jdaviz/configs/imviz/helper.py | 2 +- .../aper_phot_simple/aper_phot_simple.py | 28 ++++- .../imviz/plugins/coords_info/coords_info.py | 20 +++- jdaviz/configs/mosviz/helper.py | 20 ++-- jdaviz/configs/mosviz/plugins/parsers.py | 16 +-- jdaviz/configs/mosviz/plugins/viewers.py | 6 +- .../configs/mosviz/tests/test_data_loading.py | 8 +- jdaviz/configs/specviz/helper.py | 28 ++--- .../plugins/line_analysis/line_analysis.py | 6 +- .../line_analysis/tests/test_line_analysis.py | 6 +- .../line_analysis/tests/test_lineflux.py | 12 +- jdaviz/configs/specviz/plugins/parsers.py | 22 ++-- .../tests/test_unit_conversion.py | 6 +- jdaviz/configs/specviz/plugins/viewers.py | 4 +- jdaviz/configs/specviz/tests/test_helper.py | 18 +-- jdaviz/configs/specviz/tests/test_viewers.py | 4 +- jdaviz/configs/specviz2d/helper.py | 8 +- jdaviz/configs/specviz2d/plugins/parsers.py | 4 +- .../tests/test_spectral_extraction.py | 12 +- jdaviz/configs/specviz2d/tests/test_helper.py | 4 +- jdaviz/conftest.py | 32 ++--- jdaviz/core/data_formats.py | 8 +- jdaviz/core/helpers.py | 17 +-- jdaviz/core/launcher.py | 2 +- jdaviz/core/marks.py | 8 +- jdaviz/core/template_mixin.py | 18 +-- jdaviz/tests/test_subsets.py | 10 +- notebooks/MosvizExample.ipynb | 4 +- pyproject.toml | 2 +- 64 files changed, 424 insertions(+), 399 deletions(-) diff --git a/docs/create_products.rst b/docs/create_products.rst index 3a78bd6991..ebd7c62a9f 100644 --- a/docs/create_products.rst +++ b/docs/create_products.rst @@ -5,14 +5,14 @@ Creating Jdaviz-readable Products Spectroscopic data products (1D, 2D, and 3D) can be loaded in the different ``jdaviz`` configurations using -essentially two methods, i.e., loading :class:`~specutils.Spectrum1D` objects or +essentially two methods, i.e., loading :class:`~specutils.Spectrum` objects or from FITS files. Here, we list a few ways in which data can be packaged to be easily loaded into a ``jdaviz`` configuration. Data in a database ------------------ -If the data are stored in a database, we recommend storing a :class:`~specutils.Spectrum1D` object +If the data are stored in a database, we recommend storing a :class:`~specutils.Spectrum` object per entry. This would allow the user to query the data and visualize it in ``jdaviz`` with few lines of code; also see :ref:`create_product_spectrum1d_obj`. @@ -34,8 +34,8 @@ Available loaders can be listed with the following commands: .. code-block:: python - from specutils import Spectrum1D - Spectrum1D.read.list_formats() + from specutils import Spectrum + Spectrum.read.list_formats() The majority are fairly specific to missions and instruments. Four formats are more generic and adaptable: ``ASCII``, ``ECSV``, ``tabular-fits``, and @@ -55,14 +55,14 @@ is available. We are working on the necessary documentation to prompt .. _create_product_spectrum1d_obj: -Providing scripts to load the data as Spectrum1D objects +Providing scripts to load the data as Spectrum objects ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If none of the above is an acceptable option, the user can create the data products with their custom format and provide scripts or Jupyter Notebooks -that show how to read the products and create :class:`~specutils.Spectrum1D` objects +that show how to read the products and create :class:`~specutils.Spectrum` objects that can be read into ``jdaviz``. More about -how to create :class:`~specutils.Spectrum1D` objects for the 1D, 2D, and 3D cases can be +how to create :class:`~specutils.Spectrum` objects for the 1D, 2D, and 3D cases can be found in the corresponding "Importing data" sections of the various configurations: * :ref:`specviz-import-data` diff --git a/docs/cubeviz/export_data.rst b/docs/cubeviz/export_data.rst index 976e204d4d..87587b3819 100644 --- a/docs/cubeviz/export_data.rst +++ b/docs/cubeviz/export_data.rst @@ -65,12 +65,12 @@ with the name of the data you want to extract): mydata = cubeviz.get_data(data_label="data_name") -The data is returned as a 3D `specutils.Spectrum1D` object. +The data is returned as a 3D `specutils.Spectrum` object. -To write out a `specutils.Spectrum1D` cube from Cubeviz +To write out a `specutils.Spectrum` cube from Cubeviz (e.g., a fitted cube from :ref:`model-fitting`), where the mask (if available) is as defined in -`Spectrum1D masks `_: +`Spectrum masks `_: .. code-block:: python diff --git a/docs/cubeviz/import_data.rst b/docs/cubeviz/import_data.rst index 27ced24856..d9cb50be99 100644 --- a/docs/cubeviz/import_data.rst +++ b/docs/cubeviz/import_data.rst @@ -5,24 +5,24 @@ Importing Data into Cubeviz *************************** By design, Cubeviz only supports data that can be parsed as -:class:`~specutils.Spectrum1D` objects. Despite the name, :class:`~specutils.Spectrum1D` -supports 3D cubes and allows the Python-level interface and parsing tools to +:class:`~specutils.Spectrum` objects. :class:`~specutils.Spectrum` supports 3D cubes +and allows the Python-level interface and parsing tools to be defined in ``specutils`` instead of being duplicated in Jdaviz. -:class:`~specutils.Spectrum1D` objects are very flexible in their capabilities, however, +:class:`~specutils.Spectrum` objects are very flexible in their capabilities, however, and hence should address most astronomical spectrum use cases. If you are creating your own data products, please read the page :ref:`create_products`. Cubeviz will automatically parse the data into the multiple viewers as described in :ref:`cubeviz-display-cubes`. For the best experience, data loaded into Cubeviz should contain valid WCS -keywords. For more information on how :class:`~specutils.Spectrum1D` -uses WCS, please go to the `Spectrum1D defining WCS section `_. +keywords. For more information on how :class:`~specutils.Spectrum` +uses WCS, please go to the `Spectrum defining WCS section `_. To check if your FITS file contains valid WCS keywords, please use `Astropy WCS validate `. For an example on loading a cube with valid WCS keywords, please see the :ref:`cubeviz-import-api` section below. Loading data without WCS is also possible as long as they are compatible -with :class:`~specutils.Spectrum1D`. However, not all plugins will work with this data. +with :class:`~specutils.Spectrum`. However, not all plugins will work with this data. .. _cubeviz-viewers: @@ -60,7 +60,7 @@ Importing data through the GUI Users may load data into the Cubeviz application by clicking the :guilabel:`Import Data` button at the top left of the application's user interface. This opens a dialogue with a prompt to select a file -that can be parsed as a :class:`~specutils.Spectrum1D` object. +that can be parsed as a :class:`~specutils.Spectrum` object. After clicking :guilabel:`Import`, the data file will be parsed and loaded into the application. A notification will appear to confirm whether the data import @@ -75,7 +75,7 @@ Importing data via the API Alternatively, users who work in a coding environment like a Jupyter notebook can access the Cubeviz helper class API. Using this API, users can load data into the application through code with the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` -method, which takes as input a :class:`~specutils.Spectrum1D` object. +method, which takes as input a :class:`~specutils.Spectrum` object. FITS Files ---------- @@ -89,33 +89,33 @@ The example below loads a FITS file into Cubeviz: cubeviz.load_data("/path/to/data/file.fits") cubeviz.show() -Spectrum1D (from file) +Spectrum (from file) ---------------------- For cases where the built-in parser is unable to understand your file format, -you can try the `~specutils.Spectrum1D` parser directly and then pass the object to the +you can try the `~specutils.Spectrum` parser directly and then pass the object to the :py:meth:`~jdaviz.core.helpers.ConfigHelper.load_data` method: .. code-block:: python - from specutils import Spectrum1D + from specutils import Spectrum from jdaviz import Cubeviz - spec3d = Spectrum1D.read("/path/to/data/file.fits") + spec3d = Spectrum.read("/path/to/data/file.fits") cubeviz = Cubeviz() cubeviz.load_data(spec3d, data_label='My Cube') cubeviz.show() -Spectrum1D (from array) +Spectrum (from array) ----------------------- -You can create your own :class:`~specutils.Spectrum1D` object by hand to load into Cubeviz: +You can create your own :class:`~specutils.Spectrum` object by hand to load into Cubeviz: .. code-block:: python import numpy as np from astropy import units as u from astropy.wcs import WCS - from specutils import Spectrum1D + from specutils import Spectrum from jdaviz import Cubeviz flux = np.arange(16).reshape((2, 2, 4)) * u.Jy @@ -125,7 +125,7 @@ You can create your own :class:`~specutils.Spectrum1D` object by hand to load in "CRPIX1": 0, "CRPIX2": 0, "CRPIX3": 0} w = WCS(wcs_dict) - cube = Spectrum1D(flux=flux, wcs=w) + cube = Spectrum(flux=flux, wcs=w) cubeviz = Cubeviz() cubeviz.load_data(cube, data_label='My Cube') cubeviz.show() @@ -149,7 +149,7 @@ object, you can load it into Cubeviz as follows: mydatamodel = datamodels.open(file) # mydatamodel is a jwst.datamodels object - # Due to current schema in jwst.datamodels, you'll need to create your own WCS object before you create your Spectrum1D object + # Due to current schema in jwst.datamodels, you'll need to create your own WCS object before you create your Spectrum object wcs_dict = {"CTYPE1": mydatamodel.meta.wcsinfo.ctype3, "CTYPE2": mydatamodel.meta.wcsinfo.ctype2, "CTYPE3": mydatamodel.meta.wcsinfo.ctype1, "CRVAL1": mydatamodel.meta.wcsinfo.crval3, "CRVAL2": mydatamodel.meta.wcsinfo.crval2, @@ -166,7 +166,7 @@ object, you can load it into Cubeviz as follows: data = np.swapaxes(data, 1, 2) # Create your spectrum1 - spec3d = Spectrum1D(data, wcs=my_wcs) + spec3d = Spectrum(data, wcs=my_wcs) cubeviz = Cubeviz() cubeviz.load_data(spec3d, data_label='My Cube') cubeviz.show() diff --git a/docs/cubeviz/plugins.rst b/docs/cubeviz/plugins.rst index fb24f0727c..074ea066bf 100644 --- a/docs/cubeviz/plugins.rst +++ b/docs/cubeviz/plugins.rst @@ -12,7 +12,7 @@ icon in the upper right corner of the Cubeviz application. The data analysis plugins are meant to aid quick-look analysis of both 3D and 1D spectroscopic data. In many cases, functions can be applied to -`~specutils.Spectrum1D` objects, which include both 3D and 1D datasets. +`~specutils.Spectrum` objects, which include both 3D and 1D datasets. Plugins that are specific to 1D spectra are described in more detail under :ref:`Specviz: Data Analysis Plugins `. In many cases, these capabilities can be further applied on a per spaxel basis diff --git a/docs/mosviz/plugins.rst b/docs/mosviz/plugins.rst index c49ba83f67..2b99238a41 100644 --- a/docs/mosviz/plugins.rst +++ b/docs/mosviz/plugins.rst @@ -3,7 +3,7 @@ Data Analysis Plugins ********************* The Mosviz data analysis plugins include operations on both -2D images and Spectrum1D one dimensional datasets. +2D images and Spectrum one dimensional datasets. Plugins that are specific to 1D spectra are described in more detail under Specviz:Data Analysis Plugins. All plugins are accessed via the plugin icon in the upper right corner @@ -120,5 +120,5 @@ The :guilabel:`Remove` button can be used to remove a slit once it has been appl In order to plot a slit onto the image viewer, we need WCS information from an image and slit position from a 2D spectrum. The slit position is calculated using the ``S_REGION`` header extension value, located in the -`~specutils.Spectrum1D.meta` attribute of the :class:`~specutils.Spectrum1D` object +`~specutils.Spectrum.meta` attribute of the :class:`~specutils.Spectrum` object that is active in the 2D spectrum viewer. diff --git a/docs/specviz/export_data.rst b/docs/specviz/export_data.rst index 886098751c..59fcd3e2fe 100644 --- a/docs/specviz/export_data.rst +++ b/docs/specviz/export_data.rst @@ -14,8 +14,8 @@ those data currently back into your Jupyter notebook: specviz.get_spectra() -which yields a either a single `specutils.Spectrum1D` object or a dictionary of -`specutils.Spectrum1D` (if there are multiple displayed spectra) that you can +which yields a either a single `specutils.Spectrum` object or a dictionary of +`specutils.Spectrum` (if there are multiple displayed spectra) that you can manipulate however you wish. You can then load the modified spectrum back into the notebook via the API described in :ref:`specviz-import-api`. @@ -35,7 +35,7 @@ To extract a spectrum with a spectral subset applied: specviz.get_data(spectral_subset='Subset 1') -In this case, the returned `specutils.Spectrum1D` object will have a ``mask`` +In this case, the returned `specutils.Spectrum` object will have a ``mask`` attribute, where ``True`` corresponds to the region outside the selected subset (i.e., the region that has been masked out). You could load back in a copy of the spectrum containing only your subset by running: @@ -43,7 +43,7 @@ spectrum containing only your subset by running: .. code-block:: python spec = specviz.get_data(spectral_subset='Subset 1') - subset_spec = Spectrum1D(flux=spec.flux[~spec.mask], + subset_spec = Spectrum(flux=spec.flux[~spec.mask], spectral_axis=spec.spectral_axis[~spec.mask]) specviz.load_data(subset_spec) diff --git a/docs/specviz/import_data.rst b/docs/specviz/import_data.rst index 075c6aefd0..000f310875 100644 --- a/docs/specviz/import_data.rst +++ b/docs/specviz/import_data.rst @@ -4,17 +4,17 @@ Importing Data Into Specviz *************************** -By design, Specviz only supports data that can be parsed as :class:`~specutils.Spectrum1D` objects, +By design, Specviz only supports data that can be parsed as :class:`~specutils.Spectrum` objects, as that allows the Python-level interface and parsing tools to be defined in ``specutils`` instead of being duplicated in Jdaviz. -:class:`~specutils.Spectrum1D` objects are very flexible in their capabilities, however, +:class:`~specutils.Spectrum` objects are very flexible in their capabilities, however, and hence should address most astronomical spectrum use cases. If you are creating your own data products, please read the page :ref:`create_products`. .. seealso:: `Reading from a File `_ - Specutils documentation on loading data as :class:`~specutils.Spectrum1D` objects. + Specutils documentation on loading data as :class:`~specutils.Spectrum` objects. .. _specviz-import-commandline: @@ -36,7 +36,7 @@ Importing data through the GUI You can load your data into the Specviz application by clicking the :guilabel:`Import Data` button at the top left of the application's user interface. This opens a dialogue where the user can select a file -that can be parsed as a :class:`~specutils.Spectrum1D`. +that can be parsed as a :class:`~specutils.Spectrum`. After clicking :guilabel:`Import`, the data file will be parsed and loaded into the application. A notification will appear to let users know if the data import @@ -52,7 +52,7 @@ Alternatively, users who work in a coding environment like a Jupyter notebook can access the Specviz helper class API. Using this API, users can load data into the application through code with the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` -method, which takes as input a :class:`~specutils.Spectrum1D` object. +method, which takes as input a :class:`~specutils.Spectrum` object. FITS Files ---------- @@ -61,13 +61,13 @@ The example below loads a FITS file into Specviz: .. code-block:: python - from specutils import Spectrum1D - spec1d = Spectrum1D.read("/path/to/data/file") + from specutils import Spectrum + spec1d = Spectrum.read("/path/to/data/file") specviz = Specviz() specviz.load_data(spec1d, data_label="my_spec") specviz.show() -You can also pass the path to a file that `~specutils.Spectrum1D` understands directly to the +You can also pass the path to a file that `~specutils.Spectrum` understands directly to the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method: .. code-block:: python @@ -83,12 +83,12 @@ You can create your own array to load into Specviz: import numpy as np import astropy.units as u - from specutils import Spectrum1D + from specutils import Spectrum from jdaviz import Specviz flux = np.random.randn(200) * u.Jy wavelength = np.arange(5100, 5300) * u.AA - spec1d = Spectrum1D(spectral_axis=wavelength, flux=flux) + spec1d = Spectrum(spectral_axis=wavelength, flux=flux) specviz = Specviz() specviz.load_data(spec1d, data_label="my_spec") specviz.show() @@ -101,7 +101,7 @@ object, you can load it into Specviz as follows: .. code-block:: python - from specutils import Spectrum1D + from specutils import Spectrum from jdaviz import Specviz # mydatamodel is a jwst.datamodels.MultiSpecModel object @@ -109,7 +109,7 @@ object, you can load it into Specviz as follows: flux = a.spec_table['FLUX'] wave = a.spec_table['WAVELENGTH'] - spec1d = Spectrum1D(flux=flux, spectral_axis=wave) + spec1d = Spectrum(flux=flux, spectral_axis=wave) specviz = Specviz() specviz.load_data(spec1d, data_label="MultiSpecModel") specviz.show() @@ -124,7 +124,7 @@ Importing a SpectrumList The :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` also accepts a `~specutils.SpectrumList` object, in which case it will both load the -individual `~specutils.Spectrum1D` objects in the list and additionally attempt +individual `~specutils.Spectrum` objects in the list and additionally attempt to stitch together the spectra into a single data object so that they can be manipulated and analyzed in the application as a single entity: diff --git a/docs/specviz/plugins.rst b/docs/specviz/plugins.rst index 8770eb7be8..443b9e58bc 100644 --- a/docs/specviz/plugins.rst +++ b/docs/specviz/plugins.rst @@ -65,12 +65,12 @@ Markers Gaussian Smooth =============== -Gaussian Smooth is performed on a Spectrum1D data object. +Gaussian Smooth is performed on a Spectrum data object. The spectrum is convolved with a Gaussian function. The Gaussian standard deviation in pixels must be entered into the :guilabel:`Standard deviation` field in the plugin. -A new Spectrum1D object is generated and is added to the spectrum +A new Spectrum object is generated and is added to the spectrum viewer. It can be selected and shown in the viewer via the :guilabel:`Data` icon in the viewer toolbar. diff --git a/docs/specviz2d/export_data.rst b/docs/specviz2d/export_data.rst index 4fc2ecd8bc..a91d67b222 100644 --- a/docs/specviz2d/export_data.rst +++ b/docs/specviz2d/export_data.rst @@ -9,7 +9,7 @@ Exporting Data From Specviz2D 2D Spectra ========== -Images in the 2D spectrum viewer can be exported as `specutils.Spectrum1D` objects into +Images in the 2D spectrum viewer can be exported as `specutils.Spectrum` objects into the notebook (replace "2D data" with the label of the desired data): .. code-block:: python diff --git a/docs/specviz2d/import_data.rst b/docs/specviz2d/import_data.rst index 6018ef122f..cb6554c3a5 100644 --- a/docs/specviz2d/import_data.rst +++ b/docs/specviz2d/import_data.rst @@ -4,17 +4,17 @@ Importing Data Into Specviz2D ***************************** -By design, Specviz2D only supports data that can be parsed as :class:`~specutils.Spectrum1D` objects, +By design, Specviz2D only supports data that can be parsed as :class:`~specutils.Spectrum` objects, as that allows the Python-level interface and parsing tools to be defined in ``specutils`` instead of being duplicated in Jdaviz. -:class:`~specutils.Spectrum1D` objects are very flexible in their capabilities, however, +:class:`~specutils.Spectrum` objects are very flexible in their capabilities, however, and hence should address most astronomical spectrum use cases. If you are creating your own data products, please read the page :ref:`create_products`. .. seealso:: `Reading from a File `_ - Specutils documentation on loading data as :class:`~specutils.Spectrum1D` objects. + Specutils documentation on loading data as :class:`~specutils.Spectrum` objects. Specviz2D can either take both a 2D and 1D spectrum as input, or can automatically extract a 1D spectrum if only a 2D spectrum is provided. To view the extraction parameters and override the @@ -42,7 +42,7 @@ Importing data through the GUI You can load your data into the Specviz2D application by clicking the :guilabel:`Import Data` button at the top left of the application's user interface. This opens a dialogue where the user can select a file -that can be parsed as a :class:`~specutils.Spectrum1D`. +that can be parsed as a :class:`~specutils.Spectrum`. After clicking :guilabel:`Import`, the data file will be parsed and loaded into the application. @@ -56,7 +56,7 @@ Alternatively, users who work in a coding environment like a Jupyter notebook can access the Specviz2D helper class API. Using this API, users can load data into the application through code with the :meth:`~jdaviz.configs.specviz2d.helper.Specviz2d.load_data` -method, which takes as input a :class:`~specutils.Spectrum1D` object or filename for the +method, which takes as input a :class:`~specutils.Spectrum` object or filename for the 2D spectrum and (optionally) the 1D spectrum. .. code-block:: python diff --git a/docs/specviz2d/plugins.rst b/docs/specviz2d/plugins.rst index d7639dd97e..7ec9206b9b 100644 --- a/docs/specviz2d/plugins.rst +++ b/docs/specviz2d/plugins.rst @@ -156,7 +156,7 @@ To export and access the specreduce Background object defined in the plugin, cal bg = sp_ext.export_bg() -To access the background image, background spectrum, or background-subtracted image as a :class:`~specutils.Spectrum1D` object, +To access the background image, background spectrum, or background-subtracted image as a :class:`~specutils.Spectrum` object, call :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.export_bg_img`, :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.export_bg_spectrum`, or :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.export_bg_sub`, respectively. @@ -203,7 +203,7 @@ To export and access the specreduce extraction object defined in the plugin, cal ext = sp_ext.export_extract() -To access the extracted spectrum as a :class:`~specutils.Spectrum1D` object, call :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.export_extract_spectrum`. +To access the extracted spectrum as a :class:`~specutils.Spectrum` object, call :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.export_extract_spectrum`. To import the parameters from a specreduce extraction object (either a new object, or an exported one modified in the notebook) into the plugin, call :py:meth:`~jdaviz.configs.specviz2d.plugins.spectral_extraction.spectral_extraction.SpectralExtraction.import_extract`: diff --git a/jdaviz/app.py b/jdaviz/app.py index ffbd64a19d..1d29cb9323 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -35,7 +35,7 @@ from ipyvuetify import VuetifyTemplate from ipywidgets import widget_serialization from traitlets import Dict, Bool, Unicode, Any -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from jdaviz import __version__ from jdaviz import style_registry @@ -105,10 +105,10 @@ def to_unit(self, data, cid, values, original_units, target_units): return values elif cid.label == "flux": try: - spec = data.get_object(cls=Spectrum1D) + spec = data.get_object(cls=Spectrum) except RuntimeError: data = data.get_object(cls=NDDataArray) - spec = Spectrum1D(flux=data.data * u.Unit(original_units)) + spec = Spectrum(flux=data.data * u.Unit(original_units)) return flux_conversion(values, original_units, target_units, spec) else: # spectral axis return spectral_axis_conversion(values, original_units, target_units) @@ -755,17 +755,14 @@ def _link_new_data(self, reference_data=None, data_to_be_linked=None): elif self.config == 'cubeviz' and linked_data.ndim == 1: # Don't want to use negative indices in case there are extra components like a mask - ref_wavelength_component = dc[0].components[5] + spectral_axis_index = dc[0].meta['spectral_axis_index'] + ref_wavelength_component = dc[0].components[spectral_axis_index] + # May need to update this for specutils 2 ref_flux_component = dc[0].components[6] linked_wavelength_component = dc[-1].components[1] linked_flux_component = dc[-1].components[-1] - links = [ - LinkSameWithUnits(ref_wavelength_component, linked_wavelength_component), - LinkSame(ref_flux_component, linked_flux_component) - ] - - dc.add_link(links) + dc.add_link(LinkSame(ref_wavelength_component, linked_wavelength_component)) return elif (linked_data.meta.get('Plugin', None) == 'Spectral Extraction' or @@ -1082,7 +1079,7 @@ def _get_range_subset_bounds(self, subset_state, if not hasattr(subset_state.att, "parent"): # e.g., Cubeviz viewer = self.get_viewer(self._jdaviz_helper._default_spectrum_viewer_reference_name) data = viewer.data() - if data and len(data) > 0 and isinstance(data[0], Spectrum1D): + if data and len(data) > 0 and isinstance(data[0], Spectrum): units = data[0].spectral_axis.unit else: raise ValueError("Unable to find spectral axis units") @@ -1535,7 +1532,7 @@ def return_data_label(self, loaded_object, ext=None, alt_name=None, check_unique Parameters ---------- loaded_object : str or object - The path to a data file or FITS HDUList or image object or Spectrum1D or + The path to a data file or FITS HDUList or image object or Spectrum or NDData array or numpy.ndarray. ext : str, optional The extension (or other distinguishing feature) of data used to identify it. @@ -1573,8 +1570,8 @@ def return_data_label(self, loaded_object, ext=None, alt_name=None, check_unique data_label = f"{loaded_object.file_name}[HDU object]" else: data_label = "Unknown HDU object" - elif isinstance(loaded_object, Spectrum1D): - data_label = "Spectrum1D" + elif isinstance(loaded_object, Spectrum): + data_label = "Spectrum" elif isinstance(loaded_object, NDData): data_label = "NDData" elif isinstance(loaded_object, np.ndarray): diff --git a/jdaviz/configs/cubeviz/helper.py b/jdaviz/configs/cubeviz/helper.py index 42065f8401..2c98a38030 100644 --- a/jdaviz/configs/cubeviz/helper.py +++ b/jdaviz/configs/cubeviz/helper.py @@ -1,3 +1,5 @@ +from jdaviz.core.events import SnackbarMessage +from jdaviz.core.helpers import ImageConfigHelper from jdaviz.configs.default.plugins.line_lists.line_list_mixin import LineListMixin from jdaviz.configs.specviz import Specviz from jdaviz.core.events import AddDataMessage, SnackbarMessage @@ -54,7 +56,7 @@ def load_data(self, data, data_label=None, override_cube_limit=False, **kwargs): Parameters ---------- - data : str, `~astropy.io.fits.HDUList`, `~specutils.Spectrum1D`, or ndarray + data : str, `~astropy.io.fits.HDUList`, `~specutils.Spectrum`, or ndarray A string file path, astropy FITS object pointing to the data cube, a spectrum object, or a Numpy array cube. If plain array is given, axes order must be ``(x, y, z)``. diff --git a/jdaviz/configs/cubeviz/plugins/mixins.py b/jdaviz/configs/cubeviz/plugins/mixins.py index 41ec392cf7..c941e7bd4a 100644 --- a/jdaviz/configs/cubeviz/plugins/mixins.py +++ b/jdaviz/configs/cubeviz/plugins/mixins.py @@ -63,10 +63,19 @@ def _set_slice_indicator_value(self, value): class WithSliceSelection: - @property + @cached_property def slice_index(self): # index in state.slices corresponding to the slice axis - return 2 + for layer in self.layers: + try: + data_obj = layer.layer.data + return data_obj.meta['spectral_axis_index'] + except (AttributeError, KeyError): + raise + else: + break + else: + return 2 @property def slice_component_label(self): diff --git a/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py b/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py index ddc9c70dfc..400a76a347 100644 --- a/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py +++ b/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py @@ -4,7 +4,7 @@ from astropy.nddata import CCDData from astropy.utils import minversion from traitlets import Bool, List, Unicode, observe -from specutils import manipulation, analysis, Spectrum1D +from specutils import manipulation, analysis, Spectrum from jdaviz.core.custom_traitlets import IntHandleEmpty, FloatHandleEmpty from jdaviz.core.events import SnackbarMessage, GlobalDisplayUnitChanged @@ -306,12 +306,12 @@ def calculate_moment(self, add_data=True): ref_wavelength = self.reference_wavelength * u.Unit(self.dataset_spectral_unit) slab_sa = slab.spectral_axis.to("km/s", doppler_convention="relativistic", doppler_rest=ref_wavelength) - slab = Spectrum1D(slab.flux, slab_sa, uncertainty=slab.uncertainty) + slab = Spectrum(slab.flux, slab_sa, uncertainty=slab.uncertainty) # Otherwise convert spectral axis to display units, have to do frequency <-> wavelength # before calculating else: slab_sa = slab.spectral_axis.to(self.app._get_display_unit('spectral')) - slab = Spectrum1D(slab.flux, slab_sa, uncertainty=slab.uncertainty) + slab = Spectrum(slab.flux, slab_sa, uncertainty=slab.uncertainty) # Finally actually calculate the moment self.moment = analysis.moment(slab, order=n_moment).T diff --git a/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py b/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py index 44389b352d..4ee0f26819 100644 --- a/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py +++ b/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py @@ -84,6 +84,9 @@ def test_moment_calculation(cubeviz_helper, spectrum1d_cube, flux_viewer = cubeviz_helper.app.get_viewer(cubeviz_helper._default_flux_viewer_reference_name) + print(spectrum1d_cube.shape, spectrum1d_cube.spectral_axis_index) + print(flux_viewer.layers[0].layer.data.meta['spectral_axis_index']) + # Since we are not really displaying, need this to trigger GUI stuff. flux_viewer.shape = (100, 100) flux_viewer.state._set_axes_aspect_ratio(1) @@ -109,7 +112,7 @@ def test_moment_calculation(cubeviz_helper, spectrum1d_cube, assert len(mv_data) == 1 assert mv_data[0].label == 'moment 0' - assert len(dc.links) == 19 + assert len(dc.links) == 17 # label should remain unchanged but raise overwrite warnings assert mm._obj.results_label == 'moment 0' @@ -119,7 +122,7 @@ def test_moment_calculation(cubeviz_helper, spectrum1d_cube, label_mouseover = cubeviz_helper.app.session.application._tools['g-coords-info'] label_mouseover._viewer_mouse_event(flux_viewer, {'event': 'mousemove', 'domain': {'x': 0, 'y': 0}}) - assert flux_viewer.state.slices == (0, 0, 1) + assert flux_viewer.state.slices == (1, 0, 0) # Slice 0 has 8 pixels, this is Slice 1 assert label_mouseover.as_text() == (f"Pixel x=00.0 y=00.0 Value +8.00000e+00 {cube_unit}", "World 13h39m59.9731s +27d00m00.3600s (ICRS)", diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py index 4bb4afa2b2..d1450659c1 100644 --- a/jdaviz/configs/cubeviz/plugins/parsers.py +++ b/jdaviz/configs/cubeviz/plugins/parsers.py @@ -6,9 +6,8 @@ from astropy import units as u from astropy.io import fits from astropy.nddata import StdDevUncertainty -from astropy.time import Time from astropy.wcs import WCS -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.custom_units_and_equivs import PIX2, _eqv_flux_to_sb_pixel from jdaviz.core.registries import data_parser_registry @@ -24,7 +23,8 @@ @data_parser_registry("cubeviz-data-parser") def parse_data(app, file_obj, data_type=None, data_label=None, - parent=None, cache=None, local_path=None, timeout=None): + parent=None, cache=None, local_path=None, timeout=None, + specutils_format=None): """ Attempts to parse a data file and auto-populate available viewers in cubeviz. @@ -52,6 +52,11 @@ def parse_data(app, file_obj, data_type=None, data_label=None, remote requests in seconds (passed to `~astropy.utils.data.download_file` or `~astroquery.mast.Conf.timeout`). + specutils_format : str, optional + Optional format string to pass to Spectrum.read(), see + https://specutils.readthedocs.io/en/stable/spectrum1d.html#list-of-loaders + for valid format strings. Useful for processed files that may not include + the original headers with information used to auto-identify. """ flux_viewer_reference_name = app._jdaviz_helper._default_flux_viewer_reference_name @@ -91,6 +96,16 @@ def parse_data(app, file_obj, data_type=None, data_label=None, file_obj, cache=cache, local_path=local_path, timeout=timeout ) + if specutils_format is not None: + sc = Spectrum.read(file_obj, format=specutils_format) + _parse_spectrum1d_3d( + app, sc, data_label=data_label, + flux_viewer_reference_name=flux_viewer_reference_name, + uncert_viewer_reference_name=uncert_viewer_reference_name + ) + app.get_tray_item_from_name("Spectral Extraction").disabled_msg = "" + return + file_name = os.path.basename(file_obj) with fits.open(file_obj) as hdulist: @@ -100,27 +115,16 @@ def parse_data(app, file_obj, data_type=None, data_label=None, # NOTE: Alerted to deprecation of FILETYPE keyword from pipeline on 2022-07-08 # Kept for posterity in for data processed prior to this date. Use EXP_TYPE instead filetype = prihdr.get('FILETYPE', '').lower() - system = prihdr.get('SYSTEM', '').lower() if telescop == 'jwst' and ('ifu' in exptype or 'mrs' in exptype or filetype == '3d ifu cube'): - sc = Spectrum1D.read(file_obj) + sc = Spectrum.read(file_obj) data_label = app.return_data_label(file_name) _parse_spectrum1d_3d( app, sc, data_label=data_label, flux_viewer_reference_name=flux_viewer_reference_name, - spectrum_viewer_reference_name=spectrum_viewer_reference_name, uncert_viewer_reference_name=uncert_viewer_reference_name ) - elif telescop == 'jwst' and filetype == 'r3d' and system == 'esa-pipeline': - for ext, viewer_name in (('DATA', flux_viewer_reference_name), - ('ERR', uncert_viewer_reference_name), - ('QUALITY', None)): - data_label = app.return_data_label(file_name, ext) - _parse_esa_s3d( - app, hdulist, data_label, ext=ext, viewer_name=viewer_name, - flux_viewer_reference_name=flux_viewer_reference_name, - ) else: try: _parse_spectrum1d_3d( @@ -138,9 +142,8 @@ def parse_data(app, file_obj, data_type=None, data_label=None, # If the data types are custom data objects, use explicit parsers. Note # that this relies on the glue-astronomy machinery to turn the data object # into something glue can understand. - elif isinstance(file_obj, Spectrum1D) and file_obj.flux.ndim in (1, 3): + elif isinstance(file_obj, Spectrum) and file_obj.flux.ndim in (1, 3): if file_obj.flux.ndim == 3: - print("Parsing 3D Spectrum1D") _parse_spectrum1d_3d( app, file_obj, data_label=data_label, flux_viewer_reference_name=flux_viewer_reference_name, @@ -167,7 +170,8 @@ def _get_celestial_wcs(wcs): def _return_spectrum_with_correct_units(flux, wcs, metadata, data_type=None, target_wave_unit=None, hdulist=None, - uncertainty=None, mask=None, apply_pix2=False): + uncertainty=None, mask=None, apply_pix2=False, + spectral_axis=None): """Upstream issue of WCS not using the correct units for data must be fixed here. Issue: https://github.com/astropy/astropy/issues/3658. @@ -183,7 +187,11 @@ def _return_spectrum_with_correct_units(flux, wcs, metadata, data_type=None, warnings.filterwarnings( 'ignore', message='Input WCS indicates that the spectral axis is not last', category=UserWarning) - sc = Spectrum1D(flux=flux, wcs=wcs, meta=metadata, uncertainty=uncertainty, mask=mask) + if spectral_axis is None: + sc = Spectrum(flux=flux, wcs=wcs, uncertainty=uncertainty, mask=mask) + else: + sc = Spectrum(flux=flux, spectral_axis=spectral_axis, + uncertainty=uncertainty, mask=mask) # convert flux and uncertainty to per-pix2 if input is not a surface brightness target_flux_unit = None @@ -211,6 +219,8 @@ def _return_spectrum_with_correct_units(flux, wcs, metadata, data_type=None, target_wave_unit = u.Unit(hdr[cunit_key]) found_target = True break + print(f"Target wave unit: {target_wave_unit}") + print(f"Current unit: {sc.spectral_axis.unit}") if target_wave_unit == sc.spectral_axis.unit: target_wave_unit = None @@ -311,59 +321,6 @@ def _parse_hdulist(app, hdulist, file_name=None, app._jdaviz_helper._loaded_flux_cube = app.data_collection[data_label] -def _parse_esa_s3d(app, hdulist, data_label, ext='DATA', flux_viewer_reference_name=None, - spectrum_viewer_reference_name=None): - hdu = hdulist[ext] - data_type = _get_data_type_by_hdu(hdu) - - if ext == 'QUALITY': # QUALITY flags have no unit - flux = hdu.data << u.dimensionless_unscaled - else: - unit = u.Unit(hdu.header.get('BUNIT', 'count')) - flux = hdu.data << unit - - hdr = hdulist[1].header - - wcs_dict = { - 'CTYPE1': 'WAVE ', 'CUNIT1': 'um', 'CDELT1': hdr['CDELT3'] * 1E6, - 'CRPIX1': hdr['CRPIX3'], - 'CRVAL1': hdr['CRVAL3'] * 1E6, 'NAXIS1': hdr['NAXIS3'], - 'CTYPE2': 'DEC--TAN', 'CUNIT2': 'deg', 'CDELT2': hdr['CDELT1'], 'CRPIX2': hdr['CRPIX1'], - 'CRVAL2': hdr['CRVAL1'], 'NAXIS2': hdr['NAXIS1'], - 'CTYPE3': 'RA---TAN', 'CUNIT3': 'deg', 'CDELT3': hdr['CDELT2'], 'CRPIX3': hdr['CRPIX2'], - 'CRVAL3': hdr['CRVAL2'], 'NAXIS3': hdr['NAXIS2']} - - wcs = WCS(wcs_dict) - flux = np.moveaxis(flux, 0, -1) - flux = np.swapaxes(flux, 0, 1) - - metadata = standardize_metadata(hdu.header) - metadata.update(wcs_dict) # To be internally consistent - if hdu.name != 'PRIMARY' and 'PRIMARY' in hdulist: - metadata[PRIHDR_KEY] = standardize_metadata(hdulist['PRIMARY'].header) - - # store original WCS in metadata. this is a hacky workaround for converting subsets - # to sky regions, where the parent data of the subset might have dropped spatial WCS info - metadata['_orig_spatial_wcs'] = _get_celestial_wcs(wcs) - - data = _return_spectrum_with_correct_units( - flux, wcs, metadata, data_type=data_type, hdulist=hdulist) - - app.add_data(data, data_label) - - if data_type == 'flux': # Forced wave unit conversion made it lose stuff, so re-add - app.data_collection[-1].get_component("flux").units = flux.unit - - app.add_data_to_viewer(flux_viewer_reference_name, data_label) - - if data_type == 'flux': - app._jdaviz_helper._loaded_flux_cube = app.data_collection[data_label] - elif data_type == 'uncert': - app._jdaviz_helper._loaded_uncert_cube = app.data_collection[data_label] - elif data_type == 'mask': - app._jdaviz_helper._loaded_mask_cube = app.data_collection[data_label] - - def _parse_spectrum1d_3d(app, file_obj, data_label=None, flux_viewer_reference_name=None, uncert_viewer_reference_name=None, @@ -388,8 +345,8 @@ def _parse_spectrum1d_3d(app, file_obj, data_label=None, if parent is not None: parent_data_label = parent - elif ext == 'DQ': - parent_data_label = app.return_data_label(data_label, "FLUX") + elif attr == 'uncertainty': + parent_data_label = app.return_data_label(data_label, "FLUX").split(" ")[0] else: parent_data_label = None @@ -426,7 +383,7 @@ def _parse_spectrum1d_3d(app, file_obj, data_label=None, def _parse_spectrum1d(app, file_obj, data_label=None, spectrum_viewer_reference_name=None): - # Here 'file_obj' is a Spectrum1D + # Here 'file_obj' is a Spectrum if data_label is None: data_label = app.return_data_label(file_obj) @@ -436,7 +393,7 @@ def _parse_spectrum1d(app, file_obj, data_label=None, spectrum_viewer_reference_ file_obj.meta['_orig_spatial_wcs'] = _get_celestial_wcs(file_obj.wcs) if hasattr(file_obj, 'wcs') else None # noqa: E501 # TODO: glue-astronomy translators only look at the flux property of - # specutils Spectrum1D objects. Fix to support uncertainties and masks. + # specutils Spectrum objects. Fix to support uncertainties and masks. # convert data loaded in flux units to a per-square-pixel surface # brightness unit (e.g Jy to Jy/pix**2) @@ -465,7 +422,7 @@ def _parse_ndarray(app, file_obj, data_label=None, data_type=None, flux = flux << (u.count / PIX2) meta = standardize_metadata({'_orig_spatial_wcs': None}) - s3d = Spectrum1D(flux=flux, meta=meta) + s3d = Spectrum(flux=flux, meta=meta) # convert data loaded in flux units to a per-square-pixel surface # brightness unit (e.g Jy to Jy/pix**2) @@ -498,7 +455,7 @@ def _parse_gif(app, file_obj, data_label=None, flux_viewer_reference_name=None): flux = np.rot90(np.moveaxis(flux, 0, 2), k=-1, axes=(0, 1)) meta = {'filename': file_name, '_orig_spatial_wcs': None} - s3d = Spectrum1D(flux=flux * (u.count / PIX2), meta=standardize_metadata(meta)) + s3d = Spectrum(flux=flux * (u.count / PIX2), meta=standardize_metadata(meta)) app.add_data(s3d, data_label) app.add_data_to_viewer(flux_viewer_reference_name, data_label) diff --git a/jdaviz/configs/cubeviz/plugins/slice/slice.py b/jdaviz/configs/cubeviz/plugins/slice/slice.py index b2c9ae3974..ea95c35bba 100644 --- a/jdaviz/configs/cubeviz/plugins/slice/slice.py +++ b/jdaviz/configs/cubeviz/plugins/slice/slice.py @@ -166,7 +166,7 @@ def slice_display_unit_name(self): @property def valid_slice_att_names(self): if self.app.config == 'cubeviz': - return _spectral_axis_names + ['Pixel Axis 2 [x]', 'World 0'] + return _spectral_axis_names + ['Pixel Axis 2 [x]'] elif self.app.config == 'rampviz': return _temporal_axis_names + ['Pixel Axis 2 [x]'] diff --git a/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py b/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py index 06d647bc68..981c3f2042 100644 --- a/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py +++ b/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py @@ -34,8 +34,8 @@ def test_slice(cubeviz_helper, spectrum1d_cube): assert sl.value == slice_values[1] assert cubeviz_helper.app.get_viewer("flux-viewer").slice == 1 - assert cubeviz_helper.app.get_viewer("flux-viewer").state.slices[-1] == 1 - assert cubeviz_helper.app.get_viewer("uncert-viewer").state.slices[-1] == 1 + assert cubeviz_helper.app.get_viewer("flux-viewer").state.slices[0] == 1 + assert cubeviz_helper.app.get_viewer("uncert-viewer").state.slices[0] == 1 cubeviz_helper.select_wavelength(slice_values[0]) assert cubeviz_helper.app.get_viewer("flux-viewer").slice == 0 assert sl.value == slice_values[0] @@ -124,7 +124,7 @@ def test_init_slice(cubeviz_helper, spectrum1d_cube): assert sl.value == slice_values[1] assert fv.slice == 1 - assert fv.state.slices == (0, 0, 1) + assert fv.state.slices == (1, 0, 0) # make sure adding new data doesn't revert slice to 0 mm = cubeviz_helper.plugins['Moment Maps'] @@ -132,4 +132,4 @@ def test_init_slice(cubeviz_helper, spectrum1d_cube): assert sl.value == slice_values[1] assert fv.slice == 1 - assert fv.state.slices == (0, 0, 1) + assert fv.state.slices == (1, 0, 0) diff --git a/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py b/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py index c1f87223cc..a6db319ce1 100644 --- a/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py +++ b/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py @@ -5,6 +5,7 @@ from astropy import units as u from astropy.nddata import NDDataArray, StdDevUncertainty from traitlets import Any, Bool, Dict, Float, List, Unicode, observe +from specutils import Spectrum from jdaviz.core.custom_traitlets import FloatHandleEmpty from jdaviz.core.events import SnackbarMessage, SliceValueUpdatedMessage, GlobalDisplayUnitChanged @@ -142,6 +143,8 @@ def __init__(self, *args, **kwargs): self.aperture._initialize_choices() self.aperture.select_default() + self.spectral_axis_index = 0 + self.background = ApertureSubsetSelect(self, 'bg_items', 'bg_selected', @@ -487,6 +490,8 @@ def _extract_from_aperture(self, cube, uncert_cube, mask_cube, aperture, mask = nddata.mask # Use the spectral coordinate from the WCS: + pass_spectral_axis = False + spectral_axis = None if '_orig_spec' in cube.meta: wcs = cube.meta['_orig_spec'].wcs.spectral elif hasattr(cube.coords, 'spectral'): @@ -495,7 +500,8 @@ def _extract_from_aperture(self, cube, uncert_cube, mask_cube, aperture, # This is the attribute for a PaddedSpectrumWCS in the 3D case wcs = cube.coords.spectral_wcs else: - wcs = None + wcs = spectral_cube.coords + pass_spectral_axis=True # Filter out NaNs (False = good) mask = np.logical_or(mask, np.isnan(flux)) @@ -553,6 +559,11 @@ def _extract_from_aperture(self, cube, uncert_cube, mask_cube, aperture, aperture_area = 1 * sq_angle_unit collapsed_nddata = collapsed_nddata.multiply(aperture_area, propagate_uncertainties=True) + # Convert to Spectrum, with the spectral axis in correct units: + print(self.spectral_axis_index) + if hasattr(spectral_cube.coords, 'spectral_wcs'): + print(f"Has spectral_wcs: {spectral_cube.coords.spectral_wcs.world_axis_units}") + target_wave_unit = spectral_cube.coords.spectral_wcs.world_axis_units[self.spectral_axis_index] else: collapsed_nddata = getattr(nddata_reshaped, selected_func)( axis=self.spatial_axes, **kwargs @@ -563,9 +574,9 @@ def _extract_from_aperture(self, cube, uncert_cube, mask_cube, aperture, def _return_extracted(self, cube, wcs, collapsed_nddata): # Convert to Spectrum1D, with the spectral axis in correct units: if hasattr(cube.coords, 'spectral_wcs'): - target_wave_unit = cube.coords.spectral_wcs.world_axis_units[0] + target_wave_unit = cube.coords.spectral_wcs.world_axis_units[self.spectral_axis_index] elif hasattr(cube.coords, 'spectral'): - target_wave_unit = cube.coords.spectral.world_axis_units[0] + target_wave_unit = cube.coords.spectral.world_axis_units[self.spectral_axis_index] else: target_wave_unit = None @@ -576,11 +587,20 @@ def _return_extracted(self, cube, wcs, collapsed_nddata): mask = collapsed_nddata.mask uncertainty = collapsed_nddata.uncertainty + if pass_spectral_axis: + wcs_args = [0, 0, 0] + spec_indices = np.arange(spectral_cube.shape[self.spectral_axis_index]) + wcs_args[self.spectral_axis_index] = spec_indices + wcs_args.reverse() + spectral_and_spatial = wcs.pixel_to_world(*wcs_args) + spectral_axis = [x for x in spectral_and_spatial if isinstance(x, SpectralCoord)][0] # noqa + collapsed_spec = _return_spectrum_with_correct_units( flux, wcs, collapsed_nddata.meta, data_type='flux', target_wave_unit=target_wave_unit, uncertainty=uncertainty, - mask=mask + mask=mask, + spectral_axis=spectral_axis ) return collapsed_spec diff --git a/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py b/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py index 9893fd5420..76a7d2c3fd 100644 --- a/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py +++ b/jdaviz/configs/cubeviz/plugins/spectral_extraction/tests/test_spectral_extraction.py @@ -12,7 +12,7 @@ from numpy.testing import assert_allclose, assert_array_equal from regions import (CirclePixelRegion, CircleAnnulusPixelRegion, EllipsePixelRegion, RectanglePixelRegion, PixCoord) -from specutils import Spectrum1D +from specutils import Spectrum from specutils.manipulation import FluxConservingResampler from jdaviz.core.custom_units_and_equivs import PIX2, SPEC_PHOTON_FLUX_DENSITY_UNITS @@ -47,7 +47,7 @@ def test_version_after_nddata_update(cubeviz_helper, spectrum1d_cube_with_uncert assert plg._obj.disabled_msg == '' assert isinstance(spectral_cube, NDDataArray) - assert isinstance(collapsed_cube_s1d, Spectrum1D) + assert isinstance(collapsed_cube_s1d, Spectrum) assert_allclose( collapsed_cube_nddata.data, @@ -393,7 +393,7 @@ def test_cone_and_cylinder_errors(cubeviz_helper, spectrum1d_cube_largest): def test_cone_aperture_with_frequency_units(cubeviz_helper, spectral_cube_wcs): - data = Spectrum1D(flux=np.ones((128, 129, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 129, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data, data_label="Test Flux") cubeviz_helper.plugins['Subset Tools'].import_region( [CirclePixelRegion(PixCoord(14, 15), radius=2.5)]) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py index dd3e587628..8e92a47e7a 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py @@ -4,7 +4,7 @@ import pytest from astropy import units as u from astropy.wcs import WCS -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from glue_astronomy.translators.spectrum1d import PaddedSpectrumWCS from numpy.testing import assert_allclose, assert_array_equal @@ -32,7 +32,7 @@ def test_fits_image_hdu_with_microns(image_cube_hdu_obj_microns, cubeviz_helper) assert len(cubeviz_helper.app.data_collection) == 4 # 3 cubes and extracted spectrum assert cubeviz_helper.app.data_collection[0].label == 'has_microns[FLUX]' - flux_cube = cubeviz_helper.app.data_collection[0].get_object(Spectrum1D, statistic=None) + flux_cube = cubeviz_helper.app.data_collection[0].get_object(Spectrum, statistic=None) assert flux_cube.spectral_axis.unit == u.um # This tests the same data as test_fits_image_hdu_parse above. @@ -125,7 +125,7 @@ def test_fits_image_hdu_parse_from_file(tmpdir, image_cube_hdu_obj, cubeviz_help def test_spectrum3d_parse(image_cube_hdu_obj, cubeviz_helper): flux = image_cube_hdu_obj[1].data << u.Unit(image_cube_hdu_obj[1].header['BUNIT']) wcs = WCS(image_cube_hdu_obj[1].header, image_cube_hdu_obj) - sc = Spectrum1D(flux=flux, wcs=wcs) + sc = Spectrum(flux=flux, wcs=wcs) cubeviz_helper.load_data(sc) data = cubeviz_helper.app.data_collection[0] @@ -152,7 +152,7 @@ def test_spectrum3d_parse(image_cube_hdu_obj, cubeviz_helper): @pytest.mark.parametrize("flux_unit", [u.nJy, u.DN, u.DN / u.s]) def test_spectrum3d_no_wcs_parse(cubeviz_helper, flux_unit): - sc = Spectrum1D(flux=np.ones(24).reshape((2, 3, 4)) * flux_unit) # x, y, z + sc = Spectrum(flux=np.ones(24).reshape((2, 3, 4)) * flux_unit) # x, y, z cubeviz_helper.load_data(sc) assert sc.spectral_axis.unit == u.pix @@ -169,7 +169,7 @@ def test_spectrum1d_parse(spectrum1d, cubeviz_helper): cubeviz_helper.load_data(spectrum1d) assert len(cubeviz_helper.app.data_collection) == 1 - assert cubeviz_helper.app.data_collection[0].label.endswith('Spectrum1D') + assert cubeviz_helper.app.data_collection[0].label.endswith('Spectrum') assert cubeviz_helper.app.data_collection[0].meta['uncertainty_type'] == 'std' # Coordinate display is only for spatial image, which is missing here. @@ -265,7 +265,7 @@ def test_invalid_data_types(cubeviz_helper): cubeviz_helper.load_data(WCS(naxis=3)) with pytest.raises(NotImplementedError, match='Unsupported data format'): - cubeviz_helper.load_data(Spectrum1D(flux=np.ones((2, 2)) * u.nJy)) + cubeviz_helper.load_data(Spectrum(flux=np.ones((2, 2)) * u.nJy)) with pytest.raises(NotImplementedError, match='Unsupported data format'): cubeviz_helper.load_data(np.ones((2, 2))) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_regions.py b/jdaviz/configs/cubeviz/plugins/tests/test_regions.py index 230f7b9dda..234a4eb1f4 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_regions.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_regions.py @@ -7,7 +7,7 @@ from astropy import units as u from astropy.coordinates import SkyCoord from regions import PixCoord, CirclePixelRegion, CircleSkyRegion, EllipsePixelRegion -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from jdaviz.configs.imviz.tests.test_regions import BaseRegionHandler @@ -82,4 +82,4 @@ def test_spatial_spectral_mix(self): 'Spectrum (sum) (Subset 2)', 'Spectrum (Subset 1, sum) (Subset 2)'] for sp in spectral_subsets.values(): - assert isinstance(sp, Spectrum1D) + assert isinstance(sp, Spectrum) diff --git a/jdaviz/configs/cubeviz/plugins/tools.py b/jdaviz/configs/cubeviz/plugins/tools.py index ec77106dda..d18ac0f181 100644 --- a/jdaviz/configs/cubeviz/plugins/tools.py +++ b/jdaviz/configs/cubeviz/plugins/tools.py @@ -5,7 +5,7 @@ from glue_jupyter.bqplot.image import BqplotImageView from glue.viewers.common.tool import CheckableTool import numpy as np -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.events import SliceToolStateMessage, SliceSelectSliceMessage from jdaviz.core.tools import PanZoom, BoxZoom, _MatchedZoomMixin @@ -147,7 +147,7 @@ def _mouse_move_worker(self, x, y): return cube_data = cube_data[0] - if isinstance(cube_data, Spectrum1D): + if isinstance(cube_data, Spectrum): spectrum = cube_data else: spectrum = cube_data.get_object(statistic=None) @@ -155,7 +155,7 @@ def _mouse_move_worker(self, x, y): x_unit = self._profile_viewer.state.x_display_unit if spectrum.spectral_axis.unit != x_unit: new_spectral_axis = spectrum.spectral_axis.to(x_unit) - spectrum = Spectrum1D(spectrum.flux, new_spectral_axis) + spectrum = Spectrum(spectrum.flux, new_spectral_axis) if x >= spectrum.flux.shape[0] or x < 0 or y >= spectrum.flux.shape[1] or y < 0: self._reset_profile_viewer_bounds() diff --git a/jdaviz/configs/default/plugins/collapse/tests/test_collapse.py b/jdaviz/configs/default/plugins/collapse/tests/test_collapse.py index eec8067aa1..cd408f6cb9 100644 --- a/jdaviz/configs/default/plugins/collapse/tests/test_collapse.py +++ b/jdaviz/configs/default/plugins/collapse/tests/test_collapse.py @@ -1,12 +1,12 @@ import numpy as np import pytest from astropy import units as u -from specutils import Spectrum1D +from specutils import Spectrum @pytest.mark.filterwarnings('ignore') def test_linking_after_collapse(cubeviz_helper, spectral_cube_wcs): - cubeviz_helper.load_data(Spectrum1D(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs)) + cubeviz_helper.load_data(Spectrum(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs)) dc = cubeviz_helper.app.data_collection # TODO: this now fails when instantiating Collapse after initialization @@ -39,7 +39,7 @@ def test_linking_after_collapse(cubeviz_helper, spectral_cube_wcs): def test_collapsed_to_extract_plugin(cubeviz_helper, spectral_cube_wcs, tmp_path): - cubeviz_helper.load_data(Spectrum1D(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs)) + cubeviz_helper.load_data(Spectrum(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs)) collapse_plugin = cubeviz_helper.plugins['Collapse'] diff --git a/jdaviz/configs/default/plugins/export/export.py b/jdaviz/configs/default/plugins/export/export.py index 9360d075f6..b32615ddf3 100644 --- a/jdaviz/configs/default/plugins/export/export.py +++ b/jdaviz/configs/default/plugins/export/export.py @@ -1,8 +1,13 @@ import os import time from pathlib import Path -from traitlets import Bool, List, Unicode, observe + +from astropy import units as u +from astropy.nddata import CCDData +from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage from glue_jupyter.bqplot.image import BqplotImageView +from specutils import Spectrum +from traitlets import Bool, List, Unicode, observe from jdaviz.core.custom_traitlets import FloatHandleEmpty, IntHandleEmpty from jdaviz.core.marks import ShadowMixin @@ -12,15 +17,9 @@ SubsetSelectMixin, PluginTableSelectMixin, PluginPlotSelectMixin, AutoTextField, MultiselectMixin, with_spinner) -from glue.core.message import SubsetCreateMessage, SubsetDeleteMessage, SubsetUpdateMessage - from jdaviz.core.events import AddDataMessage, SnackbarMessage from jdaviz.core.user_api import PluginUserApi -from specutils import Spectrum1D -from astropy import units as u -from astropy.nddata import CCDData - try: import cv2 except ImportError: @@ -361,7 +360,7 @@ def _set_dataset_not_supported_msg(self, msg=None): # NOTE: should not be a valid choice due to dataset filters, but we'll include # another check here. self.data_invalid_msg = "Data export is only available for plugin generated data." - elif not isinstance(self.dataset.selected_obj, (Spectrum1D, CCDData)): + elif not isinstance(self.dataset.selected_obj, (Spectrum, CCDData)): self.data_invalid_msg = "Export is not yet implemented for this type of data" elif (data_unit := self.dataset.selected_obj.unit) == u.Unit('DN/s'): self.data_invalid_msg = f'Export Disabled: The unit {data_unit} could not be saved in native FITS format.' # noqa: E501 diff --git a/jdaviz/configs/default/plugins/export/tests/test_export.py b/jdaviz/configs/default/plugins/export/tests/test_export.py index 35b61c25cd..aad7982e82 100644 --- a/jdaviz/configs/default/plugins/export/tests/test_export.py +++ b/jdaviz/configs/default/plugins/export/tests/test_export.py @@ -8,7 +8,7 @@ from astropy.nddata import NDData from glue.core.roi import CircularROI from regions import Regions, CircleSkyRegion -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from pathlib import Path @@ -75,8 +75,8 @@ def test_not_implemented(self, cubeviz_helper, spectral_cube_wcs): the correct warning message to display in UI). """ - data = Spectrum1D(flux=np.ones((500, 500, 2)) * u.nJy, - wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((500, 500, 2)) * u.nJy, + wcs=spectral_cube_wcs) cubeviz_helper.load_data(data) subset_plugin = cubeviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CircularROI(xc=255, yc=255, radius=50)) @@ -119,7 +119,7 @@ def test_export_subsets_wcs(self, imviz_helper, spectral_cube_wcs): def test_basic_export_subsets_cubeviz(self, cubeviz_helper, spectral_cube_wcs): - data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data) subset_plugin = cubeviz_helper.plugins['Subset Tools'] @@ -256,7 +256,7 @@ def test_disable_export_for_unsupported_units(specviz2d_helper): dn_per_s = u.DN / u.s data = np.zeros((5, 10)) data[3] = np.arange(10) - data = Spectrum1D(flux=data*dn_per_s, spectral_axis=data[3]*u.um) + data = Spectrum(flux=data*dn_per_s, spectral_axis=data[3]*u.um) specviz2d_helper.load_data(data) gs = specviz2d_helper.plugins["Gaussian Smooth"] diff --git a/jdaviz/configs/default/plugins/gaussian_smooth/gaussian_smooth.py b/jdaviz/configs/default/plugins/gaussian_smooth/gaussian_smooth.py index 7fe61ada22..f2b344e2dc 100644 --- a/jdaviz/configs/default/plugins/gaussian_smooth/gaussian_smooth.py +++ b/jdaviz/configs/default/plugins/gaussian_smooth/gaussian_smooth.py @@ -1,7 +1,7 @@ import numpy as np from astropy.convolution import convolve, Gaussian2DKernel -from specutils import Spectrum1D +from specutils import Spectrum from specutils.manipulation import gaussian_smooth from traitlets import List, Unicode, Bool, observe @@ -151,7 +151,7 @@ def smooth(self, add_data=True): Returns ------- - spec : `~specutils.Spectrum1D` + spec : `~specutils.Spectrum` The smoothed spectrum or data cube """ if self.mode_selected == 'Spatial': @@ -190,18 +190,18 @@ def spectral_smooth(self): Returns ------- - spec : `~specutils.Spectrum1D` + spec : `~specutils.Spectrum` The smoothed spectrum """ # Testing inputs to make sure putting smoothed spectrum into # datacollection works # input_flux = Quantity(np.array([0.2, 0.3, 2.2, 0.3]), u.Jy) # input_spaxis = Quantity(np.array([1, 2, 3, 4]), u.micron) - # spec1 = Spectrum1D(input_flux, spectral_axis=input_spaxis) + # spec1 = Spectrum(input_flux, spectral_axis=input_spaxis) # Takes the user input from the dialog (stddev) and uses it to # define a standard deviation for gaussian smoothing - cube = self.dataset.get_object(cls=Spectrum1D, statistic=None) + cube = self.dataset.get_object(cls=Spectrum, statistic=None) spec_smoothed = gaussian_smooth(cube, stddev=self.stddev) return spec_smoothed @@ -215,7 +215,7 @@ def spatial_smooth(self): Returns ------- - cube : `~specutils.Spectrum1D` + cube : `~specutils.Spectrum` The smoothed cube """ cube = self.dataset.selected_obj @@ -237,6 +237,6 @@ def spatial_smooth(self): # Create a new cube with the old metadata. Note that astropy # convolution generates values for masked (NaN) data. - newcube = Spectrum1D(flux=convolved_data * flux_unit, wcs=w) + newcube = Spectrum(flux=convolved_data * flux_unit, wcs=w) return newcube diff --git a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py index 32dbffa23a..49cf6da0c0 100644 --- a/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py +++ b/jdaviz/configs/default/plugins/gaussian_smooth/tests/test_gaussian_smooth.py @@ -1,7 +1,7 @@ import numpy as np import pytest from astropy.utils.exceptions import AstropyUserWarning -from specutils import Spectrum1D +from specutils import Spectrum def test_linking_after_spectral_smooth(cubeviz_helper, spectrum1d_cube): @@ -122,7 +122,7 @@ def test_spatial_convolution(cubeviz_helper, spectrum1d_cube): assert len(dc) == 3 assert dc[-1].label == f'{data_label}[FLUX] spatial-smooth stddev-3.0' assert dc[-1].shape == (4, 2, 2) # specutils moved spectral axis to last - assert (dc[f'{data_label}[FLUX] spatial-smooth stddev-3.0'].get_object(cls=Spectrum1D, + assert (dc[f'{data_label}[FLUX] spatial-smooth stddev-3.0'].get_object(cls=Spectrum, statistic=None).shape == (4, 2, 2)) diff --git a/jdaviz/configs/default/plugins/line_lists/line_lists.py b/jdaviz/configs/default/plugins/line_lists/line_lists.py index 81bad779a1..febff06a54 100644 --- a/jdaviz/configs/default/plugins/line_lists/line_lists.py +++ b/jdaviz/configs/default/plugins/line_lists/line_lists.py @@ -208,7 +208,7 @@ def _on_viewer_data_changed(self, msg=None): self._bounds["min"] = viewer_data.spectral_axis[0] self._bounds["max"] = viewer_data.spectral_axis[-1] - # set redshift slider to redshift stored in Spectrum1D object + # set redshift slider to redshift stored in Spectrum object if viewer_data.meta.get('plugin', None) is not None: self.rs_redshift = (viewer_data.redshift.value if hasattr(viewer_data.redshift, 'value') diff --git a/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py b/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py index 37062dde72..24499f1f8f 100644 --- a/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py +++ b/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py @@ -4,15 +4,15 @@ import astropy.units as u from astropy.table import QTable -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.marks import SpectralLine from jdaviz.core.linelists import get_available_linelists def test_line_lists(specviz_helper): - spec = Spectrum1D(flux=np.random.rand(100)*u.Jy, - spectral_axis=np.arange(6000, 7000, 10)*u.AA) + spec = Spectrum(flux=np.random.rand(100)*u.Jy, + spectral_axis=np.arange(6000, 7000, 10)*u.AA) specviz_helper.load_data(spec) lt = QTable() diff --git a/jdaviz/configs/default/plugins/model_fitting/fitting_backend.py b/jdaviz/configs/default/plugins/model_fitting/fitting_backend.py index 9c2bb75a78..4afec6a648 100644 --- a/jdaviz/configs/default/plugins/model_fitting/fitting_backend.py +++ b/jdaviz/configs/default/plugins/model_fitting/fitting_backend.py @@ -7,7 +7,7 @@ import astropy.units as u -from specutils import Spectrum1D +from specutils import Spectrum from specutils.fitting import fit_lines __all__ = ['fit_model_to_spectrum'] @@ -16,7 +16,7 @@ def fit_model_to_spectrum(spectrum, component_list, expression, run_fitter=False, window=None, n_cpu=None): """Fits a `~astropy.modeling.CompoundModel` to a - `~specutils.Spectrum1D` instance. + `~specutils.Spectrum` instance. If the input spectrum represents a spectral cube, then fits the model to every spaxel in the cube, using @@ -25,7 +25,7 @@ def fit_model_to_spectrum(spectrum, component_list, expression, Parameters ---------- - spectrum : `~specutils.Spectrum1D` + spectrum : `~specutils.Spectrum` The spectrum to be fitted. component_list : list @@ -62,7 +62,7 @@ def fit_model_to_spectrum(spectrum, component_list, expression, spaxel, a list with 2D arrays, each one storing fitted parameter values for all spaxels, is returned. - output_spectrum : `~specutils.Spectrum1D` + output_spectrum : `~specutils.Spectrum` The realization of the fitted model as a spectrum. The spectrum will be 1D or 3D depending on the shape of input spectrum. """ @@ -77,13 +77,13 @@ def fit_model_to_spectrum(spectrum, component_list, expression, def _fit_1D(initial_model, spectrum, run_fitter, filter_non_finite=True, window=None): """ - Fits an astropy CompoundModel to a Spectrum1D instance. + Fits an astropy CompoundModel to a Spectrum instance. Parameters ---------- initial_model : :class: `astropy.modeling.CompoundModel` Initial guess for the model to be fitted. - spectrum : :class:`specutils.Spectrum1D` + spectrum : :class:`specutils.Spectrum` The spectrum to be fitted. run_fitter : bool When False (the default), the function composes the compound @@ -95,7 +95,7 @@ def _fit_1D(initial_model, spectrum, run_fitter, filter_non_finite=True, window= ------- output_model : :class: `astropy.modeling.CompoundModel` The model resulting from the fit. - output_spectrum : :class:`specutils.Spectrum1D` + output_spectrum : :class:`specutils.Spectrum` The realization of the fitted model as a spectrum. """ @@ -113,9 +113,9 @@ def _fit_1D(initial_model, spectrum, run_fitter, filter_non_finite=True, window= output_values = initial_model(spectrum.spectral_axis) # Build return spectrum - output_spectrum = Spectrum1D(spectral_axis=spectrum.spectral_axis, - flux=output_values, - mask=spectrum.mask) + output_spectrum = Spectrum(spectral_axis=spectrum.spectral_axis, + flux=output_values, + mask=spectrum.mask) return output_model, output_spectrum @@ -130,7 +130,7 @@ def _fit_3D(initial_model, spectrum, window=None, n_cpu=None): ---------- initial_model : :class: `astropy.modeling.CompoundModel` Initial guess for the model to be fitted. - spectrum : :class:`specutils.Spectrum1D` + spectrum : :class:`specutils.Spectrum` The spectrum that stores the cube in its 'flux' attribute. window : `None` or :class:`specutils.spectra.SpectralRegion` See :func:`specutils.fitting.fitmodels.fit_lines`. @@ -145,7 +145,7 @@ def _fit_3D(initial_model, spectrum, window=None, n_cpu=None): output_model : :list: a list that stores 2D arrays. Each array contains one parameter from `astropy.modeling.CompoundModel` instances fitted to every spaxel in the input cube. - output_spectrum : :class:`specutils.Spectrum1D` + output_spectrum : :class:`specutils.Spectrum` The spectrum that stores the fitted model values in its 'flux' attribute. """ @@ -215,9 +215,9 @@ def collect_result(results): # Build output 3D spectrum funit = spectrum.flux.unit - output_spectrum = Spectrum1D(wcs=spectrum.wcs, - flux=output_flux_cube * funit, - mask=spectrum.mask) + output_spectrum = Spectrum(wcs=spectrum.wcs, + flux=output_flux_cube * funit, + mask=spectrum.mask) return fitted_models, output_spectrum @@ -251,7 +251,7 @@ def __call__(self): x = parameters[0] y = parameters[1] - # Calling the Spectrum1D constructor for every spaxel + # Calling the Spectrum constructor for every spaxel # turned out to be less expensive than expected. Experiments # show that the cost amounts to a couple percent additional # running time in comparison with a version that uses a 3D @@ -266,7 +266,7 @@ def __call__(self): # If no mask is provided: mask = np.zeros_like(flux.value).astype(bool) - sp = Spectrum1D(spectral_axis=self.wave, flux=flux, mask=mask) + sp = Spectrum(spectral_axis=self.wave, flux=flux, mask=mask) if sp.uncertainty and not np.all(sp.uncertainty.array == 0): weights = 'unc' @@ -366,7 +366,7 @@ def _generate_spaxel_list(spectrum): Parameters ---------- - spectrum : :class:`specutils.Spectrum1D` + spectrum : :class:`specutils.Spectrum` The spectrum that stores the cube in its ``'flux'`` attribute. Returns diff --git a/jdaviz/configs/default/plugins/model_fitting/model_fitting.py b/jdaviz/configs/default/plugins/model_fitting/model_fitting.py index f9346e5e2f..bf13586c72 100644 --- a/jdaviz/configs/default/plugins/model_fitting/model_fitting.py +++ b/jdaviz/configs/default/plugins/model_fitting/model_fitting.py @@ -3,7 +3,7 @@ from copy import deepcopy import astropy.units as u -from specutils import Spectrum1D +from specutils import Spectrum from specutils.utils import QuantityModel from traitlets import Bool, List, Unicode, observe @@ -195,6 +195,7 @@ def _param_units(self, param, model_type=None): y_params = ["amplitude", "amplitude_L", "intercept", "scale"] if param == "slope": + print(self._units) return str(u.Unit(self._units["y"]) / u.Unit(self._units["x"])) elif model_type == 'Polynomial1D': # param names are all named cN, where N is the order @@ -358,7 +359,7 @@ def _dataset_selected_changed(self, event=None): if selected_spec is None: return - # Replace NaNs from collapsed Spectrum1D in Cubeviz + # Replace NaNs from collapsed Spectrum in Cubeviz # (won't affect calculations because these locations are masked) selected_spec.flux[np.isnan(selected_spec.flux)] = 0.0 @@ -979,7 +980,7 @@ def _fit_model_to_cube(self, add_data): if "_orig_spec" in data.meta: spec = data.meta["_orig_spec"] else: - spec = data.get_object(cls=Spectrum1D, statistic=None) + spec = data.get_object(cls=Spectrum, statistic=None) sb_unit = self.app._get_display_unit('sb') if spec.flux.unit != sb_unit: @@ -1040,7 +1041,7 @@ def _fit_model_to_cube(self, add_data): temp_label = "{} ({}, {})".format(self.results_label, m["x"], m["y"]) self.app.fitted_models[temp_label] = m["model"] - output_cube = Spectrum1D(flux=fitted_spectrum.flux, wcs=fitted_spectrum.wcs) + output_cube = Spectrum(flux=fitted_spectrum.flux, wcs=fitted_spectrum.wcs) selected_spec = self.dataset.selected_obj if '_pixel_scale_factor' in selected_spec.meta: diff --git a/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py index 403167e533..22b8730daf 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py @@ -10,7 +10,7 @@ from astropy.wcs import WCS from glue.core.roi import XRangeROI from numpy.testing import assert_allclose, assert_array_equal -from specutils.spectra import Spectrum1D +from specutils.spectra import Spectrum from jdaviz.configs.default.plugins.model_fitting import fitting_backend as fb from jdaviz.configs.default.plugins.model_fitting import initializers @@ -58,7 +58,7 @@ def test_model_params(): def test_model_ids(cubeviz_helper, spectral_cube_wcs): - cubeviz_helper.load_data(Spectrum1D(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs), + cubeviz_helper.load_data(Spectrum(flux=np.ones((3, 4, 5)) * u.nJy, wcs=spectral_cube_wcs), data_label='test') plugin = cubeviz_helper.plugins["Model Fitting"]._obj plugin.dataset_selected = 'Spectrum (sum)' @@ -86,7 +86,7 @@ def test_parameter_retrieval(cubeviz_helper, spectral_cube_wcs): flux = np.ones((3, 4, 5)) flux[2, 2, :] = [1, 2, 3, 4, 5] - cubeviz_helper.load_data(Spectrum1D(flux=flux * flux_unit, wcs=spectral_cube_wcs), + cubeviz_helper.load_data(Spectrum(flux=flux * flux_unit, wcs=spectral_cube_wcs), data_label='test') plugin = cubeviz_helper.plugins["Model Fitting"] @@ -129,7 +129,7 @@ def test_fitting_backend(unc): uncertainties = StdDevUncertainty(np.zeros(y.shape)*u.Jy) elif unc is None: uncertainties = None - spectrum = Spectrum1D(flux=y*u.Jy, spectral_axis=x*u.um, uncertainty=uncertainties) + spectrum = Spectrum(flux=y*u.Jy, spectral_axis=x*u.um, uncertainty=uncertainties) g1f = models.Gaussian1D(0.7*u.Jy, 4.65*u.um, 0.3*u.um, name='g1') g2f = models.Gaussian1D(2.0*u.Jy, 5.55*u.um, 0.3*u.um, name='g2') @@ -166,7 +166,7 @@ def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path): IMAGE_SIZE_X = 15 IMAGE_SIZE_Y = 14 - # Flux cube oriented as in JWST data. To build a Spectrum1D + # Flux cube oriented as in JWST data. To build a Spectrum # instance with this, one need to transpose it so the spectral # axis direction corresponds to the last index. flux_cube = np.zeros((SPECTRUM_SIZE, IMAGE_SIZE_X, IMAGE_SIZE_Y)) @@ -181,7 +181,7 @@ def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path): for spx in spaxels: flux_cube[:, spx[0], spx[1]] = build_spectrum(sigma=SIGMA)[1] - # Transpose so it can be packed in a Spectrum1D instance. + # Transpose so it can be packed in a Spectrum instance. flux_cube = flux_cube.transpose(1, 2, 0) # (15, 14, 200) cube_wcs = WCS({ 'WCSAXES': 3, 'RADESYS': 'ICRS', 'EQUINOX': 2000.0, @@ -201,8 +201,8 @@ def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path): elif unc is None: uncertainties = None - spectrum = Spectrum1D(flux=flux_cube*u.Jy, wcs=cube_wcs, - uncertainty=uncertainties, mask=mask) + spectrum = Spectrum(flux=flux_cube*u.Jy, wcs=cube_wcs, + uncertainty=uncertainties, mask=mask) # Initial model for fit. g1f = models.Gaussian1D(0.7*u.Jy, 4.65*u.um, 0.3*u.um, name='g1') @@ -238,7 +238,7 @@ def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path): assert fitted_model[0].mean.unit == u.um # Check that spectrum result is formatted as expected. - assert isinstance(fitted_spectrum, Spectrum1D) + assert isinstance(fitted_spectrum, Spectrum) assert len(fitted_spectrum.shape) == 3 assert fitted_spectrum.shape == (IMAGE_SIZE_X, IMAGE_SIZE_Y, SPECTRUM_SIZE) diff --git a/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py b/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py index 3069429174..01e3cab289 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py @@ -15,7 +15,7 @@ import astropy.units as u from glue.core.roi import CircularROI -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from jdaviz.configs.default.plugins.model_fitting.initializers import MODELS @@ -168,9 +168,9 @@ def test_initialize_gaussian_with_cube(cubeviz_helper, spectrum1d_cube_larger): def test_fit_cube_no_wcs(cubeviz_helper): - # This is like when user does something to a cube outside of Jdaviz - # and then loads it back into a new instance of Cubeviz for further analysis. - sp = Spectrum1D(flux=np.ones((7, 8, 9)) * u.nJy) # nx, ny, nz + # This is like when user do something to a cube outside of Jdaviz + # and then load it back into a new instance of Cubeviz for further analysis. + sp = Spectrum(flux=np.ones((7, 8, 9)) * u.nJy, spectral_axis_index=2) # ny, nx, nz cubeviz_helper.load_data(sp, data_label="test_cube") mf = cubeviz_helper.plugins['Model Fitting'] mf.create_model_component('Linear1D') @@ -352,7 +352,7 @@ def test_subset_masks(cubeviz_helper, spectrum1d_cube_larger): # Get the data object again (ensures mask == None) data = cubeviz_helper.app.data_collection[-1].get_object() subset = cubeviz_helper.app.data_collection[-1].get_subset_object( - p.spectral_subset_selected, cls=Spectrum1D, statistic=None + p.spectral_subset_selected, cls=Spectrum, statistic=None ) masked_data = p._apply_subset_masks(data, p.spectral_subset) @@ -368,8 +368,8 @@ def test_invalid_subset(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label="right_spectrum") # 5000-7000 - sp2 = Spectrum1D(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, - flux=spectrum1d.flux * 1.25) + sp2 = Spectrum(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, + flux=spectrum1d.flux * 1.25) specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum @@ -404,7 +404,7 @@ def test_all_nan_uncert(specviz_helper): # being filtered in the fitter, as would normally happen with nans) uncertainty = StdDevUncertainty([np.nan, np.nan, np.nan, np.nan, np.nan, np.nan] * u.Jy) - spec = Spectrum1D(flux=[1, 2, 3, 4, 5, 6]*u.Jy, uncertainty=uncertainty) + spec = Spectrum(flux=[1, 2, 3, 4, 5, 6]*u.Jy, uncertainty=uncertainty) specviz_helper.load_data(spec) plugin = specviz_helper.plugins['Model Fitting'] @@ -431,7 +431,7 @@ def test_all_nan_uncert_subset(specviz_helper): # message uncertainty = StdDevUncertainty([1, 1, np.nan, np.nan, np.nan, np.nan] * u.Jy) - spec = Spectrum1D(flux=[2, 4, 3, 4, 5, 6]*u.Jy, uncertainty=uncertainty) + spec = Spectrum(flux=[2, 4, 3, 4, 5, 6]*u.Jy, uncertainty=uncertainty) specviz_helper.load_data(spec) plugin = specviz_helper.plugins['Model Fitting'] diff --git a/jdaviz/configs/imviz/helper.py b/jdaviz/configs/imviz/helper.py index dbd97f369c..dc868be035 100644 --- a/jdaviz/configs/imviz/helper.py +++ b/jdaviz/configs/imviz/helper.py @@ -335,7 +335,7 @@ def get_data(self, data_label=None, spatial_subset=None, cls=None): Provide a label to retrieve a specific data set from data_collection. spatial_subset : str, optional Spatial subset applied to data. - cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional + cls : `~specutils.Spectrum`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. Returns diff --git a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py index 557420b367..a75d42c6ed 100644 --- a/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py +++ b/jdaviz/configs/imviz/plugins/aper_phot_simple/aper_phot_simple.py @@ -491,9 +491,17 @@ def _calc_background_median(self, reg, data=None): data = self.dataset.selected_dc_item comp = data.get_component(data.main_components[0]) + if self.config == "cubeviz" and data.ndim > 2: + if "spectral_axis_index" in getattr(data, "meta", {}): + spectral_axis_index = data.meta["spectral_axis_index"] + else: + spectral_axis_index = 0 if self.config == "cubeviz" and data.ndim > 2: - comp_data = comp.data[:, :, self._cubeviz_slice_ind].T # nx, ny --> ny, nx + if spectral_axis_index == 0: + comp_data = comp.data[self._cubeviz_slice_ind, :, :] + else: + comp_data = comp.data[:, :, self._cubeviz_slice_ind].T # nx, ny --> ny, nx # Similar to coords_info logic. if '_orig_spec' in getattr(data, 'meta', {}): w = data.meta['_orig_spec'].wcs.celestial @@ -592,6 +600,12 @@ def calculate_photometry(self, dataset=None, aperture=None, background=None, # we can use the pre-cached value data = self.dataset.selected_dc_item + if self.config == "cubeviz" and data.ndim > 2: + if "spectral_axis_index" in getattr(data, "meta", {}): + spectral_axis_index = data.meta["spectral_axis_index"] + else: + spectral_axis_index = 0 + if aperture is not None: if aperture not in self.aperture.choices: raise ValueError(f"aperture must be one of {self.aperture.choices}") @@ -675,7 +689,10 @@ def calculate_photometry(self, dataset=None, aperture=None, background=None, raise ValueError('Missing or invalid background value') if self.config == "cubeviz" and data.ndim > 2: - comp_data = comp.data[:, :, self._cubeviz_slice_ind].T # nx, ny --> ny, nx + if spectral_axis_index == 0: + comp_data = comp.data[self._cubeviz_slice_ind, :, :] + else: + comp_data = comp.data[:, :, self._cubeviz_slice_ind].T # nx, ny --> ny, nx # Similar to coords_info logic. if '_orig_spec' in getattr(data, 'meta', {}): w = data.meta['_orig_spec'].wcs @@ -696,8 +713,11 @@ def calculate_photometry(self, dataset=None, aperture=None, background=None, ycenter = reg.center.y if data.coords is not None: if self.config == "cubeviz" and data.ndim > 2: - sky_center = w.pixel_to_world(self._cubeviz_slice_ind, - ycenter, xcenter)[1] + if spectral_axis_index == 0: + sky = w.pixel_to_world(xcenter, ycenter, self._cubeviz_slice_ind) + else: + sky = w.pixel_to_world(self._cubeviz_slice_ind, ycenter, xcenter) + sky_center = [coord for coord in sky if hasattr(coord, "icrs")][0] else: # "imviz" sky_center = w.pixel_to_world(xcenter, ycenter) else: diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index bec01c238c..af8ebc709c 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -63,6 +63,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._marks = {} self._dict = {} # dictionary representation of current mouseover info + self._spectral_axis_index = 2 # Needed for cube data self._x, self._y = None, None # latest known cursor positions self.image_unit = None @@ -252,7 +253,10 @@ def _image_shape_inds(self, image): # return the indices in image.shape for the x and y dimension, respectively if image.ndim == 3: # cubeviz case - return (0, 1) # (ix_shape, iy_shape) + if self._spectral_axis_index == 0: + return (2, 1) + else: + return (0, 1) # (ix_shape, iy_shape) elif image.ndim == 2: return (1, 0) # (ix_shape, iy_shape) else: # pragma: no cover @@ -261,7 +265,10 @@ def _image_shape_inds(self, image): def _get_cube_value(self, image, arr, x, y, viewer): if image.ndim == 3: # cubeviz case: - return arr[int(round(x)), int(round(y)), viewer.state.slices[-1]] + if self._spectral_axis_index == 0: + return arr[viewer.state.slices[-1], int(round(y)), int(round(x))] + else: + return arr[int(round(x)), int(round(y)), viewer.state.slices[-1]] elif image.ndim == 2: if isinstance(viewer, RampvizImageView): x, y = y, x @@ -359,10 +366,17 @@ def _image_viewer_update(self, viewer, x, y): else: data_wcs = None + if "spectral_axis_index" in getattr(coo_data, "meta", {}): + self._spectral_axis_index = coo_data.meta["spectral_axis_index"] + if data_wcs: try: if wcs_ndim == 3: - sky = data_wcs.pixel_to_world(viewer.slice, y, x)[1].icrs + if self._spectral_axis_index == 0: + sky = data_wcs.pixel_to_world(x, y, viewer.slice) + else: + sky = data_wcs.pixel_to_world(viewer.slice, y, x) + sky = [coord for coord in sky if hasattr(coord, "icrs")][0].icrs else: # wcs_ndim == 2 sky = data_wcs.pixel_to_world(x, y).icrs except Exception: diff --git a/jdaviz/configs/mosviz/helper.py b/jdaviz/configs/mosviz/helper.py index bd3cf23e1e..f0f192ad70 100644 --- a/jdaviz/configs/mosviz/helper.py +++ b/jdaviz/configs/mosviz/helper.py @@ -258,7 +258,7 @@ def _get_sp_attribute(table_data, row, attr, fill=None): if sp1_val is not None and sp1_val != sp2_val: # then there was a conflict - msg = f"Warning: value for {attr} in row {row} in disagreement between Spectrum1D and Spectrum2D" # noqa + msg = f"Warning: value for {attr} in row {row} in disagreement between Spectrum and Spectrum2D" # noqa msg = SnackbarMessage(msg, color='warning', sender=self) self.app.hub.broadcast(msg) @@ -283,12 +283,12 @@ def load_data(self, spectra_1d=None, spectra_2d=None, images=None, ---------- spectra_1d : list or str A list of spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. spectra_2d : list or str A list of spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. images : list of obj, str, or `None` @@ -460,12 +460,12 @@ def load_spectra(self, spectra_1d, spectra_2d): ---------- spectra_1d : list or str A list of spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. spectra_2d : list or str A list of spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. """ @@ -499,7 +499,7 @@ def load_1d_spectra(self, data_obj, data_labels=None, add_redshift_column=False) ---------- data_obj : list or str A list of spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. data_labels : str or list String representing the label for the data item loaded via @@ -527,7 +527,7 @@ def load_2d_spectra(self, data_obj, data_labels=None, add_redshift_column=False) ---------- data_obj : list or str A list of 2D spectra as translatable container objects (e.g. - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. data_labels : str or list String representing the label for the data item loaded via @@ -921,7 +921,7 @@ def get_spectrum_1d(self, row=None, apply_slider_redshift="Warn"): Returns ------- - `~specutils.Spectrum1D` + `~specutils.Spectrum` """ return self._get_spectrum('1D Spectra', row, apply_slider_redshift) @@ -941,7 +941,7 @@ def get_spectrum_2d(self, row=None, apply_slider_redshift="Warn"): Returns ------- - `~specutils.Spectrum1D` + `~specutils.Spectrum` """ return self._get_spectrum('2D Spectra', row, apply_slider_redshift) @@ -956,7 +956,7 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None): Provide a label to retrieve a specific data set from data_collection. spectral_subset : str, optional Spectral subset applied to data. - cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional + cls : `~specutils.Spectrum`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. Returns diff --git a/jdaviz/configs/mosviz/plugins/parsers.py b/jdaviz/configs/mosviz/plugins/parsers.py index 31579799ac..196e0b4682 100644 --- a/jdaviz/configs/mosviz/plugins/parsers.py +++ b/jdaviz/configs/mosviz/plugins/parsers.py @@ -10,7 +10,7 @@ from astropy.wcs import WCS from glue.core.data import Data from glue.core.link_helpers import LinkSameWithUnits -from specutils import Spectrum1D, SpectrumList, SpectrumCollection +from specutils import Spectrum, SpectrumList, SpectrumCollection from specutils.io.default_loaders.jwst_reader import identify_jwst_s2d_multi_fits from jdaviz.configs.imviz.plugins.parsers import get_image_data_iterator @@ -226,10 +226,10 @@ def mos_spec1d_parser(app, data_obj, data_labels=None, if not isinstance(data_obj, (list, tuple, SpectrumCollection)): data_obj = [data_obj] - # If the file has multiple objects in it, the Spectrum1D read machinery + # If the file has multiple objects in it, the Spectrum read machinery # will fail to find a reader for it, and we fall back on SpectrumList try: - data_obj = [Spectrum1D.read(x) if _check_is_file(x) else x for x in data_obj] + data_obj = [Spectrum.read(x) if _check_is_file(x) else x for x in data_obj] except IORegistryError: if len(data_obj) == 1: if _check_is_file(data_obj[0]): @@ -330,7 +330,7 @@ def _parse_as_spectrum1d(hdulist, ext, transpose): else: kw = {'wcs': wcs} - return Spectrum1D(flux=data * data_unit, meta=metadata, **kw) + return Spectrum(flux=data * data_unit, meta=metadata, **kw) # Coerce into list-like object if (not isinstance(data_obj, (list, tuple, SpectrumCollection)) or @@ -356,7 +356,7 @@ def _parse_as_spectrum1d(hdulist, ext, transpose): with app.data_collection.delay_link_manager_update(): for index, data in enumerate(data_obj): - # If we got a filepath, first try and parse using the Spectrum1D and + # If we got a filepath, first try and parse using the Spectrum and # SpectrumList parsers, and then fall back to parsing it as a generic # FITS file. @@ -369,7 +369,7 @@ def _parse_as_spectrum1d(hdulist, ext, transpose): with fits.open(data) as hdulist: data = _parse_as_spectrum1d(hdulist, ext, transpose) else: - data = Spectrum1D.read(data) + data = Spectrum.read(data) except IORegistryError: with fits.open(data) as hdulist: data = _parse_as_spectrum1d(hdulist, ext, transpose) @@ -903,7 +903,7 @@ def mos_niriss_parser(app, data_dir, instrument=None, meta[PRIHDR_KEY] = standardize_metadata(temp[0].header) # The wavelength is stored in a WAVELENGTH HDU. This is - # a 2D array, but in order to be able to use Spectrum1D + # a 2D array, but in order to be able to use Spectrum # we use the average wavelength for all image rows if data.shape[0] > data.shape[1]: @@ -914,7 +914,7 @@ def mos_niriss_parser(app, data_dir, instrument=None, else: wav = temp[wav_hdus[sci]].data.mean(axis=0) * u.micron - spec2d = Spectrum1D(data * u.one, spectral_axis=wav, meta=meta) + spec2d = Spectrum(data * u.one, spectral_axis=wav, meta=meta) spec2d.meta['INSTRUME'] = instrument.upper() spec2d.meta['mosviz_row'] = len(spec_labels_2d) diff --git a/jdaviz/configs/mosviz/plugins/viewers.py b/jdaviz/configs/mosviz/plugins/viewers.py index a84d772998..4f5e8e670f 100644 --- a/jdaviz/configs/mosviz/plugins/viewers.py +++ b/jdaviz/configs/mosviz/plugins/viewers.py @@ -7,7 +7,7 @@ from glue_jupyter.bqplot.image import BqplotImageView from glue_jupyter.table import TableViewer from scipy.interpolate import interp1d -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.events import (AddDataToViewerMessage, RemoveDataFromViewerMessage, @@ -71,7 +71,7 @@ def _mark_targets(self): class MosvizProfile2DView(JdavizViewerMixin, BqplotImageView): # Due to limitations in CCDData and 2D data that has spectral and spatial # axes, the default conversion class must handle cubes - default_class = Spectrum1D + default_class = Spectrum # categories: zoom resets, zoom, pan, subset, select tools, shortcuts tools_nested = [ @@ -221,7 +221,7 @@ def set_plot_axes(self): self.figure.axes[1].tick_format = None self.figure.axes[1].label_location = "middle" - # Sync with Spectrum1D viewer (that is also used by other viz). + # Sync with Spectrum viewer (that is also used by other viz). # Make it so y axis label is not covering tick numbers. self.figure.fig_margin["left"] = 95 self.figure.fig_margin["bottom"] = 60 diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index 21b0865bc9..cc4fce29c6 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -5,7 +5,7 @@ import numpy as np import pytest from astropy.nddata import CCDData -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.utils import PRIHDR_KEY @@ -27,7 +27,7 @@ def test_load_spectrum1d(mosviz_helper, spectrum1d): ).data() assert len(data) == 1 - assert isinstance(data[0], Spectrum1D) + assert isinstance(data[0], Spectrum) with pytest.raises(AttributeError): mosviz_helper.load_1d_spectra([1, 2, 3]) @@ -74,7 +74,7 @@ def test_load_spectrum_collection(mosviz_helper, spectrum_collection): ).data() assert len(data) == 1 - assert isinstance(data[0], Spectrum1D) + assert isinstance(data[0], Spectrum) def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): @@ -96,7 +96,7 @@ def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): ).data() assert len(data) == 1 - assert isinstance(data[0], Spectrum1D) + assert isinstance(data[0], Spectrum) def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index 066456198b..655f8c6245 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -4,7 +4,7 @@ from astropy.utils.decorators import deprecated from regions.core.core import Region from glue.core.subset_group import GroupedSubset -from specutils import SpectralRegion, Spectrum1D +from specutils import SpectralRegion, Spectrum from jdaviz.core.helpers import ConfigHelper from jdaviz.core.events import RedshiftMessage @@ -17,13 +17,13 @@ def _apply_redshift_to_spectra(spectra, redshift): flux = spectra.flux # This is a hack around inability to input separate redshift with - # a SpectralAxis instance in Spectrum1D + # a SpectralAxis instance in Spectrum spaxis = spectra.spectral_axis.value * spectra.spectral_axis.unit mask = spectra.mask uncertainty = spectra.uncertainty - output_spectra = Spectrum1D(flux, spectral_axis=spaxis, - redshift=redshift, mask=mask, - uncertainty=uncertainty) + output_spectra = Spectrum(flux, spectral_axis=spaxis, + redshift=redshift, mask=mask, + uncertainty=uncertainty) return output_spectra @@ -49,13 +49,13 @@ def load_data(self, data, data_label=None, format=None, show_in_viewer=True, Parameters ---------- - data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` - Spectrum1D, SpectrumList, or path to compatible data file. + data : str, `~specutils.Spectrum`, or `~specutils.SpectrumList` + Spectrum, SpectrumList, or path to compatible data file. data_label : str The Glue data label found in the ``DataCollection``. format : str Loader format specification used to indicate data format in - `~specutils.Spectrum1D.read` io method. + `~specutils.Spectrum.read` io method. show_in_viewer : bool Show data in viewer(s). concat_by_file : bool @@ -98,7 +98,7 @@ def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshi if data_label is not None: spectrum = get_data_method(data_label=data_label, spectral_subset=spectral_subset, - cls=Spectrum1D) + cls=Spectrum) spectra[data_label] = spectrum else: for layer_state in viewer.state.layers: @@ -107,7 +107,7 @@ def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshi if lyr.label == spectral_subset: spectrum = get_data_method(data_label=lyr.data.label, spectral_subset=spectral_subset, - cls=Spectrum1D) + cls=Spectrum) spectra[lyr.data.label] = spectrum else: continue @@ -120,11 +120,11 @@ def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshi isinstance(all_subsets[lyr.label], SpectralRegion)): spectrum = get_data_method(data_label=lyr.data.label, spectral_subset=lyr.label, - cls=Spectrum1D) + cls=Spectrum) spectra[f'{lyr.data.label} ({lyr.label})'] = spectrum else: spectrum = get_data_method(data_label=lyr.label, - cls=Spectrum1D) + cls=Spectrum) spectra[lyr.label] = spectrum if not apply_slider_redshift: @@ -133,7 +133,7 @@ def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshi return spectra else: output_spectra = {} - # We need to create new Spectrum1D outputs with the redshifts set + # We need to create new Spectrum outputs with the redshifts set for key in spectra.keys(): output_spectra[key] = _apply_redshift_to_spectra(spectra[key], self._redshift) @@ -310,7 +310,7 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None, Provide a label to retrieve a specific data set from data_collection. spectral_subset : str, optional Spectral subset applied to data. - cls : `~specutils.Spectrum1D`, optional + cls : `~specutils.Spectrum`, optional The type that data will be returned as. use_display_units: bool, optional Whether to convert to the display units defined in the plugin. diff --git a/jdaviz/configs/specviz/plugins/line_analysis/line_analysis.py b/jdaviz/configs/specviz/plugins/line_analysis/line_analysis.py index b95dbc332f..f364c3e51d 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/line_analysis.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/line_analysis.py @@ -6,7 +6,7 @@ from glue_jupyter.common.toolbar_vuetify import read_icon from traitlets import Bool, List, Float, Unicode, observe from astropy import units as u -from specutils import analysis, Spectrum1D +from specutils import analysis, Spectrum from jdaviz.core.events import (AddDataMessage, RemoveDataMessage, @@ -313,7 +313,7 @@ def _uncertainty(result): if (flux_unit.is_equivalent(u.Jy) or flux_unit.is_equivalent(u.Jy / solid_angle_in_flux_unit)): # Perform integration in frequency space - freq_spec = Spectrum1D( + freq_spec = Spectrum( spectral_axis=spec_subtracted.spectral_axis.to(u.Hz, equivalencies=u.spectral()), flux=spec_subtracted.flux, @@ -349,7 +349,7 @@ def _uncertainty(result): elif (flux_unit.is_equivalent(u.Unit('W/(m2 m)')) or flux_unit.is_equivalent(u.Unit(f'W/(m2 m {solid_angle_string})'))): # Perform integration in wavelength space using MKS unit (meters) - wave_spec = Spectrum1D( + wave_spec = Spectrum( spectral_axis=spec_subtracted.spectral_axis.to(u.m, equivalencies=u.spectral()), flux=spec_subtracted.flux, diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py index 6b077a14ca..24a0ab7ea3 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py @@ -5,7 +5,7 @@ from astropy.tests.helper import assert_quantity_allclose from numpy.testing import assert_allclose from regions import RectanglePixelRegion, PixCoord -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from jdaviz.configs.specviz.plugins.line_analysis.line_analysis import _coerce_unit from jdaviz.core.custom_units_and_equivs import PIX2 @@ -480,8 +480,8 @@ def test_invalid_subset(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label="right_spectrum") # 5000-7000 - sp2 = Spectrum1D(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, - flux=spectrum1d.flux * 1.25) + sp2 = Spectrum(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, + flux=spectrum1d.flux * 1.25) specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py index 44fe58070a..0326219a9a 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py @@ -3,7 +3,7 @@ from glue.viewers.profile.state import FUNCTIONS as COLLAPSE_FUNCTIONS import numpy as np import pytest -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz import Cubeviz @@ -40,17 +40,17 @@ def _gauss_with_unity_area(x, mean, sigma): # unit-flux gaussian in frequency space freq = np.arange(1, 2, 0.001)*u.Hz flux_freq = _gauss_with_unity_area(freq.value, mn, sig)*1.0E26*u.Jy -fnu_freq = Spectrum1D(spectral_axis=freq, flux=flux_freq) +fnu_freq = Spectrum(spectral_axis=freq, flux=flux_freq) unit_flux_gaussian_test_cases.append(fnu_freq) -fnu_wave = Spectrum1D(spectral_axis=fnu_freq.wavelength, flux=flux_freq) +fnu_wave = Spectrum(spectral_axis=fnu_freq.wavelength, flux=flux_freq) unit_flux_gaussian_test_cases.append(fnu_wave) # unit-flux gaussian in wavelength space lam = np.arange(1, 2, 0.001)*u.m flux_wave = _gauss_with_unity_area(lam.value, mn, sig)*1.0*u.W/u.m**2/u.m -flam_wave = Spectrum1D(spectral_axis=lam, flux=flux_wave) +flam_wave = Spectrum(spectral_axis=lam, flux=flux_wave) unit_flux_gaussian_test_cases.append(flam_wave) -flam_freq = Spectrum1D(spectral_axis=flam_wave.frequency, flux=flux_wave) +flam_freq = Spectrum(spectral_axis=flam_wave.frequency, flux=flux_wave) unit_flux_gaussian_test_cases.append(flam_freq) @@ -118,7 +118,7 @@ def test_unit_gaussian_mixed_units_per_steradian(specviz_helper): # test changed from Surface Brightness to Flux, # u.erg/u.s/u.cm**2/u.Angstrom/u.sr in untranslatable units (check unit_conversion.py) flx_wave = _gauss_with_unity_area(lam_a.value, mn, sig)*1E3*u.erg/u.s/u.cm**2/u.Angstrom - fl_wave = Spectrum1D(spectral_axis=lam_a, flux=flx_wave) + fl_wave = Spectrum(spectral_axis=lam_a, flux=flx_wave) specviz_helper.load_data(fl_wave) lineflux_result = _calculate_line_flux(specviz_helper) diff --git a/jdaviz/configs/specviz/plugins/parsers.py b/jdaviz/configs/specviz/plugins/parsers.py index 8af49c2bc3..195bd3005f 100644 --- a/jdaviz/configs/specviz/plugins/parsers.py +++ b/jdaviz/configs/specviz/plugins/parsers.py @@ -6,7 +6,7 @@ from astropy.io.registry import IORegistryError from astropy.nddata import StdDevUncertainty from astropy.io import fits -from specutils import Spectrum1D, SpectrumList, SpectrumCollection +from specutils import Spectrum, SpectrumList, SpectrumCollection from jdaviz.core.events import SnackbarMessage from jdaviz.core.registries import data_parser_registry @@ -21,17 +21,17 @@ def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_v concat_by_file=False, cache=None, local_path=os.curdir, timeout=None, load_as_list=False): """ - Loads a data file or `~specutils.Spectrum1D` object into Specviz. + Loads a data file or `~specutils.Spectrum` object into Specviz. Parameters ---------- - data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` - Spectrum1D, SpectrumList, or path to compatible data file. + data : str, `~specutils.Spectrum`, or `~specutils.SpectrumList` + Spectrum, SpectrumList, or path to compatible data file. data_label : str The Glue data label found in the ``DataCollection``. format : str Loader format specification used to indicate data format in - `~specutils.Spectrum1D.read` io method. + `~specutils.Spectrum.read` io method. concat_by_file : bool If True and there is more than one available extension, concatenate the extensions within each spectrum file passed to the parser and @@ -65,7 +65,7 @@ def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_v if isinstance(data, SpectrumCollection): raise TypeError("SpectrumCollection detected." " Please provide a Spectrum1D or SpectrumList") - elif isinstance(data, Spectrum1D): + elif isinstance(data, Spectrum): # Handle the possibility of 2D spectra by splitting into separate spectra if data.flux.ndim == 1: data_label = [data_label] @@ -79,7 +79,7 @@ def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_v elif isinstance(data, list): # special processing for HDUList if isinstance(data, fits.HDUList): - data = [Spectrum1D.read(data)] + data = [Spectrum.read(data)] data_label = [data_label] else: # list treated as SpectrumList if not an HDUList @@ -235,7 +235,7 @@ def group_spectra_by_filename(datasets): def combine_lists_to_1d_spectrum(wl, fnu, dfnu, wave_units, flux_units): """ - Combine lists of 1D spectra into a composite `~specutils.Spectrum1D` object. + Combine lists of 1D spectra into a composite `~specutils.Spectrum` object. Parameters ---------- @@ -248,7 +248,7 @@ def combine_lists_to_1d_spectrum(wl, fnu, dfnu, wave_units, flux_units): Returns ------- - spec : `~specutils.Spectrum1D` + spec : `~specutils.Spectrum` Composite 1D spectrum. """ wlallarr = np.array(wl) @@ -266,8 +266,8 @@ def combine_lists_to_1d_spectrum(wl, fnu, dfnu, wave_units, flux_units): else: unc = None - spec = Spectrum1D(flux=fnuall * flux_units, spectral_axis=wlall * wave_units, - uncertainty=unc) + spec = Spectrum(flux=fnuall * flux_units, spectral_axis=wlall * wave_units, + uncertainty=unc) return spec diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py index 9dbc4dd800..a129b534b2 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py @@ -2,7 +2,7 @@ import pytest from astropy import units as u from astropy.nddata import InverseVariance -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.custom_units_and_equivs import SPEC_PHOTON_FLUX_DENSITY_UNITS @@ -118,7 +118,7 @@ def test_non_stddev_uncertainty(specviz_helper): var = stddev ** 2 inv_var = np.ones(len(flux)) / var wavelength = np.linspace(1, 5, len(flux)) * u.um - spec = Spectrum1D( + spec = Spectrum( flux, uncertainty=InverseVariance(inv_var), spectral_axis=wavelength @@ -147,7 +147,7 @@ def test_flux_unit_choices(specviz_helper, flux_unit, expected_choices): convertable flux units in the dropdown is correct. """ - spec = Spectrum1D([1, 2, 3] * flux_unit, [4, 5, 6] * u.um) + spec = Spectrum([1, 2, 3] * flux_unit, [4, 5, 6] * u.um) specviz_helper.load_data(spec) uc_plg = specviz_helper.plugins['Unit Conversion'] diff --git a/jdaviz/configs/specviz/plugins/viewers.py b/jdaviz/configs/specviz/plugins/viewers.py index baf6372f06..6dd221cc29 100644 --- a/jdaviz/configs/specviz/plugins/viewers.py +++ b/jdaviz/configs/specviz/plugins/viewers.py @@ -3,7 +3,7 @@ import numpy as np from astropy import table from matplotlib.colors import cnames -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.events import SpectralMarksChangedMessage, LineIdentifyMessage from jdaviz.core.registries import viewer_registry @@ -27,7 +27,7 @@ class SpecvizProfileView(JdavizProfileView): ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] ] - default_class = Spectrum1D + default_class = Spectrum spectral_lines = None _state_cls = FreezableProfileViewerState _default_profile_subset_type = 'spectral' diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index 927046d8a5..a467dd8dab 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -5,7 +5,7 @@ from astropy import units as u from astropy.io import fits from astropy.tests.helper import assert_quantity_allclose -from specutils import Spectrum1D, SpectrumList, SpectrumCollection, SpectralRegion +from specutils import Spectrum, SpectrumList, SpectrumCollection, SpectralRegion from astropy.utils.data import download_file from jdaviz.app import Application @@ -33,7 +33,7 @@ def test_load_spectrum1d(self): data = self.spec_app.get_data() - assert isinstance(data, Spectrum1D) + assert isinstance(data, Spectrum) def test_load_hdulist(self): # Create a fake fits file with a 1D spectrum for testing. @@ -52,8 +52,8 @@ def test_load_hdulist(self): self.label = "Test 1D Spectrum" self.spec_app.load_data(fake_hdulist) data = self.spec_app.get_data(data_label=self.label) - # HDUList should load as Spectrum1D - assert isinstance(data, Spectrum1D) + # HDUList should load as Spectrum + assert isinstance(data, Spectrum) def test_load_spectrum_list_no_labels(self): # now load three more spectra from a SpectrumList, without labels @@ -472,11 +472,11 @@ def test_spectra_partial_overlap(specviz_helper): wave_1 = np.linspace(6000, 7000, 10) * u.AA flux_1 = ([1200] * wave_1.size) * u.nJy - sp_1 = Spectrum1D(flux=flux_1, spectral_axis=wave_1) + sp_1 = Spectrum(flux=flux_1, spectral_axis=wave_1) wave_2 = wave_1 + (800 * u.AA) flux_2 = ([60] * wave_2.size) * u.nJy - sp_2 = Spectrum1D(flux=flux_2, spectral_axis=wave_2) + sp_2 = Spectrum(flux=flux_2, spectral_axis=wave_2) specviz_helper.load_data(sp_1, data_label='left') specviz_helper.load_data(sp_2, data_label='right') @@ -495,10 +495,10 @@ def test_spectra_partial_overlap(specviz_helper): def test_spectra_incompatible_flux(specviz_helper): """https://github.com/spacetelescope/jdaviz/issues/2459""" wav = [1.1, 1.2, 1.3] * u.um - sp1 = Spectrum1D(flux=[1, 1.1, 1] * (u.MJy / u.sr), spectral_axis=wav) - sp2 = Spectrum1D(flux=[1, 1, 1.1] * (u.MJy), spectral_axis=wav) + sp1 = Spectrum(flux=[1, 1.1, 1] * (u.MJy / u.sr), spectral_axis=wav) + sp2 = Spectrum(flux=[1, 1, 1.1] * (u.MJy), spectral_axis=wav) flux3 = ([1, 1.1, 1] * u.MJy).to(u.erg / u.s / u.cm / u.cm / u.AA, u.spectral_density(wav)) - sp3 = Spectrum1D(flux=flux3, spectral_axis=wav) + sp3 = Spectrum(flux=flux3, spectral_axis=wav) specviz_helper.load_data(sp2, data_label="2") # OK specviz_helper.load_data(sp1, data_label="1") # Not OK diff --git a/jdaviz/configs/specviz/tests/test_viewers.py b/jdaviz/configs/specviz/tests/test_viewers.py index 28981fbaea..2a4228298a 100644 --- a/jdaviz/configs/specviz/tests/test_viewers.py +++ b/jdaviz/configs/specviz/tests/test_viewers.py @@ -2,7 +2,7 @@ import numpy as np import pytest import warnings -from specutils import Spectrum1D +from specutils import Spectrum @pytest.mark.parametrize( @@ -18,7 +18,7 @@ def test_spectrum_viewer_axis_labels(specviz_helper, input_unit, y_axis_label): flux = np.arange(1, 10) * input_unit spectral_axis = np.arange(1, 10) * u.um - spec = Spectrum1D(flux, spectral_axis) + spec = Spectrum(flux, spectral_axis) with warnings.catch_warnings(): warnings.filterwarnings("ignore", message=".*contains multiple slashes, which is discouraged by the FITS standard.*") # noqa diff --git a/jdaviz/configs/specviz2d/helper.py b/jdaviz/configs/specviz2d/helper.py index 87522333cb..8959acd700 100644 --- a/jdaviz/configs/specviz2d/helper.py +++ b/jdaviz/configs/specviz2d/helper.py @@ -38,12 +38,12 @@ def load_data(self, spectrum_2d=None, spectrum_1d=None, spectrum_1d_label=None, ---------- spectrum_2d: str A spectrum as translatable container objects (e.g., - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. - spectrum_1d: str or Spectrum1D + spectrum_1d: str or Spectrum A spectrum as translatable container objects (e.g., - ``Spectrum1D``) that can be read by glue-jupyter. Alternatively, + ``Spectrum``) that can be read by glue-jupyter. Alternatively, can be a string file path. spectrum_1d_label : str @@ -182,7 +182,7 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None): Provide a label to retrieve a specific data set from data_collection. spectral_subset : str, optional Spectral subset applied to data. - cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional + cls : `~specutils.Spectrum`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. Returns diff --git a/jdaviz/configs/specviz2d/plugins/parsers.py b/jdaviz/configs/specviz2d/plugins/parsers.py index 199e0fd622..662671d4eb 100644 --- a/jdaviz/configs/specviz2d/plugins/parsers.py +++ b/jdaviz/configs/specviz2d/plugins/parsers.py @@ -1,6 +1,6 @@ from pathlib import Path -from specutils import Spectrum1D +from specutils import Spectrum from astropy.io import fits import astropy.units as u import numpy as np @@ -62,7 +62,7 @@ def spec2d_1d_parser(app, data_obj, data_label=None, show_in_viewer=True): metadata = standardize_metadata(header) metadata[PRIHDR_KEY] = standardize_metadata(prihdr) - data_obj = Spectrum1D(flux, spectral_axis=spectral_axis, meta=metadata) + data_obj = Spectrum(flux, spectral_axis=spectral_axis, meta=metadata) data_label = app.return_data_label(data_label, alt_name="specviz2d_data") app.data_collection[data_label] = data_obj diff --git a/jdaviz/configs/specviz2d/plugins/spectral_extraction/tests/test_spectral_extraction.py b/jdaviz/configs/specviz2d/plugins/spectral_extraction/tests/test_spectral_extraction.py index 5f4cecd774..d062221cf3 100644 --- a/jdaviz/configs/specviz2d/plugins/spectral_extraction/tests/test_spectral_extraction.py +++ b/jdaviz/configs/specviz2d/plugins/spectral_extraction/tests/test_spectral_extraction.py @@ -8,7 +8,7 @@ import numpy as np from packaging.version import Version from specreduce import tracing, background, extract -from specutils import Spectrum1D +from specutils import Spectrum GWCS_LT_0_18_1 = Version(gwcs.__version__) < Version('0.18.1') @@ -109,11 +109,11 @@ def test_plugin(specviz2d_helper): pext.import_bg(bg) assert pext.bg_width == 4 bg_img = pext.export_bg_img() - assert isinstance(bg_img, Spectrum1D) + assert isinstance(bg_img, Spectrum) bg_spec = pext.export_bg_spectrum() - assert isinstance(bg_spec, Spectrum1D) + assert isinstance(bg_spec, Spectrum) bg_sub = pext.export_bg_sub() - assert isinstance(bg_sub, Spectrum1D) + assert isinstance(bg_sub, Spectrum) # interact with extraction section, check marks pext.ext_width = 1 @@ -131,11 +131,11 @@ def test_plugin(specviz2d_helper): pext.import_extract(ext) assert pext.ext_width == 2 sp_ext = pext.export_extract_spectrum() - assert isinstance(sp_ext, Spectrum1D) + assert isinstance(sp_ext, Spectrum) pext.ext_type_selected = 'Horne' sp_ext = pext.export_extract_spectrum() - assert isinstance(sp_ext, Spectrum1D) + assert isinstance(sp_ext, Spectrum) # test API calls for step in ['trace', 'bg', 'ext']: diff --git a/jdaviz/configs/specviz2d/tests/test_helper.py b/jdaviz/configs/specviz2d/tests/test_helper.py index a6eb3a203b..d03ff48cf3 100644 --- a/jdaviz/configs/specviz2d/tests/test_helper.py +++ b/jdaviz/configs/specviz2d/tests/test_helper.py @@ -1,4 +1,4 @@ -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz import Specviz @@ -10,7 +10,7 @@ def test_helper(specviz2d_helper, mos_spectrum2d): returned_data = specviz2d_helper.get_data("Spectrum 2D") assert len(returned_data.shape) == 1 - assert isinstance(returned_data, Spectrum1D) + assert isinstance(returned_data, Spectrum) def test_plugin_user_apis(specviz2d_helper): diff --git a/jdaviz/conftest.py b/jdaviz/conftest.py index 748a519905..aefd05b706 100644 --- a/jdaviz/conftest.py +++ b/jdaviz/conftest.py @@ -12,7 +12,7 @@ from astropy.io import fits from astropy.nddata import CCDData, StdDevUncertainty from astropy.wcs import WCS -from specutils import Spectrum1D, SpectrumCollection, SpectrumList +from specutils import Spectrum, SpectrumCollection, SpectrumList from jdaviz import __version__, Cubeviz, Imviz, Mosviz, Specviz, Specviz2d, Rampviz from jdaviz.configs.imviz.tests.utils import create_wfi_image_model @@ -208,7 +208,7 @@ def _create_spectrum1d_with_spectral_unit(spectralunit=u.AA): meta = dict(header=dict(FILENAME="jdaviz-test-file.fits")) - return Spectrum1D(spectral_axis=spec_axis, flux=flux, uncertainty=uncertainty, meta=meta) + return Spectrum(spectral_axis=spec_axis, flux=flux, uncertainty=uncertainty, meta=meta) @pytest.fixture @@ -244,8 +244,8 @@ def multi_order_spectrum_list(spectrum1d, spectral_orders=10): spec_axis.value / 500) * u.Jy uncertainty = StdDevUncertainty(np.abs(np.random.randn(len(spec_axis.value))) * u.Jy) meta = dict(header=dict(FILENAME="jdaviz-test-multi-order-file.fits")) - spectrum1d = Spectrum1D(spectral_axis=spec_axis, flux=flux, - uncertainty=uncertainty, meta=meta) + spectrum1d = Spectrum(spectral_axis=spec_axis, flux=flux, + uncertainty=uncertainty, meta=meta) sc.append(spectrum1d) @@ -265,12 +265,12 @@ def _create_spectrum1d_cube_with_fluxunit(fluxunit=u.Jy, shape=(2, 2, 4), with_u if with_uncerts: uncert = StdDevUncertainty(np.abs(np.random.normal(flux) * fluxunit)) - return Spectrum1D(flux=flux, - uncertainty=uncert, - wcs=w, - meta=wcs_dict) + return Spectrum(flux=flux, + uncertainty=uncert, + wcs=w, + meta=wcs_dict) else: - return Spectrum1D(flux=flux, wcs=w, meta=wcs_dict) + return Spectrum(flux=flux, wcs=w, meta=wcs_dict) @pytest.fixture @@ -297,7 +297,7 @@ def spectrum1d_cube_largest(): w = WCS(wcs_dict) flux = np.zeros((20, 30, 3001), dtype=np.float32) # nx=20 ny=30 nz=3001 flux[1:11, 5:15, :] = 1 # Bright corner - return Spectrum1D(flux=flux * u.Jy, wcs=w, meta=wcs_dict) + return Spectrum(flux=flux * u.Jy, wcs=w, meta=wcs_dict) @pytest.fixture @@ -335,19 +335,19 @@ def mos_spectrum1d(mos_spectrum2d): 10*np.exp(-0.001*(spec_axis.value-6563)**2) + spec_axis.value/500) * u.Jy - return Spectrum1D(spectral_axis=spec_axis, flux=flux) + return Spectrum(spectral_axis=spec_axis, flux=flux) @pytest.fixture def spectrum2d(): ''' - A simple 2D Spectrum1D with a center "trace" array rising from 0 to 10 + A simple 2D Spectrum with a center "trace" array rising from 0 to 10 with two "zero array" buffers above and below ''' data = np.zeros((5, 10)) data[3] = np.arange(10) - return Spectrum1D(flux=data*u.MJy, spectral_axis=data[3]*u.um) + return Spectrum(flux=data*u.MJy, spectral_axis=data[3]*u.um) def _generate_mos_spectrum2d(): @@ -367,15 +367,15 @@ def _generate_mos_spectrum2d(): @pytest.fixture def mos_spectrum2d(): ''' - A specially defined 2D (spatial) Spectrum1D whose wavelength range matches the + A specially defined 2D (spatial) Spectrum whose wavelength range matches the mos-specific 1D spectrum. - TODO: This should be reformed to match the global Spectrum1D defined above so that we may + TODO: This should be reformed to match the global Spectrum defined above so that we may deprecate the mos-specific spectrum1d. ''' data, header = _generate_mos_spectrum2d() wcs = WCS(header) - return Spectrum1D(data, wcs=wcs, meta=header) + return Spectrum(data, wcs=wcs, meta=header) @pytest.fixture diff --git a/jdaviz/core/data_formats.py b/jdaviz/core/data_formats.py index 32f464f704..7da07906a6 100644 --- a/jdaviz/core/data_formats.py +++ b/jdaviz/core/data_formats.py @@ -7,7 +7,7 @@ from astropy.wcs import WCS from specutils.io.registers import identify_spectrum_format -from specutils import Spectrum1D, SpectrumList, SpectrumCollection +from specutils import Spectrum, SpectrumList, SpectrumCollection from stdatamodels import asdf_in_fits from jdaviz.core.config import list_configurations @@ -27,7 +27,7 @@ # get formats table for specutils objects formats_table = astropy.io.registry.get_formats(readwrite='Read') formats_table.add_index('Data class') -formats_table = formats_table.loc[['Spectrum1D', 'SpectrumList']] +formats_table = formats_table.loc[['Spectrum', 'SpectrumList']] formats_table.sort(['Data class', 'Format']) file_to_config_mapping = {i: default_mapping.get( @@ -161,7 +161,7 @@ def identify_helper(filename, ext=1): The HDUList of the file opened to identify the helper """ supported_dtypes = [ - Spectrum1D, + Spectrum, SpectrumList, SpectrumCollection, CCDData @@ -238,7 +238,7 @@ def identify_helper(filename, ext=1): return (['specviz'], hdul) # If the data could be spectral: - for cls in [Spectrum1D, SpectrumList]: + for cls in [Spectrum, SpectrumList]: if cls in possible_formats.keys(): recognized_spectrum_format = possible_formats[cls][0].lower() diff --git a/jdaviz/core/helpers.py b/jdaviz/core/helpers.py index 44242d1a01..4a0839a9d2 100644 --- a/jdaviz/core/helpers.py +++ b/jdaviz/core/helpers.py @@ -23,7 +23,7 @@ import astropy.units as u from astropy.utils.decorators import deprecated from regions.core.core import Region -from specutils import Spectrum1D, SpectralRegion +from specutils import Spectrum, SpectralRegion from jdaviz.app import Application from jdaviz.core.events import SnackbarMessage, ExitBatchLoadMessage, SliceSelectSliceMessage @@ -470,7 +470,7 @@ def toggle_api_hints(self, enabled=None): def _handle_display_units(self, data, use_display_units=True): if use_display_units: - if isinstance(data, Spectrum1D): + if isinstance(data, Spectrum): spectral_unit = self.app._get_display_unit('spectral') if not spectral_unit: return data @@ -510,10 +510,11 @@ def _handle_display_units(self, data, use_display_units=True): spectral_unit) * u.Unit(spectral_unit)) - data = Spectrum1D(spectral_axis=new_spec, + data = Spectrum(spectral_axis=new_spec, flux=new_y, uncertainty=new_uncert, - mask=data.mask) + mask=data.mask, + spectral_axis_index=data.meta['spectral_axis_index']) else: # pragma: nocover raise NotImplementedError(f"converting {data.__class__.__name__} to display units is not supported") # noqa return data @@ -558,7 +559,7 @@ def _get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, if 'Trace' in data.meta: cls = None elif data.ndim == 2 and self.app.config == "specviz2d": - cls = Spectrum1D + cls = Spectrum elif data.ndim == 2: cls = CCDData elif data.ndim in [1, 3]: @@ -566,10 +567,10 @@ def _get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, cls = NDDataArray else: # for cubeviz, specviz, mosviz, this must be a spectrum: - cls = Spectrum1D + cls = Spectrum object_kwargs = {} - if cls == Spectrum1D: + if cls == Spectrum: object_kwargs['statistic'] = None if not spatial_subset and not mask_subset: @@ -643,7 +644,7 @@ def get_data(self, data_label=None, cls=None, use_display_units=False, **kwargs) ---------- data_label : str, optional Provide a label to retrieve a specific data set from data_collection. - cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional + cls : `~specutils.Spectrum`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. use_display_units : bool, optional Whether to convert to the display units defined in the plugin. diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index c59f079488..2d1c672984 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -71,7 +71,7 @@ def _launch_config_with_data(config, data=None, show=True, filepath=None, **kwar 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 Spectrum1D or CCDData) + A filepath or Jdaviz-compatible data object (such as Spectrum or CCDData) show : bool Determines whether to immediately show the application filepath : str diff --git a/jdaviz/core/marks.py b/jdaviz/core/marks.py index 870da67412..d8c39d19f1 100644 --- a/jdaviz/core/marks.py +++ b/jdaviz/core/marks.py @@ -4,7 +4,7 @@ from bqplot import LinearScale from bqplot.marks import Lines, Label, Scatter from glue.core import HubListener -from specutils import Spectrum1D +from specutils import Spectrum from jdaviz.core.events import GlobalDisplayUnitChanged from jdaviz.core.events import (SliceToolStateMessage, LineIdentifyMessage, @@ -130,9 +130,9 @@ def set_y_unit(self, unit=None): unit = u.Unit(unit) if self.yunit is not None and not np.all([s == 0 for s in self.y.shape]): - if self.viewer.default_class is Spectrum1D: + if self.viewer.default_class is Spectrum: - spec = self.viewer.state.reference_data.get_object(cls=Spectrum1D) + spec = self.viewer.state.reference_data.get_object(cls=Spectrum) pixar_sr = spec.meta.get('PIXAR_SR', 1) cube_wave = self.x * self.xunit @@ -184,7 +184,7 @@ def _update_reference_data(self, reference_data): if reference_data is None or self.viewer.jdaviz_app.config == 'rampviz': return - self._update_unit(reference_data.get_object(cls=Spectrum1D).spectral_axis.unit) + self._update_unit(reference_data.get_object(cls=Spectrum).spectral_axis.unit) def _update_unit(self, new_unit): # the x-units may have changed. We want to convert the internal self.x diff --git a/jdaviz/core/template_mixin.py b/jdaviz/core/template_mixin.py index a678570a95..7115ac79c5 100644 --- a/jdaviz/core/template_mixin.py +++ b/jdaviz/core/template_mixin.py @@ -32,7 +32,7 @@ from glue_jupyter.widgets.linked_dropdown import get_choices as _get_glue_choices from photutils.aperture import CircularAperture, EllipticalAperture, RectangularAperture from regions import PixelRegion -from specutils import Spectrum1D +from specutils import Spectrum from specutils.manipulation import extract_region from traitlets import Any, Bool, Dict, Float, HasTraits, List, Unicode, observe @@ -2216,8 +2216,8 @@ def selected_min_max(self, dataset): """ if self.is_multiselect: # pragma: no cover raise TypeError("This action cannot be done when multiselect is active") - if not isinstance(dataset, Spectrum1D): # pragma: no cover - raise TypeError("dataset must be a Spectrum1D object") + if not isinstance(dataset, Spectrum): # pragma: no cover + raise TypeError("dataset must be a Spectrum object") if self.selected_obj is None: return np.nanmin(dataset.spectral_axis), np.nanmax(dataset.spectral_axis) @@ -3135,6 +3135,7 @@ def _get_continuum(self, dataset, spectral_subset, update_marks=False, per_pixel return None, None, None spectral_axis = full_spectrum.spectral_axis + spectral_axis_index = full_spectrum.spectral_axis_index if spectral_axis.unit == u.pix: # plugin should be disabled so not get this far, but can still get here # before the disabled message is set @@ -3239,18 +3240,19 @@ def _get_continuum(self, dataset, spectral_subset, update_marks=False, per_pixel min_x = min(spectral_axis.value) if per_pixel: # full_spectrum.flux is a cube, so we want to act on all spaxels independently - continuum_y = full_spectrum.flux[:, :, continuum_mask].value + continuum_y = np.take(full_spectrum.flux, continuum_mask, axis=spectral_axis_index).value def fit_continuum(continuum_y_spaxel): return np.polyfit(continuum_x-min_x, continuum_y_spaxel, deg=1) # compute the linear fit for each spaxel independently, along the spectral axis - slopes_intercepts = np.apply_along_axis(fit_continuum, 2, continuum_y) - slopes = slopes_intercepts[:, :, 0] - intercepts = slopes_intercepts[:, :, 1] + slopes_intercepts = np.apply_along_axis(fit_continuum, spectral_axis_index, continuum_y) + slopes = np.take(slopes_intercepts, 0, spectral_axis_index) + intercepts = np.take(slopes_intercepts, 1, spectral_axis_index) # spectrum.spectral_axis is an array, but we need our continuum to have the same # shape as the fluxes in the cube, so let's just duplicate to the correct shape + print(spectrum.flux.shape) spectral_axis_cube = np.zeros(spectrum.flux.shape) spectral_axis_cube[:, :] = spectrum.spectral_axis.value @@ -3570,7 +3572,7 @@ def default_data_cls(self): return NDData if 'is_trace' in self.filters: return None - return Spectrum1D + return Spectrum def _get_dc_item(self, selected): if selected not in self.labels: diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index b00dd5f6be..d25808133d 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -8,7 +8,7 @@ from regions import (PixCoord, CirclePixelRegion, CircleSkyRegion, RectanglePixelRegion, EllipsePixelRegion, CircleAnnulusPixelRegion) from numpy.testing import assert_allclose -from specutils import SpectralRegion, Spectrum1D +from specutils import SpectralRegion, Spectrum from astropy.nddata import NDData from jdaviz.utils import get_subset_type, MultiMaskSubsetState @@ -156,7 +156,7 @@ def test_region_from_subset_3d(cubeviz_helper): def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): - data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) subset_plugin = cubeviz_helper.plugins['Subset Tools']._obj cubeviz_helper.load_data(data, data_label='Test 1D Flux') @@ -205,7 +205,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): def test_disjoint_spectral_subset(cubeviz_helper, spectral_cube_wcs): subset_plugin = cubeviz_helper.plugins['Subset Tools']._obj - data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data, data_label="Test Flux") spectral_axis_unit = u.Unit(cubeviz_helper.plugins['Unit Conversion'].spectral_unit.selected) @@ -791,7 +791,7 @@ def test_delete_subsets(cubeviz_helper, spectral_cube_wcs): """ Test that the toolbar selections get reset when the subset being actively edited gets deleted. """ - data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data, data_label="Test Flux") dc = cubeviz_helper.app.data_collection @@ -821,7 +821,7 @@ class TestRegionsFromSubsets: def test_get_regions_from_subsets_cubeviz(self, cubeviz_helper, spectral_cube_wcs): """ Basic tests for retrieving Sky Regions from spatial subsets in Cubeviz. """ - data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) + data = Spectrum(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data) # basic test, a single circular region diff --git a/notebooks/MosvizExample.ipynb b/notebooks/MosvizExample.ipynb index 2b667033e8..a033fdb97b 100644 --- a/notebooks/MosvizExample.ipynb +++ b/notebooks/MosvizExample.ipynb @@ -53,7 +53,7 @@ "source": [ "But before we can use it, we need some data.\n", "\n", - "The Mosviz parsers accept lists of `Spectrum1D` 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 `Spectrum` 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." ] }, { @@ -96,7 +96,7 @@ "fn = download_file(example_data, cache=True)\n", "with ZipFile(fn, 'r') as sample_data_zip:\n", " sample_data_zip.extractall(data_dir)\n", - " \n", + "\n", "data_dir = (pathlib.Path(data_dir) / 'mosviz_nirspec_data_0.3' / 'level3')" ] }, diff --git a/pyproject.toml b/pyproject.toml index 87cffe5ede..5951a3ae37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "ipywidgets>=8.0.6", "solara>=1.40.0", "pyyaml>=5.4.1", - "specutils>=1.18", + "specutils>=2.0", "specreduce>=1.4.1", "photutils>=1.4", "glue-astronomy>=0.10",