Repository: astropy/astropy

Issue:
fits.CompImageHDU writes unexpected/incorrect BZERO keyword after conversion from unsigned image data
When I read or create a FITS image HDU with unsigned integer data, then convert it to floating point and save it in a `CompImageHDU`, an additional `BZERO` keyword & value get written into the relevant header, even with Astropy telling me it's not there.

Here is a minimal example:

```python
from __future__ import print_function                                               
import sys                                                                          
from tempfile import NamedTemporaryFile                                             
import numpy as np                                                                  
from astropy.io import fits                                                         
                                                                                    
data = np.arange(0, 100, dtype=np.uint16)                                            
hdu = fits.ImageHDU(data=data)                                                      
data = hdu.data.astype(np.float64)                                                  
header = hdu.header                                                                 
hdulist = [fits.PrimaryHDU(),                                                       
           fits.ImageHDU(data=data, header=header, name='normal'),                  
           fits.CompImageHDU(data=data, header=header, name='compressed')]          
                                                                                    
with NamedTemporaryFile() as tmpfile:                                               
    name = tmpfile.name                                                             
    fits.HDUList(hdulist).writeto(name, output_verify='warn')                       
                                                                                    
    print('HDU list as in memory')                                                  
    for hdu in hdulist:                                                             
        print('BZERO in', hdu.name, ':', hdu.header.get('bzero'))                   
        print('BSCALE in', hdu.name, ':', hdu.header.get('bscale'))                 
    print()                                                                         
    print('Re-opening test file')                                                   
    with fits.open(name) as hdulist:                                                
        for hdu in hdulist:                                                         
            print('BZERO in', hdu.name, ':', hdu.header.get('bzero'))               
            print('BSCALE in', hdu.name, ':', hdu.header.get('bscale'))             
```

This produces:
```
HDU list as in memory
BZERO in PRIMARY : None
BSCALE in PRIMARY : None
BZERO in NORMAL : None
BSCALE in NORMAL : None
BZERO in COMPRESSED : None
BSCALE in COMPRESSED : None

Re-opening test file
BZERO in PRIMARY : None
BSCALE in PRIMARY : None
BZERO in NORMAL : None
BSCALE in NORMAL : None
BZERO in COMPRESSED : 32768
BSCALE in COMPRESSED : None
```

The penultimate output line is obviously in error.
Changing the dtype in `np.arange` to `uint32` shows a value of 2147483648, while using a signed integer or floating point dtype shows the `BZERO` keyword to be absent.  
I find the interesting part that the `BZERO` key gets written (without an accompanying `BSCALE` key!), but the in-memory header does not contain it whatsoever. All other header keywords seem to be ok.

I've not had time yet to explore this in more depth in Astropy, nor compare it with other current issues with `CompImageHDU` to see if they are related.

So far, I've seen this happen with various combinations of Python 2.7 & 3.6, Astropy master, 1.3.0, 1.32 and 1.2.1, on Linux and macOS.


Discussion:
github-actions[bot]: Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  - [ ] Do the proposed changes actually accomplish desired goals?
  - [ ] Do the proposed changes follow the [Astropy coding guidelines](https://docs.astropy.org/en/latest/development/codeguide.html)?
  - [ ] Are tests added/updated as required? If so, do they follow the [Astropy testing guidelines](https://docs.astropy.org/en/latest/development/testguide.html)?
  - [ ] Are docs added/updated as required? If so, do they follow the [Astropy documentation guidelines](https://docs.astropy.org/en/latest/development/docguide.html)?
  - [ ] Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for [rebase](https://docs.astropy.org/en/latest/development/development_details.html#rebase-if-necessary) and [squash](https://docs.astropy.org/en/latest/development/development_details.html#squash-if-necessary).
  - [ ] Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the [bot](https://docs.astropy.org/en/latest/development/development_details.html#pre-commit).
  - [ ] Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  - [ ] Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  - [ ] At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) *before* merge.
neutrinoceros: failures look real and weirdly platform dependent. If you need a hand from someone working on macOS let me know ! (I don't know your dev platform)

Patch:
diff --git a/astropy/io/fits/hdu/compressed/compressed.py b/astropy/io/fits/hdu/compressed/compressed.py
index 28e3f244f1..f898199aae 100644
--- a/astropy/io/fits/hdu/compressed/compressed.py
+++ b/astropy/io/fits/hdu/compressed/compressed.py
@@ -325,12 +325,6 @@ class CompImageHDU(ImageHDU):
         else:
             # Create at least a skeleton HDU that matches the input
             # header and data (if any were input)
-
-            if header is not None:
-                bscale = header.get("BSCALE")
-                bzero = header.get("BZERO")
-                simple = header.get("SIMPLE")
-
             super().__init__(
                 data=data,
                 header=header or Header(),
@@ -340,15 +334,8 @@ class CompImageHDU(ImageHDU):
                 scale_back=scale_back,
             )
 
-            if header is not None:
-                if bscale is not None:
-                    self.header["BSCALE"] = bscale
-
-                if bzero is not None:
-                    self.header["BZERO"] = bzero
-
-                if simple is not None:
-                    self.header["SIMPLE"] = simple
+            if header is not None and "SIMPLE" in header:
+                self.header["SIMPLE"] = header["SIMPLE"]
 
             self.compression_type = compression_type
             self.tile_shape = _validate_tile_shape(
diff --git a/astropy/table/table.py b/astropy/table/table.py
index a5338f1024..8895bd33a0 100644
--- a/astropy/table/table.py
+++ b/astropy/table/table.py
@@ -209,9 +209,9 @@ def _get_names_from_list_of_dict(rows):
     if rows is None:
         return None
 
-    names = {}
+    names = set()
     for row in rows:
-        if not isinstance(row, (Mapping, Row)):
+        if not isinstance(row, Mapping):
             return None
         names.update(row)
     return list(names)
@@ -1189,7 +1189,17 @@ class Table:
         MISSING = object()
 
         # Gather column names that exist in the input `data`.
-        names_from_data = _get_names_from_list_of_dict(data)
+        names_from_data = set()
+        for row in data:
+            names_from_data.update(row)
+
+        if set(data[0].keys()) == names_from_data:
+            names_from_data = list(data[0].keys())
+        else:
+            names_from_data = sorted(names_from_data)
+
+        # Note: if set(data[0].keys()) != names_from_data, this will give an
+        # exception later, so NO need to catch here.
 
         # Convert list of dict into dict of list (cols), keep track of missing
         # indexes and put in MISSING placeholders in the `cols` lists.
diff --git a/astropy/visualization/interval.py b/astropy/visualization/interval.py
index 4f4d6253de..66fac6a117 100644
--- a/astropy/visualization/interval.py
+++ b/astropy/visualization/interval.py
@@ -146,25 +146,19 @@ class AsymmetricPercentileInterval(BaseInterval):
 
     Parameters
     ----------
-    lower_percentile : float or None
-        The lower percentile below which to ignore pixels. If None, then
-        defaults to 0.
-    upper_percentile : float or None
-        The upper percentile above which to ignore pixels. If None, then
-        defaults to 100.
+    lower_percentile : float
+        The lower percentile below which to ignore pixels.
+    upper_percentile : float
+        The upper percentile above which to ignore pixels.
     n_samples : int, optional
         Maximum number of values to use. If this is specified, and there
         are more values in the dataset as this, then values are randomly
         sampled from the array (with replacement).
     """
 
-    def __init__(self, lower_percentile=None, upper_percentile=None, n_samples=None):
-        self.lower_percentile = (
-            lower_percentile if lower_percentile is not None else 0.0
-        )
-        self.upper_percentile = (
-            upper_percentile if upper_percentile is not None else 100.0
-        )
+    def __init__(self, lower_percentile, upper_percentile, n_samples=None):
+        self.lower_percentile = lower_percentile
+        self.upper_percentile = upper_percentile
         self.n_samples = n_samples
 
     def get_limits(self, values):
diff --git a/astropy/visualization/mpl_normalize.py b/astropy/visualization/mpl_normalize.py
index 356a9b186f..78a10a35a2 100644
--- a/astropy/visualization/mpl_normalize.py
+++ b/astropy/visualization/mpl_normalize.py
@@ -37,7 +37,7 @@ else:
             raise ImportError("matplotlib is required in order to use this class.")
 
 
-__all__ = ["ImageNormalize", "SimpleNorm", "simple_norm", "imshow_norm"]
+__all__ = ["ImageNormalize", "simple_norm", "imshow_norm"]
 
 __doctest_requires__ = {"*": ["matplotlib"]}
 
@@ -212,229 +212,6 @@ class ImageNormalize(Normalize):
         return values_norm * (self.vmax - self.vmin) + self.vmin
 
 
-class SimpleNorm:
-    """
-    Class to create a normalization object that can be used for
-    displaying images with Matplotlib.
-
-    This convenience class provides the most common image stretching
-    functions. Additional stretch functions are available in
-    `~astropy.visualization.mpl_normalize.ImageNormalize`.
-
-    Parameters
-    ----------
-    stretch : {'linear', 'sqrt', 'power', log', 'asinh', 'sinh'}, optional
-        The stretch function to apply to the image. The default is
-        'linear'.
-
-    percent : float, optional
-        The percentage of the image values used to determine the pixel
-        values of the minimum and maximum cut levels. The lower cut
-        level will set at the ``(100 - percent) / 2`` percentile, while
-        the upper cut level will be set at the ``(100 + percent) / 2``
-        percentile. The default is 100.0. ``percent`` is ignored if
-        either ``min_percent`` or ``max_percent`` is input.
-
-    min_percent : float, optional
-        The percentile value used to determine the pixel value of
-        minimum cut level. The default is 0.0. ``min_percent`` overrides
-        ``percent``.
-
-    max_percent : float, optional
-        The percentile value used to determine the pixel value of
-        maximum cut level. The default is 100.0. ``max_percent``
-        overrides ``percent``.
-
-    vmin : float, optional
-        The pixel value of the minimum cut level. Data values less
-        than ``vmin`` will set to ``vmin`` before stretching the
-        image. The default is the image minimum. ``vmin`` overrides
-        ``min_percent``.
-
-    vmax : float, optional
-        The pixel value of the maximum cut level. Data values greater
-        than ``vmax`` will set to ``vmax`` before stretching the
-        image. The default is the image maximum. ``vmax`` overrides
-        ``max_percent``.
-
-    power : float, optional
-        The power index for ``stretch='power'``. The default is 1.0.
-
-    log_a : float, optional
-        The log index for ``stretch='log'``. The default is 1000.
-
-    asinh_a : float, optional
-        For ``stretch='asinh'``, the value where the asinh curve
-        transitions from linear to logarithmic behavior, expressed as a
-        fraction of the normalized image. Must be in the range between 0
-        and 1. The default is 0.1.
-
-    sinh_a : float, optional
-        The scaling parameter for ``stretch='sinh'``. The default is
-        0.3.
-
-    clip : bool, optional
-        If `True`, data values outside the [0:1] range are clipped to
-        the [0:1] range.
-
-    invalid : None or float, optional
-        Value to assign NaN values generated by the normalization. NaNs
-        in the input ``data`` array are not changed. For matplotlib
-        normalization, the ``invalid`` value should map to the
-        matplotlib colormap "under" value (i.e., any finite value < 0).
-        If `None`, then NaN values are not replaced. This keyword has no
-        effect if ``clip=True``.
-
-    See Also
-    --------
-    simple_norm
-
-    Examples
-    --------
-    .. plot::
-        :include-source:
-
-        import numpy as np
-        import matplotlib.pyplot as plt
-        from astropy.visualization import SimpleNorm
-
-        image = np.arange(65536).reshape((256, 256))
-        snorm = SimpleNorm('sqrt', percent=98)
-        norm = snorm(image)
-        fig, ax = plt.subplots()
-        axim = ax.imshow(image, norm=norm, origin='lower')
-        fig.colorbar(axim)
-    """
-
-    def __init__(
-        self,
-        stretch="linear",
-        percent=None,
-        *,
-        min_percent=None,
-        max_percent=None,
-        vmin=None,
-        vmax=None,
-        power=1.0,
-        log_a=1000,
-        asinh_a=0.1,
-        sinh_a=0.3,
-        clip=False,
-        invalid=-1.0,
-    ):
-        if percent is not None:
-            interval = PercentileInterval(percent)
-        elif min_percent is not None or max_percent is not None:
-            interval = AsymmetricPercentileInterval(
-                lower_percentile=min_percent, upper_percentile=max_percent
-            )
-        elif vmin is not None or vmax is not None:
-            interval = ManualInterval(vmin, vmax)
-        else:
-            interval = MinMaxInterval()
-        self.interval = interval
-
-        if stretch == "linear":
-            stretch = LinearStretch()
-        elif stretch == "sqrt":
-            stretch = SqrtStretch()
-        elif stretch == "power":
-            stretch = PowerStretch(power)
-        elif stretch == "log":
-            stretch = LogStretch(log_a)
-        elif stretch == "asinh":
-            stretch = AsinhStretch(asinh_a)
-        elif stretch == "sinh":
-            stretch = SinhStretch(sinh_a)
-        else:
-            raise ValueError(f"Unknown stretch: {stretch}.")
-        self.stretch = stretch
-
-        self.clip = clip
-        self.invalid = invalid
-
-    def __call__(self, data):
-        """
-        Return an `ImageNormalize` instance that can be used for displaying
-        images with Matplotlib.
-
-        Parameters
-        ----------
-        data : ndarray
-            The image array.
-
-        Returns
-        -------
-        result : `ImageNormalize` instance
-            An `ImageNormalize` instance that can be used for
-            displaying images with Matplotlib.
-        """
-        vmin, vmax = self.interval.get_limits(data)
-        return ImageNormalize(
-            vmin=vmin,
-            vmax=vmax,
-            stretch=self.stretch,
-            clip=self.clip,
-            invalid=self.invalid,
-        )
-
-    def imshow(self, data, ax=None, **kwargs):
-        """
-        A convenience function to display an image using matplotlib's
-        `matplotlib.pyplot.imshow` function with the normalization
-        defined by this class.
-
-        Parameters
-        ----------
-        data : 2D or 3D array-like
-            The data to display. Can be whatever
-            `~matplotlib.pyplot.imshow` and `ImageNormalize` both
-            accept.
-
-        ax : None or `~matplotlib.axes.Axes`, optional
-            The matplotlib axes on which to plot. If `None`, then the
-            current `~matplotlib.axes.Axes` instance is used.
-
-        **kwargs : dict, optional
-            Keywords arguments passed to `~matplotlib.pyplot.imshow`.
-            Cannot include the ``norm`` or ``X`` keyword.
-
-        Returns
-        -------
-        result : `~matplotlib.image.AxesImage`
-            The `~matplotlib.image.AxesImage` generated by
-            `~matplotlib.pyplot.imshow`.
-
-        Examples
-        --------
-        .. plot::
-            :include-source:
-
-            import numpy as np
-            import matplotlib.pyplot as plt
-            from astropy.visualization import SimpleNorm
-
-            image = np.arange(65536).reshape((256, 256))
-            snorm = SimpleNorm('sqrt', percent=98)
-            fig, ax = plt.subplots()
-            axim = snorm.imshow(image, ax=ax, origin='lower')
-            fig.colorbar(axim)
-        """
-        import matplotlib.pyplot as plt
-
-        if ax is None:
-            ax = plt.gca()
-
-        if "norm" in kwargs:
-            raise ValueError(
-                "This class already defines the norm. Use "
-                "matplotlib.pyplot.imshow directly to use your norm."
-            )
-
-        axim = ax.imshow(data, norm=self(data), **kwargs)
-        return axim
-
-
 @deprecated_renamed_argument(["min_cut", "max_cut"], ["vmin", "vmax"], ["6.1", "6.1"])
 def simple_norm(
     data,
@@ -532,26 +309,38 @@ def simple_norm(
     result : `ImageNormalize` instance
         An `ImageNormalize` instance that can be used for displaying
         images with Matplotlib.
-
-    See Also
-    --------
-    SimpleNorm
     """
-    simple_norm = SimpleNorm(
-        stretch=stretch,
-        percent=percent,
-        min_percent=min_percent,
-        max_percent=max_percent,
-        vmin=vmin,
-        vmax=vmax,
-        power=power,
-        log_a=log_a,
-        asinh_a=asinh_a,
-        sinh_a=sinh_a,
-        clip=clip,
-        invalid=invalid,
+    if percent is not None:
+        interval = PercentileInterval(percent)
+    elif min_percent is not None or max_percent is not None:
+        interval = AsymmetricPercentileInterval(
+            min_percent or 0.0, max_percent or 100.0
+        )
+    elif vmin is not None or vmax is not None:
+        interval = ManualInterval(vmin, vmax)
+    else:
+        interval = MinMaxInterval()
+
+    if stretch == "linear":
+        stretch = LinearStretch()
+    elif stretch == "sqrt":
+        stretch = SqrtStretch()
+    elif stretch == "power":
+        stretch = PowerStretch(power)
+    elif stretch == "log":
+        stretch = LogStretch(log_a)
+    elif stretch == "asinh":
+        stretch = AsinhStretch(asinh_a)
+    elif stretch == "sinh":
+        stretch = SinhStretch(sinh_a)
+    else:
+        raise ValueError(f"Unknown stretch: {stretch}.")
+
+    vmin, vmax = interval.get_limits(data)
+
+    return ImageNormalize(
+        vmin=vmin, vmax=vmax, stretch=stretch, clip=clip, invalid=invalid
     )
-    return simple_norm(data)
 
 
 # used in imshow_norm
@@ -613,6 +402,9 @@ def imshow_norm(data, ax=None, **kwargs):
 
         ax = plt.gca()
 
+    if "X" in kwargs:
+        raise ValueError("Cannot give both ``X`` and ``data``")
+
     if "norm" in kwargs:
         raise ValueError(
             "There is no point in using imshow_norm if you give "
diff --git a/docs/changes/io.fits/17237.bugfix.rst b/docs/changes/io.fits/17237.bugfix.rst
new file mode 100644
index 0000000000..2bc43c02c9
--- /dev/null
+++ b/docs/changes/io.fits/17237.bugfix.rst
@@ -0,0 +1,2 @@
+Fix creating CompImageHDU from header with BSCALE/BZERO: keywords are now
+ignored, as done in ImageHDU.
diff --git a/docs/changes/table/15735.bugfix.rst b/docs/changes/table/15735.bugfix.rst
deleted file mode 100644
index 5c83d2f273..0000000000
--- a/docs/changes/table/15735.bugfix.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Fix a bug where column names would be lost when instantiating ``Table`` from a list of ``Row`` objects.
diff --git a/docs/changes/visualization/17217.feature.rst b/docs/changes/visualization/17217.feature.rst
deleted file mode 100644
index 8d58074a9c..0000000000
--- a/docs/changes/visualization/17217.feature.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Added a SimpleNorm class to create a matplotlib normalization object.
diff --git a/docs/table/construct_table.rst b/docs/table/construct_table.rst
index 04baa5bae7..fcf238b4d5 100644
--- a/docs/table/construct_table.rst
+++ b/docs/table/construct_table.rst
@@ -258,17 +258,26 @@ You can specify the column order with the ``names`` argument::
    10   5
    20  15
 
-If ``names`` are not provided then column ordering will be determined by
-order in which they appear as the :class:`list` of :class:`dict` is iterated over.
+If ``names`` are not provided then column ordering will be determined by the
+first :class:`dict` if it contains values for all the columns, or by sorting
+the column names alphabetically if it doesn't::
 
-  >>> data = [{'b': 10, 'c': 7, },
+  >>> data = [{'b': 10, 'c': 7, 'a': 5},
   ...         {'a': 15, 'c': 35, 'b': 20}]
   >>> t = Table(rows=data)
   >>> print(t)
    b   c   a
   --- --- ---
-   10   7  --
+   10   7   5
    20  35  15
+  >>> data = [{'b': 10, 'c': 7, },
+  ...         {'a': 15, 'c': 35, 'b': 20}]
+  >>> t = Table(rows=data)
+  >>> print(t)
+   a   b   c
+  --- --- ---
+   --  10   7
+   15  20  35
 
 **Single row**
 
diff --git a/docs/visualization/normalization.rst b/docs/visualization/normalization.rst
index ec2bae986c..5b6c5e5faf 100644
--- a/docs/visualization/normalization.rst
+++ b/docs/visualization/normalization.rst
@@ -39,15 +39,8 @@ The Quick Way
 -------------
 
 ``astropy`` provides a convenience
-:func:`~astropy.visualization.mpl_normalize.simple_norm` function and
-a :class:`~astropy.visualization.mpl_normalize.SimpleNorm` class that
-can be useful for quick interactive analysis. These convenience tools
-create a :class:`~astropy.visualization.mpl_normalize.ImageNormalize`
-normalization object that can be used with Matplotlib's
-:meth:`~matplotlib.axes.Axes.imshow` method
-
-Here's an example using the
-:func:`~astropy.visualization.mpl_normalize.simple_norm` function:
+:func:`~astropy.visualization.mpl_normalize.simple_norm` function that can be
+useful for quick interactive analysis:
 
 .. plot::
     :include-source:
@@ -69,29 +62,12 @@ Here's an example using the
     im = ax.imshow(image, origin='lower', norm=norm)
     fig.colorbar(im)
 
-Here's an example using the
-:class:`~astropy.visualization.mpl_normalize.SimpleNorm` class with its
-:meth:`~astropy.visualization.mpl_normalize.SimpleNorm.imshow`
-method:
-
-.. plot::
-    :include-source:
-    :align: center
-
-    import numpy as np
-    import matplotlib.pyplot as plt
-    from astropy.visualization import SimpleNorm
-
-    # Generate a test image
-    image = np.arange(65536).reshape((256, 256))
-
-    # Create an ImageNormalize object
-    snorm = SimpleNorm('sqrt', percent=98)
-
-    # Display the image
-    fig, ax = plt.subplots()
-    axim = snorm.imshow(image, ax=ax, origin='lower')
-    fig.colorbar(axim)
+This convenience function combines a :class:`Stretch
+<astropy.visualization.stretch.BaseStretch>` object with an :class:`Interval
+<astropy.visualization.interval.BaseInterval>` object.
+We recommend using
+:class:`~astropy.visualization.mpl_normalize.ImageNormalize` directly
+in scripted programs instead of this convenience function.
 
 
 The detailed way
diff --git a/docs/whatsnew/7.0.rst b/docs/whatsnew/7.0.rst
index b3231ea844..1a4c47bf9c 100644
--- a/docs/whatsnew/7.0.rst
+++ b/docs/whatsnew/7.0.rst
@@ -15,7 +15,6 @@ In particular, this release includes:
 * :ref:`whatsnew-7.0-table-masked-quantity`
 * :ref:`whatsnew-7.0-table-frames`
 * :ref:`whatsnew-7.0-table-notebook-backends`
-* :ref:`whatsnew-7.0-table-init-columns-order`
 * :ref:`whatsnew_7_0_quantity_to_string_formatter`
 * :ref:`whatsnew_7_0_numpy_constructors_like_quantity`
 * :ref:`whatsnew_7_0_ecsv_meta_default_dict`
@@ -26,7 +25,6 @@ In particular, this release includes:
 * :ref:`whatsnew_7_0_rgb_image_visualization_enhancement`
 * :ref:`whatsnew_7_0_lorentz2d_model`
 * :ref:`whatsnew_7_0_votable_1_5`
-* :ref:`whatsnew_7_0_simplenorm`
 
 In addition to these major changes, Astropy v7.0 includes a large number of
 smaller improvements and bug fixes, which are described in the :ref:`changelog`.
@@ -79,43 +77,6 @@ backends for rendering Astropy tables in Jupyter notebooks.
    :width: 450px
    :alt: Animated DataGrid usage example from ipydatagrid
 
-.. _whatsnew-7.0-table-init-columns-order:
-
-Ordering of table columns constructed from rows
-===============================================
-
-The column order in a `~astropy.table.Table` constructed from a `list` or rows (`dict`
-or `~astropy.table.Row`) may change using ``astropy`` version ``7.0`` *if the
-first row has missing values*.
-
-Before ``7.0``, the column ordering was determined from the first row if it contained
-values for all the columns, or by sorting the final column names alphabetically if it
-did not. Starting with ``7.0``, columns are always added in the order they appear
-when iterating over  the `list` of rows.
-
-For example, create a table as shown below:
-
-    >>> from astropy.table import Table
-    >>> data = [{'b': 10, 'c': 7, },
-    ...         {'a': 15, 'c': 35, 'b': 20}]
-    >>> t = Table(data)  # or Table(rows=data), which is equivalent
-
-
-Before ``7.0`` the table would look like this::
-
-     a   b   c
-    --- --- ---
-     --  10   7
-     15  20  35
-
-
-Starting with ``7.0`` the table would instead look like this::
-
-     b   c   a
-    --- --- ---
-     10   7  --
-     20  35  15
-
 .. _whatsnew_7_0_quantity_to_string_formatter:
 
 ``Quantity.to_string`` supports ``formatter`` for formatting
@@ -356,43 +317,6 @@ now supports version 1.5 of the VOTable standard.  The main new feature is that
 At this writing, version 1.5 is a proposed standard, but it is expected to be approved as an
 official recommendation soon.
 
-.. _whatsnew_7_0_simplenorm:
-
-New ``SimpleNorm`` class
-========================
-
-A new convenience class, :class:`~astropy.visualization.SimpleNorm`,
-has been added to the ``astropy.visualization`` module.
-This class provides a simple interface to create
-a :class:`~astropy.visualization.ImageNormalize`
-normalization object that can be used with Matplotlib's
-:meth:`~matplotlib.axes.Axes.imshow` method. It also provides
-a :meth:`~astropy.visualization.SimpleNorm.imshow` method that
-wraps Matplotlib's :meth:`matplotlib.axes.Axes.imshow` method and
-automatically sets the normalization.
-
-Here's an example using the :func:`~astropy.visualization.SimpleNorm`
-function with its :meth:`~astropy.visualization.SimpleNorm.imshow`
-method:
-
-.. plot::
-    :include-source:
-
-    import numpy as np
-    import matplotlib.pyplot as plt
-    from astropy.visualization import SimpleNorm
-
-    # Generate a test image
-    image = np.arange(65536).reshape((256, 256))
-
-    # Create an ImageNormalize object
-    snorm = SimpleNorm('sqrt', percent=98)
-
-    # Display the image
-    fig, ax = plt.subplots()
-    axim = snorm.imshow(image, ax=ax, origin='lower')
-    fig.colorbar(axim)
-
 Full change log
 ===============
 


Test patch:
diff --git a/astropy/io/fits/hdu/compressed/tests/test_compressed.py b/astropy/io/fits/hdu/compressed/tests/test_compressed.py
index 9db87c6f03..04bd5de659 100644
--- a/astropy/io/fits/hdu/compressed/tests/test_compressed.py
+++ b/astropy/io/fits/hdu/compressed/tests/test_compressed.py
@@ -239,6 +239,8 @@ class TestCompressedImage(FitsTestCase):
         # it
         with fits.open(self.data("scale.fits"), do_not_scale_image_data=True) as hdul:
             chdu = fits.CompImageHDU(data=hdul[0].data, header=hdul[0].header)
+            chdu.header["BZERO"] = hdul[0].header["BZERO"]
+            chdu.header["BSCALE"] = hdul[0].header["BSCALE"]
             chdu.writeto(self.temp("scale.fits"))
         mtime = os.stat(self.temp("scale.fits")).st_mtime
 
@@ -354,6 +356,8 @@ class TestCompressedImage(FitsTestCase):
         # Create a compressed version of the scaled image
         with fits.open(self.data("scale.fits"), do_not_scale_image_data=True) as hdul:
             chdu = fits.CompImageHDU(data=hdul[0].data, header=hdul[0].header)
+            chdu.header["BZERO"] = hdul[0].header["BZERO"]
+            chdu.header["BSCALE"] = hdul[0].header["BSCALE"]
             chdu.writeto(self.temp("scale.fits"))
 
         with fits.open(self.temp("scale.fits"), mode="update", scale_back=True) as hdul:
@@ -1005,11 +1009,11 @@ class TestCompHDUSections:
         )
 
         header2 = fits.Header()
-        header2["BSCALE"] = 2
-        header2["BZERO"] = 100
         hdu2 = fits.CompImageHDU(
             self.data, header2, compression_type="RICE_1", tile_shape=(5, 4, 5)
         )
+        hdu2.header["BSCALE"] = 2
+        hdu2.header["BZERO"] = 100
         hdulist = fits.HDUList([fits.PrimaryHDU(), hdu1, hdu2])
         hdulist.writeto(tmp_path / "sections.fits")
 
@@ -1126,7 +1130,6 @@ def test_uint_option(tmp_path):
         assert_allclose(hdulist[1].data, data)
 
 
-@pytest.mark.xfail
 def test_incorrect_bzero(tmp_path):
     """
     Regression test for https://github.com/astropy/astropy/issues/5999 which is
@@ -1158,6 +1161,7 @@ def test_incorrect_bzero(tmp_path):
         for hdu in hdulist_read:
             assert hdu.header.get("BZERO") is None
             assert hdu.header.get("BSCALE") is None
+        np.testing.assert_array_equal(hdulist_read[1].data, hdulist_read[2].data)
 
 
 def test_custom_extname():
diff --git a/astropy/table/tests/test_init_table.py b/astropy/table/tests/test_init_table.py
index c4aa058df2..413fdf820a 100644
--- a/astropy/table/tests/test_init_table.py
+++ b/astropy/table/tests/test_init_table.py
@@ -197,7 +197,6 @@ class TestInitFromListOfDicts(BaseInitFromListLike):
     def _setup(self, table_type):
         self.data = [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4, "c": 5}]
         self.data_ragged = [{"a": 1, "b": 2}, {"a": 2, "c": 4}]
-        self.data_acb = [{"a": 2, "c": 4}, {"a": 1, "b": 2}]
 
     def test_names(self, table_type):
         self._setup(table_type)
@@ -209,14 +208,6 @@ class TestInitFromListOfDicts(BaseInitFromListLike):
         t = table_type(self.data, names=("c", "b", "a"))
         assert t.colnames == ["c", "b", "a"]
 
-    def test_rows_without_names_args(self, table_type):
-        # see https://github.com/astropy/astropy/pull/15735
-        self._setup(table_type)
-        t1 = table_type(rows=self.data)
-        assert t1.colnames == ["a", "b", "c"]
-        t2 = table_type(rows=self.data_acb)
-        assert t2.colnames == ["a", "c", "b"]
-
     def test_missing_data_init_from_dict(self, table_type):
         self._setup(table_type)
         dat = self.data_ragged
@@ -261,7 +252,6 @@ class TestInitFromListOfMapping(TestInitFromListOfDicts):
     def _setup(self, table_type):
         self.data = [DictLike(a=1, b=2, c=3), DictLike(a=3, b=4, c=5)]
         self.data_ragged = [DictLike(a=1, b=2), DictLike(a=2, c=4)]
-        self.data_acb = [DictLike(a=2, c=4), DictLike(a=1, b=2)]
         # Make sure data rows are not a dict subclass
         assert not isinstance(self.data[0], dict)
 
diff --git a/astropy/table/tests/test_table.py b/astropy/table/tests/test_table.py
index fe883df593..3c34f65b7a 100644
--- a/astropy/table/tests/test_table.py
+++ b/astropy/table/tests/test_table.py
@@ -1784,17 +1784,6 @@ def test_rows_equal():
     )
 
 
-def test_table_from_rows():
-    # see https://github.com/astropy/astropy/issues/5923
-    t1 = Table()
-    t1["a"] = [1, 2, 3]
-    t1["b"] = [2.0, 3.0, 4.0]
-
-    rows = [row for row in t1]  # noqa: C416
-    t2 = Table(rows=rows)
-    assert_array_equal(t2.colnames, t1.colnames)
-
-
 def test_equality_masked():
     t = table.Table.read(
         [
diff --git a/astropy/visualization/tests/test_norm.py b/astropy/visualization/tests/test_norm.py
index 093540aaf1..0e703680a5 100644
--- a/astropy/visualization/tests/test_norm.py
+++ b/astropy/visualization/tests/test_norm.py
@@ -7,12 +7,7 @@ from numpy.testing import assert_allclose, assert_array_equal, assert_equal
 
 from astropy.utils.compat.optional_deps import HAS_MATPLOTLIB, HAS_PLT
 from astropy.visualization.interval import ManualInterval, PercentileInterval
-from astropy.visualization.mpl_normalize import (
-    ImageNormalize,
-    SimpleNorm,
-    imshow_norm,
-    simple_norm,
-)
+from astropy.visualization.mpl_normalize import ImageNormalize, imshow_norm, simple_norm
 from astropy.visualization.stretch import LogStretch, PowerStretch, SqrtStretch
 
 DATA = np.linspace(0.0, 15.0, 6)
@@ -286,55 +281,31 @@ class TestImageScaling:
             simple_norm(DATA2, stretch="invalid")
 
 
-@pytest.mark.skipif(not HAS_PLT, reason="requires matplotlib.pyplot")
-@pytest.mark.parametrize("stretch", ["linear", "sqrt", "power", "log", "asinh", "sinh"])
-def test_simplenorm(stretch):
-    data = np.arange(25).reshape((5, 5))
-    snorm = SimpleNorm(stretch, percent=99)
-    norm = snorm(data)
-    assert isinstance(norm, ImageNormalize)
-    assert_allclose(norm(data), simple_norm(data, stretch, percent=99)(data))
-
-
-@pytest.mark.skipif(not HAS_PLT, reason="requires matplotlib.pyplot")
-def test_simplenorm_imshow():
-    import matplotlib.pyplot as plt
-    from matplotlib.image import AxesImage
-
-    data = np.arange(25).reshape((5, 5))
-    fig, ax = plt.subplots()
-    snorm = SimpleNorm("sqrt", percent=99)
-    axim = snorm.imshow(data, ax=ax)
-    assert isinstance(axim, AxesImage)
-    keys = ("vmin", "vmax", "stretch", "clip", "invalid")
-    for key in keys:
-        assert getattr(axim.norm, key) == getattr(snorm(data), key)
-
-    fig.clear()
-    axim = snorm.imshow(data, ax=None)
-
-    with pytest.raises(ValueError):
-        snorm.imshow(data, ax=ax, norm=ImageNormalize())
-
-
 @pytest.mark.skipif(not HAS_PLT, reason="requires matplotlib.pyplot")
 def test_imshow_norm():
     import matplotlib.pyplot as plt
 
     image = np.random.randn(10, 10)
 
-    fig, ax = plt.subplots(label="test_imshow_norm")
+    plt.clf()
+    ax = plt.subplot(label="test_imshow_norm")
     imshow_norm(image, ax=ax)
 
+    with pytest.raises(ValueError):
+        # X and data are the same, can't give both
+        imshow_norm(image, X=image, ax=ax)
+
     with pytest.raises(ValueError):
         # illegal to manually pass in normalization since that defeats the point
         imshow_norm(image, ax=ax, norm=ImageNormalize())
 
-    fig.clear()
+    plt.clf()
     imshow_norm(image, ax=ax, vmin=0, vmax=1)
 
     # make sure the pyplot version works
-    fig.clear()
+    plt.clf()
     imres, norm = imshow_norm(image, ax=None)
 
     assert isinstance(norm, ImageNormalize)
+
+    plt.close("all")