drizzle 2.1.1__cp314-cp314t-win32.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.

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