Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

  • a83768b
  • /
  • pyrate
  • /
  • core
  • /
  • gamma.py
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
content badge
swh:1:cnt:cc0c9236024c8d509ab525acdea03f97a4982ab2
directory badge
swh:1:dir:ed693b95c0a5007ecce0ec1d4ec6ff72befbfa5b

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
gamma.py
#   This Python module is part of the PyRate software package.
#
#   Copyright 2022 Geoscience Australia
#
#   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.
"""
This Python module contains tools for reading GAMMA format input data.
"""
# coding: utf-8
import re
import os
from os.path import split
from pathlib import Path
from datetime import date, time, timedelta
import numpy as np

import pyrate.constants as C
from pyrate.configuration import ConfigException, parse_namelist
import pyrate.core.ifgconstants as ifc
from pyrate.constants import sixteen_digits_pattern, BASELINE_FILE_PATHS, BASE_FILE_DIR
from pyrate.core.shared import extract_epochs_from_filename, data_format
from pyrate.core.logger import pyratelogger as log
import struct


# constants
GAMMA_DATE = 'date'
GAMMA_TIME = 'center_time'
GAMMA_WIDTH = 'width'
GAMMA_NROWS = 'nlines'
GAMMA_CORNER_LAT = 'corner_lat'
GAMMA_CORNER_LONG = 'corner_lon'
GAMMA_Y_STEP = 'post_lat'
GAMMA_X_STEP = 'post_lon'
GAMMA_DATUM = 'ellipsoid_name'
GAMMA_FREQUENCY = 'radar_frequency'
GAMMA_INCIDENCE = 'incidence_angle'
GAMMA_HEADING = 'heading'
GAMMA_AZIMUTH = 'azimuth_angle'
GAMMA_RANGE_PIX = 'range_pixel_spacing'
GAMMA_RANGE_N = 'range_samples'
GAMMA_RANGE_LOOKS = 'range_looks'
GAMMA_AZIMUTH_PIX = 'azimuth_pixel_spacing'
GAMMA_AZIMUTH_N = 'azimuth_lines'
GAMMA_AZIMUTH_LOOKS = 'azimuth_looks'
GAMMA_PRF = 'prf'
GAMMA_NEAR_RANGE = 'near_range_slc'
GAMMA_SAR_EARTH = 'sar_to_earth_center'
GAMMA_SEMI_MAJOR_AXIS = 'earth_semi_major_axis'
GAMMA_SEMI_MINOR_AXIS = 'earth_semi_minor_axis'
GAMMA_INITIAL_BASELINE = 'initial_baseline(TCN)'
GAMMA_INITIAL_BASELINE_RATE = 'initial_baseline_rate'
GAMMA_PRECISION_BASELINE = 'precision_baseline(TCN)'
GAMMA_PRECISION_BASELINE_RATE = 'precision_baseline_rate'
RADIANS = 'RADIANS'
GAMMA = 'GAMMA'


def _parse_header(path):
    """Parses all GAMMA header file fields into a dictionary"""
    with open(path) as f:
        text = f.read().splitlines()
        raw_segs = [line.split() for line in text if ':' in line]

    # convert the content into a giant dict of all key, values
    return dict((i[0][:-1], i[1:]) for i in raw_segs)


def parse_epoch_header(path):
    """
    Returns dictionary of epoch metadata required for PyRate

    :param str path: Full path to GAMMA mli.par file. Note that the mli.par is required as input since the baseline calculations require the input values valid for the GAMMA multi-looked products and also the GAMMA lookup table gives radar coordinates for the multi-looked geometry.

    :return: subset: subset of full metadata
    :rtype: dict
    """
    lookup = _parse_header(path)
    subset = _parse_date_time(lookup)

    # handle conversion of radar frequency to wavelength
    freq, unit = lookup[GAMMA_FREQUENCY]
    if unit != "Hz":  # pragma: no cover
        msg = 'Unrecognised unit field for radar_frequency: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_WAVELENGTH_METRES] = _frequency_to_wavelength(float(freq))

    incidence, unit = lookup[GAMMA_INCIDENCE]
    if unit != "degrees":  # pragma: no cover
        msg = 'Unrecognised unit field for incidence_angle: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_INCIDENCE_DEGREES] = float(incidence)

    sat_heading, unit = lookup[GAMMA_HEADING]
    if unit != "degrees":  # pragma: no cover
        msg = 'Unrecognised unit field for heading: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_HEADING_DEGREES] = float(sat_heading)

    sat_azimuth, unit = lookup[GAMMA_AZIMUTH]
    if unit != "degrees":  # pragma: no cover
        msg = 'Unrecognised unit field for azimuth_angle: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_AZIMUTH_DEGREES] = float(sat_azimuth)

    range_pix, unit = lookup[GAMMA_RANGE_PIX]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for range_pixel_spacing: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_RANGE_PIX_METRES] = float(range_pix)

    range_n = lookup[GAMMA_RANGE_N] # number without a unit in .par file
    subset[ifc.PYRATE_RANGE_N] = int(range_n[0])

    range_looks = lookup[GAMMA_RANGE_LOOKS]  # number without a unit in .par file
    subset[ifc.PYRATE_RANGE_LOOKS] = int(range_looks[0])

    azimuth_pix, unit = lookup[GAMMA_AZIMUTH_PIX]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for azimuth_pixel_spacing: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_AZIMUTH_PIX_METRES] = float(azimuth_pix)

    azimuth_n = lookup[GAMMA_AZIMUTH_N] # number without a unit in .par file
    subset[ifc.PYRATE_AZIMUTH_N] = int(azimuth_n[0])

    azimuth_looks = lookup[GAMMA_AZIMUTH_LOOKS]  # number without a unit in .par file
    subset[ifc.PYRATE_AZIMUTH_LOOKS] = int(azimuth_looks[0])

    pulse_rep_freq, unit = lookup[GAMMA_PRF]
    if unit != "Hz":  # pragma: no cover
        msg = 'Unrecognised unit field for prf: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_PRF_HERTZ] = float(pulse_rep_freq)

    near_range, unit = lookup[GAMMA_NEAR_RANGE]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for near_range_slc: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_NEAR_RANGE_METRES] = float(near_range)

    sar_to_earth, unit = lookup[GAMMA_SAR_EARTH]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for sar_to_earth_center: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_SAR_EARTH_METRES] = float(sar_to_earth)

    semi_major_axis, unit = lookup[GAMMA_SEMI_MAJOR_AXIS]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for earth_semi_major_axis: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_SEMI_MAJOR_AXIS_METRES] = float(semi_major_axis)

    semi_minor_axis, unit = lookup[GAMMA_SEMI_MINOR_AXIS]
    if unit != "m":  # pragma: no cover
        msg = 'Unrecognised unit field for earth_semi_minor_axis: %s'
        raise GammaException(msg % unit)
    subset[ifc.PYRATE_SEMI_MINOR_AXIS_METRES] = float(semi_minor_axis)

    return subset


def _parse_date_time(lookup):
    """Grab date and time metadata and convert to datetime objects"""
    subset = {}
    if len(lookup[GAMMA_DATE]) == 3:  # pragma: no cover
        year, month, day, = [int(float(i)) for i in lookup[GAMMA_DATE][:3]]
        if lookup.get(GAMMA_TIME) is not None:
            t = lookup[GAMMA_TIME][0]
            h, m, s = str(timedelta(seconds=float(t))).split(":")
            hour = int(h)
            min = int(m)
            sec = int(s.split(".")[0])
        else:
            # Occasionally GAMMA header has no time information - default to midnight
            hour, min, sec = 0, 0, 0
    elif len(lookup[GAMMA_DATE]) == 6:
        year, month, day, hour, min, sec = [int(float(i)) for i in lookup[GAMMA_DATE][:6]]
    else:  # pragma: no cover
        msg = "Date and time information not complete in GAMMA headers"
        raise GammaException(msg)

    subset[ifc.FIRST_DATE] = date(year, month, day)
    subset[ifc.FIRST_TIME] = time(hour, min, sec)

    return subset


def parse_dem_header(path):
    """
    Returns dictionary of DEM metadata required for PyRate

    :param str path: `Full path to Gamma *dem.par file`

    :return: subset: subset of full metadata
    :rtype: dict
    """
    lookup = _parse_header(path)

    # NB: many lookup fields have multiple elements, eg ['1000', 'Hz']
    subset = {ifc.PYRATE_NCOLS: int(lookup[GAMMA_WIDTH][0]), ifc.PYRATE_NROWS: int(lookup[GAMMA_NROWS][0])}

    expected = ['decimal', 'degrees']
    for k in [GAMMA_CORNER_LAT, GAMMA_CORNER_LONG, GAMMA_X_STEP, GAMMA_Y_STEP]:
        units = lookup[GAMMA_CORNER_LAT][1:]
        if units != expected:  # pragma: no cover
            msg = "Unrecognised units for GAMMA %s field\n. Got %s, expected %s"
            raise GammaException(msg % (k, units, expected))

    subset[ifc.PYRATE_LAT] = float(lookup[GAMMA_CORNER_LAT][0])
    subset[ifc.PYRATE_LONG] = float(lookup[GAMMA_CORNER_LONG][0])
    subset[ifc.PYRATE_Y_STEP] = float(lookup[GAMMA_Y_STEP][0])
    subset[ifc.PYRATE_X_STEP] = float(lookup[GAMMA_X_STEP][0])
    subset[ifc.PYRATE_DATUM] = "".join(lookup[GAMMA_DATUM])
    subset[ifc.PYRATE_INSAR_PROCESSOR] = GAMMA
    return subset


def parse_baseline_header(path: str) -> dict:
    """
    Returns dictionary of Baseline metadata required for PyRate.
    Will read the Precise baseline estimate, if available,
    otherwise will read the Initial baseline estimate.

    :param path: Full path to GAMMA base.par file

    :return: bdict: Dictionary of baseline values
    """
    lookup = _parse_header(path)  # read file contents in to a dict

    # split the initial and precise baselines
    initial = lookup[GAMMA_INITIAL_BASELINE]
    initial_rate = lookup[GAMMA_INITIAL_BASELINE_RATE]
    precise = lookup[GAMMA_PRECISION_BASELINE]
    precise_rate = lookup[GAMMA_PRECISION_BASELINE_RATE]

    # read the initial baseline if all precise components are zero
    # (indicates that the precise baseline estimation was not ran in GAMMA workflow)
    if float(precise[0]) == 0.0 and float(precise[1]) == 0.0 and float(precise[2]) == 0.0:
        log.debug('Reading Initial GAMMA baseline values')
        baseline, baseline_rate = initial, initial_rate
    else:
        log.debug('Reading Precise GAMMA baseline values')
        baseline, baseline_rate = precise, precise_rate

    # Extract and return a dict of baseline values
    bdict = {}

    # baseline vector (along Track, aCross track, Normal to the track)
    bdict[ifc.PYRATE_BASELINE_T] = float(baseline[0])
    bdict[ifc.PYRATE_BASELINE_C] = float(baseline[1])
    bdict[ifc.PYRATE_BASELINE_N] = float(baseline[2])
    bdict[ifc.PYRATE_BASELINE_RATE_T] = float(baseline_rate[0])
    bdict[ifc.PYRATE_BASELINE_RATE_C] = float(baseline_rate[1])
    bdict[ifc.PYRATE_BASELINE_RATE_N] = float(baseline_rate[2])

    return bdict


def _frequency_to_wavelength(freq):
    """
    Convert radar frequency to wavelength
    """
    return ifc.SPEED_OF_LIGHT_METRES_PER_SECOND / freq


def combine_headers(hdr0, hdr1, dem_hdr, base_hdr=None):
    """
    Combines metadata for first and second image epochs, DEM and baselines
    into a single dictionary for an interferogram.

    :param dict hdr0: Metadata for the first image
    :param dict hdr1: Metadata for the second image
    :param dict dem_hdr: Metadata for the DEM
    :param dict base_hdr: Metadata for baselines (if available)

    :return: chdr: combined metadata
    :rtype: dict
    """
    if not all([isinstance(a, dict) for a in [hdr0, hdr1, dem_hdr]]):
        raise GammaException('Header args need to be dicts')

    if base_hdr and not isinstance(base_hdr, dict):
        raise GammaException('Header args need to be dicts')

    date0, date1 = hdr0[ifc.FIRST_DATE], hdr1[ifc.FIRST_DATE]
    if date0 == date1:
        raise GammaException("Can't combine headers for the same day")
    elif date1 < date0:
        raise GammaException("Wrong date order")

    chdr = {ifc.PYRATE_TIME_SPAN: (date1 - date0).days / ifc.DAYS_PER_YEAR,
            ifc.FIRST_DATE: date0,
            ifc.FIRST_TIME: hdr0[ifc.FIRST_TIME],
            ifc.SECOND_DATE: date1,
            ifc.SECOND_TIME: hdr1[ifc.FIRST_TIME],
            ifc.DATA_UNITS: RADIANS,
            ifc.PYRATE_INSAR_PROCESSOR: GAMMA}

    # set incidence angle to mean of first amd second image values
    inc_ang = hdr0[ifc.PYRATE_INCIDENCE_DEGREES]
    if np.isclose(inc_ang, hdr1[ifc.PYRATE_INCIDENCE_DEGREES], atol=1e-1):
        chdr[ifc.PYRATE_INCIDENCE_DEGREES] = (hdr0[ifc.PYRATE_INCIDENCE_DEGREES] + hdr1[
            ifc.PYRATE_INCIDENCE_DEGREES]) / 2
    else:
        msg = "Incidence angles differ by more than 0.1 degrees"
        raise GammaException(msg)

    wavelen = hdr0[ifc.PYRATE_WAVELENGTH_METRES]
    if np.isclose(wavelen, hdr1[ifc.PYRATE_WAVELENGTH_METRES], atol=1e-6):
        chdr[ifc.PYRATE_WAVELENGTH_METRES] = wavelen
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Wavelength mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    # use parameter of first image (as done by GAMMA during interferometric processing)
    heading_ang = hdr0[ifc.PYRATE_HEADING_DEGREES]
    if np.isclose(heading_ang, hdr1[ifc.PYRATE_HEADING_DEGREES], atol=5e-1):
        chdr[ifc.PYRATE_HEADING_DEGREES] = heading_ang
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Satellite heading angles differ by more than 0.5 degrees"
        raise GammaException(msg % args)

    azimuth_ang = hdr0[ifc.PYRATE_AZIMUTH_DEGREES]
    if np.isclose(azimuth_ang, hdr1[ifc.PYRATE_AZIMUTH_DEGREES], atol=1e-1):
        chdr[ifc.PYRATE_AZIMUTH_DEGREES] = azimuth_ang
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Satellite azimuth angles differ by more than 0.1 degrees"
        raise GammaException(msg % args)

    range_pix = hdr0[ifc.PYRATE_RANGE_PIX_METRES]
    if np.isclose(range_pix, hdr1[ifc.PYRATE_RANGE_PIX_METRES], atol=1e-1):
        chdr[ifc.PYRATE_RANGE_PIX_METRES] = range_pix
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Range pixel spacing differs by more than 0.001 metres"
        raise GammaException(msg % args)

    range_n = hdr0[ifc.PYRATE_RANGE_N]
    if range_n == hdr1[ifc.PYRATE_RANGE_N]:
        chdr[ifc.PYRATE_RANGE_N] = range_n
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Number of range pixels mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    range_looks = hdr0[ifc.PYRATE_RANGE_LOOKS]
    if range_looks == hdr1[ifc.PYRATE_RANGE_LOOKS]:
        chdr[ifc.PYRATE_RANGE_LOOKS] = range_looks
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Number of range looks mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    azimuth_pix = hdr0[ifc.PYRATE_AZIMUTH_PIX_METRES]
    if np.isclose(azimuth_pix, hdr1[ifc.PYRATE_AZIMUTH_PIX_METRES], atol=1e-1):
        chdr[ifc.PYRATE_AZIMUTH_PIX_METRES] = azimuth_pix
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Azimuth pixel spacing differs by more than 0.001 metres"
        raise GammaException(msg % args)

    azimuth_n = hdr0[ifc.PYRATE_AZIMUTH_N]
    if azimuth_n == hdr1[ifc.PYRATE_AZIMUTH_N]:
        chdr[ifc.PYRATE_AZIMUTH_N] = azimuth_n
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Number of azimuth pixels mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    azimuth_looks = hdr0[ifc.PYRATE_AZIMUTH_LOOKS]
    if azimuth_looks == hdr1[ifc.PYRATE_AZIMUTH_LOOKS]:
        chdr[ifc.PYRATE_AZIMUTH_LOOKS] = azimuth_looks
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Number of azimuth looks mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    prf_hertz = hdr0[ifc.PYRATE_PRF_HERTZ]
    if np.isclose(prf_hertz, hdr1[ifc.PYRATE_PRF_HERTZ], atol=1e-6):
        chdr[ifc.PYRATE_PRF_HERTZ] = prf_hertz
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Pulse repetition frequency mismatch, check both header files for %s & %s"
        raise GammaException(msg % args)

    near_range = hdr0[ifc.PYRATE_NEAR_RANGE_METRES]
    if np.isclose(near_range, hdr1[ifc.PYRATE_NEAR_RANGE_METRES], atol=1e3):
        chdr[ifc.PYRATE_NEAR_RANGE_METRES] = near_range
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Near range differs by more than 1000 metres"
        raise GammaException(msg % args)

    sar_earth = hdr0[ifc.PYRATE_SAR_EARTH_METRES]
    if np.isclose(sar_earth, hdr1[ifc.PYRATE_SAR_EARTH_METRES], atol=1e3):
        chdr[ifc.PYRATE_SAR_EARTH_METRES] = sar_earth
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "SAR to Earth Center differs by more than 1000 metres"
        raise GammaException(msg % args)

    semi_major_axis = hdr0[ifc.PYRATE_SEMI_MAJOR_AXIS_METRES]
    if np.isclose(semi_major_axis, hdr1[ifc.PYRATE_SEMI_MAJOR_AXIS_METRES], atol=1e-4):
        chdr[ifc.PYRATE_SEMI_MAJOR_AXIS_METRES] = semi_major_axis
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Earth semi major axis differs by more than 0.0001 metres"
        raise GammaException(msg % args)

    semi_minor_axis = hdr0[ifc.PYRATE_SEMI_MINOR_AXIS_METRES]
    if np.isclose(semi_minor_axis, hdr1[ifc.PYRATE_SEMI_MINOR_AXIS_METRES], atol=1e-4):
        chdr[ifc.PYRATE_SEMI_MINOR_AXIS_METRES] = semi_minor_axis
    else:
        args = (chdr[ifc.FIRST_DATE], chdr[ifc.SECOND_DATE])
        msg = "Earth semi minor axis differs by more than 0.0001 metres"
        raise GammaException(msg % args)

    # non-cropped, non-multilooked geotif process step information added
    chdr[ifc.DATA_TYPE] = ifc.ORIG

    chdr.update(dem_hdr)  # add geographic data

    if base_hdr:
        chdr.update(base_hdr)  # add baseline information

    return chdr


def manage_headers(dem_header_file, header_paths, baseline_paths=None):
    """
    Manage and combine header files for GAMMA interferograms, DEM and
    incidence files

    :param str dem_header_file: DEM header path
    :param list header_paths: List of first/second image header paths

    :return: combined_header: Combined metadata dictionary
    :rtype: dict
    """
    dem_header = parse_dem_header(dem_header_file)
    # find param files containing filename dates
    if len(header_paths) == 2:
        hdrs = [parse_epoch_header(hp) for hp in header_paths]
        if baseline_paths is not None:
            baseline_header = parse_baseline_header(baseline_paths)
            combined_header = combine_headers(hdrs[0], hdrs[1], dem_header, baseline_header)
        else:
            combined_header = combine_headers(hdrs[0], hdrs[1], dem_header)
    elif len(header_paths) > 2:
        msg = f'There are too many parameter files for one interferogram; there should only be two. {len(header_paths)} parameter files have been given: {header_paths}.'
        raise GammaException(msg)
    else:
        # probably have DEM or incidence file
        combined_header = dem_header
        combined_header[ifc.DATA_TYPE] = ifc.DEM

    return combined_header


def get_header_paths(input_file, slc_file_list):
    """
    Function that matches input GAMMA file names with GAMMA header file names

    :param str input_file: input GAMMA image file.
    :param slc_file_list: file listing the pool of available header files (GAMMA: slc.par, ROI_PAC: .rsc)
    :return: list of matching header files
    :rtype: list
    """
    f = Path(input_file)
    epochs = extract_epochs_from_filename(f.name)
    header_names = parse_namelist(slc_file_list)
    matches = [hdr for hdr in header_names if any(e in hdr for e in epochs)]
    return matches


def gamma_header(ifg_file_path, params):
    """
    Function to obtain combined Gamma headers for image file
    
    Args:
        ifg_file_path: Path to interferogram file to find headers for.
        params: PyRate parameters dictionary.

    Returns:
        A combined header dictionary containing metadata from matching
        gamma headers and DEM header.   
    """
    dem_hdr_path = params[C.DEM_HEADER_FILE]
    header_paths = get_header_paths(ifg_file_path, params[C.HDR_FILE_LIST])
    if len(header_paths) == 2 and params[C.BASE_FILE_LIST] is not None:
        baseline_path = baseline_paths_for(ifg_file_path, params)
    else:
        baseline_path = None  # don't read baseline files for DEM

    combined_headers = manage_headers(dem_hdr_path, header_paths, baseline_path)

    if os.path.basename(ifg_file_path).split('.')[1] == \
            (params[C.APS_INCIDENCE_EXT] or params[C.APS_ELEVATION_EXT]):
        # TODO: implement incidence class here
        combined_headers['FILE_TYPE'] = 'Incidence'

    return combined_headers


def read_lookup_table(head, data_path, xlooks, ylooks, xmin, xmax, ymin, ymax):
    # pylint: disable = too - many - statements
    """
    Creates a copy of input lookup table file in a numpy array and applies the ifg ML factors

    :param IFG object head: first IFG in the list to read metadata
    :param str data_path: Input file
    :param int xlooks: multi-looking factor in x
    :param int ylooks: multi-looking factor in y
    :param int xmin: start pixel of cropped extent in x
    :param int xmax: end pixel of cropped extent in x
    :param int ymin: start pixel of cropped extent in y
    :param int ymax: end pixel of cropped extent in y

    :return: np-array lt_data_az: azimuth (i.e. row) of radar-coded MLI
    :return: np-array lt_data_rg: range (i.e. column) of radar-coded MLI
    """
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-locals

    # read relevant metadata parameters
    nrows_lt = int(head.meta_data[ifc.PYRATE_NROWS]) # number of rows of original geotiff files
    ncols_lt = int(head.meta_data[ifc.PYRATE_NCOLS]) # number of columns of original geotiff files
    nrows = head.nrows # number of rows in multi-looked and cropped data sets
    ncols = head.ncols # number of columns in multi-looked and cropped data sets

    ifg_proc = head.meta_data[ifc.PYRATE_INSAR_PROCESSOR]
    # get dimensions of lookup table file
    bytes_per_col, fmtstr = data_format(ifg_proc, True, ncols_lt*2) # float complex data set containing value tupels

    # check if lookup table has the correct size
    small_size = _check_raw_data(bytes_per_col * 2, data_path, ncols_lt, nrows_lt)
    # todo: delete the following if condition once a suitable test data set has been included
    if small_size: # this is a test data set without a corresponding lt-file
        lt_data_az = np.empty((nrows, ncols)) * np.nan # nan array with size of input data set
        lt_data_rg = np.empty((nrows, ncols)) * np.nan # nan array with size of input data set

    else: # this is a real data set with an lt-file of correct size
        row_bytes = ncols_lt * 2 * bytes_per_col
        lt_data_az = np.empty((0, ncols))  # empty array with correct number of columns
        lt_data_rg = np.empty((0, ncols))  # empty array with correct number of column
        # for indexing: lookup table file contains value pairs (i.e. range, azimuth)
        # value pair 0 would be index 0 and 1, value pair 1 would be index 2 and 3, and so on
        # example: for a multi-looking factor of 10 we want value pair 4, 14, 24, ...
        # this would be index 8 and 9, index 28 and 29, 48 and 49, ...
        # start column needs to be added in case cropping is applied
        if (xlooks % 2) == 0:  # for even ml factors
            idx_start = xmin + int(xlooks / 2) - 1
        else: # for odd ml factors
            idx_start = xmin + int((xlooks - 1) / 2)
        # indices of range info in lookup table for the cropped and multi-looked data set
        idx_rg = np.arange(2 * idx_start, xmax * 2, 2 * xlooks)  # first value
        idx_az = np.arange(2 * idx_start + 1, xmax * 2, 2 * xlooks)  # second value
        # set up row idx, e.g. for ml=10 (without cropping): 4, 14, 24, ...
        if (ylooks % 2) == 0: # for even ml factors
            idx_start = ymin + int(ylooks / 2) - 1
        else: # for odd ml factors
            idx_start = ymin + int((ylooks - 1) / 2)
        row_idx = np.arange(idx_start, ymax, ylooks)

        # read the binary lookup table file and save the range/azimuth value pair for each position in the cropped and
        # multi-looked data set
        log.debug(f"Reading lookup table file {data_path}")
        with open(data_path, 'rb') as f:
            for y in range(nrows_lt): # loop through all lines in file
                # this could potentially be made quicker by skipping unwanted bytes in the f.read command?
                data = struct.unpack(fmtstr, f.read(row_bytes))
                # but only read data from lines in row index:
                if y in row_idx:
                    row_data = np.array(data)
                    row_data_ml_az = row_data[idx_az] # azimuth for PyRate
                    row_data_ml_rg = row_data[idx_rg] # range for PyRate
                    lt_data_az = np.append(lt_data_az, [row_data_ml_az], axis=0)
                    lt_data_rg = np.append(lt_data_rg, [row_data_ml_rg], axis=0)

    return lt_data_az, lt_data_rg


def _check_raw_data(bytes_per_col, data_path, ncols, nrows):
    """
    Convenience function to check the file size is as expected
    """
    size = ncols * nrows * bytes_per_col
    act_size = os.stat(data_path).st_size
    if act_size != size:
        msg = '%s should have size %s, not %s. Is the correct file being used?'
        if size < 28000:
            # test data set doesn't currently fit the lookup table size, stop further calculation
            # todo: delete this if statement once a new test data set has been introduced
            return True
        else:
            raise GammaException(msg % (data_path, size, act_size))


class GammaException(Exception):
    """Gamma generic exception class"""


def baseline_paths_for(path: str, params: dict) -> str:
    """
    Returns path to baseline file for given interferogram. Pattern matches
    based on epoch in filename.

    Example:
        '20151025-20160501_base.par'
        Date pair is the epoch.

    Args:
        path: Path to intergerogram to find baseline file for.
        params: Parameter dictionary.
        tif: Find converted tif if True (_cc.tif), else find .cc file.

    Returns:
        Path to baseline file.
    """

    _, filename = split(path)
    try:
        epoch = re.search(sixteen_digits_pattern, filename).group(0)
    except: # catch cases where filename does not have two epochs, e.g. DEM file
        return None

    base_file_paths = [f.unwrapped_path for f in params[BASELINE_FILE_PATHS] if epoch in f.unwrapped_path]

    if len(base_file_paths) > 1:
        raise ConfigException(f"'{BASE_FILE_DIR}': found more than one baseline "
                              f"file for '{path}'. There must be only one "
                              f"baseline file per interferogram. Found {base_file_paths}.")
    return base_file_paths[0]

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API