Skip to content

Commit a68902d

Browse files
committed
Added round-tripping test via Dask class and fix a number of issues that came up
1 parent 90bccfb commit a68902d

File tree

5 files changed

+339
-83
lines changed

5 files changed

+339
-83
lines changed

reproject/hips/_dask_array.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import os
2+
import struct
3+
import urllib
4+
import uuid
5+
import functools
6+
7+
import numpy as np
8+
from astropy import units as u
9+
from astropy.io import fits
10+
from astropy.wcs import WCS
11+
from astropy_healpix import HEALPix, level_to_nside
12+
from dask import array as da
13+
from astropy.utils.data import download_file
14+
from astropy.wcs.utils import celestial_frame_to_wcs
15+
16+
from .utils import is_url, load_properties, tile_filename, tile_header, map_header
17+
from .high_level import VALID_COORD_SYSTEM
18+
19+
__all__ = ['hips_as_dask_and_wcs']
20+
21+
22+
class HiPSArray:
23+
24+
def __init__(self, directory_or_url, level=None):
25+
26+
self._directory_or_url = directory_or_url
27+
28+
self._is_url = is_url(directory_or_url)
29+
30+
self._properties = load_properties(directory_or_url)
31+
32+
self._tile_width = int(self._properties["hips_tile_width"])
33+
self._order = int(self._properties["hips_order"])
34+
self._level = self._order if level is None else level
35+
self._tile_format = self._properties["hips_tile_format"]
36+
self._frame_str = self._properties["hips_frame"]
37+
self._frame = VALID_COORD_SYSTEM[self._frame_str]
38+
39+
self._hp = HEALPix(nside=level_to_nside(self._level), frame=self._frame, order="nested")
40+
41+
self._header = map_header(level=self._level, frame=self._frame, tile_size=self._tile_width)
42+
43+
self.wcs = WCS(self._header)
44+
self.shape = self.wcs.array_shape
45+
46+
self.dtype = float
47+
self.ndim = 2
48+
49+
self.chunksize = (self._tile_width, self._tile_width)
50+
51+
self._nan = np.nan * np.ones(self.chunksize, dtype=self.dtype)
52+
53+
self._blank = np.broadcast_to(np.nan, self.shape)
54+
55+
def __getitem__(self, item):
56+
57+
if item[0].start == item[0].stop or item[1].start == item[1].stop:
58+
return self._blank[item]
59+
60+
# For now assume item is a list of slices. Find
61+
62+
# imid = (item[0].start + item[0].stop) // 2
63+
# jmid = (item[1].start + item[1].stop) // 2
64+
65+
istart = item[0].start
66+
irange = item[0].stop - item[0].start
67+
imid = np.array([istart + 0.25 * irange, istart + 0.75 * irange])
68+
69+
jstart = item[1].start
70+
jrange = item[1].stop - item[1].start
71+
jmid = np.array([jstart + 0.25 * jrange, jstart + 0.75 * jrange])
72+
73+
# Convert pixel coordinates to HEALPix indices
74+
75+
coord = self.wcs.pixel_to_world(jmid, imid)
76+
77+
if self._frame_str == "equatorial":
78+
lon, lat = coord.ra.deg, coord.dec.deg
79+
elif self._frame_str == "galactic":
80+
lon, lat = coord.l.deg, coord.b.deg
81+
else:
82+
raise NotImplementedError()
83+
84+
if np.all(np.isnan(lon) | np.isnan(lat)):
85+
return self._nan
86+
87+
index = self._hp.skycoord_to_healpix(coord)
88+
89+
if np.all(index == -1):
90+
return self._nan
91+
92+
index = np.max(index)
93+
94+
return self._get_tile(level=self._level, index=index)
95+
96+
@functools.lru_cache(maxsize=128)
97+
def _get_tile(self, *, level, index):
98+
99+
filename_or_url = tile_filename(
100+
level=self._level,
101+
index=index,
102+
output_directory=self._directory_or_url,
103+
extension="fits",
104+
)
105+
106+
if self._is_url:
107+
try:
108+
filename = download_file(filename_or_url, cache=True)
109+
except urllib.error.HTTPError:
110+
return self._nan
111+
elif not os.path.exists(filename_or_url):
112+
return self._nan
113+
else:
114+
filename = filename_or_url
115+
116+
with fits.open(filename) as hdulist:
117+
hdu = hdulist[0]
118+
# data = hdu.data[::-1]
119+
data = hdu.data
120+
121+
# return np.ones(hdu.data.shape) * index
122+
123+
return data
124+
125+
126+
def hips_as_dask_and_wcs(directory_or_url, *, level=None):
127+
array_wrapper = HiPSArray(directory_or_url, level=level)
128+
return da.from_array(
129+
array_wrapper,
130+
chunks=array_wrapper.chunksize,
131+
name=str(uuid.uuid4()),
132+
meta=np.array([], dtype=float)
133+
), array_wrapper.wcs

reproject/hips/high_level.py

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import numpy as np
1111
from astropy.coordinates import ICRS, BarycentricTrueEcliptic, Galactic
1212
from astropy.io import fits
13-
from astropy.nddata import block_reduce
1413
from astropy_healpix import (
1514
HEALPix,
1615
level_to_nside,
@@ -204,6 +203,8 @@ def reproject_to_hips(
204203
# Determine center of image and radius to furthest corner, to determine
205204
# which HiPS tiles need to be generated
206205

206+
# TODO: this will fail for e.g. allsky maps
207+
207208
ny, nx = array_in.shape[-2:]
208209

209210
cen_x, cen_y = (nx - 1) / 2, (ny - 1) / 2
@@ -216,9 +217,6 @@ def reproject_to_hips(
216217

217218
radius = cor_world.separation(cen_world).max() * 2
218219

219-
print(cen_world)
220-
print(cor_world)
221-
222220
# TODO: in future if astropy-healpix implements polygon searches, we could
223221
# use that instead
224222

@@ -227,9 +225,9 @@ def reproject_to_hips(
227225
nside = level_to_nside(level)
228226
hp = HEALPix(nside=nside, order="nested", frame=frame)
229227

230-
indices = hp.cone_search_skycoord(cen_world, radius=radius)
228+
# indices = hp.cone_search_skycoord(cen_world, radius=radius)
231229

232-
print(indices)
230+
indices = np.arange(hp.npix)
233231

234232
logger.info(f"Found {len(indices)} tiles (at most) to generate at level {level}")
235233

@@ -241,12 +239,28 @@ def reproject_to_hips(
241239

242240
# Iterate over the tiles and generate them
243241
def process(index):
244-
header = tile_header(level=level, index=index, frame=frame, tile_size=tile_size)
245242
if hasattr(wcs_in, "deepcopy"):
246243
wcs_in_copy = wcs_in.deepcopy()
247244
else:
248245
wcs_in_copy = deepcopy(wcs_in)
249-
array_out, footprint = reproject_function((array_in, wcs_in_copy), header, **kwargs)
246+
247+
header = tile_header(level=level, index=index, frame=frame, tile_size=tile_size)
248+
249+
if isinstance(header, tuple):
250+
array_out1, footprint1 = reproject_function(
251+
(array_in, wcs_in_copy), header[0], **kwargs
252+
)
253+
array_out2, footprint2 = reproject_function(
254+
(array_in, wcs_in_copy), header[1], **kwargs
255+
)
256+
array_out = (
257+
np.nan_to_num(array_out1) * footprint1 + np.nan_to_num(array_out2) * footprint2
258+
) / (footprint1 + footprint2)
259+
footprint = (footprint1 + footprint2) / 2
260+
header = header[0]
261+
else:
262+
array_out, footprint = reproject_function((array_in, wcs_in_copy), header, **kwargs)
263+
250264
if tile_format != "png":
251265
array_out[np.isnan(array_out)] = 0.0
252266
if np.all(footprint == 0):
@@ -260,6 +274,7 @@ def process(index):
260274
extension=EXTENSION[tile_format],
261275
),
262276
array_out,
277+
header,
263278
)
264279
else:
265280
if tile_format == "png":
@@ -294,76 +309,76 @@ def process(index):
294309

295310
indices = np.array(generated_indices)
296311

297-
# Iterate over higher levels and compute lower resolution tiles
298-
for ilevel in range(level - 1, -1, -1):
299-
300-
# Find index of tiles to produce at lower-resolution levels
301-
indices = np.sort(np.unique(indices // 4))
302-
303-
make_tile_folders(level=ilevel, indices=indices, output_directory=output_directory)
304-
305-
for index in indices:
306-
307-
header = tile_header(level=ilevel, index=index, frame=frame, tile_size=tile_size)
308-
309-
if tile_format == "fits":
310-
array = np.zeros((tile_size, tile_size))
311-
elif tile_format == "png":
312-
array = np.zeros((tile_size, tile_size, 4), dtype=np.uint8)
313-
else:
314-
array = np.zeros((tile_size, tile_size, 3), dtype=np.uint8)
315-
316-
for subindex in range(4):
317-
318-
current_index = 4 * index + subindex
319-
subtile_filename = tile_filename(
320-
level=ilevel + 1,
321-
index=current_index,
322-
output_directory=output_directory,
323-
extension=EXTENSION[tile_format],
324-
)
325-
326-
if os.path.exists(subtile_filename):
327-
328-
if tile_format == "fits":
329-
data = block_reduce(fits.getdata(subtile_filename), 2, func=np.mean)
330-
else:
331-
data = block_reduce(
332-
np.array(Image.open(subtile_filename))[::-1], (2, 2, 1), func=np.mean
333-
)
334-
335-
if subindex == 0:
336-
array[256:, :256] = data
337-
elif subindex == 2:
338-
array[256:, 256:] = data
339-
elif subindex == 1:
340-
array[:256, :256] = data
341-
elif subindex == 3:
342-
array[:256, 256:] = data
343-
344-
if tile_format == "fits":
345-
fits.writeto(
346-
tile_filename(
347-
level=ilevel,
348-
index=index,
349-
output_directory=output_directory,
350-
extension=EXTENSION[tile_format],
351-
),
352-
array,
353-
header,
354-
)
355-
else:
356-
image = as_transparent_rgb(array.transpose(2, 0, 1))
357-
if tile_format == "jpeg":
358-
image = image.convert("RGB")
359-
image.save(
360-
tile_filename(
361-
level=ilevel,
362-
index=index,
363-
output_directory=output_directory,
364-
extension=EXTENSION[tile_format],
365-
)
366-
)
312+
# # Iterate over higher levels and compute lower resolution tiles
313+
# for ilevel in range(level - 1, -1, -1):
314+
315+
# # Find index of tiles to produce at lower-resolution levels
316+
# indices = np.sort(np.unique(indices // 4))
317+
318+
# make_tile_folders(level=ilevel, indices=indices, output_directory=output_directory)
319+
320+
# for index in indices:
321+
322+
# header = tile_header(level=ilevel, index=index, frame=frame, tile_size=tile_size)
323+
324+
# if tile_format == "fits":
325+
# array = np.zeros((tile_size, tile_size))
326+
# elif tile_format == "png":
327+
# array = np.zeros((tile_size, tile_size, 4), dtype=np.uint8)
328+
# else:
329+
# array = np.zeros((tile_size, tile_size, 3), dtype=np.uint8)
330+
331+
# for subindex in range(4):
332+
333+
# current_index = 4 * index + subindex
334+
# subtile_filename = tile_filename(
335+
# level=ilevel + 1,
336+
# index=current_index,
337+
# output_directory=output_directory,
338+
# extension=EXTENSION[tile_format],
339+
# )
340+
341+
# if os.path.exists(subtile_filename):
342+
343+
# if tile_format == "fits":
344+
# data = block_reduce(fits.getdata(subtile_filename), 2, func=np.mean)
345+
# else:
346+
# data = block_reduce(
347+
# np.array(Image.open(subtile_filename))[::-1], (2, 2, 1), func=np.mean
348+
# )
349+
350+
# if subindex == 0:
351+
# array[256:, :256] = data
352+
# elif subindex == 2:
353+
# array[256:, 256:] = data
354+
# elif subindex == 1:
355+
# array[:256, :256] = data
356+
# elif subindex == 3:
357+
# array[:256, 256:] = data
358+
359+
# if tile_format == "fits":
360+
# fits.writeto(
361+
# tile_filename(
362+
# level=ilevel,
363+
# index=index,
364+
# output_directory=output_directory,
365+
# extension=EXTENSION[tile_format],
366+
# ),
367+
# array,
368+
# header,
369+
# )
370+
# else:
371+
# image = as_transparent_rgb(array.transpose(2, 0, 1))
372+
# if tile_format == "jpeg":
373+
# image = image.convert("RGB")
374+
# image.save(
375+
# tile_filename(
376+
# level=ilevel,
377+
# index=index,
378+
# output_directory=output_directory,
379+
# extension=EXTENSION[tile_format],
380+
# )
381+
# )
367382

368383
# Generate properties file
369384

0 commit comments

Comments
 (0)