Astrometric Unwarp

Remove SIP distortion from a plate-solved image, delivering a pixel-for-pixel-correct frame with a clean linear WCS.

What this does. Takes an image with a plate solution that includes SIP (Simple Imaging Polynomial) terms and resamples it onto a distortion-free tangent-plane grid. Every output pixel is projected to the sky through the linear WCS, then back into the original frame through the full SIP WCS, and sampled there. The canvas is expanded automatically so the corners the distortion pushes outward aren't clipped. The result is a brand-new image carrying a clean linear WCS with no SIP terms — ready for mosaicking, downstream analysis, or publication.

1. When you want to unwarp

SIP terms are standard output from Astrometry.net and the SASpro Plate Solver — they encode the residual distortion that a simple TAN projection can't capture, letting the solution reach sub-pixel accuracy even for wide-field or heavily aberrated optics. SIP is a mathematical description of the distortion, not a correction of it. The pixels in your image still live where they were when the sensor read them out, so if you overlay a star catalog on the raw image, the mapping involves evaluating a polynomial at every point.

Reasons to actually bake the correction into pixels:

2. Workflow

  1. Plate solve first. The image must have a WCS with SIP terms. Run the SASpro Plate Solver and leave SIP enabled (the default). Without a WCS, there's nothing to invert; without SIP, there's nothing to remove.
  2. Open the Unwarp dialog. The active image is selected automatically. If you have multiple images open, pick the right one from the Source dropdown.
  3. Check the detected solution in the info readout — SIP order, pixel scale, input and output dimensions, and growth factor.
  4. Choose interpolation — Bicubic (default, best quality), Bilinear (faster), or Nearest (for mask-like data where interpolation would be wrong).
  5. Decide on canvas expansion — leave on by default. Turn off only if you absolutely need the output to be the same size as the input and accept that distortion-corrected corners may fall outside the frame.
  6. Pick fill behavior — black (default) or NaN (preserves "no data" information through downstream processing).
  7. Deliver. Apply (in place) to overwrite the current doc with undo, New View for a side-by-side comparison, or Save FITS to write straight to disk.

3. The info readout

Before you commit, the dialog shows what the unwarp will do:

SIP order N · s″/px — N is the polynomial order (2 for small aberrations, 3-4 for strong wide-field distortion). s is the mean pixel scale from the linear part of the WCS.
input W × H — your source image dimensions.
output W' × H' (canvas grows ×G) — the output size after unwarping. G is the area growth factor. If expansion is off, this stays the same as input and the report says "canvas unchanged".
⚠ large expansion — if G exceeds 2.5, the dialog warns you. In practice this means the SIP polynomial is predicting very large edge distortion. It can be legitimate for extreme optics but it's also a classic sign of a bad plate solution — a polynomial that fit noise and extrapolated wildly beyond the image bounds. Worth a sanity check before you commit.

After the unwarp completes, the status line reports the measured correction:

Peak edge-to-edge correction — the maximum pixel shift observed across the border + interior sample grid, with the arcsec equivalent shown when pixel scale is known.
Median — the typical correction. For well-behaved optics this is often small (sub-pixel to a few pixels) while the peak is at the frame corners.
Final line — a reminder that re-solving the output should produce much smaller residuals (ideally a linear-only solution with no SIP needed).

4. Delivery modes

ModeWhat happensBest for
Apply (in place)The source image is replaced with the unwarped version. The source document's WCS and header are updated. Fully undoable.Normal workflow — commit the correction and keep processing.
New ViewOpens the unwarped image as a new document, leaving the source untouched.Comparing before/after, keeping the SIP-tagged version as a reference, or branching your workflow.
Save FITS…Writes straight to disk as a FITS file with the rewritten linear WCS. The current doc is not modified.Exporting to another program (DS9, Aladin, AstroImageJ) or archiving a corrected frame separately.

5. The preset drag grip

Bottom-left of the dialog is the Star-Stretch-style preset drag handle. The current settings (interpolation, expand toggle, fill behavior) form a canonical three-field preset:

{ "expand": True|False, "order": 0|1|3, "fill_nan": True|False }

Drag this handle onto:

The headless path respects the same guards as the dialog: if the target has no WCS or no SIP terms, the drop is a logged no-op rather than an error — nothing is modified.

6. Workflow recipes

Standard wide-field pipeline. Plate solve with SIP (default order 3). Unwarp with Bicubic + Expand + black fill. Apply in place. Continue to stretching / stacking / further processing on the clean linear frame. One drag of the preset grip onto the canvas lets you repeat the exact same unwarp settings on your next frame with a double-click.
Mosaic preparation. For each panel: plate solve → unwarp → save FITS (or push through your mosaic tool directly). All panels now share a common distortion-free coordinate system, so your mosaic blender only has to handle the panel-to-panel geometry. Use NaN fill if your mosaic tool handles no-data regions.
Sanity check a suspect plate solution. If the info readout shows "⚠ large expansion" (growth factor > 2.5), don't just click Apply. Re-run the Plate Solver with a lower SIP order (2 or even TAN-only), or with a different star catalog, or on a cropped region, and compare. A good solution typically produces growth under 1.15 for astrograph-class optics, under 1.5 for fast widefield, and only exceeds 2 on genuinely extreme systems.
Comparing before and after. Use New View instead of Apply. The source document keeps its SIP terms, the new view carries the clean linear WCS. Overlay a star catalog on each and visually compare how tightly the catalog positions sit on top of the stars in the corners of the frame. Catalog overlays using the unwarped image should sit nearly dead-center on every star across the entire field.
Preserve mask data (nearest-neighbor). If you're unwarping a label mask, segmentation output, or any image whose pixel values are categorical rather than continuous (e.g. 0/1/2 representing different star types or exclusion zones), set interpolation to Nearest. Bilinear or Bicubic would blend category values and produce meaningless intermediate numbers.

7. Interpolation choice

MethodOrderWhen to use
Bicubic (best)3Default. Produces the sharpest, cleanest result for normal astronomical imagery. Preserves stellar PSFs without ringing on typical data.
Bilinear1Faster, slightly softer. Good for very large images or quick previews. Still vastly better than Nearest for continuous data.
Nearest0Only for categorical / label data (masks, segmentation). Never produces intermediate values, so categorical meaning is preserved.

8. Technical details

The SIP polynomial, the two-step projection pipeline, the canvas-expansion math, the band-parallel resampler, and the header-rewrite rules.

8.1 SIP polynomial — the distortion model

SIP (Shupe et al. 2005) extends a standard FITS WCS with two polynomial corrections applied in pixel space before the linear transformation runs. Given a reference pixel (CRPIX1, CRPIX2), define pixel offsets:

u = x - \mathrm{CRPIX1}, \quad v = y - \mathrm{CRPIX2}

SIP introduces two polynomials of order n (the SIP order shown in the info readout):

f(u, v) = \sum_{p+q \leq n} A_{p,q} \cdot u^p v^q
g(u, v) = \sum_{p+q \leq n} B_{p,q} \cdot u^p v^q

The corrected (distortion-removed-in-pixel-space) position is:

x_{\mathrm{corr}} = u + f(u, v) + \mathrm{CRPIX1}
y_{\mathrm{corr}} = v + g(u, v) + \mathrm{CRPIX2}

The A and B coefficients are stored in the FITS header as A_i_j and B_i_j keywords, with A_ORDER and B_ORDER giving the maximum order. For the inverse mapping (from the corrected coordinates back to raw pixel coordinates) there are optional AP_i_j and BP_i_j polynomials, which Astrometry.net typically fits alongside the forward ones. SIP order is reported as the max of A and B order:

n = \max(A_{\mathrm{order}}, B_{\mathrm{order}})

8.2 The two-step projection pipeline

The unwarp works by, for each pixel of the output image, finding where its sky position lives in the source image. Two WCS objects are involved:

For every output pixel (x_out, y_out):

1. Project through the linear WCS forward to get sky coordinates:

(x_{\mathrm{out}}, y_{\mathrm{out}}) \to (\alpha, \delta) \; \text{via linear WCS}

2. Project back through the full SIP WCS inverse to get source pixel coordinates:

(\alpha, \delta) \to (x_{\mathrm{src}}, y_{\mathrm{src}}) \; \text{via full SIP WCS}

3. Sample the source image at (x_src, y_src) using the chosen interpolation order:

I_{\mathrm{out}}(x_o, y_o) = I_{\mathrm{src}}(x_s, y_s)

The result is an image where sampling at (x_out, y_out) and projecting through the output's linear WCS gives the same sky position as sampling at (x_src, y_src) and projecting through the full SIP WCS. Both WCSes agree about the sky — just with and without distortion in pixel space.

8.3 Canvas expansion

Removing distortion generally changes the shape of the image's footprint on the sky. The corners of a strongly distorted frame get pushed outward when the distortion is removed, so if the output canvas is kept the same size as the input, the corrected corners fall off the frame.

To find the correct canvas size, the tool samples points densely around the input image border (96 samples per side) plus a coarse 24×24 interior grid, projects each through SIP forward and linear inverse, and takes the bounding box of all the resulting positions. The output canvas is sized to fit this bounding box, and CRPIX is adjusted so the new canvas is correctly aligned to the sky:

\mathrm{CRPIX}_{\mathrm{new}} = \mathrm{CRPIX}_{\mathrm{old}} - (x_0, y_0)

where (x_0, y_0) is the lower-left offset of the bounding box relative to the original frame. The growth factor reported in the info readout is:

\mathrm{growth} = \frac{W_{\mathrm{out}} \cdot H_{\mathrm{out}}}{W_{\mathrm{in}} \cdot H_{\mathrm{in}}}

If expansion is disabled, the canvas keeps the original dimensions and (x_0, y_0) = 0, which may clip the corrected corners.

8.4 Shift measurement

After computing the output WCS, the tool measures how far each sample point moved from its source position to its new linear-WCS position. For each sample pixel (p_x, p_y) in the source:

  1. Project to true sky via the full SIP WCS: (α, δ) = WCS_full(p_x, p_y).
  2. Project back into the output's linear WCS: (q_x, q_y) = LinearWCS-1(α, δ), accounting for the origin shift.
  3. Compute the shift magnitude:
\Delta = \sqrt{(x_{\mathrm{linear}} - x_{\mathrm{source}})^2 + (y_{\mathrm{linear}} - y_{\mathrm{source}})^2}

Then take the median across all samples (typical correction) and the maximum (peak correction, usually at the corners). The arcsec equivalent uses the plate scale:

\Delta_{\mathrm{arcsec}} = \Delta_{\mathrm{px}} \cdot s_{\mathrm{plate}}

where splate is the mean pixel scale derived from the determinant of the CD matrix:

s_{\mathrm{plate}} = \sqrt{|\det(\mathrm{CD})|} \cdot 3600 \; \mathrm{arcsec/px}

8.5 Band-parallel resampling

The output image is divided into horizontal bands (16-256 rows depending on frame height) and the resample is dispatched across a thread pool with worker count equal to the CPU core count. Each band builds a (y, x) meshgrid for its rows, projects the full grid through linear-forward + SIP-inverse to get source coordinates, and calls scipy.ndimage.map_coordinates once per output channel with the chosen interpolation order.

WCS objects are not thread-safe. The resampler keeps a thread-local pair of deepcopied WCS objects (one linear-out, one full-SIP-in) per worker thread, created lazily on first use. Sharing a single WCS instance across threads caused silent corruption in earlier builds — the astropy WCS internals cache intermediate state during each projection call, and concurrent calls stomp on each other's cache without raising an error. The thread-local pattern avoids this at the cost of a modest memory overhead (two small Python objects per worker).

Progress is reported by rows completed rather than bands dispatched, so the progress bar is monotonic even when bands finish out of order.

8.6 Header rewrite

The output's FITS header is built by starting from the source header and stripping two categories of keyword:

Then the new linear WCS is written in via w_out.to_header(relax=True), which produces the full standard WCS keyword set. NAXIS1 and NAXIS2 are updated to the expanded canvas dimensions. A sentinel SASUNWRP = True is added so downstream tools can detect that this image has already been unwarped.

All other header keywords — instrument name, exposure, filter, object, date, observation metadata — are passed through unchanged. The intent is that the output is a drop-in replacement for the input in any pipeline that cares about these, with only the geometry corrected.

8.7 Verification

After an unwarp, re-running the Plate Solver on the output should produce a solution with:

This round-trip test is the ultimate check that the unwarp preserved the astrometry correctly:

\mathrm{residual} = \|\mathrm{WCS}^{-1}(\mathrm{WCS}(x, y)) - (x, y)\|

should be at the level of plate-solve noise (fractions of a pixel), not systematic.

Credits. SASpro Astrometric Unwarp — the two-step linear/SIP projection pipeline, the border-plus-grid canvas-sizing algorithm, the shift-measurement reporting, the thread-local-WCS band-parallel resampler, the three-mode delivery (Apply in place, New View, Save FITS), and the preset-drag-grip headless-apply integration — is original work by Franklin Marek for SetiAstro Suite Pro. Standard components credited to the community: SIP polynomial convention (Shupe, Moshir, Li, Makovoz, Narron, Hook 2005, "The SIP Convention for Representing Distortion in FITS Image Headers"); astropy for the WCS implementation (wcs.WCS with relax=True for SIP support, all_pix2world / wcs_world2pix / all_world2pix for the forward and inverse projections, wcs.utils.proj_plane_pixel_scales for pixel scale extraction); scipy for the band resampling (ndimage.map_coordinates with configurable spline order); the FITS WCS standard (Greisen & Calabretta 2002, Calabretta & Greisen 2002); and Astrometry.net (Lang et al. 2010) as the canonical producer of the SIP solutions this tool consumes.