drizzle 2.1.1__cp314-cp314-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of drizzle might be problematic. Click here for more details.

drizzle/resample.py ADDED
@@ -0,0 +1,707 @@
1
+ """
2
+ The `drizzle` module defines the `Drizzle` class, for combining input
3
+ images into a single output image using the drizzle algorithm.
4
+ """
5
+ import numpy as np
6
+
7
+ from drizzle import cdrizzle
8
+
9
+ __all__ = ["Drizzle", "blot_image"]
10
+
11
+ SUPPORTED_DRIZZLE_KERNELS = [
12
+ "square",
13
+ "gaussian",
14
+ "point",
15
+ "turbo",
16
+ "lanczos2",
17
+ "lanczos3",
18
+ ]
19
+
20
+ CTX_PLANE_BITS = 32
21
+
22
+
23
+ class Drizzle:
24
+ """
25
+ A class for managing resampling and co-adding of multiple images onto a
26
+ common output grid. The main method of this class is :py:meth:`add_image`.
27
+ The main functionality of this class is to resample and co-add multiple
28
+ images onto one output image using the "drizzle" algorithm described in
29
+ `Fruchter and Hook, PASP 2002 <https://doi.org/10.1086/338393>`_.
30
+ In the simplest terms, it redistributes flux
31
+ from input pixels to one or more output pixels based on the chosen kernel,
32
+ supplied weights, and input-to-output coordinate transformations as defined
33
+ by the ``pixmap`` argument. For more details, see :ref:`main-user-doc`.
34
+
35
+ This class keeps track of the total exposure time of all co-added images
36
+ and also of which input images have contributed to an output (resampled)
37
+ pixel. This is accomplished via *context image*.
38
+
39
+ Main outputs of :py:meth:`add_image` can be accessed as class properties
40
+ ``out_img``, ``out_wht``, ``out_ctx``, and ``exptime``.
41
+
42
+ .. warning::
43
+ Output arrays (``out_img``, ``out_wht``, and ``out_ctx``) can be
44
+ pre-allocated by the caller and be passed to the initializer or the
45
+ class initializer can allocate these arrays based on other input
46
+ parameters such as ``output_shape``. If caller-supplied output arrays
47
+ have the correct type (`numpy.float32` for ``out_img`` and ``out_wht``
48
+ and `numpy.int32` for the ``out_ctx`` array) and if ``out_ctx`` is
49
+ large enough not to need to be resized, these arrays will be used as is
50
+ and may be modified by the :py:meth:`add_image` method. If not,
51
+ a copy of these arrays will be made when converting to the expected
52
+ type (or expanding the context array).
53
+
54
+ Output Science Image
55
+ --------------------
56
+
57
+ Output science image is obtained by adding input pixel fluxes according to
58
+ equations (4) and (5) in
59
+ `Fruchter and Hook, PASP 2002 <https://doi.org/10.1086/338393>`_.
60
+ The weights and coefficients in those equations will depend on the chosen
61
+ kernel, input image weights, and pixel overlaps computed from ``pixmap``.
62
+
63
+ Output Weight Image
64
+ -------------------
65
+
66
+ Output weight image stores the total weight of output science pixels
67
+ according to equation (4) in
68
+ `Fruchter and Hook, PASP 2002 <https://doi.org/10.1086/338393>`_.
69
+ It depends on the chosen kernel, input image weights, and pixel overlaps
70
+ computed from ``pixmap``.
71
+
72
+ Output Context Image
73
+ --------------------
74
+
75
+ Each pixel in the context image is a bit field that encodes
76
+ information about which input image has contributed to the corresponding
77
+ pixel in the resampled data array. Context image uses 32 bit integers to
78
+ encode this information and hence it can keep track of only 32 input images.
79
+ The first bit corresponds to the first input image, the second bit
80
+ corresponds to the second input image, and so on.
81
+ We call this (0-indexed) order "context ID" which is represented by
82
+ the ``ctx_id`` parameter/property. If the number of
83
+ input images exceeds 32, then it is necessary to have multiple context
84
+ images ("planes") to hold information about all input images, with the first
85
+ plane encoding which of the first 32 images contributed to the output data
86
+ pixel, the second plane representing next 32 input images (number 33-64),
87
+ etc. For this reason, context array is either a 2D array (if the total
88
+ number of resampled images is less than 33) of the type `numpy.int32` and
89
+ shape ``(ny, nx)`` or a a 3D array of shape ``(np, ny, nx)`` where ``nx``
90
+ and ``ny`` are dimensions of the image data. ``np`` is the number of
91
+ "planes" computed as ``(number of input images - 1) // 32 + 1``. If a bit at
92
+ position ``k`` in a pixel with coordinates ``(p, y, x)`` is 0, then input
93
+ image number ``32 * p + k`` (0-indexed) did not contribute to the output
94
+ data pixel with array coordinates ``(y, x)`` and if that bit is 1, then
95
+ input image number ``32 * p + k`` did contribute to the pixel ``(y, x)``
96
+ in the resampled image.
97
+
98
+ As an example, let's assume we have 8 input images. Then, when ``out_ctx``
99
+ pixel values are displayed using binary representation (and decimal in
100
+ parenthesis), one could see values like this::
101
+
102
+ 00000001 (1) - only first input image contributed to this output pixel;
103
+ 00000010 (2) - 2nd input image contributed;
104
+ 00000100 (4) - 3rd input image contributed;
105
+ 10000000 (128) - 8th input image contributed;
106
+ 10000100 (132=128+4) - 3rd and 8th input images contributed;
107
+ 11001101 (205=1+4+8+64+128) - input images 1, 3, 4, 7, 8 have contributed
108
+ to this output pixel.
109
+
110
+ In order to test if a specific input image contributed to an output pixel,
111
+ one needs to use bitwise operations. Using the example above, to test
112
+ whether input images number 4 and 5 have contributed to the output pixel
113
+ whose corresponding ``out_ctx`` value is 205 (11001101 in binary form) we
114
+ can do the following:
115
+
116
+ >>> bool(205 & (1 << (5 - 1))) # (205 & 16) = 0 (== 0 => False): did NOT contribute
117
+ False
118
+ >>> bool(205 & (1 << (4 - 1))) # (205 & 8) = 8 (!= 0 => True): did contribute
119
+ True
120
+
121
+ In general, to get a list of all input images that have contributed to an
122
+ output resampled pixel with image coordinates ``(x, y)``, and given a
123
+ context array ``ctx``, one can do something like this:
124
+
125
+ .. doctest-skip::
126
+
127
+ >>> import numpy as np
128
+ >>> np.flatnonzero([v & (1 << k) for v in ctx[:, y, x] for k in range(32)])
129
+
130
+ For convenience, this functionality was implemented in the
131
+ :py:func:`~drizzle.utils.decode_context` function.
132
+
133
+ References
134
+ ----------
135
+ A full description of the drizzling algorithm can be found in
136
+ `Fruchter and Hook, PASP 2002 <https://doi.org/10.1086/338393>`_.
137
+
138
+ Examples
139
+ --------
140
+ .. highlight:: python
141
+ .. code-block:: python
142
+
143
+ # wcs1 - WCS of the input image usually with distortions (to be resampled)
144
+ # wcs2 - WCS of the output image without distortions
145
+
146
+ import numpy as np
147
+ from drizzle.resample import Drizzle
148
+ from drizzle.utils import calc_pixmap
149
+
150
+ # simulate some data and a pixel map:
151
+ data = np.ones((240, 570))
152
+ pixmap = calc_pixmap(wcs1, wcs2)
153
+ # or simulate a mapping from input image to output image frame:
154
+ # y, x = np.indices((240, 570), dtype=np.float64)
155
+ # pixmap = np.dstack([x, y])
156
+
157
+ # initialize Drizzle object
158
+ d = Drizzle(out_shape=(240, 570))
159
+ d.add_image(data, exptime=15, pixmap=pixmap)
160
+
161
+ # access outputs:
162
+ d.out_img
163
+ d.out_ctx
164
+ d.out_wht
165
+
166
+ """
167
+
168
+ def __init__(self, kernel="square", fillval=None, out_shape=None,
169
+ out_img=None, out_wht=None, out_ctx=None, exptime=0.0,
170
+ begin_ctx_id=0, max_ctx_id=None, disable_ctx=False):
171
+ """
172
+ kernel: str, optional
173
+ The name of the kernel used to combine the input. The choice of
174
+ kernel controls the distribution of flux over the kernel. The kernel
175
+ names are: "square", "gaussian", "point", "turbo",
176
+ "lanczos2", and "lanczos3". The square kernel is the default.
177
+
178
+ .. warning::
179
+ The "gaussian" and "lanczos2/3" kernels **DO NOT**
180
+ conserve flux.
181
+
182
+ out_shape : tuple, None, optional
183
+ Shape (`numpy` order ``(Ny, Nx)``) of the output images (context
184
+ image will have a third dimension of size proportional to the number
185
+ of input images). This parameter is helpful when neither
186
+ ``out_img``, ``out_wht``, nor ``out_ctx`` images are provided.
187
+
188
+ fillval: float, None, str, optional
189
+ The value of output pixels that did not have contributions from
190
+ input images' pixels. When ``fillval`` is either `None` or
191
+ ``"INDEF"`` and ``out_img`` is provided, the values of ``out_img``
192
+ will not be modified. When ``fillval`` is either `None` or
193
+ ``"INDEF"`` and ``out_img`` is **not provided**, the values of
194
+ ``out_img`` will be initialized to `numpy.nan`. If ``fillval``
195
+ is a string that can be converted to a number, then the output
196
+ pixels with no contributions from input images will be set to this
197
+ ``fillval`` value.
198
+
199
+ out_img : 2D array of float32, None, optional
200
+ A 2D numpy array containing the output image produced by
201
+ drizzling. On the first call the array values should be set to zero.
202
+ Subsequent calls it will hold the intermediate results.
203
+
204
+ out_wht : 2D array of float32, None, optional
205
+ A 2D numpy array containing the output counts. On the first
206
+ call it should be set to zero. On subsequent calls it will
207
+ hold the intermediate results.
208
+
209
+ out_ctx : 2D or 3D array of int32, None, optional
210
+ A 2D or 3D numpy array holding a bitmap of which image was an input
211
+ for each output pixel. Should be integer zero on first call.
212
+ Subsequent calls hold intermediate results. This parameter is
213
+ ignored when ``disable_ctx`` is `True`.
214
+
215
+ exptime : float, optional
216
+ Exposure time of previously resampled images when provided via
217
+ parameters ``out_img``, ``out_wht``, ``out_ctx``.
218
+
219
+ begin_ctx_id : int, optional
220
+ The context ID number (0-based) of the first image that will be
221
+ resampled (using `add_image`). Subsequent images will be asigned
222
+ consecutively increasing ID numbers. This parameter is ignored
223
+ when ``disable_ctx`` is `True`.
224
+
225
+ max_ctx_id : int, None, optional
226
+ The largest integer context ID that is *expected* to be used for
227
+ an input image. When it is a non-negative number and ``out_ctx`` is
228
+ `None`, it allows to pre-allocate the necessary array for the output
229
+ context image. If the actual number of input images that will be
230
+ resampled will exceed initial allocation for the context image,
231
+ additional context planes will be added as needed (context array
232
+ will "grow" in the third dimention as new input images are added.)
233
+ The default value of `None` is equivalent to setting ``max_ctx_id``
234
+ equal to ``begin_ctx_id``. This parameter is ignored either when
235
+ ``out_ctx`` is provided or when ``disable_ctx`` is `True`.
236
+
237
+ disable_ctx : bool, optional
238
+ Indicates to not create a context image. If ``disable_ctx`` is set
239
+ to `True`, parameters ``out_ctx``, ``begin_ctx_id``, and
240
+ ``max_ctx_id`` will be ignored.
241
+
242
+ """
243
+ self._disable_ctx = disable_ctx
244
+
245
+ if disable_ctx:
246
+ self._ctx_id = None
247
+ self._max_ctx_id = None
248
+ else:
249
+ if begin_ctx_id < 0:
250
+ raise ValueError("Invalid context image ID")
251
+ self._ctx_id = begin_ctx_id # the ID of the *last* image to be resampled
252
+ if max_ctx_id is None:
253
+ max_ctx_id = begin_ctx_id
254
+ elif max_ctx_id < begin_ctx_id:
255
+ raise ValueError("'max_ctx_id' cannot be smaller than 'begin_ctx_id'.")
256
+ self._max_ctx_id = max_ctx_id
257
+
258
+ if exptime < 0.0:
259
+ raise ValueError("Exposure time must be non-negative.")
260
+
261
+ if (exptime > 0.0 and out_img is None and out_ctx is None and out_wht is None):
262
+ raise ValueError(
263
+ "Exposure time must be 0.0 for the first resampling "
264
+ "(when no ouput resampled images have been provided)."
265
+ )
266
+
267
+ if (
268
+ exptime == 0.0 and
269
+ (
270
+ (out_ctx is not None and np.sum(out_ctx) > 0) or
271
+ (out_wht is not None and np.sum(out_wht) > 0)
272
+ )
273
+ ):
274
+ raise ValueError(
275
+ "Inconsistent exposure time and context and/or weight images: "
276
+ "Exposure time cannot be 0 when context and/or weight arrays "
277
+ "are non-zero."
278
+ )
279
+
280
+ self._texptime = exptime
281
+
282
+ if kernel.lower() not in SUPPORTED_DRIZZLE_KERNELS:
283
+ raise ValueError(f"Kernel '{kernel}' is not supported.")
284
+ self._kernel = kernel
285
+
286
+ if fillval is None:
287
+ fillval = "INDEF"
288
+
289
+ elif isinstance(fillval, str):
290
+ fillval = fillval.strip()
291
+ if fillval.upper() in ["", "INDEF"]:
292
+ fillval = "INDEF"
293
+ else:
294
+ float(fillval)
295
+ fillval = str(fillval)
296
+
297
+ else:
298
+ fillval = str(fillval)
299
+
300
+ if out_img is None and fillval == "INDEF":
301
+ fillval = "NaN"
302
+
303
+ self._fillval = fillval
304
+
305
+ # shapes will collect user specified 'out_shape' and shapes of
306
+ # out_* arrays (if provided) in order to check all shapes are the same.
307
+ shapes = set()
308
+
309
+ if out_img is not None:
310
+ out_img = np.asarray(out_img, dtype=np.float32)
311
+ shapes.add(out_img.shape)
312
+
313
+ if out_wht is not None:
314
+ out_wht = np.asarray(out_wht, dtype=np.float32)
315
+ shapes.add(out_wht.shape)
316
+
317
+ if out_ctx is not None:
318
+ out_ctx = np.asarray(out_ctx, dtype=np.int32)
319
+ if out_ctx.ndim == 2:
320
+ out_ctx = out_ctx[None, :, :]
321
+ elif out_ctx.ndim != 3:
322
+ raise ValueError("'out_ctx' must be either a 2D or 3D array.")
323
+ shapes.add(out_ctx.shape[1:])
324
+
325
+ if out_shape is not None:
326
+ shapes.add(tuple(out_shape))
327
+
328
+ if len(shapes) == 1:
329
+ self._out_shape = shapes.pop()
330
+ self._alloc_output_arrays(
331
+ out_shape=self._out_shape,
332
+ max_ctx_id=max_ctx_id,
333
+ out_img=out_img,
334
+ out_wht=out_wht,
335
+ out_ctx=out_ctx,
336
+ )
337
+ elif len(shapes) > 1:
338
+ raise ValueError(
339
+ "Inconsistent data shapes specified: 'out_shape' and/or "
340
+ "out_img, out_wht, out_ctx have different shapes."
341
+ )
342
+ else:
343
+ self._out_shape = None
344
+ self._out_img = None
345
+ self._out_wht = None
346
+ self._out_ctx = None
347
+
348
+ @property
349
+ def fillval(self):
350
+ """Fill value for output pixels without contributions from input images."""
351
+ return self._fillval
352
+
353
+ @property
354
+ def kernel(self):
355
+ """Resampling kernel."""
356
+ return self._kernel
357
+
358
+ @property
359
+ def ctx_id(self):
360
+ """Context image "ID" (0-based ) of the next image to be resampled."""
361
+ return self._ctx_id
362
+
363
+ @property
364
+ def out_img(self):
365
+ """Output resampled image."""
366
+ return self._out_img
367
+
368
+ @property
369
+ def out_wht(self):
370
+ """Output weight image."""
371
+ return self._out_wht
372
+
373
+ @property
374
+ def out_ctx(self):
375
+ """Output "context" image."""
376
+ return self._out_ctx
377
+
378
+ @property
379
+ def total_exptime(self):
380
+ """Total exposure time of all resampled images."""
381
+ return self._texptime
382
+
383
+ def _alloc_output_arrays(self, out_shape, max_ctx_id, out_img, out_wht,
384
+ out_ctx):
385
+ # allocate arrays as needed:
386
+ if out_wht is None:
387
+ self._out_wht = np.zeros(out_shape, dtype=np.float32)
388
+ else:
389
+ self._out_wht = out_wht
390
+
391
+ if self._disable_ctx:
392
+ self._out_ctx = None
393
+ else:
394
+ if out_ctx is None:
395
+ n_ctx_planes = max_ctx_id // CTX_PLANE_BITS + 1
396
+ ctx_shape = (n_ctx_planes, ) + out_shape
397
+ self._out_ctx = np.zeros(ctx_shape, dtype=np.int32)
398
+ else:
399
+ self._out_ctx = out_ctx
400
+
401
+ if not (out_wht is None and out_ctx is None):
402
+ # check that input data make sense: weight of pixels with
403
+ # non-zero context values must be different from zero:
404
+ if np.any(
405
+ np.bitwise_xor(
406
+ self._out_wht > 0.0,
407
+ np.sum(self._out_ctx, axis=0) > 0
408
+ )
409
+ ):
410
+ raise ValueError(
411
+ "Inconsistent values of supplied 'out_wht' and "
412
+ "'out_ctx' arrays. Pixels with non-zero context "
413
+ "values must have positive weights and vice-versa."
414
+ )
415
+
416
+ if out_img is None:
417
+ if self._fillval.upper() in ["INDEF", "NAN"]:
418
+ fillval = np.nan
419
+ else:
420
+ fillval = float(self._fillval)
421
+ self._out_img = np.full(out_shape, fillval, dtype=np.float32)
422
+ else:
423
+ self._out_img = out_img
424
+
425
+ def _increment_ctx_id(self):
426
+ """
427
+ Returns a pair of the *current* plane number and bit number in that
428
+ plane and increments context image ID
429
+ (after computing the return value).
430
+ """
431
+ if self._disable_ctx:
432
+ return None, 0
433
+
434
+ self._plane_no = self._ctx_id // CTX_PLANE_BITS
435
+ depth = self._out_ctx.shape[0]
436
+
437
+ if self._plane_no >= depth:
438
+ # Add a new plane to the context image if planeid overflows
439
+ plane = np.zeros((1, ) + self._out_shape, np.int32)
440
+ self._out_ctx = np.append(self._out_ctx, plane, axis=0)
441
+
442
+ plane_info = (self._plane_no, self._ctx_id % CTX_PLANE_BITS)
443
+ # increment ID for the *next* image to be added:
444
+ self._ctx_id += 1
445
+
446
+ return plane_info
447
+
448
+ def add_image(self, data, exptime, pixmap, scale=1.0,
449
+ weight_map=None, wht_scale=1.0, pixfrac=1.0, in_units='cps',
450
+ xmin=None, xmax=None, ymin=None, ymax=None):
451
+ """
452
+ Resample and add an image to the cumulative output image. Also, update
453
+ output total weight image and context images.
454
+
455
+ Parameters
456
+ ----------
457
+ data : 2D numpy.ndarray
458
+ A 2D numpy array containing the input image to be drizzled.
459
+
460
+ exptime : float
461
+ The exposure time of the input image, a positive number. The
462
+ exposure time is used to scale the image if the units are counts.
463
+
464
+ pixmap : 3D array
465
+ A mapping from input image (``data``) coordinates to resampled
466
+ (``out_img``) coordinates. ``pixmap`` must be an array of shape
467
+ ``(Ny, Nx, 2)`` where ``(Ny, Nx)`` is the shape of the input image.
468
+ ``pixmap[..., 0]`` forms a 2D array of X-coordinates of input
469
+ pixels in the ouput frame and ``pixmap[..., 1]`` forms a 2D array of
470
+ Y-coordinates of input pixels in the ouput coordinate frame.
471
+
472
+ scale : float, optional
473
+ The pixel scale of the input image. Conceptually, this is the
474
+ linear dimension of a side of a pixel in the input image, but it
475
+ is not limited to this and can be set to change how the drizzling
476
+ algorithm operates.
477
+
478
+ weight_map : 2D array, None, optional
479
+ A 2D numpy array containing the pixel by pixel weighting.
480
+ Must have the same dimensions as ``data``.
481
+
482
+ When ``weight_map`` is `None`, the weight of input data pixels will
483
+ be assumed to be 1.
484
+
485
+ wht_scale : float
486
+ A scaling factor applied to the pixel by pixel weighting.
487
+
488
+ pixfrac : float, optional
489
+ The fraction of a pixel that the pixel flux is confined to. The
490
+ default value of 1 has the pixel flux evenly spread across the image.
491
+ A value of 0.5 confines it to half a pixel in the linear dimension,
492
+ so the flux is confined to a quarter of the pixel area when the square
493
+ kernel is used.
494
+
495
+ in_units : str
496
+ The units of the input image. The units can either be "counts"
497
+ or "cps" (counts per second.)
498
+
499
+ xmin : float, optional
500
+ This and the following three parameters set a bounding rectangle
501
+ on the input image. Only pixels on the input image inside this
502
+ rectangle will have their flux added to the output image. Xmin
503
+ sets the minimum value of the x dimension. The x dimension is the
504
+ dimension that varies quickest on the image. If the value is zero,
505
+ no minimum will be set in the x dimension. All four parameters are
506
+ zero based, counting starts at zero.
507
+
508
+ xmax : float, optional
509
+ Sets the maximum value of the x dimension on the bounding box
510
+ of the input image. If the value is zero, no maximum will
511
+ be set in the x dimension, the full x dimension of the output
512
+ image is the bounding box.
513
+
514
+ ymin : float, optional
515
+ Sets the minimum value in the y dimension on the bounding box. The
516
+ y dimension varies less rapidly than the x and represents the line
517
+ index on the input image. If the value is zero, no minimum will be
518
+ set in the y dimension.
519
+
520
+ ymax : float, optional
521
+ Sets the maximum value in the y dimension. If the value is zero, no
522
+ maximum will be set in the y dimension, the full x dimension
523
+ of the output image is the bounding box.
524
+
525
+ Returns
526
+ -------
527
+ nskip : float
528
+ The number of lines from the box defined by
529
+ ``((xmin, xmax), (ymin, ymax))`` in the input image that were
530
+ ignored and did not contribute to the output image.
531
+
532
+ nmiss : float
533
+ The number of pixels from the box defined by
534
+ ``((xmin, xmax), (ymin, ymax))`` in the input image that were
535
+ ignored and did not contribute to the output image.
536
+
537
+ """
538
+ # this enables initializer to not need output image shape at all and
539
+ # set output image shape based on output coordinates from the pixmap.
540
+ #
541
+ if self._out_shape is None:
542
+ pmap_xmin = int(np.floor(np.nanmin(pixmap[:, :, 0])))
543
+ pmap_xmax = int(np.ceil(np.nanmax(pixmap[:, :, 0])))
544
+ pmap_ymin = int(np.floor(np.nanmin(pixmap[:, :, 1])))
545
+ pmap_ymax = int(np.ceil(np.nanmax(pixmap[:, :, 1])))
546
+ pixmap = pixmap.copy()
547
+ pixmap[:, :, 0] -= pmap_xmin
548
+ pixmap[:, :, 1] -= pmap_ymin
549
+ self._out_shape = (
550
+ pmap_xmax - pmap_xmin + 1,
551
+ pmap_ymax - pmap_ymin + 1
552
+ )
553
+
554
+ self._alloc_output_arrays(
555
+ out_shape=self._out_shape,
556
+ max_ctx_id=self._max_ctx_id,
557
+ out_img=None,
558
+ out_wht=None,
559
+ out_ctx=None,
560
+ )
561
+
562
+ plane_no, id_in_plane = self._increment_ctx_id()
563
+
564
+ if exptime <= 0.0:
565
+ raise ValueError("'exptime' *must* be a strictly positive number.")
566
+
567
+ # Ensure that the fillval parameter gets properly interpreted
568
+ # for use with tdriz
569
+ if in_units == 'cps':
570
+ expscale = 1.0
571
+ else:
572
+ expscale = exptime
573
+
574
+ self._texptime += exptime
575
+
576
+ data = np.asarray(data, dtype=np.float32)
577
+ pixmap = np.asarray(pixmap, dtype=np.float64)
578
+ in_ymax, in_xmax = data.shape
579
+
580
+ if pixmap.shape[:2] != data.shape:
581
+ raise ValueError(
582
+ "'pixmap' shape is not consistent with 'data' shape."
583
+ )
584
+
585
+ if xmin is None or xmin < 0:
586
+ xmin = 0
587
+
588
+ if ymin is None or ymin < 0:
589
+ ymin = 0
590
+
591
+ if xmax is None or xmax > in_xmax - 1:
592
+ xmax = in_xmax - 1
593
+
594
+ if ymax is None or ymax > in_ymax - 1:
595
+ ymax = in_ymax - 1
596
+
597
+ if weight_map is not None:
598
+ weight_map = np.asarray(weight_map, dtype=np.float32)
599
+ else: # TODO: this should not be needed after C code modifications
600
+ weight_map = np.ones_like(data)
601
+
602
+ pixmap = np.asarray(pixmap, dtype=np.float64)
603
+
604
+ if self._disable_ctx:
605
+ ctx_plane = None
606
+ else:
607
+ if self._out_ctx.ndim == 2:
608
+ raise AssertionError("Context image is expected to be 3D")
609
+ ctx_plane = self._out_ctx[plane_no]
610
+
611
+ # TODO: probably tdriz should be modified to not return version.
612
+ # we should not have git, Python, C, ... versions
613
+
614
+ # TODO: While drizzle code in cdrizzlebox.c supports weight_map=None,
615
+ # cdrizzleapi.c does not. It should be modified to support this
616
+ # for performance reasons.
617
+
618
+ _vers, nmiss, nskip = cdrizzle.tdriz(
619
+ input=data,
620
+ weights=weight_map,
621
+ pixmap=pixmap,
622
+ output=self._out_img,
623
+ counts=self._out_wht,
624
+ context=ctx_plane,
625
+ uniqid=id_in_plane + 1,
626
+ xmin=xmin,
627
+ xmax=xmax,
628
+ ymin=ymin,
629
+ ymax=ymax,
630
+ scale=scale, # scales image intensity. usually equal to pixel scale
631
+ pixfrac=pixfrac,
632
+ kernel=self._kernel,
633
+ in_units=in_units,
634
+ expscale=expscale,
635
+ wtscale=wht_scale,
636
+ fillstr=self._fillval,
637
+ )
638
+ self._cversion = _vers # TODO: probably not needed
639
+
640
+ return nmiss, nskip
641
+
642
+
643
+ def blot_image(data, pixmap, pix_ratio, exptime, output_pixel_shape,
644
+ interp='poly5', sinscl=1.0):
645
+ """
646
+ Resample the ``data`` input image onto an output grid defined by
647
+ the ``pixmap`` array. ``blot_image`` performs resampling using one of
648
+ the several interpolation algorithms and, unlike the "drizzle" algorithm
649
+ with 'square', 'turbo', and 'point' kernels, this resampling is not
650
+ flux-conserving.
651
+
652
+ This method works best for with well sampled images and thus it is
653
+ typically used to resample the output of :py:class:`Drizzle` back to the
654
+ coordinate grids of input images of :py:meth:`Drizzle.add_image`.
655
+ The output of :py:class:`Drizzle` are usually well sampled images especially
656
+ if it was created from a set of dithered images.
657
+
658
+ Parameters
659
+ ----------
660
+ data : 2D array
661
+ Input numpy array of the source image in units of 'cps'.
662
+
663
+ pixmap : 3D array
664
+ A mapping from input image (``data``) coordinates to resampled
665
+ (``out_img``) coordinates. ``pixmap`` must be an array of shape
666
+ ``(Ny, Nx, 2)`` where ``(Ny, Nx)`` is the shape of the input image.
667
+ ``pixmap[..., 0]`` forms a 2D array of X-coordinates of input
668
+ pixels in the ouput frame and ``pixmap[..., 1]`` forms a 2D array of
669
+ Y-coordinates of input pixels in the ouput coordinate frame.
670
+
671
+ output_pixel_shape : tuple of int
672
+ A tuple of two integer numbers indicating the dimensions of the output
673
+ image ``(Nx, Ny)``.
674
+
675
+ pix_ratio : float
676
+ Ratio of the input image pixel scale to the ouput image pixel scale.
677
+
678
+ exptime : float
679
+ The exposure time of the input image.
680
+
681
+ interp : str, optional
682
+ The type of interpolation used in the resampling. The
683
+ possible values are:
684
+
685
+ - "nearest" (nearest neighbor interpolation);
686
+ - "linear" (bilinear interpolation);
687
+ - "poly3" (cubic polynomial interpolation);
688
+ - "poly5" (quintic polynomial interpolation);
689
+ - "sinc" (sinc interpolation);
690
+ - "lan3" (3rd order Lanczos interpolation); and
691
+ - "lan5" (5th order Lanczos interpolation).
692
+
693
+ sincscl : float, optional
694
+ The scaling factor for "sinc" interpolation.
695
+
696
+ Returns
697
+ -------
698
+ out_img : 2D numpy.ndarray
699
+ A 2D numpy array containing the resampled image data.
700
+
701
+ """
702
+ out_img = np.zeros(output_pixel_shape[::-1], dtype=np.float32)
703
+
704
+ cdrizzle.tblot(data, pixmap, out_img, scale=pix_ratio, kscale=1.0,
705
+ interp=interp, exptime=exptime, misval=0.0, sinscl=sinscl)
706
+
707
+ return out_img