Skip to content

Commit

Permalink
Add generic projective frame.
Browse files Browse the repository at this point in the history
Good for representing images from unknown instruments.
  • Loading branch information
DanRyanIrish committed Nov 15, 2024
1 parent ea833d9 commit bb74daa
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 0 deletions.
Binary file added xrayvision/coordinates/.frames.py.swp
Binary file not shown.
Empty file.
119 changes: 119 additions & 0 deletions xrayvision/coordinates/frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import astropy.units as u
import astropy.coordinates
from astropy.wcs import WCS
from sunpy.coordinates.frameattributes import ObserverCoordinateAttribute
from sunpy.coordinates.frames import HeliographicStonyhurst, SunPyBaseCoordinateFrame
from sunpy.sun.constants import radius as _RSUN

__all__ = ["Projective"]


X_CTYPE = "PJLN-TAN"
Y_CTYPE = "PJLT-TAN"


class Projective(SunPyBaseCoordinateFrame):
"""A generic projective coordinate frame for a imaging taken by an arbitrary imager."""
observer = ObserverCoordinateAttribute(HeliographicStonyhurst)
rsun = astropy.coordinates.QuantityAttribute(default=_RSUN, unit=u.km)
frame_specific_representation_info = {
astropy.coordinates.SphericalRepresentation: [
astropy.coordinates.RepresentationMapping("lon", "Tx", u.arcsec),
astropy.coordinates.RepresentationMapping("lat", "Ty", u.arcsec),
astropy.coordinates.RepresentationMapping("distance", "distance"),
],
astropy.coordinates.UnitSphericalRepresentation: [
astropy.coordinates.RepresentationMapping("lon", "Tx", u.arcsec),
astropy.coordinates.RepresentationMapping("lat", "Ty", u.arcsec),
],
}


def projective_wcs_to_frame(wcs):
r"""
This function registers the coordinate frames to their FITS-WCS coordinate
type values in the `astropy.wcs.utils.wcs_to_celestial_frame` registry.
Parameters
----------
wcs : `astropy.wcs.WCS`
Returns
-------
: `Projective`
"""
if hasattr(wcs, "coordinate_frame"):
return wcs.coordinate_frame

Check warning on line 46 in xrayvision/coordinates/frames.py

View check run for this annotation

Codecov / codecov/patch

xrayvision/coordinates/frames.py#L46

Added line #L46 was not covered by tests

# Not a lat,lon coordinate system bail out early
if set(wcs.wcs.ctype) != {X_CTYPE, Y_CTYPE}:
return None

dateavg = wcs.wcs.dateobs

rsun = wcs.wcs.aux.rsun_ref
if rsun is not None:
rsun *= u.m

Check warning on line 56 in xrayvision/coordinates/frames.py

View check run for this annotation

Codecov / codecov/patch

xrayvision/coordinates/frames.py#L56

Added line #L56 was not covered by tests

hgs_longitude = wcs.wcs.aux.hgln_obs
hgs_latitude = wcs.wcs.aux.hglt_obs
hgs_distance = wcs.wcs.aux.dsun_obs

observer = HeliographicStonyhurst(
lat=hgs_latitude * u.deg, lon=hgs_longitude * u.deg, radius=hgs_distance * u.m, obstime=dateavg, rsun=rsun
)

frame_args = {"obstime": dateavg, "observer": observer, "rsun": rsun}

return Projective(**frame_args)


def projective_frame_to_wcs(frame, projection="TAN"):
r"""
For a given frame, this function returns the corresponding WCS object.
It registers the WCS coordinates types from their associated frame in the
`astropy.wcs.utils.celestial_frame_to_wcs` registry.
Parameters
----------
frame : `Projective`
projection : `str`, optional
Returns
-------
`astropy.wcs.WCS`
"""
# Bail out early if not STIXImaging frame
if not isinstance(frame, Projective):
return None

wcs = WCS(naxis=2)
wcs.wcs.aux.rsun_ref = frame.rsun.to_value(u.m)

# Sometimes obs_coord can be a SkyCoord, so convert down to a frame
obs_frame = frame.observer
if hasattr(obs_frame, "frame"):
obs_frame = frame.observer.frame

Check warning on line 97 in xrayvision/coordinates/frames.py

View check run for this annotation

Codecov / codecov/patch

xrayvision/coordinates/frames.py#L97

Added line #L97 was not covered by tests

wcs.wcs.aux.hgln_obs = obs_frame.lon.to_value(u.deg)
wcs.wcs.aux.hglt_obs = obs_frame.lat.to_value(u.deg)
wcs.wcs.aux.dsun_obs = obs_frame.radius.to_value(u.m)

wcs.wcs.dateobs = frame.obstime.utc.iso
wcs.wcs.cunit = ["arcsec", "arcsec"]
wcs.wcs.ctype = [X_CTYPE, Y_CTYPE]

return wcs


# Remove once min version of sunpy has https://github.com/sunpy/sunpy/pull/7594
astropy.wcs.utils.WCS_FRAME_MAPPINGS.insert(1, [projective_wcs_to_frame])
astropy.wcs.utils.FRAME_WCS_MAPPINGS.insert(1, [projective_frame_to_wcs])

PROJECTIVE_CTYPE_TO_UCD1 = {
"SXLT": "custom:pos.projective.lat",
"SXLN": "custom:pos.projective.lon",
"SXRZ": "custom:pos.projective.z",
}
astropy.wcs.wcsapi.fitswcs.CTYPE_TO_UCD1.update(PROJECTIVE_CTYPE_TO_UCD1)
Binary file not shown.
Empty file.
72 changes: 72 additions & 0 deletions xrayvision/coordinates/tests/test_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import astropy.units as u
import numpy as np
import pytest
from astropy.wcs import WCS
from sunpy.coordinates import HeliographicStonyhurst
from xrayvision.coordinates.frames import Projective, projective_frame_to_wcs, projective_wcs_to_frame


@pytest.fixture
def projective_wcs():
w = WCS(naxis=2)

w.wcs.dateobs = "2024-01-01 00:00:00.000"
w.wcs.crpix = [10, 20]
w.wcs.cdelt = np.array([2, 2])
w.wcs.crval = [0, 0]
w.wcs.ctype = ["PJLN-TAN", "PJLT-TAN"]

w.wcs.aux.hgln_obs = 10
w.wcs.aux.hglt_obs = 20
w.wcs.aux.dsun_obs = 1.5e11

return w


@pytest.fixture
def projective_frame():
obstime = "2024-01-01"
observer = HeliographicStonyhurst(10 * u.deg, 20 * u.deg, 1.5e11 * u.m, obstime=obstime)

frame_args = {"obstime": obstime, "observer": observer, "rsun": 695_700_000 * u.m}

frame = Projective(**frame_args)
return frame


def test_projective_wcs_to_frame(projective_wcs):
frame = projective_wcs_to_frame(projective_wcs)
assert isinstance(frame, Projective)

assert frame.obstime.isot == "2024-01-01T00:00:00.000"
assert frame.rsun == 695700 * u.km
assert frame.observer == HeliographicStonyhurst(
10 * u.deg, 20 * u.deg, 1.5e11 * u.m, obstime="2024-01-01T00:00:00.000"
)


def test_projective_wcs_to_frame_none():
w = WCS(naxis=2)
w.wcs.ctype = ["ham", "cheese"]
frame = projective_wcs_to_frame(w)

assert frame is None


def test_projective_frame_to_wcs(projective_frame):
wcs = projective_frame_to_wcs(projective_frame)

assert isinstance(wcs, WCS)
assert wcs.wcs.ctype[0] == "PJLN-TAN"
assert wcs.wcs.cunit[0] == "arcsec"
assert wcs.wcs.dateobs == "2024-01-01 00:00:00.000"

assert wcs.wcs.aux.rsun_ref == projective_frame.rsun.to_value(u.m)
assert wcs.wcs.aux.dsun_obs == 1.5e11
assert wcs.wcs.aux.hgln_obs == 10
assert wcs.wcs.aux.hglt_obs == 20


def test_projective_frame_to_wcs_none():
wcs = projective_frame_to_wcs(HeliographicStonyhurst())
assert wcs is None

0 comments on commit bb74daa

Please sign in to comment.