satcube 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of satcube might be problematic. Click here for more details.
- satcube-0.1.0/LICENSE +22 -0
- satcube-0.1.0/PKG-INFO +31 -0
- satcube-0.1.0/README.md +3 -0
- satcube-0.1.0/pyproject.toml +116 -0
- satcube-0.1.0/satcube/__init__.py +3 -0
- satcube-0.1.0/satcube/cloud_detection.py +24 -0
- satcube-0.1.0/satcube/dataclass.py +39 -0
- satcube-0.1.0/satcube/main.py +453 -0
- satcube-0.1.0/satcube/utils.py +1087 -0
- satcube-0.1.0/setup.py +43 -0
satcube-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Cesar Aybar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
satcube-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: satcube
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package to create cloud-free monthly composites by fusing Landsat and Sentinel-2 data.
|
|
5
|
+
Home-page: https://github.com/IPL-UV/satcube
|
|
6
|
+
Author: Cesar Aybar
|
|
7
|
+
Author-email: fcesar.aybar@uv.es
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Requires-Dist: earthengine-api (>=0.1.4.0)
|
|
12
|
+
Requires-Dist: fastcubo (>=0.0.999)
|
|
13
|
+
Requires-Dist: matplotlib (>=3.7.0)
|
|
14
|
+
Requires-Dist: numpy (>=1.25.0)
|
|
15
|
+
Requires-Dist: pandas (>=2.0.0)
|
|
16
|
+
Requires-Dist: phicloudmask (>=0.0.2)
|
|
17
|
+
Requires-Dist: pydantic (>=2.8.0)
|
|
18
|
+
Requires-Dist: rasterio (>=1.2.0)
|
|
19
|
+
Requires-Dist: requests (>=2.26.0)
|
|
20
|
+
Requires-Dist: satalign (>=0.0.999)
|
|
21
|
+
Requires-Dist: scikit-learn (>=1.2.0)
|
|
22
|
+
Requires-Dist: segmentation-models-pytorch (>=0.2.0)
|
|
23
|
+
Requires-Dist: torch (>=2.0.0)
|
|
24
|
+
Requires-Dist: xarray (>=2023.7.0)
|
|
25
|
+
Project-URL: Documentation, https://ipl-uv.github.io/satcube/
|
|
26
|
+
Project-URL: Repository, https://github.com/IPL-UV/satcube
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# satcube
|
|
30
|
+
|
|
31
|
+
[colab code](https://colab.research.google.com/drive/1)
|
satcube-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "satcube"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A Python package to create cloud-free monthly composites by fusing Landsat and Sentinel-2 data."
|
|
5
|
+
authors = ["Cesar Aybar <fcesar.aybar@uv.es>"]
|
|
6
|
+
repository = "https://github.com/IPL-UV/satcube"
|
|
7
|
+
documentation = "https://ipl-uv.github.io/satcube/"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
packages = [
|
|
10
|
+
{include = "satcube"}
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = ">=3.10,<4.0"
|
|
15
|
+
fastcubo = ">=0.0.999"
|
|
16
|
+
torch = ">=2.0.0"
|
|
17
|
+
pandas = ">=2.0.0"
|
|
18
|
+
pydantic = ">=2.8.0"
|
|
19
|
+
earthengine-api = ">=0.1.4.0"
|
|
20
|
+
matplotlib = ">=3.7.0"
|
|
21
|
+
numpy = ">=1.25.0"
|
|
22
|
+
phicloudmask = ">=0.0.2"
|
|
23
|
+
rasterio = ">=1.2.0"
|
|
24
|
+
requests = ">=2.26.0"
|
|
25
|
+
satalign = ">=0.0.999"
|
|
26
|
+
scikit-learn = ">=1.2.0"
|
|
27
|
+
segmentation-models-pytorch = ">=0.2.0"
|
|
28
|
+
xarray = ">=2023.7.0"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
|
+
pytest = "^7.2.0"
|
|
33
|
+
pytest-cov = "^4.0.0"
|
|
34
|
+
deptry = "^0.12.0"
|
|
35
|
+
mypy = "^1.5.1"
|
|
36
|
+
pre-commit = "^3.4.0"
|
|
37
|
+
tox = "^4.11.1"
|
|
38
|
+
|
|
39
|
+
[tool.poetry.group.docs.dependencies]
|
|
40
|
+
mkdocs = "^1.4.2"
|
|
41
|
+
mkdocs-material = "^9.2.7"
|
|
42
|
+
mkdocstrings = {extras = ["python"], version = "^0.23.0"}
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["poetry-core>=1.0.0"]
|
|
46
|
+
build-backend = "poetry.core.masonry.api"
|
|
47
|
+
|
|
48
|
+
[tool.mypy]
|
|
49
|
+
files = ["satcube"]
|
|
50
|
+
disallow_untyped_defs = "True"
|
|
51
|
+
disallow_any_unimported = "True"
|
|
52
|
+
no_implicit_optional = "True"
|
|
53
|
+
check_untyped_defs = "True"
|
|
54
|
+
warn_return_any = "True"
|
|
55
|
+
warn_unused_ignores = "True"
|
|
56
|
+
show_error_codes = "True"
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py37"
|
|
63
|
+
line-length = 120
|
|
64
|
+
fix = true
|
|
65
|
+
select = [
|
|
66
|
+
# flake8-2020
|
|
67
|
+
"YTT",
|
|
68
|
+
# flake8-bandit
|
|
69
|
+
"S",
|
|
70
|
+
# flake8-bugbear
|
|
71
|
+
"B",
|
|
72
|
+
# flake8-builtins
|
|
73
|
+
"A",
|
|
74
|
+
# flake8-comprehensions
|
|
75
|
+
"C4",
|
|
76
|
+
# flake8-debugger
|
|
77
|
+
"T10",
|
|
78
|
+
# flake8-simplify
|
|
79
|
+
"SIM",
|
|
80
|
+
# isort
|
|
81
|
+
"I",
|
|
82
|
+
# mccabe
|
|
83
|
+
"C90",
|
|
84
|
+
# pycodestyle
|
|
85
|
+
"E", "W",
|
|
86
|
+
# pyflakes
|
|
87
|
+
"F",
|
|
88
|
+
# pygrep-hooks
|
|
89
|
+
"PGH",
|
|
90
|
+
# pyupgrade
|
|
91
|
+
"UP",
|
|
92
|
+
# ruff
|
|
93
|
+
"RUF",
|
|
94
|
+
# tryceratops
|
|
95
|
+
"TRY",
|
|
96
|
+
]
|
|
97
|
+
ignore = [
|
|
98
|
+
# LineTooLong
|
|
99
|
+
"E501",
|
|
100
|
+
# DoNotAssignLambda
|
|
101
|
+
"E731",
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[tool.ruff.format]
|
|
105
|
+
preview = true
|
|
106
|
+
|
|
107
|
+
[tool.coverage.report]
|
|
108
|
+
skip_empty = true
|
|
109
|
+
|
|
110
|
+
[tool.coverage.run]
|
|
111
|
+
branch = true
|
|
112
|
+
source = ["satcube"]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
[tool.ruff.per-file-ignores]
|
|
116
|
+
"tests/*" = ["S101"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
class LandsatCloudDetector(torch.nn.Module):
|
|
4
|
+
def __init__(self):
|
|
5
|
+
super().__init__()
|
|
6
|
+
|
|
7
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
8
|
+
# Define bit flags for clouds based on the
|
|
9
|
+
# Landsat QA band documentation
|
|
10
|
+
cloud_flags = (1 << 3) | (1 << 4) | (1 << 1)
|
|
11
|
+
|
|
12
|
+
## Get the QA band
|
|
13
|
+
qa_band = x[6]
|
|
14
|
+
mask_band = x[:6].mean(axis=0)
|
|
15
|
+
mask_band[~torch.isnan(mask_band)] = 1
|
|
16
|
+
|
|
17
|
+
## Create a cloud mask
|
|
18
|
+
cloud_mask = torch.bitwise_and(qa_band.int(), cloud_flags) == 0
|
|
19
|
+
cloud_mask = cloud_mask.float()
|
|
20
|
+
cloud_mask[cloud_mask == 0] = torch.nan
|
|
21
|
+
cloud_mask[cloud_mask == 0] = 1
|
|
22
|
+
final_mask = cloud_mask * mask_band
|
|
23
|
+
return final_mask
|
|
24
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import pydantic
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Sensor(pydantic.BaseModel):
|
|
9
|
+
start_date: str
|
|
10
|
+
end_date: str
|
|
11
|
+
edge_size: int
|
|
12
|
+
bands: List[str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Sentinel2(Sensor):
|
|
16
|
+
weight_path: pathlib.Path
|
|
17
|
+
start_date: Optional[str] = "2015-06-27"
|
|
18
|
+
end_date: Optional[str] = datetime.now().strftime("%Y-%m-%d")
|
|
19
|
+
resolution: Optional[int] = 10
|
|
20
|
+
edge_size: Optional[int] = 384
|
|
21
|
+
embedding_universal: Optional[str] = "s2_embedding_model_universal.pt"
|
|
22
|
+
cloud_model_universal: str = "s2_cloud_model_universal.pt"
|
|
23
|
+
cloud_model_specific: str = "s2_cloud_model_specific.pt"
|
|
24
|
+
super_model_specific: str = "s2_super_model_specific.pt"
|
|
25
|
+
bands: List[str] = [
|
|
26
|
+
"B01",
|
|
27
|
+
"B02",
|
|
28
|
+
"B03",
|
|
29
|
+
"B04",
|
|
30
|
+
"B05",
|
|
31
|
+
"B06",
|
|
32
|
+
"B07",
|
|
33
|
+
"B08",
|
|
34
|
+
"B8A",
|
|
35
|
+
"B09",
|
|
36
|
+
"B10",
|
|
37
|
+
"B11",
|
|
38
|
+
"B12",
|
|
39
|
+
]
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
from typing import Optional, Tuple, Union
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import fastcubo
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import torch
|
|
8
|
+
|
|
9
|
+
from satcube.dataclass import Sensor
|
|
10
|
+
from satcube.utils import (aligned_s2, cloudmasking_s2, display_images,
|
|
11
|
+
gapfilling_s2, intermediate_process, interpolate_s2,
|
|
12
|
+
metadata_s2, monthly_composites_s2, smooth_s2, super_s2)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SatCube:
|
|
16
|
+
"""Satellite cube class to create datacubes from a specific sensor."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
sensor: Sensor,
|
|
21
|
+
output_dir: str,
|
|
22
|
+
max_workers: int,
|
|
23
|
+
coordinates: Tuple[float, float],
|
|
24
|
+
device: Union[str, torch.device],
|
|
25
|
+
):
|
|
26
|
+
"""Create a new instance of the Satellite Cube class.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
coordinates (Tuple[float, float]): The coordinates of the
|
|
30
|
+
location to download the data.
|
|
31
|
+
sensor (Sensor): The sensor object with all the information
|
|
32
|
+
to download and preprocess the data.
|
|
33
|
+
output_dir (str): The output directory to save the data.
|
|
34
|
+
max_workers (int): The maximum number of workers to use in the
|
|
35
|
+
download process.
|
|
36
|
+
device (Union[str, torch.device]): The device to use in the
|
|
37
|
+
cloud removal process.
|
|
38
|
+
"""
|
|
39
|
+
self.device = device
|
|
40
|
+
self.sensor = sensor
|
|
41
|
+
self.output_dir = pathlib.Path(output_dir)
|
|
42
|
+
self.max_workers = max_workers
|
|
43
|
+
self.lon, self.lat = coordinates
|
|
44
|
+
|
|
45
|
+
# If the output directory does not exist, create it
|
|
46
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
def metadata_s2(
|
|
49
|
+
self,
|
|
50
|
+
out_csv: Optional[pathlib.Path] = None,
|
|
51
|
+
quiet: Optional[pathlib.Path] = False,
|
|
52
|
+
force: Optional[bool] = False,
|
|
53
|
+
) -> pd.DataFrame:
|
|
54
|
+
"""Create a pd.DataFrame with the images to download and their
|
|
55
|
+
cloud cover percentage. The table is saved in a CSV file.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
force (Optional[bool], optional): If True, the query
|
|
59
|
+
process is done again. Defaults to False.
|
|
60
|
+
out_csv (Optional[pathlib.Path], optional): The path to the
|
|
61
|
+
CSV file with the query table. Defaults to None.
|
|
62
|
+
quiet (Optional[bool], optional): If True, no message is
|
|
63
|
+
displayed. Defaults to False.
|
|
64
|
+
force (Optional[bool], optional): If True, the query process
|
|
65
|
+
is done again. Defaults to False.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
pd.DataFrame: The table with the images to download.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if out_csv is None:
|
|
72
|
+
out_csv: pathlib.Path = self.output_dir / "s2_01_gee_query.csv"
|
|
73
|
+
|
|
74
|
+
if not out_csv.exists() or force:
|
|
75
|
+
query_table: pd.DataFrame = metadata_s2(
|
|
76
|
+
lon=self.lon,
|
|
77
|
+
lat=self.lat,
|
|
78
|
+
range_date=(self.sensor.start_date, self.sensor.end_date),
|
|
79
|
+
edge_size=self.sensor.edge_size,
|
|
80
|
+
quiet=quiet
|
|
81
|
+
)
|
|
82
|
+
query_table.to_csv(out_csv, index=False)
|
|
83
|
+
else:
|
|
84
|
+
query_table = pd.read_csv(out_csv)
|
|
85
|
+
|
|
86
|
+
return query_table
|
|
87
|
+
|
|
88
|
+
def download_s2_image(
|
|
89
|
+
self,
|
|
90
|
+
table: pd.DataFrame,
|
|
91
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
92
|
+
quiet: Optional[bool] = False,
|
|
93
|
+
force: Optional[bool] = False,
|
|
94
|
+
) -> pathlib.Path:
|
|
95
|
+
"""Download the images from the query table.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
table (pd.DataFrame): The table with the images to download.
|
|
99
|
+
out_csv (Optional[pathlib.Path], optional): The path to the
|
|
100
|
+
CSV file with the query table. Defaults to None.
|
|
101
|
+
quiet (Optional[bool], optional): If True, the download
|
|
102
|
+
process is not displayed. Defaults to False.
|
|
103
|
+
force (Optional[bool], optional): If True, the download
|
|
104
|
+
process is done again. Defaults to False.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
pathlib.Path: The path to the folder with the downloaded images.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
# Create the output directory if it does not exist
|
|
111
|
+
if out_folder is None:
|
|
112
|
+
output_path: pathlib.Path = self.output_dir / "s2_01_raw"
|
|
113
|
+
|
|
114
|
+
# Download the selected images
|
|
115
|
+
if not output_path.exists() or force:
|
|
116
|
+
if not quiet:
|
|
117
|
+
print(f"Saving the images in the directory {output_path}")
|
|
118
|
+
|
|
119
|
+
fastcubo.getPixels(
|
|
120
|
+
table=table, nworkers=self.max_workers, output_path=output_path
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Add folder path
|
|
124
|
+
table["folder"] = output_path
|
|
125
|
+
|
|
126
|
+
return table
|
|
127
|
+
|
|
128
|
+
def cloudmasking_s2(
|
|
129
|
+
self,
|
|
130
|
+
table: pd.DataFrame,
|
|
131
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
132
|
+
quiet: Optional[bool] = False,
|
|
133
|
+
force: Optional[bool] = False,
|
|
134
|
+
) -> pathlib.Path:
|
|
135
|
+
"""Remove the clouds from the data.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
table (pd.DataFrame): The table with the images to remove
|
|
139
|
+
the clouds.
|
|
140
|
+
out_csv (Optional[pathlib.Path], optional): The path to the
|
|
141
|
+
CSV file with the query table. Defaults to None.
|
|
142
|
+
quiet (Optional[bool], optional): If True, the messages
|
|
143
|
+
are not displayed. Defaults to False.
|
|
144
|
+
force (Optional[bool], optional): If True, the cloud removal
|
|
145
|
+
is done again. Defaults to False.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
pathlib.Path: The path to the folder with the
|
|
149
|
+
data without clouds.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
if out_folder is None:
|
|
153
|
+
out_folder: pathlib = self.output_dir / "s2_02_nocloud"
|
|
154
|
+
|
|
155
|
+
# Apply the cloud removal
|
|
156
|
+
out_table = intermediate_process(
|
|
157
|
+
table=table,
|
|
158
|
+
out_folder=out_folder,
|
|
159
|
+
process_function=cloudmasking_s2,
|
|
160
|
+
process_function_args=dict(
|
|
161
|
+
device=self.device,
|
|
162
|
+
sensor=self.sensor,
|
|
163
|
+
quiet=quiet
|
|
164
|
+
),
|
|
165
|
+
force=force,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Change the folder path
|
|
169
|
+
out_table["folder"] = out_folder
|
|
170
|
+
|
|
171
|
+
# Sort by cloud cover
|
|
172
|
+
out_table = out_table.sort_values(by="cloud_cover", ascending=False)
|
|
173
|
+
out_table.reset_index(drop=True, inplace=True)
|
|
174
|
+
|
|
175
|
+
return out_table
|
|
176
|
+
|
|
177
|
+
def gapfilling_s2(
|
|
178
|
+
self,
|
|
179
|
+
table: pd.DataFrame,
|
|
180
|
+
method: Optional[str] = "linear",
|
|
181
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
182
|
+
quiet: Optional[bool] = False,
|
|
183
|
+
force: Optional[bool] = False,
|
|
184
|
+
) -> pathlib.Path:
|
|
185
|
+
"""Fill the gaps in the data.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
force (Optional[bool], optional): If True, the gap filling
|
|
189
|
+
is done again. Defaults to False.
|
|
190
|
+
histogram_match_error (float, optional): If the error in the
|
|
191
|
+
histogram matching is greater than this value, the image
|
|
192
|
+
is not filled and therefore, it is removed. Defaults
|
|
193
|
+
to 0.10.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
pathlib.Path: The path to the folder with the
|
|
197
|
+
data without gaps.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
if out_folder is None:
|
|
201
|
+
out_folder: pathlib = self.output_dir / "s2_03_nogaps"
|
|
202
|
+
|
|
203
|
+
# Apply the cloud removal
|
|
204
|
+
out_table = intermediate_process(
|
|
205
|
+
table=table,
|
|
206
|
+
out_folder=out_folder,
|
|
207
|
+
process_function=gapfilling_s2,
|
|
208
|
+
process_function_args={"method": method, "quiet": quiet},
|
|
209
|
+
force=force,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Change the folder path
|
|
213
|
+
out_table["folder"] = out_folder
|
|
214
|
+
|
|
215
|
+
# Sort by the matching error
|
|
216
|
+
out_table = out_table.sort_values(by="match_error", ascending=False)
|
|
217
|
+
|
|
218
|
+
return out_table
|
|
219
|
+
|
|
220
|
+
def align_s2(
|
|
221
|
+
self,
|
|
222
|
+
table: pd.DataFrame,
|
|
223
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
224
|
+
quiet: Optional[bool] = False,
|
|
225
|
+
force: Optional[bool] = False,
|
|
226
|
+
) -> pathlib.Path:
|
|
227
|
+
"""Align all the images in the data cube.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
table (pd.DataFrame): The table with the images to align.
|
|
231
|
+
force (Optional[bool], optional): If True, the alignment
|
|
232
|
+
is done again. Defaults to False.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
pathlib.Path: The path to the folder with the
|
|
236
|
+
aligned images.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
if out_folder is None:
|
|
240
|
+
out_folder: pathlib = self.output_dir / "s2_04_aligned"
|
|
241
|
+
|
|
242
|
+
# Apply the cloud removal
|
|
243
|
+
out_table = intermediate_process(
|
|
244
|
+
table=table,
|
|
245
|
+
out_folder=out_folder,
|
|
246
|
+
process_function=aligned_s2,
|
|
247
|
+
process_function_args={"quiet": quiet},
|
|
248
|
+
force=force,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Change the folder path
|
|
252
|
+
out_table["folder"] = out_folder
|
|
253
|
+
|
|
254
|
+
return out_table
|
|
255
|
+
|
|
256
|
+
def monthly_composites_s2(
|
|
257
|
+
self,
|
|
258
|
+
table: Optional[pd.DataFrame],
|
|
259
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
260
|
+
agg_method: Optional[str] = "median",
|
|
261
|
+
date_range: Tuple[str, str] = ("2016-01-01", datetime.now().strftime("%Y-%m-%d")),
|
|
262
|
+
quiet: Optional[bool] = False,
|
|
263
|
+
force: Optional[bool] = False,
|
|
264
|
+
) -> pathlib.Path:
|
|
265
|
+
"""Smooth the data considering the temporal dimension.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
force (Optional[bool], optional): If True, the interpolation
|
|
269
|
+
is done again. Defaults to False.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
xr.Dataset: The interpolated data.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
if out_folder is None:
|
|
276
|
+
out_folder: pathlib = self.output_dir / "s2_05_monthlycomposites"
|
|
277
|
+
|
|
278
|
+
# Prepare the composites
|
|
279
|
+
out_table = intermediate_process(
|
|
280
|
+
table=table,
|
|
281
|
+
out_folder=out_folder,
|
|
282
|
+
process_function=monthly_composites_s2,
|
|
283
|
+
process_function_args=dict(
|
|
284
|
+
agg_method=agg_method,
|
|
285
|
+
date_range=date_range,
|
|
286
|
+
quiet=quiet
|
|
287
|
+
),
|
|
288
|
+
force=force
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Change the folder path
|
|
292
|
+
out_table["folder"] = out_folder
|
|
293
|
+
|
|
294
|
+
return out_table
|
|
295
|
+
|
|
296
|
+
def interpolate_s2(
|
|
297
|
+
self,
|
|
298
|
+
table: pd.DataFrame,
|
|
299
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
300
|
+
quiet: Optional[bool] = False,
|
|
301
|
+
force: Optional[bool] = False,
|
|
302
|
+
) -> pathlib.Path:
|
|
303
|
+
"""Interpolate the data.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
force (Optional[bool], optional): If True, the interpolation
|
|
307
|
+
is done again. Defaults to False.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
xr.Dataset: The interpolated data.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
if out_folder is None:
|
|
314
|
+
out_folder: pathlib = self.output_dir / "s2_06_interpolation"
|
|
315
|
+
|
|
316
|
+
# Apply the cloud removal
|
|
317
|
+
out_table = intermediate_process(
|
|
318
|
+
table=table,
|
|
319
|
+
out_folder=out_folder,
|
|
320
|
+
process_function=interpolate_s2,
|
|
321
|
+
process_function_args=dict(
|
|
322
|
+
quiet=quiet
|
|
323
|
+
),
|
|
324
|
+
force=force,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Change the folder path
|
|
328
|
+
out_table["folder"] = out_folder
|
|
329
|
+
|
|
330
|
+
return out_table
|
|
331
|
+
|
|
332
|
+
def smooth_s2(
|
|
333
|
+
self,
|
|
334
|
+
table: pd.DataFrame,
|
|
335
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
336
|
+
smooth_w: Optional[int] = 7,
|
|
337
|
+
smooth_p: Optional[int] = 1,
|
|
338
|
+
device: Union[str, torch.device, None] = None,
|
|
339
|
+
quiet: Optional[bool] = False,
|
|
340
|
+
force: Optional[bool] = False,
|
|
341
|
+
) -> pd.DataFrame:
|
|
342
|
+
"""Interpolate the data.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
force (Optional[bool], optional): If True, the interpolation
|
|
346
|
+
is done again. Defaults to False.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
xr.Dataset: The interpolated data.
|
|
350
|
+
"""
|
|
351
|
+
if out_folder is None:
|
|
352
|
+
out_folder: pathlib = self.output_dir / "s2_07_smoothed"
|
|
353
|
+
|
|
354
|
+
# Apply the cloud removal
|
|
355
|
+
out_table = intermediate_process(
|
|
356
|
+
table=table,
|
|
357
|
+
out_folder=out_folder,
|
|
358
|
+
process_function=smooth_s2,
|
|
359
|
+
process_function_args=dict(
|
|
360
|
+
quiet=quiet,
|
|
361
|
+
smooth_w=smooth_w,
|
|
362
|
+
smooth_p=smooth_p,
|
|
363
|
+
device=device if device is not None else self.device
|
|
364
|
+
),
|
|
365
|
+
force=force,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Change the folder path
|
|
369
|
+
out_table["folder"] = out_folder
|
|
370
|
+
|
|
371
|
+
return out_table
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def super_s2(
|
|
375
|
+
self,
|
|
376
|
+
table: pd.DataFrame,
|
|
377
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
378
|
+
quiet: Optional[bool] = False,
|
|
379
|
+
force: Optional[bool] = False,
|
|
380
|
+
) -> pd.DataFrame:
|
|
381
|
+
"""Superresolution to the Sentinel-2 image cube.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
table (pd.DataFrame): The table with the images to
|
|
385
|
+
superresolve.
|
|
386
|
+
out_folder (Optional[pathlib.Path], optional): The path to the
|
|
387
|
+
CSV file with the query table. Defaults to None.
|
|
388
|
+
quiet (Optional[bool], optional): If True, the messages
|
|
389
|
+
are not displayed. Defaults to False.
|
|
390
|
+
force (Optional[bool], optional): If True, the superresolution
|
|
391
|
+
is done again. Defaults to False.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
pd.DataFrame: The table with the superresolved images.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
if out_folder is None:
|
|
398
|
+
out_folder: pathlib.Path = self.output_dir / "s2_08_superresolution"
|
|
399
|
+
|
|
400
|
+
# Apply the Superresolution process
|
|
401
|
+
out_table = intermediate_process(
|
|
402
|
+
table=table,
|
|
403
|
+
out_folder=out_folder,
|
|
404
|
+
process_function=super_s2,
|
|
405
|
+
process_function_args=dict(
|
|
406
|
+
device=self.device,
|
|
407
|
+
sensor=self.sensor,
|
|
408
|
+
quiet=quiet
|
|
409
|
+
),
|
|
410
|
+
force=force,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Change the folder path
|
|
414
|
+
out_table["folder"] = out_folder
|
|
415
|
+
|
|
416
|
+
return out_table
|
|
417
|
+
|
|
418
|
+
def display_images(
|
|
419
|
+
self,
|
|
420
|
+
table: pd.DataFrame,
|
|
421
|
+
out_folder: Optional[pathlib.Path] = None,
|
|
422
|
+
bands: Optional[list[str]] = [2, 1, 0],
|
|
423
|
+
ratio: Optional[int] = 3000,
|
|
424
|
+
) -> pathlib.Path:
|
|
425
|
+
""" Display the images in the table.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
table (pd.DataFrame): The table with the images to display.
|
|
429
|
+
out_folder (Optional[pathlib.Path], optional): The path to the
|
|
430
|
+
CSV file with the query table. Defaults to None.
|
|
431
|
+
bands (Optional[list[str]], optional): The bands to display.
|
|
432
|
+
Defaults to [2, 1, 0].
|
|
433
|
+
ratio (Optional[int], optional): The ratio to divide the
|
|
434
|
+
image. Defaults to 3000. The larger the number, the
|
|
435
|
+
darker the image.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
pathlib.Path: The path to the folder with the
|
|
439
|
+
displayed images.
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
# Create the output folder
|
|
443
|
+
out_folder = (
|
|
444
|
+
self.output_dir / ("z_" + table["folder"].iloc[0].name + "_png")
|
|
445
|
+
)
|
|
446
|
+
out_folder.mkdir(exist_ok=True, parents=True)
|
|
447
|
+
|
|
448
|
+
return display_images(
|
|
449
|
+
table=table,
|
|
450
|
+
out_folder=out_folder,
|
|
451
|
+
bands=bands,
|
|
452
|
+
ratio=ratio,
|
|
453
|
+
)
|