@@ -101,24 +101,58 @@ def _args_to_payload(self, **kwargs):
101
101
102
102
# Convert the coordinates to FK5
103
103
coordinates = kwargs .get ('coordinates' )
104
- fk5_coords = commons .parse_coordinates (coordinates ).transform_to (coord .FK5 )
105
-
106
- if kwargs ['radius' ] is not None :
107
- radius = u .Quantity (kwargs ['radius' ]).to (u .deg )
108
- pos = 'CIRCLE {} {} {}' .format (fk5_coords .ra .degree , fk5_coords .dec .degree , radius .value )
109
- elif kwargs ['width' ] is not None and kwargs ['height' ] is not None :
110
- width = u .Quantity (kwargs ['width' ]).to (u .deg ).value
111
- height = u .Quantity (kwargs ['height' ]).to (u .deg ).value
112
- top = fk5_coords .dec .degree - (height / 2 )
113
- bottom = fk5_coords .dec .degree + (height / 2 )
114
- left = fk5_coords .ra .degree - (width / 2 )
115
- right = fk5_coords .ra .degree + (width / 2 )
116
- pos = 'RANGE {} {} {} {}' .format (left , right , top , bottom )
117
- else :
118
- raise ValueError ("Either 'radius' or both 'height' and 'width' must be supplied." )
119
-
120
- request_payload ['POS' ] = pos
121
-
104
+ if coordinates is not None :
105
+ fk5_coords = commons .parse_coordinates (coordinates ).transform_to (coord .FK5 )
106
+
107
+ if kwargs .get ('radius' ) is not None :
108
+ radius = u .Quantity (kwargs ['radius' ]).to (u .deg )
109
+ pos = 'CIRCLE {} {} {}' .format (fk5_coords .ra .degree , fk5_coords .dec .degree , radius .value )
110
+ elif kwargs .get ('width' ) is not None and kwargs .get ('height' ) is not None :
111
+ width = u .Quantity (kwargs ['width' ]).to (u .deg ).value
112
+ height = u .Quantity (kwargs ['height' ]).to (u .deg ).value
113
+ top = fk5_coords .dec .degree + (height / 2 )
114
+ bottom = fk5_coords .dec .degree - (height / 2 )
115
+ left = fk5_coords .ra .degree - (width / 2 )
116
+ right = fk5_coords .ra .degree + (width / 2 )
117
+ pos = 'RANGE {} {} {} {}' .format (left , right , bottom , top )
118
+ else :
119
+ pos = 'CIRCLE {} {} {}' .format (fk5_coords .ra .degree , fk5_coords .dec .degree , 1 * u .arcmin .to (u .deg ))
120
+
121
+ request_payload ['POS' ] = pos
122
+
123
+ if kwargs .get ('band' ) is not None :
124
+ if kwargs .get ('channel' ) is not None :
125
+ raise ValueError ("Either 'channel' or 'band' values may be provided but not both." )
126
+
127
+ band = kwargs .get ('band' )
128
+ if not isinstance (band , (list ,tuple )) or len (band ) != 2 :
129
+ raise ValueError ("The 'band' value must be a list of 2 wavelength or frequency values." )
130
+ if (band [0 ] is not None and not isinstance (band [0 ], u .Quantity )) or (band [1 ] is not None and not isinstance (band [1 ], u .Quantity )):
131
+ raise ValueError ("The 'band' value must be a list of 2 wavelength or frequency values." )
132
+ if band [0 ] is not None and band [1 ] is not None and band [0 ].unit .physical_type != band [1 ].unit .physical_type :
133
+ raise ValueError ("The 'band' values must have the same kind of units." )
134
+ if band [0 ] is not None or band [1 ] is not None :
135
+ unit = band [0 ].unit if band [0 ] is not None else band [1 ].unit
136
+ if unit .physical_type == 'length' :
137
+ min_band = '-Inf' if band [0 ] is None else str (band [0 ].to (u .m ).value )
138
+ max_band = '+Inf' if band [1 ] is None else str (band [1 ].to (u .m ).value )
139
+ elif unit .physical_type == 'frequency' :
140
+ # Swap the order when changing frequency to wavelength
141
+ min_band = '-Inf' if band [1 ] is None else str (band [1 ].to (u .m , equivalencies = u .spectral ()).value )
142
+ max_band = '+Inf' if band [0 ] is None else str (band [0 ].to (u .m , equivalencies = u .spectral ()).value )
143
+ else :
144
+ raise ValueError ("The 'band' values must be wavelengths or frequencies." )
145
+
146
+ request_payload ['BAND' ] = '{} {}' .format (min_band , max_band )
147
+
148
+ if kwargs .get ('channel' ) is not None :
149
+ channel = kwargs .get ('channel' )
150
+ if not isinstance (channel , (list ,tuple )) or len (channel ) != 2 :
151
+ raise ValueError ("The 'channel' value must be a list of 2 integer values." )
152
+ if (not isinstance (channel [0 ], int )) or (not isinstance (channel [1 ], int )):
153
+ raise ValueError ("The 'channel' value must be a list of 2 integer values." )
154
+ request_payload ['CHANNEL' ] = '{} {}' .format (channel [0 ], channel [1 ])
155
+
122
156
return request_payload
123
157
124
158
# the methods above implicitly call the private _parse_result method.
@@ -159,54 +193,11 @@ def filter_out_unreleased(self, table):
159
193
now = str (datetime .now (timezone .utc ).strftime ('%Y-%m-%dT%H:%M:%S.%f' ))
160
194
return table [(table ['obs_release_date' ] != '' ) & (table ['obs_release_date' ] < now )]
161
195
162
- def stage_data (self , table , coordinates = None , radius = None , height = None , width = None , verbose = False ):
163
- """
164
- Request access to a set of data files. All requests for data must use authentication. If you have access to the
165
- data, the requested files will be brought online and a set of URLs to download the files will be returned.
166
-
167
- This method can also be used to produce a cutout from the data files. If a set of coordinates is provided along
168
- with either a radius or a box height and width, then CASDA will produce a cutout at that location from each
169
- data file specified in the table.
170
-
171
- Parameters
172
- ----------
173
- table: `astropy.table.Table`
174
- A table describing the files to be staged, such as produced by query_region. It must include an
175
- access_url column.
176
- coordinates : str or `astropy.coordinates`, optional
177
- coordinates around which to produce a cutout
178
- radius : str or `astropy.units.Quantity`, optional
179
- the radius of the cutout
180
- height : str or `astropy.units.Quantity`, optional
181
- the height for a box cutout
182
- width : str or `astropy.units.Quantity`, optional
183
- the width for a box cutout
184
- verbose: bool, optional
185
- Should status message be logged periodically, defaults to False
186
-
187
- Returns
188
- -------
189
- A list of urls of both the requested files/cutouts and the checksums for the files/cutouts
190
- """
191
- if not self ._authenticated :
192
- raise ValueError ("Credentials must be supplied to download CASDA image data" )
193
-
194
- if table is None or len (table ) == 0 :
195
- return []
196
-
197
-
198
- if coordinates is not None :
199
- cutout_spec = self ._args_to_payload (coordinates = coordinates , radius = radius , height = height ,
200
- width = width )
201
- is_cutout = True
202
- else :
203
- cutout_spec = None
204
- is_cutout = False
205
196
197
+ def _create_job (self , table , service_name , verbose ):
206
198
# Use datalink to get authenticated access for each file
207
199
tokens = []
208
200
soda_url = None
209
- service_name = 'cutout_service' if is_cutout else 'async_service'
210
201
for row in table :
211
202
access_url = row ['access_url' ]
212
203
if access_url :
@@ -226,11 +217,10 @@ def stage_data(self, table, coordinates=None, radius=None, height=None, width=No
226
217
job_url = self ._create_soda_job (tokens , soda_url = soda_url )
227
218
if verbose :
228
219
log .info ("Created data staging job " + job_url )
229
-
230
- # Add cutout parameters, if they have been specified
231
- if cutout_spec is not None :
232
- self ._add_cutout_params (job_url , verbose , cutout_spec )
233
220
221
+ return job_url
222
+
223
+ def _complete_job (self , job_url , verbose ):
234
224
# Wait for job to be complete
235
225
final_status = self ._run_job (job_url , verbose , poll_interval = self .POLL_INTERVAL )
236
226
if final_status != 'COMPLETED' :
@@ -247,6 +237,88 @@ def stage_data(self, table, coordinates=None, radius=None, height=None, width=No
247
237
248
238
return fileurls
249
239
240
+ def stage_data (self , table , verbose = False ):
241
+ """
242
+ Request access to a set of data files. All requests for data must use authentication. If you have access to the
243
+ data, the requested files will be brought online and a set of URLs to download the files will be returned.
244
+
245
+ Parameters
246
+ ----------
247
+ table: `astropy.table.Table`
248
+ A table describing the files to be staged, such as produced by query_region. It must include an
249
+ access_url column.
250
+ verbose: bool, optional
251
+ Should status message be logged periodically, defaults to False
252
+
253
+ Returns
254
+ -------
255
+ A list of urls of both the requested files/cutouts and the checksums for the files/cutouts
256
+ """
257
+ if not self ._authenticated :
258
+ raise ValueError ("Credentials must be supplied to download CASDA image data" )
259
+
260
+ if table is None or len (table ) == 0 :
261
+ return []
262
+
263
+ job_url = self ._create_job (table , 'async_service' , verbose )
264
+
265
+ return self ._complete_job (job_url , verbose )
266
+
267
+
268
+ def cutout (self , table , coordinates = None , radius = None , height = None , width = None , band = None , channel = None , verbose = False ):
269
+ """
270
+ Produce a cutout from each selected file. All requests for data must use authentication. If you have access to
271
+ the data, the requested files will be brought online, a cutout produced from each file and a set of URLs to
272
+ download the cutouts will be returned.
273
+
274
+ If a set of coordinates is provided along with either a radius or a box height and width, then CASDA will produce a
275
+ spatial cutout at that location from each data file specified in the table. If a band or channel pair is provided
276
+ then CASDA will produce a spectral cutout of that range from each data file. These can be combined to produce
277
+ subcubes with restrictions in both spectral and spatial axes.
278
+
279
+ Parameters
280
+ ----------
281
+ table: `astropy.table.Table`
282
+ A table describing the files to be staged, such as produced by query_region. It must include an
283
+ access_url column.
284
+ coordinates : str or `astropy.coordinates`, optional
285
+ coordinates around which to produce a cutout, the radius will be 1 arcmin if no radius, height or width is provided.
286
+ radius : str or `astropy.units.Quantity`, optional
287
+ the radius of the cutout
288
+ height : str or `astropy.units.Quantity`, optional
289
+ the height for a box cutout
290
+ width : str or `astropy.units.Quantity`, optional
291
+ the width for a box cutout
292
+ band : list of `astropy.units.Quantity` with two elements, optional
293
+ the spectral range to be included, may be low and high wavelengths in metres or low and high frequencies in Hertz. Use None for an open bound.
294
+ channel : list of int with two elements, optional
295
+ the spectral range to be included, the low and high channels (i.e. planes of a cube) inclusive
296
+ verbose: bool, optional
297
+ Should status messages be logged periodically, defaults to False
298
+
299
+ Returns
300
+ -------
301
+ A list of urls of both the requested files/cutouts and the checksums for the files/cutouts
302
+ """
303
+ if not self ._authenticated :
304
+ raise ValueError ("Credentials must be supplied to download CASDA image data" )
305
+
306
+ if table is None or len (table ) == 0 :
307
+ return []
308
+
309
+ job_url = self ._create_job (table , 'cutout_service' , verbose )
310
+
311
+ cutout_spec = self ._args_to_payload (coordinates = coordinates , radius = radius , height = height ,
312
+ width = width , band = band , channel = channel )
313
+
314
+ if not cutout_spec :
315
+ raise ValueError ("Please provide cutout parameters such as coordinates, band or channel." )
316
+
317
+ self ._add_cutout_params (job_url , verbose , cutout_spec )
318
+
319
+ return self ._complete_job (job_url , verbose )
320
+
321
+
250
322
def download_files (self , urls , savedir = '' ):
251
323
"""
252
324
Download a series of files
0 commit comments