1919from astropy import units
2020from astropy .units import Quantity
2121
22+ from datetime import datetime
23+ import os
24+ import zipfile
25+ import tarfile
26+ import binascii
27+ import shutil
28+ import gzip
29+
2230from . import conf
2331from .data_access import JwstDataHandler
2432
@@ -34,6 +42,8 @@ class JwstClass(object):
3442 JWST_MAIN_TABLE = conf .JWST_MAIN_TABLE
3543 JWST_MAIN_TABLE_RA = conf .JWST_MAIN_TABLE_RA
3644 JWST_MAIN_TABLE_DEC = conf .JWST_MAIN_TABLE_DEC
45+ JWST_PLANE_TABLE = conf .JWST_PLANE_TABLE
46+ JWST_ARTIFACT_TABLE = conf .JWST_ARTIFACT_TABLE
3747
3848 JWST_DEFAULT_COLUMNS = ['observationid' , 'calibrationlevel' , 'public' ,
3949 'dataproducttype' , 'instrument_name' , 'energy_bandpassname' ,
@@ -318,14 +328,14 @@ def __query_region(self, coordinate, radius=None, width=None, height=None,
318328 columns = '*'
319329
320330 query = "SELECT DISTANCE(POINT('ICRS'," + \
321- str (self .JWST_OBSERVATION_TABLE_RA ) + "," + \
322- str (self .JWST_OBSERVATION_TABLE_DEC ) + "), \
331+ str (self .JWST_MAIN_TABLE_RA ) + "," + \
332+ str (self .JWST_MAIN_TABLE_DEC ) + "), \
323333 POINT('ICRS'," + str (ra ) + "," + str (dec ) + ")) AS dist, " + columns + " \
324- FROM jwst.main \
334+ FROM " + str ( self . JWST_MAIN_TABLE ) + " \
325335 WHERE CONTAINS(\
326336 POINT('ICRS'," + \
327- str (self .JWST_OBSERVATION_TABLE_RA )+ "," + \
328- str (self .JWST_OBSERVATION_TABLE_DEC )+ "),\
337+ str (self .JWST_MAIN_TABLE_RA )+ "," + \
338+ str (self .JWST_MAIN_TABLE_DEC )+ "),\
329339 BOX('ICRS'," + str (ra ) + "," + str (dec )+ ", " + \
330340 str (widthDeg .value )+ ", " + \
331341 str (heightDeg .value )+ "))=1 " + \
@@ -815,13 +825,15 @@ def logout(self, verbose=False):
815825 """
816826 return self .__jwsttap .logout (verbose )
817827
818- def get_product_list (self , plane_id , product_type = None ):
828+ def get_product_list (self , observation_id = None , cal_level = None , product_type = None ):
819829 """Get the list of products of a given JWST plane.
820830
821831 Parameters
822832 ----------
823- plane_id : str, mandatory
824- Plane ID of the products.
833+ observation_id : str, mandatory
834+ Observation identifier.
835+ cal_level : str, optional
836+ Calibration level.
825837 product_type : str, optional, default None
826838 List only products of the given type. If None, all products are \
827839 listed. Possible values: 'thumbnail', 'preview', 'auxiliary', \
@@ -831,15 +843,26 @@ def get_product_list(self, plane_id, product_type=None):
831843 -------
832844 The list of products (astropy.table).
833845 """
834- if plane_id is None :
835- raise ValueError ("Missing required argument: 'plane_id '" )
846+ if observation_id is None :
847+ raise ValueError ("Missing required argument: 'observation_id '" )
836848
837849 prodtype_condition = self .__get_artifact_producttype_condition (product_type )
838- query = "SELECT * " + \
839- "FROM " + str (self .JWST_ARTIFACT_TABLE ) + \
840- " WHERE planeid='" + plane_id + "' " + \
850+ cal_level_condition = self .__get_calibration_level_condition (cal_level )
851+ #query = "SELECT * " +\
852+ # "FROM " + str(self.JWST_ARTIFACT_TABLE) +\
853+ # " WHERE planeid='"+plane_id+"' " +\
854+ # prodtype_condition +\
855+ # "ORDER BY producttype ASC"
856+
857+ query = "SELECT a.*, m.calibrationlevel FROM " + \
858+ str (self .JWST_ARTIFACT_TABLE ) + " AS a, " + \
859+ str (self .JWST_MAIN_TABLE ) + " AS m " + \
860+ "WHERE a.obsid = m.obsid AND " + \
861+ "a.obsid = '" + observation_id + "' " + \
862+ cal_level_condition + \
841863 prodtype_condition + \
842- "ORDER BY producttype ASC"
864+ " ORDER BY a.producttype ASC"
865+
843866 job = self .__jwsttap .launch_job (query = query )
844867 return job .get_results ()
845868
@@ -887,11 +910,129 @@ def get_product(self, artifact_id=None, file_name=None):
887910 #return file
888911 try :
889912 self .__jwsttap .load_data (params_dict = params_dict , output_file = output_file_name )
890- except :
891- raise ValueError ('Product ' + err_msg + ' not available' )
913+ except Exception as exx :
914+ raise ValueError ('Error retrieving product for ' +
915+ err_msg + ': %s' % str (exx ))
892916 print ("Product saved at: %s" % (output_file_name ))
893917 return output_file_name
894918
919+ def get_obs_products (self , observation_id = None , cal_level = None , product_type = None , output_file = None ):
920+ """Get a JWST product given its Artifact ID.
921+
922+ Parameters
923+ ----------
924+ observation_id : str, mandatory
925+ Observation identifier.
926+ cal_level : str, optional
927+ Calibration level.
928+ product_type : str, optional, default None
929+ List only products of the given type. If None, all products are \
930+ listed. Possible values: 'thumbnail', 'preview', 'auxiliary', \
931+ 'science'.
932+ output_file : str, optional
933+ Output file. If no value is provided, a temporary one is created.
934+
935+ Returns
936+ -------
937+ local_path : str
938+ Returns the local path where the product(s) are saved.
939+ """
940+
941+ params_dict = {}
942+ params_dict ['RETRIEVAL_TYPE' ] = 'OBSERVATION'
943+ params_dict ['DATA_RETRIEVAL_ORIGIN' ] = 'ASTROQUERY'
944+
945+ if observation_id is None :
946+ raise ValueError ("Missing required argument: 'observation_id'" )
947+
948+ params_dict ['obsid' ] = observation_id
949+ if cal_level is not None :
950+ params_dict ['calibrationlevel' ] = str (cal_level )
951+
952+ if product_type is not None :
953+ params_dict ['product_type' ] = str (product_type )
954+
955+ if output_file is None :
956+ now = datetime .now ()
957+ formatted_now = now .strftime ("%Y%m%d_%H%M%S" )
958+ output_dir = os .getcwd () + os .sep + "temp_" + \
959+ formatted_now
960+ output_file_full_path = output_dir + os .sep + observation_id + \
961+ "_all_products"
962+ else :
963+ output_file_full_path = output_file
964+ output_dir = os .path .dirname (output_file_full_path )
965+
966+ # Get file name only
967+ output_file_name = os .path .basename (output_file_full_path )
968+
969+ try :
970+ os .makedirs (output_dir , exist_ok = True )
971+ except OSError as err :
972+ print ("Creation of the directory %s failed: %s" % (output_dir , err .strerror ))
973+ raise err
974+
975+ try :
976+ self .__jwsttap .load_data (params_dict = params_dict , output_file = output_file_full_path )
977+ except Exception as exx :
978+ raise ValueError ('Cannot retrieve products for observation ' +
979+ observation_id + ': %s' % str (exx ))
980+ print ("Product(s) saved at: %s" % output_file_full_path )
981+
982+ files = []
983+
984+ if tarfile .is_tarfile (output_file_full_path ):
985+ with tarfile .open (output_file_full_path ) as tar_ref :
986+ tar_ref .extractall (path = output_dir )
987+ elif zipfile .is_zipfile (output_file_full_path ):
988+ with zipfile .ZipFile (output_file_full_path , 'r' ) as zip_ref :
989+ zip_ref .extractall (output_dir )
990+ elif not JwstClass .is_gz_file (output_file_full_path ):
991+ # single file: return it
992+ files .append (output_file_full_path )
993+ print ("Product = %s" % output_file_full_path )
994+ return files
995+
996+ num_files_in_dir = len (os .listdir (output_dir ))
997+ if num_files_in_dir == 1 :
998+ if output_file_name .endswith ("_all_products" ):
999+ p = output_file_name .rfind ('_all_products' )
1000+ output_f = output_file_name [0 :p ]
1001+ else :
1002+ output_f = output_file_name
1003+
1004+ output_full_path = output_dir + os .sep + output_f
1005+
1006+ os .rename (output_file_full_path , output_full_path )
1007+ files .append (output_full_path )
1008+ #if JwstClass.is_gz_file(output_file_full_path):
1009+ # extracted_output_full_path = JwstClass.gzip_uncompress_and_rename_single_file(output_full_path)
1010+ # files.append(extracted_output_full_path)
1011+ #else:
1012+ # os.rename(output_file_full_path, output_full_path)
1013+ # files.append(output_full_path)
1014+ else :
1015+ # r=root, d=directories, f = files
1016+ for r , d , f in os .walk (output_dir ):
1017+ for file in f :
1018+ if file != output_file_name :
1019+ files .append (os .path .join (r , file ))
1020+ #compressed_file = os.path.join(r, file)
1021+ #if JwstClass.is_gz_file(compressed_file):
1022+ # uncompressed_output = JwstClass.gzip_uncompress_and_rename_single_file(compressed_file)
1023+ # #output_decompressed_file = output_dir + os.sep + file + "_decompressed"
1024+ # #self.gzip_uncompress(input_file=compressed_file, output_file=output_decompressed_file)
1025+ # #os.remove(compressed_file)
1026+ # #os.rename(output_decompressed_file, compressed_file)
1027+ # files.append(uncompressed_output)
1028+ #else:
1029+ # files.append(os.path.join(r, file))
1030+
1031+ for f in files :
1032+ print ("Product = %s" % f )
1033+
1034+ return files
1035+
8951036 def __get_quantity_input (self , value , msg ):
8961037 if value is None :
8971038 raise ValueError ("Missing required argument: '" + str (msg )+ "'" )
@@ -999,5 +1140,43 @@ def __get_artifact_producttype_condition(self, product_type=None):
9991140 condition = " AND producttype ILIKE '" + product_type + "' "
10001141 return condition
10011142
1143+ def __get_calibration_level_condition (self , cal_level = None ):
1144+ condition = ""
1145+ if (cal_level is not None ):
1146+ if (not isinstance (cal_level , int )):
1147+ raise ValueError ("product_type must be an integer" )
1148+ else :
1149+ condition = " AND m.calibrationlevel = " + str (cal_level )+ " "
1150+ else :
1151+ condition = " AND m.calibrationlevel = m.max_cal_level"
1152+ return condition
1153+
1154+ @staticmethod
1155+ def is_gz_file (filepath ):
1156+ with open (filepath , 'rb' ) as test_f :
1157+ return binascii .hexlify (test_f .read (2 )) == b'1f8b'
1158+
1159+ @staticmethod
1160+ def gzip_uncompress (input_file , output_file ):
1161+ with open (output_file , 'wb' ) as f_out , gzip .open (input_file , 'rb' ) as f_in :
1162+ shutil .copyfileobj (f_in , f_out )
1163+
1164+ @staticmethod
1165+ def gzip_uncompress_and_rename_single_file (input_file ):
1166+ output_dir = os .path .dirname (input_file )
1167+ file = os .path .basename (input_file )
1168+ output_decompressed_file = output_dir + os .sep + file + "_decompressed"
1169+ JwstClass .gzip_uncompress (input_file = input_file , output_file = output_decompressed_file )
1170+ # Remove uncompressed file and rename decompressed file to the original one
1171+ os .remove (input_file )
1172+ if file .lower ().endswith (".gz" ):
1173+ # remove .gz
1174+ new_file_name = file [:len (file )- 3 ]
1175+ output = output_dir + os .sep + new_file_name
1176+ else :
1177+ output = input_file
1178+ os .rename (output_decompressed_file , output )
1179+ return output
1180+
10021181
10031182Jwst = JwstClass ()
0 commit comments