Repository: astropy/astropy

Issue:
# io.fits.getdata() lower and upper keywords broken

astropy 1.0.2 broke the functionality of the `lower` and `upper` arguments to astropy.io.fits.getdata when reading binary fits tables.  Prior to 1.0.2, the returned FITS_rec object properly handled case insensitive column names, while allowing the lower/upper keywords to specify what should be used if those objects were cast to normal numpy.array objects or written back out.  Starting with astropy 1.0.2, this broke; using the `lower` or `upper` arguments resulted in _neither_ case working.  The code below demonstrates the problem:

```
import astropy
print astropy.__version__

from astropy.table import Table
t = Table([np.zeros(5), np.ones(5), np.arange(5)], names=['a', 'b', 'c'])
t.write('lower.fits')
t = Table([np.zeros(5), np.ones(5), np.arange(5)], names=['A', 'B', 'C'])
t.write('upper.fits')

#- Without the lower/upper keywords, columns can be accessed with either
#- upper- or lower-case names; the .dtype.names reflects whatever was in
#- the source fits file
from astropy.io import fits
lower = fits.getdata('lower.fits', 1)
upper = fits.getdata('upper.fits', 1)

print lower.dtype.names
print upper.dtype.names

np.all(lower['a'] == lower['A'])    #- Works
np.all(upper['a'] == upper['A'])    #- Works

#- Prior to astropy 1.0.2, the upper/lower keywords affected the case of
#- .dtype.names and how they are cast to normal numpy.array objects, while
#- still supporting case-insensitive column names.  This broke with
#- astropy 1.0.2, where neither upper- nor lower-case works after using
#- the upper/lower keywords.
lowup = fits.getdata('lower.fits', 1, upper=True)
lowup.dtype.names
lowup['A']              #- KeyError
lowup['a']              #- KeyError
lowup.A                 #- Works
np.array(lowup)['A']    #- Works

uplow = fits.getdata('upper.fits', 1, lower=True)
uplow.dtype.names
uplow['A']              #- KeyError
uplow['a']              #- KeyError
uplow.a                 #- Works
np.array(uplow)['a']    #- Works
```


Implementation patch:
diff --git a/astropy/io/fits/convenience.py b/astropy/io/fits/convenience.py
index 3253ba9caa2f..83b64816071d 100644
--- a/astropy/io/fits/convenience.py
+++ b/astropy/io/fits/convenience.py
@@ -251,7 +251,9 @@ def getdata(filename, *args, header=None, lower=None, upper=None, view=None, **k
         if data.dtype.descr[0][0] == "":
             # this data does not have fields
             return
-        data.dtype.names = [trans(n) for n in data.dtype.names]
+
+        for n in data.dtype.names:
+            data.columns.change_name(n, trans(n))
 
     # allow different views into the underlying ndarray.  Keep the original
     # view just in case there is a problem


Test patch:
diff --git a/astropy/io/fits/tests/test_convenience.py b/astropy/io/fits/tests/test_convenience.py
index 14c021dadea2..a699f0b30534 100644
--- a/astropy/io/fits/tests/test_convenience.py
+++ b/astropy/io/fits/tests/test_convenience.py
@@ -513,3 +513,22 @@ def test_getdata_ext_not_given_nodata_noext(self):
             IndexError, match="No data in Primary HDU and no extension HDU found."
         ):
             fits.getdata(buf)
+
+    def test_getdata_lower_upper(self, tmp_path):
+        t = Table([np.zeros(5), np.ones(5), np.arange(5)], names=["a", "b", "c"])
+        t.write(tmp_path / "lower.fits")
+        t = Table([np.zeros(5), np.ones(5), np.arange(5)], names=["A", "B", "C"])
+        t.write(tmp_path / "upper.fits")
+
+        ref = np.zeros(5)
+        lowup = fits.getdata(tmp_path / "lower.fits", 1, upper=True)
+        assert lowup.dtype.names == ("A", "B", "C")
+        assert_array_equal(lowup["A"], ref)
+        assert_array_equal(lowup["a"], ref)
+        assert_array_equal(lowup.A, ref)
+
+        uplow = fits.getdata(tmp_path / "upper.fits", 1, lower=True)
+        assert uplow.dtype.names == ("a", "b", "c")
+        assert_array_equal(uplow["A"], ref)
+        assert_array_equal(uplow["a"], ref)
+        assert_array_equal(uplow.a, ref)