diff --git a/requirements.txt b/requirements.txt index f72277766a31b9834400c2da68b13522bb935497..63c41f9ac6c9ad8b9079d0c2eeed9d658ccc0a8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ geopandas>=0.5.0 scipy pillow python-crontab +packaging diff --git a/sen2chain/__init__.py b/sen2chain/__init__.py index edcd026ac40ab1e73d134163ad53b862b234c49e..ae8336082ae44b3a27fb1b881a64ab23e6ce44bc 100644 --- a/sen2chain/__init__.py +++ b/sen2chain/__init__.py @@ -28,7 +28,7 @@ from .indices import IndicesCollection from .download_and_process import DownloadAndProcess from .time_series import TimeSeries from .automatization import Automatization -from .utils import format_word, grouper, datetime_to_str, str_to_datetime, human_size_decimal, human_size, get_current_Sen2Cor_version +from .utils import format_word, grouper, datetime_to_str, str_to_datetime, human_size_decimal, human_size, get_Sen2Cor_version, get_latest_s2c_version_path from .geo_utils import serialise_tiles_index, get_processed_indices_vect, crop_product_by_shp from .multi_processing import l2a_multiprocessing, cld_version_probability_iterations_reprocessing_multiprocessing, idx_multiprocessing from .tileset import TileSet diff --git a/sen2chain/config.py b/sen2chain/config.py index 3454613b2e5b03375876a20b9bceefa5e5d4c0b5..697c9ba0f416b7bae5f0913eaf866c4dceef673f 100644 --- a/sen2chain/config.py +++ b/sen2chain/config.py @@ -44,17 +44,21 @@ class Config: def __init__(self) -> None: self._config_params = ConfigParser() - self._config_params["DATA PATHS"] = {"temp_path": "", - "l1c_path": "", - "l1c_archive_path": "", - "l2a_path": "", - "l2a_archive_path": "", - "indices_path": "", - "time_series_path": "", - "temporal_summaries_path": "", - "cloudmasks_path": "", - } - self._config_params["SEN2COR PATH"] = {"sen2cor_bashrc_path": ""} + self._config_params["DATA PATHS"] = { + "temp_path": "", + "l1c_path": "", + "l1c_archive_path": "", + "l2a_path": "", + "l2a_archive_path": "", + "indices_path": "", + "time_series_path": "", + "temporal_summaries_path": "", + "cloudmasks_path": "", + } + self._config_params["SEN2COR PATH"] = { + "sen2cor_bashrc_path": "", + "sen2cor_alternative_bashrc_path": "", + } self._config_params["HUBS LOGINS"] = {"scihub_id": "", "scihub_pwd": "", "peps_config_path": ""} diff --git a/sen2chain/products.py b/sen2chain/products.py index f93bff57706fd160c1a850635f9b04b731cb227b..3404d16cd658c3776b57856e0a935751de5531e3 100755 --- a/sen2chain/products.py +++ b/sen2chain/products.py @@ -12,9 +12,10 @@ import os from pathlib import Path # type annotations -from typing import List, Tuple, Optional +from typing import List, Tuple, Optional, Union +from packaging import version -from .utils import grouper, setPermissions, get_current_Sen2Cor_version +from .utils import grouper, setPermissions, get_Sen2Cor_version, get_latest_s2c_version_path from .config import Config, SHARED_DATA from .xmlparser import MetadataParser, Sen2ChainMetadataParser from .sen2cor import process_sen2cor @@ -178,20 +179,26 @@ class L1cProduct(Product): if not re.match(r".*L1C_.*", self.identifier): raise ValueError("Invalid L1C product name") - def process_l2a(self, reprocess: bool = False) -> "L1cProduct": - """ process sen2cor """ + def process_l2a( + self, + reprocess: bool = False, + s2c_path: Union[str, pathlib.PosixPath] = None, + ) -> "L1cProduct": + """ process with sen2cor """ logger.info("{}: processing L2A".format(self.identifier)) l2a_identifier = self.identifier.replace("L1C_", "L2A_").replace("_OPER_", "_USER_") l2a_path = self.path.parent / (l2a_identifier + ".SAFE") l2a_prod = L2aProduct(l2a_identifier, self.tile, l2a_path.parent) - + + process_it = False if not l2a_prod.in_library and not l2a_path.exists(): - process_it = True + s2c_path = s2c_path or get_latest_s2c_version_path(self.identifier) + if s2c_path: + process_it = True else: if not reprocess: logger.info("{} already exists.".format(l2a_identifier)) - process_it = False else: if l2a_prod.in_library: shutil.rmtree(str(l2a_prod.path)) @@ -200,11 +207,19 @@ class L1cProduct(Product): # if reprocessing from the temp folder shutil.rmtree(str(l2a_path)) logger.info("Deleted {}".format(l2a_path)) - - process_it = True + + #### check here if better to delete after sen2cor version check compatible... ?? + s2c_path = s2c_path or get_latest_s2c_version_path(self.identifier) + if s2c_path: + process_it = True if process_it: - process_sen2cor(self.path, l2a_path, self.processing_baseline) + process_sen2cor( + l1c_product_path = self.path, + l2a_product_path = l2a_path, + s2c_path = s2c_path, + pb = self.processing_baseline, + ) # sen2cor creates the L2A in the parent folder of the L1C. # Therefore, if the L1C computed is in the L1C library folder, # the newly L2A must be moved to his L2A library folder. @@ -212,7 +227,7 @@ class L1cProduct(Product): l2a_prod.archive() l2a_prod = L2aProduct(l2a_identifier) l2a_prod.setPermissions() - l2a_prod.update_md() + l2a_prod.update_md(sen2cor_version = get_Sen2Cor_version(s2c_path)) return self def process_ql(self, @@ -805,9 +820,13 @@ class L2aProduct(Product): sen2cor_version: str = None, ): """ Set custom sen2chain, sen2chain_processing and sen2cor versions """ - Sen2ChainMetadataParser(self._sen2chain_info_path).set_metadata(sen2chain_version = sen2chain_version, - sen2chain_processing_version = sen2chain_processing_version, - sen2cor_version = sen2cor_version) + Sen2ChainMetadataParser( + self._sen2chain_info_path + ).set_metadata( + sen2chain_version = sen2chain_version, + sen2chain_processing_version = sen2chain_processing_version, + sen2cor_version = sen2cor_version + ) def remove(self): if self.path.is_symlink(): diff --git a/sen2chain/sen2cor.py b/sen2chain/sen2cor.py index 5ff53a0b7ce8573189d4c439af771fc112c7c5fe..14670e83b5e8ea63eb6f87f47dda3c238e14b306 100644 --- a/sen2chain/sen2cor.py +++ b/sen2chain/sen2cor.py @@ -11,7 +11,7 @@ import subprocess from typing import Union from .config import Config -from .utils import get_current_Sen2Cor_version +from .utils import get_Sen2Cor_version, get_latest_s2c_version_path logger = logging.getLogger(__name__) @@ -21,6 +21,7 @@ logging.basicConfig(level=logging.INFO) def process_sen2cor( l1c_product_path: Union[str, pathlib.PosixPath], l2a_product_path: Union[str, pathlib.PosixPath], + s2c_path: Union[str, pathlib.PosixPath] = None, pb: str = '99.99', resolution: int = 10 ) -> None: @@ -36,56 +37,53 @@ def process_sen2cor( # TODO: Add 60m resolution. - sen2cor_bashrc_path = Config().get("sen2cor_bashrc_path") - #~ s2c_v = next(iter(re.findall('Sen2Cor-(\d{2}\.\d{2}\.\d{2})', str(sen2cor_bashrc_path))), None) - s2c_v = get_current_Sen2Cor_version() - l2a_product_path_tmp = l2a_product_path.parent / (l2a_product_path.stem + '.tmp') - - if s2c_v == '02.05.05': - logger.info("sen2cor {} processing: {}".format(s2c_v, l1c_product_path)) - if resolution == 10: - - logger.info("sen2cor processing 20 m: {}".format(l1c_product_path)) - command1 = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {res} {l1c_folder}".format( - sen2cor_bashrc=str(sen2cor_bashrc_path), - res=20, - l1c_folder=str(l1c_product_path) + s2c_path = s2c_path or get_latest_s2c_version_path(pathlib.Path(l1c_product_path).stem) + if s2c_path: + s2c_v = get_Sen2Cor_version(s2c_path) + l2a_product_path_tmp = l2a_product_path.parent / (l2a_product_path.stem + '.tmp') + if s2c_v == '02.05.05': + logger.info("sen2cor {} processing: {}".format(s2c_v, l1c_product_path)) + if resolution == 10: + logger.info("sen2cor processing 20 m: {}".format(l1c_product_path)) + command1 = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {res} {l1c_folder}".format( + sen2cor_bashrc=str(s2c_path), + res=20, + l1c_folder=str(l1c_product_path) + ) + process1 = subprocess.run(command1.split(", ")) + logger.info("sen2cor processing 10 m: {}".format(l1c_product_path)) + command2 = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {res} {l1c_folder}".format( + sen2cor_bashrc=str(s2c_path), + res=10, + l1c_folder=str(l1c_product_path) ) - - process1 = subprocess.run(command1.split(", ")) - - logger.info("sen2cor processing 10 m: {}".format(l1c_product_path)) - command2 = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {res} {l1c_folder}".format( - sen2cor_bashrc=str(sen2cor_bashrc_path), - res=10, - l1c_folder=str(l1c_product_path) + process2 = subprocess.run(command2.split(", ")) + + else: + logger.debug("sen2cor processing: {}".format(l1c_product_path)) + command = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {resolution} {l1c_folder}".format( + sen2cor_bashrc=str(s2c_path), + resolution=resolution, + l1c_folder=str(l1c_product_path) ) - process2 = subprocess.run(command2.split(", ")) + process = subprocess.run(command.split(", ")) + + elif s2c_v in ['02.08.00','02.09.00', '02.10.01']: + logger.info("sen2cor {} processing: {}".format(s2c_v, l1c_product_path)) + command = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --processing_baseline {processing_baseline} --output_dir {out_dir} {l1c_folder}".format( + sen2cor_bashrc = str(s2c_path), + processing_baseline = pb, + out_dir = l2a_product_path_tmp, + l1c_folder = str(l1c_product_path) + ) + process = subprocess.run(command.split(", ")) + sorted(l2a_product_path_tmp.glob("*.SAFE"))[0].rename(l2a_product_path.parent / (l2a_product_path.stem + '.SAFE')) + l2a_product_path_tmp.rmdir() + elif s2c_v is not None: + logger.info('Provided Sen2Cor version {} is not compatible with Sen2Chain yet'.format(s2c_v)) else: - logger.debug("sen2cor processing: {}".format(l1c_product_path)) - command = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --resolution {resolution} {l1c_folder}".format( - sen2cor_bashrc=str(sen2cor_bashrc_path), - resolution=resolution, - l1c_folder=str(l1c_product_path) - ) - process = subprocess.run(command.split(", ")) - - elif s2c_v in ['02.08.00','02.09.00', '02.10.01']: - logger.info("sen2cor {} processing: {}".format(s2c_v, l1c_product_path)) - command = "/bin/bash, -c, source {sen2cor_bashrc} && L2A_Process --processing_baseline {processing_baseline} --output_dir {out_dir} {l1c_folder}".format( - sen2cor_bashrc = str(sen2cor_bashrc_path), - processing_baseline = pb, - out_dir = l2a_product_path_tmp, - l1c_folder = str(l1c_product_path) - ) - process = subprocess.run(command.split(", ")) - sorted(l2a_product_path_tmp.glob("*.SAFE"))[0].rename(l2a_product_path.parent / (l2a_product_path.stem + '.SAFE')) - l2a_product_path_tmp.rmdir() - - elif s2c_v is not None: - logger.info('Sen2Cor version {} is not compatible with Sen2Chain'.format(s2c_v)) + logger.info('Could not determine Sen2Cor version from provided path, please check pattern "Sen2Cor-**.**.**" is in path') - else: - logger.info('Could not determine sen2cor version from path, please check pattern "Sen2Cor-**.**.**" is in path') + logger.info('Could not determine a compatible installed sen2cor version to process this product') diff --git a/sen2chain/tiles.py b/sen2chain/tiles.py index e0053c56c636d338d30ca87aa71e2a0dd36c94b3..85ffe04eed23b0682b24cb5614e59dad5bd1bea7 100644 --- a/sen2chain/tiles.py +++ b/sen2chain/tiles.py @@ -18,9 +18,10 @@ from datetime import datetime from pprint import pformat # type annotations from typing import List, Dict, Iterable +from packaging import version from .config import Config, SHARED_DATA -from .utils import str_to_datetime, human_size, getFolderSize +from .utils import str_to_datetime, human_size, getFolderSize, get_latest_s2c_version_path from .indices import IndicesCollection from .products import L1cProduct, L2aProduct, OldCloudMaskProduct, NewCloudMaskProduct, IndiceProduct from .multi_processing import l2a_multiprocessing, cld_version_probability_iterations_reprocessing_multiprocessing, idx_multiprocessing @@ -73,22 +74,61 @@ class ProductsList: return TileProduct(prod, max_date, self._dict[prod]["cloud_cover"]) def filter_dates( - self, - date_min: str = None, date_max: str = None + self, + date_min: str = None, + date_max: str = None, + date_exact: str = None, + ) -> "ProductsList": """Filters products list in a time range. - :param date_min: oldest date. :param date_max: newest date. """ min_date = str_to_datetime(date_min, "ymd") if date_min else self.first.date max_date = str_to_datetime(date_max, "ymd") if date_max else self.last.date filtered = ProductsList() - for k, v in self._dict.items(): - if min_date.date() <= v["date"].date() <= max_date.date(): - filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + if date_exact: + if type(date_exact) is list: + for date in date_exact: + if len(date) == 2: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%m") == date) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date) == 4: + for k, v in self._dict.items(): + if (str(v["date"].date().year) == date) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date) == 6: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%Y%m") == date) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date) == 10: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%Y-%m-%d") == date) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + else: + if len(date_exact) == 2: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%m") == date_exact) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date_exact) == 4: + for k, v in self._dict.items(): + if (str(v["date"].date().year) == date_exact) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date_exact) == 6: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%Y%m") == date_exact) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + elif len(date_exact) == 10: + for k, v in self._dict.items(): + if (datetime.strftime(v["date"].date(), "%Y-%m-%d") == date_exact) and (min_date.date() <= v["date"].date() <= max_date.date()): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + else: + for k, v in self._dict.items(): + if min_date.date() <= v["date"].date() <= max_date.date(): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} return filtered - + def filter_clouds( self, cover_min: int = 0, cover_max: int = 100 @@ -105,7 +145,24 @@ class ProductsList: else: filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} return filtered - + + def filter_processing_baseline( + self, + pb_min: str = "0.0", + pb_max: str = "9999", + ) -> "ProductsList": + """Filters products list with processing baseline version + :param pb_min: minimum processing baseline version + :param pb_max: maximum processing baseline version + """ + filtered = ProductsList() + for k, v in self._dict.items(): + if "L1C" in k: + processing_baseline = L1cProduct(identifier=k).processing_baseline + if version.parse(pb_min) <= version.parse(processing_baseline) <= version.parse(pb_max): + filtered[k] = {"date": v["date"], "cloud_cover": v["cloud_cover"]} + return filtered + def __len__(self) -> int: return len(self._dict) @@ -474,6 +531,17 @@ class Tile: #~ prods_list[prod] = {"date": self._products["l2a"][prod].date, #~ "cloud_cover": self._products["l2a"][prod].cloud_cover} #~ return prods_list + + @property + def l1c_processable(self) -> "ProductsList": + """Returns tile's L1C products that can be processes with sen2cor installation + """ + prod_list = ProductsList() + processable_l1c_set = {identifier for identifier in self.l1c.products if get_latest_s2c_version_path(identifier)} + for prod in processable_l1c_set: + prod_list[prod] = {"date": self._products["l1c"][prod].date, + "cloud_cover": self._products["l1c"][prod].cloud_cover} + return prod_list def cloudmasks_missing(self, cm_version: str = "cm001", @@ -568,6 +636,8 @@ class Tile: p_60m_missing: bool = False, date_min: str = None, date_max: str = None, + cover_min: int = 0, + cover_max: int = 100, nb_proc: int = 4): """ Compute all missing l2a for l1c products between date_min and date_max @@ -576,14 +646,19 @@ class Tile: """ if reprocess: if p_60m_missing: - l2a_remove_list = [product.identifier for product in self.l2a.filter_dates(date_min = date_min, date_max = date_max) if not L2aProduct(product.identifier).b01_60m] + l2a_remove_list = [product.identifier for product in self.l2a.filter_dates(date_min = date_min, date_max = date_max).filter_clouds(cover_min = cover_min, cover_max = cover_max) if not L2aProduct(product.identifier).b01_60m] else: - l2a_remove_list = [product.identifier for product in self.l2a.filter_dates(date_min = date_min, date_max = date_max)] + l2a_remove_list = [product.identifier for product in self.l2a.filter_dates(date_min = date_min, date_max = date_max).filter_clouds(cover_min = cover_min, cover_max = cover_max)] if l2a_remove_list: self.remove_l2a(l2a_remove_list) - l1c_process_list = [] - l1c_process_list.append(list(p.identifier for p in self.l2a_missings.filter_dates(date_min = date_min, date_max = date_max))) - l1c_process_list = list(chain.from_iterable(l1c_process_list)) + l1c_process_list = [ + product.identifier for product in ( + set(self.l2a_missings.filter_dates(date_min = date_min, date_max = date_max).filter_clouds(cover_min = cover_min, cover_max = cover_max)) + & + set(self.l1c_processable) + ) + ] + if l1c_process_list: logger.info("{} l1c products to process:".format(len(l1c_process_list))) logger.info("{}".format(l1c_process_list)) @@ -592,8 +667,6 @@ class Tile: l2a_res = False if l1c_process_list: l2a_res = l2a_multiprocessing(l1c_process_list, nb_proc=nb_proc) - - #~ def compute_cloudmasks(self, #~ version: str = "cm001", diff --git a/sen2chain/utils.py b/sen2chain/utils.py index f06a4e368c37a3c080a56bba7414ae624ea39d69..195568a13b97a58040eb7bedbb2a361f9e88a37f 100644 --- a/sen2chain/utils.py +++ b/sen2chain/utils.py @@ -8,10 +8,14 @@ import os, stat import logging from itertools import zip_longest from datetime import datetime -from typing import Iterable +from typing import Iterable, Union import re +import pathlib +from packaging import version + from .config import Config +# from .products import L1cProduct logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -20,6 +24,7 @@ logging.basicConfig(level=logging.INFO) # useful dates formats encountered when working with Sentinel-2 data DATES_FORMATS = dict( ymd="%Y-%m-%d", + Ymd = "%Y%m%d", filename="%Y%m%dT%H%M%S", metadata="%Y-%m-%dT%H:%M:%S.%fZ") @@ -100,7 +105,7 @@ def getFolderSize(folder, follow_symlinks = False): pass return total_size -def setPermissions(path): +def set_permissions(path): os.chmod(str(path), os.stat(str(path)).st_mode | stat.S_IWGRP | stat.S_IWUSR) os.chmod(str(path), os.stat(str(path)).st_mode & ~stat.S_IWOTH) for dir_path, dir_names, files in os.walk(str(path)): @@ -118,11 +123,42 @@ def setPermissions(path): os.chmod(name, os.stat(name).st_mode | stat.S_IWGRP | stat.S_IWUSR) os.chmod(name, os.stat(name).st_mode & ~stat.S_IXOTH & ~stat.S_IWOTH) -def get_current_Sen2Cor_version(): - """ Returns your current Sen2Cor version """ - sen2cor_bashrc_path = Config().get("sen2cor_bashrc_path") - return next(iter(re.findall('Sen2Cor-(\d{2}\.\d{2}\.\d{2})', str(sen2cor_bashrc_path))), None) - - +def get_Sen2Cor_version(path: Union[str, pathlib.PosixPath] = None): + """ Returns + - the current Sen2Cor version if no argument provided + - the Sen2Cor version if a Sen2Cor path is provided + """ + if not path: + path = Config().get("sen2cor_bashrc_path") + return next(iter(re.findall('Sen2Cor-(\d{2}\.\d{2}\.\d{2})', str(path))), None) + +def get_latest_s2c_version_path(l1c_identifier): + """ Returns + - the path of the first Sen2Cor compatible version, starting from sen2cor_bashrc_path then sen2cor_alternative_bashrc_path + - None if no compatible Sen2Cor version is provided + """ + from .products import L1cProduct + current_path = Config().get("sen2cor_bashrc_path") + alternate_path = Config().get("sen2cor_alternative_bashrc_path") - + if version.parse(L1cProduct(l1c_identifier).processing_baseline) >= version.parse("4.0"): + if version.parse(get_Sen2Cor_version(current_path)) >= version.parse("2.10.01"): + return current_path + else: + if version.parse(get_Sen2Cor_version(alternate_path)) >= version.parse("2.10.01"): + return alternate_path + else: + return None + elif version.parse(L1cProduct(l1c_identifier).processing_baseline) >= version.parse("2.06"): + return current_path + else: + if version.parse(get_Sen2Cor_version(current_path)) >= version.parse("2.10.01"): + if alternate_path: + if version.parse(get_Sen2Cor_version(alternate_path)) >= version.parse("2.10.01"): + return None + else: + return alternate_path + else: + return None + else: + return current_path diff --git a/sen2chain/xmlparser.py b/sen2chain/xmlparser.py index ed0cac46f3914c74f906a7f4697ba0e2893f363a..48d98d3c5bd81d530adeb8ae834780e2cb90dbd0 100644 --- a/sen2chain/xmlparser.py +++ b/sen2chain/xmlparser.py @@ -10,7 +10,7 @@ import re import xml.etree.ElementTree as et import sen2chain -from .utils import get_current_Sen2Cor_version +from .utils import get_Sen2Cor_version from .config import SHARED_DATA, Config @@ -169,7 +169,7 @@ class Sen2ChainMetadataParser: def init_metadata(self): self._root.find("SEN2CHAIN_VERSION").text = sen2chain.__version__ self._root.find("SEN2CHAIN_PROCESSING_VERSION").text = Config().get("sen2chain_processing_version") - self._root.find("SEN2COR_VERSION").text = get_current_Sen2Cor_version() + self._root.find("SEN2COR_VERSION").text = get_Sen2Cor_version() self._tree.write(str(self.xml_path), encoding="UTF-8", xml_declaration=True) def set_metadata(self, @@ -179,6 +179,6 @@ class Sen2ChainMetadataParser: ): self._root.find("SEN2CHAIN_VERSION").text = sen2chain_version or sen2chain.__version__ self._root.find("SEN2CHAIN_PROCESSING_VERSION").text = sen2chain_processing_version or Config().get("sen2chain_processing_version") - self._root.find("SEN2COR_VERSION").text = sen2cor_version or get_current_Sen2Cor_version() + self._root.find("SEN2COR_VERSION").text = sen2cor_version or get_Sen2Cor_version() self._tree.write(str(self.xml_path), encoding="UTF-8", xml_declaration=True)