From 6eb8ab402c5817ebf774bb82b757caec883a26d4 Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 4 Dec 2023 16:45:34 -0900 Subject: [PATCH] adds baseline functionality to other product types --- asf_search/ASFProduct.py | 10 +++-- asf_search/Products/ALOSProduct.py | 22 +++++++---- asf_search/Products/ARIAS1GUNWProduct.py | 5 ++- asf_search/Products/ERSProduct.py | 37 ++++++++++++++++++- asf_search/Products/JERSProduct.py | 37 ++++++++++++++++++- asf_search/Products/OPERAS1Product.py | 10 ++++- asf_search/Products/RadarsatProduct.py | 29 +++++++++++++-- asf_search/Products/S1BURSTProduct.py | 16 ++++++-- asf_search/Products/S1Product.py | 47 +++++++++++++++++------- asf_search/baseline/stack.py | 25 ++++--------- asf_search/search/baseline_search.py | 2 +- asf_search/search/search_generator.py | 2 - 12 files changed, 186 insertions(+), 56 deletions(-) diff --git a/asf_search/ASFProduct.py b/asf_search/ASFProduct.py index 8fe5bc44..d5ddeedc 100644 --- a/asf_search/ASFProduct.py +++ b/asf_search/ASFProduct.py @@ -143,9 +143,9 @@ def get_stack_opts(self) -> ASFSearchOptions: :return: ASFSearchOptions describing appropriate options for building a stack from this product """ - from .search.baseline_search import get_stack_opts + # from .search.baseline_search import get_stack_opts - return get_stack_opts(reference=self) + return {} def centroid(self) -> Point: """ @@ -212,7 +212,8 @@ def _get_property_paths() -> dict: def get_baseline_calc_properties(self) -> dict: return {} - def get_default_product_type(self): + @staticmethod + def get_default_product_type(): # scene_name = product.properties['sceneName'] # if get_platform(scene_name) in ['AL']: @@ -224,3 +225,6 @@ def get_default_product_type(self): # return 'BURST' # return 'SLC' return None + + def is_valid_reference(self): + return False \ No newline at end of file diff --git a/asf_search/Products/ALOSProduct.py b/asf_search/Products/ALOSProduct.py index f4956def..4b1b605b 100644 --- a/asf_search/Products/ALOSProduct.py +++ b/asf_search/Products/ALOSProduct.py @@ -31,17 +31,17 @@ def get_baseline_calc_properties(self) -> dict: return None - def get_stack_opts(self, reference: ASFProduct, + def get_stack_opts(self, opts: ASFSearchOptions = None): stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) - stack_opts.processingLevel = self.get_default_product_type(reference) + stack_opts.processingLevel = ALOSProduct.get_default_product_type() - if reference.properties['insarStackId'] not in [None, 'NA', 0, '0']: - stack_opts.insarStackId = reference.properties['insarStackId'] + if self.properties.get('insarStackId') not in [None, 'NA', 0, '0']: + stack_opts.insarStackId = self.properties['insarStackId'] return stack_opts - raise ASFBaselineError(f'Requested reference product needs a baseline stack ID but does not have one: {reference.properties["fileID"]}') + raise ASFBaselineError(f'Requested reference product needs a baseline stack ID but does not have one: {self.properties["fileID"]}') @staticmethod def _get_property_paths() -> dict: @@ -50,5 +50,13 @@ def _get_property_paths() -> dict: **ALOSProduct.base_properties } - def get_default_product_type(self): - return 'L1.1' \ No newline at end of file + @staticmethod + def get_default_product_type(): + return 'L1.1' + + def is_valid_reference(self): + # we don't stack at all if any of stack is missing insarBaseline, unlike stacking S1 products(?) + if 'insarBaseline' not in self.baseline: + raise ValueError('No baseline values available for precalculated dataset') + + return True \ No newline at end of file diff --git a/asf_search/Products/ARIAS1GUNWProduct.py b/asf_search/Products/ARIAS1GUNWProduct.py index 47934ba0..4f13d4b6 100644 --- a/asf_search/Products/ARIAS1GUNWProduct.py +++ b/asf_search/Products/ARIAS1GUNWProduct.py @@ -25,5 +25,6 @@ def _get_property_paths() -> dict: **ARIAS1GUNWProduct.base_properties } - def get_default_product_type(self): - return 'BURST' + @staticmethod + def get_default_product_type(): + return None diff --git a/asf_search/Products/ERSProduct.py b/asf_search/Products/ERSProduct.py index 5f97ebd2..cdbec2cd 100644 --- a/asf_search/Products/ERSProduct.py +++ b/asf_search/Products/ERSProduct.py @@ -1,7 +1,8 @@ import copy -from asf_search import ASFSession, ASFProduct +from asf_search import ASFSearchOptions, ASFSession, ASFProduct from asf_search.CMR.translate import get_state_vector, get as umm_get, cast as umm_cast, try_parse_float, try_parse_int, try_round_float from asf_search.constants import PLATFORM +from asf_search.exceptions import ASFBaselineError class ERSProduct(ASFProduct): base_properties = { @@ -20,6 +21,29 @@ class ERSProduct(ASFProduct): def __init__(self, args: dict = {}, session: ASFSession = ASFSession()): super().__init__(args, session) + self.baseline = self.get_baseline_calc_properties() + + def get_baseline_calc_properties(self) -> dict: + insarBaseline = umm_cast(float, umm_get(self.umm, 'AdditionalAttributes', ('Name', 'INSAR_BASELINE'), 'Values', 0)) + + if insarBaseline is not None: + return { + 'insarBaseline': insarBaseline + } + + return None + + def get_stack_opts(self, + opts: ASFSearchOptions = None): + + stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) + stack_opts.processingLevel = ERSProduct.get_default_product_type() + + if self.properties.get('insarStackId') not in [None, 'NA', 0, '0']: + stack_opts.insarStackId = self.properties['insarStackId'] + return stack_opts + + raise ASFBaselineError(f'Requested reference product needs a baseline stack ID but does not have one: {self.properties["fileID"]}') @staticmethod def _get_property_paths() -> dict: @@ -27,3 +51,14 @@ def _get_property_paths() -> dict: **ASFProduct._get_property_paths(), **ERSProduct.base_properties } + + @staticmethod + def get_default_product_type(): + return 'L0' + + def is_valid_reference(self): + # we don't stack at all if any of stack is missing insarBaseline, unlike stacking S1 products(?) + if 'insarBaseline' not in self.baseline: + raise ValueError('No baseline values available for precalculated dataset') + + return True \ No newline at end of file diff --git a/asf_search/Products/JERSProduct.py b/asf_search/Products/JERSProduct.py index 54f00e63..d70b6912 100644 --- a/asf_search/Products/JERSProduct.py +++ b/asf_search/Products/JERSProduct.py @@ -1,7 +1,8 @@ import copy -from asf_search import ASFSession, ASFProduct +from asf_search import ASFSearchOptions, ASFSession, ASFProduct from asf_search.CMR.translate import get_state_vector, get as umm_get, cast as umm_cast, try_parse_float, try_parse_int from asf_search.constants import PLATFORM +from asf_search.exceptions import ASFBaselineError class JERSProduct(ASFProduct): base_properties = { @@ -20,6 +21,17 @@ class JERSProduct(ASFProduct): def __init__(self, args: dict = {}, session: ASFSession = ASFSession()): super().__init__(args, session) + self.baseline = self.get_baseline_calc_properties() + + def get_baseline_calc_properties(self) -> dict: + insarBaseline = umm_cast(float, umm_get(self.umm, 'AdditionalAttributes', ('Name', 'INSAR_BASELINE'), 'Values', 0)) + + if insarBaseline is not None: + return { + 'insarBaseline': insarBaseline + } + + return None @staticmethod def _get_property_paths() -> dict: @@ -27,3 +39,26 @@ def _get_property_paths() -> dict: **ASFProduct._get_property_paths(), **JERSProduct.base_properties } + + def get_stack_opts(self, + opts: ASFSearchOptions = None): + + stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) + stack_opts.processingLevel = JERSProduct.get_default_product_type() + + if self.properties.get('insarStackId') not in [None, 'NA', 0, '0']: + stack_opts.insarStackId = self.properties['insarStackId'] + return stack_opts + + raise ASFBaselineError(f'Requested reference product needs a baseline stack ID but does not have one: {self.properties["fileID"]}') + + @staticmethod + def get_default_product_type(): + return 'L0' + + def is_valid_reference(self): + # we don't stack at all if any of stack is missing insarBaseline, unlike stacking S1 products(?) + if 'insarBaseline' not in self.baseline: + raise ValueError('No baseline values available for precalculated dataset') + + return True \ No newline at end of file diff --git a/asf_search/Products/OPERAS1Product.py b/asf_search/Products/OPERAS1Product.py index 966b6d46..ebab10f4 100644 --- a/asf_search/Products/OPERAS1Product.py +++ b/asf_search/Products/OPERAS1Product.py @@ -54,5 +54,13 @@ def _get_property_paths() -> dict: **OPERAS1Product.base_properties } - def get_default_product_type(self): + @staticmethod + def get_default_product_type(): return 'CSLC' + + def is_valid_reference(self): + # we don't stack at all if any of stack is missing insarBaseline, unlike stacking S1 products(?) + if 'insarBaseline' not in self.baseline: + raise ValueError('No baseline values available for precalculated dataset') + + return True \ No newline at end of file diff --git a/asf_search/Products/RadarsatProduct.py b/asf_search/Products/RadarsatProduct.py index 75aa3a99..b247bfa6 100644 --- a/asf_search/Products/RadarsatProduct.py +++ b/asf_search/Products/RadarsatProduct.py @@ -1,5 +1,7 @@ -from asf_search import ASFSession, ASFProduct +import copy +from asf_search import ASFSearchOptions, ASFSession, ASFProduct from asf_search.CMR.translate import get as umm_get, cast as umm_cast, try_parse_float, try_parse_int +from asf_search.exceptions import ASFBaselineError class RadarsatProduct(ASFProduct): base_properties = { @@ -29,6 +31,18 @@ def get_baseline_calc_properties(self) -> dict: return None + def get_stack_opts(self, + opts: ASFSearchOptions = None): + + stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) + stack_opts.processingLevel = RadarsatProduct.get_default_product_type() + + if self.properties.get('insarStackId') not in [None, 'NA', 0, '0']: + stack_opts.insarStackId = self.properties['insarStackId'] + return stack_opts + + raise ASFBaselineError(f'Requested reference product needs a baseline stack ID but does not have one: {self.properties["fileID"]}') + @staticmethod def _get_property_paths() -> dict: return { @@ -36,6 +50,13 @@ def _get_property_paths() -> dict: **RadarsatProduct.base_properties } - def get_default_product_type(self): - # if get_platform(scene_name) in ['R1', 'E1', 'E2', 'J1']: - return 'L0' \ No newline at end of file + @staticmethod + def get_default_product_type(): + return 'L0' + + def is_valid_reference(self): + # we don't stack at all if any of stack is missing insarBaseline, unlike stacking S1 products(?) + if 'insarBaseline' not in self.baseline: + raise ValueError('No baseline values available for precalculated dataset') + + return True \ No newline at end of file diff --git a/asf_search/Products/S1BURSTProduct.py b/asf_search/Products/S1BURSTProduct.py index 2c503a33..d2911c66 100644 --- a/asf_search/Products/S1BURSTProduct.py +++ b/asf_search/Products/S1BURSTProduct.py @@ -1,4 +1,5 @@ -from asf_search import ASFSession +import copy +from asf_search import ASFSearchOptions, ASFSession from asf_search.Products import S1Product from asf_search.CMR.translate import get, try_parse_int from asf_search.CMR.translate import get_state_vector, get as umm_get, cast as umm_cast @@ -17,7 +18,7 @@ class S1BURSTProduct(S1Product): def __init__(self, args: dict = {}, session: ASFSession = ASFSession()): super().__init__(args, session) self.properties['sceneName'] = self.properties['fileID'] - self.properties['bytes'] = umm_get(self.umm, ['AdditionalAttributes', ('Name', 'BYTE_LENGTH'), 'Values', 0]) + self.properties['bytes'] = umm_get(self.umm, 'AdditionalAttributes', ('Name', 'BYTE_LENGTH'), 'Values', 0) self.properties['burst'] = { 'absoluteBurstID': self.properties.pop('absoluteBurstID'), 'relativeBurstID': self.properties.pop('relativeBurstID'), @@ -35,6 +36,14 @@ def __init__(self, args: dict = {}, session: ASFSession = ASFSession()): self.properties['fileName'] = self.properties['fileID'] + '.' + urls[0].split('.')[-1] self.properties['additionalUrls'] = [urls[1]] + def get_stack_opts(self, opts: ASFSearchOptions = None): + stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) + + stack_opts.processingLevel = S1BURSTProduct.get_default_product_type() + stack_opts.fullBurstID = self.properties['burst']['fullBurstID'] + stack_opts.polarization = [self.properties['polarization']] + return stack_opts + @staticmethod def _get_property_paths() -> dict: return { @@ -42,5 +51,6 @@ def _get_property_paths() -> dict: **S1BURSTProduct.base_properties } - def get_default_product_type(self): + @staticmethod + def get_default_product_type(): return 'BURST' diff --git a/asf_search/Products/S1Product.py b/asf_search/Products/S1Product.py index f3caf158..73665cae 100644 --- a/asf_search/Products/S1Product.py +++ b/asf_search/Products/S1Product.py @@ -1,5 +1,5 @@ import copy -from asf_search import ASFSession, ASFProduct +from asf_search import ASFSearchOptions, ASFSession, ASFProduct from asf_search.CMR.translate import get_state_vector, get as umm_get, cast as umm_cast, try_parse_int from asf_search.constants import PLATFORM @@ -48,18 +48,29 @@ def get_state_vectors(self) -> dict: 'velocities': velocities } - def get_stack_opts(self): + def get_stack_opts(self, + opts: ASFSearchOptions = None): - # stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) - return { - 'processingLevel': 'SLC', - 'beamMode': [self.properties['beamModeType']], - 'flightDirection': self.properties['flightDirection'], - 'relativeOrbit': [int(self.properties['pathNumber'])], # path - 'platform': [PLATFORM.SENTINEL1A, PLATFORM.SENTINEL1B], - 'polarization': ['HH','HH+HV'] if self.properties['polarization'] in ['HH','HH+HV'] else ['VV', 'VV+VH'], - 'intersectsWith': self.centroid().wkt - } + stack_opts = (ASFSearchOptions() if opts is None else copy(opts)) + + stack_opts.processingLevel = S1Product.get_default_product_type() + stack_opts.beamMode = [self.properties['beamModeType']] + stack_opts.flightDirection = self.properties['flightDirection'] + stack_opts.relativeOrbit = [int(self.properties['pathNumber'])], # path + stack_opts.platform = [PLATFORM.SENTINEL1A, PLATFORM.SENTINEL1B] + stack_opts.polarization = ['HH','HH+HV'] if self.properties['polarization'] in ['HH','HH+HV'] else ['VV', 'VV+VH'] + stack_opts.intersectsWith = self.centroid().wkt + + return stack_opts + # return { + # 'processingLevel': 'SLC', + # 'beamMode': [self.properties['beamModeType']], + # 'flightDirection': self.properties['flightDirection'], + # 'relativeOrbit': [int(self.properties['pathNumber'])], # path + # 'platform': [PLATFORM.SENTINEL1A, PLATFORM.SENTINEL1B], + # 'polarization': ['HH','HH+HV'] if self.properties['polarization'] in ['HH','HH+HV'] else ['VV', 'VV+VH'], + # 'intersectsWith': self.centroid().wkt + # } # if reference.properties['processingLevel'] == 'BURST': # stack_opts.processingLevel = 'BURST' # else: @@ -93,5 +104,15 @@ def _get_property_paths() -> dict: **S1Product.base_properties } - def get_default_product_type(self): + @staticmethod + def get_default_product_type(): return 'SLC' + + def is_valid_reference(self): + return self.valid_state_vectors() + + def valid_state_vectors(self): + for key in ['postPosition', 'postPositionTime', 'prePosition', 'postPositionTime']: + if key not in self.baseline['stateVectors']['positions'] or self.baseline['stateVectors']['positions'][key] == None: + return False + return True \ No newline at end of file diff --git a/asf_search/baseline/stack.py b/asf_search/baseline/stack.py index 638917ee..ad3f8dac 100644 --- a/asf_search/baseline/stack.py +++ b/asf_search/baseline/stack.py @@ -22,17 +22,9 @@ def get_baseline_from_stack(reference: ASFProduct, stack: ASFSearchResults): return ASFSearchResults(stack), warnings -def valid_state_vectors(product: ASFProduct): - if product is None: - raise ValueError('Attempting to check state vectors on None, this is fatal') - for key in ['postPosition', 'postPositionTime', 'prePosition', 'postPositionTime']: - if key not in product.baseline['stateVectors']['positions'] or product.baseline['stateVectors']['positions'][key] == None: - return False - return True - def find_new_reference(stack: ASFSearchResults): for product in stack: - if valid_state_vectors(product): + if product.is_valid_reference(): return product return None @@ -42,15 +34,12 @@ def check_reference(reference: ASFProduct, stack: ASFSearchResults): reference = stack[0] warnings = [{'NEW_REFERENCE': 'A new reference scene had to be selected in order to calculate baseline values.'}] - if get_platform(reference.properties['sceneName']) in precalc_datasets: - if 'insarBaseline' not in reference.baseline: - raise ValueError('No baseline values available for precalculated dataset') - else: - if not valid_state_vectors(reference): # the reference might be missing state vectors, pick a valid reference, replace above warning if it also happened - reference = find_new_reference(stack) - if reference == None: - raise ValueError('No valid state vectors on any scenes in stack, this is fatal') - warnings = [{'NEW_REFERENCE': 'A new reference had to be selected in order to calculate baseline values.'}] + # non-s1 is_valid_reference raise an error, while we try to find a valid s1 reference + # do we want this behaviour for pre-calc stacks? + if not reference.is_valid_reference(): + reference = find_new_reference(stack) + if reference == None: + raise ValueError('No valid state vectors on any scenes in stack, this is fatal') return reference, stack, warnings diff --git a/asf_search/search/baseline_search.py b/asf_search/search/baseline_search.py index fda69709..3c77b059 100644 --- a/asf_search/search/baseline_search.py +++ b/asf_search/search/baseline_search.py @@ -33,7 +33,7 @@ def stack_from_product( opts = (ASFSearchOptions() if opts is None else copy(opts)) - opts.merge_args(**reference.get_stack_opts()) + opts.merge_args(**dict(reference.get_stack_opts())) stack = search(opts=opts) is_complete = stack.searchComplete diff --git a/asf_search/search/search_generator.py b/asf_search/search/search_generator.py index 0a6244b4..77352320 100644 --- a/asf_search/search/search_generator.py +++ b/asf_search/search/search_generator.py @@ -260,8 +260,6 @@ def as_ASFProduct(item: dict, session: ASFSession) -> ASFProduct: 'OPERA-S1': ASFProductType.OPERAS1Product, 'SLC-BURST': ASFProductType.S1BURSTProduct, 'ALOS': ASFProductType.ALOSProduct, - # 'ALOS PALSAR': ASFProductType.ALOSProduct, - # 'ALOS AVNIR-2': ASFProductType.ALOSProduct, 'SIR-C': ASFProductType.SIRCProduct, 'ARIA S1 GUNW': ASFProductType.ARIAS1GUNWProduct, 'SMAP': ASFProductType.SMAPProduct,