Source code for asard.metadata.extract
import numpy as np
from dateutil.parser import parse as dateparse
from spatialist import Raster
from spatialist.ancillary import finder
from spatialist.raster import rasterize
from asard import snap
from asard.config import version_dict
from asard.metadata.mapping import ORB_MAP, NOISE_MAP, URL
from cesard.metadata.mapping import DEM_MAP, LERC_ERR_THRES
from cesard.metadata.extract import (calc_enl, geometry_from_vec,
calc_performance_estimates,
vec_from_srccoords)
[docs]
def get_prod_meta(tif, src_ids, sar_dir):
"""
Collect ARD product metadata. Items are obtained from parsing the name
of the ARD product and from reading a measurement GeoTIFF file of this
product.
Parameters
----------
tif: str
The paths to a measurement GeoTIFF file of the ARD product.
src_ids: list[pyroSAR.drivers.ID]
The source product object(s).
sar_dir: str
A path pointing to the processed SAR datasets.
Returns
-------
dict
A dictionary containing metadata for the product scene.
"""
out = dict()
coord_list = [src.meta['coordinates'] for src in src_ids]
with Raster(tif) as ras:
vec = ras.bbox()
srs = vec.srs
out['wkt'] = srs.ExportToWkt()
out['epsg'] = vec.getProjection(type='epsg')
out['rows'] = ras.rows
out['cols'] = ras.cols
out['res'] = ras.res
geo = ras.geo
out['transform'] = [geo['xres'], geo['rotation_x'], geo['xmin'],
geo['rotation_y'], geo['yres'], geo['ymax']]
out['geom'] = geometry_from_vec(vectorobject=vec)
# Calculate number of nodata border pixels based on source scene(s) footprint
with vec_from_srccoords(coord_list=coord_list, crs=4326) as srcvec:
ras_srcvec = rasterize(vectorobject=srcvec, reference=ras, burn_values=[1])
arr_srcvec = ras_srcvec.array()
out['nodata_borderpx'] = np.count_nonzero(np.isnan(arr_srcvec))
proc_meta = snap.get_metadata(scene=src_ids[0], outdir=sar_dir)
out['ML_nRgLooks'] = proc_meta['rlks'] * src_ids[0].meta['looks'][0]
out['ML_nAzLooks'] = proc_meta['azlks'] * src_ids[0].meta['looks'][1]
return out
[docs]
def meta_dict(config, prod_meta, src_ids, compression):
"""
Creates a dictionary containing metadata for an ARD product as well as
its source products. The dictionary can then be used to parse XML and
STAC JSON metadata files, respectively.
Parameters
----------
config: dict
Dictionary of the parsed config parameters for the current process.
prod_meta: dict
general product metadata as extracted by :func:`asard.ard.product_info`
src_ids: list[pyroSAR.drivers.ID]
List of :class:`~pyroSAR.drivers.ID` objects of all source products
that overlap with the current MGRS tile.
compression: str
The compression type applied to raster files of the product.
Returns
-------
meta: dict
A dictionary containing an extensive collection of metadata for the
ARD product as well as source products.
"""
dummy_num = -99999
dummy_str = 'TBD'
meta = {'prod': {},
'source': {},
'common': {}}
ref_tif = finder(prod_meta['dir_ard'], ['[hv]{2}-g-lin.tif$'], regex=True)[0]
np_tifs = finder(prod_meta['dir_ard'], ['-np-[hv]{2}.tif$'], regex=True)
prod_meta.update(get_prod_meta(tif=ref_tif, src_ids=src_ids, sar_dir=config['processing']['sar_dir']))
op_mode = prod_meta['mode']
src_sid = {}
for sid in src_ids:
uid = sid.outname_base()
src_sid[uid] = sid
src0 = list(src_sid.keys())[0] # first key/first file
sid0 = src_sid[src0]
dem_name = config['processing']['dem_type']
dem_ref = DEM_MAP[dem_name]['ref']
dem_type = DEM_MAP[dem_name]['type']
dem_egm = DEM_MAP[dem_name]['egm']
# Common metadata (sorted alphabetically)
meta['common']['antennaLookDirection'] = 'RIGHT'
if sid0.sensor == 'ASAR':
constellation = 'envisat'
elif sid0.sensor.startswith('ERS'):
constellation = 'ers'
else:
raise ValueError('Unknown sensor type')
meta['common']['antennaLookDirection'] = 'RIGHT'
meta['common']['constellation'] = constellation
meta['common']['instrumentShortName'] = {'ERS1': 'SAR', 'ERS2': 'SAR', 'ASAR': 'ASAR'}[sid0.sensor]
meta['common']['operationalMode'] = op_mode
meta['common']['orbitDirection'] = {'A': 'ascending', 'D': 'descending'}[sid0.orbit]
meta['common']['orbitMeanAltitude'] = '{:.2e}'.format(693000)
meta['common']['orbitNumber_abs'] = sid0.orbitNumber_abs
meta['common']['orbitNumber_rel'] = sid0.orbitNumber_rel
pid_lookup = {'ERS1': '1', 'ERS2': '2', 'ASAR': '1'}
meta['common']['platformIdentifier'] = pid_lookup[sid0.sensor]
psn_lookup = {'ERS1': 'ERS', 'ERS2': 'ERS', 'ASAR': 'ENVISAT'}
meta['common']['platformShortName'] = psn_lookup[sid0.sensor]
meta['common']['platformFullname'] = '{}-{}'.format(meta['common']['platformShortName'].lower(),
meta['common']['platformIdentifier'].lower())
meta['common']['platformReference'] = URL['platformReference'][meta['common']['platformFullname']]
meta['common']['polarisationChannels'] = sid0.polarizations
meta['common']['polarisationMode'] = prod_meta['polarization']
meta['common']['processingLevel'] = 'L1C'
meta['common']['radarBand'] = 'C'
meta['common']['radarCenterFreq'] = 5300000000
meta['common']['sensorType'] = 'RADAR'
meta['common']['swathIdentifier'] = op_mode
meta['common']['wrsLongitudeGrid'] = str(sid0.meta['orbitNumber_rel'])
# Product metadata (sorted alphabetically)
meta['prod']['access'] = None
meta['prod']['acquisitionType'] = 'NOMINAL'
meta['prod']['ancillaryData_KML'] = URL['ancillaryData_KML']
meta['prod']['azimuthNumberOfLooks'] = sid0.meta['looks'][1]
meta['prod']['backscatterConvention'] = 'linear power'
meta['prod']['backscatterConversionEq'] = '10*log10(DN)'
meta['prod']['backscatterMeasurement'] = 'gamma0'
meta['prod']['card4l-link'] = URL['card4l_nrb']
meta['prod']['card4l-name'] = 'NRB'
meta['prod']['card4l-version'] = '5.5'
meta['prod']['compression_type'] = compression
meta['prod']['compression_zerrors'] = LERC_ERR_THRES
meta['prod']['crsEPSG'] = str(prod_meta['epsg'])
meta['prod']['crsWKT'] = prod_meta['wkt']
meta['prod']['demAccess'] = DEM_MAP[config['processing']['dem_type']]['access']
meta['prod']['demEGMReference'] = DEM_MAP[config['processing']['dem_type']]['egm']
meta['prod']['demEGMResamplingMethod'] = 'bilinear'
meta['prod']['demGSD'] = DEM_MAP[config['processing']['dem_type']]['gsd']
meta['prod']['demName'] = dem_name
meta['prod']['demReference'] = dem_ref
meta['prod']['demResamplingMethod'] = 'bilinear'
meta['prod']['demType'] = dem_type
meta['prod']['doi'] = None
meta['prod']['ellipsoidalHeight'] = None
meta['prod']['equivalentNumberOfLooks'] = calc_enl(tif=ref_tif)
# meta['prod']['fileBitsPerSample'] = '32'
# meta['prod']['fileByteOrder'] = 'little-endian'
# meta['prod']['fileDataType'] = 'float'
# meta['prod']['fileFormat'] = 'COG'
# meta['prod']['filterApplied'] = False
# meta['prod']['filterType'] = None
# meta['prod']['filterWindowSizeCol'] = None
# meta['prod']['filterWindowSizeLine'] = None
meta['prod']['geoCorrAccuracyEasternBias'] = dummy_num
meta['prod']['geoCorrAccuracyEasternSTDev'] = dummy_num
meta['prod']['geoCorrAccuracyNorthernBias'] = dummy_num
meta['prod']['geoCorrAccuracyNorthernSTDev'] = dummy_num
meta['prod']['geoCorrAccuracy_rRMSE'] = dummy_num
meta['prod']['geoCorrAccuracyReference'] = URL['geoCorrAccuracyReference']
meta['prod']['geoCorrAccuracyType'] = 'slant-range'
meta['prod']['geoCorrAlgorithm'] = URL['geoCorrAlgorithm']
meta['prod']['geoCorrResamplingMethod'] = 'bilinear'
meta['prod']['geom_stac_bbox_native'] = prod_meta['geom']['bbox_native']
meta['prod']['geom_stac_bbox_4326'] = prod_meta['geom']['bbox']
meta['prod']['geom_stac_geometry_4326'] = prod_meta['geom']['geometry']
meta['prod']['geom_xml_center'] = prod_meta['geom']['center']
meta['prod']['geom_xml_envelope'] = prod_meta['geom']['envelope']
meta['prod']['griddingConvention'] = 'Military Grid Reference System (MGRS)'
meta['prod']['griddingConventionURL'] = URL['griddingConventionURL']
meta['prod']['licence'] = None
meta['prod']['mgrsID'] = prod_meta['tile']
meta['prod']['noiseRemovalApplied'] = NOISE_MAP[sid0.acquisition_mode]
meta['prod']['noiseRemovalAlgorithm'] = URL['noiseRemovalAlgorithm']
meta['prod']['numberOfAcquisitions'] = str(len(src_ids))
meta['prod']['numBorderPixels'] = prod_meta['nodata_borderpx']
meta['prod']['numLines'] = str(prod_meta['rows'])
meta['prod']['numPixelsPerLine'] = str(prod_meta['cols'])
# meta['prod']['columnSpacing'] = prod_meta['columnSpacing']
# meta['prod']['rowSpacing'] = prod_meta['rowSpacing']
meta['prod']['pixelCoordinateConvention'] = 'upper-left'
meta['prod']['processingCenter'] = dummy_str
meta['prod']['location'] = dummy_str
meta['prod']['processingLevel'] = 'Level 2'
meta['prod']['processingMode'] = 'PROTOTYPE'
meta['prod']['processorName'] = 'asard'
sw_versions = version_dict(processor_name=config['processing']['processor'])
meta['prod']['processorVersion'] = sw_versions
meta['prod']['productName'] = 'Normalised Radar Backscatter'
meta['prod']['productName-short'] = 'NRB'
meta['prod']['pxSpacingColumn'] = str(prod_meta['res'][0])
meta['prod']['pxSpacingRow'] = str(prod_meta['res'][1])
meta['prod']['radiometricAccuracyAbsolute'] = dummy_num
meta['prod']['radiometricAccuracyRelative'] = dummy_num
meta['prod']['radiometricAccuracyReference'] = URL['radiometricAccuracyReference']
meta['prod']['rangeNumberOfLooks'] = sid0.meta['looks'][0]
meta['prod']['RTCAlgorithm'] = URL['RTCAlgorithm']
meta['prod']['speckleFilterApplied'] = None
meta['prod']['status'] = 'PROTOTYPE'
meta['prod']['timeCreated'] = prod_meta['proc_time']
meta['prod']['timeStart'] = prod_meta['start']
meta['prod']['timeStop'] = prod_meta['stop']
meta['prod']['transform'] = prod_meta['transform']
# meta['prod']['wrsLongitudeGrid'] = str(meta['common']['orbitNumbers_rel'])
# Source product metadata (sorted alphabetically)
for uid in list(src_sid.keys()):
meta['source'][uid] = {}
meta['source'][uid]['access'] = URL['source_access']
meta['source'][uid]['acquisitionType'] = 'NOMINAL'
meta['source'][uid]['ascendingNodeDate'] = None
meta['source'][uid]['azimuthLookBandwidth'] = {op_mode: dummy_num}
meta['source'][uid]['azimuthNumberOfLooks'] = {op_mode: src_sid[uid].meta['looks'][1]}
meta['source'][uid]['azimuthPixelSpacing'] = {op_mode: src_sid[uid].meta['spacing'][1]}
meta['source'][uid]['azimuthResolution'] = {op_mode: src_sid[uid].meta['resolution'][1]}
meta['source'][uid]['dataGeometry'] = src_sid[uid].meta['image_geometry'].replace('_', '-').lower()
# meta['source'][uid]['datatakeID'] = None # needed?
meta['source'][uid]['doi'] = URL['source_doi']
meta['source'][uid]['faradayMeanRotationAngle'] = None
meta['source'][uid]['faradayRotationReference'] = URL['faradayRotationReference']
meta['source'][uid]['filename'] = src_sid[uid].file
meta['source'][uid]['geom_stac_bbox_4326'] = prod_meta['geom']['bbox']
meta['source'][uid]['geom_stac_geometry_4326'] = prod_meta['geom']['geometry']
meta['source'][uid]['geom_xml_center'] = prod_meta['geom']['center']
meta['source'][uid]['geom_xml_envelope'] = prod_meta['geom']['envelope']
meta['source'][uid]['incidenceAngleMax'] = src_sid[uid].meta['incidence_fr']
meta['source'][uid]['incidenceAngleMin'] = src_sid[uid].meta['incidence_nr']
meta['source'][uid]['incidenceAngleMidSwath'] = src_sid[uid].meta['incidence']
meta['source'][uid]['instrumentAzimuthAngle'] = None
meta['source'][uid]['ionosphereIndicator'] = None
meta['source'][uid]['lutApplied'] = None
meta['source'][uid]['majorCycleID'] = str(sid0.meta['cycleNumber'])
meta['source'][uid]['orbitDataAccess'] = URL['orbitDataAccess']
osv = src_sid[uid].meta['origin']['DSD']['ORBIT STATE VECTOR 1']['FILENAME'] # use the one from processing!
meta['source'][uid]['orbitStateVector'] = osv
meta['source'][uid]['orbitDataSource'] = ORB_MAP[src_sid[uid].meta['origin']['MPH']['VECTOR_SOURCE']]
if len(np_tifs) > 0:
meta['source'][uid]['perfEstimates'] = calc_performance_estimates(files=np_tifs)
meta['source'][uid]['perfNoiseEquivalentIntensityType'] = 'sigma0'
else:
stats = {stat: None for stat in ['minimum', 'mean', 'maximum']}
pe = {pol: stats for pol in meta['common']['polarisationChannels']}
meta['source'][uid]['perfEstimates'] = pe
meta['source'][uid]['perfNoiseEquivalentIntensityType'] = None
meta['source'][uid]['perfEquivalentNumberOfLooks'] = None
meta['source'][uid]['perfIntegratedSideLobeRatio'] = None
meta['source'][uid]['perfPeakSideLobeRatio'] = None
meta['source'][uid]['polCalMatrices'] = None
meta['source'][uid]['processingCenter'] = src_sid[uid].meta['origin']['MPH']['PROC_CENTER']
proc_time = src_sid[uid].meta['origin']['MPH']['PROC_TIME']
meta['source'][uid]['processingDate'] = proc_time
meta['source'][uid]['processingLevel'] = 'Level 1'
meta['source'][uid]['processingMode'] = 'NOMINAL'
try:
proc_name, proc_version = src_sid[uid].meta['origin']['MPH']['SOFTWARE_VER'].split('/')
meta['source'][uid]['processorName'] = proc_name
meta['source'][uid]['processorVersion'] = {proc_name: proc_version}
except:
meta['source'][uid]['processorName'] = None
meta['source'][uid]['processorVersion'] = None
meta['source'][uid]['productType'] = src_sid[uid].meta['product']
meta['source'][uid]['rangeLookBandwidth'] = {op_mode: dummy_num}
meta['source'][uid]['rangeNumberOfLooks'] = {op_mode: src_sid[uid].meta['looks'][0]}
meta['source'][uid]['rangePixelSpacing'] = {op_mode: src_sid[uid].meta['spacing'][0]}
meta['source'][uid]['rangeResolution'] = {op_mode: src_sid[uid].meta['resolution'][0]}
meta['source'][uid]['sensorCalibration'] = URL['sensorCalibration']
meta['source'][uid]['status'] = 'ARCHIVED'
meta['source'][uid]['swaths'] = [op_mode]
meta['source'][uid]['timeCompletionFromAscendingNode'] = None
meta['source'][uid]['timeStartFromAscendingNode'] = None
meta['source'][uid]['timeStart'] = dateparse(src_sid[uid].start)
meta['source'][uid]['timeStop'] = dateparse(src_sid[uid].stop)
return meta