Skip to content

Commit 65c91a3

Browse files
Merge pull request #404 from observerly/feature/wcs/convertPixelToWorldCoordinateSystem
feat: add convertPixelToWorldCoordinateSystem() to wcs module in @observerly/astrometry
2 parents 12bfe37 + 5f6c7d5 commit 65c91a3

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

src/wcs.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,80 @@ export type WCS = {
109109
SIP?: SIP2DParameters
110110
}
111111

112+
/*****************************************************************************************************************/
113+
114+
/**
115+
*
116+
* convertPixelToWorldCoordinateSystem
117+
*
118+
* Converts a given image pixel to an equatorial coordinate base on the prescribed WCS
119+
*
120+
* @param wcs the World Coordinate System (WCS) parameters to use for the conversion
121+
* @param pixel the pixel to convert to an equatorial coordinate, e.g., { x: 0, y: 0 }
122+
* @returns the equatorial coordinate of a given pixel in the prescribed WCS
123+
*/
124+
export const convertPixelToWorldCoordinateSystem = (
125+
wcs: WCS,
126+
pixel: CartesianCoordinate
127+
): EquatorialCoordinate => {
128+
// Calculate the pixel delta in the X dimension relative to central reference pixel:
129+
let deltaX = pixel.x - wcs.crpix.x
130+
131+
// Calculate the pixel delta in the Y dimension relative to central reference pixel:
132+
let deltaY = pixel.y - wcs.crpix.y
133+
134+
// Initialize SIP correction terms for A and B:
135+
let A = 0
136+
let B = 0
137+
138+
// Apply SIP polynomial corrections if SIP parameters are defined
139+
if (wcs.SIP) {
140+
// Apply A polynomial corrections:
141+
for (const term in wcs.SIP.APower) {
142+
const coeff = wcs.SIP.APower[term]
143+
const indices = parseSIPTerm(term, 'A')
144+
if (indices) {
145+
const [i, j] = indices
146+
A += coeff * deltaX ** i * deltaY ** j
147+
}
148+
}
149+
150+
// Apply B polynomial corrections:
151+
for (const term in wcs.SIP.BPower) {
152+
const coeff = wcs.SIP.BPower[term]
153+
const indices = parseSIPTerm(term, 'B')
154+
if (indices) {
155+
const [i, j] = indices
156+
B += coeff * deltaX ** i * deltaY ** j
157+
}
158+
}
159+
}
160+
161+
// Apply forward SIP transformation to correct for non-linear distortions:
162+
deltaX += A
163+
deltaY += B
164+
165+
// Calculate the reference equatorial coordinate for the right ascension:
166+
let ra = wcs.cd11 * deltaX + wcs.cd12 * deltaY + wcs.E
167+
168+
// Correct for large values of RA:
169+
ra = ra % 360
170+
171+
// Correct for negative values of RA:
172+
if (ra < 0) {
173+
ra += 360
174+
}
175+
176+
// Calculate the reference equatorial coordinate for the declination:
177+
let dec = wcs.cd21 * deltaX + wcs.cd22 * deltaY + wcs.F
178+
179+
// Correct for large values of DEC:
180+
dec = dec % 90
181+
182+
return {
183+
ra,
184+
dec
185+
}
186+
}
112187

113188
/*****************************************************************************************************************/

tests/wcs.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*****************************************************************************************************************/
2+
3+
// @author Michael Roberts <[email protected]>
4+
// @package @observerly/astrometry/wcs
5+
// @license Copyright © 2021-2023 observerly
6+
7+
/*****************************************************************************************************************/
8+
9+
import { describe, expect, it } from 'vitest'
10+
11+
import {
12+
type SIP2DParameters,
13+
WCS,
14+
convertPixelToWorldCoordinateSystem
15+
} from '../src'
16+
17+
/*****************************************************************************************************************/
18+
19+
describe('convertPixelToWorldCoordinateSystem', () => {
20+
it('should be defined', () => {
21+
expect(convertPixelToWorldCoordinateSystem).toBeDefined()
22+
})
23+
24+
it('should convert pixel coordinates to equatorial coordinates correctly without SIP transformations', () => {
25+
const wcs = {
26+
crpix: { x: 200, y: 200 },
27+
crval: { ra: 0, dec: 0 },
28+
cd11: 0.2,
29+
cd12: 30,
30+
cd21: 0.2,
31+
cd22: 0.2,
32+
E: 0,
33+
F: 0
34+
} satisfies WCS
35+
36+
const pixel = { x: 100, y: 100 }
37+
38+
const equatorial = convertPixelToWorldCoordinateSystem(wcs, pixel)
39+
40+
expect(equatorial).toEqual({ ra: 220, dec: -40 })
41+
})
42+
43+
it('should convert pixel coordinates to equatorial coordinates correctly with SIP transformations', () => {
44+
const sip = {
45+
AOrder: 2,
46+
BOrder: 2,
47+
APower: { A_1_0: 0, A_0_1: 0, A_1_1: 2.47110429124e-07, A_2_0: 6.71868472834e-07, A_0_2: 2.8223469986e-07 },
48+
BPower: { B_1_0: 0, B_0_1: 0, B_1_1: 2.47110429124e-07, B_2_0: 6.71868472834e-07, B_0_2: 2.8223469986e-07 }
49+
} satisfies SIP2DParameters
50+
51+
const wcs = {
52+
crpix: { x: 200, y: 200 },
53+
crval: { ra: 0, dec: 0 },
54+
cd11: 0.2,
55+
cd12: 30,
56+
cd21: 0.2,
57+
cd22: 0.2,
58+
E: 0,
59+
F: 0,
60+
SIP: sip
61+
} satisfies WCS
62+
63+
const pixel = { x: 100, y: 100 }
64+
65+
const equatorial = convertPixelToWorldCoordinateSystem(wcs, pixel)
66+
67+
expect(equatorial.ra).toBeCloseTo(220.36, 1)
68+
expect(equatorial.dec).toBeCloseTo(-39.99, 1)
69+
})
70+
})
71+
72+
/*****************************************************************************************************************/

0 commit comments

Comments
 (0)