1405 lines
50 KiB
Python
Executable File
1405 lines
50 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
|
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
# 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: Piyush Agram
|
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import xml.etree.ElementTree as ET
|
|
import datetime
|
|
import isceobj
|
|
from .BurstSLC import BurstSLC
|
|
from isceobj.Util import Poly1D, Poly2D
|
|
from isceobj.Planet.Planet import Planet
|
|
from isceobj.Orbit.Orbit import StateVector, Orbit
|
|
from isceobj.Planet.AstronomicalHandbook import Const
|
|
from iscesys.Component.Component import Component
|
|
from iscesys.Component.ProductManager import ProductManager
|
|
from iscesys.DateTimeUtil.DateTimeUtil import DateTimeUtil as DTUtil
|
|
import os
|
|
import glob
|
|
import numpy as np
|
|
import shelve
|
|
|
|
XML_LIST = Component.Parameter('xml',
|
|
public_name = 'xml',
|
|
default = [],
|
|
container = list,
|
|
type = str,
|
|
doc = 'List of input XML files to stitch together')
|
|
|
|
TIFF_LIST = Component.Parameter('tiff',
|
|
public_name = 'tiff',
|
|
default = [],
|
|
container = list,
|
|
type = str,
|
|
doc = 'List of input TIFF files to stitch together')
|
|
|
|
MANIFEST = Component.Parameter('manifest',
|
|
public_name = 'manifest',
|
|
default = [],
|
|
container = list,
|
|
type = str,
|
|
doc = 'Manifest file with IPF version')
|
|
|
|
SAFE_LIST = Component.Parameter('safe',
|
|
public_name = 'safe',
|
|
default = [],
|
|
container = list,
|
|
type = str,
|
|
doc = 'List of safe directories')
|
|
|
|
SWATH_NUMBER = Component.Parameter('swathNumber',
|
|
public_name = 'swath number',
|
|
default = None,
|
|
type = int,
|
|
mandatory = True,
|
|
doc = 'Swath number to process')
|
|
|
|
POLARIZATION = Component.Parameter('polarization',
|
|
public_name = 'polarization',
|
|
default = 'vv',
|
|
type = str,
|
|
mandatory = True,
|
|
doc = 'Polarization')
|
|
|
|
ORBIT_FILE = Component.Parameter('orbitFile',
|
|
public_name = 'orbit file',
|
|
default = None,
|
|
type = str,
|
|
doc = 'External orbit file with state vectors')
|
|
|
|
AUX_FILE = Component.Parameter('auxFile',
|
|
public_name = 'auxiliary file',
|
|
default = None,
|
|
type = str,
|
|
doc = 'External auxiliary file to use for antenna pattern')
|
|
|
|
ORBIT_DIR = Component.Parameter('orbitDir',
|
|
public_name = 'orbit directory',
|
|
default = None,
|
|
type = str,
|
|
doc = 'Directory to search for orbit files')
|
|
|
|
AUX_DIR = Component.Parameter('auxDir',
|
|
public_name = 'auxiliary data directory',
|
|
default = None,
|
|
type = str,
|
|
doc = 'Directory to search for auxiliary data')
|
|
|
|
OUTPUT = Component.Parameter('output',
|
|
public_name = 'output directory',
|
|
default = None,
|
|
type = str,
|
|
doc = 'Directory where bursts get unpacked')
|
|
|
|
ROI = Component.Parameter('regionOfInterest',
|
|
public_name = 'region of interest',
|
|
default = [],
|
|
container = list,
|
|
type = float,
|
|
doc = 'User defined area to crop in SNWE')
|
|
|
|
####List of facilities
|
|
PRODUCT = Component.Facility('product',
|
|
public_name='product',
|
|
module = 'isceobj.Sensor.TOPS',
|
|
factory='createTOPSSwathSLCProduct',
|
|
args = (),
|
|
mandatory = True,
|
|
doc = 'TOPS SLC Swath product populated by the reader')
|
|
|
|
|
|
class Sentinel1(Component):
|
|
"""
|
|
Sentinel-1A TOPS reader
|
|
"""
|
|
|
|
family = 's1atops'
|
|
logging = 'isce.sensor.S1A_TOPS'
|
|
|
|
parameter_list = (XML_LIST,
|
|
TIFF_LIST,
|
|
MANIFEST,
|
|
SAFE_LIST,
|
|
ORBIT_FILE,
|
|
AUX_FILE,
|
|
ORBIT_DIR,
|
|
AUX_DIR,
|
|
OUTPUT,
|
|
ROI,
|
|
SWATH_NUMBER,
|
|
POLARIZATION)
|
|
|
|
facility_list = (PRODUCT,)
|
|
|
|
def __init__(self, name=''):
|
|
super().__init__(family=self.__class__.family, name=name)
|
|
|
|
####Number of swaths
|
|
self.maxSwaths = 3
|
|
|
|
###Variables never meant to be controlled by user
|
|
self._xml_root=None
|
|
self._burstWidth = None ###Common width
|
|
self._burstLength = None ###Common length
|
|
self._numSlices = None ###Number of slides
|
|
self._parsed = False ###If products have been parsed already
|
|
self._tiffSrc = []
|
|
|
|
####Specifically used only for IPF 002.36
|
|
####Scotch tape fix
|
|
self._elevationAngleVsTau = [] ###For storing time samples
|
|
self._Geap = None ###IQ antenna pattern
|
|
self._delta_theta = None ###Elevation angle increment
|
|
|
|
return
|
|
|
|
def validateUserInputs(self):
|
|
'''
|
|
Validate inputs provided by user.
|
|
Populate tiff and xml list using SAFE folder names.
|
|
'''
|
|
import fnmatch
|
|
import zipfile
|
|
|
|
if len(self.xml) == 0:
|
|
if self.swathNumber is None:
|
|
raise Exception('Desired swath number is not provided')
|
|
|
|
if len(self.safe) == 0:
|
|
raise Exception('SAFE directory is not provided')
|
|
elif self.swathNumber not in [1,2,3]:
|
|
raise Exception('Swath number must be one out of [1,2,3]')
|
|
|
|
|
|
elif len(self.tiff) != 0:
|
|
if len(self.xml) != len(self.tiff):
|
|
raise Exception('Number of TIFF and XML files dont match')
|
|
|
|
|
|
####First find annotation file
|
|
####Dont need swath number when driving with xml and tiff file
|
|
if len(self.xml) == 0:
|
|
swathid = 's1?-iw%d'%(self.swathNumber)
|
|
|
|
polid = self.polarization
|
|
|
|
if len(self.xml) == 0:
|
|
match = None
|
|
for dirname in self.safe:
|
|
match = None
|
|
|
|
if dirname.endswith('.zip'):
|
|
pattern = os.path.join('*SAFE','annotation', swathid) + '-slc-' + polid + '*.xml'
|
|
zf = zipfile.ZipFile(dirname, 'r')
|
|
match = fnmatch.filter(zf.namelist(), pattern)
|
|
zf.close()
|
|
|
|
if (len(match) == 0):
|
|
raise Exception('No annotation xml file found in zip file: {0}'.format(dirname))
|
|
|
|
####Add /vsizip at the start to make it a zip file
|
|
self.xml.append('/vsizip/'+os.path.join(dirname, match[0]) )
|
|
|
|
else:
|
|
pattern = os.path.join('annotation',swathid)+'-slc-'+polid+'*.xml'
|
|
match = glob.glob( os.path.join(dirname, pattern))
|
|
|
|
if (len(match) == 0):
|
|
raise Exception('No annotation xml file found in {0}'.format(dirname))
|
|
|
|
self.xml.append(match[0])
|
|
|
|
if len(self.xml) == 0:
|
|
raise Exception('No annotation files found')
|
|
|
|
print('Input XML files: ', self.xml)
|
|
|
|
####Find TIFF file
|
|
if (len(self.tiff) == 0) and (len(self.safe) != 0 ):
|
|
for dirname in self.safe:
|
|
match = None
|
|
if dirname.endswith('.zip'):
|
|
pattern = os.path.join('*SAFE','measurement', swathid) + '-slc-' + polid + '*.tiff'
|
|
zf = zipfile.ZipFile(dirname, 'r')
|
|
match = fnmatch.filter(zf.namelist(), pattern)
|
|
zf.close()
|
|
|
|
if (len(match) == 0):
|
|
raise Exception('No tiff file found in zip file: {0}'.format(dirname))
|
|
|
|
####Add /vsizip at the start to make it a zip file
|
|
self.tiff.append('/vsizip/' + os.path.join(dirname, match[0]) )
|
|
|
|
|
|
else:
|
|
pattern = os.path.join('measurement', swathid) + '-slc-' + polid + '*.tiff'
|
|
match = glob.glob(os.path.join(dirname, pattern))
|
|
|
|
if len(match) == 0 :
|
|
raise Exception('No tiff file found in directory: {0}'.format(dirname))
|
|
|
|
self.tiff.append(match[0])
|
|
|
|
print('Input TIFF files: ', self.tiff)
|
|
|
|
|
|
if len(self.tiff) != 0 :
|
|
if len(self.tiff) != len(self.xml):
|
|
raise Exception('Number of XML and TIFF files dont match')
|
|
|
|
|
|
####Find manifest files
|
|
if len(self.safe) != 0:
|
|
for dirname in self.safe:
|
|
if dirname.endswith('.zip'):
|
|
pattern='*SAFE/manifest.safe'
|
|
zf = zipfile.ZipFile(dirname, 'r')
|
|
match = fnmatch.filter(zf.namelist(), pattern)
|
|
zf.close()
|
|
self.manifest.append('/vsizip/' + os.path.join(dirname, match[0]))
|
|
else:
|
|
self.manifest.append(os.path.join(dirname, 'manifest.safe'))
|
|
|
|
print('Manifest files: ', self.manifest)
|
|
|
|
|
|
####Check bbox
|
|
roi = self.regionOfInterest
|
|
if len(roi) != 0:
|
|
if len(roi) != 4:
|
|
raise Exception('4 floats in SNWE format expected for bbox/ROI')
|
|
|
|
if (roi[0] >= roi[1]) or (roi[2] >= roi[3]):
|
|
raise Exception('Error in bbox definition: SNWE expected')
|
|
|
|
return
|
|
|
|
|
|
def parse(self):
|
|
'''
|
|
Parser for S1A IW data.
|
|
This is meant to only read in the metadata and does not read any imagery.
|
|
Can be used only with the annotation xml files if needed.
|
|
'''
|
|
|
|
####Check user inputs
|
|
self.validateUserInputs()
|
|
|
|
self._numSlices = len(self.xml)
|
|
|
|
if self._numSlices > 1:
|
|
self._parseMultiSlice()
|
|
else:
|
|
self._parseOneSlice()
|
|
|
|
###Set the parsed flag to True
|
|
self._parsed = True
|
|
return
|
|
|
|
|
|
def _parseOneSlice(self):
|
|
'''
|
|
'''
|
|
if self.xml[0].startswith('/vsizip'):
|
|
import zipfile
|
|
parts = self.xml[0].split(os.path.sep)
|
|
|
|
if parts[2] == '':
|
|
parts[2] = os.path.sep
|
|
|
|
zipname = os.path.join(*(parts[2:-3]))
|
|
fname = os.path.join(*(parts[-3:]))
|
|
|
|
zf = zipfile.ZipFile(zipname, 'r')
|
|
xmlstr = zf.read(fname)
|
|
zf.close()
|
|
else:
|
|
with open(self.xml[0],'r') as fid:
|
|
xmlstr = fid.read()
|
|
|
|
|
|
self._xml_root = ET.fromstring(xmlstr)
|
|
numberBursts = self.getNumberOfBurstsFromAnnotation()
|
|
|
|
####Create empty burst SLCs
|
|
for kk in range(numberBursts):
|
|
slc = BurstSLC()
|
|
slc.configure()
|
|
slc.burstNumber = kk+1
|
|
self.product.bursts.append(slc)
|
|
|
|
self.product.numberOfBursts = numberBursts
|
|
|
|
####Populate processing software info
|
|
if len(self.manifest) != 0:
|
|
self.populateIPFVersion()
|
|
|
|
####Populate common metadata
|
|
self.populateCommonMetadata()
|
|
|
|
####Populate specific metadata
|
|
self.populateBurstSpecificMetadata()
|
|
|
|
####Populate orbit information
|
|
###Try and locate an orbit file
|
|
if self.orbitFile is None:
|
|
if self.orbitDir is not None:
|
|
self.orbitFile = s1_findOrbitFile(self.orbitDir,
|
|
self.product.bursts[0].sensingStart,
|
|
self.product.bursts[-1].sensingStop,
|
|
mission = self.product.mission)
|
|
|
|
####Read in the orbits
|
|
if self.orbitFile:
|
|
orb = self.extractPreciseOrbit()
|
|
else:
|
|
orb = self.extractOrbitFromAnnotation()
|
|
|
|
for burst in self.product.bursts:
|
|
if self.orbitFile:
|
|
burst.orbit.setOrbitSource(os.path.basename(self.orbitFile))
|
|
else:
|
|
burst.orbit.setOrbitSource('Annotation')
|
|
|
|
for sv in orb:
|
|
burst.orbit.addStateVector(sv)
|
|
|
|
|
|
####Determine pass direction using Vz
|
|
VV = burst.orbit.interpolateOrbit(burst.sensingMid, method='hermite').getVelocity()
|
|
if VV[2] >= 0:
|
|
burst.passDirection = 'ASCENDING'
|
|
else:
|
|
burst.passDirection = 'DESCENDING'
|
|
|
|
|
|
####If user provided a region of interest
|
|
if len(self.regionOfInterest) != 0:
|
|
self.crop(self.regionOfInterest)
|
|
|
|
return
|
|
|
|
def _parseMultiSlice(self):
|
|
'''
|
|
Responsible for parsing multiple slices meant to be stitched.
|
|
'''
|
|
|
|
slices = []
|
|
relTimes = []
|
|
burstWidths = []
|
|
burstLengths = []
|
|
|
|
for kk in range(self._numSlices):
|
|
|
|
###Create a new reader for one slice only
|
|
aslice = Sentinel1()
|
|
aslice.configure()
|
|
|
|
####Populate all the fields for reader
|
|
aslice.xml = [self.xml[kk]]
|
|
aslice.tiff = [self.tiff[kk]]
|
|
aslice.manifest = [self.manifest[kk]]
|
|
aslice.output = self.output
|
|
aslice.orbitFile = self.orbitFile
|
|
aslice.orbitDir = self.orbitDir
|
|
aslice.regionOfInterest = self.regionOfInterest
|
|
aslice.swathNumber = self.swathNumber
|
|
aslice.parse()
|
|
|
|
####If there are any bursts left after cropping
|
|
if aslice.product.numberOfBursts != 0:
|
|
slices.append(aslice)
|
|
relTimes.append((aslice.product.bursts[0].sensingStart - slices[0].product.bursts[0].sensingStart).total_seconds())
|
|
burstWidths.append(aslice.product.bursts[0].numberOfSamples)
|
|
burstLengths.append(aslice.product.bursts[0].numberOfLines)
|
|
|
|
|
|
####Update number of slices
|
|
self._numSlices = len(slices)
|
|
|
|
if self._numSlices == 0 :
|
|
raise Exception('There is no imagery to extract. Check region of interest and inputs.')
|
|
|
|
self.burstWidth = max(burstWidths)
|
|
self.burstLength = max(burstLengths)
|
|
|
|
if self._numSlices == 1: #If only one remains after crop
|
|
self.product = slices[0].product
|
|
self.product.numberOfBursts = len(self.product.bursts)
|
|
self._tiffSrc = (self.product.numberOfBursts) * (slices[0].tiff)
|
|
self._elevationAngleVsTau = slices[0]._elevationAngleVsTau
|
|
return
|
|
|
|
####If there are multiple slices to combine
|
|
print('Stitching {0} slices together'.format(self._numSlices))
|
|
|
|
###Sort slices by start times
|
|
indices = np.argsort(relTimes)
|
|
|
|
####Start with the first slice
|
|
firstSlice = slices[indices[0]]
|
|
|
|
####Estimate burstinterval
|
|
t0 = firstSlice.product.bursts[0].burstStartUTC
|
|
if len(firstSlice.product.bursts) > 1:
|
|
burstStartInterval = firstSlice.product.bursts[1].burstStartUTC - t0
|
|
elif len(slices) > 1:
|
|
burstStartInterval = slices[indices[1]].product.bursts[0].burstStartUTC - t0
|
|
else:
|
|
raise Exception('Atleast 2 bursts must be present in the cropped region for TOPS processing.')
|
|
|
|
self.product.processingSoftwareVersion = '+'.join(set([x.product.processingSoftwareVersion for x in slices]))
|
|
|
|
if len( self.product.processingSoftwareVersion.split('+')) != 1:
|
|
raise Exception('Trying to combine SLC products from different IPF versions {0}\n'.format(self.product.processingSoftwareVersion) +
|
|
'This is not possible as SLCs are sliced differently with different versions of the processor. \n' +
|
|
'Integer shift between bursts cannot be guaranteed\n'+
|
|
'Exiting .......')
|
|
|
|
|
|
for index in indices:
|
|
aslice = slices[index]
|
|
|
|
offset = np.int(np.rint((aslice.product.bursts[0].burstStartUTC - t0).total_seconds() / burstStartInterval.total_seconds()))
|
|
|
|
for kk in range(aslice.product.numberOfBursts):
|
|
#####Skip appending if burst also exists from previous scene
|
|
if (offset+kk) < len(self.product.bursts):
|
|
continue
|
|
|
|
elif (offset+kk) == len(self.product.bursts):
|
|
self.product.bursts.append(aslice.product.bursts[kk])
|
|
if len(self.tiff):
|
|
self._tiffSrc.append(aslice.tiff[0])
|
|
|
|
self._elevationAngleVsTau.append(aslice._elevationAngleVsTau[kk])
|
|
else:
|
|
print('Offset indices = ', indices)
|
|
raise Exception('There appears to be a gap between slices. Cannot stitch them successfully.')
|
|
|
|
self.product.numberOfBursts = len(self.product.bursts)
|
|
|
|
print('COMBINED VERSION: ', self.product.processingSoftwareVersion)
|
|
self.product.ascendingNodeTime = firstSlice.product.ascendingNodeTime
|
|
self.product.mission = firstSlice.product.mission
|
|
return
|
|
|
|
|
|
def getxmlattr(self, path, key):
|
|
try:
|
|
res = self._xml_root.find(path).attrib[key]
|
|
except:
|
|
raise Exception('Cannot find attribute %s at %s'%(key, path))
|
|
|
|
return res
|
|
|
|
def getxmlvalue(self, path):
|
|
try:
|
|
res = self._xml_root.find(path).text
|
|
except:
|
|
raise Exception('Tag= %s not found'%(path))
|
|
|
|
if res is None:
|
|
raise Exception('Tag = %s not found'%(path))
|
|
|
|
return res
|
|
|
|
def getxmlelement(self, path):
|
|
try:
|
|
res = self._xml_root.find(path)
|
|
except:
|
|
raise Exception('Cannot find path %s'%(path))
|
|
|
|
if res is None:
|
|
raise Exception('Cannot find path %s'%(path))
|
|
|
|
return res
|
|
|
|
def convertToDateTime(self, string):
|
|
dt = datetime.datetime.strptime(string,"%Y-%m-%dT%H:%M:%S.%f")
|
|
return dt
|
|
|
|
def getNumberOfBurstsFromAnnotation(self):
|
|
return int(self.getxmlattr('swathTiming/burstList', 'count'))
|
|
|
|
|
|
def populateCommonMetadata(self):
|
|
"""
|
|
Create metadata objects from the metadata files
|
|
"""
|
|
####Set each parameter one - by - one
|
|
mission = self.getxmlvalue('adsHeader/missionId')
|
|
swath = self.getxmlvalue('adsHeader/swath')
|
|
polarization = self.getxmlvalue('adsHeader/polarisation')
|
|
orbitnumber = int(self.getxmlvalue('adsHeader/absoluteOrbitNumber'))
|
|
frequency = float(self.getxmlvalue('generalAnnotation/productInformation/radarFrequency'))
|
|
passDirection = self.getxmlvalue('generalAnnotation/productInformation/pass')
|
|
|
|
rangeSampleRate = float(self.getxmlvalue('generalAnnotation/productInformation/rangeSamplingRate'))
|
|
rangePixelSize = Const.c/(2.0*rangeSampleRate)
|
|
azimuthPixelSize = float(self.getxmlvalue('imageAnnotation/imageInformation/azimuthPixelSpacing'))
|
|
azimuthTimeInterval = float(self.getxmlvalue('imageAnnotation/imageInformation/azimuthTimeInterval'))
|
|
|
|
lines = int(self.getxmlvalue('swathTiming/linesPerBurst'))
|
|
samples = int(self.getxmlvalue('swathTiming/samplesPerBurst'))
|
|
|
|
slantRangeTime = float(self.getxmlvalue('imageAnnotation/imageInformation/slantRangeTime'))
|
|
startingRange = float(self.getxmlvalue('imageAnnotation/imageInformation/slantRangeTime'))*Const.c/2.0
|
|
incidenceAngle = float(self.getxmlvalue('imageAnnotation/imageInformation/incidenceAngleMidSwath'))
|
|
steeringRate = np.radians(float( self.getxmlvalue('generalAnnotation/productInformation/azimuthSteeringRate')))
|
|
|
|
|
|
prf = float(self.getxmlvalue('generalAnnotation/downlinkInformationList/downlinkInformation/prf'))
|
|
terrainHeight = float(self.getxmlvalue('generalAnnotation/terrainHeightList/terrainHeight/value'))
|
|
|
|
####Sentinel is always right looking
|
|
lookSide = -1
|
|
|
|
###Read ascending node for phase calibration
|
|
ascTime = self.convertToDateTime(self.getxmlvalue('imageAnnotation/imageInformation/ascendingNodeTime'))
|
|
|
|
|
|
####Product parameters
|
|
self.product.ascendingNodeTime = ascTime
|
|
self.product.mission = mission
|
|
self.product.spacecraftName = 'Sentinel-1'
|
|
|
|
for index, burst in enumerate(self.product.bursts):
|
|
burst.numberOfSamples = samples
|
|
burst.numberOfLines = lines
|
|
burst.startingRange = startingRange
|
|
|
|
if mission == 'S1A':
|
|
burst.trackNumber = (orbitnumber-73)%175 + 1
|
|
elif mission == 'S1B':
|
|
burst.trackNumber = (orbitnumber-27)%175 + 1
|
|
else:
|
|
raise ValueError('Encountered unknown mission id {0}'.format(mission))
|
|
|
|
burst.orbitNumber = orbitnumber
|
|
burst.frameNumber = 1 #S1A doesnt appear to have a frame system
|
|
burst.polarization = polarization
|
|
burst.swathNumber = int(swath.strip()[-1])
|
|
burst.passDirection = passDirection
|
|
burst.radarWavelength = Const.c / frequency
|
|
burst.rangePixelSize = rangePixelSize
|
|
burst.azimuthTimeInterval = azimuthTimeInterval
|
|
burst.azimuthSteeringRate = steeringRate
|
|
burst.prf = prf
|
|
burst.terrainHeight = terrainHeight
|
|
burst.rangeSamplingRate = rangeSampleRate
|
|
#add these for doing bandpass filtering, Cunren Liang, 27-FEB-2018
|
|
burst.rangeWindowType = self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/rangeProcessing/windowType')
|
|
burst.rangeWindowCoefficient = float(self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/rangeProcessing/windowCoefficient'))
|
|
burst.rangeProcessingBandwidth = float(self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/rangeProcessing/processingBandwidth'))
|
|
burst.azimuthWindowType = self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/azimuthProcessing/windowType')
|
|
burst.azimuthWindowCoefficient = float(self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/azimuthProcessing/windowCoefficient'))
|
|
burst.azimuthProcessingBandwidth = float(self.getxmlvalue('imageAnnotation/processingInformation/swathProcParamsList/swathProcParams/azimuthProcessing/processingBandwidth'))
|
|
|
|
return
|
|
|
|
|
|
def populateBurstSpecificMetadata(self):
|
|
'''
|
|
Extract burst specific metadata from the xml file.
|
|
'''
|
|
|
|
burstList = self.getxmlelement('swathTiming/burstList')
|
|
for index, burst in enumerate(burstList.getchildren()):
|
|
bb = self.product.bursts[index]
|
|
bb.sensingStart = self.convertToDateTime(burst.find('azimuthTime').text)
|
|
deltaT = datetime.timedelta(seconds=(bb.numberOfLines - 1)*bb.azimuthTimeInterval)
|
|
bb.sensingStop = bb.sensingStart + deltaT
|
|
|
|
bb.burstStartUTC = self.convertToDateTime(burst.find('sensingTime').text)
|
|
deltaT = datetime.timedelta(seconds=(bb.numberOfLines-1)/bb.prf)
|
|
bb.burstStopUTC = bb.burstStartUTC + deltaT
|
|
|
|
firstValidSample = [int(val) for val in burst.find('firstValidSample').text.split()]
|
|
lastValidSample = [int(val) for val in burst.find('lastValidSample').text.split()]
|
|
|
|
first=False
|
|
last=False
|
|
count=0
|
|
for ii, val in enumerate(firstValidSample):
|
|
if (val >= 0) and (not first):
|
|
first = True
|
|
bb.firstValidLine = ii
|
|
|
|
if (val < 0) and (first) and (not last):
|
|
last = True
|
|
bb.numValidLines = ii - bb.firstValidLine
|
|
|
|
lastLine = bb.firstValidLine + bb.numValidLines - 1
|
|
|
|
bb.firstValidSample = max(firstValidSample[bb.firstValidLine], firstValidSample[lastLine])
|
|
lastSample = min(lastValidSample[bb.firstValidLine], lastValidSample[lastLine])
|
|
|
|
bb.numValidSamples = lastSample - bb.firstValidSample
|
|
|
|
####Read in fm rates separately
|
|
fmrateList = self.getxmlelement('generalAnnotation/azimuthFmRateList')
|
|
fmRates = []
|
|
for index, burst in enumerate(fmrateList.getchildren()):
|
|
r0 = 0.5 * Const.c * float(burst.find('t0').text)
|
|
try:
|
|
c0 = float(burst.find('c0').text)
|
|
c1 = float(burst.find('c1').text)
|
|
c2 = float(burst.find('c2').text)
|
|
coeffs = [c0,c1,c2]
|
|
except AttributeError:
|
|
coeffs = [float(val) for val in burst.find('azimuthFmRatePolynomial').text.split()]
|
|
|
|
refTime = self.convertToDateTime(burst.find('azimuthTime').text)
|
|
poly = Poly1D.Poly1D()
|
|
poly.initPoly(order=len(coeffs)-1)
|
|
poly.setMean(r0)
|
|
poly.setNorm(0.5*Const.c)
|
|
poly.setCoeffs(coeffs)
|
|
|
|
fmRates.append((refTime, poly))
|
|
|
|
for index, burst in enumerate(self.product.bursts):
|
|
|
|
dd = [ np.abs((burst.sensingMid - val[0]).total_seconds()) for val in fmRates]
|
|
|
|
arg = np.argmin(dd)
|
|
burst.azimuthFMRate = fmRates[arg][1]
|
|
|
|
# print('FM rate matching: Burst %d to Poly %d'%(index, arg))
|
|
|
|
|
|
|
|
dcList = self.getxmlelement('dopplerCentroid/dcEstimateList')
|
|
dops = [ ]
|
|
for index, burst in enumerate(dcList.getchildren()):
|
|
|
|
r0 = 0.5 * Const.c* float(burst.find('t0').text)
|
|
refTime = self.convertToDateTime(burst.find('azimuthTime').text)
|
|
coeffs = [float(val) for val in burst.find('dataDcPolynomial').text.split()]
|
|
poly = Poly1D.Poly1D()
|
|
poly.initPoly(order=len(coeffs)-1)
|
|
poly.setMean(r0)
|
|
poly.setNorm(0.5*Const.c)
|
|
poly.setCoeffs(coeffs)
|
|
|
|
dops.append((refTime, poly))
|
|
|
|
for index, burst in enumerate(self.product.bursts):
|
|
|
|
dd = [np.abs((burst.sensingMid - val[0]).total_seconds()) for val in dops]
|
|
|
|
arg = np.argmin(dd)
|
|
burst.doppler = dops[arg][1]
|
|
|
|
# print('Doppler matching: Burst %d to Poly %d'%(index, arg))
|
|
|
|
####Specifically for IPF 002.36
|
|
if self.product.processingSoftwareVersion == '002.36':
|
|
eapList = self.getxmlelement('antennaPattern/antennaPatternList')
|
|
eaps = []
|
|
|
|
for index, burst in enumerate(eapList.getchildren()):
|
|
refTime = self.convertToDateTime(burst.find('azimuthTime').text)
|
|
taus = [float(val) for val in burst.find('slantRangeTime').text.split()]
|
|
angs = [float(val) for val in burst.find('elevationAngle').text.split()]
|
|
eaps.append((refTime, (taus,angs)))
|
|
|
|
for index, burst in enumerate(self.product.bursts):
|
|
dd = [np.abs((burst.sensingMid - val[0]).total_seconds()) for val in eaps]
|
|
|
|
arg = np.argmin(dd)
|
|
self._elevationAngleVsTau.append(eaps[arg][1])
|
|
else:
|
|
for index, burst in enumerate(self.product.bursts):
|
|
self._elevationAngleVsTau.append(None)
|
|
|
|
|
|
|
|
def populateIPFVersion(self):
|
|
'''
|
|
Get IPF version from the manifest file.
|
|
'''
|
|
|
|
try:
|
|
if self.manifest[0].startswith('/vsizip'):
|
|
import zipfile
|
|
parts = self.manifest[0].split(os.path.sep)
|
|
if parts[2] == '':
|
|
parts[2] = os.path.sep
|
|
zipname = os.path.join(*(parts[2:-2]))
|
|
fname = os.path.join(*(parts[-2:]))
|
|
print('MANS: ', zipname, fname)
|
|
|
|
zf = zipfile.ZipFile(zipname, 'r')
|
|
xmlstr = zf.read(fname)
|
|
|
|
else:
|
|
with open(self.manifest[0], 'r') as fid:
|
|
xmlstr = fid.read()
|
|
|
|
####Setup namespace
|
|
nsp = "{http://www.esa.int/safe/sentinel-1.0}"
|
|
|
|
root = ET.fromstring(xmlstr)
|
|
|
|
elem = root.find('.//metadataObject[@ID="processing"]')
|
|
|
|
rdict = elem.find('.//xmlData/' + nsp + 'processing/' + nsp + 'facility').attrib
|
|
self.product.processingFacility = rdict['site'] +', '+ rdict['country']
|
|
|
|
rdict = elem.find('.//xmlData/' + nsp + 'processing/' + nsp + 'facility/' + nsp + 'software').attrib
|
|
|
|
self.product.processingSystem = rdict['name']
|
|
self.product.processingSoftwareVersion = rdict['version']
|
|
print('Setting IPF version to : ', self.product.processingSoftwareVersion)
|
|
|
|
except: ###Not a critical error ... continuing
|
|
print('Could not read version number successfully from manifest file: ', self.manifest)
|
|
pass
|
|
|
|
return
|
|
|
|
|
|
def extractOrbitFromAnnotation(self):
|
|
'''
|
|
Extract orbit information from xml node.
|
|
'''
|
|
node = self._xml_root.find('generalAnnotation/orbitList')
|
|
|
|
print('Extracting orbit from annotation XML file')
|
|
frameOrbit = Orbit()
|
|
frameOrbit.configure()
|
|
|
|
for child in node.getchildren():
|
|
timestamp = self.convertToDateTime(child.find('time').text)
|
|
pos = []
|
|
vel = []
|
|
posnode = child.find('position')
|
|
velnode = child.find('velocity')
|
|
for tag in ['x','y','z']:
|
|
pos.append(float(posnode.find(tag).text))
|
|
|
|
for tag in ['x','y','z']:
|
|
vel.append(float(velnode.find(tag).text))
|
|
|
|
vec = StateVector()
|
|
vec.setTime(timestamp)
|
|
vec.setPosition(pos)
|
|
vec.setVelocity(vel)
|
|
frameOrbit.addStateVector(vec)
|
|
|
|
|
|
#####Orbits provided in annotation files are not InSAR-grade
|
|
#####These also need extensions for interpolation to work
|
|
|
|
return frameOrbit
|
|
|
|
def extractPreciseOrbit(self, margin=60.0):
|
|
'''
|
|
Extract precise orbit from given Orbit file.
|
|
'''
|
|
try:
|
|
fp = open(self.orbitFile,'r')
|
|
except IOError as strerr:
|
|
print("IOError: %s" % strerr)
|
|
return
|
|
|
|
_xml_root = ET.ElementTree(file=fp).getroot()
|
|
|
|
node = _xml_root.find('Data_Block/List_of_OSVs')
|
|
|
|
print('Extracting orbit from Orbit File: ', self.orbitFile)
|
|
orb = Orbit()
|
|
orb.configure()
|
|
|
|
margin = datetime.timedelta(seconds=margin)
|
|
tstart = self.product.bursts[0].sensingStart - margin
|
|
tend = self.product.bursts[-1].sensingStop + margin
|
|
|
|
for child in node.getchildren():
|
|
timestamp = self.convertToDateTime(child.find('UTC').text[4:])
|
|
|
|
if (timestamp >= tstart) and (timestamp < tend):
|
|
|
|
pos = []
|
|
vel = []
|
|
|
|
for tag in ['VX','VY','VZ']:
|
|
vel.append(float(child.find(tag).text))
|
|
|
|
for tag in ['X','Y','Z']:
|
|
pos.append(float(child.find(tag).text))
|
|
|
|
###Warn if state vector quality is not nominal
|
|
quality = child.find('Quality').text.strip()
|
|
if quality != 'NOMINAL':
|
|
print('WARNING: State Vector at time {0} tagged as {1} in orbit file {2}'.format(timestamp, quality, self.orbitFile))
|
|
|
|
vec = StateVector()
|
|
vec.setTime(timestamp)
|
|
vec.setPosition(pos)
|
|
vec.setVelocity(vel)
|
|
orb.addStateVector(vec)
|
|
|
|
fp.close()
|
|
|
|
return orb
|
|
|
|
def extractCalibrationPattern(self):
|
|
'''
|
|
Read the AUX CAL file for elevation angle antenna pattern.
|
|
'''
|
|
burst = self.product.bursts[0]
|
|
|
|
Geap_IQ = None
|
|
|
|
print("extracting aux from: " + self.auxFile)
|
|
fp = open(self.auxFile,'r')
|
|
xml_root = ET.ElementTree(file=fp).getroot()
|
|
res = xml_root.find('calibrationParamsList/calibrationParams')
|
|
paramsList = xml_root.find('calibrationParamsList')
|
|
for par in (paramsList.getchildren()):
|
|
if (par.find('swath').text.strip() == ('IW'+str(burst.swathNumber))) and (par.find('polarisation').text == burst.polarization):
|
|
self._delta_theta = float(par.find('elevationAntennaPattern/elevationAngleIncrement').text)
|
|
Geap_IQ = [float(val) for val in par.find('elevationAntennaPattern/values').text.split()]
|
|
|
|
len(Geap_IQ)
|
|
I = np.array(Geap_IQ[0::2])
|
|
Q = np.array(Geap_IQ[1::2])
|
|
self._Geap = I[:]+Q[:]*1j # Complex vector of Elevation Antenna Pattern
|
|
|
|
return
|
|
|
|
def extractImage(self, virtual=False):
|
|
"""
|
|
Use gdal python bindings to extract image
|
|
"""
|
|
try:
|
|
from osgeo import gdal
|
|
except ImportError:
|
|
raise Exception('GDAL python bindings not found. Need this for RSAT2/ TandemX / Sentinel1A.')
|
|
|
|
if self.output is None:
|
|
raise Exception('No output directory specified')
|
|
|
|
if not self._parsed:
|
|
self.parse()
|
|
|
|
numberBursts = len(self.product.bursts)
|
|
if (numberBursts == 0):
|
|
raise Exception('NODATA: No bursts to extract')
|
|
|
|
if '002.36' in self.product.processingSoftwareVersion:
|
|
'''Range dependent correction needed.'''
|
|
|
|
if self.auxFile is None:
|
|
self.auxFile = s1_findAuxFile(self.auxDir,self.product.bursts[numberBursts//2].sensingMid,
|
|
mission = self.product.mission)
|
|
|
|
if self.auxFile is None:
|
|
print('******************************')
|
|
print('Warning: Strongly recommend using auxiliary information')
|
|
print(' when using products generated with IPF 002.36')
|
|
print('******************************')
|
|
|
|
|
|
####These get set if stitching multiple slices
|
|
####Slices can have different burst dimensions
|
|
width = self._burstWidth
|
|
length = self._burstLength
|
|
|
|
|
|
####Check if aux file corrections are needed
|
|
useAuxCorrections = False
|
|
if ('002.36' in self.product.processingSoftwareVersion) and (self.auxFile is not None):
|
|
useAuxCorrections = True
|
|
|
|
|
|
|
|
###If not specified, for single slice, use width and length from first burst
|
|
if width is None:
|
|
width = self.product.bursts[0].numberOfSamples
|
|
|
|
if length is None:
|
|
length = self.product.bursts[0].numberOfLines
|
|
|
|
os.makedirs(self.output, exist_ok=True)
|
|
|
|
prevTiff = None
|
|
for index, burst in enumerate(self.product.bursts):
|
|
|
|
####tiff for single slice
|
|
if (len(self._tiffSrc) == 0) and (len(self.tiff)==1):
|
|
tiffToRead = self.tiff[0]
|
|
else: ##tiffSrc for multi slice
|
|
tiffToRead = self._tiffSrc[index]
|
|
|
|
|
|
###To minimize reads and speed up
|
|
if tiffToRead != prevTiff:
|
|
src=None
|
|
band=None
|
|
src = gdal.Open(tiffToRead, gdal.GA_ReadOnly)
|
|
fullWidth = src.RasterXSize
|
|
fullLength = src.RasterYSize
|
|
band = src.GetRasterBand(1)
|
|
prevTiff = tiffToRead
|
|
|
|
outfile = os.path.join(self.output, 'burst_%02d'%(index+1) + '.slc')
|
|
originalWidth = burst.numberOfSamples
|
|
originalLength = burst.numberOfLines
|
|
|
|
####Use burstnumber to look into tiff file
|
|
####burstNumber still refers to original burst in slice
|
|
lineOffset = (burst.burstNumber-1) * burst.numberOfLines
|
|
|
|
###We are doing this before we extract any data because
|
|
###renderHdr also calls renderVRT and for virtual calls
|
|
###we will overwrite the VRT.
|
|
###Ideally, when we move to a single VRT interface, this
|
|
###will occur later
|
|
####Render ISCE XML
|
|
slcImage = isceobj.createSlcImage()
|
|
slcImage.setByteOrder('l')
|
|
slcImage.setFilename(outfile)
|
|
slcImage.setAccessMode('read')
|
|
slcImage.setWidth(width)
|
|
slcImage.setLength(length)
|
|
slcImage.setXmin(0)
|
|
slcImage.setXmax(width)
|
|
slcImage.renderHdr()
|
|
burst.image = slcImage
|
|
|
|
|
|
###When you need data actually written as a burst file.
|
|
if useAuxCorrections or (not virtual):
|
|
###Write original SLC to file
|
|
fid = open(outfile, 'wb')
|
|
|
|
|
|
|
|
###Read whole burst for debugging. Only valid part is used.
|
|
data = band.ReadAsArray(0, lineOffset, burst.numberOfSamples, burst.numberOfLines)
|
|
|
|
###Create output array and copy in valid part only
|
|
###Easier then appending lines and columns.
|
|
outdata = np.zeros((length,width), dtype=np.complex64)
|
|
outdata[burst.firstValidLine:burst.lastValidLine, burst.firstValidSample:burst.lastValidSample] = data[burst.firstValidLine:burst.lastValidLine, burst.firstValidSample:burst.lastValidSample]
|
|
|
|
print('Read outdata')
|
|
###################################################################################
|
|
#Check if IPF version is 2.36 we need to correct for the Elevation Antenna Pattern
|
|
if (useAuxCorrections) and (self._elevationAngleVsTau[index] is not None):
|
|
|
|
print('The IPF version is 2.36. Correcting the Elevation Antenna Pattern ...')
|
|
|
|
self.extractCalibrationPattern()
|
|
|
|
Geap = self.computeElevationAntennaPatternCorrection(burst, index)
|
|
|
|
for i in range(burst.firstValidLine, burst.lastValidLine):
|
|
outdata[i, burst.firstValidSample:burst.lastValidSample] = outdata[i, burst.firstValidSample:burst.lastValidSample]/Geap[burst.firstValidSample:burst.lastValidSample]
|
|
########################
|
|
print('Normalized')
|
|
|
|
outdata.tofile(fid)
|
|
fid.close()
|
|
|
|
else: ####VRT to point to the original file.
|
|
|
|
createBurstVRT(tiffToRead, fullWidth, fullLength,
|
|
lineOffset,burst,
|
|
width, length, outfile+'.vrt')
|
|
|
|
#Updated width and length to match extraction
|
|
burst.numberOfSamples = width
|
|
burst.numberOfLines = length
|
|
print('Updating burst number from {0} to {1}'.format(burst.burstNumber, index+1))
|
|
burst.burstNumber = index + 1
|
|
|
|
|
|
src=None
|
|
band=None
|
|
|
|
####Dump the product
|
|
pm = ProductManager()
|
|
pm.configure()
|
|
|
|
outxml = self.output
|
|
if outxml.endswith('/'):
|
|
outxml = outxml[:-1]
|
|
|
|
pm.dumpProduct(self.product, os.path.join(outxml + '.xml'))
|
|
|
|
return
|
|
|
|
def computeAzimuthCarrier(self, burst, offset=0.0, position=None):
|
|
'''
|
|
Returns the ramp function as a numpy array.
|
|
|
|
Straight from S1A documentation.
|
|
'''
|
|
|
|
####Magnitude of velocity
|
|
Vs = np.linalg.norm(burst.orbit.interpolateOrbit(burst.sensingMid, method='hermite').getVelocity())
|
|
|
|
####Steering rate component
|
|
Ks = 2 * Vs * burst.azimuthSteeringRate / burst.radarWavelength
|
|
|
|
|
|
####If user does not provide specific locations to compute ramp at.
|
|
if position is None:
|
|
rng = np.arange(burst.numberOfSamples) * burst.rangePixelSize + burst.startingRange
|
|
|
|
eta =( np.arange(0, burst.numberOfLines) - (burst.numberOfLines//2)) * burst.azimuthTimeInterval + offset * burst.azimuthTimeInterval
|
|
|
|
f_etac = burst.doppler(rng)
|
|
Ka = burst.azimuthFMRate(rng)
|
|
|
|
eta_ref = (burst.doppler(burst.startingRange) / burst.azimuthFMRate(burst.startingRange) ) - (f_etac / Ka)
|
|
|
|
Kt = Ks / (1.0 - Ks/Ka)
|
|
|
|
carr = np.pi * Kt[None,:] * ((eta[:,None] - eta_ref[None,:])**2)
|
|
|
|
else:
|
|
####If user provides specific locations to compute ramp at.
|
|
####y and x need to be zero index
|
|
y, x = position
|
|
|
|
eta = (y - (burst.numberOfLines//2)) * burst.azimuthTimeInterval + offset * burst.azimuthTimeInterval
|
|
rng = burst.startingRange + x * burst.rangePixelSize
|
|
f_etac = burst.doppler(rng)
|
|
Ka = burst.azimuthFMRate(rng)
|
|
|
|
eta_ref = (burst.doppler(burst.startingRange) / burst.azimuthFMRate(burst.startingRange)) - (f_etac / Ka)
|
|
|
|
Kt = Ks / (1.0 - Ks/Ka)
|
|
|
|
carr = np.pi * Kt * ((eta - eta_ref)**2)
|
|
|
|
return carr
|
|
|
|
def computeElevationAntennaPatternCorrection(self,burst,index):
|
|
'''
|
|
Use scipy for antenna pattern interpolation.
|
|
'''
|
|
from scipy.interpolate import interp1d
|
|
|
|
eta_anx = self.product.ascendingNodeTime
|
|
Ns = burst.numberOfSamples
|
|
fs = burst.rangeSamplingRate
|
|
eta_start = burst.sensingStart
|
|
tau0 = 2 * burst.startingRange / Const.c
|
|
|
|
tau_sub, theta_sub = self._elevationAngleVsTau[index]
|
|
tau_sub = np.array(tau_sub)
|
|
theta_sub = np.array(theta_sub)
|
|
|
|
Nelt = np.shape(self._Geap)[0]
|
|
#########################
|
|
# Vector of elevation angle in antenna frame
|
|
theta_AM = np.arange(-(Nelt-1.)/2,(Nelt-1.)/2+1)*self._delta_theta
|
|
########################
|
|
delta_anx = (eta_start - eta_anx).total_seconds()
|
|
|
|
theta_offnadir = s1_anx2roll(delta_anx)
|
|
theta_eap = theta_AM + theta_offnadir
|
|
########################
|
|
#interpolate the 2-way complex EAP
|
|
tau = tau0 + np.arange(Ns)/fs
|
|
|
|
theta = np.interp(tau, tau_sub, theta_sub)
|
|
|
|
f2 = interp1d(theta_eap,self._Geap)
|
|
Geap_interpolated = f2(theta)
|
|
phi_EAP = np.angle(Geap_interpolated)
|
|
cJ = np.complex64(1.0j)
|
|
GEAP = np.exp(cJ * phi_EAP)
|
|
return GEAP
|
|
|
|
|
|
def computeRamp(self, burst, offset=0.0, position=None):
|
|
'''
|
|
Compute the phase ramp.
|
|
'''
|
|
cJ = np.complex64(1.0j)
|
|
carr = self.computeAzimuthCarrier(burst,offset=offset, position=position)
|
|
ramp = np.exp(-cJ * carr)
|
|
return ramp
|
|
|
|
def crop(self, bbox):
|
|
'''
|
|
Crop a given slice with a user provided bbox (SNWE).
|
|
'''
|
|
|
|
from iscesys.Component import createTraitSeq
|
|
|
|
def overlap(box1,box2):
|
|
'''
|
|
Overlapping rectangles overlap both horizontally & vertically
|
|
'''
|
|
hoverlaps = True
|
|
voverlaps = True
|
|
|
|
if (box1[2] >= box2[3]) or (box1[3] <= box2[2]):
|
|
hoverlaps = False
|
|
|
|
if (box1[1] <= box2[0]) or (box1[0] >= box2[1]):
|
|
voverlaps = False
|
|
|
|
return hoverlaps and voverlaps
|
|
|
|
|
|
cropList = createTraitSeq('burst')
|
|
tiffList = []
|
|
eapList = []
|
|
|
|
print('Number of Bursts before cropping: ', len(self.product.bursts))
|
|
|
|
###For each burst
|
|
for ind, burst in enumerate(self.product.bursts):
|
|
burstBox = burst.getBbox()
|
|
|
|
#####If it overlaps, keep the burst
|
|
if overlap(burstBox, bbox):
|
|
cropList.append(burst)
|
|
if len(self._tiffSrc):
|
|
tiffList.append(self._tiffSrc[ind])
|
|
eapList.append(self._elevationAngleVsTau[ind])
|
|
|
|
|
|
####Actual cropping
|
|
|
|
self.product.bursts = cropList #self.product.bursts[minInd:maxInd]
|
|
self.product.numberOfBursts = len(self.product.bursts)
|
|
|
|
self._tiffSrc = tiffList
|
|
self._elevationAngleVsTau = eapList
|
|
print('Number of Bursts after cropping: ', len(self.product.bursts))
|
|
|
|
return
|
|
|
|
#################
|
|
'''
|
|
Sentinel-1A specific utilities.
|
|
'''
|
|
#################
|
|
|
|
def s1_findAuxFile(auxDir, timeStamp, mission='S1A'):
|
|
'''
|
|
Find appropriate auxiliary information file based on time stamps.
|
|
'''
|
|
|
|
if auxDir is None:
|
|
return
|
|
|
|
datefmt = "%Y%m%dT%H%M%S"
|
|
|
|
match = []
|
|
|
|
files = glob.glob(os.path.join(auxDir, mission+'_AUX_CAL_*'))
|
|
|
|
###List all AUX files
|
|
### Bugfix: Bekaert David [DB 03/2017] : Give the latest generated AUX file
|
|
for result in files:
|
|
fields = result.split('_')
|
|
|
|
taft = datetime.datetime.strptime(fields[-1][1:16], datefmt)
|
|
tbef = datetime.datetime.strptime(fields[-2][1:16], datefmt)
|
|
|
|
##### Get all AUX files defined prior to the acquisition
|
|
if (tbef <= timeStamp) and (taft >= timeStamp):
|
|
match.append((result, abs((timeStamp-taft).total_seconds())))
|
|
|
|
##### Return the latest generated AUX file
|
|
if len(match) != 0:
|
|
bestmatch = max(match, key = lambda x: x[1])
|
|
if len(match) >= 1:
|
|
return os.path.join(bestmatch[0], 'data', mission.lower()+'-aux-cal.xml')
|
|
|
|
|
|
if len(match) == 0:
|
|
print('******************************************')
|
|
print('Warning: Aux file requested but no suitable auxiliary file found.')
|
|
print('******************************************')
|
|
|
|
return None
|
|
|
|
def s1_findOrbitFile(orbitDir, tstart, tstop, mission='S1A'):
|
|
'''
|
|
Find correct orbit file in the orbit directory.
|
|
'''
|
|
|
|
datefmt = "%Y%m%dT%H%M%S"
|
|
types = ['POEORB', 'RESORB']
|
|
match = []
|
|
|
|
timeStamp = tstart + 0.5 * (tstop - tstart)
|
|
|
|
for orbType in types:
|
|
files = glob.glob( os.path.join(orbitDir, mission+'_OPER_AUX_' + orbType + '_OPOD*'))
|
|
|
|
###List all orbit files
|
|
for result in files:
|
|
fields = result.split('_')
|
|
taft = datetime.datetime.strptime(fields[-1][0:15], datefmt)
|
|
tbef = datetime.datetime.strptime(fields[-2][1:16], datefmt)
|
|
|
|
#####Get all files that span the acquisition
|
|
if (tbef <= tstart) and (taft >= tstop):
|
|
tmid = tbef + 0.5 * (taft - tbef)
|
|
match.append((result, abs((timeStamp-tmid).total_seconds())))
|
|
|
|
#####Return the file with the image is aligned best to the middle of the file
|
|
if len(match) != 0:
|
|
bestmatch = min(match, key = lambda x: x[1])
|
|
return bestmatch[0]
|
|
|
|
|
|
if len(match) == 0:
|
|
raise Exception('No suitable orbit file found. If you want to process anyway - unset the orbitdir parameter')
|
|
|
|
return
|
|
|
|
|
|
|
|
def s1_anx2roll(delta_anx):
|
|
'''
|
|
Returns the Platform nominal roll as function of elapsed time from
|
|
ascending node crossing time (ANX).
|
|
|
|
Straight from S1A documentation.
|
|
'''
|
|
|
|
####Estimate altitude based on time elapsed since ANX
|
|
altitude = s1_anx2Height(delta_anx)
|
|
|
|
####Reference altitude
|
|
href=711.700 #;km
|
|
|
|
####Reference boresight at reference altitude
|
|
boresight_ref= 29.450 # ; deg
|
|
|
|
####Partial derivative of roll vs altitude
|
|
alpha_roll = 0.0566 # ;deg/km
|
|
|
|
####Estimate nominal roll
|
|
nominal_roll = boresight_ref - alpha_roll* (altitude/1000.0 - href) #Theta off nadir
|
|
|
|
return nominal_roll
|
|
|
|
def s1_anx2Height(delta_anx):
|
|
'''
|
|
Returns the platform nominal height as function of elapse time from
|
|
ascending node crossing time (ANX).
|
|
|
|
Straight from S1A documention.
|
|
'''
|
|
|
|
###Average height
|
|
h0 = 707714.8 #;m
|
|
|
|
####Perturbation amplitudes
|
|
h = np.array([8351.5, 8947.0, 23.32, 11.74]) #;m
|
|
|
|
####Perturbation phases
|
|
phi = np.array([3.1495, -1.5655 , -3.1297, 4.7222]) #;radians
|
|
|
|
###Orbital time period in seconds
|
|
Torb = (12*24*60*60)/175.
|
|
|
|
###Angular velocity
|
|
worb = 2*np.pi / Torb
|
|
|
|
####Evaluation of series
|
|
ht=h0
|
|
for i in range(len(h)):
|
|
ht += h[i] * np.sin((i+1) * worb * delta_anx + phi[i])
|
|
|
|
return ht
|
|
|
|
|
|
def createBurstVRT(filename, fullWidth, fullLength,
|
|
yoffset, burst,
|
|
outwidth, outlength, outfile):
|
|
'''
|
|
Create a VRT file representing a single burst.
|
|
'''
|
|
|
|
if filename.startswith('/vsizip'):
|
|
parts = filename.split(os.path.sep)
|
|
|
|
if parts[2] == '':
|
|
parts[2] = os.path.sep
|
|
|
|
fname = os.path.join(*(parts[2:]))
|
|
# relfilename = os.path.join( '/vsizip', os.path.relpath(fname, os.path.dirname(outfile)))
|
|
relfilename = '/vsizip/' + os.path.abspath(fname)
|
|
else:
|
|
relfilename = os.path.relpath(filename, os.path.dirname(outfile))
|
|
|
|
rdict = { 'inwidth' : burst.lastValidSample - burst.firstValidSample,
|
|
'inlength' : burst.lastValidLine - burst.firstValidLine,
|
|
'outwidth' : outwidth,
|
|
'outlength' : outlength,
|
|
'filename' : relfilename,
|
|
'yoffset': yoffset + burst.firstValidLine,
|
|
'localyoffset': burst.firstValidLine,
|
|
'xoffset' : burst.firstValidSample,
|
|
'fullwidth': fullWidth,
|
|
'fulllength': fullLength}
|
|
|
|
|
|
tmpl = '''<VRTDataset rasterXSize="{outwidth}" rasterYSize="{outlength}">
|
|
<VRTRasterBand dataType="CFloat32" band="1">
|
|
<NoDataValue>0.0</NoDataValue>
|
|
<SimpleSource>
|
|
<SourceFilename relativeToVRT="1">{filename}</SourceFilename>
|
|
<SourceBand>1</SourceBand>
|
|
<SourceProperties RasterXSize="{fullwidth}" RasterYSize="{fulllength}" DataType="CInt16"/>
|
|
<SrcRect xOff="{xoffset}" yOff="{yoffset}" xSize="{inwidth}" ySize="{inlength}"/>
|
|
<DstRect xOff="{xoffset}" yOff="{localyoffset}" xSize="{inwidth}" ySize="{inlength}"/>
|
|
</SimpleSource>
|
|
</VRTRasterBand>
|
|
</VRTDataset>'''
|
|
|
|
with open(outfile, 'w') as fid:
|
|
fid.write( tmpl.format(**rdict))
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|