-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathretrieve_cutouts.py
255 lines (218 loc) · 11.5 KB
/
retrieve_cutouts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
### This script defines functions from different existing image cutout
### services to retrieve images of WALLABY detections
import numpy as np
from astropy.table import Table
from astropy.io import fits
import requests
from functools import reduce
import pandas as pd
from io import StringIO
from tqdm.auto import tqdm
# PanSTARRS: This uses the convenience functions provided by PanSTARRS
# here: https://ps1images.stsci.edu/ps1image.html
# PS pixel scale is 0.25 arcseconds/pixel
def ps1_getimages(ra,dec,size=240,filters="grizy"):
"""Query ps1filenames.py service to get a list of images
ra, dec = position in degrees
size = image size in pixels (0.25 arcsec/pixel)
filters = string with filters to include
Returns a table with the results
"""
service = "https://ps1images.stsci.edu/cgi-bin/ps1filenames.py"
url = ("{service}?ra={ra}&dec={dec}&size={size}&format=fits"
"&filters={filters}").format(**locals())
table = Table.read(url, format='ascii')
return table
def ps1_geturl(ra, dec, size=240, output_size=None, filters="grizy", format="jpg", color=False):
"""Get URL for images in the table
ra, dec = position in degrees
size = extracted image size in pixels (0.25 arcsec/pixel)
output_size = output (display) image size in pixels (default = size).
output_size has no effect for fits format images.
filters = string with filters to include
format = data format (options are "jpg", "png" or "fits")
color = if True, creates a color image (only for jpg or png format).
Default is return a list of URLs for single-filter grayscale images.
Returns a string with the URL
"""
if color and format == "fits":
raise ValueError("color images are available only for jpg or png formats")
if format not in ("jpg","png","fits"):
raise ValueError("format must be one of jpg, png, fits")
table = ps1_getimages(ra,dec,size=size,filters=filters)
url = ("https://ps1images.stsci.edu/cgi-bin/fitscut.cgi?"
"ra={ra}&dec={dec}&size={size}&format={format}").format(**locals())
if output_size:
url = url + "&output_size={}".format(output_size)
# sort filters from red to blue
flist = ["yzirg".find(x) for x in table['filter']]
table = table[np.argsort(flist)]
if color:
if len(table) > 3:
# pick 3 filters
table = table[[0,len(table)//2,len(table)-1]]
for i, param in enumerate(["red","green","blue"]):
url = url + "&{}={}".format(param,table['filename'][i])
else:
urlbase = url + "&red="
url = []
for filename in table['filename']:
url.append(urlbase+filename)
return url
# Recent updated version for bulk image download:
# https://outerspace.stsci.edu/display/PANSTARRS/PS1+Image+Cutout+Service#PS1ImageCutoutService-BulkImageDownloadPythonScript
ps1filename = "https://ps1images.stsci.edu/cgi-bin/ps1filenames.py"
fitscut = "https://ps1images.stsci.edu/cgi-bin/fitscut.cgi"
def ps1_getimages_bulk(tra, tdec, size=240, filters="grizy", format="fits", imagetypes="stack"):
"""Query ps1filenames.py service for multiple positions to get a list of images
This adds a url column to the table to retrieve the cutout.
tra, tdec = list of positions in degrees
size = image size in pixels (0.25 arcsec/pixel)
filters = string with filters to include
format = data format (options are "fits", "jpg", or "png")
imagetypes = list of any of the acceptable image types. Default is stack;
other common choices include warp (single-epoch images), stack.wt (weight image),
stack.mask, stack.exp (exposure time), stack.num (number of exposures),
warp.wt, and warp.mask. This parameter can be a list of strings or a
comma-separated string.
Returns an astropy table with the results
"""
if format not in ("jpg","png","fits"):
raise ValueError("format must be one of jpg, png, fits")
# if imagetypes is a list, convert to a comma-separated string
if not isinstance(imagetypes,str):
imagetypes = ",".join(imagetypes)
# put the positions in an in-memory file object
cbuf = StringIO()
cbuf.write('\n'.join(["{} {}".format(ra, dec) for (ra, dec) in zip(tra,tdec)]))
cbuf.seek(0)
# use requests.post to pass in positions as a file
r = requests.post(ps1filename, data=dict(filters=filters, type=imagetypes),
files=dict(file=cbuf))
r.raise_for_status()
tab = Table.read(r.text, format="ascii")
urlbase = "{}?size={}&format={}".format(fitscut,size,format)
tab["url"] = ["{}&ra={}&dec={}&red={}".format(urlbase,ra,dec,filename)
for (filename,ra,dec) in zip(tab["filename"],tab["ra"],tab["dec"])]
return tab
# Mege and concantenate
def ps1_merge_and_concat(ps1_df, wallaby_df):
wall_ps1_merged = wallaby_df.merge(ps1_df, how = 'inner', left_on = 'ra', right_on = 'ra')
ps1_urls_concat = wall_ps1_merged.url.groupby(wall_ps1_merged.index//5).agg(', '.join)
#print(ps1_urls_concat)
wall_names = wall_ps1_merged.name.str.replace(r"\'",'',regex=True).drop_duplicates()
#print(wall_names)
merged_concat = pd.concat([wall_names.reset_index(drop=True), ps1_urls_concat.reset_index(drop=True)], axis=1)
merged_concat.rename(columns={"name": "wallaby_id"}, inplace=True)
merged_concat['url'] = ("[" + merged_concat.url + "]")
#print(merged_concat)
#image_exists_names = name_list[matching_idx]
return merged_concat#ps1_df.groupby(ps1_df.index//5).agg(', '.join)
# SkyMapper data are retrieved using their SimpleImageAccess Service. A list
# of cutout urls can be extracted from the returned table. There seems to be
# a maximum on the size of the requested cutout. So, this could be an issue
# for large sources and will require some form of a mosaic
def skymapper_getcutouts(name_list, ra_list, dec_list, size):
url_list = []
image_exists_id = []
for (name, ra,dec) in tqdm(zip(name_list, ra_list,dec_list), total=len(ra_list)):
query_url = "https://api.skymapper.nci.org.au/public/siap/dr2/query?POS={},{}&SIZE={}&FORMAT=image/fits&BAND=g,r,i,z&INTERSECT=center&RESPONSEFORMAT=CSV".format(ra,dec,size/60.)
sm_table = pd.read_csv(query_url,delimiter=',',on_bad_lines='skip')
available_bands = list(sm_table['band'].unique())
target_url = []
for band in available_bands:
sm_table_band = sm_table[sm_table['band']==band]
sm_table_max_exptime = sm_table_band[(sm_table_band['exptime'] == sm_table_band['exptime'].max())]
sm_table_max_size = sm_table_max_exptime[(sm_table_max_exptime['size'] == sm_table_max_exptime['size'].max())]
target_url.append(sm_table_max_size['get_fits'].values[0])
url_list.append(target_url)
image_exists_id.append(name)
skymapper_cutout_df = pd.DataFrame({'wallaby_id': image_exists_id, 'url':url_list})
skymapper_cutout_df['url'] = skymapper_cutout_df.url.astype(str).str.replace(r"\'",'',regex=True)
return skymapper_cutout_df
# WISE, 2MASS, AND GALEX data are retrieved from the hips2fits service
# provided by CDS: https://alasky.u-strasbg.fr/hips-image-services/hips2fits
# These data come with a slightly different initial scaling. However, during
# testing the simple background subtract values from these data are virtually
# identical (<5% difference).
#
# WISE pixel scale is 1.37499998090796 arcseconds/pixel
# unWISE pixel scale is ~2.75 arcseconds/pixels
# 2MASS pixel scale is 1.0000000242 arcseconds/pixel
# GALEX pixel scale is 1.5 arcseconds/pixel
# UnWISE
def unwise_cutouts(name_list, ra_list, dec_list, size):
unwise_pix_scale = 2.75
width = height = int(size*60/unwise_pix_scale)
fov = size/60.
url_list = []
image_exists_id = []
for (name, ra,dec) in tqdm(zip(name_list, ra_list,dec_list), total=len(ra_list)):
#print(f"Retrieving unWISE cutouts for RA = {ra}, Dec = {dec}")
query_url = f'https://alasky.u-strasbg.fr/hips-image-services/hips2fits?hips=CDS%2FP%2FunWISE%2Fcolor-W2-W1W2-W1&width={width}&height={height}&fov={fov}&projection=TAN&coordsys=icrs&rotation_angle=0.0&ra={ra}&dec={dec}&format=fits'
url_list.append(query_url)
image_exists_id.append(name)
unwise_cutout_df = pd.DataFrame({'wallaby_id': image_exists_id, 'url':url_list})
return unwise_cutout_df
# TwoMass
def twomass_cutouts(name_list, ra_list, dec_list, size):
twomass_pix_scale = 1.0000000242
width = height = int(size*60/twomass_pix_scale)
fov = size/60.
url_list = []
image_exists_id = []
for (name, ra, dec) in tqdm(zip(name_list, ra_list, dec_list), total=len(ra_list)):
#print(f"Retrieving 2MASS cutouts for RA = {ra}, Dec = {dec}")
query_url = f'https://alasky.u-strasbg.fr/hips-image-services/hips2fits?hips=CDS%2FP%2F2MASS%2Fcolor&width={width}&height={height}&fov={fov}&projection=TAN&coordsys=icrs&rotation_angle=0.0&ra={ra}&dec={dec}&format=fits'
url_list.append(query_url)
image_exists_id.append(name)
twomass_cutout_df = pd.DataFrame({'wallaby_id': image_exists_id, 'url':url_list})
return twomass_cutout_df
# Galex Survey
def galex_cutouts(name_list, ra_list, dec_list, size):
galex_pix_scale = 1.5
width = height = int(size*60/galex_pix_scale)
fov = size/60.
url_list = []
image_exists_id = []
for (name, ra, dec) in tqdm(zip(name_list, ra_list, dec_list), total=len(ra_list)):
#print(f"Retrieving GALEX cutouts for RA = {ra}, Dec = {dec}")
query_url = f'https://alasky.u-strasbg.fr/hips-image-services/hips2fits?hips=CDS%2FGALEXGR6%2FAIS%2Fcolor&width={width}&height={height}&fov={fov}&projection=TAN&coordsys=icrs&rotation_angle=0.0&ra={ra}&dec={dec}&format=fits'
url_list.append(query_url)
image_exists_id.append(name)
galex_cutout_df = pd.DataFrame({'wallaby_id': image_exists_id, 'url':url_list})
return galex_cutout_df
# LegacySurvey
def ls_cutouts(name_list, ra_list, dec_list, size):
ls_pix_scale = 0.262
width = height = int(size*60/ls_pix_scale)
url_list = []
image_exists_id = []
for (name, ra, dec) in tqdm(zip(name_list, ra_list, dec_list), total=len(ra_list)):
query_url = f'https://www.legacysurvey.org/viewer/cutout.fits?ra={ra}&dec={dec}&layer=ls-dr10&pixscale=0.262&size={width}'
url_list.append(query_url)
image_exists_id.append(name)
ls_cutout_df = pd.DataFrame({'wallaby_id': image_exists_id, 'url':url_list})
return ls_cutout_df
# Merge cutouts from different DFs
def merge_cutout_df(ps1, skymapper, unwise, twomass, galex, ls):
merged_mw_cutouts = ps1
columns={'url_ps1': 'panstarrs_url'}
# Entries fofr each Catalog/Survey
if not skymapper.empty:
merged_mw_cutouts = merged_mw_cutouts.merge(skymapper,on='wallaby_id',suffixes=('_ps1','_skymapper'))
columns['url_skymapper']='skymapper_url'
if not unwise.empty:
merged_mw_cutouts = merged_mw_cutouts.merge(unwise, on='wallaby_id')
columns['url_unwise']='unwise_url'
if not twomass.empty:
merged_mw_cutouts = merged_mw_cutouts.merge(twomass,on='wallaby_id',suffixes=('_unwise','_twomass'))
columns['url_twomass']='twomass_url'
if not galex.empty:
merged_mw_cutouts = merged_mw_cutouts.merge(galex,on='wallaby_id')
columns['url_galex']='galex_url'
if not ls.empty:
merged_mw_cutouts = merged_mw_cutouts.merge(ls,on='wallaby_id',suffixes=('_galex','_ls'))
columns['url_ls']='ls_url'
merged_mw_cutouts.rename(columns=columns,inplace=True)
return merged_mw_cutouts