0
# Coordinate Transformation Utilities
1
2
Tools for calculating pixel mappings between different coordinate systems, estimating pixel scale ratios, and working with World Coordinate System (WCS) objects. These utilities support the coordinate transformations needed for drizzling operations.
3
4
```python { .api }
5
from typing import Optional, Union, List, Tuple
6
import numpy as np
7
```
8
9
## Capabilities
10
11
### Pixel Map Calculation
12
13
Calculate discretized coordinate mappings between two images using their World Coordinate System (WCS) objects.
14
15
```python { .api }
16
def calc_pixmap(
17
wcs_from,
18
wcs_to,
19
shape: Optional[Tuple[int, int]] = None,
20
disable_bbox: str = "to"
21
) -> np.ndarray:
22
"""
23
Calculate pixel-to-pixel mapping between two coordinate systems.
24
25
Transforms pixel coordinates from the 'from' image to pixel coordinates
26
in the 'to' image using their respective WCS objects.
27
28
Parameters:
29
- wcs_from: WCS object for source coordinate system
30
Must have array_shape property or bounding_box if shape not provided
31
- wcs_to: WCS object for destination coordinate system
32
- shape: Output array shape (Ny, Nx) in numpy order
33
If None, uses wcs_from.array_shape or derives from bounding_box
34
- disable_bbox: Controls bounding box usage ("to", "from", "both", "none")
35
"to" disables bounding box for destination WCS
36
"from" disables bounding box for source WCS
37
"both" disables both, "none" uses both
38
39
Returns:
40
3D numpy array of shape (Ny, Nx, 2) where:
41
- [..., 0] contains X-coordinates in destination system
42
- [..., 1] contains Y-coordinates in destination system
43
44
Raises:
45
- ValueError: If output shape cannot be determined from inputs
46
47
Notes:
48
- Output frames of both WCS objects must have same units
49
- Coordinates outside bounding boxes may be set to NaN depending on disable_bbox
50
- Used to generate pixmap arrays for Drizzle.add_image()
51
"""
52
```
53
54
### Pixel Scale Ratio Estimation
55
56
Estimate the ratio of pixel scales between two WCS objects at specified reference positions.
57
58
```python { .api }
59
def estimate_pixel_scale_ratio(
60
wcs_from,
61
wcs_to,
62
refpix_from: Optional[Union[np.ndarray, Tuple, List]] = None,
63
refpix_to: Optional[Union[np.ndarray, Tuple, List]] = None
64
) -> float:
65
"""
66
Compute ratio of pixel scales between two WCS objects.
67
68
Pixel scale is estimated as square root of pixel area (assumes square pixels).
69
If reference pixel positions are not provided, uses center of bounding box,
70
center of pixel_shape, or (0, 0) as fallback.
71
72
Parameters:
73
- wcs_from: Source WCS object (must have pixel_shape property)
74
- wcs_to: Destination WCS object
75
- refpix_from: Reference pixel coordinates in source image for scale calculation
76
Can be numpy array, tuple, or list of coordinates
77
- refpix_to: Reference pixel coordinates in destination image for scale calculation
78
Can be numpy array, tuple, or list of coordinates
79
80
Returns:
81
Float representing ratio of destination to source pixel scales
82
(pixel_scale_to / pixel_scale_from)
83
84
Notes:
85
- Useful for determining scale parameter in drizzling operations
86
- Reference pixels default to image centers when not specified
87
- Uses approximate algorithm for efficiency
88
"""
89
```
90
91
## Usage Examples
92
93
### Basic Pixel Mapping
94
95
```python
96
import numpy as np
97
from drizzle.utils import calc_pixmap
98
from astropy import wcs
99
100
# Create sample WCS objects (in real usage, these come from FITS headers)
101
# Here we simulate simple WCS objects
102
class SimpleWCS:
103
def __init__(self, pixel_shape, scale=1.0, offset=(0, 0)):
104
self.array_shape = pixel_shape
105
self.scale = scale
106
self.offset = offset
107
108
def pixel_to_world_values(self, x, y):
109
return x * self.scale + self.offset[0], y * self.scale + self.offset[1]
110
111
def world_to_pixel_values(self, world_x, world_y):
112
return (world_x - self.offset[0]) / self.scale, (world_y - self.offset[1]) / self.scale
113
114
# Source image: 100x100 pixels
115
wcs_from = SimpleWCS(pixel_shape=(100, 100), scale=1.0, offset=(0, 0))
116
117
# Destination: 150x150 pixels with different scale and offset
118
wcs_to = SimpleWCS(pixel_shape=(150, 150), scale=0.8, offset=(10, 5))
119
120
# Calculate pixel mapping
121
pixmap = calc_pixmap(wcs_from, wcs_to)
122
123
print(f"Pixel map shape: {pixmap.shape}") # (100, 100, 2)
124
print(f"Sample mapping - pixel (50,50) maps to: ({pixmap[50,50,0]:.2f}, {pixmap[50,50,1]:.2f})")
125
```
126
127
### Custom Shape and Bounding Box Control
128
129
```python
130
from drizzle.utils import calc_pixmap
131
132
# Calculate mapping with custom output shape
133
pixmap = calc_pixmap(
134
wcs_from,
135
wcs_to,
136
shape=(200, 180), # Custom shape instead of using wcs_from.array_shape
137
disable_bbox="both" # Disable both bounding boxes
138
)
139
140
print(f"Custom shape mapping: {pixmap.shape}")
141
142
# Different bounding box configurations
143
pixmap_from_disabled = calc_pixmap(wcs_from, wcs_to, disable_bbox="from")
144
pixmap_to_disabled = calc_pixmap(wcs_from, wcs_to, disable_bbox="to") # Default
145
pixmap_none_disabled = calc_pixmap(wcs_from, wcs_to, disable_bbox="none")
146
```
147
148
### Pixel Scale Ratio Calculation
149
150
```python
151
import numpy as np
152
from drizzle.utils import estimate_pixel_scale_ratio
153
154
# Calculate pixel scale ratio between two coordinate systems
155
ratio = estimate_pixel_scale_ratio(wcs_from, wcs_to)
156
print(f"Pixel scale ratio (to/from): {ratio:.4f}")
157
158
# Calculate ratio at specific reference points
159
ref_from = (25, 25) # Reference point in source image
160
ref_to = (30, 28) # Reference point in destination image
161
162
ratio_at_refs = estimate_pixel_scale_ratio(
163
wcs_from,
164
wcs_to,
165
refpix_from=ref_from,
166
refpix_to=ref_to
167
)
168
print(f"Ratio at reference points: {ratio_at_refs:.4f}")
169
170
# Using numpy arrays for reference points
171
ref_from_array = np.array([25.5, 25.5])
172
ref_to_array = np.array([30.2, 28.1])
173
174
ratio_arrays = estimate_pixel_scale_ratio(
175
wcs_from,
176
wcs_to,
177
refpix_from=ref_from_array,
178
refpix_to=ref_to_array
179
)
180
print(f"Ratio with array inputs: {ratio_arrays:.4f}")
181
```
182
183
### Integration with Drizzling
184
185
```python
186
import numpy as np
187
from drizzle.resample import Drizzle
188
from drizzle.utils import calc_pixmap, estimate_pixel_scale_ratio
189
190
# Setup coordinate systems
191
wcs_input = SimpleWCS(pixel_shape=(120, 120), scale=1.0)
192
wcs_output = SimpleWCS(pixel_shape=(200, 200), scale=0.75)
193
194
# Calculate transformations
195
pixmap = calc_pixmap(wcs_input, wcs_output)
196
scale_ratio = estimate_pixel_scale_ratio(wcs_input, wcs_output)
197
198
# Create sample data
199
data = np.random.random((120, 120)).astype(np.float32)
200
201
# Use calculated parameters in drizzling
202
drizzler = Drizzle(out_shape=(200, 200))
203
nmiss, nskip = drizzler.add_image(
204
data=data,
205
exptime=10.0,
206
pixmap=pixmap,
207
scale=scale_ratio # Use calculated scale ratio
208
)
209
210
print(f"Drizzling completed: missed={nmiss}, skipped={nskip}")
211
```
212
213
### Working with Real Astropy WCS
214
215
```python
216
# Example with actual astropy WCS objects (requires astropy)
217
try:
218
from astropy import wcs
219
from astropy.io import fits
220
221
# Load WCS from FITS headers (example)
222
# header1 = fits.getheader('input_image.fits')
223
# header2 = fits.getheader('output_image.fits')
224
# wcs1 = wcs.WCS(header1)
225
# wcs2 = wcs.WCS(header2)
226
227
# For demonstration, create simple WCS
228
w1 = wcs.WCS(naxis=2)
229
w1.wcs.crpix = [50, 50]
230
w1.wcs.cdelt = [0.1, 0.1]
231
w1.wcs.crval = [0, 0]
232
w1.array_shape = (100, 100)
233
234
w2 = wcs.WCS(naxis=2)
235
w2.wcs.crpix = [75, 75]
236
w2.wcs.cdelt = [0.08, 0.08]
237
w2.wcs.crval = [1, 1]
238
w2.array_shape = (150, 150)
239
240
# Calculate pixel mapping
241
pixmap = calc_pixmap(w1, w2)
242
scale_ratio = estimate_pixel_scale_ratio(w1, w2)
243
244
print(f"WCS pixel map shape: {pixmap.shape}")
245
print(f"Scale ratio: {scale_ratio:.4f}")
246
247
except ImportError:
248
print("Astropy not available for this example")
249
```
250
251
## Error Handling
252
253
```python
254
from drizzle.utils import calc_pixmap, estimate_pixel_scale_ratio
255
256
# Handle cases where shape cannot be determined
257
class BadWCS:
258
def __init__(self):
259
self.array_shape = None
260
# No bounding_box property either
261
262
try:
263
bad_wcs = BadWCS()
264
good_wcs = SimpleWCS((100, 100))
265
pixmap = calc_pixmap(bad_wcs, good_wcs) # No shape info available
266
except ValueError as e:
267
print(f"Shape determination error: {e}")
268
269
# Handle coordinate transformation errors
270
try:
271
# WCS objects that don't implement required methods
272
class IncompleteWCS:
273
array_shape = (50, 50)
274
# Missing required transformation methods
275
276
incomplete_wcs = IncompleteWCS()
277
pixmap = calc_pixmap(incomplete_wcs, good_wcs)
278
except AttributeError as e:
279
print(f"WCS method error: {e}")
280
```
281
282
## Notes
283
284
- These utilities are designed to work with WCS objects that implement the standard transformation methods (`pixel_to_world_values`, `world_to_pixel_values`)
285
- The `calc_pixmap` function is the primary way to generate coordinate mappings for drizzling operations
286
- Pixel scale ratios help determine appropriate scaling parameters for flux conservation
287
- Bounding box handling allows control over coordinate transformations at image edges
288
- All coordinate arrays use numpy's float64 precision for accuracy in astronomical coordinate transformations