From a80a0e5dc2b9eb875ec6a9fd5d9ae9c2d3d42786 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 09:23:05 +0100 Subject: [PATCH 01/27] ADD: logo --- doc/logo.png | Bin 0 -> 7532 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/logo.png diff --git a/doc/logo.png b/doc/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fc854dbcfc60b8aa0f40a0f8df459c96e424716e GIT binary patch literal 7532 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 CR%L(y literal 0 HcmV?d00001 -- GitLab From cd600b49f9b8f7f498b65e79b5385be14ae4e700 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:16:27 +0100 Subject: [PATCH 02/27] REFAC: architecture --- scenes/core.py | 152 +++++++++++++++++++++++++++++++++++++++++++ scenes/indexation.py | 2 +- scenes/spot.py | 143 +++++++--------------------------------- scenes/utils.py | 18 ++++- 4 files changed, 194 insertions(+), 121 deletions(-) create mode 100644 scenes/core.py diff --git a/scenes/core.py b/scenes/core.py new file mode 100644 index 0000000..d2f3de3 --- /dev/null +++ b/scenes/core.py @@ -0,0 +1,152 @@ +""" +This module provides the Source class, which aims to handle the access to the rasters delivered by EO products +""" +import pyotb +from abc import ABC, abstractmethod +from scenes import utils +import datetime + +class Source(pyotb.Output): + """ + Source class. + Holds common operations on image sources (e.g. drill, resample, extract an ROI, etc.) + """ + def __init__(self, root_imagery, out, parent=None): + """ + :param root_imagery: root Imagery instance + :param out: image to deliver (can be an image filename (str), a pyotb.App, etc.) + :param parent: parent Source instance + """ + assert isinstance(root_imagery, Imagery) + self.root_imagery = root_imagery # root imagery + # Here we call the pyotb.Output() constructor. + # Since it can only be called with pyotb apps, we do the following: + # - if the output is a str, (e.g. the original dimap filename), we instantiate a pyotb.Input(), + # - else we use the original output (should be pyotb application) + super().__init__(app=pyotb.Input(out) if isinstance(out, str) else out, output_parameter_key="out") + assert parent is not self, "You cannot assign a new source to its parent instance" + self.parent = parent # parent source (is another Source instance) + self._app_stack = [] # list of otb applications or output to keep trace + + def new_source(self, *args): + """ + Return a new Source instance with new apps added at the end of the pipeline. + :param *args: list of pyotb.app instances to append to the existing pipeline + :return: new source + """ + for new_app in args: + self._app_stack.append(new_app) + return Source(root_imagery=self.root_imagery, out=self._app_stack[-1], parent=self) + + def drilled(self, msk_vec_file, inside=True, nodata=0): + """ + Return the source drilled from the input vector data. + The default behavior is that the hole is made inside the polygon. + This can be changed setting the "inside" parameter to False. + :param msk_vec_file: input vector data filename + :param inside: whether the drill is happening inside the polygon or outside + :param nodata: nodata value inside holes + :return: drilled source + """ + if utils.open_vector_layer(msk_vec_file): + # Cloud mask is not empty + rasterization = pyotb.Rasterization({"in": msk_vec_file, + "im": self, + "mode": "binary", + "mode.binary.foreground": 0 if inside else 255, + "background": 255 if inside else 255}) + manage_nodata = pyotb.ManageNoData({"in": self, + "mode": "apply", + "mode.apply.mask": rasterization, + "mode.apply.ndval": nodata}) + return self.new_source(rasterization, manage_nodata) + return self # Nothing but a soft copy of the source + + def resample_over(self, ref_img, interpolator="nn", nodata=0): + """ + Return the source superimposed over the input image + :param ref_img: reference image + :param interpolator: interpolator + :param nodata: no data value + :return: resampled image source + """ + return self.new_source(pyotb.Superimpose({"inm": self, + "inr": ref_img, + "interpolator": interpolator, + "fv": nodata})) + + def clip_over_img(self, ref_img): + """ + Return the source clipped over the ROI specified by the input image extent + :param ref_img: reference image + :return: ROI clipped source + """ + return self.new_source(pyotb.ExtractROI({"in": self, + "mode": "fit", + "mode.fit.im": ref_img})) + + def clip_over_vec(self, ref_vec): + """ + Return the source clipped over the ROI specified by the input vector extent + :param ref_vec: reference vector data + :return: ROI clipped source + """ + return self.new_source(pyotb.ExtractROI({"in": self, + "mode": "fit", + "mode.fit.vec": ref_vec})) + + +class Imagery: + """ + Imagery class. + This class carry the base image source, and additional generic stuff common to all sensors imagery. + """ + + def __init__(self, root_scene): + """ + :param root_scene: The Scene of which the Imagery instance is attached + """ + self.root_scene = root_scene + + +class Scene(ABC): + """ + Scene class. + The class carries all the metadata from the scene, and can be used to retrieve its imagery. + The get_imagery() function is abstract and must be implemented in child classes. + """ + + def __init__(self, acquisition_date, bbox_wgs84, epsg): + """ + Constructor + :param acquisition_date: Acquisition date + :param bbox_wgs84: Bounding box, in WGS84 coordinates reference system + :param epsg: EPSG code + """ + + assert isinstance(acquisition_date, datetime.datetime), "acquisition_date must be a datetime.datetime instance" + self.acquisition_date = acquisition_date + self.bbox_wgs84 = bbox_wgs84 + assert isinstance(epsg, int), "epsg must be an int" + self.epsg = epsg + + @abstractmethod + def get_imagery(self, **kwargs): + """ + Must be implemented in child classes. + Return the imagery. + :param **kwargs: Imagery options + :return: Imagery instance + """ + pass + + def __repr__(self): + """ + Enable one instance to be used with print() + """ + out = { + "Acquisition date": self.acquisition_date, + "Bounding box (WGS84)": self.bbox_wgs84, + "EPSG": self.epsg, + } + return "\n".join(["{}: {}".format(key, value) for key, value in out.items()]) diff --git a/scenes/indexation.py b/scenes/indexation.py index e3026a7..3de42ba 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -54,7 +54,7 @@ class Index: properties.dimension = 3 self.index = rtree.index.Index(properties=properties) for scene_idx, scene in enumerate(scenes_list): - bbox = self.new_bbox(bbox_wgs84=scene.bbox, dt=scene.acquisition_date) + bbox = self.new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date) self.index.insert(scene_idx, bbox) def find_indices(self, bbox_wgs84, dt_min=None, dt_max=None): diff --git a/scenes/spot.py b/scenes/spot.py index 1a39146..921fa55 100644 --- a/scenes/spot.py +++ b/scenes/spot.py @@ -3,67 +3,15 @@ Spot 6/7 root_scene class """ import datetime import xml.etree.ElementTree as ET -import gdal -from scenes import utils import pyotb +from scenes import utils +from scenes.core import Source, Imagery, Scene -class Source(pyotb.Output): +class Spot67Source(Source): """ - Spot 6/7 source class. - Holds common operations on image sources (e.g. drill, resample, extract an ROI, etc.) + Spot 6/7 source class """ - - def __init__(self, root_imagery, out, parent=None): - """ - :param root_imagery: root Imagery instance - :param out: image to deliver (can be an image filename (str), a pyotb.App, etc.) - :param parent: parent Source instance - """ - self.root_imagery = root_imagery # root imagery - # Here we call the pyotb.Output() constructor. - # Since it can only be called with pyotb apps, we do the following: - # - if the output is a str, (e.g. the original dimap filename), we instantiate a pyotb.Input(), - # - else we use the original output (should be pyotb application) - super().__init__(app=pyotb.Input(out) if isinstance(out, str) else out, output_parameter_key="out") - assert parent is not self, "You cannot assign a new source to its parent instance" - self.parent = parent # parent source (is another Source instance) - self._app_stack = [] # list of otb applications or output to keep trace - - def new_source(self, *args): - """ - Return a new Source instance with new apps added at the end of the pipeline. - :param *args: list of pyotb.app instances to append to the existing pipeline - :return: new source - """ - for new_app in args: - self._app_stack.append(new_app) - return Source(root_imagery=self.root_imagery, out=self._app_stack[-1], parent=self) - - def drilled(self, msk_vec_file, inside=True, nodata=0): - """ - Return the source drilled from the input vector data. - The default behavior is that the hole is made inside the polygon. - This can be changed setting the "inside" parameter to False. - :param msk_vec_file: input vector data filename - :param inside: whether the drill is happening inside the polygon or outside - :param nodata: nodata value inside holes - :return: drilled source - """ - if utils.open_vector_layer(msk_vec_file): - # Cloud mask is not empty - rasterization = pyotb.Rasterization({"in": msk_vec_file, - "im": self, - "mode": "binary", - "mode.binary.foreground": 0 if inside else 255, - "background": 255 if inside else 255}) - manage_nodata = pyotb.ManageNoData({"in": self, - "mode": "apply", - "mode.apply.mask": rasterization, - "mode.apply.ndval": nodata}) - return self.new_source(rasterization, manage_nodata) - return self # Nothing but a soft copy of the source - def cld_msk_drilled(self, nodata=0): """ Return the source drilled from the cloud mask @@ -72,53 +20,19 @@ class Source(pyotb.Output): """ return self.drilled(msk_vec_file=self.root_imagery.root_scene.cld_msk_file, nodata=nodata) - def resample_over(self, ref_img, interpolator="nn", nodata=0): - """ - Return the source superimposed over the input image - :param ref_img: reference image - :param interpolator: interpolator - :param nodata: no data value - :return: resampled image source - """ - return self.new_source(pyotb.Superimpose({"inm": self, - "inr": ref_img, - "interpolator": interpolator, - "fv": nodata})) - - def clip_over_img(self, ref_img): - """ - Return the source clipped over the ROI specified by the input image extent - :param ref_img: reference image - :return: ROI clipped source - """ - return self.new_source(pyotb.ExtractROI({"in": self, - "mode": "fit", - "mode.fit.im": ref_img})) - - def clip_over_vec(self, ref_vec): - """ - Return the source clipped over the ROI specified by the input vector extent - :param ref_vec: reference vector data - :return: ROI clipped source - """ - return self.new_source(pyotb.ExtractROI({"in": self, - "mode": "fit", - "mode.fit.vec": ref_vec})) - -class Imagery: +class Spot67Imagery(Imagery): """ Spot 6/7 imagery class. This class carry the base image source, which can be radiometrically or geometrically corrected. """ - - def __init__(self, scene, epsg=None, reflectance=None): + def __init__(self, root_scene, epsg=None, reflectance=None): """ - :param scene: The Scene of which the Imagery instance is attached + :param root_scene: The Scene of which the Imagery instance is attached :param epsg: optional option to project PAN and MS images :param reflectance: optional level of reflectance """ - self.root_scene = scene + super().__init__(root_scene=root_scene) # Base self.xs = self.root_scene.dimap_file_xs @@ -169,7 +83,7 @@ class Imagery: inside=False) # new source masked outside the original ROI -class Scene: +class Spot67Scene(Scene): """ Spot 6/7 root_scene class. The class carries all the metadata from the root_scene, and can be used to retrieve its imagery. @@ -228,7 +142,7 @@ class Scene: c_nodes = root.find("Geometric_Data/Use_Area/Located_Geometric_Values") for node in c_nodes: if node.tag == "TIME": - self.acquisition_date = datetime.datetime.strptime(node.text[0:10], "%Y-%m-%d") + acquisition_date = datetime.datetime.strptime(node.text[0:10], "%Y-%m-%d") break # Sun angles @@ -240,21 +154,14 @@ class Scene: self.sun_elevation = float(node.text) # Get EPSG and bounding box - def _get_epsg_bbox(dimap_file): - gdal_ds = gdal.Open(dimap_file) - epsg = utils.get_epsg(gdal_ds) - bbox_wgs84 = utils.get_bbox_wgs84(gdal_ds) - return epsg, bbox_wgs84 - - epsg_xs, self.bbox_xs = _get_epsg_bbox(dimap_file_xs) - epsg_pan, self.bbox_pan = _get_epsg_bbox(dimap_file_pan) + epsg_xs, self.bbox_wgs84_xs = utils.get_epsg_bbox(dimap_file_xs) + epsg_pan, self.bbox_wgs84_pan = utils.get_epsg_bbox(dimap_file_pan) # Check that EPSG for PAN and XS are the same if epsg_pan != epsg_xs: raise ValueError("EPSG of XS and PAN sources are different: " "XS EPSG is {}, PAN EPSG is {}" .format(epsg_xs, epsg_pan)) - self.epsg = int(epsg_xs) # Here we compute bounding boxes overlap, to choose the most appropriated # CLD and ROI masks for the scene. Indeed, sometimes products are not @@ -262,8 +169,8 @@ class Scene: # so CLD and ROI masks are not the same for XS and PAN. # We keep the ROI+CLD masks of the PAN or XS imagery lying completely inside # the other one. - self.bbox_xs_overlap = utils.bbox_overlap(self.bbox_xs, self.bbox_pan) - self.bbox_pan_overlap = utils.bbox_overlap(self.bbox_pan, self.bbox_xs) + self.bbox_xs_overlap = utils.bbox_overlap(self.bbox_wgs84_xs, self.bbox_wgs84_pan) + self.bbox_pan_overlap = utils.bbox_overlap(self.bbox_wgs84_pan, self.bbox_wgs84_xs) # Get ROI+CLD filenames in XS and PAN products self.cld_msk_file_xs = _get_mask(dimap_file_xs, "CLD*.GML") @@ -272,25 +179,28 @@ class Scene: self.roi_msk_file_pan = _get_mask(dimap_file_pan, "ROI*.GML") # Choice based on the pxs overlap - self.bbox = self.bbox_xs + bbox_wgs84 = self.bbox_wgs84_xs self.cld_msk_file = self.cld_msk_file_xs self.roi_msk_file = self.roi_msk_file_xs self.pxs_overlap = self.bbox_xs_overlap if self.bbox_pan_overlap > self.bbox_xs_overlap: - self.bbox = self.bbox_pan + bbox_wgs84 = self.bbox_wgs84_pan self.cld_msk_file = self.cld_msk_file_pan self.roi_msk_file = self.roi_msk_file_pan self.pxs_overlap = self.bbox_pan_overlap # Throw some warning or error, depending on the pxs overlap value msg = "Bounding boxes of XS and PAN sources have {:.2f}% overlap. " \ - "\n\tXS bbox: {} \n\tPAN bbox: {}" \ - "".format(100 * self.pxs_overlap, self.bbox_xs, self.bbox_pan) + "\n\tXS bbox_wgs84: {} \n\tPAN bbox_wgs84: {}" \ + "".format(100 * self.pxs_overlap, self.bbox_wgs84_xs, self.bbox_wgs84_pan) if self.pxs_overlap == 0: raise ValueError(msg) if self.has_partial_pxs_overlap(): raise Warning(msg) + # Call parent constructor + super().__init(acquisition_date=acquisition_date, bbox_wgs84=bbox_wgs84, epsg=epsg_xs) + def has_partial_pxs_overlap(self): """ :return: True if at least PAN or XS imagery lies completely inside the other one. False else. @@ -304,7 +214,7 @@ class Scene: :param reflectance: optional level of reflectance :return: Imagery instance """ - return Imagery(self, epsg=epsg, reflectance=reflectance) + return Spot67Imagery(self, epsg=epsg, reflectance=reflectance) def __repr__(self): """ @@ -324,12 +234,9 @@ class Scene: "Viewing angle along track": self.viewing_angle_along, "Viewing angle": self.viewing_angle, "Incidence angle": self.incidence_angle, - "Acquisition date": self.acquisition_date, "Sun elevation": self.sun_elevation, "Sun azimuth": self.sun_azimuth, - "EPSG": self.epsg, - "Bounding box (WGS84)": self.bbox, - "Bounding box XS (WGS84)": self.bbox_xs, - "Bounding box PAN (WGS84)": self.bbox_pan + "Bounding box XS (WGS84)": self.bbox_wgs84_xs, + "Bounding box PAN (WGS84)": self.bbox_wgs84_pan } - return "\n".join(["{}: {}".format(key, value) for key, value in out.items()]) + return super().__repr__() + "\n".join(["{}: {}".format(key, value) for key, value in out.items()]) diff --git a/scenes/utils.py b/scenes/utils.py index 2aad643..ec69727 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -25,7 +25,9 @@ def get_epsg(gdal_ds): :return: EPSG code (int) """ proj = osr.SpatialReference(wkt=gdal_ds.GetProjection()) - return proj.GetAttrValue('AUTHORITY', 1) + epsg = proj.GetAttrValue('AUTHORITY', 1) + assert isinstance(epsg, int) + return epsg def get_extent(gdal_ds): @@ -72,6 +74,18 @@ def get_bbox_wgs84(gdal_ds): return reproject_coords(coords, src_srs, tgt_srs) +def get_epsg_bbox(filename): + """ + Returns (epsg, bbox_wgs84) from a raster file that GDAL can open. + :param filename: file name + :return: (epsg, bbox_wgs84) + """ + gdal_ds = gdal.Open(filename) + epsg = get_epsg(gdal_ds) + bbox_wgs84 = get_bbox_wgs84(gdal_ds) + return epsg, bbox_wgs84 + + def coords2poly(coords): """ Converts a list of coordinates into a polygon @@ -122,7 +136,7 @@ def bbox_overlap(bbox, other_bbox): Returns the ratio of bounding boxes overlap. :param bbox: bounding box :param other_bbox: other bounding box - :return: overlap (in the [0, 1] range). 0 -> no overlap with other_bbox, 1 -> bbox lies inside other_bbox + :return: overlap (in the [0, 1] range). 0 -> no overlap with other_bbox, 1 -> bbox_wgs84 lies inside other_bbox """ poly = coords2poly(bbox) other_poly = coords2poly(other_bbox) -- GitLab From cf2537f848e51b9ab4a53754249b6a75b84962d1 Mon Sep 17 00:00:00 2001 From: Cresson Remi <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:33:23 +0100 Subject: [PATCH 03/27] DOC: Update arch.md --- doc/arch.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/arch.md b/doc/arch.md index 345f1cc..f4e0383 100644 --- a/doc/arch.md +++ b/doc/arch.md @@ -49,6 +49,8 @@ superimposed.write("superimposed.tif") ```mermaid classDiagram + Scene <|-- Spot67Scene + Imagery <|-- Spot67Imagery Scene --* Imagery: root_scene Imagery --* Source: root_imagery @@ -56,14 +58,18 @@ classDiagram +datetime acquisition_date +int epsg +bbox_wgs84 - +bbox + +Imagery get_imagery() + +__repr__() + } + + class Spot67Scene{ +float azimuth_angle +float azimuth_angle_across +float azimuth_angle_along +float incidence_angle +float sun_azimuth_angle +float sun_elev_angle - +Imagery get_imagery() + +Spot67Imagery get_imagery() +__repr__() } @@ -80,11 +86,15 @@ classDiagram } class Imagery{ - +__init__(scene, epsg=None, reflectance=None) + +__init__(root_scene) + +Scene root_scene + } + + class Spot67Imagery{ + +__init__(root_scene, epsg=None, reflectance=None) +Source get_pxs() +Source get_pan() +Source get_xs() - +Scene root_scene } ``` -- GitLab From 68d36e1290151ce3470051d5ebccf2efb64a81da Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:40:08 +0100 Subject: [PATCH 04/27] REFAC: architecture --- scenes/core.py | 6 +++--- scenes/spot.py | 2 +- scenes/utils.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scenes/core.py b/scenes/core.py index d2f3de3..94f94f3 100644 --- a/scenes/core.py +++ b/scenes/core.py @@ -1,10 +1,11 @@ """ This module provides the Source class, which aims to handle the access to the rasters delivered by EO products """ -import pyotb from abc import ABC, abstractmethod -from scenes import utils import datetime +import pyotb +from scenes import utils + class Source(pyotb.Output): """ @@ -138,7 +139,6 @@ class Scene(ABC): :param **kwargs: Imagery options :return: Imagery instance """ - pass def __repr__(self): """ diff --git a/scenes/spot.py b/scenes/spot.py index 921fa55..d911681 100644 --- a/scenes/spot.py +++ b/scenes/spot.py @@ -199,7 +199,7 @@ class Spot67Scene(Scene): raise Warning(msg) # Call parent constructor - super().__init(acquisition_date=acquisition_date, bbox_wgs84=bbox_wgs84, epsg=epsg_xs) + super().__init__(acquisition_date=acquisition_date, bbox_wgs84=bbox_wgs84, epsg=epsg_xs) def has_partial_pxs_overlap(self): """ diff --git a/scenes/utils.py b/scenes/utils.py index ec69727..d4495a8 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -4,7 +4,7 @@ A set of helpers for generic purpose import os import glob import pathlib -from osgeo import osr, ogr +from osgeo import osr, ogr, gdal def epsg2srs(epsg): -- GitLab From a6962a0a37102b31001e6795057187d41f9f8cc4 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:42:23 +0100 Subject: [PATCH 05/27] REFAC: architecture --- scenes/drs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/drs.py b/scenes/drs.py index 2f61d61..96fe9dd 100644 --- a/scenes/drs.py +++ b/scenes/drs.py @@ -41,7 +41,7 @@ def get_spot67_scenes(root_dir): raise ValueError("{} DIMAPS candidates found in {} ".format(nb_files, pan_path)) dimap_file_pan = dimap_pan_files[0] # Instantiate a new scene object - new_scene = spot.Scene(dimap_file_pan=dimap_file_pan, dimap_file_xs=dimap_file_xs) + new_scene = spot.Spot67Scene(dimap_file_pan=dimap_file_pan, dimap_file_xs=dimap_file_xs) scenes.append(new_scene) except Exception as error: if dimap_file_xs not in errors: -- GitLab From 8aec7a3f9a2fec22193936047a6d346ed6a18481 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:45:25 +0100 Subject: [PATCH 06/27] REFAC: architecture --- test/imagery_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/imagery_test.py b/test/imagery_test.py index de8bd9b..f90daae 100644 --- a/test/imagery_test.py +++ b/test/imagery_test.py @@ -15,8 +15,8 @@ class ImageryTest(ScenesTestBase): "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") def get_scene1(self): - return spot.Scene(dimap_file_xs=self.get_dimap_xs1(), - dimap_file_pan=self.get_dimap_pan1()) + return spot.Spot67Scene(dimap_file_xs=self.get_dimap_xs1(), + dimap_file_pan=self.get_dimap_pan1()) def get_scene1_imagery(self): scene1 = self.get_scene1() -- GitLab From fa8209f403680f9ba4fc35f8ae745a7560dfbb51 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:48:16 +0100 Subject: [PATCH 07/27] REFAC: architecture --- scenes/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/utils.py b/scenes/utils.py index d4495a8..9b14974 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -26,8 +26,8 @@ def get_epsg(gdal_ds): """ proj = osr.SpatialReference(wkt=gdal_ds.GetProjection()) epsg = proj.GetAttrValue('AUTHORITY', 1) - assert isinstance(epsg, int) - return epsg + assert str(epsg).isdigit() + return int(epsg) def get_extent(gdal_ds): -- GitLab From af3dce654990dae94aed64001e178d89937c3948 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 10:50:07 +0100 Subject: [PATCH 08/27] REFAC: architecture --- scenes/spot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scenes/spot.py b/scenes/spot.py index d911681..f6af8a9 100644 --- a/scenes/spot.py +++ b/scenes/spot.py @@ -207,9 +207,9 @@ class Spot67Scene(Scene): """ return self.pxs_overlap < self.PXS_OVERLAP_THRESH - def get_imagery(self, epsg=None, reflectance=None): + def get_imagery(self, epsg=None, reflectance=None): # pylint: disable=arguments-differ """ - Return the imagery + Return the Spot 6/7 imagery :param epsg: optional option to project PAN and MS images :param reflectance: optional level of reflectance :return: Imagery instance -- GitLab From f9a6086c6b3fd3bc0281c9f129982d3d95f3f453 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 11:10:44 +0100 Subject: [PATCH 09/27] ADD: pickle serialization --- scenes/drs.py | 25 +++++++++++++++++++++++-- test/drs_import.py | 6 +++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/scenes/drs.py b/scenes/drs.py index 96fe9dd..7745faf 100644 --- a/scenes/drs.py +++ b/scenes/drs.py @@ -2,6 +2,7 @@ A set of utils to deal with DRS products """ import tqdm +import pickle from scenes import spot, utils @@ -17,8 +18,8 @@ def find_all_dimaps(pth): def get_spot67_scenes(root_dir): """ Return the list of pairs of PAN/XS DIMAPS - :param root_dir: directory - :return: list of pairs of filenames + :param root_dir: directory containing "MS" and "PAN" subdirectories + :return: list of Spot67Scenes instances """ # List files look_dir = root_dir + "/MS" @@ -59,3 +60,23 @@ def get_spot67_scenes(root_dir): print("\t{}".format(error)) return scenes + + +def save_scenes(scenes_list, pickle_file): + """ + Use pickle to save scenes + :param scenes_list: a list of Scene instances + :param pickle_file: pickle file + """ + pickle.dump(scenes_list, open(pickle_file, "wb")) + + +def load_scenes(pickle_file): + """ + Use pickle to save Spot-6/7 scenes + :param pickle_file: pickle file + :return: list of Scene instances + """ + return pickle.load(open(pickle_file, "rb")) + + diff --git a/test/drs_import.py b/test/drs_import.py index 1cfad67..9aec815 100644 --- a/test/drs_import.py +++ b/test/drs_import.py @@ -4,7 +4,11 @@ from scenes import drs # Arguments parser = argparse.ArgumentParser(description="Test",) parser.add_argument("--root_dir", help="Root directory containing MS and PAN folders", required=True) +parser.add_argument("--out_pickle", help="Output pickle file", required=True) params = parser.parse_args() -# Find pairs of DIMAPS +# Get all scenes in the root_dir scenes = drs.get_spot67_scenes(params.root_dir) + +# Save scenes in a pickle file +drs.save_scenes(scenes, params.out_pickle) -- GitLab From f9cf3f331f66422c440c4284a0bce87905a10ae0 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 11:14:36 +0100 Subject: [PATCH 10/27] ADD: pickle serialization --- test/drs_import.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/drs_import.py b/test/drs_import.py index 9aec815..2be81e5 100644 --- a/test/drs_import.py +++ b/test/drs_import.py @@ -3,12 +3,14 @@ from scenes import drs # Arguments parser = argparse.ArgumentParser(description="Test",) -parser.add_argument("--root_dir", help="Root directory containing MS and PAN folders", required=True) +parser.add_argument("--root_dir", nargs='+', help="Root directories containing MS and PAN folders", required=True) parser.add_argument("--out_pickle", help="Output pickle file", required=True) params = parser.parse_args() # Get all scenes in the root_dir -scenes = drs.get_spot67_scenes(params.root_dir) +scenes = [] +for root_dir in params.root_dir: + scenes += drs.get_spot67_scenes(root_dir) # Save scenes in a pickle file drs.save_scenes(scenes, params.out_pickle) -- GitLab From 25cf5cb9d2348ae7caa6922d22c4f968ce0ef761 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 11:54:30 +0100 Subject: [PATCH 11/27] ADD: pickle serialization --- scenes/spot.py | 14 +++++++------- scenes/utils.py | 37 ++++++++++++++++++++++++------------- test/drs_search.py | 2 +- test/drs_stack.py | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/scenes/spot.py b/scenes/spot.py index f6af8a9..be13d46 100644 --- a/scenes/spot.py +++ b/scenes/spot.py @@ -154,8 +154,8 @@ class Spot67Scene(Scene): self.sun_elevation = float(node.text) # Get EPSG and bounding box - epsg_xs, self.bbox_wgs84_xs = utils.get_epsg_bbox(dimap_file_xs) - epsg_pan, self.bbox_wgs84_pan = utils.get_epsg_bbox(dimap_file_pan) + epsg_xs, extent_wgs84_xs, self.bbox_wgs84_xs = utils.get_epsg_extent_bbox(dimap_file_xs) + epsg_pan, extent_wgs84_pan, self.bbox_wgs84_pan = utils.get_epsg_extent_bbox(dimap_file_pan) # Check that EPSG for PAN and XS are the same if epsg_pan != epsg_xs: @@ -169,8 +169,8 @@ class Spot67Scene(Scene): # so CLD and ROI masks are not the same for XS and PAN. # We keep the ROI+CLD masks of the PAN or XS imagery lying completely inside # the other one. - self.bbox_xs_overlap = utils.bbox_overlap(self.bbox_wgs84_xs, self.bbox_wgs84_pan) - self.bbox_pan_overlap = utils.bbox_overlap(self.bbox_wgs84_pan, self.bbox_wgs84_xs) + self.xs_overlap = utils.extent_overlap(extent_wgs84_xs, extent_wgs84_pan) + self.pan_overlap = utils.extent_overlap(extent_wgs84_pan, extent_wgs84_xs) # Get ROI+CLD filenames in XS and PAN products self.cld_msk_file_xs = _get_mask(dimap_file_xs, "CLD*.GML") @@ -182,12 +182,12 @@ class Spot67Scene(Scene): bbox_wgs84 = self.bbox_wgs84_xs self.cld_msk_file = self.cld_msk_file_xs self.roi_msk_file = self.roi_msk_file_xs - self.pxs_overlap = self.bbox_xs_overlap - if self.bbox_pan_overlap > self.bbox_xs_overlap: + self.pxs_overlap = self.xs_overlap + if self.pan_overlap > self.xs_overlap: bbox_wgs84 = self.bbox_wgs84_pan self.cld_msk_file = self.cld_msk_file_pan self.roi_msk_file = self.roi_msk_file_pan - self.pxs_overlap = self.bbox_pan_overlap + self.pxs_overlap = self.pan_overlap # Throw some warning or error, depending on the pxs overlap value msg = "Bounding boxes of XS and PAN sources have {:.2f}% overlap. " \ diff --git a/scenes/utils.py b/scenes/utils.py index 9b14974..10f34eb 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -60,7 +60,7 @@ def reproject_coords(coords, src_srs, tgt_srs): return trans_coords -def get_bbox_wgs84(gdal_ds): +def get_extent_wgs84(gdal_ds): """ Returns the bounding box in WGS84 CRS from a GDAL dataset :param gdal_ds: GDAL dataset @@ -74,16 +74,27 @@ def get_bbox_wgs84(gdal_ds): return reproject_coords(coords, src_srs, tgt_srs) -def get_epsg_bbox(filename): +def get_bbox_from_extent(extent): """ - Returns (epsg, bbox_wgs84) from a raster file that GDAL can open. + Converts an extent into a bounding box + :param extent: extent + :return: bounding box (xmin, xmax, ymin, ymax) + """ + (xmin, ymax), (xmax, _), (_, ymin), (_, _) = extent + return xmin, xmax, ymin, ymax + + +def get_epsg_extent_bbox(filename): + """ + Returns (epsg, extent_wgs84) from a raster file that GDAL can open. :param filename: file name - :return: (epsg, bbox_wgs84) + :return: (epsg, extent_wgs84) """ gdal_ds = gdal.Open(filename) epsg = get_epsg(gdal_ds) - bbox_wgs84 = get_bbox_wgs84(gdal_ds) - return epsg, bbox_wgs84 + extent_wgs84 = get_extent_wgs84(gdal_ds) + bbox_wgs84 = get_bbox_from_extent(extent_wgs84) + return epsg, extent_wgs84, bbox_wgs84 def coords2poly(coords): @@ -131,15 +142,15 @@ def poly_overlap(poly, other_poly): return inter.GetArea() / poly.GetArea() -def bbox_overlap(bbox, other_bbox): +def extent_overlap(extent, other_extent): """ - Returns the ratio of bounding boxes overlap. - :param bbox: bounding box - :param other_bbox: other bounding box - :return: overlap (in the [0, 1] range). 0 -> no overlap with other_bbox, 1 -> bbox_wgs84 lies inside other_bbox + Returns the ratio of extents overlap. + :param extent: extent + :param other_extent: other extent + :return: overlap (in the [0, 1] range). 0 -> no overlap with other_extent, 1 -> extent lies inside other_extent """ - poly = coords2poly(bbox) - other_poly = coords2poly(other_bbox) + poly = coords2poly(extent) + other_poly = coords2poly(other_extent) return poly_overlap(poly=poly, other_poly=other_poly) diff --git a/test/drs_search.py b/test/drs_search.py index a076d4f..938fe61 100644 --- a/test/drs_search.py +++ b/test/drs_search.py @@ -19,7 +19,7 @@ idx = indexation.Index(scenes) # search print("search roi") gdal_ds = gdal.Open(params.roi) -bbox = utils.get_bbox_wgs84(gdal_ds) +bbox = utils.get_extent_wgs84(gdal_ds) matches = idx.find(bbox_wgs84=bbox) print("{} scenes found.".format(len(matches))) #for scene_match in matches: diff --git a/test/drs_stack.py b/test/drs_stack.py index 7e65e7e..bb00738 100644 --- a/test/drs_stack.py +++ b/test/drs_stack.py @@ -20,7 +20,7 @@ idx = indexation.Index(scenes) # search print("search roi") gdal_ds = gdal.Open(params.roi) -bbox = utils.get_bbox_wgs84(gdal_ds) +bbox = utils.get_extent_wgs84(gdal_ds) matches = idx.find(bbox_wgs84=bbox) print("{} scenes found.".format(len(matches))) -- GitLab From a47f8176e833ecac22a1a42e21f00046c7671cc6 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 11:57:00 +0100 Subject: [PATCH 12/27] ADD: pickle serialization --- scenes/drs.py | 4 +--- scenes/indexation.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scenes/drs.py b/scenes/drs.py index 7745faf..e40efab 100644 --- a/scenes/drs.py +++ b/scenes/drs.py @@ -1,8 +1,8 @@ """ A set of utils to deal with DRS products """ -import tqdm import pickle +import tqdm from scenes import spot, utils @@ -78,5 +78,3 @@ def load_scenes(pickle_file): :return: list of Scene instances """ return pickle.load(open(pickle_file, "rb")) - - diff --git a/scenes/indexation.py b/scenes/indexation.py index 3de42ba..cf7de49 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -28,7 +28,7 @@ class Index: """ eps_ts = 10 * 3600 # Ten hours timestamp = get_timestamp(dt) - _, (xmax, ymax), _, (xmin, ymin) = bbox_wgs84 + (xmin, xmax, ymin, ymax) = bbox_wgs84 return xmin, ymin, timestamp - eps_ts, xmax, ymax, timestamp + eps_ts @staticmethod @@ -40,7 +40,7 @@ class Index: :param dt_max: date max (datetime.datetime) :return: item for rtree """ - _, (xmax, ymax), _, (xmin, ymin) = bbox_wgs84 + (xmin, xmax, ymin, ymax) = bbox_wgs84 return xmin, ymin, get_timestamp(dt_min), xmax, ymax, get_timestamp(dt_max) def __init__(self, scenes_list): -- GitLab From be9112ab9594be22e144e25f6619810b33e51964 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 12:22:35 +0100 Subject: [PATCH 13/27] TEST: indexation tests --- .gitlab-ci.yml | 5 +++++ test/imagery_test.py | 4 ---- test/indexation_test.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 test/indexation_test.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a6c2e1..4939b2b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,3 +46,8 @@ imagery: extends: .test_base script: - pytest -o log_cli=true --log-cli-level=INFO --junitxml=report_imagery_test.xml test/imagery_test.py + +indexation: + extends: .test_base + script: + - pytest -o log_cli=true --log-cli-level=INFO --junitxml=report_indexation_test.xml test/indexation_test.py diff --git a/test/imagery_test.py b/test/imagery_test.py index f90daae..b1603c3 100644 --- a/test/imagery_test.py +++ b/test/imagery_test.py @@ -41,10 +41,6 @@ class ImageryTest(ScenesTestBase): # We test the central area only, because bayes method messes with nodata outside image self.compare_images(pxs, pxs_baseline, roi=[2000, 2000, 1000, 1000]) - def test_epsg(self): - scene1 = self.get_scene1() - self.assertTrue(scene1.epsg == 2154) - if __name__ == '__main__': unittest.main() diff --git a/test/indexation_test.py b/test/indexation_test.py new file mode 100644 index 0000000..357c4e8 --- /dev/null +++ b/test/indexation_test.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from scenes_test_base import ScenesTestBase +from scenes import spot, indexation +import pyotb + + +class ImageryTest(ScenesTestBase): + + def get_dimap_xs1(self): + return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" + "DIM_SPOT6_MS_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") + + def get_dimap_pan1(self): + return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" + "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") + + def get_scene1(self): + return spot.Spot67Scene(dimap_file_xs=self.get_dimap_xs1(), + dimap_file_pan=self.get_dimap_pan1()) + + def test_scene1_indexation(self): + bbox_wgs84 = (4.3448, 43.6605, 4.3980, 43.6993) + scene1 = self.get_scene1() + index = indexation.Index(scenes_list=[scene1]) + matches = index.find(bbox_wgs84=bbox_wgs84) + self.assertTrue(len(matches)==1) + + def test_epsg(self): + scene1 = self.get_scene1() + self.assertTrue(scene1.epsg == 2154) + + +if __name__ == '__main__': + unittest.main() -- GitLab From edf4917fff4370c9d47aa45fbad9f4a6d3e805d5 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 12:33:19 +0100 Subject: [PATCH 14/27] TEST: indexation tests --- .gitlab-ci.yml | 2 +- test/imagery_test.py | 16 ++-------------- test/indexation_test.py | 23 ++++------------------- test/scenes_test_base.py | 23 ----------------------- test/tests_data.py | 16 ++++++++++++++++ 5 files changed, 23 insertions(+), 57 deletions(-) create mode 100644 test/tests_data.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4939b2b..a727df1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,7 +36,7 @@ codespell: stage: Test allow_failure: false before_script: - - sudo python3 -m pip install pytest pytest-cov pyotb + - sudo python3 -m pip install pytest pytest-cov pyotb rtree - export PYTHONPATH=$PYTHONPATH:$PWD - wget -P . --no-verbose -e robots=off --recursive --level=inf --no-parent -R "index.html*" -R "LOGO.JPG" --cut-dirs=3 --no-host-directories --content-on-error http://indexof.montpellier.irstea.priv/projets/geocicd/scenes/test_data/ - mkdir tests_artifacts diff --git a/test/imagery_test.py b/test/imagery_test.py index b1603c3..1dd0c86 100644 --- a/test/imagery_test.py +++ b/test/imagery_test.py @@ -1,26 +1,14 @@ # -*- coding: utf-8 -*- from scenes_test_base import ScenesTestBase -from scenes import spot import pyotb +import tests_data class ImageryTest(ScenesTestBase): - def get_dimap_xs1(self): - return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" - "DIM_SPOT6_MS_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") - - def get_dimap_pan1(self): - return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" - "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") - - def get_scene1(self): - return spot.Spot67Scene(dimap_file_xs=self.get_dimap_xs1(), - dimap_file_pan=self.get_dimap_pan1()) def get_scene1_imagery(self): - scene1 = self.get_scene1() - return scene1.get_imagery() + return tests_data.SCENE1.get_imagery() def test_xs_imagery1(self): imagery = self.get_scene1_imagery() diff --git a/test/indexation_test.py b/test/indexation_test.py index 357c4e8..bda7fda 100644 --- a/test/indexation_test.py +++ b/test/indexation_test.py @@ -1,33 +1,18 @@ # -*- coding: utf-8 -*- from scenes_test_base import ScenesTestBase -from scenes import spot, indexation -import pyotb - +from scenes import indexation +import tests_data class ImageryTest(ScenesTestBase): - def get_dimap_xs1(self): - return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" - "DIM_SPOT6_MS_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") - - def get_dimap_pan1(self): - return self.get_path("input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" - "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML") - - def get_scene1(self): - return spot.Spot67Scene(dimap_file_xs=self.get_dimap_xs1(), - dimap_file_pan=self.get_dimap_pan1()) - def test_scene1_indexation(self): bbox_wgs84 = (4.3448, 43.6605, 4.3980, 43.6993) - scene1 = self.get_scene1() - index = indexation.Index(scenes_list=[scene1]) + index = indexation.Index(scenes_list=[tests_data.SCENE1]) matches = index.find(bbox_wgs84=bbox_wgs84) self.assertTrue(len(matches)==1) def test_epsg(self): - scene1 = self.get_scene1() - self.assertTrue(scene1.epsg == 2154) + self.assertTrue(tests_data.SCENE1.epsg == 2154) if __name__ == '__main__': diff --git a/test/scenes_test_base.py b/test/scenes_test_base.py index b121be6..bad32a8 100644 --- a/test/scenes_test_base.py +++ b/test/scenes_test_base.py @@ -1,37 +1,14 @@ # -*- coding: utf-8 -*- -import os import unittest import filecmp from abc import ABC import pyotb -def get_env_var(var): - """ - Return the value of the sepficied environment variable - :param var: environment variable to return - :return: value of the environment variable - """ - value = os.environ[var] - if value is None: - print("Environment variable {} is not set. Returning value None.".format(var)) - return value - - class ScenesTestBase(ABC, unittest.TestCase): """ Base class for tests """ - TEST_DATA_DIR = get_env_var("TEST_DATA_DIR") - - def get_path(self, path): - """ - Return a path - :param path: relative path - :return: actual absolute path - """ - return self.TEST_DATA_DIR + "/" + path - def compare_images(self, image, reference, roi=None, mae_threshold=0.01): """ Compare one image (typically: an output image) with a reference diff --git a/test/tests_data.py b/test/tests_data.py new file mode 100644 index 0000000..4319945 --- /dev/null +++ b/test/tests_data.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import os +from scenes import spot + + +# TEST_DATA_DIR environment variable +TEST_DATA_DIR = os.environ["TEST_DATA_DIR"] + +# Filenames +DIMAP1_XS = TEST_DATA_DIR + "input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" \ + "DIM_SPOT6_MS_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML" +DIMAP1_P = TEST_DATA_DIR + "input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" \ + "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML" + +# Instances +SCENE1 = spot.Spot67Scene(dimap_file_xs=DIMAP1_XS, dimap_file_pan=DIMAP1_P) \ No newline at end of file -- GitLab From 93b24a4752a7dac3bf221d5e76a2ce5e2788ac9c Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 12:36:53 +0100 Subject: [PATCH 15/27] TEST: indexation tests --- test/tests_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests_data.py b/test/tests_data.py index 4319945..75d0e3a 100644 --- a/test/tests_data.py +++ b/test/tests_data.py @@ -7,9 +7,9 @@ from scenes import spot TEST_DATA_DIR = os.environ["TEST_DATA_DIR"] # Filenames -DIMAP1_XS = TEST_DATA_DIR + "input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" \ +DIMAP1_XS = TEST_DATA_DIR + "/input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_MS_001_A/" \ "DIM_SPOT6_MS_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML" -DIMAP1_P = TEST_DATA_DIR + "input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" \ +DIMAP1_P = TEST_DATA_DIR + "/input/ROI_1_Bundle_Ortho_GSD2015/PROD_SPOT6_001/VOL_SPOT6_001_A/IMG_SPOT6_P_001_A/" \ "DIM_SPOT6_P_201503261014386_ORT_SPOT6_20170524_1422391k0ha487979cy_1.XML" # Instances -- GitLab From 5e6cb91f8e4ddcb91e14b0f98882e547aabfbafc Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 12:42:25 +0100 Subject: [PATCH 16/27] TEST: indexation tests --- scenes/indexation.py | 2 ++ test/imagery_test.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scenes/indexation.py b/scenes/indexation.py index cf7de49..bf653ba 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -28,7 +28,9 @@ class Index: """ eps_ts = 10 * 3600 # Ten hours timestamp = get_timestamp(dt) + print(bbox_wgs84) (xmin, xmax, ymin, ymax) = bbox_wgs84 + print(xmin) return xmin, ymin, timestamp - eps_ts, xmax, ymax, timestamp + eps_ts @staticmethod diff --git a/test/imagery_test.py b/test/imagery_test.py index 1dd0c86..b1d496b 100644 --- a/test/imagery_test.py +++ b/test/imagery_test.py @@ -13,18 +13,18 @@ class ImageryTest(ScenesTestBase): def test_xs_imagery1(self): imagery = self.get_scene1_imagery() xs = imagery.get_xs() - self.compare_images(xs, self.get_dimap_xs1()) + self.compare_images(xs, tests_data.DIMAP1_XS) def test_pan_imagery1(self): imagery = self.get_scene1_imagery() pan = imagery.get_pan() - self.compare_images(pan, self.get_dimap_pan1()) + self.compare_images(pan, tests_data.DIMAP1_P) def test_pxs_imagery1(self): imagery = self.get_scene1_imagery() pxs = imagery.get_pxs() - pxs_baseline = pyotb.BundleToPerfectSensor({"inxs": self.get_dimap_xs1(), - "inp": self.get_dimap_pan1(), + pxs_baseline = pyotb.BundleToPerfectSensor({"inxs": tests_data.DIMAP1_XS, + "inp": tests_data.DIMAP1_P, "method": "bayes"}) # We test the central area only, because bayes method messes with nodata outside image self.compare_images(pxs, pxs_baseline, roi=[2000, 2000, 1000, 1000]) -- GitLab From 20df136b149b686d783753ed4ec35861ff7ea51b Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 12:47:54 +0100 Subject: [PATCH 17/27] TEST: indexation tests --- scenes/indexation.py | 2 -- scenes/utils.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scenes/indexation.py b/scenes/indexation.py index bf653ba..cf7de49 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -28,9 +28,7 @@ class Index: """ eps_ts = 10 * 3600 # Ten hours timestamp = get_timestamp(dt) - print(bbox_wgs84) (xmin, xmax, ymin, ymax) = bbox_wgs84 - print(xmin) return xmin, ymin, timestamp - eps_ts, xmax, ymax, timestamp + eps_ts @staticmethod diff --git a/scenes/utils.py b/scenes/utils.py index 10f34eb..49ef761 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -81,7 +81,7 @@ def get_bbox_from_extent(extent): :return: bounding box (xmin, xmax, ymin, ymax) """ (xmin, ymax), (xmax, _), (_, ymin), (_, _) = extent - return xmin, xmax, ymin, ymax + return min(xmin, xmax), max(xmin, xmax), min(ymin, ymax), max(ymin, ymax) def get_epsg_extent_bbox(filename): -- GitLab From 73810126f28d656d0213d63f6c3ee92748e87371 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 13:26:31 +0100 Subject: [PATCH 18/27] TEST: indexation tests --- scenes/indexation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scenes/indexation.py b/scenes/indexation.py index cf7de49..a3ddb13 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -29,6 +29,10 @@ class Index: eps_ts = 10 * 3600 # Ten hours timestamp = get_timestamp(dt) (xmin, xmax, ymin, ymax) = bbox_wgs84 + print(xmin) + print(xmax) + print(ymin) + print(ymax) return xmin, ymin, timestamp - eps_ts, xmax, ymax, timestamp + eps_ts @staticmethod -- GitLab From 746e97fbb8ec08c9c6ee7bf4732a78a1494024f4 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 13:27:34 +0100 Subject: [PATCH 19/27] TEST: indexation tests --- scenes/indexation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scenes/indexation.py b/scenes/indexation.py index a3ddb13..3959489 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -29,6 +29,7 @@ class Index: eps_ts = 10 * 3600 # Ten hours timestamp = get_timestamp(dt) (xmin, xmax, ymin, ymax) = bbox_wgs84 + print("bbox:") print(xmin) print(xmax) print(ymin) @@ -45,6 +46,11 @@ class Index: :return: item for rtree """ (xmin, xmax, ymin, ymax) = bbox_wgs84 + print("bbox:") + print(xmin) + print(xmax) + print(ymin) + print(ymax) return xmin, ymin, get_timestamp(dt_min), xmax, ymax, get_timestamp(dt_max) def __init__(self, scenes_list): -- GitLab From 29e80589e4ddb8582a27afef74ed5a43db463798 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 13:57:47 +0100 Subject: [PATCH 20/27] TEST: indexation tests --- scenes/utils.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scenes/utils.py b/scenes/utils.py index 49ef761..8407477 100644 --- a/scenes/utils.py +++ b/scenes/utils.py @@ -62,9 +62,9 @@ def reproject_coords(coords, src_srs, tgt_srs): def get_extent_wgs84(gdal_ds): """ - Returns the bounding box in WGS84 CRS from a GDAL dataset + Returns the extent in WGS84 CRS from a GDAL dataset :param gdal_ds: GDAL dataset - :return: bounding box in WGS84 CRS + :return: extent coordinates in WGS84 CRS """ coords = get_extent(gdal_ds) src_srs = osr.SpatialReference() @@ -74,7 +74,7 @@ def get_extent_wgs84(gdal_ds): return reproject_coords(coords, src_srs, tgt_srs) -def get_bbox_from_extent(extent): +def extent_to_bbox(extent): """ Converts an extent into a bounding box :param extent: extent @@ -84,6 +84,16 @@ def get_bbox_from_extent(extent): return min(xmin, xmax), max(xmin, xmax), min(ymin, ymax), max(ymin, ymax) +def get_bbox_wgs84(gdal_ds): + """ + Returns the bounding box in WGS84 CRS from a GDAL dataset + :param gdal_ds: GDAL dataset + :return: bounding box (xmin, xmax, ymin, ymax) + """ + extend_wgs84 = get_extent_wgs84(gdal_ds) + return extent_to_bbox(extend_wgs84) + + def get_epsg_extent_bbox(filename): """ Returns (epsg, extent_wgs84) from a raster file that GDAL can open. @@ -93,7 +103,7 @@ def get_epsg_extent_bbox(filename): gdal_ds = gdal.Open(filename) epsg = get_epsg(gdal_ds) extent_wgs84 = get_extent_wgs84(gdal_ds) - bbox_wgs84 = get_bbox_from_extent(extent_wgs84) + bbox_wgs84 = extent_to_bbox(extent_wgs84) return epsg, extent_wgs84, bbox_wgs84 -- GitLab From 691241ffd8bf50d6c20040182d59b4ca9fc191d4 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 13:58:44 +0100 Subject: [PATCH 21/27] ADD: pickle serialization --- test/drs_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/drs_search.py b/test/drs_search.py index 938fe61..16dd34a 100644 --- a/test/drs_search.py +++ b/test/drs_search.py @@ -19,7 +19,7 @@ idx = indexation.Index(scenes) # search print("search roi") gdal_ds = gdal.Open(params.roi) -bbox = utils.get_extent_wgs84(gdal_ds) +bbox = utils.get_bbox_wgs84(gdal_ds=gdal_ds) matches = idx.find(bbox_wgs84=bbox) print("{} scenes found.".format(len(matches))) #for scene_match in matches: -- GitLab From 7c56267822d981765e11a8e91bee5a0df04e19a3 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 14:06:26 +0100 Subject: [PATCH 22/27] ADD: pickle serialization --- scenes/indexation.py | 64 +++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/scenes/indexation.py b/scenes/indexation.py index 3959489..0a5afb0 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -14,45 +14,35 @@ def get_timestamp(dt): return dt.replace(tzinfo=datetime.timezone.utc).timestamp() -class Index: +def new_bbox(bbox_wgs84, dt): """ - Stores an indexation structures for a list of Scenes + Return a bounding box in the domain (lat, lon, time) + :param bbox_wgs84: Bounding box (in WGS84) + :param dt: date datetime.datetime + :return: item for rtree """ - @staticmethod - def new_bbox(bbox_wgs84, dt): - """ - Return a bounding box in the domain (lat, lon, time) - :param bbox_wgs84: Bounding box (in WGS84) - :param dt: date datetime.datetime) - :return: item for rtree - """ - eps_ts = 10 * 3600 # Ten hours - timestamp = get_timestamp(dt) - (xmin, xmax, ymin, ymax) = bbox_wgs84 - print("bbox:") - print(xmin) - print(xmax) - print(ymin) - print(ymax) - return xmin, ymin, timestamp - eps_ts, xmax, ymax, timestamp + eps_ts + dt_min = dt - datetime.timedelta(days=1) + dt_max = dt + datetime.timedelta(days=1) + + return bbox(bbox_wgs84=bbox_wgs84, dt_min=dt_min, dt_max=dt_max) - @staticmethod - def bbox(bbox_wgs84, dt_min, dt_max): - """ - Return a bounding box in the domain (lat, lon, time) - :param bbox_wgs84: Bounding box (in WGS84) - :param dt_min: date min (datetime.datetime) - :param dt_max: date max (datetime.datetime) - :return: item for rtree - """ - (xmin, xmax, ymin, ymax) = bbox_wgs84 - print("bbox:") - print(xmin) - print(xmax) - print(ymin) - print(ymax) - return xmin, ymin, get_timestamp(dt_min), xmax, ymax, get_timestamp(dt_max) +def bbox(bbox_wgs84, dt_min, dt_max): + """ + Return a bounding box in the domain (lat, lon, time) + :param bbox_wgs84: Bounding box (in WGS84) + :param dt_min: date min (datetime.datetime) + :param dt_max: date max (datetime.datetime) + :return: item for rtree + """ + (xmin, xmax, ymin, ymax) = bbox_wgs84 + return xmin, ymin, get_timestamp(dt_min), xmax, ymax, get_timestamp(dt_max) + + +class Index: + """ + Stores an indexation structures for a list of Scenes + """ def __init__(self, scenes_list): """ :param scenes_list: list of scenes @@ -64,7 +54,7 @@ class Index: properties.dimension = 3 self.index = rtree.index.Index(properties=properties) for scene_idx, scene in enumerate(scenes_list): - bbox = self.new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date) + bbox = new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date) self.index.insert(scene_idx, bbox) def find_indices(self, bbox_wgs84, dt_min=None, dt_max=None): @@ -79,7 +69,7 @@ class Index: dt_min = datetime.datetime.strptime("2000-01-01", "%Y-%m-%d") if not dt_max: dt_max = datetime.datetime.strptime("3000-01-01", "%Y-%m-%d") - bbox_search = self.bbox(bbox_wgs84=bbox_wgs84, dt_min=dt_min, dt_max=dt_max) + bbox_search = bbox(bbox_wgs84=bbox_wgs84, dt_min=dt_min, dt_max=dt_max) return self.index.intersection(bbox_search) def find(self, bbox_wgs84, dt_min=None, dt_max=None): -- GitLab From b5b20eade4f83ee81e1a394cd07994b765e4fe2e Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 14:12:10 +0100 Subject: [PATCH 23/27] ADD: pickle serialization --- test/indexation_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/indexation_test.py b/test/indexation_test.py index bda7fda..706948b 100644 --- a/test/indexation_test.py +++ b/test/indexation_test.py @@ -6,10 +6,11 @@ import tests_data class ImageryTest(ScenesTestBase): def test_scene1_indexation(self): - bbox_wgs84 = (4.3448, 43.6605, 4.3980, 43.6993) index = indexation.Index(scenes_list=[tests_data.SCENE1]) - matches = index.find(bbox_wgs84=bbox_wgs84) - self.assertTrue(len(matches)==1) + matches1 = index.find(bbox_wgs84=(43.6605, 43.6993, 4.3448, 4.3980)) + self.assertTrue(matches1) + matches2 = index.find(bbox_wgs84=(43.000, 43.001, 3.000, 3.001)) + self.assertFalse(matches2) def test_epsg(self): self.assertTrue(tests_data.SCENE1.epsg == 2154) -- GitLab From 4bbc99332cb80015e210a26a2fb7c93d38fdc114 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 14:16:44 +0100 Subject: [PATCH 24/27] ADD: pickle serialization --- test/indexation_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/indexation_test.py b/test/indexation_test.py index 706948b..6b440d6 100644 --- a/test/indexation_test.py +++ b/test/indexation_test.py @@ -6,6 +6,7 @@ import tests_data class ImageryTest(ScenesTestBase): def test_scene1_indexation(self): + print(tests_data.SCENE1.bbox_wgs84) index = indexation.Index(scenes_list=[tests_data.SCENE1]) matches1 = index.find(bbox_wgs84=(43.6605, 43.6993, 4.3448, 4.3980)) self.assertTrue(matches1) -- GitLab From f18445b3bb2c8a8ccc131ad722b250d7209b5399 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 14:17:47 +0100 Subject: [PATCH 25/27] ADD: pickle serialization --- test/indexation_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/indexation_test.py b/test/indexation_test.py index 6b440d6..319c4f1 100644 --- a/test/indexation_test.py +++ b/test/indexation_test.py @@ -8,7 +8,7 @@ class ImageryTest(ScenesTestBase): def test_scene1_indexation(self): print(tests_data.SCENE1.bbox_wgs84) index = indexation.Index(scenes_list=[tests_data.SCENE1]) - matches1 = index.find(bbox_wgs84=(43.6605, 43.6993, 4.3448, 4.3980)) + matches1 = index.find(bbox_wgs84=(43.706, 43.708, 4.317, 4.420)) self.assertTrue(matches1) matches2 = index.find(bbox_wgs84=(43.000, 43.001, 3.000, 3.001)) self.assertFalse(matches2) -- GitLab From 0353747e9fff64c486a0c4d63fd0f8a6d394e705 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 14:18:34 +0100 Subject: [PATCH 26/27] ADD: pickle serialization --- test/indexation_test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/indexation_test.py b/test/indexation_test.py index 319c4f1..c764d32 100644 --- a/test/indexation_test.py +++ b/test/indexation_test.py @@ -6,12 +6,9 @@ import tests_data class ImageryTest(ScenesTestBase): def test_scene1_indexation(self): - print(tests_data.SCENE1.bbox_wgs84) index = indexation.Index(scenes_list=[tests_data.SCENE1]) - matches1 = index.find(bbox_wgs84=(43.706, 43.708, 4.317, 4.420)) - self.assertTrue(matches1) - matches2 = index.find(bbox_wgs84=(43.000, 43.001, 3.000, 3.001)) - self.assertFalse(matches2) + self.assertTrue(index.find(bbox_wgs84=(43.706, 43.708, 4.317, 4.420))) + self.assertFalse(index.find(bbox_wgs84=(43.000, 43.001, 3.000, 3.001))) def test_epsg(self): self.assertTrue(tests_data.SCENE1.epsg == 2154) -- GitLab From 714805a02843ab0e110f31c757119c7def6d700f Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@irstea.fr> Date: Wed, 23 Feb 2022 16:13:25 +0100 Subject: [PATCH 27/27] ADD: pickle serialization --- scenes/indexation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scenes/indexation.py b/scenes/indexation.py index 0a5afb0..1968902 100644 --- a/scenes/indexation.py +++ b/scenes/indexation.py @@ -54,8 +54,7 @@ class Index: properties.dimension = 3 self.index = rtree.index.Index(properties=properties) for scene_idx, scene in enumerate(scenes_list): - bbox = new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date) - self.index.insert(scene_idx, bbox) + self.index.insert(scene_idx, new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date)) def find_indices(self, bbox_wgs84, dt_min=None, dt_max=None): """ -- GitLab