From cb51f8858766cd64c8624b2dd3deabe3030b5329 Mon Sep 17 00:00:00 2001 From: Mario Buikhuizen Date: Mon, 18 Nov 2024 17:58:25 +0100 Subject: [PATCH 1/5] fix: golden layout not rendering when created outside viewport (#3299) * fix: golden layout not rendering when created outside viewport Since Lab 4.2 cells outside the viewport get the style "display: none" which causes the content to not have height. This causes an error in size calculations of golden layout from which it doesn't recover. * Add changelog --------- Co-authored-by: Ricky O'Steen (cherry picked from commit 7e5ddfa4ceabc73d683b4153e45057875a611c51) --- CHANGES.rst | 2 ++ jdaviz/app.vue | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f7e7de9e5c..68e49856e4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,8 @@ Bug Fixes - Added ``nbclassic`` dependency to fix ``solara``-based popouts. [#3282] +- Fixed viewer widgets displaying improperly if initialized out of view in Jupyter Lab. [#3299] + Cubeviz ^^^^^^^ diff --git a/jdaviz/app.vue b/jdaviz/app.vue index 6ca646dc6f..cca52dd6ae 100644 --- a/jdaviz/app.vue +++ b/jdaviz/app.vue @@ -76,6 +76,7 @@ export default { + data() { + return { + outputCellHasHeight: false, + }; + }, methods: { checkNotebookContext() { this.notebook_context = document.getElementById("ipython-main-app") @@ -188,6 +194,13 @@ export default { if (jpOutputElem) { jpOutputElem.classList.remove('jupyter-widgets'); } + /* Workaround for Lab 4.2: cells outside the viewport get the style "display: none" which causes the content to not + * have height. This causes an error in size calculations of golden layout from which it doesn't recover. + */ + new ResizeObserver(entries => { + this.outputCellHasHeight = entries[0].contentRect.height > 0; + }).observe(this.$refs.mainapp.$el); + this.outputCellHasHeight = this.$refs.mainapp.$el.offsetHeight > 0 } }; From 415845db2c0346e746599caf1285c3977e471bb4 Mon Sep 17 00:00:00 2001 From: Ricky O'Steen Date: Mon, 18 Nov 2024 12:26:44 -0500 Subject: [PATCH 2/5] Fix changelog header --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 68e49856e4..9c63d69827 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -A.B.1 (unreleased) +4.0.1 (unreleased) ================== Bug Fixes @@ -21,7 +21,7 @@ Cubeviz - Add missing styling to API hints entry for aperture_method in the spectral extraction plugin. [#3231] -- Fixed "spectrum at spaxel" tool so it no longer resets spectral axis zoom. [#3249] +- Fixed "spectrum at spaxel" tool so it no longer resets spectral axis zoom. [#3249] - Fixed initializing a Gaussian1D model component when ``Cube Fit`` is toggled on. [#3295] From 8a966fe20d72de8506ccdca28478ec57d0ece63e Mon Sep 17 00:00:00 2001 From: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:14:36 -0500 Subject: [PATCH 3/5] Respect loaded mask cube properly in spectral extraction. (#3319) * Track loaded mask cube in cubeviz * First pass at handling mask cube in extraction * Fix type * Add changelog * Handle mask cubes that have NaNs instead of 0s * Act on copy of this mask array * Don't save DQ array as loaded_mask for JWST * Add snackbar warning and note in docs * codestyle * Add extraction test on file with mask * Codestyle (cherry picked from commit bf8cb2614ba7c823e7566b2fa8528d0de0e1c6ee) --- CHANGES.rst | 2 ++ docs/cubeviz/plugins.rst | 5 +++- jdaviz/configs/cubeviz/helper.py | 1 + jdaviz/configs/cubeviz/plugins/parsers.py | 14 +++++++--- .../spectral_extraction.py | 28 +++++++++++++++++-- .../cubeviz/plugins/tests/test_parsers.py | 21 ++++++++++++++ 6 files changed, 63 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 128539f823..87f4201abd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,8 @@ Cubeviz - Fixed initializing a Gaussian1D model component when ``Cube Fit`` is toggled on. [#3295] +- Spectral extraction now correctly respects the loaded mask cube. [#3319] + Imviz ^^^^^ diff --git a/docs/cubeviz/plugins.rst b/docs/cubeviz/plugins.rst index 55fa3c0276..3cbf2cba16 100644 --- a/docs/cubeviz/plugins.rst +++ b/docs/cubeviz/plugins.rst @@ -87,7 +87,7 @@ The slice plugin provides the ability to select the slice of the cube currently visible in the image viewers, with the corresponding wavelength highlighted in the spectrum viewer. -To choose a specific slice, enter an approximate wavelength (in which case the nearest slice will +To choose a specific slice, enter an approximate wavelength (in which case the nearest slice will be selected and the wavelength entry will "span" to the exact value of that slice). The snapping behavior can be disabled in the plugin settings to allow for smooth scrubbing, in which case the closest slice will still be displayed in the cube viewer. @@ -301,6 +301,9 @@ optionally choose a :guilabel:`Spatial region`, if you have one. Click :guilabel:`EXTRACT` to produce a new 1D spectrum dataset from the spectral cube, which has uncertainties propagated by `astropy.nddata `_. +By default, if a mask was loaded with the cube, it will be applied to the +cube when extracting in addition to any subsets chosen as an aperture. This +is not currently done for Data Quality arrays, e.g. the DQ extension in JWST files. If using a simple subset (currently only works for a circular subset applied to data with spatial axis units in wavelength) for the spatial aperture, an option to diff --git a/jdaviz/configs/cubeviz/helper.py b/jdaviz/configs/cubeviz/helper.py index fce4f61b15..42065f8401 100644 --- a/jdaviz/configs/cubeviz/helper.py +++ b/jdaviz/configs/cubeviz/helper.py @@ -22,6 +22,7 @@ class Cubeviz(CubeConfigHelper, LineListMixin): _loaded_flux_cube = None _loaded_uncert_cube = None + _loaded_mask_cube = None _cube_viewer_cls = CubevizImageView def __init__(self, *args, **kwargs): diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py index 6e8f58c938..5c80b77a98 100644 --- a/jdaviz/configs/cubeviz/plugins/parsers.py +++ b/jdaviz/configs/cubeviz/plugins/parsers.py @@ -306,8 +306,9 @@ def _parse_hdulist(app, hdulist, file_name=None, app.add_data(sc, data_label) if data_type == 'mask': - # We no longer auto-populate the mask cube into a viewer - pass + # We no longer auto-populate the mask cube into a viewer, but we still want + # to keep track of this cube for use in, e.g., spectral extraction. + app._jdaviz_helper._loaded_mask_cube = app.data_collection[data_label] elif data_type == 'uncert': app.add_data_to_viewer(uncert_viewer_reference_name, data_label) @@ -429,8 +430,10 @@ def _parse_esa_s3d(app, hdulist, data_label, ext='DATA', flux_viewer_reference_n if data_type == 'flux': app._jdaviz_helper._loaded_flux_cube = app.data_collection[data_label] - if data_type == 'uncert': + 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, @@ -482,7 +485,8 @@ def _parse_spectrum1d_3d(app, file_obj, data_label=None, elif attr == 'uncertainty': app.add_data_to_viewer(uncert_viewer_reference_name, cur_data_label) app._jdaviz_helper._loaded_uncert_cube = app.data_collection[cur_data_label] - # We no longer auto-populate the mask cube into a viewer + elif attr == 'mask': + app._jdaviz_helper._loaded_mask_cube = app.data_collection[cur_data_label] def _parse_spectrum1d(app, file_obj, data_label=None, spectrum_viewer_reference_name=None): @@ -540,6 +544,8 @@ def _parse_ndarray(app, file_obj, data_label=None, data_type=None, elif data_type == 'uncert': app.add_data_to_viewer(uncert_viewer_reference_name, data_label) 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_gif(app, file_obj, data_label=None, flux_viewer_reference_name=None): # pragma: no cover diff --git a/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py b/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py index 0d3b61e727..17b3bf2c73 100644 --- a/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py +++ b/jdaviz/configs/cubeviz/plugins/spectral_extraction/spectral_extraction.py @@ -370,6 +370,16 @@ def uncert_cube(self): # TODO: allow selecting or associating an uncertainty cube? return None + @property + def mask_cube(self): + if (hasattr(self._app._jdaviz_helper, '_loaded_flux_cube') and + hasattr(self.app._jdaviz_helper, '_loaded_mask_cube') and + self.dataset.selected == self._app._jdaviz_helper._loaded_flux_cube.label): + return self._app._jdaviz_helper._loaded_mask_cube + else: + # TODO: allow selecting or associating a mask/DQ cube? + return None + @property def slice_display_unit(self): return astropy.units.Unit(self.app._get_display_unit(self.slice_display_unit_name)) @@ -430,7 +440,7 @@ def aperture_area_along_spectral(self): def bg_area_along_spectral(self): return np.sum(self.bg_weight_mask, axis=self.spatial_axes) - def _extract_from_aperture(self, cube, uncert_cube, aperture, + def _extract_from_aperture(self, cube, uncert_cube, mask_cube, aperture, weight_mask, wavelength_dependent, selected_func, **kwargs): # This plugin collapses over the *spatial axes* (optionally over a spatial subset, @@ -486,6 +496,18 @@ def _extract_from_aperture(self, cube, uncert_cube, aperture, # Filter out NaNs (False = good) mask = np.logical_or(mask, np.isnan(flux)) + # Also apply the cube's original mask array + if mask_cube: + snackbar_message = SnackbarMessage( + "Note: Applied loaded mask cube during extraction", + color="warning", + sender=self) + self.hub.broadcast(snackbar_message) + mask_from_cube = mask_cube.get_component('flux').data.copy() + # Some mask cubes have NaNs where they are not masked instead of 0 + mask_from_cube[np.where(np.isnan(mask_from_cube))] = 0 + mask = np.logical_or(mask, mask_from_cube.astype('bool')) + nddata_reshaped = NDDataArray( flux, mask=mask, uncertainty=uncertainties, wcs=wcs, meta=nddata.meta ) @@ -588,7 +610,7 @@ def extract(self, return_bg=False, add_data=True, **kwargs): raise ValueError("aperture and background cannot be set to the same subset") selected_func = self.function_selected.lower() - spec = self._extract_from_aperture(self.cube, self.uncert_cube, + spec = self._extract_from_aperture(self.cube, self.uncert_cube, self.mask_cube, self.aperture, self.aperture_weight_mask, self.wavelength_dependent, selected_func, **kwargs) @@ -642,7 +664,7 @@ def extract_bg_spectrum(self, add_data=False, **kwargs): # allow internal calls to override the behavior of the bg_spec_per_spaxel traitlet bg_spec_per_spaxel = kwargs.pop('bg_spec_per_spaxel', self.bg_spec_per_spaxel) if self.background.selected != self.background.default_text: - bg_spec = self._extract_from_aperture(self.cube, self.uncert_cube, + bg_spec = self._extract_from_aperture(self.cube, self.uncert_cube, self.mask_cube, self.background, self.bg_weight_mask, self.bg_wavelength_dependent, self.function_selected.lower(), **kwargs) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py index 9c3d715548..17cdd4967f 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py @@ -1,3 +1,5 @@ +import warnings + import numpy as np import pytest from astropy import units as u @@ -205,6 +207,25 @@ def test_numpy_cube(cubeviz_helper): assert flux.units == 'ct' +@pytest.mark.remote_data +def test_manga_cube(cubeviz_helper): + # Remote data test of loading and extracting an up-to-date (as of 11/19/2024) MaNGA cube + # This also tests that spaxel is converted to pix**2 + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + cubeviz_helper.load_data("https://stsci.box.com/shared/static/gts87zqt5265msuwi4w5u003b6typ6h0.gz", cache=True) # noqa + + uc = cubeviz_helper.plugins['Unit Conversion'] + uc.spectral_y_type = "Surface Brightness" + + se = cubeviz_helper.plugins['Spectral Extraction'] + se.function = "Mean" + se.extract() + extracted_max = cubeviz_helper.get_data("Spectrum (mean)").max() + assert_allclose(extracted_max.value, 2.836957E-18) + assert extracted_max.unit == u.Unit("erg / Angstrom s cm**2 pix**2") + + def test_invalid_data_types(cubeviz_helper): with pytest.raises(ValueError, match=r"The input file 'does_not_exist\.fits'"): cubeviz_helper.load_data('does_not_exist.fits') From 3d1a177119ae6f19bc519f99a6433f9b171cd49b Mon Sep 17 00:00:00 2001 From: "Brett M. Morris" Date: Tue, 3 Dec 2024 11:15:13 -0500 Subject: [PATCH 4/5] Removing filter warning for mpl-scatter-density (#3327) * removing filter warning for mpl-scatter-density * replace deprecated pkg_resources call in linelists (cherry picked from commit 3f1abf7b76db57f9094761d0463092b10bba6a99) --- jdaviz/core/linelists.py | 8 +++----- pyproject.toml | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/jdaviz/core/linelists.py b/jdaviz/core/linelists.py index 7a004343c8..3c43e11a3d 100644 --- a/jdaviz/core/linelists.py +++ b/jdaviz/core/linelists.py @@ -1,4 +1,4 @@ -import pkg_resources +from importlib import resources import json from astropy.table import QTable @@ -8,8 +8,7 @@ def get_linelist_metadata(): """Return metadata for line lists.""" - metadata_file = pkg_resources.resource_filename("jdaviz", - "data/linelists/linelist_metadata.json") + metadata_file = resources.files("jdaviz").joinpath("data/linelists/linelist_metadata.json") with open(metadata_file) as f: metadata = json.load(f) return metadata @@ -34,8 +33,7 @@ def load_preset_linelist(name): raise ValueError("Line name not in available set of line lists. " + "Valid list names are: {}".format(list(metadata.keys()))) fname_base = metadata[name]["filename_base"] - fname = pkg_resources.resource_filename("jdaviz", - "data/linelists/{}.csv".format(fname_base)) + fname = resources.files("jdaviz").joinpath("data/linelists/{}.csv".format(fname_base)) units = metadata[name]["units"] linetable = QTable.read(fname) diff --git a/pyproject.toml b/pyproject.toml index 83f8179694..98d2ccb61a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,8 +138,6 @@ filterwarnings = [ "ignore::DeprecationWarning:glue", "ignore::DeprecationWarning:asteval", "ignore:::specutils.spectra.spectrum1d", - # Remove the following line once https://github.com/astrofrog/mpl-scatter-density/issues/46 is addressed - "ignore:pkg_resources is deprecated as an API:DeprecationWarning:mpl_scatter_density", # Ignore numpy 2.0 warning, see https://github.com/astropy/astropy/pull/15495 # and https://github.com/scipy/scipy/pull/19275 "ignore:.*numpy\\.core.*:DeprecationWarning", From 8ee5da2ef90e5126db862f255892d3eea409e0e5 Mon Sep 17 00:00:00 2001 From: Ricky O'Steen Date: Wed, 11 Dec 2024 12:54:57 -0500 Subject: [PATCH 5/5] Allow larger rtol on new test --- jdaviz/configs/cubeviz/plugins/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py index 17cdd4967f..1f305b2742 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py @@ -222,7 +222,7 @@ def test_manga_cube(cubeviz_helper): se.function = "Mean" se.extract() extracted_max = cubeviz_helper.get_data("Spectrum (mean)").max() - assert_allclose(extracted_max.value, 2.836957E-18) + assert_allclose(extracted_max.value, 2.836957E-18, rtol=1E-5) assert extracted_max.unit == u.Unit("erg / Angstrom s cm**2 pix**2")