• No results found

Differential Interferometry and Multiple-Aperture Interferometry for Retrieving Three-Dimensional Measurements of Glacial Surface Velocity

N/A
N/A
Protected

Academic year: 2021

Share "Differential Interferometry and Multiple-Aperture Interferometry for Retrieving Three-Dimensional Measurements of Glacial Surface Velocity"

Copied!
86
0
0

Loading.... (view fulltext now)

Full text

(1)

Master’s thesis

Physical Geography and Quaternary Geology, 45 Credits

Department of Physical Geography

Differential Interferometry

and Multiple-Aperture

Interferometry for Retrieving

Three-Dimensional Measurements

of Glacial Surface Velocity

Luke Webber

(2)
(3)

Preface

This Master’s thesis is Luke Webber’s degree project in Physical Geography and Quaternary

Geology at the Department of Physical Geography, Stockholm University. The Master’s

thesis comprises 45 credits (one and a half term of full-time studies).

Supervisor has been Ian Brown at the Department of Physical Geography, Stockholm

University. Examiner has been Johan Kleman at the Department of Physical Geography,

Stockholm University.

The author is responsible for the contents of this thesis.

Stockholm, 15 September 2016

(4)
(5)
(6)
(7)
(8)
(9)
(10)
(11)
(12)
(13)
(14)
(15)
(16)
(17)
(18)
(19)
(20)
(21)
(22)
(23)
(24)
(25)
(26)
(27)
(28)
(29)

𝚫𝑩

𝚫𝒇

𝑫𝑪

(30)

𝒙̅

𝜸

𝝈

𝜸

𝑪

𝒗

(31)

𝚫𝑩

(32)

𝒙̅

𝑫𝑰𝑺𝑷𝑳𝑨𝑪𝑬𝑴𝑬𝑵𝑻

𝝈

𝑫𝑰𝑺𝑷𝑳𝑨𝑪𝑬𝑴𝑬𝑵𝑻

𝒙̅

𝑽𝑬𝑳𝑶𝑪𝑰𝑻𝒀

𝑥̅

𝐷𝐼𝑆𝑃𝐿𝐴𝐶𝐸𝑀𝐸𝑁𝑇

𝜎

𝐷𝐼𝑆𝑃𝐿𝐴𝐶𝐸𝑀𝐸𝑁𝑇

(33)
(34)
(35)
(36)
(37)
(38)
(39)

𝚫𝑩

𝚫𝒇

𝑫𝑪

𝒙̅

𝜸

𝝈

𝜸

𝑪

𝒗

Δ𝐵

Δ𝑓

𝐷𝐶

(40)

𝐶

𝑣

𝛾

𝑡ℎ𝑒𝑟𝑚𝑎𝑙

𝑥̅

𝑦

(41)
(42)
(43)
(44)
(45)
(46)
(47)
(48)
(49)
(50)
(51)
(52)
(53)
(54)
(55)
(56)
(57)
(58)
(59)
(60)
(61)
(62)
(63)
(64)
(65)
(66)
(67)
(68)
(69)
(70)
(71)
(72)
(73)
(74)
(75)
(76)

import argparse

import time

import sys

import numpy as np

import scipy.fftpack as fft

import spectral.io.envi as envi

from tqdm import tqdm

def compute_interferogram(mst, slv): """

Computes along-track interferogram by splitting doppler frequencies into positive and negative frequencies, corresponding to the forwards and backwards look angles, and computing the double difference interferogram.

:param mst: complex master SLC image

:param slv: complex slave SLC image co-registered to master :return: along-track interferogram

"""

# Transform data along azimuthal (columnar) direction into frequency domain

master_doppler = fft.fft(mst, axis=0) slave_doppler = fft.fft(slv, axis=0)

frequencies = fft.fftfreq(master_doppler.shape[0]) master_doppler_fpos = master_doppler.copy()

master_doppler_fneg = master_doppler.copy() slave_doppler_fpos = slave_doppler.copy() slave_doppler_fneg = slave_doppler.copy() # Taper to zero unwanted frequencies

master_doppler_fpos[np.where(frequencies < 0)] = 0.0 + 0.0j

master_doppler_fneg[np.where(frequencies > 0)] = 0.0 + 0.0j

slave_doppler_fpos[np.where(frequencies < 0)] = 0.0 + 0.0j

slave_doppler_fneg[np.where(frequencies > 0)] = 0.0 + 0.0j

# Inverse transform back into time domain

master_forward_look = fft.ifft(master_doppler_fpos, axis=0) master_backward_look = fft.ifft(master_doppler_fneg, axis=0) slave_forward_look = fft.ifft(slave_doppler_fpos, axis=0) slave_backward_look = fft.ifft(slave_doppler_fneg, axis=0) # Compute double difference interferogram

i1 = slave_backward_look * np.conj(master_backward_look) i2 = slave_forward_look * np.conj(master_forward_look) ifg = i1 * np.conj(i2)

# Normalise signal

ifg /= np.absolute(ifg) return ifg

def main(i_master, q_master, i_slave, q_slave, i_out, q_out, bursts=None): """

Reads input file into memory, computes along-track interferogram, interpolates the interferogram and writes the results to the output file.

(77)

:param q_slave: Path to slave file containing real component.

:param i_out: Path to save completed along-track imaginary component. :param q_out: Path to save completed along-track real component. :param bursts: (Optional) Number of bursts present if TOPSAR product. """

print("[{0}] Opening files for reading...".format(time.strftime("%H:%M:%S"))) i_mst = envi.open(i_master)

q_mst = envi.open(q_master) i_slv = envi.open(i_slave) q_slv = envi.open(q_slave)

print("[{0}] Files opened".format(time.strftime("%H:%M:%S")))

print("[{0}] Copying data into arrays...".format(time.strftime("%H:%M:%S"))) master = np.array(i_mst.load()) + 1j * np.array(q_mst.load())

slave = np.array(i_slv.load()) + 1j * np.array(q_slv.load()) ifg = np.empty(master.shape, dtype=complex)

print("[{0}] Data copied into arrays".format(time.strftime("%H:%M:%S"))) if bursts:

burst_height = master.shape[0]//bursts

print("[{0}] Processing data per burst...".format(time.strftime("%H:%M:%S"))) for i in tqdm(range(0, burst_height*bursts, burst_height)):

j = i + burst_height

ifg[i:j, :] = compute_interferogram(master[i:j, :], slave[i:j, :]) else:

print("[{0}] Processing data...".format(time.strftime("%H:%M:%S"))) for i in tqdm(range(0, master.shape[1])):

ifg[:, i] = compute_interferogram(master[:, i], slave[:, i]) print("[{0}] Writing data to disk...".format(time.strftime("%H:%M:%S")))

# In order to preserve backwards compatibility with the BEAM-DIMAP format used by the

# SNAP S1TBX program output files are written with the Spectral Python module

# in big-endian ENVI format.

envi.save_image(i_out, np.real(ifg), dtype='float32', interleave='bsq', byteorder='1', metadata=i_mst.metadata, force=True)

envi.save_image(q_out, np.imag(ifg), dtype='float32', interleave='bsq', byteorder='1', metadata=q_mst.metadata, force=True)

print("[{0}] Data written to disk".format(time.strftime("%H:%M:%S"))) print("[{0}] Finished!".format(time.strftime("%H:%M:%S")))

if __name__ == "__main__":

class Parser(argparse.ArgumentParser): def error(self, message):

sys.stderr.write('error: %s\n' % message) self.print_help()

sys.exit(2)

parser = Parser(description="Along-track interferogram formation")

parser.add_argument("i_master", type=open, help="Path to master real component") parser.add_argument("q_master", type=open, help="Path to master imaginary component") parser.add_argument("i_slave", type=open, help="Path to slave real component")

parser.add_argument("q_slave", type=open, help="Path to slave imaginary component") parser.add_argument("i_out", help="Path to save output real component")

parser.add_argument("q_out", help="Path to save output imaginary component") parser.add_argument("-b", "--bursts", type=int,

help="Number of bursts if input file is TOPSAR product") np.seterr(divide='ignore', invalid='ignore')

args = parser.parse_args()

(78)

import sys import gdal import math import argparse import numpy as np import multiprocessing as mp

from scipy.ndimage.filters import generic_filter

from osgeo.gdalconst import GDT_Float32

def window_stdev(arr, size): """

Calculate standard deviation over an 2-dimensional array using a moving window. To avoid edge effects in the final result, ensure displacement observations have an outside buffer of at least 'size'/2 or greater.

:param arr: target array

:param size: size of moving window

:return: array of stardard deviations calculated pixel-by-pixel """

return generic_filter(arr, np.std, size, mode='reflect')

def rot_matrix(a_inc, d_inc, a_az, d_az): """

Generate rotation matrix.

:param a_inc: ascending geometry radar incidence angle in radians :param d_inc: descending geometry radar incidence angle in radians :param a_az: ascending geometry orbital azimuth angle in radians :param d_az: descending geometry orbital azimuth angle in radians :return: rotation matrix

"""

p = 3*math.pi/2

return np.matrix([[math.cos(a_inc), -math.sin(a_inc)*math.sin(a_az - p), -math.sin(a_inc)*math.cos(a_az - p)],

[math.cos(d_inc), -math.sin(d_inc)*math.sin(d_az - p), -math.sin(d_inc)*math.cos(d_az - p)],

[0, -math.cos(a_az - p), -math.sin(a_az - p)], [0, -math.cos(d_az - p), -math.sin(d_az - p)]])

def chunk_array(arr_size, splits): """

Determine the location of array chunks in preparation for multiprocessing. :param arr_size: size of array along axis to be chunked

:param splits: number of chunks the array is to be split into :return: location of chunk splits

"""

axis_pos = np.arange(arr_size)

return [(min(axis_pos[i*arr_size//splits:(i+1)*arr_size//splits]),

max(axis_pos[i*arr_size//splits:(i+1)*arr_size//splits])) for i in range(splits)]

def worker(worker_id, queue): """

Subprocess worker for calculating weighted least squares adjustment. :param worker_id: identifying number

:param queue: queue for passing back data :return: displacement vectors

"""

# Perform weighted least squares adjustment to derive displacement vector from LOS and

# azimuthal observations, values along 3rd axis of final displacement map represent

# up, east and north respectively.

(79)

for j in range(chunk_loc[worker_id][0], chunk_loc[worker_id][1]+1): displacement_vectors = np.array([d_a_los[i, j], d_d_los[i, j], d_a_azi[i, j], d_d_azi[i, j]]) if displacement_vectors.any():

rotation_matrix = rot_matrix(asc_incidence[i, j], dsc_incidence[i, j], asc_orbit_azi[i, j], dsc_orbit_azi[i, j]) covariance_matrix = np.asmatrix(np.diag([stdev_d_a_los[i, j],

stdev_d_d_los[i, j], stdev_d_a_azi[i, j], stdev_d_d_azi[i, j]])) try:

a = rotation_matrix.T @ covariance_matrix.I @ rotation_matrix

b = a.I @ rotation_matrix.T @ covariance_matrix.I @ displacement_vectors

displacement_map[i, j] = np.asarray(b)[0] except np.linalg.linalg.LinAlgError:

# Catch singular matrix error, singular matrices are invertible, this

# typically occurs because all values of standard deviation are zero

# within the covariance matrix, the cause of this is due to areas of

# no-data having zero variance, as there is no data, displacements

# along all three dimensions are set to zero.

displacement_map[i, j] = [0.0, 0.0, 0.0] else:

# All measured values of displacement in the LOS and along-direction were zero,

# hence displacements along all three dimensions are set to zero.

displacement_map[i, j] = [0.0, 0.0, 0.0] queue.put([worker_id, displacement_map])

def write_results(displacements, src): """

Extracts displacment components and writes results into tiff files: :param displacements: Numpy array holding results

:param src: GDAL file handle, for copying projection and location data to output files. "up_down.tiff" Up-Down component

"east_west.tiff" East-West component "north_south.tiff" North-South component """

driver = src.GetDriver()

ud = driver.Create("up_down.tiff",

displacements.shape[1], displacements.shape[0], 1, GDT_Float32) ud.SetMetadata(src.GetMetadata())

ud.SetGeoTransform(src.GetGeoTransform()) ud.SetProjection(src.GetProjection())

ud.GetRasterBand(1).WriteArray(displacements[:, :, 0], 0, 0) ud.GetRasterBand(1).FlushCache()

ew = driver.Create("east_west.tiff",

displacements.shape[1], displacements.shape[0], 1, GDT_Float32) ew.SetMetadata(src.GetMetadata())

ew.SetGeoTransform(src.GetGeoTransform()) ew.SetProjection(src.GetProjection())

ew.GetRasterBand(1).WriteArray(displacements[:, :, 1], 0, 0) ew.GetRasterBand(1).FlushCache()

ns = driver.Create("north_south.tiff",

displacements.shape[1], displacements.shape[0], 1, GDT_Float32) ns.SetMetadata(src.GetMetadata())

ns.SetGeoTransform(src.GetGeoTransform()) ns.SetProjection(src.GetProjection())

ns.GetRasterBand(1).WriteArray(displacements[:, :, 2], 0, 0) ns.GetRasterBand(1).FlushCache()

if __name__ == "__main__":

class Parser(argparse.ArgumentParser): def error(self, message):

(80)

self.print_help() sys.exit(2)

parser = Parser(description="Three-dimensional displacement field generation") parser.add_argument("asc_los", type=open,

help="Path to LOS measurements from the ascending orbital pass") parser.add_argument("dsc_los", type=open,

help="Path to LOS measurements from the descending orbital pass") parser.add_argument("asc_at", type=open,

help="Path to along-track measurements from the ascending orbital pass") parser.add_argument("dsc_at", type=open,

help="Path to along-track measurements from the descending orbital pass") parser.add_argument("asc_inc", help="Incidence angle of ascending pass")

parser.add_argument("dsc_inc", help="Incidence angle of descending pass") parser.add_argument("asc_azi", help="Orbital azimuth angle of ascending pass") parser.add_argument("dsc_azi", help="Orbital azimuth angle of descending pass") args = parser.parse_args()

# Open files for reading

d_a_los_source = gdal.Open(args.asc_los.name) d_d_los_source = gdal.Open(args.dsc_los.name) d_a_azi_source = gdal.Open(args.asc_at.name) d_d_azi_source = gdal.Open(args.dsc_at.name) # Extract data from raster bands

d_a_los = d_a_los_source.GetRasterBand(1).ReadAsArray() d_d_los = d_d_los_source.GetRasterBand(1).ReadAsArray() d_a_azi = d_a_azi_source.GetRasterBand(1).ReadAsArray() d_d_azi = d_d_azi_source.GetRasterBand(1).ReadAsArray() array_size = d_a_los.shape

# Calculate standard deviation arrays over input displacement vector files.

stdev_d_a_los = window_stdev(d_a_los, 5) stdev_d_d_los = window_stdev(d_d_los, 5) stdev_d_a_azi = window_stdev(d_a_azi, 5) stdev_d_d_azi = window_stdev(d_d_azi, 5)

# Create matrix of ascending and descending incidence/orbital azimuthal angles.

# Ideally these should be calculated on a per-pixel basis and loaded from a raster,

# rather than using an average value over the entire radar scene!

asc_incidence = np.full(array_size, math.radians(float(args.asc_inc))) dsc_incidence = np.full(array_size, math.radians(float(args.dsc_inc))) asc_orbit_azi = np.full(array_size, math.radians(float(args.asc_azi))) dsc_orbit_azi = np.full(array_size, math.radians(float(args.dsc_azi)))

# The weighted least squares calculations are independent of each other, and are calculated

# on a pixel-by-pixel basis, as such the calculations can be parallelised to run on

# multiple cores, for maximum speed! Due to the GIL lock, threading in Python offers no

# speedup, instead new processes are spawned.

displacement_map = np.empty((array_size + (3,))) # Empty array to hold final results

cores = mp.cpu_count() # Number of cores available

# Determine location of chunks for equally splitting the work between cores

chunk_loc = chunk_array(array_size[1], cores) # Queue for worker processes to return data by

result_queue = mp.Queue()

jobs = [mp.Process(target=worker, args=(i, result_queue)) for i in range(cores)] for job in jobs:

job.start()

# There is a limit on how much data can be piped between processes before Process.join()

# will hang indefinitely, instead the calling processes is blocked with a loop until

# all results have been de-queued into a temporary buffer

temp_results = []

(81)

temp_results.append(result_queue.get())

# Rebuild final displacement map from temporary buffer

for result in temp_results:

displacement_map[:, chunk_loc[result[0]][0]:chunk_loc[result[0]][1]+1] = \ result[1][:, chunk_loc[result[0]][0]:chunk_loc[result[0]][1]+1]

# Reverse displacements to true polarity, otherwise results will be backwards!

displacement_map *= -1.0

# Write results to file

(82)
(83)
(84)
(85)
(86)

References

Related documents

Through the NAWOU gender training programmes the trainers can see that women have learnt how to save money and by saving in the group the women have got credit capacity to make

The four sources the analysis is based on are Frankfurter Allgemeine Zeitung (FAZ), Süddeutsche Zeitung (SZ), Der Spiegel and FOCUS. The FAZ is a conservative- liberal daily

Cross-border structures do not constitute additional administration levels. They are rather a cross-border interface or exchange to enhance the cross-border efficiency of

That so far no clear and coherent EU Arctic policy is established is apparent from the Council stating that it “welcomes the gradual formulation of a policy on Arctic issues to

Fig 29 Tanzania. Population density, 1967. Fig31 Example of the distribution of homestead fields and garden plots in a clus- ter of homesteads in Ugogo. Fig 32 A

This Bachelor’s thesis is Amanda Åberg’s degree project in Geography at the Department of Physical Geography, Stockholm University. The Bachelor’s thesis comprises 15 credits (a

This Master’s thesis is Åsa Cecilia Wallin’s degree project in Physical Geography and Quaternary Geology at the Department of Physical Geography, Stockholm University.. The

This scheme essentially relies on consumers making use of other policies like the Green Deal or ECO or it relies on consumers carrying out energy renovations themselves without