astrofrog / pyavm

Pure-python AVM library
http://astrofrog.github.io/pyavm/
Other
20 stars 9 forks source link

AVM-to-WCS conversion arguably gets parity wrong #39

Open pkgw opened 2 years ago

pkgw commented 2 years ago

With some of the work I've been doing lately with WWT image processing, I've had to get down and dirty with image parities in world-coordinate transformations. I'm logging this issue mostly to record some of my takeaways.

One of those takeaways is that, if you're being a stickler about parity, you can argue that the WCS structures that pyavm gives you are incorrect. That's mostly because, in my reading of it, the AVM specification is unclear and possibly not self-consistent when it comes to image parity.

Heuristically, we can talk about FITS files usually having "positive" parity on the sky, meaning that the first row of data is found at the bottom of the image as viewed from inside the celestial sphere, assuming no rotation. RGB images (JPEGs etc) usually have "negative" parity, meaning that the first row is found at the top of the image. If you take a picture of the sky with your phone you'll get an image with negative parity.

I've found that a more helpful way to think about parity is in the transformation between raw data coordinates (a 2D coordinate system of pixel X and pixel Y) and sky coordinates (a 2D coordinate system of latitude/longitude where longitude increases clockwise around the sphere and we're viewing the sphere from its interior). In the FITS data layout, a small vector of positive delta-x and delta-y in the data space maps to a small vector of positive delta-lon and delta-lat in world space (bigger X = bigger RA = move right, bigger Y = bigger Dec = move up) — hence positivity. The JPEG-to-world coordinate change requires a parity flip because there bigger Y = smaller Dec = move down.

The reason that I like this model is because it helps to reason about the relationship between WCS and different parity conventions, regardless of whether one is using CDELT and CROTA or a PC matrix or a CD matrix or whatever. WCS can express transformations of either parity, and you can find ways to think about all of the various parameterizations as coordinate transformations and determine their parities. In particular, if the CD matrix has a negative determinant, that's positive image parity, just because that's how the terms got defined.

Anyway, the arguable problem is that the WCS returned by pyavm, when derived from AVM data seen in the wild, ends up expressing a positive-parity (FITS-type) coordinate transformation, while the underlying pixels have a negative-parity (JPEG-llike) data layout. So if you actually want to apply those WCS data to the image data, you have to flip the image buffer vertically, even though the WCS could correctly express the image's data layout.

It would be a bit risky to change pyavm's behavior with how it generates WCS from AVM headers, but at least it would be good to document the current behavior a bit better in this regard. And if there are any actual AVM images in the wild with positive parities (which the standard discourages but does not prohibit), it would be good to check that the WCS generated for such images work.

pkgw commented 2 years ago

I've looked into this topic a bit more — there were a couple of WWT+WCS bugs, now fixed that obscured things a bit. As far as I can tell, the AVM tags that I've seen in the wild translate into WCS that are parity-flipped relative to what they "should" be. If you're really taking the parity of the AVM-tagged imagery seriously in your display code, you need to flip the parity as is done in toasty:

https://github.com/WorldWideTelescope/toasty/blob/d52a58db0bcc9d35bb2afa3bd582329ea6cbbc2b/toasty/builder.py#L275-L277

That involves negating the Y column of the CD matrix and bouncing CRPIX2:

https://github.com/WorldWideTelescope/toasty/blob/master/toasty/image.py#L226-L243