satcube 0.1.14__py3-none-any.whl → 0.1.15__py3-none-any.whl
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/__init__.py +4 -5
- satcube/align.py +84 -135
- satcube/cloud_detection.py +41 -45
- satcube/download.py +48 -45
- satcube/objects.py +71 -0
- {satcube-0.1.14.dist-info → satcube-0.1.15.dist-info}/METADATA +3 -2
- satcube-0.1.15.dist-info/RECORD +10 -0
- satcube/cloud_detection_old.py +0 -24
- satcube/dataclass.py +0 -39
- satcube/download_old.py +0 -82
- satcube/main.py +0 -453
- satcube/utils_old.py +0 -1087
- satcube-0.1.14.dist-info/RECORD +0 -14
- {satcube-0.1.14.dist-info → satcube-0.1.15.dist-info}/LICENSE +0 -0
- {satcube-0.1.14.dist-info → satcube-0.1.15.dist-info}/WHEEL +0 -0
satcube/__init__.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from satcube.cloud_detection import cloud_masking
|
|
2
2
|
from satcube.download import download
|
|
3
3
|
from satcube.align import align
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
__all__ = ["cloud_masking", "download", "align"]
|
|
7
|
-
|
|
8
4
|
import importlib.metadata
|
|
9
|
-
|
|
5
|
+
from satcube.objects import SatCubeMetadata
|
|
6
|
+
|
|
7
|
+
__all__ = ["cloud_masking", "download", "align", "SatCubeMetadata"]
|
|
8
|
+
# __version__ = importlib.metadata.version("satcube")
|
|
10
9
|
|
satcube/align.py
CHANGED
|
@@ -3,147 +3,96 @@ from __future__ import annotations
|
|
|
3
3
|
import pathlib
|
|
4
4
|
from typing import List, Tuple
|
|
5
5
|
import pickle
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import satalign
|
|
8
|
+
import shutil
|
|
6
9
|
|
|
7
10
|
import numpy as np
|
|
8
|
-
import rasterio
|
|
11
|
+
import rasterio as rio
|
|
9
12
|
import xarray as xr
|
|
10
13
|
from affine import Affine
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def
|
|
16
|
-
input_dir
|
|
17
|
-
output_dir
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
input_dir
|
|
29
|
-
Directory containing masked Sentinel‑2 *TIFF* tiles produced by the
|
|
30
|
-
previous preprocessing stage.
|
|
31
|
-
output_dir
|
|
32
|
-
Directory where the alignment artefacts (``datacube.pickle``) and, if
|
|
33
|
-
requested, one aligned *GeoTIFF* per date will be written.
|
|
34
|
-
channel
|
|
35
|
-
Datacube band used by the *PCC* model for correlation. ``"mean"`` is
|
|
36
|
-
recommended because it carries fewer noise artefacts.
|
|
37
|
-
crop_center
|
|
38
|
-
Half‑size (in pixels) of the square window extracted around the scene
|
|
39
|
-
centre that is fed to the correlation engine.
|
|
40
|
-
num_threads
|
|
41
|
-
Number of CPU threads for the multi‑core phase‑correlation run.
|
|
42
|
-
save_tiffs
|
|
43
|
-
If *True* (default) the aligned datacube is exported to tiled *COGs* via
|
|
44
|
-
:func:`save_aligned_cube_to_tiffs`.
|
|
45
|
-
pickle_datacube
|
|
46
|
-
If *True* (default) the raw (unaligned) datacube is pickled to
|
|
47
|
-
``datacube.pickle`` inside *output_dir* for reproducibility/debugging.
|
|
48
|
-
|
|
49
|
-
Returns
|
|
50
|
-
-------
|
|
51
|
-
aligned_cube, warp_matrices
|
|
52
|
-
*aligned_cube* is the spatially aligned datacube as an
|
|
53
|
-
:class:`xarray.DataArray`; *warp_matrices* is the list of 3 × 3 affine
|
|
54
|
-
homography matrices (one per time step) returned by the
|
|
55
|
-
:pyclass:`~satalign.PCC` engine.
|
|
56
|
-
"""
|
|
57
|
-
input_path = pathlib.Path(input_dir).expanduser().resolve()
|
|
58
|
-
output_path = pathlib.Path(output_dir).expanduser().resolve()
|
|
59
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
60
|
-
|
|
61
|
-
# ── 1. Build datacube ────────────────────────────────────────────
|
|
62
|
-
da = sat.utils.create_array(input_path)
|
|
63
|
-
|
|
64
|
-
# ── 2. Select reference slice (highest cloud‑score CDF) ───────────
|
|
65
|
-
da_sorted = da.sortby("cs_cdf", ascending=False)
|
|
66
|
-
ref_slice = da_sorted.isel(time=0)
|
|
67
|
-
reference = ref_slice.where((ref_slice != 0) & (ref_slice != 65535))
|
|
68
|
-
|
|
69
|
-
# ── 3. Instantiate and run PCC model ─────────────────────────────
|
|
70
|
-
pcc_model = sat.PCC(
|
|
71
|
-
datacube=da,
|
|
72
|
-
reference=reference,
|
|
73
|
-
channel=channel,
|
|
74
|
-
crop_center=crop_center,
|
|
75
|
-
num_threads=num_threads,
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def process_row(row: pd.Series, reference: np.ndarray, input_dir: pathlib.Path, output_dir: pathlib.Path) -> None:
|
|
19
|
+
row_path = input_dir / (row["id"] + ".tif")
|
|
20
|
+
output_path = output_dir / (row["id"] + ".tif")
|
|
21
|
+
with rio.open(row_path) as src:
|
|
22
|
+
row_image = src.read()
|
|
23
|
+
profile_image = src.profile
|
|
24
|
+
|
|
25
|
+
row_image_float = row_image.astype(np.float32) / 10000
|
|
26
|
+
row_image_float = row_image_float[np.newaxis, ...]
|
|
27
|
+
|
|
28
|
+
pcc_model = satalign.LGM(
|
|
29
|
+
datacube = row_image_float,
|
|
30
|
+
reference = reference
|
|
76
31
|
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# ── 4. Optionally export as Cloud‑Optimised GeoTIFFs ────────────
|
|
80
|
-
if save_tiffs:
|
|
81
|
-
save_aligned_cube_to_tiffs(aligned_cube, output_path)
|
|
82
|
-
|
|
83
|
-
return aligned_cube, warp_matrices
|
|
32
|
+
image, _ = pcc_model.run_multicore()
|
|
33
|
+
image = (image * 10000).astype(np.uint16).squeeze()
|
|
84
34
|
|
|
35
|
+
with rio.open(output_path, "w", **profile_image) as dst:
|
|
36
|
+
dst.write(image)
|
|
85
37
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
38
|
+
def align(
|
|
39
|
+
input_dir: str | pathlib.Path = "raw",
|
|
40
|
+
output_dir: str | pathlib.Path = "aligned",
|
|
41
|
+
nworks: int = 4,
|
|
42
|
+
cache: bool = False
|
|
91
43
|
) -> None:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
data = da_t.transpose("band", "y", "x").values
|
|
131
|
-
|
|
132
|
-
profile = {
|
|
133
|
-
"driver": da_t.attrs.get("driver", "GTiff"),
|
|
134
|
-
"height": da_t.sizes["y"],
|
|
135
|
-
"width": da_t.sizes["x"],
|
|
136
|
-
"count": da_t.sizes["band"],
|
|
137
|
-
"dtype": str(da_t.dtype),
|
|
138
|
-
"transform": transform,
|
|
139
|
-
"crs": crs,
|
|
140
|
-
"nodata": int(getattr(da_t, "nodata", 0)),
|
|
141
|
-
"tiled": True,
|
|
142
|
-
"blockxsize": block_size,
|
|
143
|
-
"blockysize": block_size,
|
|
144
|
-
"interleave": "band",
|
|
44
|
+
|
|
45
|
+
input_dir = pathlib.Path(input_dir).expanduser().resolve()
|
|
46
|
+
output_dir = pathlib.Path(output_dir).expanduser().resolve()
|
|
47
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
|
|
49
|
+
metadata_path = input_dir / "metadata.csv"
|
|
50
|
+
|
|
51
|
+
if not metadata_path.exists():
|
|
52
|
+
raise FileNotFoundError(
|
|
53
|
+
f"Metadata file not found: {metadata_path}. "
|
|
54
|
+
"Please run the download step first."
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
metadata = pd.read_csv(metadata_path)
|
|
58
|
+
|
|
59
|
+
if cache:
|
|
60
|
+
exist_files = [file.stem for file in output_dir.glob("*.tif")]
|
|
61
|
+
metadata = metadata[~metadata["id"].isin(exist_files)]
|
|
62
|
+
|
|
63
|
+
if metadata.empty:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
id_reference = metadata.sort_values(
|
|
67
|
+
by=["cs_cdf", "date"],
|
|
68
|
+
ascending=False,
|
|
69
|
+
).iloc[0]["id"]
|
|
70
|
+
|
|
71
|
+
reference_path = input_dir / (id_reference + ".tif")
|
|
72
|
+
|
|
73
|
+
with rio.open(reference_path) as ref_src:
|
|
74
|
+
reference = ref_src.read()
|
|
75
|
+
|
|
76
|
+
reference_float = reference.astype(np.float32) / 10000
|
|
77
|
+
|
|
78
|
+
with ThreadPoolExecutor(max_workers=nworks) as executor:
|
|
79
|
+
futures = {
|
|
80
|
+
executor.submit(process_row, row, reference_float, input_dir, output_dir)
|
|
81
|
+
for _, row in metadata.iterrows()
|
|
145
82
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
83
|
+
for future in tqdm(
|
|
84
|
+
as_completed(futures),
|
|
85
|
+
total=len(futures),
|
|
86
|
+
desc="Aligning images",
|
|
87
|
+
unit="image",
|
|
88
|
+
leave=True
|
|
89
|
+
):
|
|
90
|
+
try:
|
|
91
|
+
future.result()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Error processing image: {e}")
|
|
94
|
+
|
|
95
|
+
metadata = input_dir / "metadata.csv"
|
|
96
|
+
if metadata.exists():
|
|
97
|
+
metadata_dst = output_dir / "metadata.csv"
|
|
98
|
+
shutil.copy(metadata, metadata_dst)
|
satcube/cloud_detection.py
CHANGED
|
@@ -20,9 +20,11 @@ import rasterio as rio
|
|
|
20
20
|
from rasterio.windows import Window
|
|
21
21
|
import torch
|
|
22
22
|
import pandas as pd
|
|
23
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
23
24
|
from tqdm import tqdm
|
|
24
25
|
import rasterio as rio
|
|
25
26
|
from rasterio.merge import merge
|
|
27
|
+
import shutil
|
|
26
28
|
|
|
27
29
|
from satcube.utils import define_iteration, DeviceManager
|
|
28
30
|
import warnings
|
|
@@ -35,7 +37,6 @@ warnings.filterwarnings(
|
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
|
|
39
40
|
def infer_cloudmask(
|
|
40
41
|
input_path: str | pathlib.Path,
|
|
41
42
|
output_path: str | pathlib.Path,
|
|
@@ -68,13 +69,10 @@ def infer_cloudmask(
|
|
|
68
69
|
-------
|
|
69
70
|
pathlib.Path : The path to the created output image.
|
|
70
71
|
"""
|
|
71
|
-
|
|
72
|
-
# output_path = "/home/contreras/Documents/GitHub/satcube2/masked/2018-07-15_6q49m.tif"
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
input_path = pathlib.Path(input_path)
|
|
75
74
|
output_path = pathlib.Path(output_path)
|
|
76
75
|
|
|
77
|
-
# 1) Validate metadata
|
|
78
76
|
with rio.open(input_path) as src:
|
|
79
77
|
meta = src.profile
|
|
80
78
|
if not meta.get("tiled", False):
|
|
@@ -85,21 +83,13 @@ def infer_cloudmask(
|
|
|
85
83
|
f"got {meta['blockxsize']}x{meta['blockysize']}")
|
|
86
84
|
height, width = meta["height"], meta["width"]
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
# 2) Crear un buffer en RAM (float32)
|
|
90
86
|
full_mask = np.zeros((height, width), dtype=np.float32)
|
|
91
87
|
|
|
92
|
-
# 3) Iterar por ventanas
|
|
93
|
-
|
|
94
88
|
coords = define_iteration((height, width), chunk_size, overlap)
|
|
95
89
|
|
|
96
90
|
with rio.open(input_path) as src:
|
|
97
91
|
|
|
98
|
-
for (row_off, col_off) in
|
|
99
|
-
coords,
|
|
100
|
-
desc=f"{prefix} Inference on {input_path.name}",
|
|
101
|
-
position=1,
|
|
102
|
-
leave=False):
|
|
92
|
+
for (row_off, col_off) in coords:
|
|
103
93
|
|
|
104
94
|
window = Window(col_off, row_off, chunk_size, chunk_size)
|
|
105
95
|
patch = src.read(window=window) / 1e4
|
|
@@ -143,9 +133,9 @@ def infer_cloudmask(
|
|
|
143
133
|
with rio.open(output_mask, "w", **out_meta) as dst:
|
|
144
134
|
dst.write(full_mask, 1)
|
|
145
135
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
|
|
137
|
+
data = src.read()
|
|
138
|
+
img_prof = src.profile.copy()
|
|
149
139
|
|
|
150
140
|
masked = data.copy()
|
|
151
141
|
masked[:, full_mask != 0] = 65535
|
|
@@ -160,9 +150,9 @@ def cloud_masking(
|
|
|
160
150
|
input: str | pathlib.Path = "raw",
|
|
161
151
|
output: str | pathlib.Path = "masked",
|
|
162
152
|
model_path: str | pathlib.Path = "SEN2CloudEnsemble",
|
|
163
|
-
save_mask: bool = False,
|
|
164
153
|
device: str = "cpu",
|
|
165
|
-
|
|
154
|
+
save_mask: bool = False,
|
|
155
|
+
nworks: int = 4,
|
|
166
156
|
) -> list[pathlib.Path]:
|
|
167
157
|
"""Write cloud-masked Sentinel-2 images.
|
|
168
158
|
|
|
@@ -214,29 +204,35 @@ def cloud_masking(
|
|
|
214
204
|
model = mlstac.load(model_path)
|
|
215
205
|
cloud_model = DeviceManager(model, init_device=device).model
|
|
216
206
|
cloud_model.eval()
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
207
|
+
|
|
208
|
+
with ThreadPoolExecutor(max_workers=nworks) as executor:
|
|
209
|
+
futures = {
|
|
210
|
+
executor.submit(
|
|
211
|
+
infer_cloudmask,
|
|
212
|
+
input_path=p,
|
|
213
|
+
output_path=dst_dir / p.name,
|
|
214
|
+
cloud_model=cloud_model,
|
|
215
|
+
device=device,
|
|
216
|
+
save_mask=save_mask,
|
|
217
|
+
prefix=f"[{i+1}/{len(tif_paths)}] "
|
|
218
|
+
): p for i, p in enumerate(tif_paths)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for future in tqdm(
|
|
222
|
+
as_completed(futures),
|
|
223
|
+
total=len(futures),
|
|
224
|
+
desc="Cloud Masking",
|
|
225
|
+
position=0,
|
|
226
|
+
leave=True
|
|
227
|
+
):
|
|
228
|
+
p = futures[future]
|
|
229
|
+
try:
|
|
230
|
+
result = future.result()
|
|
231
|
+
print(f"{result} processed successfully.")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
print(f"Error processing {p}: {e}")
|
|
234
|
+
|
|
235
|
+
metadata = src / "metadata.csv"
|
|
236
|
+
if metadata.exists():
|
|
237
|
+
metadata_dst = dst_dir / "metadata.csv"
|
|
238
|
+
shutil.copy(metadata, metadata_dst)
|
satcube/download.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import sys, time, threading, itertools
|
|
2
2
|
import cubexpress as ce
|
|
3
3
|
import pandas as pd
|
|
4
|
+
from satcube.objects import SatCubeMetadata
|
|
5
|
+
import pathlib
|
|
4
6
|
|
|
5
7
|
def download(
|
|
6
8
|
lon: float,
|
|
@@ -12,54 +14,55 @@ def download(
|
|
|
12
14
|
max_cscore: float = 1,
|
|
13
15
|
min_cscore: float = 0,
|
|
14
16
|
outfolder: str = "raw",
|
|
15
|
-
nworks: int = 4
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"""
|
|
21
|
-
"""
|
|
22
|
-
stop_flag = {"v": False}
|
|
17
|
+
nworks: int = 4
|
|
18
|
+
) -> "SatCubeMetadata":
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
outfolder = pathlib.Path(outfolder).resolve()
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
table = ce.s2_table(
|
|
24
|
+
lon=lon,
|
|
25
|
+
lat=lat,
|
|
26
|
+
edge_size=edge_size,
|
|
27
|
+
start=start,
|
|
28
|
+
end=end,
|
|
29
|
+
max_cscore=max_cscore,
|
|
30
|
+
min_cscore=min_cscore
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
requests = ce.table_to_requestset(
|
|
34
|
+
table=table,
|
|
35
|
+
mosaic=True
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
ce.get_cube(
|
|
39
|
+
requests=requests,
|
|
40
|
+
outfolder=outfolder,
|
|
41
|
+
nworks=nworks
|
|
42
|
+
)
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
table_req = (
|
|
45
|
+
requests._dataframe.copy()
|
|
46
|
+
.drop(columns=['geotransform', 'manifest', 'outname', 'width', 'height', 'scale_x', 'scale_y'])
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
table_req['date'] = table_req['id'].str.split('_').str[0]
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
table
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
start=start,
|
|
46
|
-
end=end,
|
|
47
|
-
max_cscore=max_cscore,
|
|
48
|
-
min_cscore=min_cscore,
|
|
49
|
-
cache=cache,
|
|
50
|
-
verbose=verbose
|
|
51
|
+
result_table = (
|
|
52
|
+
table.groupby('date')
|
|
53
|
+
.agg(
|
|
54
|
+
id=('id', lambda x: '-'.join(x)),
|
|
55
|
+
cs_cdf=('cs_cdf', 'first')
|
|
51
56
|
)
|
|
57
|
+
.reset_index()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
table_final = table_req.merge(
|
|
61
|
+
result_table,
|
|
62
|
+
on='date',
|
|
63
|
+
how='left'
|
|
64
|
+
).rename(columns={'id_x': 'id', 'id_y': 'gee_ids'})
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
table=table,
|
|
55
|
-
outfolder=outfolder,
|
|
56
|
-
nworks=nworks,
|
|
57
|
-
verbose=verbose,
|
|
58
|
-
cache=cache
|
|
59
|
-
)
|
|
60
|
-
finally:
|
|
61
|
-
stop_flag["v"] = True
|
|
62
|
-
if th is not None:
|
|
63
|
-
th.join()
|
|
66
|
+
table_final.to_csv(outfolder / "metadata.csv", index=False)
|
|
64
67
|
|
|
65
|
-
return
|
|
68
|
+
return SatCubeMetadata(df=table_final, raw_dir=outfolder)
|
satcube/objects.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# satcube/objects.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
import pathlib
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from satcube.align import align as _align_fn
|
|
8
|
+
from satcube.cloud_detection import cloud_masking as _cloud_fn
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class SatCubeMetadata:
|
|
12
|
+
df: pd.DataFrame = field(repr=False)
|
|
13
|
+
raw_dir: pathlib.Path = field(repr=False)
|
|
14
|
+
|
|
15
|
+
def __repr__(self) -> str:
|
|
16
|
+
return self.df.__repr__()
|
|
17
|
+
|
|
18
|
+
__str__ = __repr__
|
|
19
|
+
|
|
20
|
+
def _repr_html_(self) -> str:
|
|
21
|
+
html = getattr(self.df, "_repr_html_", None)
|
|
22
|
+
return html() if callable(html) else self.df.__repr__()
|
|
23
|
+
|
|
24
|
+
def align(
|
|
25
|
+
self,
|
|
26
|
+
input_dir: str | pathlib.Path | None = None,
|
|
27
|
+
output_dir: str | pathlib.Path = "aligned",
|
|
28
|
+
nworks: int = 4,
|
|
29
|
+
cache: bool = False
|
|
30
|
+
) -> "SatCubeMetadata":
|
|
31
|
+
|
|
32
|
+
if input_dir is None:
|
|
33
|
+
input_dir = self.raw_dir
|
|
34
|
+
else:
|
|
35
|
+
input_dir = pathlib.Path(input_dir).expanduser().resolve()
|
|
36
|
+
|
|
37
|
+
_align_fn(
|
|
38
|
+
input_dir=input_dir,
|
|
39
|
+
output_dir=output_dir,
|
|
40
|
+
nworks=nworks,
|
|
41
|
+
cache=cache
|
|
42
|
+
)
|
|
43
|
+
self.aligned_dir = pathlib.Path(output_dir).resolve()
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def cloud_masking(
|
|
47
|
+
self,
|
|
48
|
+
output_dir: str | pathlib.Path = "masked",
|
|
49
|
+
model_path: str | pathlib.Path = "SEN2CloudEnsemble",
|
|
50
|
+
device: str = "cpu"
|
|
51
|
+
) -> "SatCubeMetadata":
|
|
52
|
+
if not hasattr(self, "aligned_dir"):
|
|
53
|
+
raise RuntimeError("You must run .align() first")
|
|
54
|
+
_cloud_fn(
|
|
55
|
+
input=self.aligned_dir,
|
|
56
|
+
output=output_dir,
|
|
57
|
+
model_path=model_path,
|
|
58
|
+
device=device
|
|
59
|
+
)
|
|
60
|
+
self.masked_dir = pathlib.Path(output_dir).resolve()
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def __getattr__(self, item):
|
|
64
|
+
return getattr(self.df, item)
|
|
65
|
+
|
|
66
|
+
def __getitem__(self, key):
|
|
67
|
+
return self.df.__getitem__(key)
|
|
68
|
+
|
|
69
|
+
def __len__(self):
|
|
70
|
+
return len(self.df)
|
|
71
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: satcube
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: A Python package to create cloud-free monthly composites by fusing Landsat and Sentinel-2 data.
|
|
5
5
|
Home-page: https://github.com/IPL-UV/satcube
|
|
6
6
|
Author: Cesar Aybar
|
|
@@ -16,9 +16,10 @@ Requires-Dist: cubexpress (>=0.1.10)
|
|
|
16
16
|
Requires-Dist: mlstac (>=0.4.0)
|
|
17
17
|
Requires-Dist: phicloudmask (>=0.0.2)
|
|
18
18
|
Requires-Dist: requests (>=2.26.0)
|
|
19
|
-
Requires-Dist: satalign (>=0.1.
|
|
19
|
+
Requires-Dist: satalign (>=0.1.9)
|
|
20
20
|
Requires-Dist: scikit-learn (>=1.2.0)
|
|
21
21
|
Requires-Dist: segmentation-models-pytorch (>=0.3.0)
|
|
22
|
+
Requires-Dist: tqdm (>=4.67.1)
|
|
22
23
|
Requires-Dist: xarray (>=2023.7.0)
|
|
23
24
|
Project-URL: Documentation, https://ipl-uv.github.io/satcube/
|
|
24
25
|
Project-URL: Repository, https://github.com/IPL-UV/satcube
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
satcube/__init__.py,sha256=1q5c8Kx1dePuPBo29O7-1MA0L_GdBhi56gbxjQAa1_o,317
|
|
2
|
+
satcube/align.py,sha256=0QOzX9f-r05F8niI91xlvifmyHaAQjWgBit_4Jc6OeM,2979
|
|
3
|
+
satcube/cloud_detection.py,sha256=05OifUtWjE5jco8Q1jf_7qoQ45qrnJQgRc6LrPlMkLI,7898
|
|
4
|
+
satcube/download.py,sha256=o8kwPzipU1f_pV0KNPLmLmjUEsoWb6yhkCt7kerDViU,1545
|
|
5
|
+
satcube/objects.py,sha256=zhiYu-rUTm65TuEl2qIo7kLfpdam17_6CZl2KBCT9Jw,1988
|
|
6
|
+
satcube/utils.py,sha256=QBdmSg6_4Vy-4mXH1Z3Z2AvbIKAXBYi5g3-IgWSE1MY,3660
|
|
7
|
+
satcube-0.1.15.dist-info/LICENSE,sha256=YdB4BQMkMzWuKvXRIpQR4g91IQ_pwA5PSH2lNM97zFI,1070
|
|
8
|
+
satcube-0.1.15.dist-info/METADATA,sha256=iyw3WijWmTfyg0-c-1qdxZ8UkgTd7XbnOnzZAs2QAV4,6558
|
|
9
|
+
satcube-0.1.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
10
|
+
satcube-0.1.15.dist-info/RECORD,,
|
satcube/cloud_detection_old.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
|
satcube/dataclass.py
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
]
|