IODA Bundle
giirs_lw2ioda_bt_v2.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 #
4 # (C) Copyright 2019 UCAR
5 #
6 # This software is licensed under the terms of the Apache Licence Version 2.0
7 # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
8 #
9 
10 from __future__ import print_function
11 import sys
12 import argparse
13 import numpy as np
14 from datetime import datetime, timedelta
15 import netCDF4 as nc
16 import re
17 import dateutil.parser
18 import math
19 from pathlib import Path
20 
21 IODA_CONV_PATH = Path(__file__).parent/"@SCRIPT_LIB_PATH@"
22 if not IODA_CONV_PATH.is_dir():
23  IODA_CONV_PATH = Path(__file__).parent/'..'/'lib-python'
24 sys.path.append(str(IODA_CONV_PATH.resolve()))
25 
26 import ioda_conv_ncio as iconv
27 from orddicts import DefaultOrderedDict
28 
29 
30 vName = "brightness_temperature"
31 
32 locationKeyList = [
33  ("latitude", "float"),
34  ("longitude", "float"),
35  ("datetime", "string")
36 ]
37 
38 LocMdata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
39 RecMdata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
40 VarMdata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
41 AttrData = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
42 
43 
44 class LwRadiance(object):
45  def __init__(self, filenames, writer):
46  self.filenamesfilenames = filenames
47  self.datadata = DefaultOrderedDict(lambda: DefaultOrderedDict(dict))
48  self.units_valuesunits_values = {} # cannot use an ordered dict for this
49  self.writerwriter = writer
50  self.readInFilesreadInFiles()
51 
52  def xferMdataVar(self, ncd, inName, outName, mData, dType):
53  # This method adds a variable to the given metadata dictionary element.
54  ncVar = ncd.variables[inName]
55  mData[outName] = self.writerwriter.FillNcVector(ncVar[:], dType)
56  if 'units' in ncVar.ncattrs():
57  self.units_valuesunits_values[outName] = ncVar.getncattr('units')
58 
59  def xferLocMdataVar(self, ncd, numFiles, inName, outName, mData, dType):
60  # This method adds a variable to the given metadata dictionary element.
61  ncVar = ncd.variables[inName]
62  if (numFiles == 1):
63  mData[outName] = self.writerwriter.FillNcVector(ncVar[:], dType)
64  if 'units' in ncVar.ncattrs():
65  self.units_valuesunits_values[outName] = ncVar.getncattr('units')
66  else:
67  mData[outName] = np.append(mData[outName], self.writerwriter.FillNcVector(ncVar[:], dType), axis=0)
68 
69  def readInFiles(self):
70  # This method will transfer variable data from the input file into a
71  # dictionary (self.data) that the netcdf writer understands.
72  #
73  # The mapping from input variables to ioda variables for IR LW radiance is:
74  #
75  # dimensions:
76  # LWchannel -> nvars
77  # LWdetector -> nlocs
78  #
79  # global attributes:
80  # 'Observing Beginning Date' -> YYYY-MM-DD
81  # 'Observing Beginning Time' -> HH:MM:SS
82  # The two above attributes get translated to a single datetime value
83  # which becomes the value for each location in datetime@MetaData.
84  # The seconds value is a float number possibly containing fractional seconds.
85  #
86  # variables:
87  # LW_wnum(LWchannel) -> channel_wavenumber@VarMetaData
88  # Number the channels starting with 1 assuming that the order
89  # in variables indexed by LWchannel are sequential channel numbers (1..n).
90  #
91  # IRLW_Latitude(LWdetector) -> latitude@MetaData
92  # IRLW_Longitude(LWdetector) -> longitude@MetaData
93  # IRLW_SolarZenith(LWdetector) -> solar_zenith_angle@MetaData
94  # IRLW_SolarAzimuth(LWdetector) -> solar_azimuth_angle@MetaData
95  # IRLW_SatelliteZenith(LWdetector) -> sensor_zenith_angle@MetaData
96  # IRLW_SatelliteAzimuth(LWdetector) -> sensor_azimuth_angle@MetaData
97  #
98  # ES_RealLW(LWchannel, Lwdetector) -> brightness_temperature_#@ObsValue
99  # For now, fabricate the QC marks and error esitmate by setting all
100  # locations to 0 for brightness_temperature_#@PreQC and setting all locations
101  # to 2.0 for brightness_temperature_#@ObsError.
102  valKey = vName, self.writerwriter.OvalName()
103  errKey = vName, self.writerwriter.OerrName()
104  qcKey = vName, self.writerwriter.OqcName()
105 
106  numFiles = 0
107  self.writerwriter._nlocs = 0
108  giirsfiletxt = open(self.filenamesfilenames[0], 'r')
109  giirsfiles = giirsfiletxt.readlines()
110  giirsfiletxt.close()
111 
112  for ff in giirsfiles:
113  numFiles += 1
114  f=ff.strip()
115  ncd = nc.Dataset(f, 'r')
116  print(f)
117  # Get the number of locations and channels
118  nlocs = ncd.dimensions["LWdetector"].size
119  nchans = ncd.dimensions["LWchannel"].size
120  self.writerwriter._nlocs += nlocs
121  self.writerwriter._nvars = nchans
122  self.writerwriter._nrecs = 1
123 
124  # Channel metadata associated with longwave radiance
125  if (numFiles == 1):
126  self.xferMdataVarxferMdataVar(ncd, 'LW_wnum', 'channel_wavenumber', VarMdata, 'float')
127 
128  VarMdata['channel_number'] = self.writerwriter.FillNcVector(
129  np.ma.array(range(1, nchans+1)), 'integer')
130  self.units_valuesunits_values['channel_number'] = '1'
131 
132  # The file contains a single image with a sub-second scan time. We
133  # are currently retaining date-time stamps to the nearest second so
134  # for now just grab the beginning scan time and use that for all locations.
135  # The variables holding the time offset from 1 Jan, 0000 are problematic
136  # since the python datetime package doesn't allow year zero, and it's not
137  # clear how days are coverted over several millinnea. The observation
138  # date and time are also in global attributes and appear to line up with
139  # the file name.
140  obsDate = ncd.getncattr("Observing Beginning Date").split("-")
141  obsTime = ncd.getncattr("Observing Beginning Time").split(":")
142  obsSeconds = timedelta(seconds=int(round(float(obsTime[2]))))
143  obsDateTime = datetime(year=int(obsDate[0]), month=int(obsDate[1]),
144  day=int(obsDate[2]), hour=int(obsTime[0]),
145  minute=int(obsTime[1])) + obsSeconds
146  obsDtimeString = obsDateTime.strftime("%Y-%m-%dT%H:%M:%SZ")
147 
148  # Form a vector nlocs long containing the datetime stamp
149  if (numFiles == 1):
150  LocMdata['datetime'] = self.writerwriter.FillNcVector(
151  np.full((nlocs), obsDtimeString), 'datetime')
152  self.units_valuesunits_values['datetime'] = 'ISO 8601 format'
153  else:
154  LocMdata['datetime'] = np.append(LocMdata['datetime'], self.writerwriter.FillNcVector(np.full((nlocs), obsDtimeString), 'datetime'), axis=0)
155 
156  # Read in the latitude and longitude associated with the long wave data
157  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Latitude', 'latitude', LocMdata, 'float')
158  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Longitude', 'longitude', LocMdata, 'float')
159 
160  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Longitude', 'scan_position', LocMdata, 'integer')
161 
162  # Read in the instrument meta data associated with the long wave data
163  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SolarZenith', 'solar_zenith_angle', LocMdata, 'float')
164  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SolarAzimuth', 'solar_azimuth_angle', LocMdata, 'float')
165  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteZenith', 'sensor_zenith_angle', LocMdata, 'float')
166  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteZenith', 'sensor_view_angle', LocMdata, 'float')
167  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteAzimuth', 'sensor_azimuth_angle', LocMdata, 'float')
168 
169  # Read in the long wave radiance
170  # For now fabricate the QC marks and error estimates
171  ncVar = ncd.variables['ES_RealLW']
172  lwRadiance = ncVar[:]
173  print(self.writerwriter._nlocs)
174  H = 6.6237E-27
175  C = 2.99791E+10
176  B = 1.38024E-16
177  C1 = 2.*H*C*C
178  C2 = H*C/B
179  # GIIRS wavenumbers
180  wn_lw=np.arange(700,1130.001,0.625)
181  wn_mw=np.arange(1650,2250.001,0.625)
182  TBB = lwRadiance*0-999
183 
184  for iloc in range(nlocs):
185  for ivar in range(1,nchans-1):
186  #Hamming apodization for Ch2 - Ch688
187  lwRadiance_apo = 0.23*lwRadiance[ivar-1,iloc] +0.54*lwRadiance[ivar,iloc] + 0.23*lwRadiance[ivar+1,iloc]
188  #Radiane to Bright Temperature
189  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
190  #For the first channel Ch1 and last channel Ch689 in long wave band
191  ivar = 0
192  lwRadiance_apo = lwRadiance[ivar,iloc]
193  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
194  ivar = nchans-1
195  lwRadiance_apo = lwRadiance[ivar,iloc]
196  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
197 
198  if 'units' in ncVar.ncattrs():
199  Units = ncVar.getncattr('units')
200  else:
201  Units = None
202  for ivar in range(nchans):
203  varName = "brightness_temperature_%d" % (ivar + 1)
204  if (numFiles == 1):
205  self.datadata[(varName, 'ObsValue')] = self.writerwriter.FillNcVector(TBB[ivar, :], 'float')
206  self.datadata[(varName, 'PreQC')] = self.writerwriter.FillNcVector(np.full((nlocs), 0), 'integer')
207  self.datadata[(varName, 'ObsError')] = self.writerwriter.FillNcVector(np.full((nlocs), 2.0), 'float')
208  else:
209  self.datadata[(varName, 'ObsValue')] = np.append(self.datadata[(varName, 'ObsValue')], self.writerwriter.FillNcVector(TBB[ivar, :], 'float'), axis=0)
210  self.datadata[(varName, 'PreQC')] = np.append(self.datadata[(varName, 'PreQC')], self.writerwriter.FillNcVector(np.full((nlocs), 0), 'integer'), axis=0)
211  self.datadata[(varName, 'ObsError')] = np.append(self.datadata[(varName, 'ObsError')], self.writerwriter.FillNcVector(np.full((nlocs), 2.0), 'float'), axis=0)
212  if Units:
213  self.units_valuesunits_values[varName] = Units
214 
215  ncd.close()
216 
217 
218 def main():
219 
220  parser = argparse.ArgumentParser(
221  description=(
222  'Read NSMC GIIRS long wave radiance file(s) and convert'
223  ' to a concatenated IODA formatted output file.')
224  )
225  required = parser.add_argument_group(title='required arguments')
226  required.add_argument(
227  '-i', '--input',
228  help="file name of giirs input file(s)",
229  type=str, nargs='+', required=True)
230  required.add_argument(
231  '-o', '--output',
232  help="name of ioda output file",
233  type=str, required=True)
234  required.add_argument(
235  '-d', '--date',
236  help="base date for the center of the window",
237  metavar="YYYYMMDDHH", type=str, required=True)
238  args = parser.parse_args()
239  fdate = datetime.strptime(args.date, '%Y%m%d%H')
240 
241  writer = iconv.NcWriter(args.output, [], locationKeyList)
242 
243  # Read in the giirs lw radiance
244  lwrad = LwRadiance(args.input, writer)
245 
246  # Add the 'date_time_string' attribute which sets the reference datetime for
247  # the observations.
248  AttrData['date_time_string'] = fdate.strftime("%Y-%m-%dT%H:%M:%SZ")
249 
250  # Write the obs data and meta data into the ioda file
251  writer.BuildNetcdf(lwrad.data, RecMdata, LocMdata, VarMdata, AttrData, lwrad.units_values)
252 
253 
254 if __name__ == '__main__':
255  main()
def xferMdataVar(self, ncd, inName, outName, mData, dType)
def __init__(self, filenames, writer)
def xferLocMdataVar(self, ncd, numFiles, inName, outName, mData, dType)