ISCE_INSAR/components/isceobj/Sensor/UAVSAR_Polsar.py

391 lines
14 KiB
Python
Executable File

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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.
#
# Authors: Marco Lavalle, Eric Gurrola
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import (print_function, absolute_import,)
import datetime
import math
import numpy
import isceobj
from isceobj.Scene.Frame import Frame
from isceobj.Orbit.Orbit import StateVector as OrbitStateVector
from isceobj.Planet.Planet import Planet
from iscesys import DateTimeUtil as DTU
from iscesys.Component.Component import Component
from isceobj.Constants.Constants import SPEED_OF_LIGHT
from .Sensor import Sensor
from isceobj.Location.Coordinate import Coordinate
import os
from isceobj.Util.py2to3 import *
METADATAFILE = Component.Parameter(
'metadataFile',
public_name='annotation file',
default=None,
type=str,
mandatory=True,
doc="Name of the input annotation file"
)
OUTPUT = Component.Parameter(
'output',
public_name='OUTPUT',
default='',
type=str,
mandatory=False,
doc="Name of output slc file"
)
class UAVSAR_Polsar(Sensor):
"""
A class representing a UAVSAR Polsar SLC.
"""
family = 'uavsar_polsar'
logging_name = 'isce.Sensor.UAVSAR_Polsar'
lookMap = {'RIGHT': -1,
'LEFT': 1}
parameter_list = (METADATAFILE,) + Sensor.parameter_list
def __init__(self, name=''):
super().__init__(family=self.family, name=name)
self.frame = Frame()
self.frame.configure()
self._elp = None
self._peg = None
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(1.5)
def _populateInstrument(self, **kwargs):
fudgefactor = 1.0 # 1.0/1.0735059946800756
instrument = self.frame.getInstrument()
instrument.setRadarWavelength(
self.metadata['Center Wavelength'])
instrument.setPulseRepetitionFrequency(
fudgefactor*1.0/self.metadata['Average Pulse Repetition Interval'])
instrument.setRangePixelSize(
self.metadata['slc_mag.col_mult'])
instrument.setAzimuthPixelSize(
self.metadata['slc_mag.row_mult'])
instrument.setPulseLength(
self.metadata['Pulse Length'])
instrument.setChirpSlope(
-self.metadata['Bandwidth'] / self.metadata['Pulse Length'])
instrument.setRangeSamplingRate(
SPEED_OF_LIGHT / 2.0 / instrument.getRangePixelSize())
def _populateFrame(self, **kwargs):
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.setSensingStart(tStart)
frame.setSensingStop(tStop)
frame.setSensingMid(tMid)
frame.setNumberOfLines(
int(self.metadata['slc_mag.set_rows']))
frame.setNumberOfSamples(
int(self.metadata['slc_mag.set_cols']))
frame.C0 = self.metadata['slc_mag.col_addr']
frame.S0 = self.metadata['slc_mag.row_addr']
self.extractDoppler()
frame.setStartingRange(self.startingRange)
frame.platformHeight = self.platformHeight
width = frame.getNumberOfSamples()
deltaRange = frame.instrument.getRangePixelSize()
nearRange = frame.getStartingRange()
frame.setFarRange(nearRange+width*deltaRange)
frame._ellipsoid = self.elp
frame.peg = self.peg
frame.procVelocity = self.velocity
frame.terrainHeight = self.terrainHeight
frame.upperLeftCorner = Coordinate()
frame.upperLeftCorner.setLatitude(
math.degrees(self.metadata['Approximate Upper Left Latitude']))
frame.upperLeftCorner.setLongitude(
math.degrees(self.metadata['Approximate Upper Left Longitude']))
frame.upperLeftCorner.setHeight(self.terrainHeight)
frame.upperRightCorner = Coordinate()
frame.upperRightCorner.setLatitude(
math.degrees(self.metadata['Approximate Upper Right Latitude']))
frame.upperRightCorner.setLongitude(
math.degrees(self.metadata['Approximate Upper Right Longitude']))
frame.upperRightCorner.setHeight(self.terrainHeight)
frame.lowerRightCorner = Coordinate()
frame.lowerRightCorner.setLatitude(
math.degrees(self.metadata['Approximate Lower Right Latitude']))
frame.lowerRightCorner.setLongitude(
math.degrees(self.metadata['Approximate Lower Right Longitude']))
frame.lowerRightCorner.setHeight(self.terrainHeight)
frame.lowerLeftCorner = Coordinate()
frame.lowerLeftCorner.setLatitude(
math.degrees(self.metadata['Approximate Lower Left Latitude']))
frame.lowerLeftCorner.setLongitude(
math.degrees(self.metadata['Approximate Lower Left Longitude']))
frame.lowerLeftCorner.setHeight(self.terrainHeight)
def _populateFrameSolo(self):
self.logger.info("UAVSAR_Polsar._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)
ds = delt*1.0e-6*vel
vec = OrbitStateVector()
posSCH = [self.frame.S0 + ds, 0.0, self.platformHeight]
velSCH = [self.velocity, 0., 0.]
posXYZ, velXYZ = elp.schdot_to_xyzdot(posSCH, velSCH)
vec.setTime(torb)
vec.setPosition(posXYZ)
vec.setVelocity(velXYZ)
orbit.addStateVector(vec)
def populateMetadata(self):
self._populatePlatform()
self._populateInstrument()
self._populateFrame()
self._populateOrbit()
def extractImage(self):
from iscesys.Parsers import rdf
self.metadata = rdf.parse(self.metadataFile)
self.populateMetadata()
slcImage = isceobj.createSlcImage()
self.slcname = os.path.join(
os.path.dirname(os.path.abspath(self.metadataFile)),
self.metadata['slc'+self.polarization.upper()])
slcImage.setFilename(self.slcname)
slcImage.setXmin(0)
slcImage.setXmax(self.frame.getNumberOfSamples())
slcImage.setWidth(self.frame.getNumberOfSamples())
slcImage.setAccessMode('r')
slcImage.renderHdr()
self.frame.setImage(slcImage)
def extractDoppler(self):
# Recast the Near, Mid, and Far Reskew Doppler values
# into three RDF records because they were not parsed
# correctly by the RDF parser; it was parsed as a string.
# Use the RDF parser on the individual Doppler values to
# do the unit conversion properly.
# The units, and values parsed from the metadataFile
key = 'Reskew Doppler Near Mid Far'
u = self.metadata.data[key].units.split(',')
v = map(float, self.metadata.data[key].value.split())
k = ["Reskew Doppler "+x for x in ("Near", "Mid", "Far")]
# Use the interactive RDF accumulator to create an RDF object
# for the near, mid, and far Doppler values
from iscesys.Parsers.rdf import iRDF
dop = iRDF.RDFAccumulator()
for z in zip(k, u, v):
dop("%s (%s) = %f" % z)
self.dopplerVals = {}
for r in dop.record_list:
self.dopplerVals[r.key.split()[-1]] = r.field.value
# Quadratic model using Near, Mid, Far range doppler values
# UAVSAR has a subroutine to compute doppler values at each pixel
# that should be used instead.
frame = self.frame
instrument = frame.getInstrument()
width = frame.getNumberOfSamples()
nearRangeBin = 0.
midRangeBin = float(int((width-1.0)/2.0))
farRangeBin = width-1.0
A = numpy.matrix([[1.0, nearRangeBin, nearRangeBin**2],
[1.0, midRangeBin, midRangeBin**2],
[1.0, farRangeBin, farRangeBin**2]])
d = numpy.matrix([self.dopplerVals['Near'],
self.dopplerVals['Mid'],
self.dopplerVals['Far']]).transpose()
coefs = (numpy.linalg.inv(A)*d).transpose().tolist()[0]
prf = instrument.getPulseRepetitionFrequency()
coefs_norm = {'a': coefs[0]/prf,
'b': coefs[1]/prf,
'c': coefs[2]/prf}
self.doppler_coeff = coefs
return coefs_norm
@property
def terrainHeight(self):
# The peg point incorporates the actual terrainHeight
# return self.metadata['Global Average Terrain Height']
return 0.0
@property
def platformHeight(self):
# Reduce the platform height by the terrain height because the
# peg radius of curvature includes the terrain height
h = (self.metadata['Global Average Altitude'] -
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 Range']
@property
def squintAngle(self):
"""
Update this to use the sphere rather than planar approximation.
"""
startingRange = self.startingRange
h = self.platformHeight
v = self.velocity
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))
)
# jng squint is also used later on from the frame, just add it here
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):
platform = self.frame.getInstrument().getPlatform()
elp = platform.getPlanet().get_elp()
peg = self.peg
elp.setSCH(peg.latitude, peg.longitude, peg.heading)
scale = (elp.pegRadCur + self.platformHeight)/elp.pegRadCur
ds_ground = self.frame.instrument.getAzimuthPixelSize()
dt = 1.0/self.frame.instrument.getPulseRepetitionFrequency()
v = scale*ds_ground/dt
return v
@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']
if self.metadata['Mocomp II Applied'] is 'Y':
self.elp.setSCH(peg[0], peg[1], peg[2], th)
else:
self.elp.setSCH(peg[0], peg[1], peg[2], 0)
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_Polsar: peg radius of curvature = {}".format(self.elp.pegRadCur))
self.logger.info("UAVSAR_Polsar: terrain height = {}".format(th))
self.logger.info("UAVSAR_Polsar: mocomp II applied = {}".format(self.metadata['Mocomp II Applied']))
return self._peg