8
8
European Space Agency (ESA)
9
9
10
10
"""
11
+ import os
11
12
from urllib .parse import urlencode
12
13
13
14
from astropy import units
27
28
__all__ = ['ESAHubble' , 'ESAHubbleClass' ]
28
29
29
30
31
+ def _check_rename_to_gz (filename ):
32
+ rename = False
33
+ if os .path .exists (filename ):
34
+ with open (filename , 'rb' ) as test_f :
35
+ if test_f .read (2 ) == b'\x1f \x8b ' and not filename .endswith ('.fits.gz' ):
36
+ rename = True
37
+
38
+ if rename :
39
+ output = os .path .splitext (filename )[0 ] + '.fits.gz'
40
+ os .rename (filename , output )
41
+ return output
42
+ else :
43
+ return filename
44
+
45
+
30
46
class ESAHubbleClass (BaseQuery ):
31
47
"""
32
48
Class to init ESA Hubble Module and communicate with eHST TAP
33
49
"""
34
50
TIMEOUT = conf .TIMEOUT
35
51
calibration_levels = {"AUXILIARY" : 0 , "RAW" : 1 , "CALIBRATED" : 2 ,
36
52
"PRODUCT" : 3 }
37
- product_types = ["SCIENCE" , "PREVIEW" , "THUMBNAIL" or "AUXILIARY" ]
53
+ product_types = ["SCIENCE" , "PREVIEW" , "THUMBNAIL" , "AUXILIARY" ]
38
54
copying_string = "Copying file to {0}..."
39
55
40
56
def __init__ (self , * , tap_handler = None , show_messages = True ):
@@ -93,7 +109,7 @@ def download_product(self, observation_id, *, calibration_level=None,
93
109
"RETRIEVAL_TYPE" : "OBSERVATION" }
94
110
95
111
if filename is None :
96
- filename = observation_id + ".tar"
112
+ filename = observation_id
97
113
98
114
if calibration_level :
99
115
params ["CALIBRATIONLEVEL" ] = calibration_level
@@ -107,7 +123,7 @@ def download_product(self, observation_id, *, calibration_level=None,
107
123
filename = self ._get_product_filename (product_type , filename )
108
124
self ._tap .load_data (params_dict = params , output_file = filename , verbose = verbose )
109
125
110
- return filename
126
+ return _check_rename_to_gz ( filename = filename )
111
127
112
128
def __set_product_type (self , product_type ):
113
129
if product_type :
@@ -216,18 +232,12 @@ def __validate_product_type(self, product_type):
216
232
raise ValueError ("This product_type is not allowed" )
217
233
218
234
def _get_product_filename (self , product_type , filename ):
219
- if (product_type == "PRODUCT" ):
220
- return filename
221
- elif (product_type == "SCIENCE" ):
222
- log .info ("This is a SCIENCE_PRODUCT, the filename will be "
223
- f"renamed to { filename } .fits.gz" )
224
- return f"{ filename } .fits.gz"
225
- elif (product_type == "THUMBNAIL" or product_type == "PREVIEW" ):
226
- log .info ("This is a POSTCARD, the filename will be "
235
+ if (product_type == "THUMBNAIL" or product_type == "PREVIEW" ):
236
+ log .info ("This is an image, the filename will be "
227
237
f"renamed to { filename } .jpg" )
228
238
return f"{ filename } .jpg"
229
-
230
- return filename
239
+ else :
240
+ return f" { filename } .zip"
231
241
232
242
def get_artifact (self , artifact_id , * , filename = None , verbose = False ):
233
243
"""
@@ -236,7 +246,7 @@ def get_artifact(self, artifact_id, *, filename=None, verbose=False):
236
246
Parameters
237
247
----------
238
248
artifact_id : string
239
- id of the artifact to be downloaded, mandatory
249
+ filename to be downloaded, mandatory
240
250
The identifier of the physical product (file) we want to retrieve.
241
251
filename : string
242
252
file name to be used to store the artifact, optional, default None
@@ -250,13 +260,83 @@ def get_artifact(self, artifact_id, *, filename=None, verbose=False):
250
260
None. It downloads the artifact indicated
251
261
"""
252
262
253
- params = {"RETRIEVAL_TYPE" : "PRODUCT" , "ARTIFACTID" : artifact_id , "TAPCLIENT" : "ASTROQUERY" }
263
+ return self .download_file (file = artifact_id , filename = filename , verbose = verbose )
264
+
265
+ def get_associated_files (self , observation_id , * , verbose = False ):
266
+ """
267
+ Retrieves all the files associated to an observation
268
+
269
+ Parameters
270
+ ----------
271
+ observation_id : string
272
+ id of the observation to be downloaded, mandatory
273
+ The identifier of the observation we want to retrieve, regardless
274
+ of whether it is simple or composite.
275
+ verbose : bool
276
+ optional, default 'False'
277
+ flag to display information about the process
278
+
279
+ Returns
280
+ -------
281
+ None. The file is associated
282
+ """
283
+ query = (f"select art.artifact_id as filename, p.calibration_level, art.archive_class as type, "
284
+ f"pg_size_pretty(art.size_uncompr) as size_uncompressed from ehst.artifact art "
285
+ f"join ehst.plane p on p.plane_id = art.plane_id where "
286
+ f"art.observation_id = '{ observation_id } '" )
287
+ return self .query_tap (query = query )
288
+
289
+ def download_fits_files (self , observation_id , * , verbose = False ):
290
+ """
291
+ Retrieves all the FITS files associated to an observation
292
+
293
+ Parameters
294
+ ----------
295
+ observation_id : string
296
+ id of the observation to be downloaded, mandatory
297
+ The identifier of the observation we want to retrieve, regardless
298
+ of whether it is simple or composite.
299
+ verbose : bool
300
+ optional, default 'False'
301
+ flag to display information about the process
302
+
303
+ Returns
304
+ -------
305
+ None. The file is associated
306
+ """
307
+ results = self .get_associated_files (observation_id = observation_id , verbose = verbose )
308
+ for file in [i ['filename' ] for i in results if i ['filename' ].endswith ('.fits' )]:
309
+ if verbose :
310
+ print (f"Downloading { file } ..." )
311
+ self .download_file (file = file , filename = file , verbose = verbose )
312
+
313
+ def download_file (self , file , * , filename = None , verbose = False ):
314
+ """
315
+ Download a file from eHST based on its filename.
316
+
317
+ Parameters
318
+ ----------
319
+ file : string
320
+ file name of the artifact to be downloaded
321
+
322
+ filename : string
323
+ file name to be used to store the file, optional, default None
324
+ verbose : bool
325
+ optional, default 'False'
326
+ flag to display information about the process
327
+
328
+ Returns
329
+ -------
330
+ None. The file is associated
331
+ """
332
+
333
+ params = {"RETRIEVAL_TYPE" : "PRODUCT" , "ARTIFACTID" : file , "TAPCLIENT" : "ASTROQUERY" }
254
334
if filename is None :
255
- filename = artifact_id
335
+ filename = file
256
336
257
337
self ._tap .load_data (params_dict = params , output_file = filename , verbose = verbose )
258
338
259
- return filename
339
+ return _check_rename_to_gz ( filename = filename )
260
340
261
341
def get_postcard (self , observation_id , * , calibration_level = "RAW" ,
262
342
resolution = 256 , filename = None , verbose = False ):
@@ -391,6 +471,7 @@ def cone_search_criteria(self, radius, *, target=None,
391
471
obs_collection = None ,
392
472
instrument_name = None ,
393
473
filters = None ,
474
+ proposal = None ,
394
475
async_job = True ,
395
476
filename = None ,
396
477
output_format = 'votable' ,
@@ -428,6 +509,8 @@ def cone_search_criteria(self, radius, *, target=None,
428
509
Name(s) of the instrument(s) used to generate the dataset
429
510
filters : list of str, optional
430
511
Name(s) of the filter(s) used to generate the dataset
512
+ proposal : int, optional
513
+ Proposal ID associated to the observations
431
514
async_job : bool, optional, default 'False'
432
515
executes the query (job) in asynchronous/synchronous mode (default
433
516
synchronous)
@@ -460,6 +543,7 @@ def cone_search_criteria(self, radius, *, target=None,
460
543
obs_collection = obs_collection ,
461
544
instrument_name = instrument_name ,
462
545
filters = filters ,
546
+ proposal = proposal ,
463
547
async_job = True ,
464
548
get_query = True )
465
549
if crit_query .endswith (")" ):
@@ -619,7 +703,7 @@ def query_hst_tap(self, query, *, async_job=False, output_file=None,
619
703
def query_criteria (self , * , calibration_level = None ,
620
704
data_product_type = None , intent = None ,
621
705
obs_collection = None , instrument_name = None ,
622
- filters = None , async_job = True , output_file = None ,
706
+ filters = None , proposal = None , async_job = True , output_file = None ,
623
707
output_format = "votable" , verbose = False ,
624
708
get_query = False ):
625
709
"""
@@ -639,13 +723,15 @@ def query_criteria(self, *, calibration_level=None,
639
723
intent : str, optional
640
724
The intent of the original observer in acquiring this observation.
641
725
SCIENCE or CALIBRATION
642
- collection : list of str, optional
726
+ obs_collection : list of str, optional
643
727
List of collections that are available in eHST catalogue.
644
- HLA, HST
728
+ HLA, HST, HAP
645
729
instrument_name : list of str, optional
646
730
Name(s) of the instrument(s) used to generate the dataset
647
731
filters : list of str, optional
648
732
Name(s) of the filter(s) used to generate the dataset
733
+ proposal : int, optional
734
+ Proposal ID associated to the observations
649
735
async_job : bool, optional, default 'True'
650
736
executes the query (job) in asynchronous/synchronous mode (default
651
737
synchronous)
@@ -680,6 +766,11 @@ def query_criteria(self, *, calibration_level=None,
680
766
parameters .append ("intent LIKE '%{}%'" .format (intent .lower ()))
681
767
else :
682
768
raise ValueError ("intent must be a string" )
769
+ if proposal is not None :
770
+ if isinstance (proposal , int ):
771
+ parameters .append ("proposal_id = '{}'" .format (proposal ))
772
+ else :
773
+ raise ValueError ("Proposal ID must be an integer" )
683
774
if self .__check_list_strings (obs_collection ):
684
775
parameters .append ("(collection LIKE '%{}%')" .format (
685
776
"%' OR collection LIKE '%" .join (obs_collection )
@@ -767,7 +858,7 @@ def get_status_messages(self):
767
858
if response .status == 200 :
768
859
for line in response :
769
860
string_message = line .decode ("utf-8" )
770
- print (string_message [string_message .index ('=' )+ 1 :])
861
+ print (string_message [string_message .index ('=' ) + 1 :])
771
862
except OSError :
772
863
print ("Status messages could not be retrieved" )
773
864
@@ -810,10 +901,8 @@ def get_columns(self, table_name, *, only_names=True, verbose=False):
810
901
return columns
811
902
812
903
def _getCoordInput (self , value ):
813
- if not (isinstance (value , str )
814
- or isinstance (value , SkyCoord )):
815
- raise ValueError ("Coordinates"
816
- + " must be either a string or astropy.coordinates" )
904
+ if not (isinstance (value , str ) or isinstance (value , SkyCoord )):
905
+ raise ValueError ("Coordinates must be either a string or astropy.coordinates" )
817
906
if isinstance (value , str ):
818
907
return SkyCoord (value )
819
908
else :
0 commit comments