Repository: spacetelescope/jdaviz

Issue:
Mosviz: nirspec level 2 loader
**Reporter: Camilla Pacifici**

According to the docs, nirspec level 2 should be recognized by mosviz:
https://jdaviz.readthedocs.io/en/latest/mosviz/import_data.html#automatic-directory-loading

But I get this traceback
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[83], line 2
      1 mosviz = Mosviz()
----> 2 mosviz.load_data(file_calx1d, instrument="nirspec")
      3 mosviz.show()

File ~/opt/miniconda3/envs/jdaviz3.4/lib/python3.10/site-packages/jdaviz/configs/mosviz/helper.py:503, in Mosviz.load_data(self, spectra_1d, spectra_2d, images, spectra_1d_label, spectra_2d_label, images_label, *args, **kwargs)
    500     allow_link_table = False
    502 elif spectra_1d:
--> 503     self.load_1d_spectra(spectra_1d, spectra_1d_label)
    504     allow_link_table = False
    506 elif spectra_2d:

File ~/opt/miniconda3/envs/jdaviz3.4/lib/python3.10/site-packages/jdaviz/configs/mosviz/helper.py:608, in Mosviz.load_1d_spectra(self, data_obj, data_labels)
    593 def load_1d_spectra(self, data_obj, data_labels=None):
    594     """
    595     Load and parse a set of 1D spectra objects.
    596 
   (...)
    606         for each item in ``data_obj`` if  ``data_obj`` is a list.
    607     """
--> 608     super().load_data(data_obj, parser_reference="mosviz-spec1d-parser",
    609                       data_labels=data_labels)
    610     self._add_redshift_column()

File ~/opt/miniconda3/envs/jdaviz3.4/lib/python3.10/site-packages/jdaviz/core/helpers.py:109, in ConfigHelper.load_data(self, data, data_label, parser_reference, **kwargs)
    107 if data_label:
    108     kwargs['data_label'] = data_label
--> 109 self.app.load_data(data, parser_reference=parser_reference, **kwargs)

File ~/opt/miniconda3/envs/jdaviz3.4/lib/python3.10/site-packages/jdaviz/app.py:546, in Application.load_data(self, file_obj, parser_reference, **kwargs)
    543         parser = data_parser_registry.members.get(data_parser)
    545 if parser is not None:
--> 546     parser(self, file_obj, **kwargs)
    547 else:
    548     self._application_handler.load_data(file_obj)

File ~/opt/miniconda3/envs/jdaviz3.4/lib/python3.10/site-packages/jdaviz/configs/mosviz/plugins/parsers.py:237, in mos_spec1d_parser(app, data_obj, data_labels, table_viewer_reference_name)
    233 with app.data_collection.delay_link_manager_update():
    235     for i, (cur_data, cur_label) in enumerate(zip(data_obj, data_labels)):
    236         # Make metadata layout conform with other viz.
--> 237         cur_data.meta = standardize_metadata(cur_data.meta)
    238         cur_data.meta['mosviz_row'] = i
    240         app.add_data(cur_data, cur_label, notify_done=False)

AttributeError: 'str' object has no attribute 'meta'

A single pair cal/x1d can be found here: https://stsci.box.com/s/903uu5lgafp3hg3ayac9efyhu1lrs57n
(Data are publicly available)

 --- 

 DISCLAIMER: This issue was autocreated by the Jdaviz Issue Creation Bot on behalf of the reporter. If any information is incorrect, please contact [Duy Nguyen](https://github.com/duytnguyendtn)

Discussion:
camipacifici: Thank you very much. This fixes my original problem.
I see three additional problems here:
- the slit overlay is likely wrong (and this is probably not a jdaviz problem...lets just ignore this for now)
- the table is returning `Unspecified` for all the sources under the RA and Dec columns
- the position where the image viewer is zooming to is different from the position given by the SRCRA and SRCDEC keywords. I am not 100% positive that these keywords are the right ones, but maybe this can be double checked with the NIRISS parser that I am sure is correct.
camipacifici: Would be nice to fix the image loader for this one too:
https://github.com/spacetelescope/jdaviz/issues/2144
pllim: > the table is returning Unspecified for all the sources under the RA and Dec columns

For some reason, it is trying to look for RA and Dec on the image, not spectra. Not sure why. I added a fix.

> the position where the image viewer is zooming to is different from the position given by the SRCRA and SRCDEC keywords

Hard for me to test using the cutout off the 2GB image you provided. If this is still a problem, please be more specific on what the correct answer should be. In NIRISS, I see that the parser grab RA and Dec off a special ECSV catalog that NIRSpec does not have, so I cannot just use the NIRISS logic. And are you sure this is not the same problem as S_REGION? These are all populated by the pipeline.

> Would be nice to fix the image loader

I also added a fix for this. Please test your use case in #2144 with this PR too.
camipacifici: The table is nicely populated, but if I try to load the image I still get the same traceback that ends with
ValueError: The dimensions of component 2D Spectra are incompatible with the dimensions of this data: (50,) vs (1,)

This is the way I am calling it:
`mosviz = Mosviz()`
`mosviz.load_data(spectra_1d="../data/mastDownload/jones_calx1d_files/single/jw02136001001_03137_00001_nrs1_x1d.fits",
                 spectra_2d="../data/mastDownload/jones_s2d_level2/jw02136001001_03137_00001_nrs1_s2d.fits",
                 images="/Users/cpacifici/Dropbox/SINCRONIZZARE/Documents/WORK/CANDELS/hlsp_candels_hst_wfc3_egs-tot-60mas_f160w_v1.0_drz.fits",
                 instrument="nirspec")`
`mosviz.show()`
pllim: > The image viewer is not zooming on SRCRA and SRCDEC

I see the same thing but I dunno what the real problem is. The image emits a lot of WCS warnings when you load it. I think its WCS might not be entirely correct.

Also I don't know if SRCRA and SRCDEC suffer from the same problem as S_REGION since they are all populated by the same pipeline; have you heard back from NIRSpec/pipeline about this?

If you have another image that is more well-behaved and not 2GB, we can try to test this but on that image to be sure.
pllim: > what was the way that worked for you

I was loading using directory, having moved the image under some sub-dir that it looks for. I did not try to load all 3 components separately and when I did, I could reproduce your problem (and then fix it).
pllim: Hmm I just found the code that zoom on shared image. I forgot this is not Imviz, so maybe that is a real bug, lemme see.
camipacifici: I cannot find a decent image the covers the appropriate region sorry.
If you think there is a bug, I will test any fix you might have.
Thank you!
pllim: I think I did find a bug, where I missed that I have to set a private attribute in one of the logic route. 🤪 

There are several things going on at once with your slit and your target coordinates:

* The image WCS is not great. I had to manually remove the offending SIP coefficients (header keywords that starts with `A_` or `B_`) in the image file. Otherwise, the sky/pix transformation is suspicious and full of ~terror~ warnings.

```python
pf = fits.open(filename, mode="update")
for k in list(pf[0].header):
    if k.startswith('A_') or k.startswith('B_'):
        pf[0].header.remove(k)
pf.close()
```

* The image WCS is defined in FK5 but pretty sure JWST pipeline only knows ICRS, so I dunno, your pix/sky/pix/sky conversions might get confused in Jdaviz. Hopefully it is close enough that your target will still be in view. 🤷 

* As you know, the slit positions are wrong and unrelated to this PR, so I cannot rely on that to see how well centering is doing, so I had to make Mosviz image viewer inherit things from Astrowidgets (I think I feel Kyle's eyebrows twitching) so I can do the following sanity check:

```python
imview = mosviz.app.get_viewer(mosviz._default_image_viewer_reference_name)
imview._mark_targets()
```

So, with my new commits to this PR, you will be able to see that NIRSpec can now zoom to the correct target on table click (it is obvious after you use that hidden method to add markers). And that the slits are off. Since your cutout that I am using has the image data way off to a distance, I cannot see if the RA and Dec is actually marking any real targets, but at least it zooms to what the table reports.

I have a hard time finding good data for different instruments to this PR with. And there are way too many combos to test for each. And for some reason, Mosviz loader is very slow on my machine. So, I think you and other devs need to review this carefully even if it works for your particular NIRSpec case.
camipacifici: It works!! Thank you for the snippet to fix the image too.
<img width="749" alt="Screen Shot 2023-04-13 at 10 03 01 PM" src="https://user-images.githubusercontent.com/19998720/231923002-29321a18-5c26-47bb-a877-2c4090e5d4bb.png">

camipacifici: I will test with other datasets I can find and let you know how it goes.
camipacifici: You are right!! My mistake, the image is there and the zoom is doing what it is supposed to, i.e. zooming using the number of pixels in the cross dispersion direction in the 2D image. That is apparently too small, but it not a jdaviz problem. Also the fact that the RA/Dec are not in the header is not a jdaviz problem.

For the record, I did not have to put the image under an `images` directory. I just included it in the folder, like for NIRISS, so docs are also correct.

Sorry for the false alarm! This looks good to me!
pllim: Failures are unrelated.
rosteen: I get a test failure that appears related when I run them locally, from `test_nirspec_parser[nirspec]`:

```
>       assert len(mosviz_helper.app.data_collection) == 16
E       AssertionError: assert 11 == 16
E        +  where 11 = len(DataCollection (11 data sets)\n	  0: MOS Table\n	  1: 1D Spectrum 0\n	  2: 1D Spectrum 1\n	  3: 1D Spectrum 2\n	  4: 1D Spe...	  5: 1D Spectrum 4\n	  6: 2D Spectrum 0\n	  7: 2D Spectrum 1\n	  8: 2D Spectrum 2\n	  9: 2D Spectrum 3\n	 10: 2D Spectrum 4)
E        +    where DataCollection (11 data sets)\n	  0: MOS Table\n	  1: 1D Spectrum 0\n	  2: 1D Spectrum 1\n	  3: 1D Spectrum 2\n	  4: 1D Spe...	  5: 1D Spectrum 4\n	  6: 2D Spectrum 0\n	  7: 2D Spectrum 1\n	  8: 2D Spectrum 2\n	  9: 2D Spectrum 3\n	 10: 2D Spectrum 4 = Application(config='mosviz', events=['call_viewer_method', 'close_snackbar_message', 'data_item_remove', 'data_item_un...erflow hidden will prevent the scrollbar from\n   * appearing */\n  overflow: hidden;\n}\n</style>\n'), vdocs='latest').data_collection
E        +      where Application(config='mosviz', events=['call_viewer_method', 'close_snackbar_message', 'data_item_remove', 'data_item_un...erflow hidden will prevent the scrollbar from\n   * appearing */\n  overflow: hidden;\n}\n</style>\n'), vdocs='latest') = <jdaviz.configs.mosviz.helper.Mosviz object at 0x13f810550>.app

configs/mosviz/tests/test_parsers.py:38: AssertionError
```

I assume that you weren't seeing this locally, any idea what might be going wrong?
pllim: @rosteen , I seen that before. I think some data was not loaded properly. I thought I fixed that. Are you testing an older version of my branch? Did you try clearing the download cache?
pllim: @rosteen and @duytnguyendtn , is this okay to merge now? Thanks for the reviews!

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index f30fa0f2..67c32ed7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -30,6 +30,9 @@ Imviz
 Mosviz
 ^^^^^^
 
+- NIRSpec automatic loader now can take a single image as input, instead of requiring
+  the number of cutouts to be the same as the number of 1D spectra. [#2146]
+
 Specviz
 ^^^^^^^
 
@@ -69,6 +72,8 @@ Imviz
 Mosviz
 ^^^^^^
 
+- Fixed several data loader bugs for uncommon use cases. [#2146]
+
 Specviz
 ^^^^^^^
 
diff --git a/docs/mosviz/import_data.rst b/docs/mosviz/import_data.rst
index 95f78a3a..74621ca5 100644
--- a/docs/mosviz/import_data.rst
+++ b/docs/mosviz/import_data.rst
@@ -4,40 +4,22 @@
 Importing Data into Mosviz
 **************************
 
-Mosviz provides two different ways to load data: auto-recognition directory
-loading and manual loading.
+Mosviz provides two different ways to load data:
 
-Automatic Directory Loading
----------------------------
-Mosviz provides instrument-specific directory parsers for select instruments. At this
-time, Mosviz supports automatic parsing for the following instruments:
-
-* JWST NIRSpec (levels 2 and 3)
-* JWST NIRISS
-* JWST NIRCam
-
-The NIRSpec parser expects a directory with either level 2 files:
-
-* ``*_cal.fits`` : Single file containing level 2 2D spectra for all objects.
-* ``*_x1d.fits`` : Single file containing level 2 1D spectra for all objects.
+* :ref:`mosviz-import-auto-dir`
+* :ref:`mosviz-import-manual-loading`
 
-or level 3 files:
-
-* ``*_s2d.fits`` : N files containing level 3 2D spectra, where N is the number of objects.
-* ``*_x1d.fits`` : N files containing level 3 1D spectra, where N is the number of objects.
-
-In either the level 2 or 3 case, the NIRSpec data directory may contain a sub-directory
-named ``images``, ``cutouts``, or ``mosviz_cutouts``. This sub-directory should contain FITS files
-containing images corresponding to each target, which may be sourced from a non-JWST telescope.
+.. _mosviz-import-auto-dir:
 
-The NIRISS parser expects a directory with the following types of files:
+Automatic Directory Loading
+===========================
 
-* ``*_i2d.fits`` : Level 3 2D images from the ``calwebb_image3`` imaging pipeline
-* ``*_cat.ecsv`` : Level 3 source catalog from the ``calwebb_image3`` imaging pipeline **(For best performance, it's recommended that your directory only contain one.)**
-* ``*_cal.fits`` : Level 2 2D spectra in vertical (R) and horizontal (C) orientations from the ``calwebb_spec2`` spectroscopic pipeline *(C spectra are shown first in 2D viewer by default.)*
-* ``*_x1d.fits`` : Level 2 1D spectra in vertical (R) and horizontal (C) orientations from the ``calwebb_spec2`` spectroscopic pipeline *(C spectra are shown first in 1D viewer by default.)*
+Mosviz provides instrument-specific directory parsers for select instruments. At this
+time, Mosviz supports automatic parsing for the following instruments:
 
-The NIRCam parser expects ``*_cal.fits`` and ``*_x1d`` files in the same format as the NIRISS parser.
+* :ref:`mosviz-import-auto-dir-nirspec`
+* :ref:`mosviz-import-auto-dir-niriss`
+* :ref:`mosviz-import-auto-dir-nircam`
 
 In a Jupyter context (notebook or Lab), you must specify the instrument with a directory
 as such:
@@ -67,13 +49,53 @@ and for NIRISS:
 
     jdaviz mosviz /path/to/my/data --instrument=niriss
 
-Specifying a data directory is required to start Mosviz from the command line.
-
-If a directory is input in either case without specifying an instrument, Mosviz will
+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
 raise an error.
 
+.. _mosviz-import-auto-dir-nirspec:
+
+JWST NIRSpec (levels 2 and 3)
+-----------------------------
+
+The NIRSpec parser expects a directory with either level 2 files:
+
+* ``*_cal.fits`` : Single file containing level 2 2D spectra for all objects.
+* ``*_x1d.fits`` : Single file containing level 2 1D spectra for all objects.
+
+or level 3 files:
+
+* ``*_s2d.fits`` : N files containing level 3 2D spectra, where N is the number of objects.
+* ``*_x1d.fits`` : N files containing level 3 1D spectra, where N is the number of objects.
+
+In either the level 2 or 3 case, the NIRSpec data directory may contain a sub-directory
+named ``images``, ``cutouts``, or ``mosviz_cutouts``. This sub-directory should contain FITS files
+containing images corresponding to each target, which may be sourced from a non-JWST telescope.
+If it only contains a single image, the same image would be used for all the spectra.
+
+.. _mosviz-import-auto-dir-niriss:
+
+JWST NIRISS
+-----------
+
+The NIRISS parser expects a directory with the following types of files:
+
+* ``*_i2d.fits`` : Level 3 2D images from the ``calwebb_image3`` imaging pipeline
+* ``*_cat.ecsv`` : Level 3 source catalog from the ``calwebb_image3`` imaging pipeline **(For best performance, it's recommended that your directory only contain one.)**
+* ``*_cal.fits`` : Level 2 2D spectra in vertical (R) and horizontal (C) orientations from the ``calwebb_spec2`` spectroscopic pipeline *(C spectra are shown first in 2D viewer by default.)*
+* ``*_x1d.fits`` : Level 2 1D spectra in vertical (R) and horizontal (C) orientations from the ``calwebb_spec2`` spectroscopic pipeline *(C spectra are shown first in 1D viewer by default.)*
+
+.. _mosviz-import-auto-dir-nircam:
+
+JWST NIRCam
+-----------
+
+The NIRCam parser expects ``*_cal.fits`` and ``*_x1d`` files in the same format as the NIRISS parser.
+
+.. _mosviz-import-manual-loading:
+
 Manual Loading
---------------
+==============
 
 If an automatic parser is not provided yet for your data, Mosviz provides manual loading by
 specifying which files are which, and the associations between them. This is done by
@@ -95,5 +117,17 @@ directory that contains all the files for the dataset to be loaded:
     spectra_1d = ['target1_1d.fits', 'target2_1d.fits']
     spectra_2d = ['target1_2d.fits', 'target2_2d.fits']
     images = ['target1_img.fits', 'target2_img.fits']
-    mosviz.load_data(spectra_1d, spectra_2d, images)
+    mosviz.load_data(spectra_1d=spectra_1d, spectra_2d=spectra_2d, images=images)
+    mosviz.show()
+
+Alternatively, if you want all the spectra to share a single image (e.g., a mosaic):
+
+.. code-block:: python
+
+    from jdaviz import Mosviz
+    mosviz = Mosviz()
+    spectra_1d = ['target1_1d.fits', 'target2_1d.fits']
+    spectra_2d = ['target1_2d.fits', 'target2_2d.fits']
+    image = 'mymosaic.fits'
+    mosviz.load_data(spectra_1d=spectra_1d, spectra_2d=spectra_2d, images=image)
     mosviz.show()
diff --git a/jdaviz/configs/mosviz/helper.py b/jdaviz/configs/mosviz/helper.py
index 68afb8e8..eefc35a9 100644
--- a/jdaviz/configs/mosviz/helper.py
+++ b/jdaviz/configs/mosviz/helper.py
@@ -1,3 +1,4 @@
+import os
 import warnings
 from copy import deepcopy
 from pathlib import Path
@@ -18,7 +19,8 @@ from jdaviz.configs.specviz import Specviz
 from jdaviz.configs.specviz.helper import _apply_redshift_to_spectra
 from jdaviz.configs.specviz2d import Specviz2d
 from jdaviz.configs.mosviz.plugins import jwst_header_to_skyregion
-from jdaviz.configs.mosviz.plugins.parsers import FALLBACK_NAME
+from jdaviz.configs.mosviz.plugins.parsers import (
+    FALLBACK_NAME, mos_spec1d_parser, mos_spec2d_parser)
 from jdaviz.configs.default.plugins.line_lists.line_list_mixin import LineListMixin
 
 __all__ = ['Mosviz']
@@ -288,31 +290,32 @@ class Mosviz(ConfigHelper, LineListMixin):
     def _handle_image_zoom(self, msg):
         mos_data = self.app.data_collection['MOS Table']
 
-        # trigger zooming the image, if there is an image
-        if mos_data.find_component_id("Images") is not None:
-            if msg.shared_image:
-                center, height = self._zoom_to_object_params(msg)
-            else:
-                try:
-                    center, height = self._zoom_to_slit_params(msg)
-                except IndexError:
-                    # If there's nothing in the spectrum2d viewer, we can't get slit info
-                    return
-
-            if center is None or height is None:
-                # Can't zoom if we couldn't figure out where to zoom (e.g. if RA/Dec not in table)
-                return
+        if mos_data.find_component_id("Images") is None:
+            return
 
-            imview = self.app.get_viewer(self._default_image_viewer_reference_name)
+        imview = self.app.get_viewer(self._default_image_viewer_reference_name)
+
+        # trigger zooming the image, if there is an image
+        if msg.shared_image:
+            center, height = self._zoom_to_object_params(msg)
+        else:
+            center, height = self._zoom_to_slit_params(msg)
 
-            image_axis_ratio = ((imview.axis_x.scale.max - imview.axis_x.scale.min) /
-                                (imview.axis_y.scale.max - imview.axis_y.scale.min))
+        if height is not None:
+            image_axis_ratio = (abs(imview.state.x_max - imview.state.x_min) /
+                                abs(imview.state.y_max - imview.state.y_min))
+            x_height = image_axis_ratio * height
+            cur_xcen = (imview.state.x_min + imview.state.x_max) * 0.5
+            cur_ycen = (imview.state.y_min + imview.state.y_max) * 0.5
 
             with delay_callback(imview.state, 'x_min', 'x_max', 'y_min', 'y_max'):
-                imview.state.x_min = center[0] - image_axis_ratio*height
-                imview.state.y_min = center[1] - height
-                imview.state.x_max = center[0] + image_axis_ratio*height
-                imview.state.y_max = center[1] + height
+                imview.state.x_min = cur_xcen - x_height
+                imview.state.y_min = cur_ycen - height
+                imview.state.x_max = cur_xcen + x_height
+                imview.state.y_max = cur_ycen + height
+
+        if center is not None:
+            imview.center_on(center)
 
     def _handle_flipped_data(self):
         # Workaround for flipped data
@@ -325,7 +328,6 @@ class Mosviz(ConfigHelper, LineListMixin):
     def _zoom_to_object_params(self, msg):
 
         table_data = self.app.data_collection['MOS Table']
-        imview = self.app.get_viewer(self._default_image_viewer_reference_name)
         specview = self.app.get_viewer(self._default_spectrum_2d_viewer_reference_name)
 
         if ("R.A." not in table_data.component_ids() or
@@ -338,12 +340,16 @@ class Mosviz(ConfigHelper, LineListMixin):
         if (ra == FALLBACK_NAME) or (dec == FALLBACK_NAME):
             return None, None
 
-        pixel_height = 0.5*(specview.axis_y.scale.max - specview.axis_y.scale.min)
-        point = SkyCoord(ra*u.deg, dec*u.deg)
-
-        pix = imview.layers[0].layer.coords.world_to_pixel(point)
+        try:
+            pixel_height = abs(specview.axis_y.scale.max - specview.axis_y.scale.min) * 0.5
+        except Exception:
+            pixel_height = None
+        else:
+            if pixel_height < 1:
+                pixel_height = None
+        sky = SkyCoord(ra, dec, unit='deg')
 
-        return pix, pixel_height
+        return sky, pixel_height
 
     def _zoom_to_slit_params(self, msg):
         imview = self.app.get_viewer(self._default_image_viewer_reference_name)
@@ -351,20 +357,15 @@ class Mosviz(ConfigHelper, LineListMixin):
 
         try:
             sky_region = jwst_header_to_skyregion(specview.layers[0].layer.meta)
-        except KeyError:
+        except Exception:
             # If the header didn't have slit params, can't zoom to it.
             return None, None
-        ra = sky_region.center.ra.deg
-        dec = sky_region.center.dec.deg
 
-        pix = imview.layers[0].layer.coords.world_to_pixel(sky_region.center)
-
-        # Height of slit in decimal degrees
-        height = sky_region.height.deg
-
-        upper = imview.layers[0].layer.coords.world_to_pixel(SkyCoord(ra*u.deg,
-                                                             (dec + height)*u.deg))
-        pixel_height = upper[1] - pix[1]
+        sky = sky_region.center
+        w = imview.layers[0].layer.coords
+        pix = w.world_to_pixel(sky)
+        upper = w.world_to_pixel(SkyCoord(sky.ra, sky.dec + sky_region.height))
+        pixel_height = abs(upper[1] - pix[1])  # y
 
         return pix, pixel_height
 
@@ -406,7 +407,7 @@ class Mosviz(ConfigHelper, LineListMixin):
 
     def load_data(self, spectra_1d=None, spectra_2d=None, images=None,
                   spectra_1d_label=None, spectra_2d_label=None,
-                  images_label=None, *args, **kwargs):
+                  images_label=None, directory=None, instrument=None):
         """
         Load and parse a set of MOS spectra and images.
 
@@ -445,43 +446,56 @@ class Mosviz(ConfigHelper, LineListMixin):
         directory : str, optional
             Instead of loading lists of spectra and images, the path to a directory
             containing all files for a single JWST observation may be given.
+            If this is provided, all the above inputs are ignored.
 
         instrument : {'niriss', 'nircam', 'nirspec'}, optional
-            Required if ``directory`` is specified. Value is not case sensitive.
+            Required and only used if ``directory`` is specified. Value is not case sensitive.
         """
         # Link data after everything is loaded
         self.app.auto_link = False
         allow_link_table = True
 
-        directory = kwargs.pop('directory', None)
-        instrument = kwargs.pop('instrument', None)
-        if instrument is not None:
+        if isinstance(instrument, str):
             instrument = instrument.lower()
 
-        if directory is not None and Path(directory).is_dir():
-            if instrument not in ('nirspec', 'niriss', 'nircam'):
-                raise ValueError(
-                    "Ambiguous MOS Instrument: Only JWST NIRSpec, NIRCam, and "
-                    f"NIRISS folder parsing are currently supported but got '{instrument}'")
-            if instrument == "nirspec":
-                super().load_data(directory, parser_reference="mosviz-nirspec-directory-parser")
-            else:  # niriss or nircam
-                self.load_jwst_directory(directory, instrument=instrument)
-        elif directory is not None and is_zipfile(str(directory)):
-            raise TypeError("Please extract your data first and provide the directory")
+        if images is not None and not isinstance(images, (list, tuple)):
+            single_image = True
+        else:
+            single_image = False
+
+        if directory is not None:
+            if is_zipfile(str(directory)):
+                raise TypeError("Please extract your data first and provide the directory")
+            elif os.path.isdir(directory):
+                if instrument not in ('nirspec', 'niriss', 'nircam'):
+                    raise ValueError(
+                        "Ambiguous MOS Instrument: Only JWST NIRSpec, NIRCam, and "
+                        f"NIRISS folder parsing are currently supported but got '{instrument}'")
+                if instrument == "nirspec":
+                    super().load_data(directory, parser_reference="mosviz-nirspec-directory-parser")
+                else:  # niriss or nircam
+                    self.load_jwst_directory(directory, instrument=instrument)
+            else:
+                raise NotImplementedError(f"{directory} is not a directory")
+
+        # For the following, always load in this order: 1d, 2d, images, metadata
 
         elif (spectra_1d is not None and spectra_2d is not None
                 and images is not None):
-            # If we have a single image for multiple spectra, tell the table viewer
-            if not isinstance(images, (list, tuple)) and isinstance(spectra_1d, (list, tuple)):
+            n_specs = self.load_1d_spectra(spectra_1d, spectra_1d_label)
+            self.load_2d_spectra(spectra_2d, spectra_2d_label)
+
+            # If we have a single image for multiple spectra, tell the table viewer.
+            if single_image:
                 self._shared_image = True
                 self.app.get_viewer(self._default_table_viewer_reference_name)._shared_image = True
-                self.load_images(images, images_label, share_image=len(spectra_1d))
+                if n_specs > 1:
+                    self.load_images(images, images_label, share_image=n_specs)
+                else:
+                    self.load_images(images, images_label)
             else:
                 self.load_images(images, images_label)
 
-            self.load_2d_spectra(spectra_2d, spectra_2d_label)
-            self.load_1d_spectra(spectra_1d, spectra_1d_label)
             self.load_metadata()
 
         elif spectra_1d is not None and spectra_2d is not None:
@@ -490,13 +504,35 @@ class Mosviz(ConfigHelper, LineListMixin):
             self.load_metadata()
 
         elif spectra_1d and images:
-            self.load_1d_spectra(spectra_1d, spectra_1d_label)
-            self.load_images(images, images_label)
+            n_specs = self.load_1d_spectra(spectra_1d, spectra_1d_label)
+
+            # If we have a single image for multiple spectra, tell the table viewer.
+            if single_image:
+                self._shared_image = True
+                self.app.get_viewer(self._default_table_viewer_reference_name)._shared_image = True
+                if n_specs > 1:
+                    self.load_images(images, images_label, share_image=n_specs)
+                else:
+                    self.load_images(images, images_label)
+            else:
+                self.load_images(images, images_label)
+
             allow_link_table = False
 
         elif spectra_2d and images:
-            self.load_2d_spectra(spectra_2d, spectra_2d_label)
-            self.load_images(images, images_label)
+            n_specs = self.load_2d_spectra(spectra_2d, spectra_2d_label)
+
+            # If we have a single image for multiple spectra, tell the table viewer.
+            if single_image:
+                self._shared_image = True
+                self.app.get_viewer(self._default_table_viewer_reference_name)._shared_image = True
+                if n_specs > 1:
+                    self.load_images(images, images_label, share_image=n_specs)
+                else:
+                    self.load_images(images, images_label)
+            else:
+                self.load_images(images, images_label)
+
             allow_link_table = False
 
         elif spectra_1d:
@@ -508,10 +544,7 @@ class Mosviz(ConfigHelper, LineListMixin):
             allow_link_table = False
 
         else:
-            self.app.hub.broadcast(SnackbarMessage(
-                "Warning: Please set valid values for the load_data() method",
-                color='warning', sender=self))
-            return
+            raise NotImplementedError("Please set valid values for the Mosviz.load_data() method")
 
         if allow_link_table:
             self.link_table_data(None)
@@ -534,8 +567,8 @@ class Mosviz(ConfigHelper, LineListMixin):
         self.app.get_viewer(self._default_table_viewer_reference_name).figure_widget.highlighted = 0
 
         # Notify the user that this all loaded successfully
-        loaded_msg = SnackbarMessage("MOS data loaded successfully", color="success", sender=self)
-        self.app.hub.broadcast(loaded_msg)
+        self.app.hub.broadcast(SnackbarMessage(
+            "MOS data loaded successfully", color="success", sender=self))
 
         self._default_visible_columns = self.get_column_names(True)
 
@@ -590,7 +623,7 @@ class Mosviz(ConfigHelper, LineListMixin):
         """
         self.app.load_data(file_obj=None, parser_reference="mosviz-metadata-parser")
 
-    def load_1d_spectra(self, data_obj, data_labels=None):
+    def load_1d_spectra(self, data_obj, data_labels=None, add_redshift_column=False):
         """
         Load and parse a set of 1D spectra objects.
 
@@ -604,12 +637,21 @@ class Mosviz(ConfigHelper, LineListMixin):
             String representing the label for the data item loaded via
             ``data_obj``. Can be a list of strings representing data labels
             for each item in ``data_obj`` if  ``data_obj`` is a list.
+        add_redshift_column : bool
+            Add redshift column to Mosviz table.
+
+        Returns
+        -------
+        n_specs : int
+            Number of data objects loaded.
+
         """
-        super().load_data(data_obj, parser_reference="mosviz-spec1d-parser",
-                          data_labels=data_labels)
-        self._add_redshift_column()
+        n_specs = mos_spec1d_parser(self.app, data_obj, data_labels=data_labels)
+        if add_redshift_column:
+            self._add_redshift_column()
+        return n_specs
 
-    def load_2d_spectra(self, data_obj, data_labels=None):
+    def load_2d_spectra(self, data_obj, data_labels=None, add_redshift_column=False):
         """
         Load and parse a set of 2D spectra objects.
 
@@ -623,22 +665,34 @@ class Mosviz(ConfigHelper, LineListMixin):
             String representing the label for the data item loaded via
             ``data_obj``. Can be a list of strings representing data labels
             for each item in ``data_obj`` if  ``data_obj`` is a list.
-        """
-        super().load_data(data_obj, parser_reference="mosviz-spec2d-parser",
-                          data_labels=data_labels)
-        self._add_redshift_column()
+        add_redshift_column : bool
+            Add redshift column to Mosviz table.
 
-    def load_jwst_directory(self, data_obj, data_labels=None, instrument=None):
+        Returns
+        -------
+        n_specs : int
+            Number of data objects loaded.
+
+        """
+        n_specs = mos_spec2d_parser(self.app, data_obj, data_labels=data_labels)
+        if add_redshift_column:
+            self._add_redshift_column()
+        return n_specs
+
+    def load_jwst_directory(self, data_obj, data_labels=None, instrument=None,
+                            add_redshift_column=False):
+        """Load NIRISS or NIRCam data from a directory."""
         self.app.auto_link = False
         super().load_data(data_obj, parser_reference="mosviz-niriss-parser",
                           instrument=instrument)
 
         self.link_table_data(data_obj)
-        self._add_redshift_column()
+        if add_redshift_column:
+            self._add_redshift_column()
 
         self.app.auto_link = True
 
-    def load_images(self, data_obj, data_labels=None, share_image=0):
+    def load_images(self, data_obj, data_labels=None, share_image=0, add_redshift_column=False):
         """
         Load and parse a set of image objects. If providing a file path, it
         must be readable by ``astropy.io.fits``.
@@ -659,10 +713,13 @@ class Mosviz(ConfigHelper, LineListMixin):
             different row in the table does not reload the displayed image.
             Currently, if non-zero, the provided number must match the number of
             spectra.
+        add_redshift_column : bool
+            Add redshift column to Mosviz table.
         """
         super().load_data(data_obj, parser_reference="mosviz-image-parser",
                           data_labels=data_labels, share_image=share_image)
-        self._add_redshift_column()
+        if add_redshift_column:
+            self._add_redshift_column()
 
     def get_column_names(self, visible=None):
         """
diff --git a/jdaviz/configs/mosviz/plugins/parsers.py b/jdaviz/configs/mosviz/plugins/parsers.py
index a04ffd90..6b6259ce 100644
--- a/jdaviz/configs/mosviz/plugins/parsers.py
+++ b/jdaviz/configs/mosviz/plugins/parsers.py
@@ -1,6 +1,5 @@
 from collections.abc import Iterable
 import csv
-import glob
 import os
 from pathlib import Path
 import warnings
@@ -154,38 +153,46 @@ def mos_nirspec_directory_parser(app, data_obj, data_labels=None):
 
     # Load spectra
     level3_path = Path(data_obj)
-    for file_path in glob.iglob(str(level3_path / '*')):
+    for p in sorted(level3_path.glob('*.fits*')):
+        file_path = str(p)
         if 'x1d' in file_path or 'c1d' in file_path:
             spectra_1d.append(file_path)
         elif 's2d' in file_path:
             spectra_2d.append(file_path)
 
-    spectra_1d.sort()
-    spectra_2d.sort()
-    mos_spec1d_parser(app, spectra_1d)
+    n_specs = mos_spec1d_parser(app, spectra_1d)
     mos_spec2d_parser(app, spectra_2d)
 
     # Load images, if present
     image_path = None
 
     # Potential names of subdirectories where images are stored
-    for image_dir_name in ["cutouts", "mosviz_cutouts", "images"]:
-        if os.path.isdir(Path(str(level3_path / image_dir_name))):
-            image_path = Path(str(level3_path / image_dir_name))
+    for image_dir_name in ("cutouts", "mosviz_cutouts", "images"):
+        cur_path = level3_path / image_dir_name
+        if cur_path.is_dir():
+            image_path = cur_path
             break
     if image_path is not None:
-        images = sorted([file_path for file_path in glob.iglob(str(image_path / '*'))])
+        images = sorted(image_path.glob('*.fits*'))
+        n_images = len(images)
 
         # The amount of images needs to be equal to the amount of rows
         # of the other columns in the table
-        if len(images) == len(spectra_1d):
-            mos_image_parser(app, images)
+        if n_images == 1:
+            app._jdaviz_helper._shared_image = True
+            app.get_viewer(app._jdaviz_helper._default_table_viewer_reference_name)._shared_image = True  # noqa: E501
+            if n_specs > 1:
+                kwargs = {'share_image': n_specs}
+            else:
+                kwargs = {}
+            mos_image_parser(app, str(images[0]), **kwargs)
+        elif n_images == n_specs:
+            mos_image_parser(app, list(map(str, images)))
         else:
-            msg = ("The number of images in this directory does not match the"
-                   " number of spectra 1d and 2d files, please make the "
-                   "amounts equal or load images separately.")
-            msg = SnackbarMessage(msg, color='warning', sender=app)
-            app.hub.broadcast(msg)
+            app.hub.broadcast(SnackbarMessage(
+                "The number of images in this directory does not match the "
+                "number of spectra 1d and 2d files, please make the "
+                "amounts equal or load images separately.", color='warning', sender=app))
 
     mos_meta_parser(app)
 
@@ -205,8 +212,13 @@ def mos_spec1d_parser(app, data_obj, data_labels=None,
         the mosviz table.
     data_labels : str, optional
         The label applied to the glue data component.
-    """
 
+    Returns
+    -------
+    n_specs : int
+        Number of data objects loaded.
+
+    """
     if isinstance(data_labels, str):
         data_labels = [data_labels]
 
@@ -269,6 +281,12 @@ def mos_spec2d_parser(app, data_obj, data_labels=None, add_to_table=True,
         The extension in the FITS file that contains the data to be loaded.
     transpose : bool, optional
         Flag to transpose the data array before loading.
+
+    Returns
+    -------
+    n_specs : int
+        Number of data objects loaded.
+
     """
     spectrum_2d_viewer_reference_name = (
         app._jdaviz_helper._default_spectrum_2d_viewer_reference_name
@@ -476,8 +494,10 @@ def mos_meta_parser(app, data_obj=None, ids=None):
     with app.data_collection.delay_link_manager_update():
 
         current_columns = [comp.label for comp in app.data_collection['MOS Table'].main_components]
+        has_1d = "1D Spectra" in current_columns
+
         # source name can be taken from 1d spectra
-        if "1D Spectra" in current_columns:
+        if has_1d:
             names = _get_source_identifiers(app, "1D Spectra", data_obj, ids)
             _add_to_table(app, names, "Identifier")
 
@@ -490,8 +510,20 @@ def mos_meta_parser(app, data_obj=None, ids=None):
 
             _add_to_table(app, filters_gratings, "Filter/Grating")
 
-        # source name and coordinates are taken from image headers, if present
-        if "Images" in current_columns:
+        # Grab target sky coordinates from 1D spectrum, if possible.
+        # This has to happen after Filter/Grating because columns are insertion-ordered.
+        if has_1d:
+            ra = query_metadata_by_component(app, "SRCRA", "1D Spectra", False)
+            dec = query_metadata_by_component(app, "SRCDEC", "1D Spectra", False)
+            if all(ra) and all(dec):
+                _add_to_table(app, ra, "R.A.")
+                _add_to_table(app, dec, "Dec.")
+
+        # Refresh current_columns for Images check
+        current_columns = [comp.label for comp in app.data_collection['MOS Table'].main_components]
+
+        # If not in 1D spectrum, source coordinates are taken from image headers, if present.
+        if "R.A." not in current_columns and "Images" in current_columns:
             ra = query_metadata_by_component(app, "OBJ_RA", "Images", FALLBACK_NAME)
             dec = query_metadata_by_component(app, "OBJ_DEC", "Images", FALLBACK_NAME)
             _add_to_table(app, ra, "R.A.")
@@ -666,7 +698,7 @@ def _id_files_by_datamodl(label_dict, filepaths, catalog_key=None):
         if fp.is_dir():
             # Potential names of subdirectories where images are stored
             if fp.name in ("cutouts", "mosviz_cutouts", "images"):
-                images = sorted([file_path for file_path in glob.iglob(str(fp / '*'))])
+                images = sorted(fp.glob('*.fits*'))
                 label_dict['Direct Image'] = images
             else:
                 continue
diff --git a/jdaviz/configs/mosviz/plugins/viewers.py b/jdaviz/configs/mosviz/plugins/viewers.py
index 98a89d81..9f943ae3 100644
--- a/jdaviz/configs/mosviz/plugins/viewers.py
+++ b/jdaviz/configs/mosviz/plugins/viewers.py
@@ -1,3 +1,5 @@
+from astropy.coordinates import SkyCoord
+from astropy.table import QTable
 from glue.core import BaseData
 from glue_jupyter.bqplot.image import BqplotImageView
 from glue_jupyter.table import TableViewer
@@ -6,6 +8,7 @@ from specutils import Spectrum1D
 from jdaviz.core.events import (AddDataToViewerMessage,
                                 RemoveDataFromViewerMessage,
                                 TableClickMessage)
+from jdaviz.core.astrowidgets_api import AstrowidgetsImageViewerMixin
 from jdaviz.core.registries import viewer_registry
 from jdaviz.core.freezable_state import FreezableBqplotImageViewerState
 from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin
@@ -15,7 +18,7 @@ __all__ = ['MosvizImageView', 'MosvizProfile2DView',
 
 
 @viewer_registry("mosviz-image-viewer", label="Image 2D (Mosviz)")
-class MosvizImageView(JdavizViewerMixin, BqplotImageView):
+class MosvizImageView(JdavizViewerMixin, BqplotImageView, AstrowidgetsImageViewerMixin):
     # categories: zoom resets, zoom, pan, subset, select tools, shortcuts
     tools_nested = [
                     ['jdaviz:homezoom', 'jdaviz:prevzoom'],
@@ -28,23 +31,31 @@ class MosvizImageView(JdavizViewerMixin, BqplotImageView):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
+        self.init_astrowidgets_api()
         self._subscribe_to_layers_update()
 
+        self.state.show_axes = False  # Axes are wrong anyway
+        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)
                 for layer_state in self.state.layers
                 if hasattr(layer_state, 'layer') and
                 isinstance(layer_state.layer, BaseData)]
 
-    def set_plot_axes(self):
-        self.figure.axes[1].tick_format = None
-        self.figure.axes[0].tick_format = None
+    # NOTE: This is currently only for debugging. It is not used in app.
+    def _mark_targets(self):
+        table_data = self.jdaviz_app.data_collection['MOS Table']
 
-        self.figure.axes[1].label = "y: pixels"
-        self.figure.axes[0].label = "x: pixels"
+        if ("R.A." not in table_data.component_ids() or
+                "Dec." not in table_data.component_ids()):
+            return
 
-        # Make it so y axis label is not covering tick numbers.
-        self.figure.axes[1].label_offset = "-50"
+        ra = table_data["R.A."]
+        dec = table_data["Dec."]
+        sky = SkyCoord(ra, dec, unit='deg')
+        t = QTable({'coord': sky})
+        self.add_markers(t, use_skycoord=True, marker_name='Targets')
 
 
 @viewer_registry("mosviz-profile-2d-viewer", label="Spectrum 2D (Mosviz)")
@@ -134,7 +145,7 @@ class MosvizTableViewer(TableViewer, JdavizViewerMixin):
         else:
             components_str = [cid.label for cid in self.figure_widget.data.main_components]
             hidden = []
-            for colname in ['Images', '1D Spectra', '2D Spectra']:
+            for colname in ('Images', '1D Spectra', '2D Spectra'):
                 if colname in components_str:
                     hidden.append(self.figure_widget.data.id[colname])
             self.figure_widget.hidden_components = hidden
diff --git a/notebooks/MosvizExample.ipynb b/notebooks/MosvizExample.ipynb
index 1c552403..2b667033 100644
--- a/notebooks/MosvizExample.ipynb
+++ b/notebooks/MosvizExample.ipynb
@@ -201,11 +201,12 @@
    "source": [
     "# *** OR ***\n",
     "# Enable and run this cell if you want to load them separately.\n",
+    "# On the last data loader call, also add redshift column.\n",
     "\n",
-    "mosviz.load_metadata(images)    \n",
     "mosviz.load_1d_spectra(spectra_1d)\n",
     "mosviz.load_2d_spectra(spectra_2d)\n",
-    "mosviz.load_images(images)"
+    "mosviz.load_images(images, add_redshift_column=True)\n",
+    "mosviz.load_metadata(images)"
    ]
   },
   {
@@ -240,9 +241,10 @@
    "source": [
     "# *** OR ***\n",
     "# Enable and run this cell if you want to load them separately.\n",
+    "# On the last data loader call, also add redshift column.\n",
     "\n",
     "mosviz_no_images.load_1d_spectra(spectra_1d)\n",
-    "mosviz_no_images.load_2d_spectra(spectra_2d)"
+    "mosviz_no_images.load_2d_spectra(spectra_2d, add_redshift_column=True)"
    ]
   },
   {
@@ -313,7 +315,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.7"
+   "version": "3.10.8"
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py b/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py
index 2ccd36d9..f642cfd3 100644
--- a/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py
+++ b/jdaviz/configs/cubeviz/plugins/slice/tests/test_slice.py
@@ -1,3 +1,5 @@
+import warnings
+
 import pytest
 import numpy as np
 
@@ -56,7 +58,8 @@ def test_slice(cubeviz_helper, spectrum1d_cube):
     # test setting a static 2d image to the "watched" flux viewer to make sure it disconnects
     mm = app.get_tray_item_from_name('cubeviz-moment-maps')
     mm.add_to_viewer_selected = 'flux-viewer'
-    with pytest.warns(UserWarning, match='No observer defined on WCS'):
+    with warnings.catch_warnings():
+        warnings.filterwarnings('ignore', message=r'.*No observer defined on WCS.*')
         mm.vue_calculate_moment()
 
     assert len(sl._watched_viewers) == 2
diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py
index 64b96862..9a5ab2ca 100644
--- a/jdaviz/configs/mosviz/tests/test_data_loading.py
+++ b/jdaviz/configs/mosviz/tests/test_data_loading.py
@@ -31,10 +31,9 @@ def test_load_spectrum1d(mosviz_helper, spectrum1d):
         mosviz_helper.load_1d_spectra([1, 2, 3])
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_load_image(mosviz_helper, mos_image):
     label = "Test Image"
-    mosviz_helper.load_images(mos_image, data_labels=label)
+    mosviz_helper.load_images(mos_image, data_labels=label, add_redshift_column=True)
 
     assert len(mosviz_helper.app.data_collection) == 2
     assert mosviz_helper.app.data_collection[0].label == "MOS Table"
@@ -55,7 +54,7 @@ def test_load_image(mosviz_helper, mos_image):
 
 def test_load_spectrum_collection(mosviz_helper, spectrum_collection):
     labels = [f"Test Spectrum Collection {i}" for i in range(5)]
-    mosviz_helper.load_1d_spectra(spectrum_collection, data_labels=labels)
+    mosviz_helper.load_1d_spectra(spectrum_collection, data_labels=labels, add_redshift_column=True)
 
     # +1 for the table viewer
     assert len(mosviz_helper.app.data_collection) == len(spectrum_collection) + 1
@@ -76,7 +75,7 @@ def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d):
     spectra = [spectrum1d] * 3
 
     labels = [f"Test Spectrum 1D {i}" for i in range(3)]
-    mosviz_helper.load_1d_spectra(spectra, data_labels=labels)
+    mosviz_helper.load_1d_spectra(spectra, data_labels=labels, add_redshift_column=True)
 
     assert len(mosviz_helper.app.data_collection) == 4
     assert mosviz_helper.app.data_collection[0].label == "MOS Table"
@@ -92,7 +91,6 @@ def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d):
     assert isinstance(data[labels[0]], Spectrum1D)
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d):
 
     label = "Test 2D Spectrum"
@@ -112,7 +110,6 @@ def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d):
     assert data[label].shape == (1024, 15)
 
 
-@pytest.mark.filterwarnings('ignore')
 @pytest.mark.parametrize('label', [None, "Test Label"])
 def test_load_multi_image_spec(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d, label):
     spectra1d = [spectrum1d] * 3
@@ -169,7 +166,7 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
     # Test that loading is still possible after previous crash:
     # https://github.com/spacetelescope/jdaviz/issues/364
     with pytest.raises(ValueError, match="incompatible with the dimensions of this data:"):
-        mosviz_helper.load_data(spectra1d, spectra2d, images=[])
+        mosviz_helper.load_data(spectra1d, spectra2d, images=[mos_image, mos_image])
 
     mosviz_helper.load_data(spectra1d, spectra2d, images=mos_image, images_label=label)
 
@@ -212,7 +209,7 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
     label_mouseover._viewer_mouse_event(spec2d_viewer,
                                         {'event': 'mousemove', 'domain': {'x': 10, 'y': 100}})
     assert label_mouseover.as_text() == ('Pixel x=00010.0 y=00100.0 Value +8.12986e-01', '', '')
-    assert label_mouseover.icon == 'b'
+    assert label_mouseover.icon == 'c'
 
     # need to trigger a mouseleave or mouseover to reset the traitlets
     label_mouseover._viewer_mouse_event(spec1d_viewer, {'event': 'mouseenter'})
@@ -221,7 +218,7 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_
     assert label_mouseover.as_text() == ('Cursor 7.00000e+03, 1.70000e+02',
                                          'Wave 6.88889e+03 Angstrom (4 pix)',
                                          'Flux 1.35436e+01 Jy')
-    assert label_mouseover.icon == 'c'
+    assert label_mouseover.icon == 'b'
 
 
 def test_zip_error(mosviz_helper, tmp_path):
@@ -235,3 +232,11 @@ def test_zip_error(mosviz_helper, tmp_path):
 
     with pytest.raises(TypeError, match="Please extract"):
         mosviz_helper.load_data(directory=str(zip_path))
+
+
+def test_invalid_inputs(mosviz_helper):
+    with pytest.raises(NotImplementedError, match=r".*not a directory"):
+        mosviz_helper.load_data(directory="foo")
+
+    with pytest.raises(NotImplementedError, match="Please set valid values"):
+        mosviz_helper.load_data()
diff --git a/jdaviz/configs/mosviz/tests/test_helper.py b/jdaviz/configs/mosviz/tests/test_helper.py
index ebab1ade..60a2a7ec 100644
--- a/jdaviz/configs/mosviz/tests/test_helper.py
+++ b/jdaviz/configs/mosviz/tests/test_helper.py
@@ -7,13 +7,12 @@ from numpy.testing import assert_allclose
 from jdaviz.configs.specviz2d.helper import Specviz2d
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_viewer_axis_link(mosviz_helper, mos_spectrum1d, mos_spectrum2d):
     label1d = "Test 1D Spectrum"
     mosviz_helper.load_1d_spectra(mos_spectrum1d, data_labels=label1d)
 
     label2d = "Test 2D Spectrum"
-    mosviz_helper.load_2d_spectra(mos_spectrum2d, data_labels=label2d)
+    mosviz_helper.load_2d_spectra(mos_spectrum2d, data_labels=label2d, add_redshift_column=True)
 
     table = mosviz_helper.app.get_viewer('table-viewer')
     table.widget_table.vue_on_row_clicked(0)
@@ -30,7 +29,7 @@ def test_viewer_axis_link(mosviz_helper, mos_spectrum1d, mos_spectrum2d):
 
 def test_to_csv(tmp_path, mosviz_helper, spectrum_collection):
     labels = [f"Test Spectrum Collection {i}" for i in range(5)]
-    mosviz_helper.load_1d_spectra(spectrum_collection, data_labels=labels)
+    mosviz_helper.load_1d_spectra(spectrum_collection, data_labels=labels, add_redshift_column=True)
 
     mosviz_helper.to_csv(filename=str(tmp_path / "MOS_data.csv"))
 
@@ -49,7 +48,6 @@ def test_to_csv(tmp_path, mosviz_helper, spectrum_collection):
     assert found_rows == 5
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_table_scrolling(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     spectra1d = [spectrum1d] * 2
     spectra2d = [mos_spectrum2d] * 2
@@ -69,7 +67,6 @@ def test_table_scrolling(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     assert table.widget_table.highlighted == 1
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_column_visibility(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     spectra1d = [spectrum1d] * 2
     spectra2d = [mos_spectrum2d] * 2
@@ -92,7 +89,6 @@ def test_column_visibility(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d)
     assert 'Redshift' not in mosviz_helper.get_column_names(True)
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_custom_columns(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     spectra1d = [spectrum1d] * 2
     spectra2d = [mos_spectrum2d] * 2
@@ -122,7 +118,6 @@ def test_custom_columns(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     assert "Redshift" not in mosviz_helper.get_column_names(True)
 
 
-@pytest.mark.filterwarnings('ignore')
 def test_redshift_column(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     spectra1d = [spectrum1d] * 2
     spectra2d = [mos_spectrum2d] * 2
@@ -130,11 +125,12 @@ def test_redshift_column(mosviz_helper, mos_image, spectrum1d, mos_spectrum2d):
     mosviz_helper.load_data(spectra1d, spectra2d, images=mos_image)
 
     mosviz_helper.update_column("Redshift", 0.1, row=0)
-    assert_allclose(list(mosviz_helper.specviz.get_spectra().values())[0].redshift.value, 0.1)
+    all_spectra = mosviz_helper.specviz.get_spectra(apply_slider_redshift=True)
+    assert_allclose(list(all_spectra.values())[0].redshift.value, 0.1)
     assert isinstance(mosviz_helper.specviz2d, Specviz2d)
-    assert_allclose(mosviz_helper.get_spectrum_1d().redshift.value, 0.1)
-    assert_allclose(mosviz_helper.get_spectrum_2d().redshift.value, 0.1)
-    assert_allclose(mosviz_helper.get_spectrum_1d(row=1).redshift.value, 0.0)
+    assert_allclose(mosviz_helper.get_spectrum_1d(apply_slider_redshift=True).redshift.value, 0.1)
+    assert_allclose(mosviz_helper.get_spectrum_2d(apply_slider_redshift=True).redshift.value, 0.1)
+    assert_allclose(mosviz_helper.get_spectrum_1d(apply_slider_redshift=True, row=1).redshift.value, 0.0)  # noqa: E501
 
 
 def test_plugin_user_apis(mosviz_helper):
diff --git a/jdaviz/configs/mosviz/tests/test_parsers.py b/jdaviz/configs/mosviz/tests/test_parsers.py
index 5e690e4d..56cda033 100644
--- a/jdaviz/configs/mosviz/tests/test_parsers.py
+++ b/jdaviz/configs/mosviz/tests/test_parsers.py
@@ -7,7 +7,6 @@ from astropy.utils.data import download_file
 from jdaviz.utils import PRIHDR_KEY, COMMENTCARD_KEY
 
 
-@pytest.mark.filterwarnings('ignore')
 @pytest.mark.remote_data
 @pytest.mark.parametrize('instrument_arg', ('nirspec', None))
 def test_nirspec_parser(mosviz_helper, tmp_path, instrument_arg):
@@ -83,7 +82,6 @@ def test_nirspec_parser(mosviz_helper, tmp_path, instrument_arg):
                                              data_label=data_label)
 
 
-@pytest.mark.filterwarnings('ignore')
 @pytest.mark.remote_data
 def test_nirspec_level2_parser(mosviz_helper, tmp_path):
     '''
@@ -105,7 +103,6 @@ def test_nirspec_level2_parser(mosviz_helper, tmp_path):
 
 
 @pytest.mark.remote_data
-@pytest.mark.filterwarnings('ignore', match="'(MJy/sr)^2' did not parse as fits unit")
 def test_niriss_parser(mosviz_helper, tmp_path):
     '''
     Tests loading a NIRISS dataset
@@ -177,7 +174,6 @@ def test_niriss_parser(mosviz_helper, tmp_path):
 
 
 @pytest.mark.remote_data
-@pytest.mark.filterwarnings('ignore', match="'(MJy/sr)^2' did not parse as fits unit")
 def test_nircam_parser(mosviz_helper, tmp_path):
     '''
     Tests loading a NIRCam dataset
@@ -216,8 +212,6 @@ def test_missing_srctype(mosviz_helper, tmp_path):
 
     This dataset was our original simulated NIRISS dataset that is missing SRCTYPE.
 
-    NOTE: Under some conditions, a warning is raised when TemporaryDirectory attempts to
-    clean itself up. Most likely a race condition between TempDir and pytest
     '''
 
     # Download data