healpix-geo 0.0.7__cp312-cp312-musllinux_1_2_armv7l.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 healpix-geo might be problematic. Click here for more details.

healpix_geo/ring.py ADDED
@@ -0,0 +1,286 @@
1
+ import numpy as np
2
+
3
+ from healpix_geo import healpix_geo
4
+ from healpix_geo.utils import _check_depth, _check_ipixels, _check_ring
5
+
6
+
7
+ def healpix_to_lonlat(ipix, depth, ellipsoid="sphere", num_threads=0):
8
+ r"""Get the longitudes and latitudes of the center of some HEALPix cells.
9
+
10
+ Parameters
11
+ ----------
12
+ ipix : `numpy.ndarray`
13
+ The HEALPix cell indexes given as a `np.uint64` numpy array.
14
+ depth : `numpy.ndarray`
15
+ The HEALPix cell depth given as a `np.uint8` numpy array.
16
+ ellipsoid : str, default: "sphere"
17
+ Reference ellipsoid to evaluate healpix on. If ``"sphere"``, this will return
18
+ the same result as :py:func:`cdshealpix.ring.healpix_to_lonlat`.
19
+ num_threads : int, optional
20
+ Specifies the number of threads to use for the computation. Default to 0 means
21
+ it will choose the number of threads based on the RAYON_NUM_THREADS environment variable (if set),
22
+ or the number of logical CPUs (otherwise)
23
+
24
+ Returns
25
+ -------
26
+ lon, lat : array-like
27
+ The coordinates of the center of the HEALPix cells given as a longitude, latitude tuple.
28
+
29
+ Raises
30
+ ------
31
+ ValueError
32
+ When the HEALPix cell indexes given have values out of :math:`[0, 4^{29 - depth}[`.
33
+ ValueError
34
+ When the name of the ellipsoid is unknown.
35
+
36
+ Examples
37
+ --------
38
+ >>> from healpix_geo.ring import healpix_to_lonlat
39
+ >>> import numpy as np
40
+ >>> cell_ids = np.array([42, 6, 10])
41
+ >>> depth = 3
42
+ >>> lon, lat = healpix_to_lonlat(ipix, depth, ellipsoid="WGS84")
43
+ """
44
+ _check_depth(depth)
45
+ ipix = np.atleast_1d(ipix)
46
+ _check_ipixels(data=ipix, depth=depth)
47
+ ipix = ipix.astype(np.uint64)
48
+
49
+ num_threads = np.uint16(num_threads)
50
+
51
+ latitude = np.empty_like(ipix, dtype="float64")
52
+ longitude = np.empty_like(ipix, dtype="float64")
53
+
54
+ healpix_geo.ring.healpix_to_lonlat(
55
+ depth, ipix, ellipsoid, longitude, latitude, num_threads
56
+ )
57
+
58
+ return longitude, latitude
59
+
60
+
61
+ def lonlat_to_healpix(longitude, latitude, depth, ellipsoid="sphere", num_threads=0):
62
+ r"""Get the HEALPix indexes that contains specific points.
63
+
64
+ Parameters
65
+ ----------
66
+ lon : array-like
67
+ The longitudes of the input points, in degrees.
68
+ lat : array-like
69
+ The latitudes of the input points, in degrees.
70
+ depth : int or array-like of int
71
+ The HEALPix cell depth given as a `np.uint8` numpy array.
72
+ ellipsoid : str, default: "sphere"
73
+ Reference ellipsoid to evaluate healpix on. If ``"sphere"``, this will return
74
+ the same result as :py:func:`cdshealpix.ring.lonlat_to_healpix`.
75
+ num_threads : int, optional
76
+ Specifies the number of threads to use for the computation. Default to 0 means
77
+ it will choose the number of threads based on the RAYON_NUM_THREADS environment variable (if set),
78
+ or the number of logical CPUs (otherwise)
79
+
80
+ Returns
81
+ -------
82
+ ipix : `numpy.ndarray`
83
+ A numpy array containing all the HEALPix cell indexes stored as `np.uint64`.
84
+
85
+ Raises
86
+ ------
87
+ ValueError
88
+ When the number of longitudes and latitudes given do not match.
89
+ ValueError
90
+ When the name of the ellipsoid is unknown.
91
+
92
+ Examples
93
+ --------
94
+ >>> from cdshealpix.ring import lonlat_to_healpix
95
+ >>> import numpy as np
96
+ >>> lon = np.array([0, 50, 25], dtype="float64")
97
+ >>> lat = np.array([6, -12, 45], dtype="float64")
98
+ >>> depth = 3
99
+ >>> ipix = lonlat_to_healpix(lon, lat, depth, ellipsoid="WGS84")
100
+ """
101
+ _check_depth(depth)
102
+ longitude = np.atleast_1d(longitude).astype("float64")
103
+ latitude = np.atleast_1d(latitude).astype("float64")
104
+
105
+ num_threads = np.uint16(num_threads)
106
+
107
+ ipix = np.empty_like(longitude, dtype="uint64")
108
+
109
+ healpix_geo.ring.lonlat_to_healpix(
110
+ depth, longitude, latitude, ellipsoid, ipix, num_threads
111
+ )
112
+
113
+ return ipix
114
+
115
+
116
+ def vertices(ipix, depth, ellipsoid, num_threads=0):
117
+ """Get the longitudes and latitudes of the vertices of some HEALPix cells at a given depth.
118
+
119
+ This method returns the 4 vertices of each cell in `ipix`.
120
+
121
+ Parameters
122
+ ----------
123
+ ipix : `numpy.ndarray`
124
+ The HEALPix cell indexes given as a `np.uint64` numpy array.
125
+ depth : int, or `numpy.ndarray`
126
+ The depth of the HEALPix cells. If given as an array, should have the same shape than ipix
127
+ ellipsoid : str, default: "sphere"
128
+ Reference ellipsoid to evaluate healpix on. If ``"sphere"``, this will return
129
+ the same result as :py:func:`cdshealpix.ring.vertices`.
130
+ num_threads : int, optional
131
+ Specifies the number of threads to use for the computation. Default to 0 means
132
+ it will choose the number of threads based on the RAYON_NUM_THREADS environment variable (if set),
133
+ or the number of logical CPUs (otherwise)
134
+
135
+ Returns
136
+ -------
137
+ longitude, latitude : array-like
138
+ The sky coordinates of the 4 vertices of the HEALPix cells.
139
+ `lon` and `lat` are of shape :math:`N` x :math:`4` numpy arrays where N is the number of HEALPix cell given in `ipix`.
140
+
141
+ Raises
142
+ ------
143
+ ValueError
144
+ When the HEALPix cell indexes given have values out of :math:`[0, 4^{29 - depth}[`.
145
+
146
+ Examples
147
+ --------
148
+ >>> from healpix_geo.ring import vertices
149
+ >>> import numpy as np
150
+ >>> ipix = np.array([42, 6, 10])
151
+ >>> depth = 12
152
+ >>> lon, lat = vertices(ipix, depth, ellipsoid="sphere")
153
+ """
154
+ _check_depth(depth)
155
+ ipix = np.atleast_1d(ipix)
156
+ _check_ipixels(data=ipix, depth=depth)
157
+ ipix = ipix.astype(np.uint64)
158
+
159
+ num_threads = np.uint16(num_threads)
160
+
161
+ shape = ipix.shape + (4,)
162
+ longitude = np.empty(shape=shape, dtype="float64")
163
+ latitude = np.empty(shape=shape, dtype="float64")
164
+
165
+ healpix_geo.ring.vertices(depth, ipix, ellipsoid, longitude, latitude, num_threads)
166
+
167
+ return longitude, latitude
168
+
169
+
170
+ def kth_neighbourhood(ipix, depth, ring, num_threads=0):
171
+ """Get the kth ring neighbouring cells of some HEALPix cells at a given depth.
172
+
173
+ This method returns a :math:`N` x :math:`(2 k + 1)^2` `np.uint64` numpy array containing the neighbours of each cell of the :math:`N` sized `ipix` array.
174
+ This method is wrapped around the `kth_neighbourhood <https://docs.rs/cdshealpix/0.1.5/cdshealpix/nested/struct.Layer.html#method.neighbours_in_kth_ring>`__
175
+ method from the `cdshealpix Rust crate <https://crates.io/crates/cdshealpix>`__.
176
+
177
+ Parameters
178
+ ----------
179
+ ipix : `numpy.ndarray`
180
+ The HEALPix cell indexes given as a `np.uint64` numpy array.
181
+ depth : int
182
+ The depth of the HEALPix cells.
183
+ ring : int
184
+ The number of rings. `ring=0` returns just the input cell ids, `ring=1` returns the 8 (or 7) immediate
185
+ neighbours, `ring=2` returns the 8 (or 7) immediate neighbours plus their immediate neighbours (a total of 24 cells), and so on.
186
+ num_threads : int, optional
187
+ Specifies the number of threads to use for the computation. Default to 0 means
188
+ it will choose the number of threads based on the RAYON_NUM_THREADS environment variable (if set),
189
+ or the number of logical CPUs (otherwise)
190
+
191
+ Returns
192
+ -------
193
+ neighbours : `numpy.ndarray`
194
+ A :math:`N` x :math:`(2 k + 1)^2` `np.int64` numpy array containing the kth ring neighbours of each cell.
195
+ The :math:`5^{th}` element corresponds to the index of HEALPix cell from which the neighbours are evaluated.
196
+ All its 8 neighbours occup the remaining elements of the line.
197
+
198
+ Raises
199
+ ------
200
+ ValueError
201
+ When the HEALPix cell indexes given have values out of :math:`[0, 4^{29 - depth}[`.
202
+
203
+ Examples
204
+ --------
205
+ >>> from cdshealpix import neighbours_in_kth_ring
206
+ >>> import numpy as np
207
+ >>> ipix = np.array([42, 6, 10])
208
+ >>> depth = 12
209
+ >>> ring = 3
210
+ >>> neighbours = neighbours_in_kth_ring(ipix, depth, ring)
211
+ """
212
+ _check_depth(depth)
213
+ ipix = np.atleast_1d(ipix)
214
+ _check_ipixels(data=ipix, depth=depth)
215
+ ipix = ipix.astype(np.uint64)
216
+ _check_ring(depth, ring)
217
+
218
+ # Allocation of the array containing the neighbours
219
+ neighbours = np.full(
220
+ (*ipix.shape, (2 * ring + 1) ** 2), dtype=np.int64, fill_value=-1
221
+ )
222
+ num_threads = np.uint16(num_threads)
223
+ healpix_geo.ring.kth_neighbourhood(depth, ipix, ring, neighbours, num_threads)
224
+
225
+ return neighbours
226
+
227
+
228
+ def angular_distances(from_, to_, depth, num_threads=0):
229
+ """Compute the angular distances
230
+
231
+ Parameters
232
+ ----------
233
+ from_ : numpy.ndarray
234
+ The source Healpix cell indexes given as a ``np.uint64`` numpy array. Should be 1D.
235
+ to_ : numpy.ndarray
236
+ The destination Healpix cell indexes given as a ``np.uint64`` numpy array.
237
+ Should be 2D.
238
+ depth : int
239
+ The depth of the Healpix cells.
240
+ num_threads : int, default: 0
241
+ Specifies the number of threads to use for the computation. Default to 0 means
242
+ it will choose the number of threads based on the RAYON_NUM_THREADS environment variable (if set),
243
+ or the number of logical CPUs (otherwise)
244
+
245
+ Returns
246
+ -------
247
+ distances : numpy.ndarray
248
+ The angular distances in radians.
249
+
250
+ Raises
251
+ ------
252
+ ValueError
253
+ When the Healpix cell indexes given have values out of :math:`[0, 4^{depth}[`.
254
+ """
255
+ _check_depth(depth)
256
+
257
+ from_ = np.atleast_1d(from_)
258
+ _check_ipixels(data=from_, depth=depth)
259
+ from_ = from_.astype("uint64")
260
+
261
+ mask = to_ != -1
262
+ masked_to = np.where(mask, to_, 0)
263
+
264
+ to_ = np.atleast_1d(masked_to)
265
+ _check_ipixels(data=to_, depth=depth)
266
+ to_ = to_.astype("uint64")
267
+
268
+ if from_.shape != to_.shape and from_.shape != to_.shape[:-1]:
269
+ raise ValueError(
270
+ "The shape of `from_` must be compatible with the shape of `to_`:\n"
271
+ f"{to_.shape} or {to_.shape[:-1]} must be equal to {from_.shape}."
272
+ )
273
+
274
+ if from_.shape == to_.shape:
275
+ intermediate_shape = to_.shape + (1,)
276
+ else:
277
+ intermediate_shape = to_.shape
278
+
279
+ distances = np.full(intermediate_shape, dtype="float64", fill_value=np.nan)
280
+ num_threads = np.uint16(num_threads)
281
+
282
+ healpix_geo.ring.angular_distances(
283
+ depth, from_, np.reshape(to_, intermediate_shape), distances, num_threads
284
+ )
285
+
286
+ return np.where(mask, np.reshape(distances, to_.shape), np.nan)
healpix_geo/slices.py ADDED
@@ -0,0 +1,4 @@
1
+ from healpix_geo.healpix_geo import slices as slices_
2
+
3
+ Slice = slices_.Slice # noqa: F401
4
+ ConcreteSlice = slices_.ConcreteSlice # noqa: F401
@@ -0,0 +1,293 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ import healpix_geo.nested as healpix
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ ["flat", "expected_ids", "expected_depths", "expected_coverage"],
9
+ (
10
+ pytest.param(
11
+ False,
12
+ np.array(
13
+ [
14
+ 0,
15
+ 4,
16
+ 5,
17
+ 6,
18
+ 7,
19
+ 8,
20
+ 9,
21
+ 10,
22
+ 11,
23
+ 12,
24
+ 13,
25
+ 14,
26
+ 15,
27
+ 69,
28
+ 70,
29
+ 71,
30
+ 76,
31
+ 77,
32
+ 79,
33
+ 89,
34
+ 90,
35
+ 91,
36
+ 92,
37
+ 94,
38
+ 95,
39
+ ],
40
+ dtype="uint64",
41
+ ),
42
+ np.array(
43
+ [
44
+ 1,
45
+ 2,
46
+ 2,
47
+ 2,
48
+ 2,
49
+ 2,
50
+ 2,
51
+ 2,
52
+ 2,
53
+ 2,
54
+ 2,
55
+ 2,
56
+ 2,
57
+ 2,
58
+ 2,
59
+ 2,
60
+ 2,
61
+ 2,
62
+ 2,
63
+ 2,
64
+ 2,
65
+ 2,
66
+ 2,
67
+ 2,
68
+ 2,
69
+ ],
70
+ dtype="u8",
71
+ ),
72
+ np.array(
73
+ [
74
+ True,
75
+ True,
76
+ False,
77
+ True,
78
+ False,
79
+ True,
80
+ True,
81
+ False,
82
+ False,
83
+ True,
84
+ False,
85
+ False,
86
+ False,
87
+ False,
88
+ False,
89
+ True,
90
+ False,
91
+ False,
92
+ False,
93
+ False,
94
+ False,
95
+ True,
96
+ False,
97
+ False,
98
+ False,
99
+ ]
100
+ ),
101
+ ),
102
+ pytest.param(
103
+ True,
104
+ np.array(
105
+ [
106
+ 0,
107
+ 1,
108
+ 2,
109
+ 3,
110
+ 4,
111
+ 5,
112
+ 6,
113
+ 7,
114
+ 8,
115
+ 9,
116
+ 10,
117
+ 11,
118
+ 12,
119
+ 13,
120
+ 14,
121
+ 15,
122
+ 69,
123
+ 70,
124
+ 71,
125
+ 76,
126
+ 77,
127
+ 79,
128
+ 89,
129
+ 90,
130
+ 91,
131
+ 92,
132
+ 94,
133
+ 95,
134
+ ],
135
+ dtype="uint64",
136
+ ),
137
+ np.array(
138
+ [
139
+ 2,
140
+ 2,
141
+ 2,
142
+ 2,
143
+ 2,
144
+ 2,
145
+ 2,
146
+ 2,
147
+ 2,
148
+ 2,
149
+ 2,
150
+ 2,
151
+ 2,
152
+ 2,
153
+ 2,
154
+ 2,
155
+ 2,
156
+ 2,
157
+ 2,
158
+ 2,
159
+ 2,
160
+ 2,
161
+ 2,
162
+ 2,
163
+ 2,
164
+ 2,
165
+ 2,
166
+ 2,
167
+ ],
168
+ dtype="u8",
169
+ ),
170
+ np.array(
171
+ [
172
+ True,
173
+ True,
174
+ True,
175
+ True,
176
+ True,
177
+ False,
178
+ True,
179
+ False,
180
+ True,
181
+ True,
182
+ False,
183
+ False,
184
+ True,
185
+ False,
186
+ False,
187
+ False,
188
+ False,
189
+ False,
190
+ True,
191
+ False,
192
+ False,
193
+ False,
194
+ False,
195
+ False,
196
+ True,
197
+ False,
198
+ False,
199
+ False,
200
+ ]
201
+ ),
202
+ id="flat",
203
+ ),
204
+ ),
205
+ )
206
+ def test_zone_coverage(flat, expected_ids, expected_depths, expected_coverage):
207
+ zone = (0.0, 0.0, 90.0, 90.0)
208
+ depth = 2
209
+
210
+ cell_ids, depths, fully_covered = healpix.zone_coverage(
211
+ zone, depth, ellipsoid="sphere", flat=flat
212
+ )
213
+
214
+ np.testing.assert_equal(cell_ids, expected_ids)
215
+ np.testing.assert_equal(depths, expected_depths)
216
+ np.testing.assert_equal(fully_covered, expected_coverage)
217
+
218
+
219
+ def test_box_coverage():
220
+ center = (45.0, 45.0)
221
+ size = (10.0, 10.0)
222
+ angle = 0.0
223
+
224
+ depth = 1
225
+
226
+ cell_ids, depths, fully_covered = healpix.box_coverage(
227
+ center, size, angle, depth, ellipsoid="WGS84"
228
+ )
229
+
230
+ expected_cell_ids = np.array([0, 1, 2, 3], dtype="uint64")
231
+ expected_depths = np.array([1, 1, 1, 1], dtype="uint8")
232
+ expected_coverage = np.array([False, False, False, False])
233
+
234
+ np.testing.assert_equal(cell_ids, expected_cell_ids)
235
+ np.testing.assert_equal(depths, expected_depths)
236
+ np.testing.assert_equal(fully_covered, expected_coverage)
237
+
238
+
239
+ def test_polygon_coverage():
240
+ vertices = np.array(
241
+ [[40.0, 40.0], [50.0, 40.0], [50.0, 50.0], [40.0, 50.0]], dtype="float64"
242
+ )
243
+
244
+ depth = 1
245
+
246
+ cell_ids, depths, fully_covered = healpix.polygon_coverage(
247
+ vertices, depth, ellipsoid="WGS84"
248
+ )
249
+
250
+ expected_cell_ids = np.array([0, 1, 2, 3], dtype="uint64")
251
+ expected_depths = np.array([1, 1, 1, 1], dtype="uint8")
252
+ expected_coverage = np.array([False, False, False, False])
253
+
254
+ np.testing.assert_equal(cell_ids, expected_cell_ids)
255
+ np.testing.assert_equal(depths, expected_depths)
256
+ np.testing.assert_equal(fully_covered, expected_coverage)
257
+
258
+
259
+ def test_cone_coverage():
260
+ center = (45.0, 45.0)
261
+ radius = 5.0
262
+ depth = 1
263
+
264
+ cell_ids, depths, fully_covered = healpix.cone_coverage(
265
+ center, radius, depth, ellipsoid="WGS84"
266
+ )
267
+
268
+ expected_cell_ids = np.array([0, 1, 2, 3], dtype="uint64")
269
+ expected_depths = np.array([1, 1, 1, 1], dtype="uint8")
270
+ expected_coverage = np.array([False, False, False, False])
271
+
272
+ np.testing.assert_equal(cell_ids, expected_cell_ids)
273
+ np.testing.assert_equal(depths, expected_depths)
274
+ np.testing.assert_equal(fully_covered, expected_coverage)
275
+
276
+
277
+ def test_elliptical_cone_coverage():
278
+ center = (45.0, 45.0)
279
+ ellipse_geometry = (10.0, 8.0)
280
+ positional_angle = 30.0
281
+ depth = 1
282
+
283
+ cell_ids, depths, fully_covered = healpix.elliptical_cone_coverage(
284
+ center, ellipse_geometry, positional_angle, depth, ellipsoid="WGS84"
285
+ )
286
+
287
+ expected_cell_ids = np.array([0, 1, 2, 3], dtype="uint64")
288
+ expected_depths = np.array([1, 1, 1, 1], dtype="uint8")
289
+ expected_coverage = np.array([False, False, False, False])
290
+
291
+ np.testing.assert_equal(cell_ids, expected_cell_ids)
292
+ np.testing.assert_equal(depths, expected_depths)
293
+ np.testing.assert_equal(fully_covered, expected_coverage)
@@ -0,0 +1,81 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ import healpix_geo
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ ["depth", "indexing_scheme", "from_", "to_", "expected"],
9
+ (
10
+ (
11
+ 1,
12
+ "nested",
13
+ np.array([0, 16, 25, 32, 46]),
14
+ np.array([2, 15, 27, 40, 41], dtype="int64"),
15
+ np.array(
16
+ [0.51262797, 1.60992678, 0.51347673, 0.82227572, 0.57850402],
17
+ dtype="float64",
18
+ ),
19
+ ),
20
+ (
21
+ 1,
22
+ "ring",
23
+ np.array([0, 16, 25, 32, 46]),
24
+ np.array([2, 15, 27, 40, 41], dtype="int64"),
25
+ np.array(
26
+ [0.82227572, 0.73824548, 1.57079633, 0.51262797, 0.48146047],
27
+ dtype="float64",
28
+ ),
29
+ ),
30
+ (
31
+ 1,
32
+ "nested",
33
+ np.array([0, 16, 25, 32, 46]),
34
+ np.array([[2], [15], [27], [40], [41]], dtype="int64"),
35
+ np.array(
36
+ [[0.51262797], [1.60992678], [0.51347673], [0.82227572], [0.57850402]],
37
+ dtype="float64",
38
+ ),
39
+ ),
40
+ (
41
+ 2,
42
+ "ring",
43
+ np.array([0, 16, 25, 32, 46]),
44
+ np.array([[2, 4], [15, 7], [27, 26], [40, -1], [-1, 41]], dtype="int64"),
45
+ np.array(
46
+ [
47
+ [0.4089604, 0.23486912],
48
+ [0.30291976, 0.283655],
49
+ [0.57850402, 0.29185825],
50
+ [1.87523829, np.nan],
51
+ [np.nan, 1.60781736],
52
+ ],
53
+ dtype="float64",
54
+ ),
55
+ ),
56
+ ),
57
+ )
58
+ def test_distance(depth, indexing_scheme, from_, to_, expected):
59
+ if indexing_scheme == "nested":
60
+ angular_distances = healpix_geo.nested.angular_distances
61
+ elif indexing_scheme == "ring":
62
+ angular_distances = healpix_geo.ring.angular_distances
63
+
64
+ actual = angular_distances(from_, to_, depth)
65
+
66
+ np.testing.assert_allclose(actual, expected)
67
+
68
+
69
+ @pytest.mark.parametrize("indexing_scheme", ["ring", "nested"])
70
+ def test_distance_error(indexing_scheme):
71
+ if indexing_scheme == "nested":
72
+ angular_distances = healpix_geo.nested.angular_distances
73
+ elif indexing_scheme == "ring":
74
+ angular_distances = healpix_geo.ring.angular_distances
75
+
76
+ from_ = np.array([4, 7])
77
+ to_ = np.array([[2, 3], [4, 6], [5, 4]])
78
+ depth = 1
79
+
80
+ with pytest.raises(ValueError, match="The shape of `from_` must be compatible"):
81
+ angular_distances(from_, to_, depth)