satcube 0.1.14__tar.gz → 0.1.15__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satcube
3
- Version: 0.1.14
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.12)
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "satcube"
3
- version = "0.1.14"
3
+ version = "0.1.15"
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"
@@ -12,12 +12,13 @@ packages = [{ include = "satcube" }]
12
12
  python = ">=3.9"
13
13
  cubexpress = ">=0.1.10"
14
14
  mlstac = ">=0.4.0"
15
- satalign = ">=0.1.12"
15
+ satalign = ">=0.1.9"
16
16
  segmentation-models-pytorch = ">=0.3.0"
17
17
  phicloudmask = ">=0.0.2"
18
18
  scikit-learn = ">=1.2.0"
19
19
  requests = ">=2.26.0"
20
20
  xarray = ">=2023.7.0"
21
+ tqdm = ">=4.67.1"
21
22
 
22
23
  [tool.poetry.extras]
23
24
  full = ["torch"]
@@ -0,0 +1,9 @@
1
+ from satcube.cloud_detection import cloud_masking
2
+ from satcube.download import download
3
+ from satcube.align import align
4
+ import importlib.metadata
5
+ from satcube.objects import SatCubeMetadata
6
+
7
+ __all__ = ["cloud_masking", "download", "align", "SatCubeMetadata"]
8
+ # __version__ = importlib.metadata.version("satcube")
9
+
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ import pathlib
4
+ from typing import List, Tuple
5
+ import pickle
6
+ import pandas as pd
7
+ import satalign
8
+ import shutil
9
+
10
+ import numpy as np
11
+ import rasterio as rio
12
+ import xarray as xr
13
+ from affine import Affine
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
31
+ )
32
+ image, _ = pcc_model.run_multicore()
33
+ image = (image * 10000).astype(np.uint16).squeeze()
34
+
35
+ with rio.open(output_path, "w", **profile_image) as dst:
36
+ dst.write(image)
37
+
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
43
+ ) -> None:
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()
82
+ }
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)
@@ -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
- # image_path = "/home/contreras/Documents/GitHub/satcube2/raw/2018-07-15_6q49m.tif"
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 tqdm(
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
- with rio.open(input_path) as src_img:
147
- data = src_img.read()
148
- img_prof = src_img.profile.copy()
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
- cache: bool = True
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
- outs = [
219
- infer_cloudmask(
220
- input_path=p,
221
- output_path=dst_dir / p.name,
222
- cloud_model=cloud_model,
223
- device=device,
224
- save_mask=save_mask,
225
- prefix=f"[{i+1}/{len(tif_paths)}] "
226
- )
227
- for i, p in enumerate(tif_paths)
228
- ]
229
-
230
- df_out = (
231
- pd.Series(outs, name="path")
232
- .to_frame()
233
- .assign(path_str=lambda df: df["path"].astype(str))
234
- .assign(
235
- date = lambda df: df["path_str"].str.extract(r"(\d{4}-\d{2}-\d{2})", expand=False),
236
- score = lambda df: df["path_str"].str.extract(r"_(\d+)\.tif$", expand=False).astype(int) / 100
237
- )
238
- .drop(columns="path_str") # ya no la necesitamos
239
- .sort_values("score", ascending=False, ignore_index=True)
240
- )
241
-
242
- return df_out
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)
@@ -0,0 +1,68 @@
1
+ import sys, time, threading, itertools
2
+ import cubexpress as ce
3
+ import pandas as pd
4
+ from satcube.objects import SatCubeMetadata
5
+ import pathlib
6
+
7
+ def download(
8
+ lon: float,
9
+ lat: float,
10
+ edge_size: int,
11
+ start: str,
12
+ end: str,
13
+ *,
14
+ max_cscore: float = 1,
15
+ min_cscore: float = 0,
16
+ outfolder: str = "raw",
17
+ nworks: int = 4
18
+ ) -> "SatCubeMetadata":
19
+
20
+
21
+ outfolder = pathlib.Path(outfolder).resolve()
22
+
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
+ )
43
+
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]
50
+
51
+ result_table = (
52
+ table.groupby('date')
53
+ .agg(
54
+ id=('id', lambda x: '-'.join(x)),
55
+ cs_cdf=('cs_cdf', 'first')
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'})
65
+
66
+ table_final.to_csv(outfolder / "metadata.csv", index=False)
67
+
68
+ return SatCubeMetadata(df=table_final, raw_dir=outfolder)
@@ -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,10 +0,0 @@
1
- from satcube.cloud_detection import cloud_masking
2
- from satcube.download import download
3
- from satcube.align import align
4
-
5
-
6
- __all__ = ["cloud_masking", "download", "align"]
7
-
8
- import importlib.metadata
9
- __version__ = importlib.metadata.version("satcube")
10
-
@@ -1,149 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import pathlib
4
- from typing import List, Tuple
5
- import pickle
6
-
7
- import numpy as np
8
- import rasterio
9
- import xarray as xr
10
- from affine import Affine
11
-
12
- import satalign as sat
13
-
14
-
15
- def align(
16
- input_dir: str | pathlib.Path,
17
- output_dir: str | pathlib.Path,
18
- *,
19
- channel: str = "mean",
20
- crop_center: int = 128,
21
- num_threads: int = 2,
22
- save_tiffs: bool = True,
23
- ) -> Tuple[xr.DataArray, List[np.ndarray]]:
24
- """Align all masked Sentinel‑2 tiles found in *input_dir*.
25
-
26
- Parameters
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,
76
- )
77
- aligned_cube, warp_matrices = pcc_model.run_multicore()
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
84
-
85
-
86
- def save_aligned_cube_to_tiffs(
87
- aligned_cube: xr.DataArray,
88
- out_dir: str | pathlib.Path,
89
- *,
90
- block_size: int = 128,
91
- ) -> None:
92
- """Write each time slice of *aligned_cube* to an individual tiled COG.
93
-
94
- The filenames follow the pattern ``YYYY‑MM‑DD.tif``.
95
-
96
- Parameters
97
- ----------
98
- aligned_cube
99
- Datacube returned by :func:`align_datacube`.
100
- out_dir
101
- Target directory; it will be created if it does not exist.
102
- block_size
103
- Internal tile size (*rasterio* ``blockxsize`` and ``blockysize``).
104
- """
105
- out_dir_path = pathlib.Path(out_dir).expanduser().resolve()
106
- out_dir_path.mkdir(parents=True, exist_ok=True)
107
-
108
- # ── 1. Build affine transform from x/y coordinate vectors ────────
109
- x_vals = aligned_cube.x.values
110
- y_vals = aligned_cube.y.values
111
- x_res = float(x_vals[1] - x_vals[0]) # positive (east)
112
- y_res = float(y_vals[1] - y_vals[0]) # negative (north‑up)
113
- transform = (
114
- Affine.translation(x_vals[0] - x_res / 2.0, y_vals[0] - y_res / 2.0)
115
- * Affine.scale(x_res, y_res)
116
- )
117
-
118
- # ── 2. Retrieve CRS from datacube attributes ────────────────────
119
- attrs = aligned_cube.attrs
120
- crs: str | None = attrs.get("crs_wkt")
121
- if crs is None and "crs_epsg" in attrs:
122
- crs = f"EPSG:{int(attrs['crs_epsg'])}"
123
-
124
- # ── 3. Loop over acquisition dates and write GeoTIFFs ────────────
125
- for t in aligned_cube.time.values:
126
- date_str = str(t)[:10] # YYYY‑MM‑DD
127
- da_t = aligned_cube.sel(time=t)
128
-
129
- # Ensure (band, y, x) memory layout for rasterio
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",
145
- }
146
-
147
- outfile = out_dir_path / f"{date_str}.tif"
148
- with rasterio.open(outfile, "w", **profile) as dst:
149
- dst.write(data)
@@ -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
-
@@ -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
- ]