OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
renav_hawkeye.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import argparse
4 import numpy as np
5 import os
6 import sys
7 import pyIGRF
8 from sgp4.api import jday
9 from astropy.time import Time
10 from netCDF4 import Dataset
11 from hawknav.read_adcs import read_adcs_from_l1a
12 from hawknav.tle2orb import tle2orb
13 from hawknav.orb_interp import orb_interp
14 from hawknav.l_sun import l_sun
15 from hawknav.orb2lla import orb2lla
16 from hawknav.ned2ecr import ned2ecr
17 from hawknav.j2000_to_ecr import j2000_to_ecr
18 from hawknav.propagate import propagate
19 from hawknav.qinv import qinv
20 from hawknav.qprod import qprod
21 from hawknav.qmethod import qmethod
22 from hawknav.remake_orbit_objects import remake_orbit_objects
23 from hawknav.drop_orbit_objects import drop_orbit_objects
24 from hawknav.write_ncdf_data_object import write_ncdf_data_object
25 
26 __version__ = '0.21.1_2021-12-07'
27 
28 def renav_hawkeye(l1afile,l1arenav,tlefile,utcpolefile=None,magbiasfix=False,verbose=False):
29  # Program to recompute Hawkeye navigation in L1A file using attitude sensor data
30  # Input:
31  # - l1afile, seahawk L1A file, in netcdf format
32  # - l1arenav, modified seahawk L1A file, in netcdf format
33  # - TLEline1 & TLEline2, TLE lines
34  # - utcpolefile, Earth motion file , 'utcpole.dat'
35  # Output: new data in l1arenav file
36  # Ported from renav_hawkeye.pro by Fred Patt.
37  # Liang Hong, 2/27/2020
38  # Liang Hong, 3/4/2020, replaced geomag70 with pyIGRF lib. Note: original loadCoeffs.py in the package needs to be updated with a correction in LUT index
39  # Liang Hong, 4/2/2020, reduced for loops to improve efficiency
40  # Liang Hong, 12/3/2020, check for a time misalignment between the Sun sensor and gyro/RW data; Sun vectors to correct RW rates; only FSSXP is used;
41  # initial polynomial correction to rwr using the gyro data
42  # Liang Hong, 12/16/2020, correction to the magnetometer data; initial gyro bias to apply only to the data before the onboard gyro temperature correction was applied
43  # Liang Hong, 2/19/2021, added missing group attributes to output l1arenav file
44  # Liang Hong, 2/23/2021, added option for magnetometer bias correction on/off switch
45  # Liang Hong, 4/21/2021, resolved image folding issue in FSS and antenna interference zone, generate FSS error and weighting arrays, avoid invalid FSS
46  # Liang Hong, 8/4/2021, read image data year and date from time_coverage_start instead of image id from file name
47  # Liang Hong, 12/7/2021, changed sun sensor to use calibration sun vector instead of calculated from FSS XP
48 
49  if not utcpolefile:
50  utcpolefile = os.path.join(os.environ['OCVARROOT'],'modis','utcpole.dat')
51 
52  TLEline1 = None
53  TLEline2 = None
54  try:
55  if verbose:
56  print("reading %s" % tlefile)
57  with open(tlefile,'r') as tle:
58  TLEline1 = tle.readline()
59  TLEline2 = tle.readline()
60  TLEline1 = TLEline1.strip()
61  TLEline2 = TLEline2.strip()
62 
63  except:
64  print("Could not read TLE file.")
65  sys.exit(110)
66 
67  nc_fid = Dataset(l1afile, 'r')
68 
69  # Read ADCS data from file
70  print('Running renavigation (VER%s) on %s\n' % (__version__,l1afile))
71  ptime,sunv,mag2,gyro,rw,stime,sunxp,sunzp,sunzn,atime,quat = read_adcs_from_l1a(l1afile) # checked with .pro
72  if len(ptime)<1:
73  print('Error reading input L1A file.')
74  sys.exit(110)
75 
76  # Get date from L1A file
77 
78  t_start= Time(nc_fid.getncattr('time_coverage_start'),format='isot')
79  iyr = int(t_start.yday[0:4])
80  iday = int(t_start.yday[5:8]) # day of year
81 
82  t_start = t_start.unix - 30*60 # in UTC seconds, 30 minutes before image start
83  t_end = Time(nc_fid.getncattr('time_coverage_end'),format='isot').unix + 30*60 # in UTC seconds, 30 minutes after image end
84 
85  # generate ECR orbit data from TLE
86  t_interval = 60 # 1 minute interval in orbit data
87  orb = tle2orb(TLEline1,TLEline2,t_start,t_end,t_interval) # generate ECR data per 1 minunte interval
88 
89  # Check for time misalignment
90  if (stime[0]-ptime[0] > 0.5):
91  print('stime behind')
92  ptime = ptime[1:]
93  gyro = gyro[1:,:]
94  rw = rw[1:,:]
95  mag2 = mag2[1:,:]
96  sunv = sunv[:-1,:]
97  stime = stime[:-1]
98  sunxp = sunxp[:-1,:]
99  quat = quat[1:,:]
100 
101  if (ptime[0]-stime[0] > 0.5):
102  print('ptime behind')
103  stime = stime[1:]
104  sunxp = sunxp[1:,:]
105  ptime = ptime[:-1]
106  sunv = sunv[1:,:]
107  mag2 = mag2[:-1,:]
108  gyro = gyro[:-1,:]
109  rw = rw[:-1,:]
110  quat = quat[:-1,:]
111 
112  nsen = np.size(ptime)
113  nfss = np.size(stime)
114  nsen = np.min([nsen,nfss])
115 
116  # Interpolate orbit
117  osec = (orb[:,0]-iday)*86400.0
118  ptime_for_orb_interp = np.copy(ptime)
119  if (osec[-1]>86400):
120  # possible interpolation over a day
121  ptime_for_orb_interp[np.where(ptime<osec[0])] += 86400
122  pi,vi,orbfl = orb_interp(osec,orb[:,1:4],orb[:,4:7],ptime_for_orb_interp)
123  lon,lat,hgt = orb2lla(pi) # checked with .pro
124 
125  # Generate model mag field data
126  hgt[np.where(hgt>600)] = 600 # geomag only works to 600 km
127  nDecimalYear = Time(nc_fid.getncattr('time_coverage_start'),format='isot').decimalyear
128  bmagg = []
129  #nsen = np.size(ptime)
130  for iRec in range(0,nsen):
131  n2e = ned2ecr(lon[iRec],lat[iRec])
132  geomagout = pyIGRF.igrf_value(lat[iRec],lon[iRec],hgt[iRec],nDecimalYear)[3:6]
133  bmagg.append(np.dot(geomagout,n2e).tolist())
134  magr = np.asarray(bmagg)
135 
136  # Generate model Sun vectors
137  sunr,rs = l_sun(iyr,iday,ptime)
138  sunr = np.transpose(sunr)
139 
140  # Transform model Sun and mag field vectors to ECI
141  suni = np.zeros((nsen,3))
142  magi = np.zeros((nsen,3))
143  posi = np.zeros((nsen,3))
144  veli = np.zeros((nsen,3))
145  ecmat = j2000_to_ecr(iyr*np.ones(np.shape(ptime)),iday*np.ones(np.shape(ptime)),ptime,utcpolefile)
146  suni = np.einsum('ij,ijk->ik',sunr,np.swapaxes(ecmat,1,2))
147  magi = np.einsum('ij,ijk->ik',magr,np.swapaxes(ecmat,1,2))
148  posi = np.einsum('ij,ijk->ik',pi,np.swapaxes(ecmat,1,2))
149  veli = np.einsum('ij,ijk->ik',vi,np.swapaxes(ecmat,1,2))
150 
151  omegae = 7.29211585494e-5
152  veli[:,0] = veli[:,0] - posi[:,1]*omegae
153  veli[:,1] = veli[:,1] + posi[:,0]*omegae
154 
155  # Determine which Sun sensor to use
156  # sunb = np.copy(sunxp)
157  sunb = np.copy(sunv) # changed to use calibrated sun vector, due to FSS outage on Dec. 3, 2021
158 
159  # Correct magnetometer data
160  # seahawk onboard ADPS implemented correction after Feb. 1, 2021 (1612137600)
161  if (t_start<1612137600) or (magbiasfix==True):
162  pm2 = np.array([ [0.969584, 0.022348, 0.077772],
163  [-0.024340, 0.909508, -0.003070],
164  [-0.059786, 0.082971, 0.907878] ])
165  bm2 = [ -2.154e-06, -1.405e-06, 1.664e-06 ]
166  mag2 = np.einsum('ij,ijk->ik',mag2,np.tile(pm2,[nsen,1,1]))
167  mag2 = mag2 - np.tile(bm2,(nsen,1))
168 
169  # Use gyro data to correct RW rate data
170  bmom = [11000,12000,3500]
171  jd,fr = jday(iyr,1,iday,0,0,0)
172  gbias = [0,0,0]
173  if (jd < 2459191): # 2020-12-07 date of gyro temp correction
174  gbias = [-0.0045,-0.0042,0.0045]
175  rwr = rw/bmom
176  gyro = gyro - gbias
177  fit = np.flipud(np.polyfit(np.arange(nsen),rwr-gyro,deg=2))
178  rwr = rwr - np.transpose(np.polynomial.polynomial.polyval(np.arange(nsen), fit))
179 
180  # Use Sun sensor to correct rates
181  cs = np.cross(sunb[1:,:],sunb[0:-1,:])
182  cr = np.cross(sunb[0:-1,:],np.cross(rwr[0:-1,:],sunb[0:-1,:]))
183  serr = np.zeros(nsen-1)+0.001
184  swt = np.zeros(nsen)+100
185  ind_err = np.argwhere(np.sum(np.square(cs-cr),1)>1e-5)
186  serr[ind_err] = 1
187  swt[ind_err] = 0
188  swt[ind_err+1] = 0
189  fit = np.flipud(np.polyfit(np.arange(nsen-1),cr-cs,deg=4,w=1/serr))
190  rwr = rwr - np.transpose(np.polynomial.polynomial.polyval(np.arange(nsen),fit))
191 
192  # Propagate quaternions using RW-generated rates
193  qbp = propagate(rwr)[0:-1,:]
194 
195  # Use propagated quaternions to transform vectors to start time
196  magp = np.zeros((nsen,3))
197  qv = np.zeros((nsen,4))
198  qv[:,0:3] = sunb
199  qi = qinv(qbp)
200  qt1 = qprod(qbp,qv)
201  qt2 = qprod(qt1,qi)
202  sunp = qt2[:,0:3]
203  qv[:,0:3] = mag2
204  qt1 = qprod(qbp,qv)
205  qt2 = qprod(qt1,qi)
206  magp = qt2[:,0:3]
207 
208  # Normalize magnetometer data
209  magpm = np.sqrt(magp[:,0]**2+magp[:,1]**2+magp[:,2]**2)
210  magim = np.sqrt(magi[:,0]**2+magi[:,1]**2+magi[:,2]**2)
211  magp = np.divide(magp.T,magpm).T
212  magi = np.divide(magi.T,magim).T
213 
214  # Compute starting quaternion using q-method and propagate
215  vecb = np.concatenate((sunp,magp))
216  veci = np.concatenate((suni,magi))
217  wgt = np.zeros(2*nsen)
218  wgt[0:nsen] = swt
219  wgt[nsen:2*nsen] = 1
220 
221  ind_invalid = np.argwhere(np.isnan(veci))
222  if len(ind_invalid)>0:
223  ind_invalid_row = np.unique(ind_invalid[:,0])
224  vecb = np.delete(vecb,ind_invalid_row,0)
225  veci = np.delete(veci,ind_invalid_row,0)
226  wgt = np.delete(wgt,ind_invalid_row)
227  q0 = qmethod(vecb,veci,wgt)
228  qbb = np.zeros((nsen,4))
229  qbb[0,:] = q0
230  qbb[1:,:] = qprod(np.tile(q0,(nsen-1,1)),qbp[1:,:])
231 
232  # Re-order quaternion (scalar first)
233  quati = np.zeros((nsen,4))
234  quati[:,0] = qbb[:,3]
235  quati[:,1:4] = qbb[:,0:3]
236 
237  # Generate extended orbit data array
238  norb = nsen + 60
239  otime = np.arange(norb) + ptime[0] - 30
240  otime_for_orb_interp = np.copy(otime)
241  if (osec[-1]>86400):
242  # possible interpolation over a day
243  otime_for_orb_interp[np.where(otime<osec[0])] += 86400
244  pi,vi,orbfl = orb_interp(osec,orb[:,1:4],orb[:,4:7],otime_for_orb_interp)
245  ecmat = j2000_to_ecr(iyr*np.ones(norb),iday*np.ones(norb),otime,utcpolefile)
246  posi = np.einsum('ij,ijk->ik',pi,np.swapaxes(ecmat,1,2))
247  veli = np.einsum('ij,ijk->ik',vi,np.swapaxes(ecmat,1,2))
248 
249  omegae = 7.29211585494e-5
250  veli[:,0] = veli[:,0] - posi[:,1]*omegae
251  veli[:,1] = veli[:,1] + posi[:,0]*omegae
252 
253  # Generate renav L1A file
254  drop_orbit_objects(l1afile,l1arenav)
255 
256  nc_fid = Dataset(l1arenav, 'a')
257  print('Writing updated navigation to %s\n'%l1arenav)
258 
259  ngid = nc_fid.groups['navigation_data']
260  remake_orbit_objects(norb,nc_fid,ngid)
261 
262  # Navigation data
263  write_ncdf_data_object(ngid,'att_time',ptime)
264  write_ncdf_data_object(ngid,'att_quat',quati)
265  write_ncdf_data_object(ngid,'orb_time',otime)
266  write_ncdf_data_object(ngid,'orb_pos',posi)
267  write_ncdf_data_object(ngid,'orb_vel',veli)
268 
269  # Close original NetCDF file.
270  nc_fid.close()
271 
272 if __name__ == "__main__":
273  print("renav_hawkeye version %s"%__version__)
274  # parse command line
275  parser = argparse.ArgumentParser(prog='renav_hawkeye',
276  description='Recompute Hawkeye navigation information for a L1A file using attitude sensor data')
277  parser.add_argument('-v', '--verbose', help='print status messages',
278  action='store_true',default=False)
279  parser.add_argument('ifile', help='name of L1A file to renavigate')
280  parser.add_argument('ofile', help='output filename')
281  parser.add_argument('tle', help='TLE file')
282  parser.add_argument('--utcpole',
283  help='Polar wander file',
284  default=None)
285  parser.add_argument("--magbiasfix",
286  help='Correct magnetometer bias',
287  default=False,
288  dest='magbiasfix',
289  action='store_true')
290 
291  # parser.add_argument('--version', action='version', version="%(prog)s version " +__version__ )
292  args = parser.parse_args()
293 
294  renav_hawkeye(args.ifile,args.ofile,args.tle,utcpolefile=args.utcpole,magbiasfix=args.magbiasfix,verbose=args.verbose)
Definition: l_sun.py:1
int32_t jday(int16_t i, int16_t j, int16_t k)
Definition: jday.c:4
Definition: qinv.py:1
Definition: qprod.py:1
def read_adcs_from_l1a(l1afile)
Definition: read_adcs.py:12
def renav_hawkeye(l1afile, l1arenav, tlefile, utcpolefile=None, magbiasfix=False, verbose=False)