Repository: sunpy/sunpy

Issue:
Remove sunpy.instr.aia.aiaprep
The `aiaprep` function should be removed from the `sunpy.instr` subpackage. Any AIA specific functionality should transition to the new [`aiapy` package](https://gitlab.com/LMSAL_HUB/aia_hub/aiapy).

This is part of the broader goal of moving instrument-specific functionality out of the core sunpy package and into affiliated packages.


Discussion:
We'd have to go through a deprecation period before removal.  What's the planned timeline for `aiapy`?

A related question: what about `AIAMap`?  Should `aiapy` own that?
I agree this should happen. I would want a release of aiapy to exist before we do this, so that the deprecation warning can point people to the new thing.

 > A related question: what about AIAMap ? Should aiapy own that?

I am not in favour of this. I would want core to be able to open files from as many instruments as possible without needing extra packages.
> > A related question: what about AIAMap ? Should aiapy own that?
> 
> I am not in favour of this. I would want core to be able to open files from as many instruments as possible without needing extra packages.

It depends on how you define "open files".  We'd always be able to open arbitrary FITS files in some default manner.  The issue is that, by retaining `AIAMap`, we take responsibility for interpreting the header properly (e.g., the whole observer-coordinate business), plus we wouldn't be able to offer users the latest orientation calibration (in the case of stale headers).

Furthermore, what if an instrument wants to own their `Map`-derived class?  What if `aiapy` wants to have a `Map` class that does stuff like auto-detect needing to run `aia_prep`?  Do they hack SunPy's `AIAMap` as they import?  Do they replace the whole class?  Do they offer an alternative class (e.g., `TrueAIAMap`)?  All of the above seem undesirable to me.
> We'd have to go through a deprecation period before removal. What's the planned timeline for aiapy?

👍 

> I agree this should happen. I would want a release of aiapy to exist before we do this, so that the deprecation warning can point people to the new thing.

There's not really a strict timeline either for development of the package or for a formal release of `aiapy`. Tentatively I would say there will be release (v0.1) in the next six months, but it could be sooner. I will update here as I know more.

> Furthermore, what if an instrument wants to own their Map-derived class? What if aiapy wants to have a Map class that does stuff like auto-detect needing to run aia_prep? Do they hack SunPy's AIAMap as they import? Do they replace the whole class? Do they offer an alternative class (e.g., TrueAIAMap)? All of the above seem undesirable to me.

I agree that instrument-specific tweaks should not be the responsibility`sunpy` core and I can definitely envision this scenario of many alternative diverging map implementations. However, the same may happen if instrument-specific map classes are farmed out to (future) instrument-specific affiliated packages. This is a much larger discussion and really deserves its own issue(s). At least in the short term, I do not think `aiapy` should own `AIAMap`.

For what it is worth, in `aiapy`, the current preferred approach is to make all of these things (e.g. aiaprep, header fixes, etc) explicit, i.e. the user has to pass their map to a function and get a corrected map out rather than having these things done "automagically" at import. For example, see [this issue](https://gitlab.com/LMSAL_HUB/aia_hub/aiapy/issues/14).
There's currently [a PR to the aiapy repo to implement aiaprep](https://gitlab.com/LMSAL_HUB/aia_hub/aiapy/merge_requests/23). It is essentially the exact version that is in `sunpy/instr/aia.py` though it also addresses #3307. I suspect it will be merged within a week or less.
In light of SEP-0009, does this mean that a deprectation warning cannot be added until v2.0 and then the actual bit of code cannot be removed until v3.0?
We can add a deprecation warning at any time, and it has to be in the code for at least one LTS (i.e for the 2.0 release). So if you added it now, the deprecation warning would be in 1.1, 2.0 and then we can remove the code before 2.1 (so in ~1 year).

I wouldn't want to merge a deprecation warning until aiapy has an official release though (deprecation warnings should always present a currently available way of not getting the warning).
Ok that sounds good.

I don't have a definite timeline for an aiapy release yet, but ideally it will happen before the end of the year. Not sure if that will be before v1.1 or not.
Planning on getting 1.1 out the door no later than the end of Oct.
Re-opening because `aiaprep` was only deprecated, not removed (yet).
If we don't want to keep open an issue to remind us to remove a function that has been deprecated, then change the title of this issue so it doesn't look like we've done something that we haven't.

Resolving patch:
diff --git a/sunpy/instr/aia.py b/sunpy/instr/aia.py
deleted file mode 100644
--- a/sunpy/instr/aia.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""
-This module provides processing routines for data captured with the AIA
-instrument on SDO.
-"""
-import numpy as np
-
-import astropy.units as u
-
-from sunpy.map.sources.sdo import AIAMap, HMIMap
-from sunpy.util.decorators import deprecated
-
-__all__ = ['aiaprep']
-
-
-@deprecated("2.0", alternative="`register` in aiapy (https://aiapy.readthedocs.io) for converting \
-AIA images to level 1.5")
-def aiaprep(aiamap):
-    """
-    Processes a level 1 `~sunpy.map.sources.sdo.AIAMap` into a level 1.5
-    `~sunpy.map.sources.sdo.AIAMap`.
-
-    Rotates, scales and translates the image so that solar North is aligned
-    with the y axis, each pixel is 0.6 arcsec across, and the center of the
-    Sun is at the center of the image. The actual transformation is done by Map's
-    `~sunpy.map.mapbase.GenericMap.rotate` method.
-
-    This function is similar in functionality to ``aia_prep`` in SSWIDL, but
-    it does not use the same transformation to rotate the image and it handles
-    the meta data differently. It should therefore not be expected to produce
-    the same results.
-
-    Parameters
-    ----------
-    aiamap : `~sunpy.map.sources.sdo.AIAMap`
-        A `sunpy.map.Map` from AIA.
-
-    Returns
-    -------
-    `~sunpy.map.sources.sdo.AIAMap`:
-        A level 1.5 copy of `~sunpy.map.sources.sdo.AIAMap`.
-
-    Notes
-    -----
-    This routine modifies the header information to the standard PCi_j WCS
-    formalism. The FITS header resulting in saving a file after this
-    procedure will therefore differ from the original file.
-    """
-
-    if not isinstance(aiamap, (AIAMap, HMIMap)):
-        raise ValueError("Input must be an AIAMap or HMIMap.")
-
-    # Target scale is 0.6 arcsec/pixel, but this needs to be adjusted if the map
-    # has already been rescaled.
-    if ((aiamap.scale[0] / 0.6).round() != 1.0 * u.arcsec / u.pix
-            and aiamap.data.shape != (4096, 4096)):
-        scale = (aiamap.scale[0] / 0.6).round() * 0.6 * u.arcsec
-    else:
-        scale = 0.6 * u.arcsec  # pragma: no cover # can't test this because it needs a full res image
-    scale_factor = aiamap.scale[0] / scale
-
-    tempmap = aiamap.rotate(recenter=True, scale=scale_factor.value, missing=aiamap.min())
-
-    # extract center from padded aiamap.rotate output
-    # crpix1 and crpix2 will be equal (recenter=True), as aiaprep does not work with submaps
-    center = np.floor(tempmap.meta['crpix1'])
-    range_side = (center + np.array([-1, 1]) * aiamap.data.shape[0] / 2) * u.pix
-    newmap = tempmap.submap(u.Quantity([range_side[0], range_side[0]]),
-                            u.Quantity([range_side[1] - 1 * u.pix,
-                                        range_side[1] - 1 * u.pix]))
-
-    newmap.meta['r_sun'] = newmap.meta['rsun_obs'] / newmap.meta['cdelt1']
-    newmap.meta['lvl_num'] = 1.5
-    newmap.meta['bitpix'] = -64
-
-    return newmap


Test patch:
diff --git a/sunpy/instr/tests/test_aia.py b/sunpy/instr/tests/test_aia.py
deleted file mode 100644
--- a/sunpy/instr/tests/test_aia.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import tempfile
-
-import numpy as np
-import pytest
-
-from astropy.io.fits.verify import VerifyWarning
-
-import sunpy.data.test as test
-import sunpy.map
-from sunpy.instr.aia import aiaprep
-from sunpy.util.exceptions import SunpyDeprecationWarning
-
-
-# Define the original and prepped images first so they're available to all
-# functions
-@pytest.fixture(scope="module",
-                params=[test.get_test_filepath("aia_171_level1.fits"),
-                        test.get_test_filepath("resampled_hmi.fits")])
-def original(request):
-    return sunpy.map.Map(request.param)
-
-
-@pytest.fixture
-def prep_map(original):
-    with pytest.warns(SunpyDeprecationWarning):
-        return aiaprep(original)
-
-
-def test_aiaprep(original, prep_map):
-    # Test that header info for the map has been correctly updated
-    # Check all of these for Map attributes and .meta values?
-    # Check array shape
-    assert prep_map.data.shape == original.data.shape
-    # Check crpix values
-    assert prep_map.meta['crpix1'] == prep_map.data.shape[1] / 2.0 + 0.5
-    assert prep_map.meta['crpix2'] == prep_map.data.shape[0] / 2.0 + 0.5
-    # Check cdelt values
-    assert prep_map.meta['cdelt1'] / 0.6 == int(prep_map.meta['cdelt1'] / 0.6)
-    assert prep_map.meta['cdelt2'] / 0.6 == int(prep_map.meta['cdelt2'] / 0.6)
-    # Check rotation value, I am assuming that the inaccuracy in
-    # the CROTA -> PCi_j matrix is causing the inaccuracy here
-    np.testing.assert_allclose(
-        prep_map.rotation_matrix, np.identity(2), rtol=1e-5, atol=1e-8)
-    # Check level number
-    assert prep_map.meta['lvl_num'] == 1.5
-
-
-def test_filesave(prep_map):
-    # Test that adjusted header values are still correct after saving the map
-    # and reloading it.
-    afilename = tempfile.NamedTemporaryFile(suffix='.fits').name
-    with pytest.warns(
-            VerifyWarning, match="The 'BLANK' keyword is only applicable to integer data"):
-        prep_map.save(afilename, overwrite=True)
-    load_map = sunpy.map.Map(afilename)
-    # Check crpix values
-    assert load_map.meta['crpix1'] == prep_map.data.shape[1] / 2.0 + 0.5
-    assert load_map.meta['crpix2'] == prep_map.data.shape[0] / 2.0 + 0.5
-    # Check cdelt values
-    assert load_map.meta['cdelt1'] / 0.6 == int(load_map.meta['cdelt1'] / 0.6)
-    assert load_map.meta['cdelt2'] / 0.6 == int(load_map.meta['cdelt2'] / 0.6)
-    # Check rotation value
-    np.testing.assert_allclose(
-        prep_map.rotation_matrix, np.identity(2), rtol=1e-5, atol=1e-8)
-    # Check level number
-    assert load_map.meta['lvl_num'] == 1.5