Repository: astropy/astropy

Issue:
Bug introduced by PR #17086 deleting mask for MaskedArray input
### Description

As a result of PR #17086, the mask of a `MaskedArray` input is lost in circumstances where the changed code is executed, producing incorrect output results. This is because the array is copied with `np.copy(data)` instead of `data.copy()`, which will use the `np.ma.copy()` function if `data` is a `MaskedArray`. The fix is straightforward and I'm working on a PR but I need to set everything up and write a test, etc.

### Expected behavior

In this example, errant data have been masked so the statistics should only be calculated for the elements with value 1, and hence the mean should be 1.0 and the stddev 0.0. (The `grow` parameter is included here since, as stated in PR #17086, this part of the code isn't executed otherwise.)
```
>>> import numpy as np
>>> from astropy.stats import sigma_clipped_stats
>>> x = np.array([1,1,1,1,1,1,1,1,5,5]).astype(float)
>>> y = np.ma.masked_where(x > 1, x)
>>> print(sigma_clipped_stats(y, grow=1))
(1.0, 1.0, 0.0)
```

### How to Reproduce

The actual results show that the two values of 5 are erroneously included in the stats.
```
>>> import numpy as np
>>> from astropy.stats import sigma_clipped_stats
>>> x = np.array([1,1,1,1,1,1,1,1,5,5]).astype(float)
>>> y = np.ma.masked_where(x > 1, x)
>>> print(sigma_clipped_stats(y, grow=1))
(1.8, 1.0, 1.6)
```

### Versions

```python
import astropy
try:
    astropy.system_info()
except AttributeError:
    import platform; print(platform.platform())
    import sys; print("Python", sys.version)
    import astropy; print("astropy", astropy.__version__)
    import numpy; print("Numpy", numpy.__version__)
    import erfa; print("pyerfa", erfa.__version__)
    try:
        import scipy
        print("Scipy", scipy.__version__)
    except ImportError:
        print("Scipy not installed")
    try:
        import matplotlib
        print("Matplotlib", matplotlib.__version__)
    except ImportError:
        print("Matplotlib not installed")
```
```
macOS-14.1.1-x86_64-i386-64bit
Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:39:40) [Clang 15.0.7 ]
astropy 6.1.6
Numpy 1.25.2
pyerfa 2.0.1.4
Scipy 1.13.0
Matplotlib 3.7.2
```


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.
jehturner: @chris-simpson, I haven't seen the failing check before, but my best guess is that it wants a new line at the end of 17402.bugfix.rst.

Patch:
diff --git a/astropy/stats/sigma_clipping.py b/astropy/stats/sigma_clipping.py
index 334c16a8f7..1acfac5594 100644
--- a/astropy/stats/sigma_clipping.py
+++ b/astropy/stats/sigma_clipping.py
@@ -462,7 +462,7 @@ class SigmaClip:
             # float array type is needed to insert nans into the array
             filtered_data = data.astype(np.float32)  # also makes a copy
         else:
-            filtered_data = np.copy(data)
+            filtered_data = data.copy()
 
         # remove invalid values
         bad_mask = ~np.isfinite(filtered_data)
diff --git a/astropy/visualization/mpl_normalize.py b/astropy/visualization/mpl_normalize.py
index 356a9b186f..81ae0249ee 100644
--- a/astropy/visualization/mpl_normalize.py
+++ b/astropy/visualization/mpl_normalize.py
@@ -84,13 +84,13 @@ class ImageNormalize(Normalize):
 
     def __init__(
         self,
-        data=None,
-        interval=None,
-        vmin=None,
-        vmax=None,
-        stretch=LinearStretch(),
-        clip=False,
-        invalid=-1.0,
+        data: np.ndarray | None = None,
+        interval: BaseInterval | None = None,
+        vmin: float | None = None,
+        vmax: float | None = None,
+        stretch: BaseStretch = LinearStretch(),
+        clip: bool = False,
+        invalid: float | None = -1.0,
     ):
         # this super call checks for matplotlib
         super().__init__(vmin=vmin, vmax=vmax, clip=clip)
diff --git a/docs/changes/stats/17402.bugfix.rst b/docs/changes/stats/17402.bugfix.rst
new file mode 100644
index 0000000000..88cd3c0734
--- /dev/null
+++ b/docs/changes/stats/17402.bugfix.rst
@@ -0,0 +1,2 @@
+Fix an issue in sigma-clipping where the use of ``np.copy()`` was causing
+the input data mask to be discarded in cases where ``grow`` was set.


Test patch:
diff --git a/astropy/stats/tests/test_sigma_clipping.py b/astropy/stats/tests/test_sigma_clipping.py
index 886e0aa140..a77fa96c11 100644
--- a/astropy/stats/tests/test_sigma_clipping.py
+++ b/astropy/stats/tests/test_sigma_clipping.py
@@ -696,3 +696,12 @@ def test_mad_std_large():
     )
 
     assert_allclose(result1, result2)
+
+
+@pytest.mark.skipif(not HAS_SCIPY, reason="test requires scipy")
+def test_propagation_of_mask():
+    # Quick test to check that the mask is propagated correctly
+    x = np.array([1, 1, 1, 1, 1, 1, 1, 1, 5, 5]).astype(float)
+    y = np.ma.masked_where(x > 1, x)
+
+    assert_allclose(sigma_clipped_stats(y, grow=1), (1, 1, 0))