3
3
import datetime
4
4
import glob
5
5
import h5py
6
+ import isce3
6
7
import numpy
7
8
import os
8
9
from isce3 .stripmap .readers .l0raw .ALOS .CEOS import ImageFile , LeaderFile
9
- import isce3
10
+ from nisar . antenna . antenna_pattern import CalPath
10
11
from nisar .products .readers .Raw import Raw
12
+ from nisar .products .readers .Raw .Raw import get_rcs2body
11
13
from nisar .workflows .focus import make_doppler_lut
12
14
13
15
def cmdLineParse ():
14
16
'''
15
17
Command line parser.
16
18
'''
17
- parser = argparse .ArgumentParser (description = "Package ALOS L0 stripmap data into NISAR L0B HDF5" )
19
+ parser = argparse .ArgumentParser (description = "Package ALOS L0 stripmap data into NISAR L0B HDF5" ,
20
+ formatter_class = argparse .ArgumentDefaultsHelpFormatter )
18
21
parser .add_argument ('-i' , '--indir' , dest = 'indir' , type = str ,
19
22
help = "Folder containing one ALOS L0 module" ,
20
23
required = True )
@@ -153,6 +156,15 @@ def getset_attitude(group: h5py.Group, ldr: LeaderFile.LeaderFile,
153
156
days = [sv .DayOfYear for sv in ldr .attitude .statevectors ]
154
157
assert all (numpy .diff (days ) >= 0 ), "Unexpected roll over in day of year."
155
158
159
+ # build quaternion for antenna to spacecraft body (RCS to Body) per
160
+ # mechanical boresight angle (MB). Not sure if "NominalOffNadirAngle" is
161
+ # defined in exactly the same way, but it must be close for ALOS since
162
+ # it's steered to zero-Doppler.
163
+ q_rcs2body = get_rcs2body (el_deg = ldr .summary .NominalOffNadirAngle ,
164
+ side = 'right' )
165
+ print (f"Using off-nadir angle { ldr .summary .NominalOffNadirAngle } degrees"
166
+ f" for beam number { ldr .summary .AntennaBeamNumber } ." )
167
+
156
168
times = [] # time stamps, seconds rel orbit epoch
157
169
rpys = [] # (roll, pitch, yaw) Euler angle tuples, degrees
158
170
qs = [] # (q0,q1,q2,q3) quaternion arrays
@@ -165,7 +177,7 @@ def getset_attitude(group: h5py.Group, ldr: LeaderFile.LeaderFile,
165
177
rpys .append (rpy )
166
178
# Use time reference as orbit.
167
179
times .append ((t - orbit .reference_epoch ).total_seconds ())
168
- q = alos_quaternion (t , rpy [::- 1 ], orbit )
180
+ q = alos_quaternion (t , rpy [::- 1 ], orbit ) * q_rcs2body
169
181
qs .append ([q .w , q .x , q .y , q .z ])
170
182
171
183
# Write to HDF5. isce3.core.Quaternion.save_to_h5 doesn't really cut it,
@@ -237,13 +249,38 @@ def constructNISARHDF5(args, ldr):
237
249
inps .create_dataset ('l0aGranules' , data = numpy .string_ ([os .path .basename (args .indir )]))
238
250
239
251
#Start populating telemetry
240
- orbit_group = rrsd .create_group ('telemetry /orbit' )
252
+ orbit_group = rrsd .create_group ('lowRateTelemetry /orbit' )
241
253
orbit = get_alos_orbit (ldr )
242
254
set_h5_orbit (orbit_group , orbit )
243
- attitude_group = rrsd .create_group ("telemetry /attitude" )
255
+ attitude_group = rrsd .create_group ("lowRateTelemetry /attitude" )
244
256
getset_attitude (attitude_group , ldr , orbit )
245
257
246
258
259
+ def makeDummyCalType (n , first_bcal = 0 , first_lcal = 500 , interval = 1000 ):
260
+ '''Create a numpy array suitable for populating calType field.
261
+ '''
262
+ dtype = h5py .enum_dtype (dict (CalPath .__members__ ), basetype = "uint8" )
263
+ x = numpy .zeros (n , dtype = dtype )
264
+ x [:] = CalPath .HPA
265
+ x [first_bcal ::interval ] = CalPath .BYPASS
266
+ x [first_lcal ::interval ] = CalPath .LNA
267
+ return x
268
+
269
+
270
+ def getNominalSpacing (prf , dr , orbit , look_angle , i = 0 ):
271
+ """Return ground spacing along- and across-track
272
+ """
273
+ pos , vel = orbit .position [i ], orbit .velocity [i ]
274
+ vs = numpy .linalg .norm (vel )
275
+ ell = isce3 .core .Ellipsoid ()
276
+ lon , lat , h = ell .xyz_to_lon_lat (pos )
277
+ hdg = isce3 .geometry .heading (lon , lat , vel )
278
+ a = ell .r_dir (hdg , lat )
279
+ ds = vs / prf * a / (a + h )
280
+ dg = dr / numpy .sin (look_angle )
281
+ return ds , dg
282
+
283
+
247
284
def addImagery (h5file , ldr , imgfile , pol ):
248
285
'''
249
286
Populate swaths segment of HDF5 file.
@@ -270,24 +307,26 @@ def addImagery(h5file, ldr, imgfile, pol):
270
307
nPixels = image .description .NumberOfBytesOfSARDataPerRecord // image .description .NumberOfSamplesPerDataGroup
271
308
nLines = image .description .NumberOfSARDataRecords
272
309
310
+ # Figure out nominal ground spacing
311
+ prf = firstrec .PRFInmHz / 1000. / (1 + (ldr .summary .NumberOfSARChannels == 4 ))
312
+ look_angle = numpy .radians (ldr .summary .NominalOffNadirAngle )
313
+ ds , dg = getNominalSpacing (prf , dr , get_alos_orbit (ldr ), look_angle )
314
+ print ('ds, dg =' , ds , dg )
273
315
274
316
freqA = '/science/LSAR/RRSD/swaths/frequencyA'
275
-
276
317
#If this is first pol being written, add common information as well
277
318
if freqA not in fid :
278
319
freqA = fid .create_group (freqA )
279
- freqA .create_dataset ('centerFrequency' , data = SOL / (ldr .summary .RadarWavelengthInm ))
280
- freqA .create_dataset ('rangeBandwidth' , data = ldr .calibration .header .BandwidthInMHz * 1.0e6 )
281
- freqA .create_dataset ('chirpDuration' , data = firstrec .ChirpLengthInns * 1.0e-9 )
282
- freqA .create_dataset ('chirpSlope' , data = - ((freqA ['rangeBandwidth' ][()])/ (freqA ['chirpDuration' ][()])))
283
- freqA .create_dataset ('nominalAcquisitionPRF' , data = firstrec .PRFInmHz / 1000. / (1 + (ldr .summary .NumberOfSARChannels == 4 )))
284
- freqA .create_dataset ('slantRangeSpacing' , data = dr )
285
- freqA .create_dataset ('slantRange' , data = r0 + numpy .arange (nPixels ) * dr )
320
+ freqA .create_dataset ("listOfTxPolarizations" , data = numpy .string_ ([txP ]),
321
+ maxshape = 2 )
286
322
else :
287
323
freqA = fid [freqA ]
288
324
289
- #Add bunch of assertions here if you want to be super sure that values are not different between pols
290
-
325
+ txPolList = freqA ["listOfTxPolarizations" ]
326
+ if not numpy .string_ (txP ) in txPolList :
327
+ assert len (txPolList ) == 1
328
+ txPolList .resize ((2 ,))
329
+ txPolList [1 ] = txP
291
330
292
331
##Now add in transmit specific information
293
332
txgrpstr = '/science/LSAR/RRSD/swaths/frequencyA/tx{0}' .format (txP )
@@ -303,6 +342,20 @@ def addImagery(h5file, ldr, imgfile, pol):
303
342
txgrp .create_dataset ('radarTime' , dtype = 'f8' , shape = (nLines ,))
304
343
txgrp .create_dataset ('rangeLineIndex' , dtype = 'i8' , shape = (nLines ,))
305
344
txgrp .create_dataset ('validSamplesSubSwath1' , dtype = 'i8' , shape = (nLines ,2 ))
345
+ txgrp .create_dataset ('centerFrequency' , data = SOL / (ldr .summary .RadarWavelengthInm ))
346
+ txgrp .create_dataset ('rangeBandwidth' , data = ldr .calibration .header .BandwidthInMHz * 1.0e6 )
347
+ txgrp .create_dataset ('chirpDuration' , data = firstrec .ChirpLengthInns * 1.0e-9 )
348
+ txgrp .create_dataset ('chirpSlope' , data = - ((txgrp ['rangeBandwidth' ][()])/ (txgrp ['chirpDuration' ][()])))
349
+ txgrp .create_dataset ('nominalAcquisitionPRF' , data = prf )
350
+ txgrp .create_dataset ('slantRangeSpacing' , data = dr )
351
+ txgrp .create_dataset ('slantRange' , data = r0 + numpy .arange (nPixels ) * dr )
352
+ txgrp .create_dataset ('listOfTxTRMs' , data = numpy .asarray ([1 ], dtype = 'uint8' ))
353
+ txgrp .create_dataset ('sceneCenterAlongTrackSpacing' , data = ds )
354
+ txgrp .create_dataset ('sceneCenterGroundRangeSpacing' , data = dg )
355
+ # dummy calibration data
356
+ txgrp .create_dataset ('txPhase' , data = numpy .zeros ((nLines ,1 ), dtype = 'f4' ))
357
+ txgrp .create_dataset ('chirpCorrelator' , data = numpy .ones ((nLines ,1 ,3 ), dtype = 'c8' ))
358
+ txgrp .create_dataset ('calType' , data = makeDummyCalType (nLines ))
306
359
else :
307
360
txgrp = fid [txgrpstr ]
308
361
@@ -313,7 +366,14 @@ def addImagery(h5file, ldr, imgfile, pol):
313
366
raise ValueError ('Reparsing polarization {0}. Array already exists {1}' .format (pol , rximgstr ))
314
367
315
368
print ('Dimensions: {0}L x {1}P' .format (nLines , nPixels ))
316
- fid .create_group (rximgstr )
369
+ rxgrp = fid .create_group (rximgstr )
370
+
371
+ # Dummy cal data
372
+ rxgrp .create_dataset ('caltone' , data = numpy .ones ((nLines ,1 ), dtype = 'c8' ))
373
+ rxgrp .create_dataset ('attenuation' , data = numpy .ones ((nLines ,1 ), dtype = 'f4' ))
374
+ rxgrp .create_dataset ('TRMDataWindow' , data = numpy .ones ((nLines ,1 ), dtype = 'u1' ))
375
+ # Create List of RX TRMs
376
+ rxgrp .create_dataset ('listOfRxTRMs' , data = numpy .asarray ([1 ], dtype = 'uint8' ))
317
377
318
378
##Set up BFPQLUT
319
379
assert firstrec .SARRawSignalData .dtype .itemsize <= 2
@@ -372,11 +432,10 @@ def addImagery(h5file, ldr, imgfile, pol):
372
432
if linnum != nLines :
373
433
rec = image .readNextLine ()
374
434
375
-
376
435
if firstInPol :
377
436
#Adjust time records - ALOS provides this only to nearest millisec - not good enough
378
437
tinp = txgrp ['UTCtime' ][:]
379
- prf = freqA ['nominalAcquisitionPRF' ][()]
438
+ prf = txgrp ['nominalAcquisitionPRF' ][()]
380
439
tarr = (tinp - tinp [0 ]) * 1000
381
440
ref = numpy .arange (tinp .size ) / prf
382
441
0 commit comments