def Radiosonde_Ice_Concentration(Radiosonde_File=None, Calibrate=None, Height_Range=None, Sensor_Package=None): gu.cprint("[INFO] You are running Radiosonde_Ice_Concentration from the DEV release", type='bold') ############################################################################ """Prerequisites""" Storage_Path = '/glusterfs/phd/users/th863480/WC3_InSitu_Electrification/' Processed_Data_Path = 'Processed_Data/Radiosonde/' Raw_Data_Path = 'Raw_Data/' Plots_Path = 'Plots/Radiosonde/' t_begin = time.time() ############################################################################ """[Step 1] Check and Import Data""" #Error check that either Radiosonde_File or Sensor_Package has been specified if Radiosonde_File is None and Sensor_Package is None: sys.exit("[Error] You must specify either the Radiosonde_File location or the Sensor_Package number") #Attempt to find the radiosonde file either directly or from glob Radiosonde_File = Storage_Path + Processed_Data_Path + Radiosonde_File if Radiosonde_File is not None else glob.glob(Storage_Path + Processed_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2,'0') + '_*/Radiosonde_Flight_PhD_James_No.' + str(Sensor_Package) + '*a.txt') #If no radiosonde file was found we end program if len(Radiosonde_File) == 0: sys.exit("[Error] Radiosonde package No.%s does not exist. Has the radiosonde been launched yet or has the data been misplaced?" % (Sensor_Package)) #If the radiosonde file was found via glob we need to convert to str from list if isinstance(Radiosonde_File, list): Radiosonde_File = Radiosonde_File[0] #Import all the data if Radiosonde_File is not None: Radiosonde_Data = np.genfromtxt(Radiosonde_File, delimiter=None, skip_header=10, dtype=float, comments="#") Radiosonde_Cal = Radiosonde_Checks(Radiosonde_Data, Calibrate, Sensor_Package, Height_Range, check=1111) Radiosonde_Data = Radiosonde_Cal.return_data() Time = Radiosonde_Data[:,0][Radiosonde_Data[:,9] == 1112] PLL = Radiosonde_Data[:,7][Radiosonde_Data[:,9] == 1112] PLL[PLL == 0] = np.nan print(PLL) print(np.sum(np.isnan(PLL)))
def Radiosonde_Superplotter(Radiosonde_File=None, Calibrate=None, Height_Range=None, Sensor_Package=None): """This function will plot the data from a single radiosonde flight Parameters ---------- Radiosonde_File : str, optional Location of the radiosonde file to be processed and plotted Calibrate : tuple, optional tuple of column numbers which require calibration using the calibration metric y = x*(5/4096) Height_Range : tuple, optional lower and upper limits of the height you want to plot Sensor_Package : int, optional specify the package number related to the data. This is then used to calibrate the charge sensor Version : str, optional The version of plots you want to use """ #os.system('cls' if os.name=='nt' else 'clear') print( "[INFO] You are running Radiosonde_Superplotter from the DEV release") ############################################################################ """Pre-requisities""" Storage_Path = '/glusterfs/phd/users/th863480/WC3_InSitu_Electrification/' Processed_Data_Path = 'Processed_Data/Radiosonde/' Raw_Data_Path = 'Raw_Data/' Plots_Path = 'Plots/Radiosonde/' plt.style.use('classic') #neseccary if matplotlib version is >= 2.0.0 t_begin = time.time() ############################################################################ """[Step 1] Check and Import Data""" #Error check that either Radiosonde_File or Sensor_Package has been specified if Radiosonde_File is None and Sensor_Package is None: sys.exit( "[Error] You must specify either the Radiosonde_File location or the Sensor_Package number" ) #Attempt to find the radiosonde file either directly or from glob Radiosonde_File = Storage_Path + Processed_Data_Path + Radiosonde_File if Radiosonde_File is not None else glob.glob( Storage_Path + Processed_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/Radiosonde_Flight_PhD_James_No.' + str(Sensor_Package) + '*a.txt') #If no radiosonde file was found we end program if len(Radiosonde_File) == 0: sys.exit( "[Error] Radiosonde package No.%s does not exist. Has the radiosonde been launched yet or has the data been misplaced?" % (Sensor_Package)) #If the radiosonde file was found via glob we need to convert to str from list if isinstance(Radiosonde_File, list): Radiosonde_File = Radiosonde_File[0] #Once the radiosonde file is found we can attempt to find the GPS file in the raw file section GPS_File = glob.glob(Storage_Path + Raw_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/GPSDCC_RESULT*.tsv') #Import all the data if Radiosonde_File is not None: Radiosonde_Data = np.genfromtxt(Radiosonde_File, delimiter=None, skip_header=10, dtype=float, comments="#") if len(GPS_File) != 0: GPS_Data = np.genfromtxt(GPS_File[0], delimiter=None, skip_header=51, dtype=float, comments="#") ############################################################################ """[Step 2] Calibrate bespoke sensors""" Radiosonde_Cal = Radiosonde_Checks(Radiosonde_Data, Calibrate, Sensor_Package, Height_Range, check=1111) Radiosonde_Data = Radiosonde_Cal.wire_calibration() #Calibrate Charge Sensor if Sensor_Package is not None: Radiosonde_Data = Radiosonde_Cal.charge_calibration(Sensor_Package) #Calculate launch datetime GPS_Data = GPS_Data[GPS_Data[:, 4] > 0] if GPS_File is not None: Launch_Datetime = GPS2UTC(GPS_Data[0, 1], GPS_Data[0, 2]) ############################################################################ """[Step 3] Plot radiosonde data""" Title = 'Radiosonde Flight No.' + str( Sensor_Package) + ' (' + Launch_Datetime.strftime( "%d/%m/%Y %H%MUTC" ) + ')' if GPS_File is not None else 'Radiosonde Flight (N/A)' Superplotter = SPRadio(11, Title, Height_Range, Radiosonde_Data) Superplotter.ch(0, 'Charge Linear $(nA)$') Superplotter.ch(1, 'Charge Log $(nA)$') Superplotter.ch(2, 'Cloud Cyan $(V)$', check=1111) Superplotter.ch(3, 'Cloud IR $(V)$') Superplotter.ch(2, 'PLL $(Hz)$', check=1112) Superplotter.ch(13, 'dPLLdt $(Hz$ $s^{-1})$', check=1112) Superplotter.ch(14, 'Ice Concentration $(g$ $m^{-3})$', check=1112) ############################################################################ """[Step 4] Save plot and return""" #Specify the directory the plots are stored in path = os.path.dirname(Radiosonde_File).replace( Storage_Path + Processed_Data_Path, "") #Find any other plots stored in this directory previous_plots = glob.glob(Storage_Path + Plots_Path + path + "/*") #Find the biggest 'v' number in plots plot_version = [] for plots in previous_plots: try: plot_version.append(int(plots[-19:-17])) except ValueError: plot_version.append(int(plots[-18:-17])) plot_version = str(np.max(plot_version) + 1) if len(plot_version) != 0 else '1' #Create full directory and file name Save_Location = Storage_Path + Plots_Path + path + '/' + path + '_v' + plot_version.rjust( 2, '0') + '_' + str(Height_Range[0]).rjust(2, '0') + 'km_to_' + str( Height_Range[1]).rjust(2, '0') + 'km.png' #Ensure the directory exists on file system and save to that location gu.ensure_dir(os.path.dirname(Save_Location)) Superplotter._PlotSave(Save_Location) print("[INFO] Radiosonde_Superplotter completed successfully (In %.2fs)" % (time.time() - t_begin))
def Radiosonde_Tephigram(Radiosonde_File=None, Height_Range=None, Sensor_Package=None, plot_tephigram=False, plot_camborne=False): """The Radiosonde_Tephigram function will plot a tephigram fron the dry bulb temperature, T_dry and the Dew point Temperature, T_dew for pressure values, P at each corresponding height. Certain tephigram outputs are available from this function including: 1) Lower Condensation Level (LCL) in m 2) Level of Free Convection (LFC) in m 3) Evironmental Level (EL) in m 4) Convective Available Potential Energy (CAPE) in J/kg 5) Convective INhibition (CIN) in J/kg Parameters ---------- Outputs ------- References ---------- Ambaum, M. H. P., 2010. Water in the Atmosphere. In: Thermal Physics of the Atmosphere. Oxford: Wiley & Sons, pp. 93-109 Marlton, G. 2018. Tephigram. Original Matlab code found in Matlab_Code directory Hunt, K. 2018. Tephigram. Original Python code found in the same directory. """ gu.cprint( "[INFO] You are running Radiosonde_Tephigram from the STABLE release", type='bold') ############################################################################ """Prerequisites""" #Time Controls t_begin = time.time() #Storage Locations Storage_Path = PhD_Global.Storage_Path_WC3 Processed_Data_Path = 'Processed_Data/Radiosonde/' Raw_Data_Path = 'Raw_Data/' Plots_Path = 'Plots/Tephigram/' #Set-up data importer EPCC_Data = EPCC_Importer() ############################################################################ """[Step 1] Check and Import Data""" #Error check that either Radiosonde_File or Sensor_Package has been specified if Radiosonde_File is None and Sensor_Package is None: sys.exit( "[Error] You must specify either the Radiosonde_File location or the Sensor_Package number" ) #Attempt to find the radiosonde file either directly or from glob Radiosonde_File = Storage_Path + Processed_Data_Path + Radiosonde_File if Radiosonde_File is not None else glob.glob( Storage_Path + Processed_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/Radiosonde_Flight_PhD_James_No.' + str(Sensor_Package) + '*a.txt') #If no radiosonde file was found we end program if len(Radiosonde_File) == 0: sys.exit( "[Error] Radiosonde package No.%s does not exist. Has the radiosonde been launched yet or has the data been misplaced?" % (Sensor_Package)) #If the radiosonde file was found via glob we need to convert to str from list if isinstance(Radiosonde_File, list): Radiosonde_File = Radiosonde_File[0] print("Importing Radiosonde File @ ", Radiosonde_File) #Once the radiosonde file is found we can attempt to find the GPS file in the raw file section GPS_File = glob.glob(Storage_Path + Raw_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/GPSDCC_RESULT*.tsv') #Import all the data if Radiosonde_File is not None: Radiosonde_Data = np.genfromtxt(Radiosonde_File, delimiter=None, skip_header=9, dtype=float, comments="#") if len(GPS_File) != 0: GPS_Data = np.genfromtxt(GPS_File[0], delimiter=None, skip_header=51, dtype=float, comments="#") #Calibrate Height, Temperature and Convert PANDORA channels from counts to volts if required. Radiosonde_Cal = Radiosonde_Checks(Radiosonde_Data, calibrate=None, package_no=Sensor_Package, height_range=Height_Range, check=1111) #Return Data Radiosonde_Data = Radiosonde_Cal.return_data() #Calculate launch datetime GPS_Data = GPS_Data[GPS_Data[:, 4] > 0] if GPS_File is not None: Launch_Datetime = GPS2UTC(GPS_Data[0, 1], GPS_Data[0, 2]) #Unpack variables Z = Radiosonde_Data[:, 1] Tdry = Radiosonde_Data[:, 3] Tdew = Radiosonde_Data[:, 14] Pres = Radiosonde_Data[:, 2] RH = Radiosonde_Data[:, 4] / 100 RH -= np.max(RH) - 0.01 Wind_Mag = (Radiosonde_Data[:, 15]**2 + Radiosonde_Data[:, 16]**2)**0.5 Wind_Dir = np.arctan2(Radiosonde_Data[:, 15], Radiosonde_Data[:, 16]) * 180 / np.pi if plot_tephigram is True: print("[INFO] Plotting Tephigram...") ############################################################################ """Create Tephigram""" #Mask nan data (ONLY FOR PLOTTING) Radiosonde_Data_Plotting = gu.antinan(Radiosonde_Data.T) #Unpack variables Z_Plot = Radiosonde_Data[:, 1] Tdry_Plot = Radiosonde_Data[:, 3] Tdew_Plot = Radiosonde_Data[:, 14] Pres_Plot = Radiosonde_Data[:, 2] #Subset the tephigram to specified location locator = gu.argneararray(Z_Plot, np.array(Height_Range) * 1000) anchor = np.array([(Pres_Plot[locator]), (Tdry_Plot[locator])]).T Pres_Plot_Antinan, Tdry_Plot_Antinan, Tdew_Plot_Antinan = gu.antinan( np.array([Pres_Plot, Tdry_Plot, Tdew_Plot]), unpack=True) #Group the dews, temps and wind profile measurements dews = zip(Pres_Plot_Antinan, Tdew_Plot_Antinan) temps = zip(Pres_Plot_Antinan, Tdry_Plot_Antinan) barb_vals = zip(Pres, Wind_Dir, Pres_Plot) #Create Tephigram plot Tephigram = SPTephigram() if plot_camborne is True: #Determine ULS_File = sorted( glob.glob(PhD_Global.Storage_Path_WC2 + 'Raw_Data/Met_Data/ULS/*')) ULS_Date = np.zeros(len(ULS_File), dtype=object) for i, file in enumerate(ULS_File): try: ULS_Date[i] = datetime.strptime(os.path.basename(file), '%Y%m%d_%H_UoW_ULS.csv') except: ULS_Date[i] = datetime(1900, 1, 1) #Find Nearest Upper Level Sounding Flight to Radiosonde Flight ID = gu.argnear(ULS_Date, Launch_Datetime) print("[INFO] Radiosonde Launch Time:", Launch_Datetime, "Camborne Launch Time:", ULS_Date[ID]) #Import Camborne Radiosonde Data press_camborne, temps_camborne, dews_camborne = EPCC_Data.ULS_Calibrate( ULS_File[ID], unpack=True, PRES=True, TEMP=True, DWPT=True) #Match Camborne pressures with Reading pressures mask = [ gu.argnear(press_camborne, Pres_Plot[0]), gu.argnear(press_camborne, Pres_Plot[-1]) ] press_camborne = press_camborne[mask[0]:mask[1]] temps_camborne = temps_camborne[mask[0]:mask[1]] dews_camborne = dews_camborne[mask[0]:mask[1]] dews_camborne = zip(press_camborne, dews_camborne) temps_camborne = zip(press_camborne, temps_camborne) #Plot Reading Radiosonde profile_t1 = Tephigram.plot(temps, color="red", linewidth=1, label='Reading Dry Bulb Temperature', zorder=5) profile_d1 = Tephigram.plot(dews, color="blue", linewidth=1, label='Reading Dew Bulb Temperature', zorder=5) #Plot Camborne Radiosonde profile_t1 = Tephigram.plot(temps_camborne, color="red", linestyle=':', linewidth=1, label='Camborne Dry Bulb Temperature', zorder=5) profile_d1 = Tephigram.plot(dews_camborne, color="blue", linestyle=':', linewidth=1, label='Camborne Dew Bulb Temperature', zorder=5) else: profile_t1 = Tephigram.plot(temps, color="red", linewidth=2, **{'label': 'Dry Bulb Temperature'}) profile_d1 = Tephigram.plot(dews, color="blue", linewidth=2, **{'label': 'Dew Bulb Temperature'}) #Add extra information to Tephigram plot #Tephigram.axes.set(title=Title, xlabel="Potential Temperature $(^\circ C)$", ylabel="Dry Bulb Temperature $(^\circ C)$") Title = 'Radiosonde Tephigram Flight No.' + str( Sensor_Package ) + ' (' + Launch_Datetime.strftime( "%d/%m/%Y %H%MUTC" ) + ')' if GPS_File is not None else 'Radiosonde Tephigram Flight (N/A)' Tephigram.axes.set(title=Title) #[OPTIONAL] Add wind profile information to Tephigram. #profile_t1.barbs(barb_vals) ############################################################################ """[Step 4] Save plot and return""" #Specify the directory the plots are stored in path = os.path.dirname(Radiosonde_File).replace( Storage_Path + Processed_Data_Path, "") #Find any other plots stored in this directory previous_plots = glob.glob(Storage_Path + Plots_Path + path + "/*") #Find the biggest 'v' number in plots plot_version = [] for plots in previous_plots: try: plot_version.append(int(os.path.basename(plots)[34:37])) except ValueError: plot_version.append(int(os.path.basename(plots)[34:36])) plot_version = str(np.max(plot_version) + 1) if len(plot_version) != 0 else '1' #Create full directory and file name Save_Location = Storage_Path + Plots_Path + path + '/' + path + '_v' + plot_version.rjust( 2, '0') + '_' + str(Height_Range[0]).rjust( 2, '0') + 'km_to_' + str(Height_Range[1]).rjust(2, '0') + 'km.png' #Ensure the directory exists on file system and save to that location gu.ensure_dir(os.path.dirname(Save_Location)) Tephigram.savefig(Save_Location) #Tephigram.show() ############################################################################ """[Step ??] Calculate Stability Indices""" print("[INFO] Calculating Stability Indices...") #Common Pressure Levels P_500 = gu.argnear(Pres, 500) P_700 = gu.argnear(Pres, 700) P_850 = gu.argnear(Pres, 850) #Showalter stability index #S = Tdry[P_500] - Tl #K-Index K = (Tdry[P_850] - Tdry[P_500]) + Tdew[P_850] - (Tdry[P_700] - Tdew[P_700]) #Cross Totals Index CT = Tdew[P_850] - Tdry[P_500] #Vertical Totals Index VT = Tdry[P_850] - Tdry[P_500] #Total Totals Index TT = VT + CT #SWEAT Index ms2kn = 1.94384 #Conversion between m/s to knots SW_1 = 20 * (TT - 49) SW_2 = 12 * Tdew[P_850] SW_3 = 2 * Wind_Mag[P_850] * ms2kn SW_4 = Wind_Mag[P_500] * ms2kn SW_5 = 125 * (np.sin(Wind_Dir[P_500] - Wind_Dir[P_850]) + 0.2) #Condition SWEAT Term 1 from several conditions SW_1 = 0 if SW_1 < 49 else SW_1 #Condition SWEAT Term 5 with several conditions if (Wind_Dir[P_850] > 130) & (Wind_Dir[P_850] < 250): if (Wind_Dir[P_500] > 210) & (Wind_Dir[P_500] < 310): if Wind_Dir[P_500] - Wind_Dir[P_850] > 0: if (Wind_Mag[P_500] * ms2kn > 15) | (Wind_Mag[P_850] * ms2kn > 15): SW_5 = SW_5 else: SW_5 = 0 else: SW_5 = 0 else: SW_5 = 0 else: SW_5 = 0 #Calulate Final Product SW = SW_1 + SW_2 + SW_3 + SW_4 + SW_5 print("Stability Indices") print("-----------------") print("K-Index:", K) print("Cross Totals Index:", CT) print("Vettical Totals Index:", VT) print("Total Totals Index:", TT) print("SWEAT Index:", SW) print("\n") ############################################################################ """[Step ??] Calculate Tephigram Indices""" #Convert Temperature back to Kelvin Tdry += 273.15 Tdew += 273.15 #Convert Height into metres Z *= 1000 #Constants over27 = 0.286 # Value used for calculating potential temperature 2/7 L = 2.5e6 #Latent evaporation 2.5x10^6 epsilon = 0.622 E = 6.014 #e in hpa Rd = 287 #R constant for dry air #Equations es = lambda T: 6.112 * np.exp( (17.67 * (T - 273.15)) / (T - 29.65) ) #Teten's Formula for Saturated Vapour Pressure converted for the units of T in Kelvin rather than Centigrade #Calculate Theta and Theta Dew theta = Tdry * (1000 / Pres)**over27 thetadew = Tdew * (1000 / Pres)**over27 #Find the Lifting Condensation Level (LCL) qs_base = 0.622 * es(Tdew[0]) / Pres[0] theta_base = theta[0] Pqs_base = 0.622 * es( Tdry) / qs_base #Calculate a pressure for constant qs Pqs_base = Tdry * (1000 / Pqs_base)**( 2 / 7) #Calculates pressure in term of P temp #print("Tdew[0]", Tdew[0]) #print("Pres[0]", Pres[0]) #print("qs_base",qs_base) #print("theta_base", theta_base) #print("Pqs_base", Pqs_base) #Find first location where Pqs_base > theta_base y1 = np.arange(Pqs_base.size)[Pqs_base > theta_base][0] #print(Pqs_base[y1]) #print(y1) #print(gu.argnear(Pqs_base, theta_base)) LCL = Z[y1] print("LCL", LCL) #sys.exit() Tarr = np.zeros(Tdry.size) thetaarr = np.zeros(Tdry.size) T_temp = Tdry[y1] P_temp = 1000 * (T_temp / Pqs_base[y1])**3.5 qs0 = 0.622 * es(T_temp) / P_temp thetaarr[y1] = Pqs_base[y1] Tarr[y1] = T_temp for i in xrange(y1 + 1, y1 + 100): T_temp -= 1 P_temp = 1000 * (T_temp / thetaarr[i - 1])**3.5 qs = 0.622 * es(T_temp) / P_temp thetaarr[i] = thetaarr[i - 1] - ((2.5E6 / 1004) * (thetaarr[i - 1] / T_temp) * (qs - qs0)) qs0 = qs Tarr[i] = T_temp #Now need to integrate back to 1000hpa T_temp = Tdry[y1] P_temp = 1000 * (T_temp / Pqs_base[y1])**3.5 qs0 = 0.622 * es(T_temp) / P_temp thetaarr[y1] = Pqs_base[y1] Tarr[y1] = T_temp for i in xrange(y1 - 1): T_temp += 1 P_temp = 1000 * (T_temp / thetaarr[(y1 - i) + 1])**3.5 qs = 0.622 * es(T_temp) / P_temp thetaarr[y1 - i] = thetaarr[(y1 - i) + 1] - ( (2.5E6 / 1004) * (thetaarr[(y1 - i) + 1] / T_temp) * (qs - qs0)) qs0 = qs Tarr[y1 - i] = T_temp y8 = (thetaarr > 253) & (thetaarr < 380) thetaarr = thetaarr[y8] Tarr = Tarr[y8] #Now find environmental levels and LFC begin by converting thetaarr into P Pthetaeq = 1000 / (thetaarr / Tarr)**3.5 l5 = np.isnan(Pthetaeq) Pthetaeq[l5] = [] #Now interpolate on to rs height co-ordinates TEMP = sc.interpolate.interp1d(Pthetaeq, [thetaarr, Tarr], fill_value="extrapolate")(Pres) thetaarr = TEMP[0] Tarr = TEMP[1] del (TEMP) y5 = np.arange(Tdry.size)[Tdry < Tarr] if np.any(y5): LFC = Z[y5[0]] EL = Z[y5[-1]] #Finds CIN area above LCL y6 = np.arange(Tdry.size)[(Z < LFC) & (Z >= LCL) & (Tdry > Tarr)] y7 = np.arange(Tdry.size)[(Z < LCL) & (Tdry > Tarr)] Pstart = Pres[y5[-1]] #Now need to calculate y5 temperatures into virtual temperatures Tvdash = Tarr / (1 - (E / Pres) * (1 - epsilon)) Tv = Tdry / (1 - (E / Pres) * (1 - epsilon)) T_adiabat = ((theta_base / (1000 / Pres)**over27)) Tv_adiabat = T_adiabat / (1 - (E / Pres) * (1 - epsilon)) #Now need to calculate CAPE... and CIN to use CAPE = R_d = intergral(LFC,EL)(T'_v - T_v) d ln p CAPE = 0 for i in xrange(y5[-2], y5[0], -1): CAPE += (Rd * (Tvdash[i] - Tv[i]) * np.log(Pres[i] / Pres[i + 1])) #Now we use same technique to calculate CIN CIN = 0 if len(y6) != 0: for i in xrange(y6[-2], y6[0], -1): CIN += (Rd * (Tvdash[i] - Tv[i]) * np.log(Pres[i] / Pres[i + 1])) #Now calculate temperature along the dry adiabat y7 = np.arange(Tdry.size)[(Z < LCL) & (Tv > Tv_adiabat)] if len(y7) != 0: for i in xrange(y7[-2], y7[0], -1): CIN += (Rd * (Tv_adiabat[i] - Tv[i]) * np.log(Pres[i] / Pres[i + 1])) else: LFC = np.nan EL = np.nan CAPE = 0 CIN = 0 #Print out information print("Parcel Information") print("------------------") print("LCL = %.2fm" % LCL) print("LFC = %.2fm" % LFC) print("EL = %.2fm" % EL) print("CAPE %.2f J/kg" % CAPE) print("CIN %.2f J/kg" % CIN) print("\n") print( "[INFO] Radiosonde_Tephigram has been completed successfully (In %.2fs)" % (time.time() - t_begin)) return LCL, LFC, EL, CAPE, CIN
def Radiosonde_Superplotter(Radiosonde_File=None, Calibrate=None, Height_Range=None, Sensor_Package=None): """This function will plot the data from a single radiosonde flight Parameters ---------- Radiosonde_File : str, optional Location of the radiosonde file to be processed and plotted Calibrate : tuple, optional tuple of column numbers which require calibration using the calibration metric y = x*(5/4096) Height_Range : tuple, optional lower and upper limits of the height you want to plot Sensor_Package : int, optional specify the package number related to the data. This is then used to calibrate the charge sensor Version : str, optional The version of plots you want to use """ gu.cprint( "[INFO] You are running Radiosonde_Superplotter from the DEV release", type='bold') ############################################################################ """Prerequisites""" #Time Controls t_begin = time.time() #Storage Locations Storage_Path = PhD_Global.Storage_Path_WC3 Processed_Data_Path = 'Processed_Data/Radiosonde/' Raw_Data_Path = 'Raw_Data/' Plots_Path = 'Plots/Radiosonde/' #Plot Labels if Calibrate == "counts": Pandora_Labels = [ 'Charge (Counts)', 'Cloud Sensor\n(Counts)', 'PLL (counts)' ] elif Calibrate == "volts": Pandora_Labels = ['Charge (V)', 'Cloud Sensor\n(V)', 'PLL (Counts)'] elif Calibrate == "units": Pandora_Labels = [ 'Charge Density\n$(pC$ $m^{-3})$', 'Cloud Sensor\n(V)', 'Vibrating\nWire$(Hz)$' ] ############################################################################ """[Step 1] Check and Import Data""" #Error check that either Radiosonde_File or Sensor_Package has been specified if Radiosonde_File is None and Sensor_Package is None: sys.exit( "[Error] You must specify either the Radiosonde_File location or the Sensor_Package number" ) #Attempt to find the radiosonde file either directly or from glob Radiosonde_File = Storage_Path + Processed_Data_Path + Radiosonde_File if Radiosonde_File is not None else glob.glob( Storage_Path + Processed_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/Radiosonde_Flight_PhD_James_No.' + str(Sensor_Package) + '*a.txt') #If no radiosonde file was found we end program if len(Radiosonde_File) == 0: sys.exit( "[Error] Radiosonde package No.%s does not exist. Has the radiosonde been launched yet or has the data been misplaced?" % (Sensor_Package)) #If the radiosonde file was found via glob we need to convert to str from list if isinstance(Radiosonde_File, list): Radiosonde_File = Radiosonde_File[0] #Once the radiosonde file is found we can attempt to find the GPS file in the raw file section GPS_File = glob.glob(Storage_Path + Raw_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2, '0') + '_*/GPSDCC_RESULT*.tsv') #Import all the data if Radiosonde_File is not None: Radiosonde_Data = np.genfromtxt(Radiosonde_File, delimiter=None, skip_header=10, dtype=float, comments="#") if len(GPS_File) != 0: GPS_Data = np.genfromtxt(GPS_File[0], delimiter=None, skip_header=51, dtype=float, comments="#") ############################################################################ """[Step 2] Calibrate bespoke sensors""" #Calibrate Height, Temperature and Convert PANDORA channels from counts to volts if required. Radiosonde_Cal = Radiosonde_Checks(Radiosonde_Data, Calibrate, Sensor_Package, Height_Range, check=1111) #Calibrate OMB Sensor if Calibrate == "units": Radiosonde_Cal.wire_calibration() #Calibrate Charge Sensor if Calibrate == "units": Radiosonde_Cal.charge_calibration(Sensor_Package) #Return Data Radiosonde_Data = Radiosonde_Cal.return_data() #Calculate launch datetime #print("INDEX", np.arange(len(GPS_Data[:,4]))[GPS_Data[:,4] > 0]+51) #sys.exit() GPS_Data = GPS_Data[GPS_Data[:, 4] > 0] #GPS_Data = GPS_Data[7718:] #for No.4 if GPS_File is not None: Launch_Datetime = GPS2UTC(GPS_Data[0, 1], GPS_Data[0, 2]) ############################################################################ """[Step 3] Plot radiosonde data""" Title = 'Radiosonde Flight No.' + str( Sensor_Package) + ' (' + Launch_Datetime.strftime( "%d/%m/%Y %H%MUTC" ) + ')' if GPS_File is not None else 'Radiosonde Flight (N/A)' Superplotter = SPRadiosonde( 8, Title, Height_Range, Radiosonde_Data) if Calibrate == "units" else SPRadiosonde( 7, Title, Height_Range, Radiosonde_Data) #Plot the PANDORA data Superplotter.Charge(Linear_Channel=0, Log_Channel=1, XLabel=Pandora_Labels[0]) Superplotter.Cloud(Cyan_Channel=2, IR_Channel=3, XLabel=Pandora_Labels[1], Cyan_Check=1111) Superplotter.PLL( PLL_Channel=2, XLabel=Pandora_Labels[2], PLL_Check=1112, Point=False, Calibrate=Calibrate) if Sensor_Package < 3 else Superplotter.PLL( PLL_Channel=2, XLabel=Pandora_Labels[2], PLL_Check=1112, Point=True, Calibrate=Calibrate) #Plot the processed PLL data #if Calibrate == "units": Superplotter.ch(13, 'dPLLdt $(Hz$ $s^{-1})$', 'dPLL/dt', check=1112, point=True) if Calibrate == "units": Superplotter.ch(14, 'SLWC $(g$ $m^{-3})$', 'Supercooled Liquid\nWater Concentration', check=1112, point=True) ############################################################################ """[Step 4] Save plot and return""" #Specify the directory the plots are stored in path = os.path.dirname(Radiosonde_File).replace( Storage_Path + Processed_Data_Path, "") #Find any other plots stored in this directory previous_plots = glob.glob(Storage_Path + Plots_Path + path + "/*") #Find the biggest 'v' number in plots plot_version = [] for plots in previous_plots: try: plot_version.append(int(os.path.basename(plots)[34:37])) except ValueError: plot_version.append(int(os.path.basename(plots)[34:36])) plot_version = str(np.max(plot_version) + 1) if len(plot_version) != 0 else '1' #Create full directory and file name Save_Location = Storage_Path + Plots_Path + path + '/' + path + '_v' + plot_version.rjust( 2, '0') + '_' + str(Height_Range[0]).rjust(2, '0') + 'km_to_' + str( Height_Range[1]).rjust(2, '0') + 'km.png' #Ensure the directory exists on file system and save to that location gu.ensure_dir(os.path.dirname(Save_Location)) Superplotter._PlotSave(Save_Location) print("[INFO] Radiosonde_Superplotter completed successfully (In %.2fs)" % (time.time() - t_begin))
def Radiosonde_CloudIdentifier(Radiosonde_File=None, Height_Range=None, Sensor_Package=None): """This function will identify the cloud layers within a radiosonde ascent by using the cloud sensor and relative humidity measurements Reference --------- Zhang, J., H. Chen, Z. Li, X. Fan, L. Peng, Y. Yu, and M. Cribb (2010). Analysis of cloud layer structure in Shouxian, China using RS92 radiosonde aided by 95 GHz cloud radar. J. Geophys. Res., 115, D00K30, doi: 10.1029/2010JD014030. WMO, 2017. Clouds. In: Internal Cloud Atlas Manual on the Observation of Clouds and Other Meteors. Hong Kong: WMO, Section 2.2.1.2. """ gu.cprint("[INFO] You are running Radiosonde_CloudIdentifier from the STABLE release", type='bold') ############################################################################ """Prerequisites""" #Time Controls t_begin = time.time() #Storage Locations Storage_Path = PhD_Global.Storage_Path_WC3 Processed_Data_Path = 'Processed_Data/Radiosonde/' Raw_Data_Path = 'Raw_Data/' Plots_Path = 'Plots/Tephigram/' #Set-up data importer EPCC_Data = EPCC_Importer() ############################################################################ """[Step 1] Check and Import Data""" #Error check that either Radiosonde_File or Sensor_Package has been specified if Radiosonde_File is None and Sensor_Package is None: sys.exit("[Error] You must specify either the Radiosonde_File location or the Sensor_Package number") #Attempt to find the radiosonde file either directly or from glob Radiosonde_File = Storage_Path + Processed_Data_Path + Radiosonde_File if Radiosonde_File is not None else glob.glob(Storage_Path + Processed_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2,'0') + '_*/Radiosonde_Flight_PhD_James_No.' + str(Sensor_Package) + '*a.txt') #If no radiosonde file was found we end program if len(Radiosonde_File) == 0: sys.exit("[Error] Radiosonde package No.%s does not exist. Has the radiosonde been launched yet or has the data been misplaced?" % (Sensor_Package)) #If the radiosonde file was found via glob we need to convert to str from list if isinstance(Radiosonde_File, list): Radiosonde_File = Radiosonde_File[0] #Once the radiosonde file is found we can attempt to find the GPS file in the raw file section GPS_File = glob.glob(Storage_Path + Raw_Data_Path + 'Radiosonde_Flight_No.' + str(Sensor_Package).rjust(2,'0') + '_*/GPSDCC_RESULT*.tsv') #Import all the data if Radiosonde_File is not None: Radiosonde_Data = np.genfromtxt(Radiosonde_File, delimiter=None, skip_header=10, dtype=float, comments="#") if len(GPS_File) != 0: GPS_Data = np.genfromtxt(GPS_File[0], delimiter=None, skip_header=51, dtype=float, comments="#") ############################################################################ """[Step 2] Calibrate bespoke sensors""" #Calibrate Height, Temperature and Convert PANDORA channels from counts to volts if required. Radiosonde_Cal = Radiosonde_Checks(Radiosonde_Data, None, Sensor_Package, Height_Range, check=1111) #Calibrate Relative Humidity Sensor (e.g. find RH_ice) Radiosonde_Cal.RH(Sensor_Package) #Return Data Radiosonde_Data = Radiosonde_Cal.return_data() GPS_Data = GPS_Data[GPS_Data[:,4] > 0] if GPS_File is not None: Launch_Datetime = GPS2UTC(GPS_Data[0,1], GPS_Data[0,2]) ############################################################################ """[METHOD 1]: Relative Humidity (Zhang et al. 2010)""" #Define data into new variables Z = Radiosonde_Data[:,1] RH = Radiosonde_Data[:,4] #Create Height-Resolving RH Thresholds (see Table 1 in Zhang et al. (2010)) #N.B. use np.interp(val, RH_Thresholds['altitude'], RH_Thresholds['*RH']) where val is the height range you want the RH Threshold RH_Thresholds = {'minRH' : [0.92, 0.90, 0.88, 0.75, 0.75], 'maxRH' : [0.95, 0.93, 0.90, 0.80, 0.80], 'interRH' : [0.84, 0.82, 0.78, 0.70, 0.70], 'altitude' : [0, 2, 6, 12, 20]} #Define the cloud height levels as defined by WMO (2017). Z_Levels = {'low' : [0,2], 'middle' : [2,7], 'high' : [5,13]} #Define the types of layers that can be detected. Cloud_Types = {0 : 'Clear Air', 1 : 'Moist (Not Cloud)', 2 : 'Cloud'} #Define the min, max and interRH for all measure altitudes minRH = np.interp(Z, RH_Thresholds['altitude'], RH_Thresholds['minRH'], left=np.nan, right=np.nan)*100 maxRH = np.interp(Z, RH_Thresholds['altitude'], RH_Thresholds['maxRH'], left=np.nan, right=np.nan)*100 interRH = np.interp(Z, RH_Thresholds['altitude'], RH_Thresholds['interRH'], left=np.nan, right=np.nan)*100 #[Step 1]: The base of the lowest moist layer is determined as the level when RH exceeds the min-RH corresponding to this level minRH_mask = (RH > minRH) #[Step 2 and 3]: Above the base of the moist layer, contiguous levels with RH over the corresponding min-RH are treated as the same layer Z[~minRH_mask] = np.nan Clouds_ID = gu.contiguous(Z, 1) #[Step 4]: Moist layers with bases lower than 120m and thickness's less than 400m are discarded for Cloud in np.unique(Clouds_ID)[1:]: if Z[Clouds_ID == Cloud][0] < 0.12: if Z[Clouds_ID == Cloud][-1] - Z[Clouds_ID == Cloud][0] < 0.4: Clouds_ID[Clouds_ID == Cloud] = 0 #[Step 5]: The moist layer is classified as a cloud layer is the maximum RH within this layer is greater than the corresponding max-RH at the base of this moist layer LayerType = np.zeros(Z.size, dtype=int) #0: Clear Air, 1: Moist Layer, 2: Cloud Layer for Cloud in np.unique(Clouds_ID)[1:]: if np.any(RH[Clouds_ID == Cloud] > maxRH[Clouds_ID == Cloud][0]): LayerType[Clouds_ID == Cloud] = 2 else: LayerType[Clouds_ID == Cloud] = 1 #[Step 6]: The base of the cloud layers is set to 280m AGL, and cloud layers are discarded if their tops are lower than 280m for Cloud in np.unique(Clouds_ID)[1:]: if Z[Clouds_ID == Cloud][-1] < 0.280: Clouds_ID[Clouds_ID == Cloud] = 0 LayerType[Clouds_ID == Cloud] = 0 #[Step 7]: Two contiguous layers are considered as one-layer cloud if the distance between these two layers is less than 300m or the minimum RH within this distance is more than the maximum inter-RG value within this distance for Cloud_Below, Cloud_Above in zip(np.unique(Clouds_ID)[1:-1], np.unique(Clouds_ID)[2:]): #Define the index between clouds of interest Air_Between = np.arange(gu.bool2int(Clouds_ID == Cloud_Below)[-1], gu.bool2int(Clouds_ID == Cloud_Above)[0]) if ((Z[Clouds_ID == Cloud_Above][0] - Z[Clouds_ID == Cloud_Below][-1]) < 0.3) or (np.nanmin(RH[Air_Between]) > np.nanmax(interRH[Air_Between])): Joined_Cloud_Mask = np.arange(gu.bool2int(Clouds_ID == Cloud_Below)[0], gu.bool2int(Clouds_ID == Cloud_Above)[-1]) #Update the cloud ID array as the Cloud_Below and Cloud_Above are not distinct clouds Clouds_ID[Joined_Cloud_Mask] = Cloud_Below #Update the LayerType to reflect the new cloud merging if np.any(LayerType[Clouds_ID == Cloud_Below] == 2) or np.any(LayerType[Clouds_ID == Cloud_Above] == 2): LayerType[Joined_Cloud_Mask] = 2 else: LayerType[Joined_Cloud_Mask] = 1 #[Step 8] Clouds are discarded if their thickness's are less than 30.5m for low clouds and 61m for middle/high clouds for Cloud in np.unique(Clouds_ID)[1:]: if Z[Clouds_ID == Cloud][0] < Z_Levels['low'][1]: if Z[Clouds_ID == Cloud][-1] - Z[Clouds_ID == Cloud][0] < 0.0305: Clouds_ID[Clouds_ID == Cloud] = 0 LayerType[Clouds_ID == Cloud] = 0 else: if Z[Clouds_ID == Cloud][-1] - Z[Clouds_ID == Cloud][0] < 0.0610: Clouds_ID[Clouds_ID == Cloud] = 0 LayerType[Clouds_ID == Cloud] = 0 #Re-update numbering of each cloud identified Clouds_ID = gu.contiguous(Clouds_ID, invalid=0) print("Detected Clouds and Moist Layers\n--------------------------------") for Cloud in np.unique(Clouds_ID)[1:]: print("Cloud %s. Cloud Base = %.2fkm, Cloud Top = %.2fkm, Layer Type: %s" % (Cloud, Z[Clouds_ID == Cloud][0], Z[Clouds_ID == Cloud][-1], Cloud_Types[LayerType[Clouds_ID == Cloud][0]])) return Clouds_ID, LayerType ############################################################################ """[METHOD 2] Cloud Sensor (Own Method. Probably defunct)""" Z = Radiosonde_Data[:,1] Tdry = Radiosonde_Data[:,3] Cyan = Radiosonde_Data[:,7][Radiosonde_Data[:,9] == 1111] IR = Radiosonde_Data[:,8] #Correct IR sensor for temperature drift IR_Drift = 0.239601750967*Tdry IR += IR_Drift Z,Tdry,IR = gu.antinan((Z,Tdry,IR), unpack=True) IR_Mean = np.nanmean(np.sort(IR.copy(), kind='mergesort')[:IR.size//8]) IR_Median = np.nanmedian(np.sort(IR.copy(), kind='mergesort')[:IR.size//8]) IR_Mask = np.full(IR.size, 1100, dtype=np.float64) IR_Mask[IR < (IR_Median + 18)] = np.nan IR_Mask_Mask = np.zeros(IR_Mask.size, dtype=int) Temp_Mask = 0 Ind_Mask = () for j in xrange(IR_Mask.size): if np.isfinite(IR_Mask[j]): Temp_Mask += 1 Ind_Mask += (j,) IR_Mask_Mask[[Ind_Mask]] = Temp_Mask else: Temp_Mask = 0 Ind_Mask = () #Remove cloud base lengths less than 10 (~8 minutes) IR_Mask_Mask[IR_Mask_Mask < 2] = 0 #Convert integers from cloud length to unique cloud number Cloud_TimeBounds = gu.bool2int(np.diff(IR_Mask_Mask) != 0) + 1 #Check to see if cloud exists along day boundary. If so we add in manual time boundaries if IR_Mask_Mask[0] != 0: Cloud_TimeBounds = np.insert(Cloud_TimeBounds, 0, 0) if IR_Mask_Mask[-1] != 0: Cloud_TimeBounds = np.append(Cloud_TimeBounds, IR_Mask_Mask.size-1) Cloud_TimeBounds = Cloud_TimeBounds.reshape(int(Cloud_TimeBounds.size/2),2) #Give each cloud a unique integer for each time step for cloud_id, cloud_bounds in enumerate(Cloud_TimeBounds,1): IR_Mask_Mask[cloud_bounds[0]:cloud_bounds[1]] = cloud_id print("IR_Mask_Mask", IR_Mask_Mask.tolist()) print("No of Detected Clouds", np.unique(IR_Mask_Mask)[-1]) plt.clf() plt.hist(IR-IR_Median, bins=50) plt.yscale('log') #plt.show() #print("IR", IR) print("IR_Mean", IR_Mean) print("IR_Median", IR_Median) print("IR_Mask", IR_Mask) plt.clf() plt.plot(IR_Mask, Z, 'o', label='Cloud', linewidth=7.0) plt.plot(IR, Z, label='No filter') plt.legend(loc='upper right', prop={'size': 10}, fancybox=True, framealpha=0.5) plt.show() sys.exit()