diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml new file mode 100644 index 0000000000..3368e4e7f5 --- /dev/null +++ b/.github/workflows/standalone.yml @@ -0,0 +1,141 @@ +name: Build standalone + +on: + push: + branches: + - main + - 'v*' + tags: + - 'v*' + +defaults: + run: + shell: bash {0} + + +jobs: + build_binary: + runs-on: ${{ matrix.os }}-latest + strategy: + matrix: + os: [ubuntu, windows, macos] + steps: + # osx signing based on https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/ + - name: Import Certificates (macOS) + uses: apple-actions/import-codesign-certs@v1 + if: ${{ matrix.os == 'macos' }} + with: + p12-file-base64: ${{ secrets.DEV_ID_APP_CERT }} + p12-password: ${{ secrets.DEV_ID_APP_PASSWORD }} + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install jdaviz + run: pip install . + + - name: Install pyinstaller + run: pip install pyinstaller==5.11 + + - name: Create standalone binary + env: + DEVELOPER_ID_APPLICATION: ${{ secrets.DEVELOPER_ID_APPLICATION }} + run: (cd standalone; pyinstaller ./jdaviz.spec) + + - name: Remove invalid files for OSX + # hopefully we can improve this in the future + # by using good hooks + # i think the issue is that we have a . in the name, there are many + # google hits on pyqt having the same issue + # and we might be able to remove it after https://github.com/pyinstaller/pyinstaller/pull/7619 + # is released (pyinstaller 5.13 probably) + if: ${{ matrix.os == 'macos' }} + run: | + rm -rf standalone/dist/jdaviz.app/Contents/MacOS/skimage/.dylibs + rm -rf standalone/dist/jdaviz.app/Contents/Resources/skimage/.dylibs + + - name: Codesign (OSX) + if: ${{ matrix.os == 'macos' }} + run: | + cd standalone/dist + codesign --deep --force --options=runtime --entitlements ../entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app + + - name: Create dmg (OSX) + # if we do not call always() GHA will && with success() + if: ${{ always() && (matrix.os == 'macos') }} + # it seems ditto (not zip) should be used in combination with notarization + # see https://developer.apple.com/forums/thread/116831 + # but dmg also works + # see https://github.com/glue-viz/glue-standalone-apps/blob/main/.github/workflows/build_stable.yml + run: | + rm -rf standalone/dist/jdaviz + hdiutil create -volname "Jdaviz" -srcfolder standalone/dist -ov -format UDZO standalone/dist/jdaviz.dmg + + - name: Notary step + stapling (OSX) + if: ${{ matrix.os == 'macos' }} + run: | + output=$(xcrun notarytool submit standalone/dist/jdaviz.dmg --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true + echo "$output" + uuid=$(echo "$output" | awk -F '[ :]+' '/id:/ {print $3; exit}') + echo "UUID: $uuid" + if [[ $output == *"status: Accepted"* ]]; then + echo "Great, notarization succeeded, staple it!" + xcrun stapler staple standalone/dist/jdaviz.dmg + else + echo "Log output for failed notarization: $uuid" + xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }} $uuid || true + fi + + - name: Validate app (OSX) + if: ${{ matrix.os == 'macos' }} + run: | + spctl -a -vvv -t execute standalone/dist/jdaviz.app + + - name: Run jdaviz cmd in background + if: ${{ matrix.os == 'macos' }} + run: ./standalone/dist/jdaviz.app/Contents/MacOS/jdaviz-cli imviz& + + - name: Run jdaviz cmd in background + if: ${{ matrix.os != 'macos' }} + run: ./standalone/dist/jdaviz/jdaviz-cli imviz& + + - name: Install playwright + run: (pip install playwright; playwright install chromium) + + - name: Install pytest + run: pip install pytest-playwright + + - name: Wait for Voila to get online + uses: ifaxity/wait-on-action@v1 + with: + resource: tcp:8866 + timeout: 60000 + + - name: Test standalone + run: (cd standalone; touch pytest.ini; JUPYTER_PLATFORM_DIRS=1 pytest test.py --video=on) + + - name: Upload Test artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results-${{ matrix.os }} + path: standalone/test-results + + - name: Upload jdaviz standalone (non-OSX) + if: ${{ always() && (matrix.os != 'macos') }} + uses: actions/upload-artifact@v3 + with: + name: jdaviz-standlone-${{ matrix.os }} + path: | + standalone/dist/jdaviz + + - name: Upload jdaviz standalone (OSX) + if: ${{ always() && (matrix.os == 'macos') }} + uses: actions/upload-artifact@v3 + with: + name: jdaviz-standlone-${{ matrix.os }} + path: standalone/dist/jdaviz.dmg diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..3e8f9e57a9 --- /dev/null +++ b/.mailmap @@ -0,0 +1,25 @@ +Brett Graham +Brett M. Morris +Camilla Pacifici +Craig Jones +Duy Nguyen +Duy Nguyen +Duy Nguyen Duy Tuong Nguyen +Duy Nguyen Duy Tuong Nguyen +Duy Nguyen duytnguyendtn +Ivo Busko Ivo +Ivo Busko busko +Ivo Busko busko@stsci.edu +Jennifer Kotler +Jennifer Kotler +Jesse Averbukh +Josh Soref <2119212+jsoref@users.noreply.github.com> +Kyle Conroy Kyle +Mario Buikhuizen +Nicholas Earl +Nicholas Earl nmearl +Ori Fox Ori +P. L. Lim <2090236+pllim@users.noreply.github.com> +Patrick Ogle PatrickOgle +Ricky O'Steen <39831871+rosteen@users.noreply.github.com> +Ricky O'Steen <39831871+rosteen@users.noreply.github.com> rosteen <39831871+rosteen@users.noreply.github.com> diff --git a/CHANGES.rst b/CHANGES.rst index 98c9b63545..2593d792d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,16 +11,24 @@ New Features - Plots within plugins can now be popped-out into their own windows. [#2254] +- The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273] + +- Add first-pass launcher to select config and auto-identify data. [#2257] + Cubeviz ^^^^^^^ +- Added the ability to export cube slices to video. User will need to install + ``opencv-python`` separately or use ``[all]`` specifier when installing Jdaviz. [#2264] + Imviz ^^^^^ - Added the ability to load DS9 region files (``.reg``) using the ``IMPORT DATA`` button. However, this only works after loading at least one image into Imviz. [#2201] -- Added support for new ``CircularAnnulusROI`` subset from glue. [#2201] +- Added support for new ``CircularAnnulusROI`` subset from glue, including + a new draw tool. [#2201, #2240] Mosviz ^^^^^^ @@ -39,6 +47,13 @@ Specviz2d API Changes ----------- +- ``viz.app.get_data_from_viewer()`` is deprecated; use ``viz.get_data()``. [#2242] + +- ``viz.app.get_subsets_from_viewer()`` is deprecated; use ``viz.app.get_subsets()``. [#2242] + +- ``viz.get_data()`` now takes optional ``**kwargs``; e.g., you could pass in + ``function="sum"`` to collapse a cube in Cubeviz. [#2242] + Cubeviz ^^^^^^^ @@ -48,10 +63,13 @@ Imviz Mosviz ^^^^^^ +- Added new ``statistic`` keyword to ``mosviz.get_viewer("spectrum-2d-viewer").data()`` + to allow user to collapse 2D spectrum to 1D. [#2242] + Specviz ^^^^^^^ -* Re-enabled unit conversion support. [#2127] +- Re-enabled unit conversion support. [#2127] Specviz2d ^^^^^^^^^ @@ -61,9 +79,14 @@ Bug Fixes - Fixed wrong elliptical region translation in ``app.get_subsets()``. [#2244] +- Fixed ``cls`` input being ignored in ``viz.get_data()``. [#2242] + Cubeviz ^^^^^^^ +- Moment Map plugin now writes FITS file to working directory if no path provided + in standalone mode. [#2264] + Imviz ^^^^^ @@ -82,6 +105,10 @@ Other Changes and Additions - Gaussian smooth plugin excludes results from the gaussian smooth plugin from the input dataset dropdown. [#2239] +- CLI launchers no longer require data to be specified [#1960] + +- Added direct launchers for each config (e.g. ``specviz``) [#1960] + 3.5.1 (unreleased) ================== diff --git a/README.rst b/README.rst index 4719445abb..3a6ccd47ed 100644 --- a/README.rst +++ b/README.rst @@ -80,7 +80,7 @@ from a terminal, type: .. code-block:: bash jdaviz --help - jdaviz specviz /path/to/data/spectral_file + jdaviz --layout=specviz /path/to/data/spectral_file For more information on the command line interfaces for each tool, see the `Jdaviz docs `_. diff --git a/docs/cubeviz/export_data.rst b/docs/cubeviz/export_data.rst index 248ad932f4..11c439e7ad 100644 --- a/docs/cubeviz/export_data.rst +++ b/docs/cubeviz/export_data.rst @@ -22,7 +22,7 @@ can be used to extract the *spectrum* of a spatial subset named "Subset 1": .. code-block:: python - subset1_spec1d = cubeviz.specviz.get_spectra(subset_to_apply="Subset 1") + subset1_spec1d = cubeviz.specviz.get_spectra(spectral_subset="Subset 1") An example without accessing Specviz: @@ -41,7 +41,7 @@ To get all subsets from the spectrum viewer: .. code-block:: python - subset1_spec1d = cubeviz.app.get_subsets_from_viewer("spectrum-viewer") + subset1_spec1d = cubeviz.specviz.app.get_subsets() To access the spatial regions themselves: diff --git a/docs/cubeviz/import_data.rst b/docs/cubeviz/import_data.rst index 993ad8969f..555ae61e84 100644 --- a/docs/cubeviz/import_data.rst +++ b/docs/cubeviz/import_data.rst @@ -50,7 +50,7 @@ a data product is optional: .. code-block:: bash - jdaviz cubeviz /my/directory/cube.fits + jdaviz --layout=cubeviz /my/directory/cube.fits .. _cubeviz-import-gui: @@ -74,7 +74,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_spectrum` +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. FITS Files @@ -162,7 +162,7 @@ object, you can load it into Cubeviz as follows: # Create your spectrum1 spec3d = Spectrum1D(data, wcs=my_wcs) cubeviz = Cubeviz() - cubeviz.load_spectrum(spec3d, data_label='My Cube') + cubeviz.load_data(spec3d, data_label='My Cube') cubeviz.show() There is no plan to natively load such objects until ``datamodels`` diff --git a/docs/cubeviz/plugins.rst b/docs/cubeviz/plugins.rst index abea089de8..174a2079b4 100644 --- a/docs/cubeviz/plugins.rst +++ b/docs/cubeviz/plugins.rst @@ -274,3 +274,28 @@ Export Plot =========== This plugin allows exporting the plot in a given viewer to various image formats. + +.. _cubeviz-export-video: + +Movie +----- + +.. note:: + + For MPEG-4, this feature needs ``opencv-python`` to be installed; + see [opencv-python on PyPI](https://pypi.org/project/opencv-python/). + +Expand the "Export to video" section, then enter the desired starting and +ending slice indices (inclusive), the frame rate in frames per second (FPS), +and the filename. +If a path is not given, the file will be saved to current working +directory. Any existing file with the same name will be silently replaced. + +When you are ready, click the :guilabel:`Export to MP4` button. +The movie will be recorded at the given FPS. While recording is in progress, +it is highly recommended that you leave the app alone until it is done. + +While recording, there is an option to interrupt the recording when something +goes wrong (e.g., it is taking too long or you realized you entered the wrong inputs). +Click on the stop icon next to the :guilabel:`Export to MP4` button to interrupt it. +Doing so will result in no output video. diff --git a/docs/imviz/import_data.rst b/docs/imviz/import_data.rst index 6d23609109..0ad3995623 100644 --- a/docs/imviz/import_data.rst +++ b/docs/imviz/import_data.rst @@ -25,7 +25,7 @@ Multiple data files may be provided: .. code-block:: bash - jdaviz imviz /my/image/data1.fits /my/image/data2.fits + jdaviz --layout=imviz /my/image/data1.fits /my/image/data2.fits .. _imviz-import-gui: diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 4ae367b37e..a69819d66f 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -202,6 +202,15 @@ an interactively selected region. A typical workflow is as follows: in display data unit. Otherwise, it is only informational. If this field is not applicable for you, leave it at 0. **This field resets every time Data selection changes if auto-population not possible.** + + .. warning:: + + If your data is in surface brightness units and pixels on the image + have varying sky area, you should first convert your data from + surface brightness to flux units before using this plugin. + This is because, for performance reasons, the plugin multiplies + by the area after the aperture sum is calculated. + 7. If you also want photometry result in the unit of counts, you can enter a conversion factor in the :guilabel:`Counts conversion factor` field. The value must be in the unit of display data unit per counts. This is used to convert linear diff --git a/docs/known_bugs.rst b/docs/known_bugs.rst index 086f959a35..37112b2901 100644 --- a/docs/known_bugs.rst +++ b/docs/known_bugs.rst @@ -130,6 +130,14 @@ the side of the cell output to collapse or expand the scrollable window. This has the unintended consequence of changing the contrast of the image displayed in the Cubeviz cube viewer. +On Windows OS, latency increases exponentially with number of subsets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This seems to be an issue with Windows OS specifically, although we are still +investigating exactly what causes it. +See `Issue #2263 `_ for +updates on this topic. + .. _known_issues_imviz: Imviz diff --git a/docs/mosviz/import_data.rst b/docs/mosviz/import_data.rst index ed0f513af2..fd0b7ab026 100644 --- a/docs/mosviz/import_data.rst +++ b/docs/mosviz/import_data.rst @@ -41,13 +41,13 @@ Similarly, an instrument keyword can be specified by the command line. For NIRSp .. code-block:: bash - jdaviz mosviz /path/to/my/data --instrument=nirspec + jdaviz --layout=mosviz /path/to/my/data --instrument=nirspec and for NIRISS: .. code-block:: bash - jdaviz mosviz /path/to/my/data --instrument=niriss + jdaviz --layout=mosviz /path/to/my/data --instrument=niriss Specifying a data directory and an instrument are required to start Mosviz from the command line. If a directory is entered without specifying an instrument, Mosviz will diff --git a/docs/mosviz/index.rst b/docs/mosviz/index.rst index 64adb21405..58d747f8bb 100644 --- a/docs/mosviz/index.rst +++ b/docs/mosviz/index.rst @@ -30,7 +30,7 @@ To load a sample `NIRISS Nirspec Data Set .png``) + in the working directory before stitching all the frames into a movie. + Please make sure you have sufficient memory for this operation. + PNG files are deleted after the movie is created unless otherwise specified. + If another PNG file with the same name already exists, it will be silently replaced. + + Parameters + ---------- + i_start, i_end : int or `None` + Slices to record; each slice will be a frame in the movie. + If not given, it is obtained from plugin inputs. + Unlike Python indexing, ``i_end`` is inclusive. + Wrapping and reverse indexing are not supported. + + fps : float or `None` + Frame rate in frames per second (FPS). + If not given, it is obtained from plugin inputs. + + filename : str or `None` + Filename for the movie to be recorded. Include path if necessary. + If not given, it is obtained from plugin inputs. + If another file with the same name already exists, it will be silently replaced. + + filetype : {'mp4', `None`} + Currently only MPEG-4 is supported. This keyword is reserved for future support + of other format(s). + + rm_temp_files : bool + Remove temporary PNG files after movie creation. Default is `True`. + + Returns + ------- + out_filename : str + The absolute path to the actual output file. + + """ + if self.config != "cubeviz": + raise NotImplementedError(f"save_movie is not available for config={self.config}") + + if not HAS_OPENCV: + raise ImportError("Please install opencv-python to save cube as movie.") + + if filetype is None: + if filename is not None and '.' in filename: + filetype = filename.split('.')[-1] + else: + # default to MPEG-4 + filetype = "mp4" + + if filetype != "mp4": + raise NotImplementedError(f"filetype={filetype} not supported") + + viewer = self.viewer.selected_obj + if not isinstance(viewer, BqplotImageView): # Profile viewer in glue-jupyter cannot do this + raise TypeError(f"Movie for {viewer.__class__.__name__} is not supported.") + if viewer.shape is None: + raise ValueError("Selected viewer has no display shape.") + + if fps is None: + fps = float(self.movie_fps) + if fps <= 0: + raise ValueError("Invalid frame rate, must be positive non-zero value.") + + if filename is None: + if self.movie_filename: + filename = self.movie_filename + else: + raise ValueError("Invalid filename.") + + # Make sure file does not end up in weird places in standalone mode. + path = os.path.dirname(filename) + if path and not os.path.exists(path): + raise ValueError(f"Invalid path={path}") + elif (not path or path.startswith("..")) and os.environ.get("JDAVIZ_START_DIR", ""): # noqa: E501 # pragma: no cover + filename = os.path.join(os.environ["JDAVIZ_START_DIR"], filename) + + if i_start is None: + i_start = int(self.i_start) + + if i_end is None: + i_end = int(self.i_end) + + # No wrapping. Forward only. + slice_plg = self.app._jdaviz_helper.plugins["Slice"]._obj + if i_start < 0: # pragma: no cover + i_start = 0 + if i_end > slice_plg.max_value: # pragma: no cover + i_end = slice_plg.max_value + if i_end <= i_start: + raise ValueError(f"No frames to write: i_start={i_start}, i_end={i_end}") + + threading.Thread( + target=lambda: self._save_movie(i_start, i_end, fps, filename, rm_temp_files) + ).start() + + return os.path.abspath(filename) + + def vue_save_movie(self, filetype): # pragma: no cover + """ + Callback for save movie events in the front end viewer toolbars. Uses + the bqplot.Figure save methods. + """ + try: + filename = self.save_movie(filetype=filetype) + except Exception as err: # pragma: no cover + self.hub.broadcast(SnackbarMessage( + f"Error saving {self.movie_filename}: {err!r}", sender=self, color="error")) + else: + # Let the user know where we saved the file. + # NOTE: Because of threading, this will be emitted even as movie as recording. + self.hub.broadcast(SnackbarMessage( + f"Movie being saved to {filename} for slices {self.i_start} to {self.i_end}, " + f"inclusive, at {self.movie_fps} FPS.", + sender=self, color="success")) + + def vue_interrupt_recording(self, *args): # pragma: no cover + self.movie_interrupt = True + self.hub.broadcast(SnackbarMessage( + f"Movie recording interrupted by user, {self.movie_filename} will be deleted.", + sender=self, color="warning")) diff --git a/jdaviz/configs/default/plugins/export_plot/export_plot.vue b/jdaviz/configs/default/plugins/export_plot/export_plot.vue index 065242935a..67408e4451 100644 --- a/jdaviz/configs/default/plugins/export_plot/export_plot.vue +++ b/jdaviz/configs/default/plugins/export_plot/export_plot.vue @@ -17,6 +17,7 @@ Export to PNG @@ -25,11 +26,110 @@ Export to SVG + + + + + + Export to Video + + + + + {{ movie_msg }} + + + + + + + + + + + + + + + + + + + + + + + + Start movie recording + +
+ + + Interrupt recording and delete movie file + +
+
+
+
+
+
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 2af3cae334..e85959f49f 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 @@ -12,7 +12,7 @@ def test_line_lists(specviz_helper): spec = Spectrum1D(flux=np.random.rand(100)*u.Jy, spectral_axis=np.arange(6000, 7000, 10)*u.AA) - specviz_helper.load_spectrum(spec) + specviz_helper.load_data(spec) lt = QTable() lt['linename'] = ['O III', 'Halpha'] @@ -51,7 +51,7 @@ def test_redshift(specviz_helper, spectrum1d): assert plg._obj.disabled_msg label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) assert not plg._obj.disabled_msg @@ -102,7 +102,7 @@ def test_redshift(specviz_helper, spectrum1d): def test_load_available_preset_lists(specviz_helper, spectrum1d): """ Loads all available line lists and checks the medium requirement """ label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) # Check to make sure we got our line lists available_linelists = get_available_linelists() @@ -125,7 +125,7 @@ def test_load_available_preset_lists(specviz_helper, spectrum1d): def test_line_identify(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) lt = QTable() lt['linename'] = ['O III', 'Halpha'] 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 42232520bc..4c9d89e072 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py @@ -20,7 +20,7 @@ def test_default_model_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # By default, the spectral region should be the entire spectrum assert modelfit_plugin._obj.spectral_subset_selected == "Entire Spectrum" @@ -46,7 +46,7 @@ def test_default_model_labels(specviz_helper, spectrum1d): def test_custom_model_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] for i, model in enumerate(MODELS): @@ -68,7 +68,7 @@ def test_register_model_with_uncertainty_weighting(specviz_helper, spectrum1d): spectrum1d.uncertainty = StdDevUncertainty(spectrum1d.flux * 0.1) with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # Test registering a simple linear fit @@ -105,7 +105,7 @@ def test_register_model_uncertainty_is_none(specviz_helper, spectrum1d): spectrum1d.uncertainty = None with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # Test registering a simple linear fit @@ -157,7 +157,7 @@ def test_register_cube_model(cubeviz_helper, spectrum1d_cube): def test_user_api(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) p = specviz_helper.plugins['Model Fitting'] # even though the default label is set to C, adding Linear1D should default to its automatic @@ -195,7 +195,7 @@ def test_user_api(specviz_helper, spectrum1d): def test_fit_gaussian_with_fixed_mean(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] modelfit_plugin.create_model_component('Gaussian1D', 'G') @@ -217,7 +217,7 @@ def test_fit_gaussian_with_fixed_mean(specviz_helper, spectrum1d): def test_reestimate_parameters(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) mf = specviz_helper.plugins['Model Fitting'] mf.create_model_component('Gaussian1D', 'G') @@ -327,12 +327,12 @@ def test_subset_masks(cubeviz_helper, spectrum1d_cube_larger): def test_invalid_subset(specviz_helper, spectrum1d): # 6000-8000 - specviz_helper.load_spectrum(spectrum1d, data_label="right_spectrum") + 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) - specviz_helper.load_spectrum(sp2, data_label="left_spectrum") + specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum # NOTE: using a subset that overlaps the right_spectrum (reference) results in errors when diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py index 5bd4fb22b3..12d881ee44 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py @@ -7,8 +7,8 @@ from glue.core.message import EditSubsetMessage, SubsetUpdateMessage from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode, ReplaceMode, XorMode) -from glue.core.roi import CircularROI, EllipticalROI, RectangularROI -from glue.core.subset import RoiSubsetState, RangeSubsetState, CompositeSubsetState +from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI +from glue.core.subset import RoiSubsetState, RangeSubsetState from glue.icons import icon_path from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu from glue_jupyter.common.toolbar_vuetify import read_icon @@ -149,7 +149,11 @@ def _unpack_get_subsets_for_ui(self): _around_decimals = 6 # Avoid 30 degrees from coming back as 29.999999999999996 if not subset_information: return - if len(subset_information) == 1: + if ((len(subset_information) == 1) and + (isinstance(subset_information[0]["subset_state"], RangeSubsetState) or + (isinstance(subset_information[0]["subset_state"], RoiSubsetState) and + isinstance(subset_information[0]["subset_state"].roi, + (CircularROI, RectangularROI, EllipticalROI))))): self.is_centerable = True else: self.is_centerable = False @@ -161,7 +165,7 @@ def _unpack_get_subsets_for_ui(self): glue_state = spec["glue_state"] if isinstance(subset_state, RoiSubsetState): if isinstance(subset_state.roi, CircularROI): - x, y = subset_state.roi.get_center() + x, y = subset_state.roi.center() r = subset_state.roi.radius subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x}, {"name": "Y Center", "att": "yc", "value": y, "orig": y}, @@ -178,8 +182,7 @@ def _unpack_get_subsets_for_ui(self): {"name": "Angle", "att": "theta", "value": theta, "orig": theta}) elif isinstance(subset_state.roi, EllipticalROI): - xc = subset_state.roi.xc - yc = subset_state.roi.yc + xc, yc = subset_state.roi.center() rx = subset_state.roi.radius_x ry = subset_state.roi.radius_y theta = np.around(np.degrees(subset_state.roi.theta), decimals=_around_decimals) @@ -190,6 +193,17 @@ def _unpack_get_subsets_for_ui(self): {"name": "Y Radius", "att": "radius_y", "value": ry, "orig": ry}, {"name": "Angle", "att": "theta", "value": theta, "orig": theta}] + elif isinstance(subset_state.roi, CircularAnnulusROI): + x, y = subset_state.roi.center() + inner_r = subset_state.roi.inner_radius + outer_r = subset_state.roi.outer_radius + subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x}, + {"name": "Y Center", "att": "yc", "value": y, "orig": y}, + {"name": "Inner radius", "att": "inner_radius", + "value": inner_r, "orig": inner_r}, + {"name": "Outer radius", "att": "outer_radius", + "value": outer_r, "orig": outer_r}] + subset_type = subset_state.roi.__class__.__name__ elif isinstance(subset_state, RangeSubsetState): @@ -303,6 +317,7 @@ def _check_input(self): reason = "" for index, sub in enumerate(self.subset_definitions): lo = hi = xmin = xmax = ymin = ymax = None + inner_radius = outer_radius = None for d_att in sub: if d_att["att"] == "lo": lo = d_att["value"] @@ -320,6 +335,10 @@ def _check_input(self): ymin = d_att["value"] elif d_att["att"] == "ymax": ymax = d_att["value"] + elif d_att["att"] == "outer_radius": + outer_radius = d_att["value"] + elif d_att["att"] == "inner_radius": + inner_radius = d_att["value"] if lo and hi and hi <= lo: status = False @@ -329,6 +348,10 @@ def _check_input(self): status = False reason = "Failed to update Subset: width and length must be positive scalars" break + elif inner_radius and outer_radius and inner_radius >= outer_radius: + status = False + reason = "Failed to update Subset: inner radius must be less than outer radius" + break return status, reason @@ -375,39 +398,13 @@ def get_center(self): depending on the Subset type, if applicable. If Subset is not centerable, this returns `None`. - Raises - ------ - NotImplementedError - Subset type is not supported. - """ # Composite region cannot be centered. if not self.is_centerable: # no-op return subset_state = self.subset_select.selected_subset_state - - if isinstance(subset_state, RoiSubsetState): - sbst_obj = subset_state.roi - if isinstance(sbst_obj, (CircularROI, EllipticalROI)): - cen = sbst_obj.get_center() - elif isinstance(sbst_obj, RectangularROI): - cen = sbst_obj.center() - else: # pragma: no cover - raise NotImplementedError( - f'Getting center of {sbst_obj.__class__} is not supported') - - elif isinstance(subset_state, RangeSubsetState): - cen = (subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo - - elif isinstance(subset_state, CompositeSubsetState): - cen = None - - else: # pragma: no cover - raise NotImplementedError( - f'Getting center of {subset_state.__class__} is not supported') - - return cen + return subset_state.center() def set_center(self, new_cen, update=False): """Set the desired center for the selected Subset, if applicable. @@ -439,7 +436,7 @@ def set_center(self, new_cen, update=False): if isinstance(subset_state, RoiSubsetState): x, y = new_cen sbst_obj = subset_state.roi - if isinstance(sbst_obj, (CircularROI, EllipticalROI)): + if isinstance(sbst_obj, (CircularROI, CircularAnnulusROI, EllipticalROI)): self._set_value_in_subset_definition(0, "X Center", "value", x) self._set_value_in_subset_definition(0, "Y Center", "value", y) elif isinstance(sbst_obj, RectangularROI): @@ -454,7 +451,7 @@ def set_center(self, new_cen, update=False): raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported') elif isinstance(subset_state, RangeSubsetState): - dx = new_cen - ((subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo) + dx = new_cen - subset_state.center() self._set_value_in_subset_definition(0, "Lower bound", "value", subset_state.lo + dx) self._set_value_in_subset_definition(0, "Upper bound", "value", subset_state.hi + dx) diff --git a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py index b867e62489..3d0b4e094a 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py @@ -6,7 +6,7 @@ @pytest.mark.filterwarnings('ignore') def test_plugin(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) p = specviz_helper.plugins['Subset Tools'] # regression test for https://github.com/spacetelescope/jdaviz/issues/1693 diff --git a/jdaviz/configs/imviz/imviz.ipynb b/jdaviz/configs/imviz/imviz.ipynb new file mode 100644 index 0000000000..51c6fff42a --- /dev/null +++ b/jdaviz/configs/imviz/imviz.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PREFIX\n", + "from jdaviz import Imviz\n", + "\n", + "imviz = Imviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " imviz.load_data('DATA_FILENAME')\n", + "imviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index 5cc0ecdbeb..65c51ab88c 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -464,7 +464,7 @@ def _copy_axes_to_spectral(): sp = self.app._get_object_cache[cache_key] else: sp = self._specviz_helper.get_data(data_label=data_label, - spectral_subset=subset_label) + spatial_subset=subset_label) self.app._get_object_cache[cache_key] = sp # Calculations have to happen in the frame of viewer display units. diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py index 46467b92a4..958d076fd1 100644 --- a/jdaviz/configs/imviz/plugins/viewers.py +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -24,7 +24,7 @@ class ImvizImageView(JdavizViewerMixin, BqplotImageView, AstrowidgetsImageViewer ['jdaviz:homezoom', 'jdaviz:prevzoom'], ['jdaviz:boxzoommatch', 'jdaviz:boxzoom'], ['jdaviz:panzoommatch', 'jdaviz:imagepanzoom'], - ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse', + ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse', 'bqplot:circannulus', 'jdaviz:singlepixelregion'], ['jdaviz:blinkonce', 'jdaviz:contrastbias'], ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export', 'jdaviz:sidebar_compass'] diff --git a/jdaviz/configs/imviz/tests/test_helper.py b/jdaviz/configs/imviz/tests/test_helper.py index f7c8a33041..f2e4410cb7 100644 --- a/jdaviz/configs/imviz/tests/test_helper.py +++ b/jdaviz/configs/imviz/tests/test_helper.py @@ -25,12 +25,12 @@ def test_create_new_viewer(imviz_helper, image_2d_wcs): assert len(imviz_helper.app.get_viewer_ids()) == 2 # there should be no data in the new viewer - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 0 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 0 # then add data, and check that data were added to the new viewer imviz_helper.app.add_data_to_viewer(viewer_name, data_label) - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 1 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 1 # remove data from the new viewer, check that it was removed imviz_helper.app.remove_data_from_viewer(viewer_name, data_label) - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 0 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 0 diff --git a/jdaviz/configs/mosviz/mosviz.ipynb b/jdaviz/configs/mosviz/mosviz.ipynb new file mode 100644 index 0000000000..e9350d166f --- /dev/null +++ b/jdaviz/configs/mosviz/mosviz.ipynb @@ -0,0 +1,46 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "# PREFIX\n", + "import pathlib\n", + "from jdaviz import Mosviz\n", + "\n", + "data_path = pathlib.Path('DATA_FILENAME')\n", + "\n", + "mosviz = Mosviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " mosviz.load_data(directory=data_path, instrument='INSTRUMENT')\n", + "mosviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/mosviz/plugins/viewers.py b/jdaviz/configs/mosviz/plugins/viewers.py index ba65587373..c5ad804655 100644 --- a/jdaviz/configs/mosviz/plugins/viewers.py +++ b/jdaviz/configs/mosviz/plugins/viewers.py @@ -43,7 +43,7 @@ def __init__(self, *args, **kwargs): self.figure.fig_margin = {'left': 0, 'bottom': 0, 'top': 0, 'right': 0} def data(self, cls=None): - return [layer_state.layer # .get_object(cls=cls or self.default_class) + return [layer_state.layer.get_object(cls=cls or self.default_class) for layer_state in self.state.layers if hasattr(layer_state, 'layer') and isinstance(layer_state.layer, BaseData)] @@ -207,8 +207,9 @@ def _handle_x_axis_orientation(self, *args): x_scales.min = max(limits) if self.inverted_x_axis else min(limits) x_scales.max = min(limits) if self.inverted_x_axis else max(limits) - def data(self, cls=None): - return [layer_state.layer.get_object(cls=cls or self.default_class) + def data(self, cls=None, statistic=None): + return [layer_state.layer.get_object(statistic=statistic, + cls=cls or self.default_class) for layer_state in self.state.layers if hasattr(layer_state, 'layer') and isinstance(layer_state.layer, BaseData)] diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index 9a5ab2ca5d..67d73ffe1c 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -2,9 +2,9 @@ from zipfile import ZipFile -from astropy.nddata import CCDData import numpy as np import pytest +from astropy.nddata import CCDData from specutils import Spectrum1D from jdaviz.utils import PRIHDR_KEY @@ -20,12 +20,14 @@ def test_load_spectrum1d(mosviz_helper, spectrum1d): assert dc_1.label == label assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[label], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) with pytest.raises(AttributeError): mosviz_helper.load_1d_spectra([1, 2, 3]) @@ -42,12 +44,14 @@ def test_load_image(mosviz_helper, mos_image): assert PRIHDR_KEY not in dc_1.meta assert dc_1.meta['RADESYS'] == 'ICRS' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('image-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_image_viewer_reference_name + ).data(cls=CCDData) - dataval = data[f"{label} 0"] + assert len(data) == 1 + dataval = data[0] assert isinstance(dataval, CCDData) assert dataval.shape == (55, 55) @@ -63,12 +67,14 @@ def test_load_spectrum_collection(mosviz_helper, spectrum_collection): assert dc_1.label == labels[0] assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.select_row(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[labels[0]], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): @@ -83,12 +89,14 @@ def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): assert dc_1.label == labels[0] assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[labels[0]], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): @@ -102,12 +110,14 @@ def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): assert dc_1.label == label assert dc_1.meta['INSTRUME'] == 'nirspec' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-2d-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_2d_viewer_reference_name + ).data() - assert data[label].shape == (1024, 15) + assert len(data) == 1 + assert data[0].shape == (1024, 15) @pytest.mark.parametrize('label', [None, "Test Label"]) @@ -118,7 +128,8 @@ def test_load_multi_image_spec(mosviz_helper, mos_image, spectrum1d, mos_spectru mosviz_helper.load_data(spectra1d, spectra2d, images=images, images_label=label) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 10 qtable = mosviz_helper.to_table() @@ -134,7 +145,8 @@ def test_load_multi_image_and_spec1d_only(mosviz_helper, mos_image, spectrum1d): mosviz_helper.load_data(spectra_1d=spectra1d, images=images) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 7 @@ -144,7 +156,8 @@ def test_load_multi_image_and_spec2d_only(mosviz_helper, mos_image, mos_spectrum mosviz_helper.load_data(spectra_2d=spectra2d, images=images) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 7 @@ -170,7 +183,8 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_ mosviz_helper.load_data(spectra1d, spectra2d, images=mos_image, images_label=label) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 8 qtable = mosviz_helper.to_table() diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index e0969e7cdd..e2066a9435 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -1,6 +1,7 @@ import warnings from astropy import units as u +from astropy.utils.decorators import deprecated_renamed_argument, deprecated from regions.core.core import Region from glue.core.subset_group import GroupedSubset from specutils import SpectralRegion, Spectrum1D @@ -40,11 +41,35 @@ def __init__(self, *args, **kwargs): self.app.hub.subscribe(self, RedshiftMessage, handler=self._redshift_listener) + @deprecated(since="3.6", alternative="load_data") def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, concat_by_file=False): """ Loads a data file or `~specutils.Spectrum1D` object into Specviz. + Parameters + ---------- + data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` + Spectrum1D, 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. + show_in_viewer : bool + Show data in viewer(s). + 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 + add a concatenated spectrum to the data collection. + """ + self.load_data(data, data_label, format, show_in_viewer, concat_by_file) + + def load_data(self, data, data_label=None, format=None, show_in_viewer=True, + concat_by_file=False): + """ + Load data into Specviz. + Parameters ---------- data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` @@ -68,7 +93,8 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, show_in_viewer=show_in_viewer, concat_by_file=concat_by_file) - def get_spectra(self, data_label=None, subset_to_apply=None, apply_slider_redshift="Warn"): + @deprecated_renamed_argument("subset_to_apply", "spectral_subset", "3.6") + def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshift="Warn"): """Returns the current data loaded into the main viewer """ @@ -81,16 +107,16 @@ def get_spectra(self, data_label=None, subset_to_apply=None, apply_slider_redshi if data_label is not None: spectrum = get_data_method(data_label=data_label, - spectral_subset=subset_to_apply, + spectral_subset=spectral_subset, cls=Spectrum1D) spectra[data_label] = spectrum else: for layer_state in viewer.state.layers: lyr = layer_state.layer - if subset_to_apply is not None: - if lyr.label == subset_to_apply: + if spectral_subset is not None: + if lyr.label == spectral_subset: spectrum = get_data_method(data_label=lyr.data.label, - spectral_subset=subset_to_apply, + spectral_subset=spectral_subset, cls=Spectrum1D, **function_kwargs) spectra[lyr.data.label] = spectrum @@ -291,7 +317,7 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None, use_display_units=False, **kwargs): """ Returns data with name equal to data_label of type cls with subsets applied from - subset_to_apply. + spectral_subset. Parameters ---------- 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 34812a2453..682d6040fc 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 @@ -14,7 +14,7 @@ def test_plugin(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -79,7 +79,7 @@ def test_spatial_subset(cubeviz_helper, image_cube_hdu_obj): def test_user_api(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) sv = specviz_helper.app.get_viewer('spectrum-viewer') sv.apply_roi(XRangeROI(6500, 7400)) @@ -109,7 +109,7 @@ def test_user_api(specviz_helper, spectrum1d): def test_line_identify(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) lt = QTable() lt['linename'] = ['O III', 'Halpha'] @@ -180,7 +180,7 @@ def test_coerce_unit(): def test_continuum_surrounding_spectral_subset(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -207,7 +207,7 @@ def test_continuum_surrounding_spectral_subset(specviz_helper, spectrum1d): def test_continuum_spectral_same_value(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -234,7 +234,7 @@ def test_continuum_spectral_same_value(specviz_helper, spectrum1d): def test_continuum_surrounding_invalid_width(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -259,7 +259,7 @@ def test_continuum_surrounding_invalid_width(specviz_helper, spectrum1d): def test_continuum_subset_spectral_entire(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -286,7 +286,7 @@ def test_continuum_subset_spectral_entire(specviz_helper, spectrum1d): def test_continuum_subset_spectral_subset2(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -319,7 +319,7 @@ def test_continuum_subset_spectral_subset2(specviz_helper, spectrum1d): def test_continuum_surrounding_no_right(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -347,7 +347,7 @@ def test_continuum_surrounding_no_right(specviz_helper, spectrum1d): def test_continuum_surrounding_no_left(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -375,7 +375,7 @@ def test_continuum_surrounding_no_left(specviz_helper, spectrum1d): def test_subset_changed(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -406,12 +406,12 @@ def test_subset_changed(specviz_helper, spectrum1d): def test_invalid_subset(specviz_helper, spectrum1d): # 6000-8000 - specviz_helper.load_spectrum(spectrum1d, data_label="right_spectrum") + 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) - specviz_helper.load_spectrum(sp2, data_label="left_spectrum") + specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum # NOTE: using a subset that overlaps the right_spectrum (reference) results in errors when 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 7ec76cd0aa..3e1b07b9ce 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py @@ -99,7 +99,7 @@ def test_unit_gaussian(specviz_helper, test_case): Test an Area 1 Gaussian and ensure the result returns in W/m2 Test provided by Patrick Ogle ''' - specviz_helper.load_spectrum(test_case) + specviz_helper.load_data(test_case) lineflux_result = _calculate_line_flux(specviz_helper) assert_quantity_allclose(float(lineflux_result['result']) * u.Unit(lineflux_result['unit']), @@ -117,7 +117,7 @@ def test_unit_gaussian_mixed_units_per_steradian(specviz_helper): flx_wave = _gauss_with_unity_area(lam_a.value, mn, sig)*1E3*u.erg/u.s/u.cm**2/u.Angstrom/u.sr fl_wave = Spectrum1D(spectral_axis=lam_a, flux=flx_wave) - specviz_helper.load_spectrum(fl_wave) + specviz_helper.load_data(fl_wave) lineflux_result = _calculate_line_flux(specviz_helper) assert_quantity_allclose(float(lineflux_result['result']) * u.Unit(lineflux_result['unit']), 1*u.Unit('W/(m2sr)')) 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 245c19c124..d69e93b35f 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 @@ -10,7 +10,7 @@ ("micron", "fail", "micron", "Jy")]) def test_value_error_exception(specviz_helper, spectrum1d, new_spectral_axis, new_flux, expected_spectral_axis, expected_flux): - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -34,7 +34,7 @@ def test_value_error_exception(specviz_helper, spectrum1d, new_spectral_axis, ne def test_conv_wave_only(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -50,7 +50,7 @@ def test_conv_wave_only(specviz_helper, spectrum1d, uncert): def test_conv_flux_only(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -66,7 +66,7 @@ def test_conv_flux_only(specviz_helper, spectrum1d, uncert): def test_conv_wave_flux(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] diff --git a/jdaviz/configs/specviz/specviz.ipynb b/jdaviz/configs/specviz/specviz.ipynb new file mode 100644 index 0000000000..1f9f5fcd23 --- /dev/null +++ b/jdaviz/configs/specviz/specviz.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PREFIX\n", + "from jdaviz import Specviz\n", + "\n", + "specviz = Specviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " specviz.load_data(data_path)\n", + "specviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index 0f47db0d02..68ae8870c6 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -23,7 +23,7 @@ def setup_class(self, specviz_helper, spectrum1d, multi_order_spectrum_list): self.multi_order_spectrum_list = multi_order_spectrum_list self.label = "Test 1D Spectrum" - self.spec_app.load_spectrum(spectrum1d, data_label=self.label) + self.spec_app.load_data(spectrum1d, data_label=self.label) def test_load_spectrum1d(self): # starts with a single loaded spectrum1d object: @@ -32,14 +32,13 @@ def test_load_spectrum1d(self): assert dc_0.label == self.label assert dc_0.meta['uncertainty_type'] == 'std' - data = self.spec_app.app.get_data_from_viewer('spectrum-viewer') + data = self.spec_app.get_data() - assert isinstance(list(data.values())[0], Spectrum1D) - assert list(data.keys())[0] == self.label + assert isinstance(data, Spectrum1D) def test_load_spectrum_list_no_labels(self): # now load three more spectra from a SpectrumList, without labels - self.spec_app.load_spectrum(self.spec_list) + self.spec_app.load_data(self.spec_list) assert len(self.spec_app.app.data_collection) == 4 for i in (1, 2, 3): assert "specviz_data" in self.spec_app.app.data_collection[i].label @@ -47,24 +46,24 @@ def test_load_spectrum_list_no_labels(self): def test_load_spectrum_list_with_labels(self): # now load three more spectra from a SpectrumList, with labels: labels = ["List test 1", "List test 2", "List test 3"] - self.spec_app.load_spectrum(self.spec_list, data_label=labels) + self.spec_app.load_data(self.spec_list, data_label=labels) assert len(self.spec_app.app.data_collection) == 4 def test_load_multi_order_spectrum_list(self): assert len(self.spec_app.app.data_collection) == 1 # now load ten spectral orders from a SpectrumList: - self.spec_app.load_spectrum(self.multi_order_spectrum_list) + self.spec_app.load_data(self.multi_order_spectrum_list) assert len(self.spec_app.app.data_collection) == 11 def test_mismatched_label_length(self): with pytest.raises(ValueError, match='Length'): labels = ["List test 1", "List test 2"] - self.spec_app.load_spectrum(self.spec_list, data_label=labels) + self.spec_app.load_data(self.spec_list, data_label=labels) def test_load_spectrum_collection(self): with pytest.raises(TypeError): collection = SpectrumCollection([1]*u.AA) - self.spec_app.load_spectrum(collection) + self.spec_app.load_data(collection) def test_get_spectra(self): with pytest.warns(UserWarning, match='Applying the value from the redshift slider'): @@ -246,15 +245,15 @@ def test_get_spectra_no_spectra_label_redshift_error(specviz_helper, spectrum1d) def test_add_spectrum_after_subset(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") specviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6200, 7000)) new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 - specviz_helper.load_spectrum(new_spec, data_label="test2") + specviz_helper.load_data(new_spec, data_label="test2") def test_get_spectral_regions_unit(specviz_helper, spectrum1d): # Ensure units we put in are the same as the units we get out - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) specviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6200, 7000)) subsets = specviz_helper.get_spectral_regions() @@ -276,7 +275,7 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d): # If the reference (visible) data changes via unit conversion, # check that the region's units convert too - specviz_helper.load_spectrum(spectrum1d) # Originally Angstrom + specviz_helper.load_data(spectrum1d) # Originally Angstrom # Also check coordinates info panel. # x=0 -> 6000 A, x=1 -> 6222.222 A @@ -323,7 +322,7 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d): def test_subset_default_thickness(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) sv = specviz_helper.app.get_viewer('spectrum-viewer') sv.toolbar.active_tool = sv.toolbar.tools['bqplot:xrange'] @@ -351,7 +350,7 @@ def test_load_spectrum_list_directory(tmpdir, specviz_helper): # Load two NIRISS x1d files from FITS. They have 19 and 20 EXTRACT1D # extensions per file, for a total of 39 spectra to load: with pytest.warns(UserWarning, match='SRCTYPE is missing or UNKNOWN in JWST x1d loader'): - specviz_helper.load_spectrum(data_path) + specviz_helper.load_data(data_path) # NOTE: the length was 3 before specutils 1.9 (https://github.com/astropy/specutils/pull/982) expected_len = 39 @@ -378,12 +377,12 @@ def test_load_spectrum_list_directory_concat(tmpdir, specviz_helper): # spectra common to each file into one "Combined" spectrum to load per file. # Now the total is (19 EXTRACT 1D + 1 Combined) + (20 EXTRACT 1D + 1 Combined) = 41. with pytest.warns(UserWarning, match='SRCTYPE is missing or UNKNOWN in JWST x1d loader'): - specviz_helper.load_spectrum(data_path, concat_by_file=True) + specviz_helper.load_data(data_path, concat_by_file=True) assert len(specviz_helper.app.data_collection) == 41 def test_plot_uncertainties(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) specviz_viewer = specviz_helper.app.get_viewer("spectrum-viewer") @@ -417,7 +416,7 @@ def test_plugin_user_apis(specviz_helper): def test_data_label_as_posarg(specviz_helper, spectrum1d): # Passing in data_label keyword as posarg. - specviz_helper.load_spectrum(spectrum1d, 'my_spec') + specviz_helper.load_data(spectrum1d, 'my_spec') assert specviz_helper.app.data_collection[0].label == 'my_spec' @@ -432,8 +431,8 @@ def test_spectra_partial_overlap(specviz_helper): flux_2 = ([60] * wave_2.size) * u.nJy sp_2 = Spectrum1D(flux=flux_2, spectral_axis=wave_2) - specviz_helper.load_spectrum(sp_1, data_label='left') - specviz_helper.load_spectrum(sp_2, data_label='right') + specviz_helper.load_data(sp_1, data_label='left') + specviz_helper.load_data(sp_2, data_label='right') # Test mouseover outside of left but in range for right. # Should show right spectrum even when mouse is near left flux. diff --git a/jdaviz/conftest.py b/jdaviz/conftest.py index d56ecf77c0..3f5b9d7f2e 100644 --- a/jdaviz/conftest.py +++ b/jdaviz/conftest.py @@ -180,7 +180,7 @@ def multi_order_spectrum_list(spectrum1d, spectral_orders=10): def _create_spectrum1d_cube_with_fluxunit(fluxunit=u.Jy, shape=(2, 2, 4), with_uncerts=False): - + # nz=2 nx=2 ny=4 flux = np.arange(np.prod(shape)).reshape(shape) * fluxunit wcs_dict = {"CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CTYPE3": "WAVE-LOG", "CRVAL1": 205, "CRVAL2": 27, "CRVAL3": 4.622e-7, diff --git a/jdaviz/core/data_formats.py b/jdaviz/core/data_formats.py index c26104c663..6ede5130a9 100644 --- a/jdaviz/core/data_formats.py +++ b/jdaviz/core/data_formats.py @@ -293,7 +293,7 @@ def open(filename, show=True, **kwargs): show : bool Determines whether to immediately show the application - All other arguments are interpreted as load_data/load_spectrum arguments for + All other arguments are interpreted as load_data arguments for the autoidentified configuration class Returns @@ -312,10 +312,7 @@ def open(filename, show=True, **kwargs): # Load data data = hdul if (hdul is not None) else filename - if helper_str == "specviz": - viz_helper.load_spectrum(data, **kwargs) - else: - viz_helper.load_data(data, **kwargs) + viz_helper.load_data(data, **kwargs) # Display app if show: diff --git a/jdaviz/core/helpers.py b/jdaviz/core/helpers.py index 986547f28b..7a021d6421 100644 --- a/jdaviz/core/helpers.py +++ b/jdaviz/core/helpers.py @@ -554,7 +554,7 @@ def _handle_display_units(data, use_display_units): return _handle_display_units(data, use_display_units) - def get_data(self, data_label=None, cls=None, use_display_units=False): + def get_data(self, data_label=None, cls=None, use_display_units=False, **kwargs): """ Returns data with name equal to data_label of type cls. @@ -564,18 +564,20 @@ def get_data(self, data_label=None, cls=None, use_display_units=False): Provide a label to retrieve a specific data set from data_collection. cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. - use_display_units: bool, optional + use_display_units : bool, optional Whether to convert to the display units defined in the plugin. + kwargs : dict + For Cubeviz, you could also pass in ``function`` (str) to collapse + the cube into 1D spectrum using provided function. Returns ------- data : cls - Data is returned as type cls. + Data is returned as type ``cls``. """ - return self._get_data(data_label=data_label, spatial_subset=None, - spectral_subset=None, function=None, - cls=None, use_display_units=use_display_units) + return self._get_data(data_label=data_label, + cls=cls, use_display_units=use_display_units, **kwargs) class ImageConfigHelper(ConfigHelper): diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py new file mode 100644 index 0000000000..f079b72582 --- /dev/null +++ b/jdaviz/core/launcher.py @@ -0,0 +1,51 @@ +import ipyvuetify as v +from ipywidgets import jslink + +from jdaviz import configs as jdaviz_configs +from jdaviz.core.data_formats import open as jdaviz_open + + +def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']): + main = v.Sheet(class_="mx-4", _metadata={'mount_id': 'content'}) + main.children = [] + + # Create Intro Row + intro_row = v.Row() + welcome_text = v.Html(tag='h1', attributes={'title': 'a title'}, + children=['Welcome to Jdaviz']) + intro_row.children = [welcome_text] + + # Filepath row + filepath_row = v.Row() + text_field = v.TextField(label="File Path", v_model=None) + + def load_file(filepath): + if filepath: + helper = jdaviz_open(filepath, show=False) + main.children = [helper.app] + + open_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary", + children=[v.Icon(children=["mdi-upload"])]) + open_data_btn.on_event('click', lambda btn, event, data: load_file(btn.value)) + jslink((text_field, 'v_model'), (open_data_btn, 'value')) + + filepath_row.children = [text_field, open_data_btn] + + # Config buttons + def create_config(config): + viz_class = getattr(jdaviz_configs, config.capitalize()) + main.children = [viz_class().app] + + btns = [] + for config in configs: + config_btn = v.Btn(class_="ma-2", outlined=True, color="primary", + children=[config.capitalize()]) + config_btn.on_event('click', lambda btn, event, data: create_config(btn.children[0])) + btns.append(config_btn) + + # Create button row + btn_row = v.Row() + btn_row.children = btns + main.children = [intro_row, filepath_row, btn_row] + + return main diff --git a/jdaviz/core/template_mixin.py b/jdaviz/core/template_mixin.py index 5bc71b4e57..1b8fece455 100644 --- a/jdaviz/core/template_mixin.py +++ b/jdaviz/core/template_mixin.py @@ -1067,7 +1067,7 @@ def _update_has_subregions(self): def selected_obj(self): if self.selected in self.manual_options or self.selected not in self.labels: return None - # NOTE: we use reference names here instead of IDs since get_subsets_from_viewer requires + # NOTE: we use reference names here instead of IDs since get_subsets requires # that. For imviz, this will mean we won't be able to loop through each of the viewers, # but the original viewer should have access to all the subsets. for viewer_ref in self.viewer_refs: diff --git a/jdaviz/core/tests/test_data_menu.py b/jdaviz/core/tests/test_data_menu.py index f092c2994c..40cf1c0c41 100644 --- a/jdaviz/core/tests/test_data_menu.py +++ b/jdaviz/core/tests/test_data_menu.py @@ -29,11 +29,11 @@ def test_data_menu_toggles(specviz_helper, spectrum1d): # load 2 data entries - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") app = specviz_helper.app sv = app.get_viewer('spectrum-viewer') new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 - specviz_helper.load_spectrum(new_spec, data_label="test2") + specviz_helper.load_data(new_spec, data_label="test2") # check that both are enabled in the data menu selected_data_items = app._viewer_item_by_id('specviz-0')['selected_data_items'] diff --git a/jdaviz/core/tests/test_helpers.py b/jdaviz/core/tests/test_helpers.py index 6b32860f30..d651a2260c 100644 --- a/jdaviz/core/tests/test_helpers.py +++ b/jdaviz/core/tests/test_helpers.py @@ -37,8 +37,8 @@ def setup_class(self, specviz_helper, spectrum1d, multi_order_spectrum_list): self.spec2 = spectrum1d._copy(spectral_axis=spectrum1d.spectral_axis+1000*u.AA) self.label2 = "Test 1D Spectrum 2" - self.spec_app.load_spectrum(spectrum1d, data_label=self.label) - self.spec_app.load_spectrum(self.spec2, data_label=self.label2) + self.spec_app.load_data(spectrum1d, data_label=self.label) + self.spec_app.load_data(self.spec2, data_label=self.label2) # Add 3 subsets to cover different parts of spec and spec2 self.spec_app.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6000, 6500)) diff --git a/jdaviz/core/tests/test_template_mixin.py b/jdaviz/core/tests/test_template_mixin.py index 3523896965..3e251f2d82 100644 --- a/jdaviz/core/tests/test_template_mixin.py +++ b/jdaviz/core/tests/test_template_mixin.py @@ -11,7 +11,7 @@ def test_spectralsubsetselect(specviz_helper, spectrum1d): mask = spectrum1d.flux < spectrum1d.flux.mean() spectrum1d.mask = mask - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) sv = specviz_helper.app.get_viewer('spectrum-viewer') # create a "Subset 1" entry sv.apply_roi(XRangeROI(6500, 7400)) diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py index f86618d77e..33aa315f20 100644 --- a/jdaviz/core/tools.py +++ b/jdaviz/core/tools.py @@ -9,8 +9,9 @@ HomeTool, BqplotPanZoomMode, BqplotPanZoomXMode, BqplotPanZoomYMode, BqplotRectangleMode, BqplotCircleMode, - BqplotEllipseMode, BqplotXRangeMode, - BqplotYRangeMode, BqplotSelectionTool, + BqplotEllipseMode, BqplotCircularAnnulusMode, + BqplotXRangeMode, BqplotYRangeMode, + BqplotSelectionTool, INTERACT_COLOR) from bqplot.interacts import BrushSelector, BrushIntervalSelector @@ -18,13 +19,14 @@ __all__ = [] -ICON_DIR = os.path.join(os.path.dirname(__file__), '..', 'data', 'icons') +ICON_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'data', 'icons')) # Override icons for built-in tools from glue-jupyter BqplotRectangleMode.icon = os.path.join(ICON_DIR, 'select_xy.svg') BqplotCircleMode.icon = os.path.join(ICON_DIR, 'select_circle.svg') BqplotEllipseMode.icon = os.path.join(ICON_DIR, 'select_ellipse.svg') +BqplotCircularAnnulusMode.icon = os.path.join(ICON_DIR, 'select_annulus.svg') BqplotXRangeMode.icon = os.path.join(ICON_DIR, 'select_x.svg') BqplotYRangeMode.icon = os.path.join(ICON_DIR, 'select_y.svg') diff --git a/jdaviz/data/icons/select_annulus.svg b/jdaviz/data/icons/select_annulus.svg new file mode 100644 index 0000000000..86863e1456 --- /dev/null +++ b/jdaviz/data/icons/select_annulus.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/jdaviz/jdaviz_cli_launcher.ipynb b/jdaviz/jdaviz_cli_launcher.ipynb new file mode 100644 index 0000000000..a13d733066 --- /dev/null +++ b/jdaviz/jdaviz_cli_launcher.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from jdaviz.core.launcher import show_launcher\n", + "\n", + "show_launcher()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.10 ('envmain': venv)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + }, + "vscode": { + "interpreter": { + "hash": "f917e879dca01012f092e44ceeb72fc316d3b188a12a493299dc2bd49905dadb" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index f0138f082e..612c108fe4 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -53,25 +53,26 @@ class Customviz(Specviz): viz = Customviz() assert viz.app.get_viewer_reference_names() == ['h', 'k'] - viz.load_spectrum(spectrum1d, data_label='example label') - assert not len(viz.app.get_data_from_viewer("h", "non-existent label")) + viz.load_data(spectrum1d, data_label='example label') + with pytest.raises(ValueError): + viz.get_data("non-existent label") def test_duplicate_data_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") dc = specviz_helper.app.data_collection assert dc[0].label == "test" assert dc[1].label == "test (1)" - specviz_helper.load_spectrum(spectrum1d, data_label="test_1") - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test_1") + specviz_helper.load_data(spectrum1d, data_label="test") assert dc[2].label == "test_1" assert dc[3].label == "test (2)" def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + specviz_helper.load_data(spectrum1d, data_label="test[test]") + specviz_helper.load_data(spectrum1d, data_label="test[test]") dc = specviz_helper.app.data_collection assert len(dc) == 2 assert dc[0].label == "test[test]" @@ -105,7 +106,7 @@ def test_unique_name_variations(specviz_helper, spectrum1d): data_label = specviz_helper.app.return_unique_name(None) assert data_label == "Unknown" - specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") + specviz_helper.load_data(spectrum1d, data_label="test[flux]") data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") assert data_label == "test[flux][flux]" @@ -114,8 +115,8 @@ def test_unique_name_variations(specviz_helper, spectrum1d): def test_substring_in_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="M31") - specviz_helper.load_spectrum(spectrum1d, data_label="M32") + specviz_helper.load_data(spectrum1d, data_label="M31") + specviz_helper.load_data(spectrum1d, data_label="M32") data_label = specviz_helper.app.return_data_label("M") assert data_label == "M" @@ -126,17 +127,17 @@ def test_substring_in_label(specviz_helper, spectrum1d): def test_edge_cases(specviz_helper, spectrum1d, data_label): dc = specviz_helper.app.data_collection - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) assert dc[1].label == f"{data_label} (1)" - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) assert dc[2].label == f"{data_label} (2)" def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") + specviz_helper.load_data(spectrum1d, data_label="this used to break (1)") + specviz_helper.load_data(spectrum1d, data_label="this used to break") dc = specviz_helper.app.data_collection assert dc[0].label == "this used to break (1)" assert dc[1].label == "this used to break (2)" diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 7dd56bca67..2c9acbcd80 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -3,9 +3,10 @@ from astropy import units as u from astropy.tests.helper import assert_quantity_allclose from glue.core import Data -from glue.core.roi import CircularROI, EllipticalROI, RectangularROI, XRangeROI +from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI, XRangeROI from glue.core.edit_subset_mode import AndMode, AndNotMode, OrMode, XorMode -from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion +from regions import (PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion, + CircleAnnulusPixelRegion) from numpy.testing import assert_allclose from specutils import SpectralRegion, Spectrum1D @@ -22,8 +23,8 @@ def test_region_from_subset_2d(cubeviz_helper): cubeviz_helper.app.get_viewer('flux-viewer').apply_roi(EllipticalROI(1, 3.5, 1.2, 3.3)) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, EllipsePixelRegion) @@ -99,8 +100,8 @@ def test_region_from_subset_3d(cubeviz_helper): assert subset_plugin._get_value_from_subset_definition(0, "Ymax", key) == 3.3 assert subset_plugin._get_value_from_subset_definition(0, "Angle", key) == 45 - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert_allclose(reg.center.x, 2.75) assert_allclose(reg.center.y, 1.65) @@ -110,8 +111,8 @@ def test_region_from_subset_3d(cubeviz_helper): # Move the rectangle subset_plugin.set_center((3, 2), update=True) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert_allclose(reg.center.x, 3) assert_allclose(reg.center.y, 2) assert_allclose(reg.width, 1.5) @@ -140,7 +141,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): cubeviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(5, 15.5)) - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert len(subsets) == 1 @@ -165,7 +166,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): assert subset_plugin._get_value_from_subset_definition(0, "Lower bound", key) == 10 assert subset_plugin._get_value_from_subset_definition(0, "Upper bound", key) == 15.5 - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert_quantity_allclose(reg.lower, 10.0 * u.Hz) @@ -173,7 +174,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): # Move the Subset. subset_plugin.set_center(10, update=True) - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert_quantity_allclose(reg.lower, 7.25 * u.Hz) assert_quantity_allclose(reg.upper, 12.75 * u.Hz) @@ -200,7 +201,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert len([m for m in spectrum_viewer.figure.marks if isinstance(m, ShadowSpatialSpectral)]) == 1 # noqa - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert len(subsets) == 1 @@ -209,8 +210,8 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert_quantity_allclose(reg.lower, 5.0 * u.Hz) assert_quantity_allclose(reg.upper, 15.5 * u.Hz) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer', subset_type='spatial') - reg = subsets.get('Subset 2') + subsets = cubeviz_helper.app.get_subsets(spatial_only=True) + reg = subsets.get('Subset 2')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, RectanglePixelRegion) @@ -316,7 +317,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper): 'subset_state': reg[-1]['subset_state']} cubeviz_helper.app.session.edit_subset_mode.mode = OrMode - viewer.apply_roi(EllipticalROI(30, 30, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6)) reg = cubeviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30), width=6, height=12, angle=0.0 * u.deg) @@ -368,7 +369,7 @@ def test_composite_region_with_consecutive_and_not_states(cubeviz_helper): 'subset_state': reg[-1]['subset_state']} cubeviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(EllipticalROI(30, 30, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6)) reg = cubeviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30), width=6, height=12, angle=0.0 * u.deg) @@ -413,7 +414,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): arr = np.ones((10, 10)) data_label = 'image-data' - viewer = imviz_helper.app.get_viewer('imviz-0') + viewer = imviz_helper.default_viewer imviz_helper.load_data(arr, data_label=data_label, show_in_viewer=True) viewer.apply_roi(CircularROI(xc=5, yc=5, radius=2)) reg = imviz_helper.app.get_subsets("Subset 1") @@ -422,7 +423,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): 'subset_state': reg[-1]['subset_state']} imviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(RectangularROI(2, 4, 2, 4)) + viewer.apply_roi(RectangularROI(xmin=2, xmax=4, ymin=2, ymax=4)) reg = imviz_helper.app.get_subsets("Subset 1") rectangle1 = RectanglePixelRegion(center=PixCoord(x=3, y=3), width=2, height=2, angle=0.0 * u.deg) @@ -430,17 +431,25 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): 'subset_state': reg[-1]['subset_state']} imviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(EllipticalROI(3, 3, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=3, yc=3, radius_x=3, radius_y=6)) reg = imviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=3, y=3), width=6, height=12, angle=0.0 * u.deg) assert reg[-1] == {'name': 'EllipticalROI', 'glue_state': 'AndNotState', 'region': ellipse1, 'subset_state': reg[-1]['subset_state']} + imviz_helper.app.session.edit_subset_mode.mode = OrMode + viewer.apply_roi(CircularAnnulusROI(xc=5, yc=5, inner_radius=2.5, outer_radius=5)) + reg = imviz_helper.app.get_subsets("Subset 1") + ann1 = CircleAnnulusPixelRegion(center=PixCoord(x=5, y=5), inner_radius=2.5, outer_radius=5) + assert reg[-1] == {'name': 'CircularAnnulusROI', 'glue_state': 'OrState', 'region': ann1, + 'subset_state': reg[-1]['subset_state']} + subset_plugin = imviz_helper.app.get_tray_item_from_name('g-subset-plugin') assert subset_plugin.subset_selected == "Subset 1" - assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI'] - assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState'] + assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI', + 'CircularAnnulusROI'] + assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState', 'OrState'] def test_with_invalid_subset_name(cubeviz_helper): @@ -450,7 +459,7 @@ def test_with_invalid_subset_name(cubeviz_helper): def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6000, 7000)) reg = specviz_helper.app.get_subsets("Subset 1", simplify_spectral=False) @@ -496,7 +505,7 @@ def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6200, 6800)) @@ -545,7 +554,7 @@ def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): def test_edit_composite_spectral_with_xor(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 6600)) @@ -565,7 +574,7 @@ def test_edit_composite_spectral_with_xor(specviz_helper, spectrum1d): def test_overlapping_spectral_regions(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 7400)) @@ -584,7 +593,7 @@ def test_overlapping_spectral_regions(specviz_helper, spectrum1d): def test_only_overlapping_spectral_regions(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 6600)) diff --git a/notebooks/CubevizExample.ipynb b/notebooks/CubevizExample.ipynb index ac78ed88e7..af16b3998f 100644 --- a/notebooks/CubevizExample.ipynb +++ b/notebooks/CubevizExample.ipynb @@ -155,7 +155,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = cubeviz.app.get_data_from_viewer('flux-viewer', 'contents[FLUX]')" + "data = cubeviz.get_data('jw02732-o004_t004_miri_ch1-shortmediumlong_s3d.fits[SCI]')" ] }, { @@ -187,7 +187,7 @@ "metadata": {}, "outputs": [], "source": [ - "spectra_dict = cubeviz.specviz.get_spectra()\n", + "spectra_dict = cubeviz.specviz.get_spectra(apply_slider_redshift=True)\n", "spectra_dict" ] }, diff --git a/notebooks/ImvizDitheredExample.ipynb b/notebooks/ImvizDitheredExample.ipynb index 8af6aac55c..52c75d05cf 100644 --- a/notebooks/ImvizDitheredExample.ipynb +++ b/notebooks/ImvizDitheredExample.ipynb @@ -301,7 +301,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', 'acs_47tuc_1[SCI,1]')" + "data = imviz.get_data('acs_47tuc_1[SCI,1]')" ] }, { diff --git a/notebooks/ImvizExample.ipynb b/notebooks/ImvizExample.ipynb index b39014d4c5..1841efb1bd 100644 --- a/notebooks/ImvizExample.ipynb +++ b/notebooks/ImvizExample.ipynb @@ -348,7 +348,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', 'jw02727-o002_t062_nircam_clear-f090w_i2d[DATA]')" + "data = imviz.get_data('jw02727-o002_t062_nircam_clear-f090w_i2d[DATA]')" ] }, { diff --git a/notebooks/concepts/cubeviz_data_interactions.ipynb b/notebooks/concepts/cubeviz_data_interactions.ipynb index 862ba92047..fd6b6d0a2f 100644 --- a/notebooks/concepts/cubeviz_data_interactions.ipynb +++ b/notebooks/concepts/cubeviz_data_interactions.ipynb @@ -149,7 +149,7 @@ "source": [ "from specutils import Spectrum1D\n", "\n", - "spec_data = app.get_data_from_viewer('spectrum-viewer')\n", + "spec_data = app.get_viewer('spectrum-viewer').data()\n", "\n", "# The returned data from `get_data` is in list format, as it's \n", "# possible for there to be several data plotted in the viewer\n", @@ -170,7 +170,7 @@ "metadata": {}, "outputs": [], "source": [ - "spec = app.get_data_from_viewer('spectrum-viewer', '6de4c8ee5659e87a302e3de595074ba5[FLUX]')\n", + "spec = app._jdaviz_helper.get_data('6de4c8ee5659e87a302e3de595074ba5[FLUX]', function='sum')\n", "spec" ] }, @@ -267,7 +267,7 @@ "import matplotlib.pyplot as plt\n", "\n", "# The returned data object is a `CCData` class to represent the 2D nature of the image data.\n", - "image_data = app.get_data_from_viewer('flux-viewer')\n", + "image_data = app.get_viewer('flux-viewer').data()[0]\n", "\n", "f, ax = plt.subplots()\n", "\n", diff --git a/notebooks/concepts/cubeviz_ndarray_gif.ipynb b/notebooks/concepts/cubeviz_ndarray_gif.ipynb index e49026182c..2ea0432750 100644 --- a/notebooks/concepts/cubeviz_ndarray_gif.ipynb +++ b/notebooks/concepts/cubeviz_ndarray_gif.ipynb @@ -17,7 +17,9 @@ "cell_type": "code", "execution_count": null, "id": "0adf0be6", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from jdaviz import Cubeviz" @@ -102,8 +104,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = cubeviz.app.get_data_from_viewer('flux-viewer', 'ordered')\n", - "orig_cube = data.get_object(statistic=None)\n", + "orig_cube = cubeviz.get_data('ordered')\n", "orig_cube.shape # Input was (8, 5, 10) # x, y, z" ] }, @@ -124,7 +125,7 @@ "metadata": {}, "outputs": [], "source": [ - "data_2 = cubeviz.app.get_data_from_viewer('flux-viewer', 'roundtrip_test')\n", + "data_2 = cubeviz.get_data('roundtrip_test')\n", "data_2.shape" ] }, @@ -142,7 +143,9 @@ "cell_type": "code", "execution_count": null, "id": "fab04442", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "filename = 'baby_shark.gif'" @@ -152,7 +155,9 @@ "cell_type": "code", "execution_count": null, "id": "c89c8d5f", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "cubeviz2 = Cubeviz()" @@ -162,7 +167,9 @@ "cell_type": "code", "execution_count": null, "id": "137fdde0", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "cubeviz2.load_data(filename)" @@ -173,17 +180,168 @@ "execution_count": null, "id": "ab4eca75", "metadata": { - "scrolled": false + "tags": [] }, "outputs": [], "source": [ "cubeviz2.show()" ] }, + { + "cell_type": "markdown", + "id": "b288fe1c", + "metadata": {}, + "source": [ + "### But what about roundtripping?\n", + "\n", + "Well, sort of..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22902812", + "metadata": {}, + "outputs": [], + "source": [ + "export_plg = cubeviz2.plugins[\"Export Plot\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b78bbf5a", + "metadata": {}, + "outputs": [], + "source": [ + "# Or you can use the GUI.\n", + "export_plg.save_movie(0, 131, fps=10, filename=\"baby_shark_roundtrip.mp4\")" + ] + }, + { + "cell_type": "markdown", + "id": "4c00ffed", + "metadata": {}, + "source": [ + "See the rendered movie at https://www.youtube.com/watch?v=n8czt1ZQUNk" + ] + }, + { + "cell_type": "markdown", + "id": "baec2fe1", + "metadata": {}, + "source": [ + "### More shark!\n", + "\n", + "Ellie says “more shark”! See the demo at https://www.youtube.com/watch?v=ZTHJfSdmnBA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c68bed4b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "cubeviz2.app.add_data_to_viewer(\"uncert-viewer\", \"baby_shark\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cd9ecd1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg = cubeviz2.plugins[\"Plot Options\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e34310f2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg.viewer = \"flux-viewer\"\n", + "plot_plg.image_color_mode = \"Monochromatic\"\n", + "plot_plg.image_color = \"Red\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "252fe8be", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg.viewer = \"uncert-viewer\"\n", + "plot_plg.image_color_mode = \"Monochromatic\"\n", + "plot_plg.image_color = \"Blue\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82111003", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slice_plg = cubeviz2.plugins[\"Slice\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41de986e-c051-4021-a51e-bbdb6b478505", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slice_plg.slice = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cf924c0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# More shark!\n", + "slice_plg._obj.vue_play_start_stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b212b4fd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Run again to stop.\n", + "slice_plg._obj.vue_play_start_stop()" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "4d1af33f", + "id": "03f27eb1-531f-4509-ba7c-e5a3661a58eb", "metadata": {}, "outputs": [], "source": [] @@ -205,7 +363,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb b/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb index 08b20d5f7d..3b885fa201 100644 --- a/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb +++ b/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb @@ -280,7 +280,7 @@ "outputs": [], "source": [ "# can I access the data back out? \n", - "app.get_data_from_viewer('image-viewer')" + "app.get_viewer('image-viewer').data()" ] }, { diff --git a/notebooks/concepts/imviz_roman_asdf.ipynb b/notebooks/concepts/imviz_roman_asdf.ipynb index 818cd22d0d..7d03ba9886 100644 --- a/notebooks/concepts/imviz_roman_asdf.ipynb +++ b/notebooks/concepts/imviz_roman_asdf.ipynb @@ -202,7 +202,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', imviz.app.data_collection[0].label)" + "data = imviz.get_data(imviz.app.data_collection[0].label)" ] }, { diff --git a/notebooks/concepts/mosviz_concept.ipynb b/notebooks/concepts/mosviz_concept.ipynb index c5372c5d78..d5426ce912 100644 --- a/notebooks/concepts/mosviz_concept.ipynb +++ b/notebooks/concepts/mosviz_concept.ipynb @@ -637,7 +637,7 @@ "source": [ "# Assuming the user has done something with a glue plugin that adds another spectrum to the spectrum view\n", "\n", - "currspec = mosviz.app.get_data_from_viewer('spectrum-viewer', 'spectrum-added-by-plugin')\n", + "currspec = mosviz.get_data('spectrum-added-by-plugin')\n", "\n", "... do something with currspec ..." ] diff --git a/pyproject.toml b/pyproject.toml index 44e46a75a0..585361e9c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "bqplot>=0.12.37", "bqplot-image-gl>=1.4.11", "glue-core>=1.11", - "glue-jupyter>=0.16.3", + "glue-jupyter>=0.17", "echo>=0.5.0", "ipykernel>=6.19.4", "ipyvue>=1.6", @@ -21,12 +21,12 @@ dependencies = [ "ipysplitpanes>=0.1.0", "ipygoldenlayout>=0.3.0", "ipywidgets>=8.0.6", - "voila>=0.4", + "voila>=0.4,<0.5", "pyyaml>=5.4.1", "specutils>=1.9", "specreduce>=1.3.0,<1.4.0", "photutils>=1.4", - "glue-astronomy>=0.9", + "glue-astronomy>=0.10", "asteval>=0.9.23", "idna", "vispy>=0.6.5", @@ -66,6 +66,9 @@ mosviz = "jdaviz.configs.mosviz" imviz = "jdaviz.configs.imviz" [project.optional-dependencies] +all = [ + "opencv-python", +] test = [ "pytest", "pytest-astropy", @@ -106,6 +109,7 @@ jdaviz = [ "configs/*/*/*/*.vue", "configs/*/*.yaml", "jdaviz_cli.ipynb", + "jdaviz_cli_launcher.ipynb", ] "jdaviz.configs.imviz.tests" = [ "data/*", @@ -137,6 +141,8 @@ filterwarnings = [ "ignore::DeprecationWarning:bqscales", "ignore::DeprecationWarning:traittypes", "ignore::DeprecationWarning:voila", + "ignore::DeprecationWarning:asteval", + "ignore::FutureWarning:asteval", "ignore:::specutils.spectra.spectrum1d", ] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..479de7bc31 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,158 @@ +[metadata] +name = jdaviz +author = JDADF Developers +author_email = rosteen@stsci.edu +license = BSD 3-Clause +license_file = LICENSE.rst +url = https://jdaviz.readthedocs.io/en/latest/ +description = Astronomical data analysis development leveraging the Jupyter platform +long_description = file: README.rst +long_description_content_type = text/x-rst +edit_on_github = True +github_project = spacetelescope/jdaviz + +[options] +zip_safe = False +packages = find: +include_package_data = True +python_requires = >=3.8 +setup_requires = setuptools_scm +install_requires = + packaging + astropy>=4.3 + matplotlib + traitlets>=5.0.5 + bqplot>=0.12.36 + bqplot-image-gl>=1.4.11 + glue-core>=1.6.0 + glue-jupyter>=0.15.0 + echo>=0.5.0 + ipykernel>=6.19.4 + ipyvue>=1.6 + ipyvuetify>=1.7.0 + ipysplitpanes>=0.1.0 + ipygoldenlayout>=0.3.0 + ipywidgets>=8 + voila>=0.4 + pyyaml>=5.4.1 + specutils>=1.9 + specreduce>=1.3.0,<1.4.0 + photutils>=1.4 + glue-astronomy>=0.5.1 + asteval>=0.9.23 + idna + # vispy is an indirect dependency, but older vispy's don't play nice with jdaviz install + vispy>=0.6.5 + asdf>=2.14.3 + gwcs>=0.16.1 + regions>=0.6 + scikit-image + sidecar>=0.5.2 + ipypopout>=0.0.11 + astroquery + +[options.extras_require] +test = + pytest + pytest-astropy + pytest-tornasync +docs = + sphinx-rtd-theme + sphinx-astropy + +[options.package_data] +jdaviz = + data/* + data/*/* + *.vue + components/*.vue + configs/*/*/*/*.vue + configs/*/*.yaml + configs/*/*.ipynb +jdaviz.configs.imviz.tests = data/* + +[options.entry_points] +console_scripts = + jdaviz = jdaviz.cli:_main + specviz = jdaviz.cli:_specviz + specviz2d = jdaviz.cli:_specviz2d + imviz = jdaviz.cli:_imviz + cubeviz = jdaviz.cli:_cubeviz + mosviz = jdaviz.cli:_mosviz +gui_scripts = +jdaviz_plugins = + default = jdaviz.configs.default + cubeviz = jdaviz.configs.cubeviz + specviz = jdaviz.configs.specviz + mosviz = jdaviz.configs.mosviz + imviz = jdaviz.configs.imviz + +[tool:pytest] +testpaths = "jdaviz" "docs" +astropy_header = true +doctest_plus = enabled +text_file_format = rst +addopts = --doctest-rst --import-mode=append +filterwarnings = + error + ignore:numpy\.ufunc size changed:RuntimeWarning + ignore:numpy\.ndarray size changed:RuntimeWarning + ignore:Numpy has detected that you:DeprecationWarning + ignore:distutils Version classes are deprecated:DeprecationWarning + ignore:Passing unrecognized arguments to super:DeprecationWarning + ignore:.*With traitlets 4\.1, metadata should be set using the \.tag\(\) method:DeprecationWarning + ignore:Widget.* is deprecated:DeprecationWarning + ignore:.*np\.bool8.*is a deprecated alias for:DeprecationWarning + ignore:.*np\.uint0.*is a deprecated alias for:DeprecationWarning + ignore:.*np\.int0.*is a deprecated alias for:DeprecationWarning + ignore:zmq\.eventloop\.ioloop is deprecated in pyzmq:DeprecationWarning + ignore::DeprecationWarning:glue + ignore::DeprecationWarning:bqplot + ignore::DeprecationWarning:bqplot_image_gl + ignore::DeprecationWarning:bqscales + ignore::DeprecationWarning:traittypes + ignore::DeprecationWarning:voila + ignore:::specutils.spectra.spectrum1d + +[flake8] +max-line-length = 100 +# E123: closing bracket does not match indentation of opening bracket's line +# E126: continuation line over-indented for hanging indent +# E226: missing whitespace around arithmetic operator +# E402: Module level import not at top of file +# W503: line break before binary operator +# W504: line break after binary operator +ignore = E123,E126,E226,E402,W503,W504 + +[coverage:run] +omit = + jdaviz/_astropy_init* + jdaviz/conftest.py + jdaviz/*setup_package* + jdaviz/tests/* + jdaviz/*/tests/* + jdaviz/extern/* + jdaviz/version* + */jdaviz/_astropy_init* + */jdaviz/conftest.py + */jdaviz/*setup_package* + */jdaviz/tests/* + */jdaviz/*/tests/* + */jdaviz/extern/* + */jdaviz/version* + +[coverage:report] +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + # Don't complain about packages we have installed + except ImportError + # Don't complain if tests don't hit assertions + raise AssertionError + raise NotImplementedError + # Don't complain about script hooks + def main\(.*\): + # Ignore branches that don't pertain to this version of Python + pragma: py{ignore_python_version} + # Don't complain about IPython completion helper + def _ipython_key_completions_ diff --git a/standalone/entitlements.plist b/standalone/entitlements.plist new file mode 100644 index 0000000000..2ac1f14b34 --- /dev/null +++ b/standalone/entitlements.plist @@ -0,0 +1,13 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/standalone/hooks/hook-bqplot.py b/standalone/hooks/hook-bqplot.py new file mode 100644 index 0000000000..613900b9eb --- /dev/null +++ b/standalone/hooks/hook-bqplot.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('bqplot') diff --git a/standalone/hooks/hook-debugpy.py b/standalone/hooks/hook-debugpy.py new file mode 100644 index 0000000000..5b7d0813ff --- /dev/null +++ b/standalone/hooks/hook-debugpy.py @@ -0,0 +1,8 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +datas = collect_data_files("debugpy") +# we are picking up debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_linux_amd64.dylib +datas = filter(lambda x: not x[0].endswith('.dylib'), datas) +# binaries = collect_dynamic_libs('omp') + +# breakpoint() \ No newline at end of file diff --git a/standalone/hooks/hook-glue.py b/standalone/hooks/hook-glue.py new file mode 100644 index 0000000000..4941c0920b --- /dev/null +++ b/standalone/hooks/hook-glue.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('glue') +datas += copy_metadata('glue-core') diff --git a/standalone/hooks/hook-glue_jupyter.py b/standalone/hooks/hook-glue_jupyter.py new file mode 100644 index 0000000000..98baed00c2 --- /dev/null +++ b/standalone/hooks/hook-glue_jupyter.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('glue_jupyter') +datas += copy_metadata('glue_jupyter') diff --git a/standalone/hooks/hook-ipypopout.py b/standalone/hooks/hook-ipypopout.py new file mode 100644 index 0000000000..55422b9aa5 --- /dev/null +++ b/standalone/hooks/hook-ipypopout.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('ipypopout') diff --git a/standalone/hooks/hook-jdaviz.py b/standalone/hooks/hook-jdaviz.py new file mode 100644 index 0000000000..c7abff2b5e --- /dev/null +++ b/standalone/hooks/hook-jdaviz.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('jdaviz') +datas += copy_metadata('jdaviz') diff --git a/standalone/hooks/hook-jupyter_client.py b/standalone/hooks/hook-jupyter_client.py new file mode 100644 index 0000000000..6ecd059447 --- /dev/null +++ b/standalone/hooks/hook-jupyter_client.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("jupyter_client") diff --git a/standalone/hooks/hook-mistune.py b/standalone/hooks/hook-mistune.py new file mode 100644 index 0000000000..ac80b6f9e8 --- /dev/null +++ b/standalone/hooks/hook-mistune.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = collect_submodules("mistune") diff --git a/standalone/hooks/hook-photutils.py b/standalone/hooks/hook-photutils.py new file mode 100644 index 0000000000..31bc609d12 --- /dev/null +++ b/standalone/hooks/hook-photutils.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("photutils") +# for CITATION.rst +datas = collect_data_files('photutils') diff --git a/standalone/hooks/hook-regions.py b/standalone/hooks/hook-regions.py new file mode 100644 index 0000000000..9373dbae9a --- /dev/null +++ b/standalone/hooks/hook-regions.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("regions") +# for CITATION.rst +datas = collect_data_files('regions') diff --git a/standalone/hooks/hook-skimage.py b/standalone/hooks/hook-skimage.py new file mode 100644 index 0000000000..3f383b4319 --- /dev/null +++ b/standalone/hooks/hook-skimage.py @@ -0,0 +1,12 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +datas = collect_data_files("skimage", includes=["*.pyi"]) +# osx does not like the .dylib directory with signing +# [('.../site-packages/skimage/.dylibs/libomp.dylib', 'skimage/.dylibs')] +binaries = collect_dynamic_libs('skimage') +if binaries and binaries[0][0].endswith('.dylib'): + assert len(binaries) == 1 + assert binaries[0][0].endswith('.dylibs/libomp.dylib') + binaries = [ + (binaries[0][0], 'skimage'), + ] diff --git a/standalone/jdaviz-cli-entrypoint.py b/standalone/jdaviz-cli-entrypoint.py new file mode 100644 index 0000000000..f77f124eef --- /dev/null +++ b/standalone/jdaviz-cli-entrypoint.py @@ -0,0 +1,24 @@ +import sys + + +def start_as_kernel(): + # similar to https://github.com/astrofrog/voila-qt-app/blob/master/voila_demo.py + import sys + + from ipykernel import kernelapp as app + app.launch_new_instance() + sys.argv = [app.__file__, sys.argv[3:]] + + +if __name__ == "__main__": + # When voila starts a kernel under pyinstaller, it will use sys.executable + # (which is this entry point again) + # if called like [sys.argv[0], "-m", "ipykernel_launcher", ...] + if len(sys.argv) >= 3 and sys.argv[1] == "-m" and sys.argv[2] == "ipykernel_launcher": + # it is important that we do not import jdaviz top level + # as that would cause it to import ipywidgets before the kernel is started + start_as_kernel() + else: + import jdaviz.cli + # should change this to _main, but now it doesn't need arguments + jdaviz.cli.main(layout="") diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec new file mode 100644 index 0000000000..45768f2db4 --- /dev/null +++ b/standalone/jdaviz.spec @@ -0,0 +1,78 @@ +# -*- mode: python ; coding: utf-8 -*- +import sys +from pathlib import Path +import os + +from PyInstaller.building.build_main import Analysis +from PyInstaller.building.api import COLLECT, EXE, PYZ +from PyInstaller.building.osx import BUNDLE + +import jdaviz +codesign_identity = os.environ.get("DEVELOPER_ID_APPLICATION") + +# this copies over the nbextensions enabling json and the js assets +# for all the widgets +datas = [ + (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), + (Path(sys.prefix) / "etc" / "jupyter", "./etc/jupyter"), +] + +block_cipher = None + + +a = Analysis( + ["jdaviz-cli-entrypoint.py"], + pathex=[], + binaries=[], + datas=datas, + hiddenimports=[], + hookspath=["hooks"], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + # executable name: dist/jdaviz/jdaviz-cli + # note: cannot be called jdaviz, because there is a directory called jdaviz + name="jdaviz-cli", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=codesign_identity, + entitlements_file="entitlements.plist", +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + # directory name: dist/jdaviz + name="jdaviz", +) +app = BUNDLE( + exe, + coll, + name="jdaviz.app", + icon=None, + entitlements_file="entitlements.plist", + bundle_identifier="edu.stsci.jdaviz", + version=jdaviz.__version__, +) diff --git a/standalone/test.py b/standalone/test.py new file mode 100644 index 0000000000..fb3276a7fa --- /dev/null +++ b/standalone/test.py @@ -0,0 +1,12 @@ +import re + +from playwright.sync_api import Page, expect + + +def test_voila_basics(page: Page): + page.goto("http://localhost:8866/") + + # basic voila is loaded + page.locator("body.theme-light").wait_for() + # when jdaviz is loaded (button at the top left) + page.locator("text=Import Data").wait_for() diff --git a/tox.ini b/tox.ini index c101fc241b..2ee43453a7 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,8 @@ deps = devdeps: git+https://github.com/spacetelescope/stdatamodels.git devdeps: git+https://github.com/bqplot/bqplot.git@0.12.x devdeps: git+https://github.com/glue-viz/glue.git - devdeps: git+https://github.com/voila-dashboards/voila.git + # FIXME: https://github.com/spacetelescope/jdaviz/pull/2268 + #devdeps: git+https://github.com/voila-dashboards/voila.git devdeps: git+https://github.com/glue-viz/bqplot-image-gl.git devdeps: git+https://github.com/glue-viz/glue-jupyter.git devdeps: git+https://github.com/glue-viz/glue-astronomy.git @@ -64,8 +65,7 @@ deps = extras = test romandeps: roman - # Uncomment when we have all again in setup.cfg - #alldeps: all + alldeps: all commands = devdeps: pip install -U -i https://pypi.anaconda.org/astropy/simple astropy --pre