satcube 0.1.16__tar.gz → 0.1.18__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.16 → satcube-0.1.18}/PKG-INFO +1 -1
- {satcube-0.1.16 → satcube-0.1.18}/pyproject.toml +1 -1
- {satcube-0.1.16 → satcube-0.1.18}/satcube/__init__.py +2 -4
- satcube-0.1.18/satcube/align.py +139 -0
- satcube-0.1.18/satcube/archive_cloud_detection.py +23 -0
- satcube-0.1.18/satcube/archive_dataclass.py +39 -0
- satcube-0.1.18/satcube/archive_main.py +453 -0
- satcube-0.1.18/satcube/archive_utils.py +1087 -0
- satcube-0.1.16/satcube/cloud_detection.py → satcube-0.1.18/satcube/cloud.py +100 -95
- satcube-0.1.18/satcube/composite.py +85 -0
- {satcube-0.1.16 → satcube-0.1.18}/satcube/download.py +2 -5
- satcube-0.1.18/satcube/gapfill.py +216 -0
- satcube-0.1.18/satcube/objects.py +243 -0
- satcube-0.1.18/satcube/smooth.py +46 -0
- satcube-0.1.16/satcube/align.py +0 -98
- satcube-0.1.16/satcube/objects.py +0 -71
- {satcube-0.1.16 → satcube-0.1.18}/LICENSE +0 -0
- {satcube-0.1.16 → satcube-0.1.18}/README.md +0 -0
- {satcube-0.1.16 → satcube-0.1.18}/satcube/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "satcube"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.18"
|
|
4
4
|
description = "A Python package to create cloud-free monthly composites by fusing Landsat and Sentinel-2 data."
|
|
5
5
|
authors = ["Cesar Aybar <fcesar.aybar@uv.es>"]
|
|
6
6
|
repository = "https://github.com/IPL-UV/satcube"
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
from satcube.cloud_detection import cloud_masking
|
|
2
1
|
from satcube.download import download
|
|
3
|
-
from satcube.align import align
|
|
4
|
-
import importlib.metadata
|
|
5
2
|
from satcube.objects import SatCubeMetadata
|
|
3
|
+
import importlib.metadata
|
|
6
4
|
|
|
7
|
-
__all__ = ["
|
|
5
|
+
__all__ = ["download", "SatCubeMetadata"]
|
|
8
6
|
# __version__ = importlib.metadata.version("satcube")
|
|
9
7
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import satalign
|
|
7
|
+
import numpy as np
|
|
8
|
+
import rasterio as rio
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
def _process_image(
|
|
13
|
+
image: np.ndarray,
|
|
14
|
+
reference: np.ndarray,
|
|
15
|
+
profile: dict,
|
|
16
|
+
output_path: pathlib.Path,
|
|
17
|
+
) -> Tuple[float, float]:
|
|
18
|
+
|
|
19
|
+
image_float = image.astype(np.float32) / 10000
|
|
20
|
+
image_float = image_float[np.newaxis, ...]
|
|
21
|
+
|
|
22
|
+
image, M = satalign.LGM(
|
|
23
|
+
datacube=image_float,
|
|
24
|
+
reference=reference
|
|
25
|
+
).run_multicore()
|
|
26
|
+
|
|
27
|
+
image = (image * 10000).astype(np.uint16).squeeze()
|
|
28
|
+
|
|
29
|
+
with rio.open(output_path, "w", **profile) as dst:
|
|
30
|
+
dst.write(image)
|
|
31
|
+
|
|
32
|
+
return M[0][0, 2], M[0][1, 2]
|
|
33
|
+
|
|
34
|
+
def _process_row(
|
|
35
|
+
row: pd.Series,
|
|
36
|
+
reference: np.ndarray,
|
|
37
|
+
input_dir: pathlib.Path,
|
|
38
|
+
output_dir: pathlib.Path
|
|
39
|
+
) -> Tuple[str, float, float]:
|
|
40
|
+
|
|
41
|
+
row_path = input_dir / (row["id"] + ".tif")
|
|
42
|
+
output_path = output_dir / (row["id"] + ".tif")
|
|
43
|
+
|
|
44
|
+
with rio.open(row_path) as src:
|
|
45
|
+
image = src.read()
|
|
46
|
+
profile = src.profile
|
|
47
|
+
|
|
48
|
+
dx_px, dy_px = _process_image(
|
|
49
|
+
image=image,
|
|
50
|
+
reference=reference,
|
|
51
|
+
profile=profile,
|
|
52
|
+
output_path=output_path
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return row["id"], dx_px, dy_px
|
|
56
|
+
|
|
57
|
+
def align_fn(
|
|
58
|
+
metadata: pd.DataFrame | None = None,
|
|
59
|
+
input_dir: str | pathlib.Path = "raw",
|
|
60
|
+
output_dir: str | pathlib.Path = "aligned",
|
|
61
|
+
nworks: int = 4,
|
|
62
|
+
cache: bool = False
|
|
63
|
+
) -> pd.DataFrame | None:
|
|
64
|
+
|
|
65
|
+
input_dir = pathlib.Path(input_dir).expanduser().resolve()
|
|
66
|
+
output_dir = pathlib.Path(output_dir).expanduser().resolve()
|
|
67
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
if metadata is None:
|
|
70
|
+
raise FileNotFoundError(
|
|
71
|
+
f"Add metadata file to do alignment."
|
|
72
|
+
"Please run the download step first."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
id_reference = metadata.sort_values(
|
|
77
|
+
by=["cs_cdf"],
|
|
78
|
+
ascending=False,
|
|
79
|
+
).iloc[0]["id"]
|
|
80
|
+
|
|
81
|
+
df = metadata.copy()
|
|
82
|
+
|
|
83
|
+
if cache:
|
|
84
|
+
exist_files = [file.stem for file in output_dir.glob("*.tif")]
|
|
85
|
+
df = df[~df["id"].isin(exist_files)]
|
|
86
|
+
if df.empty:
|
|
87
|
+
return metadata
|
|
88
|
+
|
|
89
|
+
reference_path = input_dir / (id_reference + ".tif")
|
|
90
|
+
|
|
91
|
+
with rio.open(reference_path) as ref_src:
|
|
92
|
+
reference = ref_src.read()
|
|
93
|
+
|
|
94
|
+
reference_float = reference.astype(np.float32) / 10000
|
|
95
|
+
|
|
96
|
+
results = []
|
|
97
|
+
|
|
98
|
+
with ThreadPoolExecutor(max_workers=nworks) as executor:
|
|
99
|
+
futures = {
|
|
100
|
+
executor.submit(
|
|
101
|
+
_process_row,
|
|
102
|
+
row=row,
|
|
103
|
+
reference=reference_float,
|
|
104
|
+
input_dir=input_dir,
|
|
105
|
+
output_dir=output_dir
|
|
106
|
+
): row["id"]
|
|
107
|
+
for _, row in df.iterrows()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for future in tqdm(
|
|
111
|
+
as_completed(futures),
|
|
112
|
+
total=len(futures),
|
|
113
|
+
desc="Aligning images",
|
|
114
|
+
unit="image",
|
|
115
|
+
leave=True
|
|
116
|
+
):
|
|
117
|
+
try:
|
|
118
|
+
img_id, dx_px, dy_px = future.result()
|
|
119
|
+
results.append({"id": img_id,
|
|
120
|
+
"dx_px": dx_px,
|
|
121
|
+
"dy_px": dy_px})
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error processing image: {e} {futures[future]}")
|
|
124
|
+
|
|
125
|
+
shift_df = pd.DataFrame(results)
|
|
126
|
+
|
|
127
|
+
metadata = metadata.drop(
|
|
128
|
+
columns=["dx_px","dy_px"],
|
|
129
|
+
errors="ignore"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
metadata = metadata.merge(
|
|
133
|
+
shift_df,
|
|
134
|
+
on="id",
|
|
135
|
+
how="left",
|
|
136
|
+
suffixes=('', '')
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return metadata
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
|
@@ -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.archive_dataclass import Sensor
|
|
10
|
+
from satcube.archive_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
|
+
)
|