drizzle 2.2.0__cp313-cp313-win_amd64.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.
@@ -0,0 +1,245 @@
1
+ import warnings
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from numpy.testing import assert_almost_equal, assert_equal
6
+
7
+ from astropy.wcs import FITSFixedWarning
8
+ from drizzle.tests.helpers import wcs_from_file
9
+ from drizzle.utils import (
10
+ _estimate_pixel_scale,
11
+ calc_pixmap,
12
+ decode_context,
13
+ estimate_pixel_scale_ratio,
14
+ )
15
+
16
+
17
+ def test_map_rectangular():
18
+ """
19
+ Make sure the initial index array has correct values
20
+ """
21
+ naxis1 = 1000
22
+ naxis2 = 10
23
+
24
+ pixmap = np.indices((naxis1, naxis2), dtype="float32")
25
+ pixmap = pixmap.transpose()
26
+
27
+ assert_equal(pixmap[5, 500], (500, 5))
28
+
29
+
30
+ @pytest.mark.parametrize("wcs_type", ["fits", "gwcs"])
31
+ def test_map_to_self(wcs_type):
32
+ """
33
+ Map a pixel array to itself. Should return the same array.
34
+ """
35
+ input_wcs = wcs_from_file("j8bt06nyq_sip_flt.fits", ext=1, wcs_type=wcs_type)
36
+ shape = input_wcs.array_shape
37
+
38
+ ok_pixmap = np.indices(shape, dtype="float64")
39
+ ok_pixmap = ok_pixmap.transpose()
40
+
41
+ pixmap = calc_pixmap(input_wcs, input_wcs)
42
+
43
+ # Got x-y transpose right
44
+ assert_equal(pixmap.shape, ok_pixmap.shape)
45
+
46
+ # Mapping an array to itself
47
+ assert_almost_equal(pixmap, ok_pixmap, decimal=5)
48
+
49
+ # user-provided shape
50
+ pixmap = calc_pixmap(input_wcs, input_wcs, (12, 34))
51
+ assert_equal(pixmap.shape, (12, 34, 2))
52
+
53
+ # Check that an exception is raised for WCS without pixel_shape and
54
+ # bounding_box:
55
+ input_wcs.pixel_shape = None
56
+ input_wcs.bounding_box = None
57
+ with pytest.raises(ValueError):
58
+ calc_pixmap(input_wcs, input_wcs)
59
+
60
+ # user-provided shape when array_shape is not set:
61
+ pixmap = calc_pixmap(input_wcs, input_wcs, (12, 34))
62
+ assert_equal(pixmap.shape, (12, 34, 2))
63
+
64
+ # from bounding box:
65
+ input_wcs.bounding_box = ((5.3, 33.5), (2.8, 11.5))
66
+ pixmap = calc_pixmap(input_wcs, input_wcs)
67
+ assert_equal(pixmap.shape, (12, 34, 2))
68
+
69
+ # from bounding box and pixel_shape (the later takes precedence):
70
+ input_wcs.array_shape = shape
71
+ pixmap = calc_pixmap(input_wcs, input_wcs)
72
+ assert_equal(pixmap.shape, ok_pixmap.shape)
73
+
74
+
75
+ @pytest.mark.parametrize("wcs_type", ["fits", "gwcs"])
76
+ def test_translated_map(wcs_type):
77
+ """
78
+ Map a pixel array to at translated array.
79
+ """
80
+ first_wcs = wcs_from_file("j8bt06nyq_sip_flt.fits", ext=1, wcs_type=wcs_type)
81
+ second_wcs = wcs_from_file(
82
+ "j8bt06nyq_sip_flt.fits",
83
+ ext=1,
84
+ crpix_shift=(-2, -2), # shift loaded WCS by adding this to CRPIX
85
+ wcs_type=wcs_type,
86
+ )
87
+
88
+ ok_pixmap = np.indices(first_wcs.array_shape, dtype="float32") - 2.0
89
+ ok_pixmap = ok_pixmap.transpose()
90
+
91
+ pixmap = calc_pixmap(first_wcs, second_wcs)
92
+
93
+ # Got x-y transpose right
94
+ assert_equal(pixmap.shape, ok_pixmap.shape)
95
+ # Mapping an array to a translated array
96
+ assert_almost_equal(pixmap[2:, 2:], ok_pixmap[2:, 2:], decimal=5)
97
+
98
+
99
+ def test_disable_gwcs_bbox():
100
+ """
101
+ Map a pixel array to a translated version ofitself.
102
+ """
103
+ first_wcs = wcs_from_file("j8bt06nyq_sip_flt.fits", ext=1, wcs_type="gwcs")
104
+ second_wcs = wcs_from_file(
105
+ "j8bt06nyq_sip_flt.fits",
106
+ ext=1,
107
+ crpix_shift=(-2, -2), # shift loaded WCS by adding this to CRPIX
108
+ wcs_type="gwcs",
109
+ )
110
+
111
+ ok_pixmap = np.indices(first_wcs.array_shape, dtype="float64") - 2.0
112
+ ok_pixmap = ok_pixmap.transpose()
113
+
114
+ # Mapping an array to a translated array
115
+
116
+ # disable both bounding boxes:
117
+ pixmap = calc_pixmap(first_wcs, second_wcs, disable_bbox="both")
118
+ assert_almost_equal(pixmap[2:, 2:], ok_pixmap[2:, 2:], decimal=5)
119
+ assert np.all(np.isfinite(pixmap[:2, :2]))
120
+ assert np.all(np.isfinite(pixmap[-2:, -2:]))
121
+ # check bbox was restored
122
+ assert first_wcs.bounding_box is not None
123
+ assert second_wcs.bounding_box is not None
124
+
125
+ # disable "from" bounding box:
126
+ pixmap = calc_pixmap(second_wcs, first_wcs, disable_bbox="from")
127
+ assert_almost_equal(pixmap[:-2, :-2], ok_pixmap[:-2, :-2] + 4.0, decimal=5)
128
+ assert np.all(np.logical_not(np.isfinite(pixmap[-2:, -2:])))
129
+ # check bbox was restored
130
+ assert first_wcs.bounding_box is not None
131
+ assert second_wcs.bounding_box is not None
132
+
133
+ # disable "to" bounding boxes:
134
+ pixmap = calc_pixmap(first_wcs, second_wcs, disable_bbox="to")
135
+ assert_almost_equal(pixmap[2:, 2:], ok_pixmap[2:, 2:], decimal=5)
136
+ assert np.all(np.isfinite(pixmap[:2, :2]))
137
+ assert np.all(pixmap[:2, :2] < 0.0)
138
+ assert np.all(np.isfinite(pixmap[-2:, -2:]))
139
+ # check bbox was restored
140
+ assert first_wcs.bounding_box is not None
141
+ assert second_wcs.bounding_box is not None
142
+
143
+ # enable all bounding boxes:
144
+ pixmap = calc_pixmap(first_wcs, second_wcs, disable_bbox="none")
145
+ assert_almost_equal(pixmap[2:, 2:], ok_pixmap[2:, 2:], decimal=5)
146
+ assert np.all(np.logical_not(np.isfinite(pixmap[:2, :2])))
147
+ # check bbox was restored
148
+ assert first_wcs.bounding_box is not None
149
+ assert second_wcs.bounding_box is not None
150
+
151
+
152
+ def test_estimate_pixel_scale_ratio():
153
+ w = wcs_from_file("j8bt06nyq_flt.fits", ext=1)
154
+ pscale = estimate_pixel_scale_ratio(w, w, w.wcs.crpix, (0, 0))
155
+ assert abs(pscale - 0.9999999916964737) < 1.0e-9
156
+
157
+
158
+ def test_estimate_pixel_scale_no_refpix():
159
+ # create a WCS without higher order (polynomial) distortions:
160
+ with warnings.catch_warnings():
161
+ warnings.simplefilter("ignore", category=FITSFixedWarning)
162
+ w = wcs_from_file("j8bt06nyq_sip_flt.fits", ext=1)
163
+ w.sip = None
164
+ w.det2im1 = None
165
+ w.det2im2 = None
166
+ w.cpdis1 = None
167
+ w.cpdis2 = None
168
+ pixel_shape = w.pixel_shape[:]
169
+
170
+ ref_pscale = _estimate_pixel_scale(w, w.wcs.crpix)
171
+
172
+ if hasattr(w, "bounding_box"):
173
+ del w.bounding_box
174
+ pscale1 = _estimate_pixel_scale(w, None)
175
+ assert np.allclose(ref_pscale, pscale1, atol=0.0, rtol=1.0e-8)
176
+
177
+ w.bounding_box = None
178
+ w.pixel_shape = None
179
+ pscale2 = _estimate_pixel_scale(w, None)
180
+ assert np.allclose(pscale1, pscale2, atol=0.0, rtol=1.0e-8)
181
+
182
+ w.pixel_shape = pixel_shape
183
+ pscale3 = _estimate_pixel_scale(w, None)
184
+ assert np.allclose(pscale1, pscale3, atol=0.0, rtol=1.0e-14)
185
+
186
+ w.bounding_box = ((-0.5, pixel_shape[0] - 0.5), (-0.5, pixel_shape[1] - 0.5))
187
+ pscale4 = _estimate_pixel_scale(w, None)
188
+ assert np.allclose(pscale3, pscale4, atol=0.0, rtol=1.0e-8)
189
+
190
+
191
+ def test_decode_context():
192
+ ctx = np.array(
193
+ [
194
+ [
195
+ [0, 0, 0, 0, 0, 0],
196
+ [0, 0, 0, 36196864, 0, 0],
197
+ [0, 0, 0, 0, 0, 0],
198
+ [0, 0, 0, 0, 0, 0],
199
+ [0, 0, 537920000, 0, 0, 0],
200
+ ],
201
+ [
202
+ [
203
+ 0,
204
+ 0,
205
+ 0,
206
+ 0,
207
+ 0,
208
+ 0,
209
+ ],
210
+ [0, 0, 0, 67125536, 0, 0],
211
+ [0, 0, 0, 0, 0, 0],
212
+ [0, 0, 0, 0, 0, 0],
213
+ [0, 0, 163856, 0, 0, 0],
214
+ ],
215
+ [
216
+ [0, 0, 0, 0, 0, 0],
217
+ [0, 0, 0, 8203, 0, 0],
218
+ [0, 0, 0, 0, 0, 0],
219
+ [0, 0, 0, 0, 0, 0],
220
+ [0, 0, 32865, 0, 0, 0],
221
+ ],
222
+ ],
223
+ dtype=np.int32,
224
+ )
225
+
226
+ idx1, idx2 = decode_context(ctx, [3, 2], [1, 4])
227
+
228
+ assert sorted(idx1) == [9, 12, 14, 19, 21, 25, 37, 40, 46, 58, 64, 65, 67, 77]
229
+ assert sorted(idx2) == [9, 20, 29, 36, 47, 49, 64, 69, 70, 79]
230
+
231
+ # context array must be 3D:
232
+ with pytest.raises(ValueError):
233
+ decode_context(ctx[0], [3, 2], [1, 4])
234
+
235
+ # pixel coordinates must be integer:
236
+ with pytest.raises(ValueError):
237
+ decode_context(ctx, [3.0, 2], [1, 4])
238
+
239
+ # coordinate lists must be equal in length:
240
+ with pytest.raises(ValueError):
241
+ decode_context(ctx, [3, 2], [1, 4, 5])
242
+
243
+ # coordinate lists must be 1D:
244
+ with pytest.raises(ValueError):
245
+ decode_context(ctx, [[3, 2]], [[1, 4]])
drizzle/util.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ Module ``util`` has been deprecated.
3
+ """
4
+
5
+ import warnings
6
+
7
+ warnings.warn(
8
+ "Module 'drizzle.util' has been deprecated since version 2.0.0 "
9
+ "and it will be removed in a future release. "
10
+ "Please replace calls to 'util.is_blank()' with alternative "
11
+ "implementation.",
12
+ DeprecationWarning,
13
+ )
14
+
15
+
16
+ def is_blank(value):
17
+ """
18
+ Determines whether or not a value is considered 'blank'.
19
+
20
+ Parameters
21
+ ----------
22
+ value : str
23
+ The value to check
24
+
25
+ Returns
26
+ -------
27
+ True or False
28
+ """
29
+ warnings.warn(
30
+ "'is_blank()' has been deprecated since version 2.0.0 "
31
+ "and it will be removed in a future release. "
32
+ "Please replace calls to 'is_blank()' with alternative implementation.",
33
+ DeprecationWarning,
34
+ )
35
+ return value.strip() == ""
drizzle/utils.py ADDED
@@ -0,0 +1,278 @@
1
+ import math
2
+
3
+ import numpy as np
4
+
5
+ __all__ = ["calc_pixmap", "decode_context", "estimate_pixel_scale_ratio"]
6
+
7
+ _DEG2RAD = math.pi / 180.0
8
+
9
+
10
+ def calc_pixmap(wcs_from, wcs_to, shape=None, disable_bbox="to"):
11
+ """
12
+ Calculate a discretized on a grid mapping between the pixels of two images
13
+ using provided WCS of the original ("from") image and the destination ("to")
14
+ image.
15
+
16
+ .. note::
17
+ This function assumes that output frames of ``wcs_from`` and ``wcs_to``
18
+ WCS have the same units.
19
+
20
+ Parameters
21
+ ----------
22
+ wcs_from : wcs
23
+ A WCS object representing the coordinate system you are
24
+ converting from. This object's ``array_shape`` (or ``pixel_shape``)
25
+ property will be used to define the shape of the pixel map array.
26
+ If ``shape`` parameter is provided, it will take precedence
27
+ over this object's ``array_shape`` value.
28
+
29
+ wcs_to : wcs
30
+ A WCS object representing the coordinate system you are
31
+ converting to.
32
+
33
+ shape : tuple, None, optional
34
+ A tuple of integers indicating the shape of the output array in the
35
+ ``numpy.ndarray`` order. When provided, it takes precedence over the
36
+ ``wcs_from.array_shape`` property.
37
+
38
+ disable_bbox : {"to", "from", "both", "none"}, optional
39
+ Indicates whether to use or not to use the bounding box of either
40
+ (both) ``wcs_from`` or (and) ``wcs_to`` when computing pixel map. When
41
+ ``disable_bbox`` is "none", pixel coordinates outside of the bounding
42
+ box are set to `NaN` only if ``wcs_from`` or (and) ``wcs_to`` sets
43
+ world coordinates to NaN when input pixel coordinates are outside of
44
+ the bounding box.
45
+
46
+ Returns
47
+ -------
48
+ pixmap : numpy.ndarray
49
+ A three dimensional array representing the transformation between
50
+ the two. The last dimension is of length two and contains the x and
51
+ y coordinates of a pixel center, respectively. The other two coordinates
52
+ correspond to the two coordinates of the image the first WCS is from.
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ A `ValueError` is raised when output pixel map shape cannot be
58
+ determined from provided inputs.
59
+
60
+ Notes
61
+ -----
62
+ When ``shape`` is not provided and ``wcs_from.array_shape`` is not set
63
+ (i.e., it is `None`), `calc_pixmap` will attempt to determine pixel map
64
+ shape from the ``bounding_box`` property of the input ``wcs_from`` object.
65
+ If ``bounding_box`` is not available, a `ValueError` will be raised.
66
+
67
+ """
68
+ if (bbox_from := getattr(wcs_from, "bounding_box", None)) is not None:
69
+ try:
70
+ # to avoid dependency on astropy just to check whether
71
+ # the bounding box is an instance of
72
+ # modeling.bounding_box.ModelBoundingBox, we try to
73
+ # directly use and bounding_box(order='F') and if it fails,
74
+ # fall back to converting the bounding box to a tuple
75
+ # (of intervals):
76
+ bbox_from = bbox_from.bounding_box(order="F")
77
+ except AttributeError:
78
+ bbox_from = tuple(bbox_from)
79
+
80
+ if (bbox_to := getattr(wcs_to, "bounding_box", None)) is not None:
81
+ try:
82
+ # to avoid dependency on astropy just to check whether
83
+ # the bounding box is an instance of
84
+ # modeling.bounding_box.ModelBoundingBox, we try to
85
+ # directly use and bounding_box(order='F') and if it fails,
86
+ # fall back to converting the bounding box to a tuple
87
+ # (of intervals):
88
+ bbox_to = bbox_to.bounding_box(order="F")
89
+ except AttributeError:
90
+ bbox_to = tuple(bbox_to)
91
+
92
+ if shape is None:
93
+ shape = wcs_from.array_shape
94
+ if shape is None and bbox_from is not None:
95
+ if (ndim := np.ndim(bbox_from)) == 1:
96
+ bbox_from = (bbox_from,)
97
+ if ndim > 1:
98
+ shape = tuple(math.ceil(lim[1] + 0.5) for lim in bbox_from[::-1])
99
+
100
+ if shape is None:
101
+ raise ValueError('The "from" WCS must have pixel_shape property set.')
102
+
103
+ y, x = np.indices(shape, dtype=np.float64)
104
+
105
+ # temporarily disable the bounding box for the "from" WCS:
106
+ if disable_bbox in ["from", "both"] and bbox_from is not None:
107
+ wcs_from.bounding_box = None
108
+ if disable_bbox in ["to", "both"] and bbox_to is not None:
109
+ wcs_to.bounding_box = None
110
+ try:
111
+ x, y = wcs_to.world_to_pixel_values(*wcs_from.pixel_to_world_values(x, y))
112
+ finally:
113
+ if bbox_from is not None:
114
+ wcs_from.bounding_box = bbox_from
115
+ if bbox_to is not None:
116
+ wcs_to.bounding_box = bbox_to
117
+
118
+ pixmap = np.dstack([x, y])
119
+ return pixmap
120
+
121
+
122
+ def estimate_pixel_scale_ratio(wcs_from, wcs_to, refpix_from=None, refpix_to=None):
123
+ """
124
+ Compute the ratio of the pixel scale of the "to" WCS at the ``refpix_to``
125
+ position to the pixel scale of the "from" WCS at the ``refpix_from``
126
+ position. Pixel scale ratio,
127
+ when requested, is computed near the centers of the bounding box
128
+ (a property of the WCS object) or near ``refpix_*`` coordinates
129
+ if supplied.
130
+
131
+ Pixel scale is estimated as the square root of pixel's area, i.e.,
132
+ pixels are assumed to have a square shape at the reference
133
+ pixel position. If input reference pixel position for a WCS is `None`,
134
+ it will be taken as the center of the bounding box
135
+ if ``wcs_*`` has a bounding box defined, or as the center of the box
136
+ defined by the ``pixel_shape`` attribute of the input WCS if
137
+ ``pixel_shape`` is defined (not `None`), or at pixel coordinates
138
+ ``(0, 0)``.
139
+
140
+ Parameters
141
+ ----------
142
+ wcs_from : wcs
143
+ A WCS object representing the coordinate system you are
144
+ converting from. This object *must* have ``pixel_shape`` property
145
+ defined.
146
+
147
+ wcs_to : wcs
148
+ A WCS object representing the coordinate system you are
149
+ converting to.
150
+
151
+ refpix_from : numpy.ndarray, tuple, list
152
+ Image coordinates of the reference pixel near which pixel scale should
153
+ be computed in the "from" image. In FITS WCS this could be, for example,
154
+ the value of CRPIX of the ``wcs_from`` WCS.
155
+
156
+ refpix_to : numpy.ndarray, tuple, list
157
+ Image coordinates of the reference pixel near which pixel scale should
158
+ be computed in the "to" image. In FITS WCS this could be, for example,
159
+ the value of CRPIX of the ``wcs_to`` WCS.
160
+
161
+ Returns
162
+ -------
163
+ pixel_scale_ratio : float
164
+ Estimate the ratio of "to" to "from" WCS pixel scales. This value is
165
+ returned only when ``estimate_pixel_scale_ratio`` is `True`.
166
+
167
+ """
168
+ pscale_ratio = _estimate_pixel_scale(wcs_to, refpix_to) / _estimate_pixel_scale(
169
+ wcs_from, refpix_from
170
+ )
171
+ return pscale_ratio
172
+
173
+
174
+ def _estimate_pixel_scale(wcs, refpix):
175
+ # estimate pixel scale (in rad) using approximate algorithm
176
+ # from https://trs.jpl.nasa.gov/handle/2014/40409
177
+ if refpix is None:
178
+ if hasattr(wcs, "bounding_box") and wcs.bounding_box is not None:
179
+ refpix = np.mean(wcs.bounding_box, axis=-1)
180
+ else:
181
+ if wcs.pixel_shape:
182
+ refpix = np.array([(i - 1) // 2 for i in wcs.pixel_shape])
183
+ else:
184
+ refpix = np.zeros(wcs.pixel_n_dim)
185
+
186
+ else:
187
+ refpix = np.asarray(refpix)
188
+
189
+ l1, phi1 = wcs.pixel_to_world_values(*(refpix - 0.5))
190
+ l2, phi2 = wcs.pixel_to_world_values(*(refpix + [-0.5, 0.5]))
191
+ l3, phi3 = wcs.pixel_to_world_values(*(refpix + 0.5))
192
+ l4, phi4 = wcs.pixel_to_world_values(*(refpix + [0.5, -0.5]))
193
+ area = _DEG2RAD * abs(
194
+ 0.5
195
+ * (
196
+ (l4 - l2) * (math.sin(_DEG2RAD * phi1) - math.sin(_DEG2RAD * phi3))
197
+ + (l1 - l3) * (math.sin(_DEG2RAD * phi2) - math.sin(_DEG2RAD * phi4))
198
+ )
199
+ )
200
+ return math.sqrt(area)
201
+
202
+
203
+ def decode_context(context, x, y):
204
+ """Get 0-based indices of input images that contributed to (resampled)
205
+ output pixel with coordinates ``x`` and ``y``.
206
+
207
+ Parameters
208
+ ----------
209
+ context: numpy.ndarray
210
+ A 3D `~numpy.ndarray` of integral data type.
211
+
212
+ x: int, list of integers, numpy.ndarray of integers
213
+ X-coordinate of pixels to decode (3rd index into the ``context`` array)
214
+
215
+ y: int, list of integers, numpy.ndarray of integers
216
+ Y-coordinate of pixels to decode (2nd index into the ``context`` array)
217
+
218
+ Returns
219
+ -------
220
+ A list of `numpy.ndarray` objects each containing indices of input images
221
+ that have contributed to an output pixel with coordinates ``x`` and ``y``.
222
+ The length of returned list is equal to the number of input coordinate
223
+ arrays ``x`` and ``y``.
224
+
225
+ Examples
226
+ --------
227
+ An example context array for an output image of array shape ``(5, 6)``
228
+ obtained by resampling 80 input images.
229
+
230
+ >>> import numpy as np
231
+ >>> from drizzle.utils import decode_context
232
+ >>> ctx = np.array(
233
+ ... [[[0, 0, 0, 0, 0, 0],
234
+ ... [0, 0, 0, 36196864, 0, 0],
235
+ ... [0, 0, 0, 0, 0, 0],
236
+ ... [0, 0, 0, 0, 0, 0],
237
+ ... [0, 0, 537920000, 0, 0, 0]],
238
+ ... [[0, 0, 0, 0, 0, 0,],
239
+ ... [0, 0, 0, 67125536, 0, 0],
240
+ ... [0, 0, 0, 0, 0, 0],
241
+ ... [0, 0, 0, 0, 0, 0],
242
+ ... [0, 0, 163856, 0, 0, 0]],
243
+ ... [[0, 0, 0, 0, 0, 0],
244
+ ... [0, 0, 0, 8203, 0, 0],
245
+ ... [0, 0, 0, 0, 0, 0],
246
+ ... [0, 0, 0, 0, 0, 0],
247
+ ... [0, 0, 32865, 0, 0, 0]]],
248
+ ... dtype=np.int32
249
+ ... )
250
+ >>> decode_context(ctx, [3, 2], [1, 4])
251
+ [array([ 9, 12, 14, 19, 21, 25, 37, 40, 46, 58, 64, 65, 67, 77]),
252
+ array([ 9, 20, 29, 36, 47, 49, 64, 69, 70, 79])]
253
+
254
+ """
255
+ if context.ndim != 3:
256
+ raise ValueError("'context' must be a 3D array.")
257
+
258
+ x = np.atleast_1d(x)
259
+ y = np.atleast_1d(y)
260
+
261
+ if x.size != y.size:
262
+ raise ValueError("Coordinate arrays must have equal length.")
263
+
264
+ if x.ndim != 1:
265
+ raise ValueError("Coordinates must be scalars or 1D arrays.")
266
+
267
+ if not (np.issubdtype(x.dtype, np.integer) and np.issubdtype(y.dtype, np.integer)):
268
+ raise ValueError("Pixel coordinates must be integer values")
269
+
270
+ nbits = 8 * context.dtype.itemsize
271
+ one = np.array(1, context.dtype)
272
+ flags = np.array([one << i for i in range(nbits)])
273
+
274
+ idx = []
275
+ for xi, yi in zip(x, y):
276
+ idx.append(np.flatnonzero(np.bitwise_and.outer(context[:, yi, xi], flags)))
277
+
278
+ return idx