OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
prismbil2oci.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 '''
4 written by J.Scott on 2017/10/03 (joel.scott@nasa.gov)
5 '''
6 import sys
7 import argparse
8 import os
9 import logging
10 from collections import OrderedDict
11 from PRISM_module import rd_prism_hdr, rd_L1B_bil
12 import numpy as np
13 import operator
14 import re
15 import subprocess
16 from netCDF4 import Dataset
17 from datetime import datetime, timedelta
18 
19 __version__ = '1.1'
20 
21 #==========================================================================================================================================
22 
23 def ParseCommandLine(args):
24  '''
25  Defines and parses command line arguments
26  '''
27  ocdataroot = os.getenv('OCDATAROOT')
28  parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
29  description=' Converts PRISM data from the CORAL project to netCDF proxy data in the format of PACE-OCI ',
30  add_help=True)
31  parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
32  parser.add_argument('-i', '--ifile', nargs=1, required=True, type=str,
33  help=' PRISM L1 prm*img.hdr file (required) ')
34  parser.add_argument('-o', '--ofile', nargs=1, required=True, type=str,
35  help=' Output netCDF filename (required) ')
36  parser.add_argument('--cdlfile', nargs=1, type=str,
37  default=os.path.join(ocdataroot, 'prism', 'OCIP_Level-1C_Data_Structure.cdl'),
38  help=' L1C format spec CDL file name ')
39  parser.add_argument('-d', '--debug', action='store_true', default=False)
40  parsedArgs=parser.parse_args(args)
41  return parsedArgs
42 
43 #==========================================================================================================================================
44 
45 def SetLogger(pargs):
46  '''
47  Initiates a logger instance
48  '''
49  lgrName = 'prismbil2oci_%s_T_%s' % (datetime.date(datetime.now()), datetime.time(datetime.now()))
50  lgr = logging.getLogger(lgrName)
51  fmt = '%(message)s'
52  if pargs.debug:
53  level = logging.DEBUG
54  fmt = '%(asctime)s - %(name)s - %(levelname)s -\
55  [%(module)s..%(funcName)s..%(lineno)d] -\
56  %(message)s'
57  formatter = logging.Formatter(fmt)
58  fh = logging.FileHandler('%s.log' % lgrName)
59  fh.setLevel(logging.DEBUG)
60  fh.setFormatter(formatter)
61  lgr.addHandler(fh)
62 
63  level = logging.INFO
64  formatter = logging.Formatter(fmt)
65  ch = logging.StreamHandler()
66  ch.setLevel(logging.INFO)
67  ch.setFormatter(formatter)
68  lgr.addHandler(ch)
69  lgr.setLevel(level)
70  lgr.debug('Logger initialized')
71  return lgr
72 
73 #==========================================================================================================================================
74 
75 def read_prism_L1(logger, fname_in):
76  '''
77  A subroutine to read PRISM L1 data
78  '''
79  L1_hdr = OrderedDict()
80  L1_data = OrderedDict()
81 
82  #create list of hdr fnames
83  ls_hdr = []
84  ls_hdr.append(fname_in) #img.hdr
85 
86  if os.path.exists(fname_in.rsplit('_',1)[0] + '_loc_ort.hdr'):
87  ls_hdr.append(fname_in.rsplit('_',1)[0] + '_loc_ort.hdr') #loc_ort.hdr
88  else:
89  ls_hdr.append(fname_in.rsplit('_',1)[0] + '_loc.hdr') #loc.hdr
90 
91  if os.path.exists(fname_in.rsplit('_',1)[0] + '_obs_ort.hdr'):
92  ls_hdr.append(fname_in.rsplit('_',1)[0] + '_obs_ort.hdr') #obs_ort.hdr
93  else:
94  ls_hdr.append(fname_in.rsplit('_',1)[0] + '_obs.hdr') #obs.hdr
95 
96  #create list of bil fnames
97  ls_bil = [fname.rsplit('.')[0] for fname in ls_hdr]
98 
99  for fname_hdr,fname_bil in zip(ls_hdr,ls_bil):
100  if not os.path.exists(fname_hdr):
101  logger.warning('Missing PRISM header file: {:}'.format(fname_hdr))
102 
103  if not os.path.exists(fname_bil):
104  logger.warning('Missing PRISM BIL file: {:}'.format(fname_bil))
105 
106  logger.info('Reading HDR file ' + fname_hdr)
107  [hdr_type, dict_hdr] = rd_prism_hdr(fname_hdr)
108 
109  #save all hdr content
110  L1_hdr[hdr_type] = OrderedDict()
111  for key in dict_hdr:
112  L1_hdr[hdr_type][key] = dict_hdr[key]
113 
114  logger.info('Reading BIL file ' + fname_bil)
115  dict_data = rd_L1B_bil(fname_bil, dict_hdr)
116  L1_data.update(dict_data)
117 
118  return [L1_hdr, L1_data]
119 
120 #==========================================================================================================================================
121 
122 def subsample_spectral(band_data, band_weights):
123  '''
124  a subroutine to subsample via weighted averaging
125  spectral data about a desired band center
126 
127  Inputs: band_data -- 3-dim numpy array of shape-order (number_of_bands(_to_average), number_of_scans, pixels_per_scan)
128  band_weights -- list or 1-dim numpy array of length (number_of_bands(_to_average))
129 
130  Outputs: data -- 2-dim numpy array of shape-order (number_of_scans, pixels_per_scan)
131  width -- floating point value of sub-sampled FWHM value
132  '''
133  width = np.sum(band_weights)
134 
135  data_numerator = np.empty_like(band_data)
136  for i,weight in zip(range(data_numerator.shape[0]), band_weights):
137  data_numerator[i,:,:] = np.multiply(band_data[i,:,:], weight)
138 
139  data = np.divide(np.sum(data_numerator, axis=0), width)
140 
141  return [data, width]
142 
143 #==========================================================================================================================================
144 
145 def resample_prism2oci(bands_prism, widths_prism, lt_prism):
146  '''
147  A subroutine to resample PRISM data to OCI targets to create OCIP data
148  '''
149 
150  #map bands and FWHMs from PRISM to OCI for 350:890nm
151  index_ocip = list(range(0,191))
152  bands_ocip = list(bands_prism[0:191])
153  widths_ocip = list(widths_prism[0:191])
154 
155  #map bands and FWHMs from PRISM to OCI for 940nm
156  lis_940bands = list(range(202,215))
157  band_array = np.ndarray((len(lis_940bands),1,1), dtype='f8')
158  band_array[:,0,0] = [bands_prism[i] for i in lis_940bands]
159  [data,width] = subsample_spectral(band_array, [widths_prism[i] for i in lis_940bands])
160  bands_ocip.append(data[0,0])
161  widths_ocip.append(width)
162 
163  #map bands and FWHMs from PRISM to OCI for 1038nm
164  lis_1038bands = list(range(239,246))
165  band_array = np.ndarray((len(lis_1038bands),1,1), dtype='f8')
166  band_array[:,0,0] = [bands_prism[i] for i in lis_1038bands]
167  [data,width] = subsample_spectral(band_array, [widths_prism[i] for i in lis_1038bands])
168  bands_ocip.append(data[0,0])
169  widths_ocip.append(width)
170 
171  #resample Lt
172  lt_ocip = np.ndarray((len(bands_ocip),lt_prism.shape[1],lt_prism.shape[2]), dtype="f8")
173  lt_ocip[0:len(index_ocip),:,:] = lt_prism[index_ocip,:,:]
174 
175  [data,width] = subsample_spectral(lt_prism[lis_940bands,:,:], [widths_prism[i] for i in lis_940bands])
176  lt_ocip[-2,:,:] = data[:,:]
177 
178  [data,width] = subsample_spectral(lt_prism[lis_1038bands,:,:], [widths_prism[i] for i in lis_1038bands])
179  lt_ocip[-1,:,:] = data[:,:]
180 
181  return [bands_ocip, widths_ocip, lt_ocip]
182 
183 #==========================================================================================================================================
184 
185 def read_OCIP_L1C_CDL(fname_in, num_scans, num_pix, num_bands):
186  '''
187  A subroutine to read a OCIP L1C CDL file and fill the proper dimensions
188  '''
189  with open(fname_in,'r') as fid:
190  lines = fid.readlines()
191 
192  #define dims and chunksizes in CDL file
193  lines = [re.sub('NUMSSCANS', str(num_scans), line) for line in lines]
194  lines = [re.sub('NUMSPIXELS', str(num_pix), line) for line in lines]
195  lines = [re.sub('NUMSBANDS', str(num_bands), line) for line in lines]
196 
197  return lines
198 
199 #==========================================================================================================================================
200 
201 def PRISMmain(args):
202  '''
203  Coordinates calls to PRISM L1 BIL to L1C netCDF conversion process
204  '''
205  #parse input arguments and instantiate logger
206  pArgs = ParseCommandLine(args)
207  mainLogger = SetLogger(pArgs)
208  mainLogger.info("prismbil2oci.py %s" % __version__)
209  ifile = ''.join(pArgs.ifile)
210  ofile = ''.join(pArgs.ofile)
211  cfile = ''.join(pArgs.cdlfile)
212 
213  #load L1 data
214  [dsL1_hdr, dsL1_data] = read_prism_L1(mainLogger, ifile)
215 
216  #resample PRISM band centers and widths to more closely match OCI
217  [bands_ocip, widths_ocip, lt_ocip] = resample_prism2oci([float(d) for d in dsL1_hdr['rdn_img']['wavelength']],
218  [float(d) for d in dsL1_hdr['rdn_img']['fwhm'][:len(dsL1_hdr['rdn_img']['wavelength'])]],
219  dsL1_data['lt'])
220 
221  #read and parse CDL file
222  cdl_lines = read_OCIP_L1C_CDL(cfile, dsL1_data['utc time'].shape[0], dsL1_data['utc time'].shape[1], len(bands_ocip))
223 
224  #mk netCDF4 file via NCGEN subprocess call
225  if '/' in ofile:
226  nc_fname = ofile.rsplit('/',1)[-1]
227  else:
228  nc_fname = ofile
229 
230  mainLogger.info('Creating netCDF4 file {:}'.format(ofile))
231  pid = subprocess.run('ncgen -b -v3 -o ' + ofile, input=''.join(cdl_lines), encoding='ascii',
232  shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
233  if pid.stderr:
234  logger.warning('Error in ncgen call: {:}'.format(pid.stderr))
235 
236  nc_fid = Dataset(ofile, 'a')
237 
238 
239  #fill global attributes
240  mainLogger.info('Filling global attributes')
241  nc_fid.product_name = nc_fname
242  nc_fid.prism_version = dsL1_hdr['rdn_img']['prism_version']
243  dt_now = datetime.now()
244  nc_fid.date_created = dt_now.strftime("%Y-%m-%dT%H:%M:%SZ")
245  dt_start = dsL1_hdr['rdn_img']['start datetime']
246  nc_fid.flight_line = dt_start.strftime("prm%Y%m%dt%H%M%S")
247  nc_fid.time_coverage_start = dt_start.strftime("%Y-%m-%dT%H:%M:%SZ")
248  dt_end = dt_start + timedelta(microseconds=np.nanmax(dsL1_data['utc time']))
249  nc_fid.time_coverage_end = dt_end.strftime("%Y-%m-%dT%H:%M:%SZ")
250  nc_fid.northernmost_latitude = float(np.nanmax(dsL1_data['latitude (wgs-84)']))
251  nc_fid.southernmost_latitude = float(np.nanmin(dsL1_data['latitude (wgs-84)']))
252  nc_fid.easternmost_longitude = float(np.nanmax(dsL1_data['longitude (wgs-84)']))
253  nc_fid.westernmost_longitude = float(np.nanmin(dsL1_data['longitude (wgs-84)']))
254  nc_fid.geospatial_lat_max = float(np.nanmax(dsL1_data['latitude (wgs-84)']))
255  nc_fid.geospatial_lat_min = float(np.nanmin(dsL1_data['latitude (wgs-84)']))
256  nc_fid.geospatial_lon_max = float(np.nanmax(dsL1_data['longitude (wgs-84)']))
257  nc_fid.geospatial_lon_min = float(np.nanmin(dsL1_data['longitude (wgs-84)']))
258  nc_fid.day_night_flag = "Day"
259  nc_fid.earth_sun_distance_correction = float(np.nanmean(dsL1_data['earth-sun distance (au)']))
260 
261  #fill sbp-group vars
262  mainLogger.info('Filling and writing sensor_band_parameters')
263  nc_fid.groups['sensor_band_parameters'].variables['wavelength'][:] = bands_ocip
264  nc_fid.groups['sensor_band_parameters'].variables['fwhm'][:] = widths_ocip
265 
266  #fill sla-group vars
267  mainLogger.info('Filling and writing scan_line_attributes')
268  dsL1_data['scan_start_time'][np.isnan(dsL1_data['scan_start_time'])] = nc_fid.groups['scan_line_attributes'].variables['scan_start_time']._FillValue
269  nc_fid.groups['scan_line_attributes'].variables['scan_start_time'][:] = dsL1_data['scan_start_time']
270 
271  dsL1_data['scan_end_time'][np.isnan(dsL1_data['scan_end_time'])] = nc_fid.groups['scan_line_attributes'].variables['scan_end_time']._FillValue
272  nc_fid.groups['scan_line_attributes'].variables['scan_end_time'][:] = dsL1_data['scan_end_time']
273 
274  #fill nd-group vars
275  mainLogger.info('Filling and writing navigation_data')
276  dsL1_data['longitude (wgs-84)'][np.isnan(dsL1_data['longitude (wgs-84)'])] = nc_fid.groups['navigation_data'].variables['longitude']._FillValue
277  nc_fid.groups['navigation_data'].variables['longitude'][:,:] = dsL1_data['longitude (wgs-84)']
278 
279  dsL1_data['latitude (wgs-84)'][np.isnan(dsL1_data['latitude (wgs-84)'])] = nc_fid.groups['navigation_data'].variables['latitude']._FillValue
280  nc_fid.groups['navigation_data'].variables['latitude'][:,:] = dsL1_data['latitude (wgs-84)']
281 
282  dsL1_data['elevation (m)'][np.isnan(dsL1_data['elevation (m)'])] = nc_fid.groups['navigation_data'].variables['height']._FillValue
283  nc_fid.groups['navigation_data'].variables['height'][:,:] = dsL1_data['elevation (m)']
284 
285  dsL1_data['path length (m)'][np.isnan(dsL1_data['path length (m)'])] = nc_fid.groups['navigation_data'].variables['range']._FillValue
286  nc_fid.groups['navigation_data'].variables['range'][:,:] = dsL1_data['path length (m)']
287 
288  dsL1_data['to-sensor zenith (0 to 90 degrees from zenith)'][np.isnan(dsL1_data['to-sensor zenith (0 to 90 degrees from zenith)'])] = nc_fid.groups['navigation_data'].variables['sensor_zenith']._FillValue
289  nc_fid.groups['navigation_data'].variables['sensor_zenith'][:,:] = dsL1_data['to-sensor zenith (0 to 90 degrees from zenith)']
290 
291  dsL1_data['to-sensor azimuth (0 to 360 degrees cw from n)'][np.isnan(dsL1_data['to-sensor azimuth (0 to 360 degrees cw from n)'])] = nc_fid.groups['navigation_data'].variables['sensor_azimuth']._FillValue
292  nc_fid.groups['navigation_data'].variables['sensor_azimuth'][:,:] = dsL1_data['to-sensor azimuth (0 to 360 degrees cw from n)']
293 
294  dsL1_data['to-sun zenith (0 to 90 degrees from zenith)'][np.isnan(dsL1_data['to-sun zenith (0 to 90 degrees from zenith)'])] = nc_fid.groups['navigation_data'].variables['solar_zenith']._FillValue
295  nc_fid.groups['navigation_data'].variables['solar_zenith'][:,:] = dsL1_data['to-sun zenith (0 to 90 degrees from zenith)']
296 
297  dsL1_data['to-sun azimuth (0 to 360 degrees cw from n)'][np.isnan(dsL1_data['to-sun azimuth (0 to 360 degrees cw from n)'])] = nc_fid.groups['navigation_data'].variables['solar_azimuth']._FillValue
298  nc_fid.groups['navigation_data'].variables['solar_azimuth'][:,:] = dsL1_data['to-sun azimuth (0 to 360 degrees cw from n)']
299 
300  #fill od-group vars
301  mainLogger.info('Filling and writing observation_data')
302  lt_ocip[np.isnan(lt_ocip)] = nc_fid.groups['observation_data'].variables['Lt']._FillValue
303  nc_fid.groups['observation_data'].variables['Lt'][:,:,:] = lt_ocip
304 
305  #close nc file
306  nc_fid.close()
307  mainLogger.info('Successfully created and filled: {:}'.format(ofile))
308 
309  return
310 
311 if __name__ == "__main__":
312  PRISMmain(sys.argv[1:])
def PRISMmain(args)
def read_prism_L1(logger, fname_in)
Definition: prismbil2oci.py:75
list(APPEND LIBS ${PGSTK_LIBRARIES}) add_executable(atteph_info_modis atteph_info_modis.c) target_link_libraries(atteph_info_modis $
Definition: CMakeLists.txt:7
def ParseCommandLine(args)
Definition: prismbil2oci.py:23
def resample_prism2oci(bands_prism, widths_prism, lt_prism)
def subsample_spectral(band_data, band_weights)
const char * str
Definition: l1c_msi.cpp:35
def read_OCIP_L1C_CDL(fname_in, num_scans, num_pix, num_bands)
def SetLogger(pargs)
Definition: prismbil2oci.py:45