-
Notifications
You must be signed in to change notification settings - Fork 48
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
Fix make_fitswcs_transform error when handling header with no PC #142
Open
pllim
wants to merge
2
commits into
spacetelescope:master
Choose a base branch
from
pllim:read-fits-hdr
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,15 +7,11 @@ | |
import functools | ||
import numpy as np | ||
from astropy.modeling import models as astmodels | ||
from astropy.modeling.models import Mapping | ||
from astropy.modeling import core, projections | ||
from astropy.modeling import core, projections, is_separable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing import is a real bug found by my |
||
from astropy.io import fits | ||
from astropy import coordinates as coords | ||
from astropy import units as u | ||
|
||
from astropy.utils.decorators import deprecated | ||
|
||
|
||
# these ctype values do not include yzLN and yzLT pairs | ||
sky_pairs = {"equatorial": ["RA", "DEC"], | ||
"ecliptic": ["ELON", "ELAT"], | ||
|
@@ -89,7 +85,8 @@ def _get_values(units, *args): | |
---------- | ||
units : str or `~astropy.units.Unit` | ||
Units of the wcs object. | ||
The input values are converted to ``units`` before the values are returned. | ||
The input values are converted to ``units`` before | ||
the values are returned. | ||
""" | ||
val = [] | ||
values = [] | ||
|
@@ -116,7 +113,8 @@ def _compute_lon_pole(skycoord, projection): | |
Compute the longitude of the celestial pole of a standard frame in the | ||
native frame. | ||
|
||
This angle then can be used as one of the Euler angles (the other two being skyccord) | ||
This angle then can be used as one of the Euler angles | ||
(the other two being skycoord) | ||
to rotate the native frame into the standard frame ``skycoord.frame``. | ||
|
||
Parameters | ||
|
@@ -153,7 +151,8 @@ def _compute_lon_pole(skycoord, projection): | |
else: | ||
lon_pole = 180 | ||
else: | ||
raise UnsupportedProjectionError("Projection {0} is not supported.".format(projection)) | ||
raise UnsupportedProjectionError( | ||
"Projection {0} is not supported.".format(projection)) | ||
if unit is not None: | ||
lon_pole = lon_pole * unit | ||
return lon_pole | ||
|
@@ -166,7 +165,8 @@ def get_projcode(wcs_info): | |
return None | ||
projcode = wcs_info['CTYPE'][sky_axes[0]][5:8].upper() | ||
if projcode not in projections.projcodes: | ||
raise UnsupportedProjectionError('Projection code %s, not recognized' % projcode) | ||
raise UnsupportedProjectionError( | ||
'Projection code %s, not recognized' % projcode) | ||
#projcode = None | ||
return projcode | ||
|
||
|
@@ -291,18 +291,23 @@ def get_axes(header): | |
|
||
|
||
def _is_skysys_consistent(ctype, sky_inmap): | ||
""" Determine if the sky axes in CTYPE mathch to form a standard celestial system.""" | ||
""" | ||
Determine if the sky axes in CTYPE mathch to form a | ||
standard celestial system. | ||
""" | ||
|
||
for item in sky_pairs.values(): | ||
if ctype[sky_inmap[0]] == item[0]: | ||
if ctype[sky_inmap[1]] != item[1]: | ||
raise ValueError( | ||
"Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype)) | ||
"Inconsistent ctype for sky coordinates " | ||
"{0} and {1}".format(*ctype)) | ||
break | ||
elif ctype[sky_inmap[1]] == item[0]: | ||
if ctype[sky_inmap[0]] != item[1]: | ||
raise ValueError( | ||
"Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype)) | ||
"Inconsistent ctype for sky coordinates " | ||
"{0} and {1}".format(*ctype)) | ||
sky_inmap = sky_inmap[::-1] | ||
break | ||
|
||
|
@@ -319,7 +324,7 @@ def _is_skysys_consistent(ctype, sky_inmap): | |
} | ||
|
||
|
||
def make_fitswcs_transform(header): | ||
def make_fitswcs_transform(header, dict_to_fits=False): | ||
""" | ||
Create a basic FITS WCS transform. | ||
It does not include distortions. | ||
|
@@ -329,13 +334,23 @@ def make_fitswcs_transform(header): | |
header : astropy.io.fits.Header or dict | ||
FITS Header (or dict) with basic WCS information | ||
|
||
dict_to_fits : bool | ||
Convert given header (if dict) to FITS header | ||
before further processing. | ||
|
||
""" | ||
if not isinstance(header, (fits.Header, dict)): | ||
raise TypeError("Expected a FITS Header or a dict.") | ||
|
||
if isinstance(header, dict) and dict_to_fits: | ||
header = fits.Header(header) | ||
|
||
if isinstance(header, fits.Header): | ||
# This converts CD matrix into PC for linear transformation below. | ||
wcs_info = read_wcs_from_header(header) | ||
elif isinstance(header, dict): | ||
wcs_info = header | ||
else: | ||
raise TypeError("Expected a FITS Header or a dict.") | ||
wcs_info = header | ||
|
||
transforms = [] | ||
wcs_linear = fitswcs_linear(wcs_info) | ||
transforms.append(wcs_linear) | ||
|
@@ -404,7 +419,7 @@ def fitswcs_linear(header): | |
|
||
if not wcs_info['has_cd']: | ||
# Do not compute scaling since CDELT* = 1 if CD is present. | ||
scaling_models = [astmodels.Scale(scale, name='cdelt' + str(i + 1)) \ | ||
scaling_models = [astmodels.Scale(scale, name='cdelt' + str(i + 1)) | ||
for i, scale in enumerate(cdelt)] | ||
|
||
scaling = functools.reduce(lambda x, y: x & y, scaling_models) | ||
|
@@ -443,7 +458,8 @@ def fitswcs_nonlinear(header): | |
# TODO: write "def compute_lonpole(projcode, l)" | ||
# Set a defaul tvalue for now | ||
thetap = 180 | ||
n2c = astmodels.RotateNative2Celestial(phip, lonp, thetap, name="crval") | ||
n2c = astmodels.RotateNative2Celestial( | ||
phip, lonp, thetap, name="crval") | ||
transforms.append(n2c) | ||
if transforms: | ||
return functools.reduce(core._model_oper('|'), transforms) | ||
|
@@ -497,21 +513,23 @@ def separable_axes(wcsobj, start_frame=None, end_frame=None): | |
Computes the separability of axes in ``end_frame``. | ||
|
||
Returns a 1D boolean array of size frame.naxes where True means | ||
the axis is completely separable and False means the axis is nonseparable | ||
from at least one other axis. | ||
the axis is completely separable and False means the axis is | ||
nonseparable from at least one other axis. | ||
|
||
Parameters | ||
---------- | ||
wcsobj : `~gwcs.wcs.WCS` | ||
WCS object | ||
start_frame : `~gwcs.coordinate_frames.CoordinateFrame` | ||
A frame in the WCS pipeline. | ||
The transform between start_frame and the end frame is used to compute the | ||
The transform between start_frame and the end frame | ||
is used to compute the | ||
mapping inputs: outputs. | ||
If None the input_frame is used as start_frame. | ||
end_frame : `~gwcs.coordinate_frames.CoordinateFrame` | ||
A frame in the WCS pipeline. | ||
The transform between start_frame and the end frame is used to compute the | ||
The transform between start_frame and the end frame | ||
is used to compute the | ||
mapping inputs: outputs. | ||
If None wcsobj.output_frame is used. | ||
|
||
|
@@ -525,15 +543,18 @@ def separable_axes(wcsobj, start_frame=None, end_frame=None): | |
start_frame = wcsobj.input_frame | ||
else: | ||
if start_frame not in wcsobj.available_frames: | ||
raise ValueError("Unrecognized frame {0}".format(start_frame)) | ||
raise ValueError( | ||
"Unrecognized frame {0}".format(start_frame)) | ||
if end_frame is None: | ||
end_frame = wcsobj.output_frame | ||
else: | ||
if end_frame not in wcsobj.available_frames: | ||
raise ValueError("Unrecognized frame {0}".format(end_frame)) | ||
raise ValueError( | ||
"Unrecognized frame {0}".format(end_frame)) | ||
transform = wcsobj.get_transform(start_frame, end_frame) | ||
else: | ||
raise ValueError("A starting frame is needed to determine separability of axes.") | ||
raise ValueError("A starting frame is needed to determine " | ||
"separability of axes.") | ||
|
||
sep = is_separable(transform) | ||
return [sep[ax] for ax in end_frame.axes_order] |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nden , I don't understand
w()
result can't agree for defaultrtol=1e-7
w.inverse()
would give me original XY + 1 and not XYAm I using it wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GWCS by default uses 0-based pixel coordinates. CRPIX values are 1-based. So if you want to compare the inverse transform, it's going to return 0-based values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though maybe I'm misunderstanding the problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am just generally confused... What I am seeing is this:
but
All I want to know is if this is the expected behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdavies-st is correct. When you are mixing FITS and gwcs you have to take care of 0- vs 1- based coordinates. The gwcs transform is 0-based. However, the values of crpix are 1-based. If you change the CRPIX values to be 0-based, i.e. subtract 1 from them , you will get agreement (you'll get better agreement in the forward direction too).
BTW, the header has incorrect FITS WCS to start with. It is not allowed to mix CD matrix and CDELT values and in this case the CDELT values are ignored and assumed to be 1.
I don't want to encourage changes and "fixes" to these functions as I think it's time to implement full conversion between the two objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so I have to -1 from the keyword value, not inputs. Gotcha.
So, should I close this and wait for your "full conversion" then?