Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for solar frames and non-degree units in find_optimal_celestial_wcs #360

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions reproject/mosaicking/tests/test_wcs_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,58 @@ def test_input_types(valid_celestial_input_shapes, iterable):

assert_wcs_allclose(wcs_test, wcs_ref)
assert shape_test == shape_ref


SOLAR_HEADER = """
CRPIX1 = -1374.571094981584 / [pix]
CRPIX2 = 2081.629159922445 / [pix]
CRDATE1 = '2017-01-01T00:00:00.000'
CRDATE2 = '2017-01-01T00:00:00.000'
CRVAL1 = -619.0078311637853
CRVAL2 = -407.000970936774
CDELT1 = 0.01099999994039536
CDELT2 = 0.01099999994039536
CUNIT1 = 'arcsec '
CUNIT2 = 'arcsec '
CTYPE1 = 'HPLN-TAN'
CTYPE2 = 'HPLT-TAN'
PC1_1 = 0.966887196065055
PC1_2 = -0.01087372434907635
PC2_1 = 0.01173971407248916
PC2_2 = 0.9871195868097251
LONPOLE = 180.0 / [deg]
DATEREF = '2022-06-02T17:22:53.220'
OBSGEO-X= -5466045.256954942 / [m]
OBSGEO-Y= -2404388.737412784 / [m]
OBSGEO-Z= 2242133.887690042 / [m]
SPECSYS = 'TOPOCENT'
VELOSYS = 0.0
"""


@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning")
def test_solar_wcs():
# Regression test for issues that occurred when trying to find
# the optimal WCS for a set of solar WCSes

pytest.importorskip("sunpy", minversion="2.1.0")

# Make sure the WCS <-> frame functions are registered
import sunpy.coordinates

wcs_ref = WCS(fits.Header.fromstring(SOLAR_HEADER, sep="\n"))

wcs1 = wcs_ref.deepcopy()
wcs2 = wcs_ref.deepcopy()
wcs2.wcs.crpix[0] -= 4096

wcs, shape = find_optimal_celestial_wcs([((4096, 4096), wcs1), ((4096, 4096), wcs2)])

wcs.wcs.set()

assert wcs.wcs.ctype[0] == wcs_ref.wcs.ctype[0]
assert wcs.wcs.ctype[1] == wcs_ref.wcs.ctype[1]
assert wcs.wcs.cunit[0] == wcs_ref.wcs.cunit[0]
assert wcs.wcs.cunit[1] == wcs_ref.wcs.cunit[1]

assert shape == (4281, 8237)
40 changes: 25 additions & 15 deletions reproject/mosaicking/wcs_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def find_optimal_celestial_wcs(

# We start off by looping over images, checking that they are indeed
# celestial images, and building up a list of all corners and all reference
# coordinates in celestial (ICRS) coordinates.
# coordinates in the frame of reference of the first image.

corners = []
references = []
Expand Down Expand Up @@ -162,18 +162,19 @@ def find_optimal_celestial_wcs(
xc = np.array([-0.5, nx - 0.5, nx - 0.5, -0.5])
yc = np.array([-0.5, -0.5, ny - 0.5, ny - 0.5])

# We have to do .frame here to make sure that we get an ICRS object
# We have to do .frame here to make sure that we get a frame object
# without any 'hidden' attributes, otherwise the stacking below won't
# work.
corners.append(wcs.pixel_to_world(xc, yc).icrs.frame)
corners.append(wcs.pixel_to_world(xc, yc).transform_to(frame).frame)

if isinstance(wcs, WCS):
# We now figure out the reference coordinate for the image in ICRS. The
# easiest way to do this is actually to use pixel_to_skycoord with the
# reference position in pixel coordinates. We have to set origin=1
# because crpix values are 1-based.
# We now figure out the reference coordinate for the image in the
# frame of the first image. The easiest way to do this is actually
# to use pixel_to_skycoord with the reference position in pixel
# coordinates. We have to set origin=1 because crpix values are
# 1-based.
xp, yp = wcs.wcs.crpix
references.append(pixel_to_skycoord(xp, yp, wcs, origin=1).icrs.frame)
references.append(pixel_to_skycoord(xp, yp, wcs, origin=1).transform_to(frame).frame)

# Find the pixel scale at the reference position - we take the minimum
# since we are going to set up a header with 'square' pixels with the
Expand All @@ -183,7 +184,7 @@ def find_optimal_celestial_wcs(

else:
xp, yp = (nx - 1) / 2, (ny - 1) / 2
references.append(wcs.pixel_to_world(xp, yp).icrs.frame)
references.append(wcs.pixel_to_world(xp, yp).transform_to(frame).frame)

xs = np.array([xp, xp, xp + 1])
ys = np.array([yp, yp + 1, yp])
Expand All @@ -192,7 +193,7 @@ def find_optimal_celestial_wcs(
dy = abs(cs[0].separation(cs[1]).deg)
resolutions.append(min(dx, dy))

# We now stack the coordinates - however the ICRS class can't do this
# We now stack the coordinates - however the frame classes can't do this
# so we have to use the high-level SkyCoord class.
corners = SkyCoord(corners)
references = SkyCoord(references)
Expand All @@ -212,15 +213,24 @@ def find_optimal_celestial_wcs(
if resolution is None:
resolution = np.min(resolutions) * u.deg

# Determine the resolution in degrees
cdelt = resolution.to(u.deg).value

# Construct WCS object centered on position
wcs_final = celestial_frame_to_wcs(frame, projection=projection)

if wcs_final.wcs.cunit[0] == "":
wcs_final.wcs.cunit[0] = "deg"

if wcs_final.wcs.cunit[1] == "":
wcs_final.wcs.cunit[1] = "deg"

rep = reference.represent_as("unitspherical")
wcs_final.wcs.crval = rep.lon.degree, rep.lat.degree
wcs_final.wcs.cdelt = -cdelt, cdelt
wcs_final.wcs.crval = (
rep.lon.to_value(wcs_final.wcs.cunit[0]),
rep.lat.to_value(wcs_final.wcs.cunit[1]),
)
wcs_final.wcs.cdelt = (
-resolution.to_value(wcs_final.wcs.cunit[0]),
resolution.to_value(wcs_final.wcs.cunit[1]),
)

# For now, set crpix to (1, 1) and we'll then figure out where all the
# images fall in this projection, then we'll adjust crpix.
Expand Down