IODA Bundle
giirs_lw2ioda_bt_v1.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  for f in self.filenamesfilenames:
109  numFiles += 1
110  ncd = nc.Dataset(f, 'r')
111  print(f)
112  # Get the number of locations and channels
113  nlocs = ncd.dimensions["LWdetector"].size
114  nchans = ncd.dimensions["LWchannel"].size
115  self.writerwriter._nlocs += nlocs
116  self.writerwriter._nvars = nchans
117  self.writerwriter._nrecs = 1
118 
119  # Channel metadata associated with longwave radiance
120  if (numFiles == 1):
121  self.xferMdataVarxferMdataVar(ncd, 'LW_wnum', 'channel_wavenumber', VarMdata, 'float')
122 
123  VarMdata['channel_number'] = self.writerwriter.FillNcVector(
124  np.ma.array(range(1, nchans+1)), 'integer')
125  self.units_valuesunits_values['channel_number'] = '1'
126 
127  # The file contains a single image with a sub-second scan time. We
128  # are currently retaining date-time stamps to the nearest second so
129  # for now just grab the beginning scan time and use that for all locations.
130  # The variables holding the time offset from 1 Jan, 0000 are problematic
131  # since the python datetime package doesn't allow year zero, and it's not
132  # clear how days are coverted over several millinnea. The observation
133  # date and time are also in global attributes and appear to line up with
134  # the file name.
135  obsDate = ncd.getncattr("Observing Beginning Date").split("-")
136  obsTime = ncd.getncattr("Observing Beginning Time").split(":")
137  obsSeconds = timedelta(seconds=int(round(float(obsTime[2]))))
138  obsDateTime = datetime(year=int(obsDate[0]), month=int(obsDate[1]),
139  day=int(obsDate[2]), hour=int(obsTime[0]),
140  minute=int(obsTime[1])) + obsSeconds
141  obsDtimeString = obsDateTime.strftime("%Y-%m-%dT%H:%M:%SZ")
142 
143  # Form a vector nlocs long containing the datetime stamp
144  if (numFiles == 1):
145  LocMdata['datetime'] = self.writerwriter.FillNcVector(
146  np.full((nlocs), obsDtimeString), 'datetime')
147  self.units_valuesunits_values['datetime'] = 'ISO 8601 format'
148  else:
149  LocMdata['datetime'] = np.append(LocMdata['datetime'], self.writerwriter.FillNcVector(np.full((nlocs), obsDtimeString), 'datetime'), axis=0)
150 
151  # Read in the latitude and longitude associated with the long wave data
152  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Latitude', 'latitude', LocMdata, 'float')
153  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Longitude', 'longitude', LocMdata, 'float')
154 
155  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_Longitude', 'scan_position', LocMdata, 'integer')
156 
157  # Read in the instrument meta data associated with the long wave data
158  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SolarZenith', 'solar_zenith_angle', LocMdata, 'float')
159  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SolarAzimuth', 'solar_azimuth_angle', LocMdata, 'float')
160  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteZenith', 'sensor_zenith_angle', LocMdata, 'float')
161  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteZenith', 'sensor_view_angle', LocMdata, 'float')
162  self.xferLocMdataVarxferLocMdataVar(ncd, numFiles, 'IRLW_SatelliteAzimuth', 'sensor_azimuth_angle', LocMdata, 'float')
163 
164  # Read in the long wave radiance
165  # For now fabricate the QC marks and error estimates
166  ncVar = ncd.variables['ES_RealLW']
167  lwRadiance = ncVar[:]
168  print(self.writerwriter._nlocs)
169  H = 6.6237E-27
170  C = 2.99791E+10
171  B = 1.38024E-16
172  C1 = 2.*H*C*C
173  C2 = H*C/B
174  # GIIRS wavenumbers
175  wn_lw=np.arange(700,1130.001,0.625)
176  wn_mw=np.arange(1650,2250.001,0.625)
177  TBB = lwRadiance*0-999
178 
179  for iloc in range(nlocs):
180  for ivar in range(1,nchans-1):
181  #Hamming apodization for Ch2 - Ch688
182  lwRadiance_apo = 0.23*lwRadiance[ivar-1,iloc] +0.54*lwRadiance[ivar,iloc] + 0.23*lwRadiance[ivar+1,iloc]
183  #Radiane to Bright Temperature
184  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
185  #For the first channel Ch1 and last channel Ch689 in long wave band
186  ivar = 0
187  lwRadiance_apo = lwRadiance[ivar,iloc]
188  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
189  ivar = nchans-1
190  lwRadiance_apo = lwRadiance[ivar,iloc]
191  TBB[ivar,iloc] = C2*wn_lw[ivar]/math.log(C1*wn_lw[ivar]**3/lwRadiance_apo+1)
192 
193  if 'units' in ncVar.ncattrs():
194  Units = ncVar.getncattr('units')
195  else:
196  Units = None
197  for ivar in range(nchans):
198  varName = "brightness_temperature_%d" % (ivar + 1)
199  if (numFiles == 1):
200  self.datadata[(varName, 'ObsValue')] = self.writerwriter.FillNcVector(TBB[ivar, :], 'float')
201  self.datadata[(varName, 'PreQC')] = self.writerwriter.FillNcVector(np.full((nlocs), 0), 'integer')
202  self.datadata[(varName, 'ObsError')] = self.writerwriter.FillNcVector(np.full((nlocs), 2.0), 'float')
203  else:
204  self.datadata[(varName, 'ObsValue')] = np.append(self.datadata[(varName, 'ObsValue')], self.writerwriter.FillNcVector(TBB[ivar, :], 'float'), axis=0)
205  self.datadata[(varName, 'PreQC')] = np.append(self.datadata[(varName, 'PreQC')], self.writerwriter.FillNcVector(np.full((nlocs), 0), 'integer'), axis=0)
206  self.datadata[(varName, 'ObsError')] = np.append(self.datadata[(varName, 'ObsError')], self.writerwriter.FillNcVector(np.full((nlocs), 2.0), 'float'), axis=0)
207  if Units:
208  self.units_valuesunits_values[varName] = Units
209 
210  ncd.close()
211 
212 
213 def main():
214 
215  parser = argparse.ArgumentParser(
216  description=(
217  'Read NSMC GIIRS long wave radiance file(s) and convert'
218  ' to a concatenated IODA formatted output file.')
219  )
220  required = parser.add_argument_group(title='required arguments')
221  required.add_argument(
222  '-i', '--input',
223  help="name of giirs input file(s)",
224  type=str, nargs='+', required=True)
225  required.add_argument(
226  '-o', '--output',
227  help="name of ioda output file",
228  type=str, required=True)
229  required.add_argument(
230  '-d', '--date',
231  help="base date for the center of the window",
232  metavar="YYYYMMDDHH", type=str, required=True)
233  args = parser.parse_args()
234  fdate = datetime.strptime(args.date, '%Y%m%d%H')
235 
236  writer = iconv.NcWriter(args.output, [], locationKeyList)
237 
238  # Read in the giirs lw radiance
239  lwrad = LwRadiance(args.input, writer)
240 
241  # Add the 'date_time_string' attribute which sets the reference datetime for
242  # the observations.
243  AttrData['date_time_string'] = fdate.strftime("%Y-%m-%dT%H:%M:%SZ")
244 
245  # Write the obs data and meta data into the ioda file
246  writer.BuildNetcdf(lwrad.data, RecMdata, LocMdata, VarMdata, AttrData, lwrad.units_values)
247 
248 
249 if __name__ == '__main__':
250  main()
def xferMdataVar(self, ncd, inName, outName, mData, dType)
def __init__(self, filenames, writer)
def xferLocMdataVar(self, ncd, numFiles, inName, outName, mData, dType)