ISCE_INSAR/components/isceobj/Sensor/UAVSAR_Stack.py

455 lines
17 KiB
Python

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copyright 2014 California Institute of Technology. ALL RIGHTS RESERVED.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# United States Government Sponsorship acknowledged. This software is subject to
# U.S. export control laws and regulations and has been classified as 'EAR99 NLR'
# (No [Export] License Required except when exporting to an embargoed country,
# end user, or in support of a prohibited end use). By downloading this software,
# the user agrees to comply with all applicable U.S. export laws and regulations.
# The user has the responsibility to obtain export licenses, or other export
# authority as may be required before exporting this software to any 'EAR99'
# embargoed foreign country or citizen of those countries.
#
# Author: Eric Gurrola
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import (print_function, absolute_import,)
from isceobj.Util.py2to3 import *
import logging
import datetime
import math
import isceobj
from isceobj.Scene.Frame import Frame
from isceobj.Orbit.Orbit import StateVector as OrbitStateVector
from isceobj.Planet.Planet import Planet
from isceobj.Planet.AstronomicalHandbook import Const
from isceobj.Sensor import cosar
from isceobj.Util.decorators import pickled, logged
from iscesys import DateTimeUtil as DTU
from iscesys.Component.Component import Component
METADATAFILE = Component.Parameter(
'metadataFile',
public_name='annotation file',
default=None,
type=str,
mandatory=True,
doc="Name of the input annotation file"
)
SEGMENT_INDEX = Component.Parameter(
'segment_index',
public_name='segment index',
default=1,
type=int,
mandatory=False,
doc="The index of the first SLC segment to process"
)
def polyval(coeffs, rho):
v = 0.0
for i, c in enumerate(coeffs):
v += c*rho**i
return v
from isceobj.Sensor.Sensor import Sensor
@pickled
class UAVSAR_Stack(Component):
"""
A class representing a UAVSAR SLC.
"""
family = 'uavsar_stack'
logging_name = 'isce.Sensor.UAVSAR_Stack'
lookMap = {'RIGHT' : -1,
'LEFT' : 1}
parameter_list = (METADATAFILE, SEGMENT_INDEX)
@logged
def __init__(self, name=''):
super().__init__(family=self.family, name=name)
self.frame = Frame()
self.frame.configure()
self._elp = None
self._peg = None
elp = self.elp
return
def _populatePlatform(self, **kwargs):
platform = self.frame.getInstrument().getPlatform()
platform.setMission('UAVSAR')
platform.setPointingDirection(
self.lookMap[self.metadata['Look Direction'].upper()])
platform.setPlanet(Planet(pname="Earth"))
platform.setAntennaLength(self.metadata['Antenna Length'])
return
def _populateInstrument(self, **kwargs):
instrument = self.frame.getInstrument()
instrument.setRadarWavelength(
self.metadata['Center Wavelength'])
instrument.setPulseRepetitionFrequency(
1.0/self.metadata['Average Pulse Repetition Interval'])
instrument.setRangePixelSize(
self.metadata['1x1 SLC Range Pixel Spacing'])
instrument.setAzimuthPixelSize(
self.metadata['1x1 SLC Azimuth Pixel Spacing'])
instrument.setPulseLength(self.metadata['Pulse Length'])
instrument.setChirpSlope(
-self.metadata['Bandwidth']/self.metadata['Pulse Length'])
from isceobj.Constants.Constants import SPEED_OF_LIGHT
instrument.setRangeSamplingRate(
SPEED_OF_LIGHT/2.0/instrument.getRangePixelSize())
instrument.setIncidenceAngle(0.5*(
self.metadata['Minimum Look Angle'] +
self.metadata['Maximum Look Angle']))
return
def _populateFrame(self):
#Get the Start, Mid, and Stop times
import datetime
tStart = datetime.datetime.strptime(
self.metadata['Start Time of Acquisition'],
"%d-%b-%Y %H:%M:%S %Z"
)
tStop = datetime.datetime.strptime(
self.metadata['Stop Time of Acquisition'],
"%d-%b-%Y %H:%M:%S %Z"
)
dtMid = DTU.timeDeltaToSeconds(tStop - tStart)/2.
tMid = tStart + datetime.timedelta(microseconds=int(dtMid*1e6))
frame = self.frame
frame._frameNumber = 1
frame._trackNumber = 1
frame.setSensingStart(tStart)
frame.setSensingStop(tStop)
frame.setSensingMid(tMid)
frame.setNumberOfLines(int(self.metadata['slc_{}_1x1_mag.set_rows'.format(self.segment_index)]))
frame.setNumberOfSamples(int(self.metadata['slc_{}_1x1_mag.set_cols'.format(self.segment_index)]))
frame.setPolarization(self.metadata['Polarization'])
frame.C0 = self.metadata['slc_{}_1x1_mag.col_addr'.format(self.segment_index)]
frame.S0 = self.metadata['Segment {} Data Starting Azimuth'.format(self.segment_index)]
frame.nearLookAngle = self.metadata['Minimum Look Angle']
frame.farLookAngle = self.metadata['Maximum Look Angle']
frame.setStartingRange(self.startingRange)
frame.platformHeight = self.platformHeight
width = frame.getNumberOfSamples()
deltaRange = frame.instrument.getRangePixelSize()
nearRange = frame.getStartingRange()
midRange = nearRange + (width/2.)*deltaRange
frame.setFarRange(nearRange+width*deltaRange)
self.extractDoppler()
frame._ellipsoid = self.elp
frame.peg = self.peg
frame.procVelocity = self.velocity
from isceobj.Location.Coordinate import Coordinate
frame.upperLeftCorner = Coordinate()
#The corner latitude, longitudes are given as a pair
#of values in degrees at each corner (without rdf unit specified)
llC = []
for ic in range(1,5):
key = 'Segment {0} Data Approximate Corner {1}'.format(self.segment_index, ic)
self.logger.info("key = {}".format(key))
self.logger.info("metadata[key] = {}".format(self.metadata[key], type(self.metadata[key])))
llC.append(list(map(float, self.metadata[key].split(','))))
frame.terrainHeight = self.terrainHeight
frame.upperLeftCorner.setLatitude(llC[0][0])
frame.upperLeftCorner.setLongitude(llC[0][1])
frame.upperLeftCorner.setHeight(self.terrainHeight)
frame.upperRightCorner = Coordinate()
frame.upperRightCorner.setLatitude(llC[1][0])
frame.upperRightCorner.setLongitude(llC[1][1])
frame.upperRightCorner.setHeight(self.terrainHeight)
frame.lowerRightCorner = Coordinate()
frame.lowerRightCorner.setLatitude(llC[2][0])
frame.lowerRightCorner.setLongitude(llC[2][1])
frame.lowerRightCorner.setHeight(self.terrainHeight)
frame.lowerLeftCorner = Coordinate()
frame.lowerLeftCorner.setLatitude(llC[3][0])
frame.lowerLeftCorner.setLongitude(llC[3][1])
frame.lowerLeftCorner.setHeight(self.terrainHeight)
frame.nearLookAngle = math.degrees(self.metadata['Minimum Look Angle'])
frame.farLookAngle = math.degrees(self.metadata['Maximum Look Angle'])
return
def _populateFrameSolo(self):
self.logger.info("UAVSAR_Stack._populateFrameSolo")
def _populateExtras(self):
pass
def _populateOrbit(self, **kwargs):
"""
Create the orbit as the reference orbit defined by the peg
"""
numgroup = 1000
prf = self.frame.instrument.getPulseRepetitionFrequency()
daz = self.frame.instrument.getAzimuthPixelSize()
vel = daz * prf
t0 = self.frame.getSensingStart()
nlines = int(( self.frame.getSensingStop() - t0).total_seconds() * prf)
#make sure the elp property has been called
elp = self.elp
orbit = self.frame.getOrbit()
orbit.setOrbitSource('Header')
for i in range(-5*numgroup, int(nlines/numgroup)*numgroup+5*numgroup, numgroup):
delt = int(i * 1.0e6 /prf)
torb = self.frame.getSensingStart() + datetime.timedelta(microseconds=delt)
###Need to compute offset
###While taking into account, rounding off in time
ds = delt*1.0e-6*vel
vec = OrbitStateVector()
vec.setTime( torb )
posSCH = [self.frame.S0 + ds, 0.0, self.platformHeight]
velSCH = [self.velocity, 0., 0.]
posXYZ, velXYZ = elp.schdot_to_xyzdot(posSCH, velSCH)
vec.setPosition(posXYZ)
vec.setVelocity(velXYZ)
orbit.addStateVector(vec)
return
#t0 = (self.frame.getSensingStart() -
#datetime.timedelta(microseconds=delta))
#ds = deltaFactor*self.frame.instrument.getAzimuthPixelSize()
#s0 = self.platformStartingAzimuth - numExtra*ds
#self.logger.info("populateOrbit: frame.sensingStart, frame.sensingStop = ", self.frame.getSensingStart(),
#self.frame.getSensingStop())
#self.logger.info("populateOrbit: deltaFactor, numExtra, dt = ", deltaFactor, numExtra, dt)
#self.logger.info("populateOrbit: t0, startingAzimuth, platformStartingAzimuth, s0, ds = ",
#t0, self.frame.S0, self.platformStartingAzimuth, s0, ds)
#h = self.platformHeight
#v = [self.velocity, 0., 0.]
#self.logger.info("t0, dt = ", t0, dt)
#self.logger.info("s0, ds, h = ", s0, ds, h)
#self.logger.info("elp.pegRadCur = ", self.elp.pegRadCur)
#self.logger.info("v = ", v[0])
#platform = self.frame.getInstrument().getPlatform()
#elp = self.elp #make sure the elp property has been called
#orbit = self.frame.getOrbit()
#orbit.setOrbitSource('Header')
#for i in range(int(self.frame.getNumberOfLines()/deltaFactor)+1000*numExtra+1):
#vec = OrbitStateVector()
#t = t0 + datetime.timedelta(microseconds=int(i*dt*1e6))
#vec.setTime(t)
#posSCH = [s0 + i*ds , 0., h]
#velSCH = v
#posXYZ, velXYZ = self.elp.schdot_to_xyzdot(posSCH, velSCH)
#sch_pos, sch_vel = elp.xyzdot_to_schdot(posXYZ, velXYZ)
#vec.setPosition(posXYZ)
#vec.setVelocity(velXYZ)
#orbit.addStateVector(vec)
#return
def populateMetadata(self):
self._populatePlatform()
self._populateInstrument()
self._populateFrame()
#self.extractDoppler()
self._populateOrbit()
def parse(self):
from iscesys.Parsers import rdf
self.metadata = rdf.parse(self.metadataFile)
self.populateMetadata()
def extractImage(self):
self.parse()
slcImage = isceobj.createSlcImage()
self.slcname = self.metadata['slc_{}_1x1'.format(self.segment_index)]
slcImage.setFilename(self.slcname)
slcImage.setXmin(0)
slcImage.setXmax(self.frame.getNumberOfSamples())
slcImage.setWidth(self.frame.getNumberOfSamples())
slcImage.setAccessMode('r')
self.frame.setImage(slcImage)
return
def extractDoppler(self):
"""
Read doppler values from the doppler file and fit a polynomial
"""
frame = self.frame
instrument = frame.getInstrument()
rho0 = frame.getStartingRange()
drho = instrument.getRangePixelSize() #full res value, not spacing in the dop file
prf = instrument.getPulseRepetitionFrequency()
self.logger.info("extractDoppler: rho0, drho, prf = {}, {}, {}".format(rho0, drho, prf))
dopfile = getattr(self, 'dopplerFile', self.metadata['dop'])
with open(dopfile,'r') as f:
x = f.readlines() #first line is a header
import numpy
z = numpy.array(
[list(map(float, e)) for e in list(map(str.split, x[1:]))]
)
rho = z[:,0]
dop = z[:,1]
#rho0 = rho[0]
#drho = (rho[1] - rho[0])/2.0
rhoi = [(r-rho0)/drho for r in rho]
polydeg = 6 #2 #Quadratic is built in for now
fit = numpy.polynomial.polynomial.polyfit(rhoi, dop, polydeg, rcond=1.e-9,
full=True)
coefs = fit[0]
res2 = fit[1][0] #sum of squared residuals
self.logger.info("coeffs = {}".format(coefs))
self.logger.info("rms residual = {}".format(numpy.sqrt(res2/len(dop))))
with open("dop.txt", 'w') as o:
for i, d in zip(rhoi, dop):
val = polyval(coefs,i)
res = d-val
o.write("{0} {1} {2} {3}\n".format(i, d, val, res))
self.dopplerVals = {'Near':polyval(coefs, 0)} #need this temporarily in this module
self.logger.info("UAVSAR_Stack.extractDoppler: self.dopplerVals = {}".format(self.dopplerVals))
self.logger.info("UAVSAR_Stack.extractDoppler: prf = {}".format(prf))
#The doppler file values are in units rad/m. divide by 2*pi rad/cycle to convert
#to cycle/m. Then multiply by velocity to get Hz and divide by prf for dimensionless
#doppler coefficients
dop_scale = self.velocity/2.0/math.pi
coefs = [x*dop_scale for x in coefs]
#Set the coefs in frame._dopplerVsPixel because that is where DefaultDopp looks for them
self.frame._dopplerVsPixel = coefs
return coefs
@property
def terrainHeight(self):
#The peg point incorporates the actual terrainHeight
return 0.0
@property
def platformHeight(self):
h = self.metadata['Global Average Altitude']
#Reduce the platform height by the terrain height because the
#peg radius of curvature includes the terrain height
h -= self.metadata['Global Average Terrain Height']
return h
@property
def platformStartingAzimuth(self):
azimuth = self.frame.S0
return azimuth
@property
def startingRange(self):
return self.metadata['Image Starting Slant Range']
@property
def squintAngle(self):
"""
Update this to use the sphere rather than planar approximation.
"""
startingRange = self.startingRange
h = self.platformHeight
v = self.velocity
prf = self.frame.getInstrument().getPulseRepetitionFrequency()
wavelength = self.frame.getInstrument().getRadarWavelength()
if h > startingRange:
raise ValueError("Spacecraft Height too large (%s>%s)" %
(h, startingRange))
sinTheta = math.sqrt( 1 - (h/startingRange)**2 )
fd = self.dopplerVals['Near']
sinSquint = fd/(2.0*v*sinTheta)*wavelength
if sinSquint**2 > 1:
raise ValueError(
"Error in One or More of the Squint Calculation Values\n"+
"Doppler Centroid: %s\nVelocity: %s\nWavelength: %s\n" %
(fd, v, wavelength)
)
self.squint = math.degrees(
math.atan2(sinSquint, math.sqrt(1-sinSquint**2))
)
#squint is also required in the frame.
self.frame.squintAngle = math.radians(self.squint)
return self.squint
@property
def heightDt(self):
"""
Delta(height)/Delta(Time) from frame start-time to mid-time
"""
return 0.0
@property
def velocity(self):
v = self.metadata['Average Along Track Velocity']
platform = self.frame.getInstrument().getPlatform()
elp = self.elp
peg = self.peg
scale = (elp.pegRadCur + self.platformHeight)/elp.pegRadCur
ds_ground = self.frame.instrument.getAzimuthPixelSize()
dt = 1.0/self.frame.instrument.getPulseRepetitionFrequency()
v1 = scale*ds_ground/dt
return v1
@property
def elp(self):
if not self._elp:
planet = Planet(pname="Earth")
self._elp = planet.get_elp()
return self._elp
@property
def peg(self):
if not self._peg:
peg = [math.degrees(self.metadata['Peg Latitude']),
math.degrees(self.metadata['Peg Longitude']),
math.degrees(self.metadata['Peg Heading'])]
th = self.metadata['Global Average Terrain Height']
platform = self.frame.getInstrument().getPlatform()
self.elp.setSCH(peg[0], peg[1], peg[2], th)
rc = self.elp.pegRadCur
from isceobj.Location.Peg import Peg
self._peg = Peg(latitude=peg[0], longitude=peg[1], heading=peg[2],
radiusOfCurvature=rc)
self.logger.info("UAVSAR_Stack: peg radius of curvature = {}".format(self.elp.pegRadCur))
self.logger.info("UAVSAR_Stack: terrain height = {}".format(th))
return self._peg