0
# Image Resampling and Combination
1
2
Core drizzling functionality for combining multiple dithered images onto a common output grid. The drizzling algorithm redistributes flux from input pixels to output pixels based on coordinate transformations, chosen kernels, and weight maps.
3
4
```python { .api }
5
from typing import Optional, Union, Tuple
6
import numpy as np
7
```
8
9
## Capabilities
10
11
### Drizzle Class
12
13
The main class for managing resampling and co-adding of multiple images onto a common output grid. Handles exposure time tracking, context image management, and provides access to output arrays.
14
15
```python { .api }
16
class Drizzle:
17
"""
18
A class for managing resampling and co-adding of multiple images onto a
19
common output grid using the drizzle algorithm.
20
21
The main method is add_image(). Outputs are accessible as properties:
22
out_img, out_wht, out_ctx, and total_exptime.
23
"""
24
25
def __init__(
26
self,
27
kernel: str = "square",
28
fillval: Optional[Union[str, float]] = None,
29
out_shape: Optional[Tuple[int, int]] = None,
30
out_img: Optional[np.ndarray] = None,
31
out_wht: Optional[np.ndarray] = None,
32
out_ctx: Optional[np.ndarray] = None,
33
exptime: float = 0.0,
34
begin_ctx_id: int = 0,
35
max_ctx_id: Optional[int] = None,
36
disable_ctx: bool = False
37
):
38
"""
39
Parameters:
40
- kernel: Kernel for flux distribution ("square", "gaussian", "point", "turbo", "lanczos2", "lanczos3")
41
- fillval: Value for output pixels without input contributions (None, "INDEF", or numeric string)
42
- out_shape: Shape of output images (Ny, Nx). Required if no output arrays provided
43
- out_img: Pre-allocated output image array (float32). If None, will be created
44
- out_wht: Pre-allocated weight array (float32). If None, will be created
45
- out_ctx: Pre-allocated context array (int32). If None, will be created (unless disable_ctx=True)
46
- exptime: Exposure time of previously resampled images (if providing existing arrays)
47
- begin_ctx_id: Starting context ID number (0-based) for first image
48
- max_ctx_id: Maximum expected context ID for pre-allocation
49
- disable_ctx: If True, disables context image creation
50
"""
51
52
def add_image(
53
self,
54
data: np.ndarray,
55
exptime: float,
56
pixmap: np.ndarray,
57
scale: float = 1.0,
58
weight_map: Optional[np.ndarray] = None,
59
wht_scale: float = 1.0,
60
pixfrac: float = 1.0,
61
in_units: str = 'cps',
62
xmin: Optional[int] = None,
63
xmax: Optional[int] = None,
64
ymin: Optional[int] = None,
65
ymax: Optional[int] = None
66
) -> Tuple[float, float]:
67
"""
68
Resample and add an image to the cumulative output image.
69
70
Parameters:
71
- data: Input image to be drizzled (2D numpy array)
72
- exptime: Exposure time of input image (positive number)
73
- pixmap: Coordinate mapping array of shape (Ny, Nx, 2)
74
pixmap[..., 0] = X-coordinates, pixmap[..., 1] = Y-coordinates
75
- scale: Pixel scale of input image (conceptually linear dimension of input pixel)
76
- weight_map: Pixel-by-pixel weighting array. Must match data shape. If None, uniform weights used
77
- wht_scale: Scaling factor applied to pixel weighting
78
- pixfrac: Fraction of pixel flux is confined to (0-1). 1=full pixel, 0.5=half pixel
79
- in_units: Units of input image ("cps" for counts per second, "counts" for total counts)
80
- xmin, xmax, ymin, ymax: Bounding box on input image. Only pixels inside contribute to output
81
82
Returns:
83
Tuple of (nmiss, nskip):
84
- nmiss: Number of pixels that were ignored and did not contribute to output
85
- nskip: Number of lines that were ignored and did not contribute to output
86
"""
87
88
# Properties
89
@property
90
def fillval(self) -> str:
91
"""Fill value for output pixels without contributions from input images."""
92
93
@property
94
def kernel(self) -> str:
95
"""Resampling kernel."""
96
97
@property
98
def ctx_id(self) -> Optional[int]:
99
"""Context image ID (0-based) of the next image to be resampled."""
100
101
@property
102
def out_img(self) -> Optional[np.ndarray]:
103
"""Output resampled image (float32 array)."""
104
105
@property
106
def out_wht(self) -> Optional[np.ndarray]:
107
"""Output weight image (float32 array)."""
108
109
@property
110
def out_ctx(self) -> Optional[np.ndarray]:
111
"""Output context image (int32 array, 2D or 3D)."""
112
113
@property
114
def total_exptime(self) -> float:
115
"""Total exposure time of all resampled images."""
116
```
117
118
### Blot Image Function
119
120
Performs inverse drizzling (blotting) to resample drizzled images back to input coordinate grids. Uses interpolation rather than flux-conserving drizzling, making it suitable for well-sampled images.
121
122
```python { .api }
123
def blot_image(
124
data: np.ndarray,
125
pixmap: np.ndarray,
126
pix_ratio: float,
127
exptime: float,
128
output_pixel_shape: Tuple[int, int],
129
interp: str = 'poly5',
130
sinscl: float = 1.0
131
) -> np.ndarray:
132
"""
133
Resample input image onto output grid using interpolation (not flux-conserving).
134
135
Typically used to resample drizzled output back to input coordinate grids.
136
Works best with well-sampled images like drizzled outputs.
137
138
Parameters:
139
- data: Input image in units of 'cps' (2D array)
140
- pixmap: Coordinate mapping array of shape (Ny, Nx, 2)
141
pixmap[..., 0] = X-coordinates, pixmap[..., 1] = Y-coordinates
142
- pix_ratio: Ratio of input image pixel scale to output image pixel scale
143
- exptime: Exposure time of input image
144
- output_pixel_shape: Dimensions of output image (Nx, Ny)
145
- interp: Interpolation method ("nearest", "linear", "poly3", "poly5", "sinc", "lan3", "lan5")
146
- sinscl: Scaling factor for "sinc" interpolation
147
148
Returns:
149
2D numpy array containing the resampled image data (float32)
150
"""
151
```
152
153
## Usage Examples
154
155
### Basic Drizzling
156
157
```python
158
import numpy as np
159
from drizzle.resample import Drizzle
160
161
# Create sample data
162
data = np.random.random((100, 100)).astype(np.float32)
163
164
# Create identity pixel mapping (no transformation)
165
y, x = np.indices((100, 100), dtype=np.float64)
166
pixmap = np.dstack([x, y])
167
168
# Initialize drizzler
169
drizzler = Drizzle(out_shape=(100, 100), kernel="square")
170
171
# Add the image
172
nmiss, nskip = drizzler.add_image(
173
data=data,
174
exptime=30.0,
175
pixmap=pixmap,
176
pixfrac=1.0
177
)
178
179
# Get results
180
output = drizzler.out_img
181
weights = drizzler.out_wht
182
context = drizzler.out_ctx
183
184
print(f"Missed pixels: {nmiss}, Skipped lines: {nskip}")
185
print(f"Total exposure time: {drizzler.total_exptime}")
186
```
187
188
### Multiple Images with Custom Weights
189
190
```python
191
import numpy as np
192
from drizzle.resample import Drizzle
193
194
# Initialize drizzler for combining multiple images
195
drizzler = Drizzle(out_shape=(200, 200), kernel="gaussian")
196
197
# Process multiple input images
198
for i in range(3):
199
# Simulate input data and weights
200
data = np.random.random((180, 180)).astype(np.float32)
201
weights = np.ones_like(data) * (0.8 + 0.1 * i) # Different weights per image
202
203
# Simulate slight shifts in pixel mapping
204
y, x = np.indices((180, 180), dtype=np.float64)
205
x += i * 0.5 # Small shift
206
y += i * 0.3
207
pixmap = np.dstack([x, y])
208
209
# Add each image
210
nmiss, nskip = drizzler.add_image(
211
data=data,
212
exptime=15.0,
213
pixmap=pixmap,
214
weight_map=weights,
215
pixfrac=0.8,
216
scale=1.0
217
)
218
219
print(f"Image {i}: missed={nmiss}, skipped={nskip}")
220
221
# Final combined result
222
combined_image = drizzler.out_img
223
total_weights = drizzler.out_wht
224
print(f"Combined {drizzler.ctx_id} images, total exposure: {drizzler.total_exptime}s")
225
```
226
227
### Blotting Example
228
229
```python
230
import numpy as np
231
from drizzle.resample import blot_image
232
233
# Simulate a well-sampled drizzled image
234
drizzled_data = np.random.random((150, 150)).astype(np.float32)
235
236
# Create pixel mapping from drizzled grid to original input grid
237
y, x = np.indices((120, 120), dtype=np.float64)
238
# Transform coordinates (reverse of original drizzling transformation)
239
x_mapped = x * 1.25 # Scale factor
240
y_mapped = y * 1.25
241
pixmap = np.dstack([x_mapped, y_mapped])
242
243
# Blot the drizzled image back to input grid
244
blotted = blot_image(
245
data=drizzled_data,
246
pixmap=pixmap,
247
pix_ratio=1.25,
248
exptime=30.0,
249
output_pixel_shape=(120, 120),
250
interp='poly5'
251
)
252
253
print(f"Blotted image shape: {blotted.shape}")
254
```
255
256
## Error Handling
257
258
```python
259
from drizzle.resample import Drizzle
260
261
try:
262
# Invalid kernel
263
drizzler = Drizzle(kernel="invalid_kernel")
264
except ValueError as e:
265
print(f"Kernel error: {e}")
266
267
try:
268
# Invalid exposure time
269
drizzler = Drizzle(out_shape=(100, 100))
270
drizzler.add_image(data, exptime=-1.0, pixmap=pixmap)
271
except ValueError as e:
272
print(f"Exposure time error: {e}")
273
274
try:
275
# Inconsistent array shapes
276
data = np.ones((100, 100))
277
pixmap = np.ones((50, 50, 2)) # Wrong shape
278
drizzler.add_image(data, exptime=1.0, pixmap=pixmap)
279
except ValueError as e:
280
print(f"Shape error: {e}")
281
```