timesat-cli 1.0.0__tar.gz → 1.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: timesat-cli
3
- Version: 1.0.0
3
+ Version: 1.3.1
4
4
  Summary: Python-based command line interface for TIMESAT
5
5
  Author: Zhanzhang Cai
6
6
  License: GPL-3.0-only
@@ -16,9 +16,7 @@ Requires-Python: >=3.10
16
16
  Requires-Dist: numpy
17
17
  Requires-Dist: pandas
18
18
  Requires-Dist: rasterio
19
- Requires-Dist: timesat
20
- Provides-Extra: parallel
21
- Requires-Dist: ray; extra == 'parallel'
19
+ Requires-Dist: timesat>=4.1.11
22
20
  Description-Content-Type: text/markdown
23
21
 
24
22
  # TIMESAT CLI
@@ -91,19 +89,6 @@ pip install timesat-cli
91
89
 
92
90
  ---
93
91
 
94
- ## ⚙️ Optional: Parallel Processing Support
95
-
96
- `timesat-cli` provides an optional extra for **parallel execution** using [`ray`](https://www.ray.io/).
97
-
98
- To install with parallel-processing support:
99
-
100
- ```bash
101
- pip install timesat-cli[parallel]
102
- ```
103
-
104
- This installs the base package plus the ray dependency.
105
-
106
- ---
107
92
 
108
93
  ## Running the Application
109
94
 
@@ -68,19 +68,6 @@ pip install timesat-cli
68
68
 
69
69
  ---
70
70
 
71
- ## ⚙️ Optional: Parallel Processing Support
72
-
73
- `timesat-cli` provides an optional extra for **parallel execution** using [`ray`](https://www.ray.io/).
74
-
75
- To install with parallel-processing support:
76
-
77
- ```bash
78
- pip install timesat-cli[parallel]
79
- ```
80
-
81
- This installs the base package plus the ray dependency.
82
-
83
- ---
84
71
 
85
72
  ## Running the Application
86
73
 
@@ -23,17 +23,12 @@ dependencies = [
23
23
  "numpy",
24
24
  "rasterio",
25
25
  "pandas",
26
- "timesat",
26
+ "timesat>=4.1.11",
27
27
  ]
28
28
 
29
29
  [dependency-groups]
30
30
  dev = ["pre-commit", "ruff", "matplotlib"]
31
31
 
32
- [project.optional-dependencies]
33
- parallel = [
34
- 'ray',
35
- ]
36
-
37
32
  [tool.hatch.version]
38
33
  source = "vcs" # <-- tell Hatch to use Git tags
39
34
 
@@ -12,7 +12,7 @@ Email:
12
12
  zhanzhang.cai@nateko.lu.se
13
13
  """
14
14
 
15
- __version__ = "0.1.0"
15
+ __version__ = "1.2.6"
16
16
  __all__ = ["run"]
17
17
 
18
18
  from .processing import run
@@ -0,0 +1,28 @@
1
+ import os
2
+ import json
3
+ import argparse
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(description="Run TIMESAT processing pipeline.")
7
+ parser.add_argument("settings_json", help="Path to the JSON configuration file.")
8
+ args = parser.parse_args()
9
+
10
+ # read config first (pure Python)
11
+ with open(args.settings_json) as f:
12
+ s = json.load(f)
13
+
14
+ # thread control BEFORE heavy imports
15
+ threads = s.get("threads")
16
+ if threads:
17
+ threads = str(int(threads))
18
+ os.environ["OMP_NUM_THREADS"] = threads
19
+ os.environ.setdefault("OPENBLAS_NUM_THREADS", threads)
20
+ os.environ.setdefault("MKL_NUM_THREADS", threads)
21
+ os.environ.setdefault("NUMEXPR_NUM_THREADS", threads)
22
+
23
+ # late import of processing (safe)
24
+ from .processing import run
25
+ run(args.settings_json)
26
+
27
+ if __name__ == "__main__":
28
+ main()
@@ -39,13 +39,12 @@ class Settings:
39
39
  p_outlier: int
40
40
  p_printflag: int
41
41
  max_memory_gb: float
42
- para_check: int
43
- ray_dir: str
44
42
  scale: float
45
43
  offset: float
46
44
  p_hrvppformat: int
47
45
  p_nclasses: int
48
46
  classes: List[ClassParams]
47
+ outputvariables: int
49
48
 
50
49
 
51
50
  @dataclass
@@ -104,11 +103,10 @@ def load_config(jsfile: str) -> Config:
104
103
  p_outlier=int(s["p_outlier"]["value"]),
105
104
  p_printflag=int(s["p_printflag"]["value"]),
106
105
  max_memory_gb=float(s["max_memory_gb"]["value"]),
107
- para_check=int(s["para_check"]["value"]),
108
- ray_dir=s["ray_dir"]["value"],
109
106
  scale=float(s["scale"]["value"]),
110
107
  offset=float(s["offset"]["value"]),
111
108
  p_hrvppformat=int(s["p_hrvppformat"]["value"]),
109
+ outputvariables=int(s["outputvariables"]["value"]),
112
110
  p_nclasses=nclasses,
113
111
  classes=classes,
114
112
  )
@@ -0,0 +1,95 @@
1
+ """
2
+ Utility functions for handling date operations in TIMESAT processing.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import datetime
8
+ import numpy as np
9
+
10
+ __all__ = ["date_with_ignored_day", "build_monthly_sample_indices"]
11
+
12
+
13
+ def is_leap_year(y: int) -> bool:
14
+ """
15
+ Return True if year y is a Gregorian leap year, False otherwise.
16
+ """
17
+ return (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)
18
+
19
+
20
+ def date_with_ignored_day(yrstart: int, i_tv: int, p_ignoreday: int) -> datetime.date:
21
+ """
22
+ Convert a synthetic TIMESAT time index (1-based, assuming 365 days/year)
23
+ into a real calendar date while skipping one day in leap years.
24
+ """
25
+
26
+ # ---- Step 1: synthetic 365-day calendar ----
27
+ i = int(i_tv)
28
+ year_offset, doy_365 = divmod(i - 1, 365)
29
+ doy_365 += 1
30
+ year = yrstart + year_offset
31
+
32
+ jan1 = datetime.date(year, 1, 1)
33
+
34
+ if is_leap_year(year):
35
+ if not (1 <= p_ignoreday <= 366):
36
+ raise ValueError("p_ignoreday must be in [1, 366] for leap years")
37
+
38
+ if p_ignoreday == 1:
39
+ real_ordinal = doy_365 + 1
40
+ elif p_ignoreday == 366:
41
+ real_ordinal = doy_365
42
+ else:
43
+ real_ordinal = doy_365 if doy_365 < p_ignoreday else doy_365 + 1
44
+ else:
45
+ real_ordinal = doy_365
46
+
47
+ return jan1 + datetime.timedelta(days=real_ordinal - 1)
48
+
49
+
50
+ def build_monthly_sample_indices(yrstart: int, yr: int) -> np.ndarray:
51
+ """
52
+ Build a synthetic time index (1-based) for sampling the 1st, 11th, and 21st
53
+ of each month across multiple years.
54
+
55
+ The synthetic timeline always uses 365 days per year.
56
+ In leap years we:
57
+ - keep Feb 29
58
+ - drop Dec 31
59
+ so that each year still has 365 synthetic days.
60
+
61
+ Parameters
62
+ ----------
63
+ yrstart : int
64
+ Starting year of the period.
65
+
66
+ yr : int
67
+ Number of years to include.
68
+
69
+ Returns
70
+ -------
71
+ np.ndarray
72
+ A 1D array of indices into the synthetic timeline (1-based).
73
+ """
74
+
75
+ indices: list[int] = []
76
+ year_offset = 0 # offset of each synthetic year start (0, 365, 730, ...)
77
+
78
+ for year in range(yrstart, yrstart + yr):
79
+ if is_leap_year(year):
80
+ # Include Feb 29, drop Dec 31
81
+ days_in_month = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 30]
82
+ else:
83
+ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
84
+
85
+ cum = 0 # cumulative day count within the current year
86
+
87
+ for dim in days_in_month:
88
+ for d in (1, 11, 21):
89
+ if d <= dim:
90
+ indices.append(year_offset + cum + d)
91
+ cum += dim
92
+
93
+ year_offset += 365
94
+
95
+ return np.array(indices, dtype=int)
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import math
4
+
5
+ __all__ = ["create_output_folders", "close_all"]
6
+
7
+
8
+ def create_output_folders(outfolder: str) -> tuple[str, str]:
9
+ vpp_folder = os.path.join(outfolder, "VPP")
10
+ st_folder = os.path.join(outfolder, "ST")
11
+ os.makedirs(vpp_folder, exist_ok=True)
12
+ os.makedirs(st_folder, exist_ok=True)
13
+ return st_folder, vpp_folder
14
+
15
+
16
+ def memory_plan(
17
+ dx: int,
18
+ dy: int,
19
+ z: int,
20
+ p_outindex_num: int,
21
+ yr: int,
22
+ max_memory_gb: float,
23
+ ) -> tuple[int, int]:
24
+ num_layers = (
25
+ 2 * z # VI + QA
26
+ + 2 * p_outindex_num # yfit + yfit QA
27
+ + 2 * 13 * 2 * yr # VPP + VPP QA
28
+ + yr # nseason
29
+ )
30
+
31
+ bytes_per = 4 # float32
32
+ safety = 0.8 # keep 60% margin for overhead
33
+ max_bytes = max_memory_gb * (2 ** 30) * safety
34
+
35
+ dy_max = max_bytes / (dx * num_layers * bytes_per) if num_layers > 0 else dy
36
+ y_slice_size = int(min(math.floor(dy_max), dy)) if dy_max > 0 else dy
37
+ y_slice_size = max(1, y_slice_size)
38
+ num_block = int(math.ceil(dy / y_slice_size))
39
+ return y_slice_size, num_block
40
+
41
+
42
+ def close_all(*items):
43
+ """
44
+ Close datasets or other objects that have a .close() method.
45
+ Accepts individual objects and iterables (lists/tuples/etc).
46
+ Ignores None safely.
47
+ """
48
+ for obj in items:
49
+ if obj is None:
50
+ continue
51
+
52
+ # If it's an iterable of objects (e.g. list of datasets)
53
+ if isinstance(obj, (list, tuple, set)):
54
+ for x in obj:
55
+ if x is None:
56
+ continue
57
+ close = getattr(x, "close", None)
58
+ if callable(close):
59
+ close()
60
+ else:
61
+ # Single object
62
+ close = getattr(obj, "close", None)
63
+ if callable(close):
64
+ close()
@@ -0,0 +1,206 @@
1
+ from __future__ import annotations
2
+ import math, os, datetime
3
+
4
+ from .config import load_config, build_param_array
5
+ from .readers import read_file_lists, open_image_data
6
+ from .fsutils import create_output_folders, memory_plan, close_all
7
+ from .writers import prepare_profiles, write_layers
8
+ from .dateutils import date_with_ignored_day, build_monthly_sample_indices
9
+
10
+ VPP_NAMES = ["SOSD","SOSV","LSLOPE","EOSD","EOSV","RSLOPE","LENGTH",
11
+ "MINV","MAXD","MAXV","AMPL","TPROD","SPROD"]
12
+
13
+ def _build_output_filenames(st_folder: str, vpp_folder: str, p_outindex, yrstart: int, yrend: int, p_ignoreday: int):
14
+ outyfitfn = []
15
+ outyfitqafn = []
16
+ for i_tv in p_outindex:
17
+ yfitdate = date_with_ignored_day(yrstart, int(i_tv), p_ignoreday)
18
+ outyfitfn.append(os.path.join(st_folder, f"TIMESAT_{yfitdate.strftime('%Y%m%d')}.tif"))
19
+ outyfitqafn.append(os.path.join(st_folder, f"TIMESAT_{yfitdate.strftime('%Y%m%d')}_QA.tif"))
20
+
21
+ outvppfn = []
22
+ outvppqafn = []
23
+ outnsfn = []
24
+ for i_yr in range(yrstart, yrend + 1):
25
+ for i_seas in range(2):
26
+ for name in VPP_NAMES:
27
+ outvppfn.append(os.path.join(vpp_folder, f"TIMESAT_{name}_{i_yr}_season_{i_seas+1}.tif"))
28
+ outvppqafn.append(os.path.join(vpp_folder, f"TIMESAT_QA_{i_yr}_season_{i_seas+1}.tif"))
29
+ outnsfn.append(os.path.join(vpp_folder, f"TIMESAT_{i_yr}_numseason.tif"))
30
+ return outyfitfn, outyfitqafn, outvppfn, outvppqafn, outnsfn
31
+
32
+
33
+ def run(jsfile: str) -> None:
34
+
35
+ import numpy as np
36
+ import rasterio
37
+ import timesat # external dependency
38
+
39
+ print(jsfile)
40
+ cfg = load_config(jsfile)
41
+ s = cfg.settings
42
+
43
+ if s.outputfolder == '':
44
+ print('Nothing to do...')
45
+ return
46
+
47
+ # Precompute arrays once per block to pass into timesat
48
+ landuse_arr = build_param_array(s, 'landuse', 'uint8')
49
+ p_fitmethod_arr = build_param_array(s, 'p_fitmethod', 'uint8')
50
+ p_smooth_arr = build_param_array(s, 'p_smooth', 'double')
51
+ p_nenvi_arr = build_param_array(s, 'p_nenvi', 'uint8')
52
+ p_wfactnum_arr = build_param_array(s, 'p_wfactnum', 'double')
53
+ p_startmethod_arr = build_param_array(s, 'p_startmethod', 'uint8')
54
+ p_startcutoff_arr = build_param_array(s, 'p_startcutoff', 'double', shape=(2,), fortran_2d=True)
55
+ p_low_percentile_arr = build_param_array(s, 'p_low_percentile', 'double')
56
+ p_fillbase_arr = build_param_array(s, 'p_fillbase', 'uint8')
57
+ p_seasonmethod_arr = build_param_array(s, 'p_seasonmethod', 'uint8')
58
+ p_seapar_arr = build_param_array(s, 'p_seapar', 'double')
59
+
60
+
61
+ timevector, flist, qlist, yr, yrstart, yrend = read_file_lists(s.tv_list, s.image_file_list, s.quality_file_list)
62
+
63
+ z = len(flist)
64
+ print(f'num of images: {z}')
65
+ print('First image: ' + os.path.basename(flist[0]))
66
+ print('Last image: ' + os.path.basename(flist[-1]))
67
+ print(yrstart)
68
+
69
+ if int(s.p_st_timestep)>0:
70
+ p_outindex = np.arange(1, yr * 365 + 1)[:: int(s.p_st_timestep)]
71
+ elif int(s.p_st_timestep)<0:
72
+ p_outindex = build_monthly_sample_indices(yrstart, yr)
73
+ elif int(s.p_st_timestep)==0:
74
+ p_outindex = np.arange(1, yr * 365 + 1)[:: int(9999)]
75
+ p_outindex_num = len(p_outindex)
76
+
77
+ with rasterio.open(flist[0], 'r') as temp:
78
+ img_profile = temp.profile
79
+
80
+ if sum(s.imwindow) == 0:
81
+ dx, dy = img_profile['width'], img_profile['height']
82
+ else:
83
+ dx, dy = int(s.imwindow[2]), int(s.imwindow[3])
84
+
85
+ st_folder, vpp_folder = create_output_folders(s.outputfolder)
86
+
87
+ outyfitfn, outyfitqafn, outvppfn, outvppqafn, outnsfn = _build_output_filenames(st_folder, vpp_folder, p_outindex, yrstart, yrend, s.p_ignoreday)
88
+
89
+ img_profile_st, img_profile_vpp, img_profile_qa, img_profile_ns = prepare_profiles(img_profile, s.p_nodata, s.scale, s.offset)
90
+ # Open output datasets once and reuse them for all blocks
91
+ st_datasets = []
92
+ stqa_datasets = []
93
+ vpp_datasets = []
94
+ vppqa_datasets = []
95
+ ns_dataset = []
96
+
97
+ # VPP outputs
98
+ if s.outputvariables == 1:
99
+ for path in outvppfn:
100
+ ds = rasterio.open(path, "w", **img_profile_vpp)
101
+ vpp_datasets.append(ds)
102
+ for path in outvppqafn:
103
+ ds = rasterio.open(path, "w", **img_profile_qa)
104
+ vppqa_datasets.append(ds)
105
+ for path in outnsfn:
106
+ ds = rasterio.open(path, "w", **img_profile_ns)
107
+ ns_dataset.append(ds)
108
+
109
+ # ST (yfit) outputs
110
+ for path in outyfitfn:
111
+ ds = rasterio.open(path, "w", **img_profile_st)
112
+ st_datasets.append(ds)
113
+ for path in outyfitqafn:
114
+ ds = rasterio.open(path, "w", **img_profile_qa)
115
+ stqa_datasets.append(ds)
116
+
117
+
118
+ # compute memory blocks
119
+ y_slice_size, num_block = memory_plan(dx, dy, z, p_outindex_num, yr, s.max_memory_gb)
120
+ y_slice_end = dy % y_slice_size if (dy % y_slice_size) > 0 else y_slice_size
121
+ print('y_slice_size = ' + str(y_slice_size))
122
+
123
+ s3_opts = getattr(s, "s3", None)
124
+
125
+ if s3_opts:
126
+ # Open all files inside a single S3 environment
127
+ with rasterio.Env(**s3_opts):
128
+ data_datasets = [rasterio.open(p, "r") for p in flist]
129
+ qa_datasets = [rasterio.open(p, "r") for p in qlist] if qlist else []
130
+ lc_dataset = rasterio.open(s.lc_file, "r") if s.lc_file else None
131
+ else:
132
+ # Local files
133
+ data_datasets = [rasterio.open(p, "r") for p in flist]
134
+ qa_datasets = [rasterio.open(p, "r") for p in qlist] if qlist else []
135
+ lc_dataset = rasterio.open(s.lc_file, "r") if s.lc_file else None
136
+
137
+ for iblock in range(num_block):
138
+ print(f'Processing block: {iblock + 1}/{num_block} starttime: {datetime.datetime.now()}')
139
+ x = dx
140
+ y = int(y_slice_size) if iblock != num_block - 1 else int(y_slice_end)
141
+ x_map = int(s.imwindow[0])
142
+ y_map = int(iblock * y_slice_size + s.imwindow[1])
143
+
144
+ vi, qa, lc = open_image_data(
145
+ x_map, y_map, x, y,
146
+ data_datasets,
147
+ qa_datasets,
148
+ lc_dataset,
149
+ img_profile['dtype'],
150
+ s.p_a,
151
+ s.p_band_id
152
+ )
153
+
154
+ print('--- start TIMESAT processing --- starttime: ' + str(datetime.datetime.now()))
155
+
156
+ if s.scale != 1 or s.offset != 0:
157
+ vi = vi * s.scale + s.offset
158
+
159
+ vpp, vppqa, nseason, yfit, yfitqa, seasonfit, tseq = timesat.tsfprocess(
160
+ yr, vi, qa, timevector, lc, s.p_nclasses, landuse_arr, p_outindex,
161
+ s.p_ignoreday, s.p_ylu, s.p_printflag, p_fitmethod_arr, p_smooth_arr,
162
+ s.p_nodata, s.p_davailwin, s.p_outlier,
163
+ p_nenvi_arr, p_wfactnum_arr, p_startmethod_arr, p_startcutoff_arr,
164
+ p_low_percentile_arr, p_fillbase_arr, s.p_hrvppformat,
165
+ p_seasonmethod_arr, p_seapar_arr, s.outputvariables)
166
+
167
+ print('--- start writing geotif --- starttime: ' + str(datetime.datetime.now()))
168
+ window = (x_map, y_map, x, y)
169
+
170
+ if s.outputvariables == 1:
171
+ vpp = np.moveaxis(vpp, -1, 0)
172
+ write_layers(vpp_datasets, vpp, window)
173
+
174
+ vppqa = np.moveaxis(vppqa, -1, 0)
175
+ write_layers(vppqa_datasets, vppqa, window)
176
+
177
+ nseason = np.moveaxis(nseason, -1, 0)
178
+ write_layers(ns_dataset, nseason, window)
179
+
180
+ if s.scale == 1 and s.offset == 0:
181
+ yfit = np.moveaxis(yfit, -1, 0).astype(img_profile['dtype'])
182
+ else:
183
+ yfit = np.moveaxis(yfit, -1, 0).astype('float32')
184
+ write_layers(st_datasets, yfit, window)
185
+
186
+ yfitqa = np.moveaxis(yfitqa, -1, 0)
187
+ write_layers(stqa_datasets, yfitqa, window)
188
+
189
+ print(f'Block: {iblock + 1}/{num_block} finishedtime: {datetime.datetime.now()}')
190
+
191
+ close_all(
192
+ data_datasets,
193
+ qa_datasets,
194
+ lc_dataset,
195
+ st_datasets,
196
+ stqa_datasets,
197
+ )
198
+
199
+ if s.outputvariables == 1:
200
+ close_all(
201
+ vpp_datasets,
202
+ vppqa_datasets,
203
+ ns_dataset,
204
+ )
205
+
206
+
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import os
5
+ import re
6
+
7
+ import numpy as np
8
+ import rasterio
9
+ from rasterio.windows import Window
10
+
11
+ from .qa import assign_qa_weight
12
+
13
+ __all__ = ["read_file_lists", "open_image_data"]
14
+
15
+ def _parse_dates_from_name(name: str) -> tuple[int, int, int]:
16
+ date_regex1 = r"\d{4}-\d{2}-\d{2}"
17
+ date_regex2 = r"\d{4}\d{2}\d{2}"
18
+ try:
19
+ dates = re.findall(date_regex1, name)
20
+ position = name.find(dates[0])
21
+ y = int(name[position : position + 4])
22
+ m = int(name[position + 5 : position + 7])
23
+ d = int(name[position + 8 : position + 10])
24
+ return y, m, d
25
+ except Exception:
26
+ try:
27
+ dates = re.findall(date_regex2, name)
28
+ position = name.find(dates[0])
29
+ y = int(name[position : position + 4])
30
+ m = int(name[position + 4 : position + 6])
31
+ d = int(name[position + 6 : position + 8])
32
+ return y, m, d
33
+ except Exception as e:
34
+ raise ValueError(f"No date found in filename: {name}") from e
35
+
36
+
37
+ def _read_time_vector(tlist: str, filepaths: list[str]):
38
+ """Return (timevector, yr, yrstart, yrend) in YYYYDOY format."""
39
+ flist = [os.path.basename(p) for p in filepaths]
40
+ timevector = np.ndarray(len(flist), order="F", dtype="uint32")
41
+ if tlist == "":
42
+ for i, fname in enumerate(flist):
43
+ y, m, d = _parse_dates_from_name(fname)
44
+ doy = (datetime.date(y, m, d) - datetime.date(y, 1, 1)).days + 1
45
+ timevector[i] = y * 1000 + doy
46
+ else:
47
+ with open(tlist, "r") as f:
48
+ lines = f.read().splitlines()
49
+ for idx, val in enumerate(lines):
50
+ n = len(val)
51
+ if n == 8: # YYYYMMDD
52
+ dt = datetime.datetime.strptime(val, "%Y%m%d")
53
+ timevector[idx] = int(f"{dt.year}{dt.timetuple().tm_yday:03d}")
54
+ elif n == 7: # YYYYDOY
55
+ _ = datetime.datetime.strptime(val, "%Y%j")
56
+ timevector[idx] = int(val)
57
+ else:
58
+ raise ValueError(f"Unrecognized date format: {val}")
59
+
60
+ yrstart = int(np.floor(timevector.min() / 1000))
61
+ yrend = int(np.floor(timevector.max() / 1000))
62
+ yr = yrend - yrstart + 1
63
+ return timevector, yr, yrstart, yrend
64
+
65
+
66
+ def _unique_by_timevector(flist: list[str], qlist: list[str], timevector):
67
+ tv_unique, indices = np.unique(timevector, return_index=True)
68
+ flist2 = [flist[i] for i in indices]
69
+ qlist2 = [qlist[i] for i in indices] if qlist else []
70
+ return tv_unique, flist2, qlist2
71
+
72
+
73
+ def read_file_lists(
74
+ tlist: str, data_list: str, qa_list: str
75
+ ) -> tuple[np.ndarray, list[str], list[str], int, int, int]:
76
+ qlist: list[str] | str = ""
77
+ with open(data_list, "r") as f:
78
+ flist = f.read().splitlines()
79
+ if qa_list != "":
80
+ with open(qa_list, "r") as f:
81
+ qlist = f.read().splitlines()
82
+ if len(flist) != len(qlist):
83
+ raise ValueError("No. of Data and QA are not consistent")
84
+
85
+ timevector, yr, yrstart, yrend = _read_time_vector(tlist, flist)
86
+ timevector, flist, qlist = _unique_by_timevector(flist, qlist, timevector)
87
+ return (
88
+ timevector,
89
+ flist,
90
+ (qlist if isinstance(qlist, list) else []),
91
+ yr,
92
+ yrstart,
93
+ yrend,
94
+ )
95
+
96
+ def open_image_data(
97
+ x_map: int,
98
+ y_map: int,
99
+ x: int,
100
+ y: int,
101
+ data_datasets: list,
102
+ qa_datasets: list,
103
+ lc_dataset,
104
+ data_type: str,
105
+ p_a,
106
+ layer: int,
107
+ ):
108
+ """
109
+ Read VI, QA, and LC blocks using already-open rasterio datasets.
110
+ This is fast because we do NOT call rasterio.open() for each block.
111
+ """
112
+
113
+ z = len(data_datasets)
114
+
115
+ # allocate arrays
116
+ vi = np.ndarray((y, x, z), order="F", dtype=data_type)
117
+ qa = np.ndarray((y, x, z), order="F", dtype=data_type)
118
+ lc = np.ndarray((y, x), order="F", dtype=np.uint8)
119
+
120
+ win = Window(x_map, y_map, x, y)
121
+
122
+ # -----------------------------------------------------------
123
+ # 1) Read VI stack
124
+ # -----------------------------------------------------------
125
+ for i, ds in enumerate(data_datasets):
126
+ arr = ds.read(layer, window=win)
127
+ if arr.ndim == 3:
128
+ arr = arr[0, :, :]
129
+ vi[:, :, i] = arr
130
+
131
+ # -----------------------------------------------------------
132
+ # 2) Read QA stack (or fill with ones)
133
+ # -----------------------------------------------------------
134
+ if len(qa_datasets) == 0:
135
+ qa[:] = 1
136
+ else:
137
+ for i, ds in enumerate(qa_datasets):
138
+ arr = ds.read(layer, window=win)
139
+ if arr.ndim == 3:
140
+ arr = arr[0, :, :]
141
+ qa[:, :, i] = arr
142
+
143
+ # Weight QA
144
+ from .qa import assign_qa_weight
145
+ qa = assign_qa_weight(p_a, qa)
146
+
147
+ # -----------------------------------------------------------
148
+ # 3) Land cover (single layer)
149
+ # -----------------------------------------------------------
150
+ if lc_dataset is None:
151
+ lc[:] = 1
152
+ else:
153
+ arr = lc_dataset.read(1, window=win)
154
+ if arr.ndim == 3:
155
+ arr = arr[0, :, :]
156
+ lc[:, :] = arr.astype(np.uint8)
157
+
158
+ return vi, qa, lc
159
+
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+
5
+ import numpy as np
6
+ import rasterio
7
+ from rasterio.windows import Window
8
+
9
+ __all__ = ["prepare_profiles", "write_layers"]
10
+
11
+
12
+ def prepare_profiles(img_profile, p_nodata: float, scale: float, offset: float):
13
+ img_profile_st = copy.deepcopy(img_profile)
14
+ img_profile_st.update(compress="lzw")
15
+ if scale != 1 or offset != 0:
16
+ img_profile_st.update(dtype=rasterio.float32)
17
+
18
+ img_profile_vpp = copy.deepcopy(img_profile)
19
+ img_profile_vpp.update(nodata=p_nodata, dtype=rasterio.float32, compress="lzw")
20
+
21
+ img_profile_qa = copy.deepcopy(img_profile)
22
+ img_profile_qa.update(nodata=0, dtype=rasterio.uint8, compress="lzw")
23
+
24
+ img_profile_ns = copy.deepcopy(img_profile)
25
+ img_profile_ns.update(nodata=255, dtype=rasterio.uint8, compress="lzw")
26
+
27
+ return img_profile_st, img_profile_vpp, img_profile_qa, img_profile_ns
28
+
29
+
30
+ def write_layers(
31
+ datasets: list[rasterio.io.DatasetWriter],
32
+ arrays: np.ndarray,
33
+ window: tuple[int, int, int, int],
34
+ ) -> None:
35
+ """
36
+ Write a block (window) for each array into the corresponding open dataset.
37
+
38
+ datasets : list of open rasterio DatasetWriter objects
39
+ arrays : np.ndarray with shape (n_layers, y, x) or iterable of 2D arrays
40
+ window : (x_map, y_map, x, y)
41
+ """
42
+ x_map, y_map, x, y = window
43
+ win = Window(x_map, y_map, x, y)
44
+
45
+ for i, arr in enumerate(arrays, 1):
46
+ dst = datasets[i - 1]
47
+ dst.write(arr, window=win, indexes=1)
@@ -1,12 +0,0 @@
1
- # src/timesat_cli/__main__.py
2
- import argparse
3
- from .processing import run
4
-
5
- def main():
6
- parser = argparse.ArgumentParser(description="Run TIMESAT processing pipeline.")
7
- parser.add_argument("settings_json", help="Path to the JSON configuration file.")
8
- args = parser.parse_args()
9
- run(args.settings_json)
10
-
11
- if __name__ == "__main__":
12
- main()
@@ -1,25 +0,0 @@
1
- from __future__ import annotations
2
- import os
3
- import math
4
- from typing import Tuple
5
-
6
- __all__ = ["create_output_folders"]
7
-
8
-
9
- def create_output_folders(outfolder: str) -> Tuple[str, str]:
10
- vpp_folder = os.path.join(outfolder, "VPP")
11
- st_folder = os.path.join(outfolder, "ST")
12
- os.makedirs(vpp_folder, exist_ok=True)
13
- os.makedirs(st_folder, exist_ok=True)
14
- return st_folder, vpp_folder
15
-
16
-
17
- def memory_plan(dx: int, dy: int, z: int, p_outindex_num: int, yr: int, max_memory_gb: float) -> Tuple[int, int]:
18
- num_layers = p_outindex_num + z * 2 + (13 * 2) * yr
19
- bytes_per = 4 # float32
20
- max_bytes = max_memory_gb * (2 ** 30)
21
- dy_max = max_bytes / (dx * num_layers * bytes_per)
22
- y_slice_size = int(min(math.floor(dy_max), dy)) if dy_max > 0 else dy
23
- y_slice_size = max(1, y_slice_size)
24
- num_block = int(math.ceil(dy / y_slice_size))
25
- return y_slice_size, num_block
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- __all__ = ["maybe_init_ray"]
4
-
5
-
6
- def maybe_init_ray(para_check: int, ray_dir: str | None = None) -> bool:
7
- """
8
- Initialize Ray if parallelism is requested.
9
-
10
- Parameters
11
- ----------
12
- para_check : int
13
- Number of CPUs to use (if >1, Ray will be initialized).
14
- ray_dir : str or None, optional
15
- Temporary directory for Ray logs/state. If None or empty,
16
- Ray will use its default temp location.
17
-
18
- Returns
19
- -------
20
- bool
21
- True if Ray was initialized, False otherwise.
22
- """
23
- if para_check > 1:
24
- import ray
25
- kwargs = {"num_cpus": para_check}
26
- if ray_dir: # only include if user provided a valid path
27
- kwargs["_temp_dir"] = ray_dir
28
-
29
- ray.init(**kwargs)
30
- return True
31
-
32
- return False
@@ -1,167 +0,0 @@
1
- from __future__ import annotations
2
- import math, os, datetime
3
- from typing import List, Tuple
4
-
5
- import numpy as np
6
- import rasterio
7
-
8
- import timesat # external dependency
9
-
10
- from .config import load_config, build_param_array
11
- from .readers import read_file_lists, open_image_data
12
- from .fsutils import create_output_folders, memory_plan
13
- from .writers import prepare_profiles, write_vpp_layers, write_st_layers
14
- from .parallel import maybe_init_ray
15
-
16
- VPP_NAMES = ["SOSD","SOSV","LSLOPE","EOSD","EOSV","RSLOPE","LENGTH",
17
- "MINV","MAXD","MAXV","AMPL","TPROD","SPROD"]
18
-
19
- def _build_output_filenames(st_folder: str, vpp_folder: str, p_outindex, yrstart: int, yrend: int):
20
- outyfitfn = []
21
- for i_tv in p_outindex:
22
- yfitdate = datetime.date(yrstart, 1, 1) + datetime.timedelta(days=int(i_tv)) - datetime.timedelta(days=1)
23
- outyfitfn.append(os.path.join(st_folder, f"TIMESAT_{yfitdate.strftime('%Y%m%d')}.tif"))
24
-
25
- outvppfn = []
26
- for i_yr in range(yrstart, yrend + 1):
27
- for i_seas in range(2):
28
- for name in VPP_NAMES:
29
- outvppfn.append(os.path.join(vpp_folder, f"TIMESAT_{name}_{i_yr}_season_{i_seas+1}.tif"))
30
- outnsfn = os.path.join(vpp_folder, 'TIMESAT_nsperyear.tif')
31
- return outyfitfn, outvppfn, outnsfn
32
-
33
-
34
- def run(jsfile: str) -> None:
35
- print(jsfile)
36
- cfg = load_config(jsfile)
37
- s = cfg.settings
38
-
39
- if s.outputfolder == '':
40
- print('Nothing to do...')
41
- return
42
-
43
- # Precompute arrays once per block to pass into timesat
44
- landuse_arr = build_param_array(s, 'landuse', 'uint8')
45
- p_fitmethod_arr = build_param_array(s, 'p_fitmethod', 'uint8')
46
- p_smooth_arr = build_param_array(s, 'p_smooth', 'double')
47
- p_nenvi_arr = build_param_array(s, 'p_nenvi', 'uint8')
48
- p_wfactnum_arr = build_param_array(s, 'p_wfactnum', 'double')
49
- p_startmethod_arr = build_param_array(s, 'p_startmethod', 'uint8')
50
- p_startcutoff_arr = build_param_array(s, 'p_startcutoff', 'double', shape=(2,), fortran_2d=True)
51
- p_low_percentile_arr = build_param_array(s, 'p_low_percentile', 'double')
52
- p_fillbase_arr = build_param_array(s, 'p_fillbase', 'uint8')
53
- p_seasonmethod_arr = build_param_array(s, 'p_seasonmethod', 'uint8')
54
- p_seapar_arr = build_param_array(s, 'p_seapar', 'double')
55
-
56
- ray_inited = maybe_init_ray(s.para_check, s.ray_dir)
57
-
58
- timevector, flist, qlist, yr, yrstart, yrend = read_file_lists(s.tv_list, s.image_file_list, s.quality_file_list)
59
-
60
- z = len(flist)
61
- print(f'num of images: {z}')
62
- print('First image: ' + os.path.basename(flist[0]))
63
- print('Last image: ' + os.path.basename(flist[-1]))
64
- print(yrstart)
65
-
66
- p_outindex = np.arange(
67
- (datetime.datetime(yrstart, 1, 1) - datetime.datetime(yrstart, 1, 1)).days + 1,
68
- (datetime.datetime(yrstart + yr - 1, 12, 31) - datetime.datetime(yrstart, 1, 1)).days + 1
69
- )[:: int(s.p_st_timestep)]
70
- p_outindex_num = len(p_outindex)
71
-
72
- with rasterio.open(flist[0], 'r') as temp:
73
- img_profile = temp.profile
74
-
75
- if sum(s.imwindow) == 0:
76
- dx, dy = img_profile['width'], img_profile['height']
77
- else:
78
- dx, dy = int(s.imwindow[2]), int(s.imwindow[3])
79
-
80
- imgprocessing = not (s.imwindow[2] + s.imwindow[3] == 2)
81
-
82
- if imgprocessing:
83
- st_folder, vpp_folder = create_output_folders(s.outputfolder)
84
- outyfitfn, outvppfn, outnsfn = _build_output_filenames(st_folder, vpp_folder, p_outindex, yrstart, yrend)
85
- img_profile_st, img_profile_vpp, img_profile_ns = prepare_profiles(img_profile, s.p_nodata, s.scale, s.offset)
86
- # pre-create files
87
- for path in outvppfn:
88
- with rasterio.open(path, 'w', **img_profile_vpp):
89
- pass
90
- for path in outyfitfn:
91
- with rasterio.open(path, 'w', **img_profile_st):
92
- pass
93
-
94
- # compute memory blocks
95
- y_slice_size, num_block = memory_plan(dx, dy, z, p_outindex_num, yr, s.max_memory_gb)
96
- y_slice_end = dy % y_slice_size if (dy % y_slice_size) > 0 else y_slice_size
97
- print('y_slice_size = ' + str(y_slice_size))
98
-
99
- for iblock in range(num_block):
100
- print(f'Processing block: {iblock + 1}/{num_block} starttime: {datetime.datetime.now()}')
101
- x = dx
102
- y = int(y_slice_size) if iblock != num_block - 1 else int(y_slice_end)
103
- x_map = int(s.imwindow[0])
104
- y_map = int(iblock * y_slice_size + s.imwindow[1])
105
-
106
- vi, qa, lc = open_image_data(
107
- x_map, y_map, x, y, flist, qlist if qlist else '', s.lc_file,
108
- img_profile['dtype'], s.p_a, s.para_check, s.p_band_id
109
- )
110
-
111
- print('--- start TIMESAT processing --- starttime: ' + str(datetime.datetime.now()))
112
-
113
- if s.scale != 1 or s.offset != 0:
114
- vi = vi * s.scale + s.offset
115
-
116
- if s.para_check > 1 and ray_inited:
117
- import ray
118
-
119
- @ray.remote
120
- def runtimesat(vi_temp, qa_temp, lc_temp):
121
- vpp_para, vppqa, nseason_para, yfit_para, yfitqa, seasonfit, tseq = timesat.tsf2py(
122
- yr, vi_temp, qa_temp, timevector, lc_temp, s.p_nclasses,landuse_arr, p_outindex,
123
- s.p_ignoreday, s.p_ylu, s.p_printflag, p_fitmethod_arr, p_smooth_arr,
124
- s.p_nodata, s.p_davailwin, s.p_outlier,
125
- p_nenvi_arr, p_wfactnum_arr, p_startmethod_arr, p_startcutoff_arr,
126
- p_low_percentile_arr, p_fillbase_arr, s.p_hrvppformat,
127
- p_seasonmethod_arr, p_seapar_arr,
128
- 1, x, len(flist), p_outindex_num
129
- )
130
- vpp_para = vpp_para[0, :, :]
131
- yfit_para = yfit_para[0, :, :]
132
- nseason_para = nseason_para[0, :]
133
- return vpp_para, yfit_para, nseason_para
134
-
135
- futures = [
136
- runtimesat.remote(
137
- np.expand_dims(vi[i, :, :], axis=0),
138
- np.expand_dims(qa[i, :, :], axis=0),
139
- np.expand_dims(lc[i, :], axis=0)
140
- ) for i in range(y)
141
- ]
142
- results = ray.get(futures)
143
- vpp = np.stack([r[0] for r in results], axis=0)
144
- yfit = np.stack([r[1] for r in results], axis=0)
145
- nseason = np.stack([r[2] for r in results], axis=0)
146
- else:
147
- vpp, vppqa, nseason, yfit, yfitqa, seasonfit, tseq = timesat.tsf2py(
148
- yr, vi, qa, timevector, lc, s.p_nclasses, landuse_arr, p_outindex,
149
- s.p_ignoreday, s.p_ylu, s.p_printflag, p_fitmethod_arr, p_smooth_arr,
150
- s.p_nodata, s.p_davailwin, s.p_outlier,
151
- p_nenvi_arr, p_wfactnum_arr, p_startmethod_arr, p_startcutoff_arr,
152
- p_low_percentile_arr, p_fillbase_arr, s.p_hrvppformat,
153
- p_seasonmethod_arr, p_seapar_arr,
154
- y, x, len(flist), p_outindex_num)
155
-
156
- vpp = np.moveaxis(vpp, -1, 0)
157
- if s.scale == 0 and s.offset == 0:
158
- yfit = np.moveaxis(yfit, -1, 0).astype(img_profile['dtype'])
159
- else:
160
- yfit = np.moveaxis(yfit, -1, 0).astype('float32')
161
-
162
- print('--- start writing geotif --- starttime: ' + str(datetime.datetime.now()))
163
- window = (x_map, y_map, x, y)
164
- write_vpp_layers(outvppfn, vpp, window, img_profile_vpp)
165
- write_st_layers(outyfitfn, yfit, window, img_profile_st)
166
-
167
- print(f'Block: {iblock + 1}/{num_block} finishedtime: {datetime.datetime.now()}')
@@ -1,204 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime
4
- import os
5
- import re
6
- from typing import List, Tuple
7
-
8
- import numpy as np
9
- import rasterio
10
- from rasterio.windows import Window
11
-
12
- from .qa import assign_qa_weight
13
-
14
- try:
15
- import ray
16
- except Exception: # optional
17
- ray = None
18
-
19
- __all__ = ["read_file_lists", "open_image_data"]
20
-
21
-
22
- def _parse_dates_from_name(name: str) -> Tuple[int, int, int]:
23
- date_regex1 = r"\d{4}-\d{2}-\d{2}"
24
- date_regex2 = r"\d{4}\d{2}\d{2}"
25
- try:
26
- dates = re.findall(date_regex1, name)
27
- position = name.find(dates[0])
28
- y = int(name[position : position + 4])
29
- m = int(name[position + 5 : position + 7])
30
- d = int(name[position + 8 : position + 10])
31
- return y, m, d
32
- except Exception:
33
- try:
34
- dates = re.findall(date_regex2, name)
35
- position = name.find(dates[0])
36
- y = int(name[position : position + 4])
37
- m = int(name[position + 4 : position + 6])
38
- d = int(name[position + 6 : position + 8])
39
- return y, m, d
40
- except Exception as e:
41
- raise ValueError(f"No date found in filename: {name}") from e
42
-
43
-
44
- def _read_time_vector(tlist: str, filepaths: List[str]):
45
- """Return (timevector, yr, yrstart, yrend) in YYYYDOY format."""
46
- flist = [os.path.basename(p) for p in filepaths]
47
- timevector = np.ndarray(len(flist), order="F", dtype="uint32")
48
- if tlist == "":
49
- for i, fname in enumerate(flist):
50
- y, m, d = _parse_dates_from_name(fname)
51
- doy = (datetime.date(y, m, d) - datetime.date(y, 1, 1)).days + 1
52
- timevector[i] = y * 1000 + doy
53
- else:
54
- with open(tlist, "r") as f:
55
- lines = f.read().splitlines()
56
- for idx, val in enumerate(lines):
57
- n = len(val)
58
- if n == 8: # YYYYMMDD
59
- dt = datetime.datetime.strptime(val, "%Y%m%d")
60
- timevector[idx] = int(f"{dt.year}{dt.timetuple().tm_yday:03d}")
61
- elif n == 7: # YYYYDOY
62
- _ = datetime.datetime.strptime(val, "%Y%j")
63
- timevector[idx] = int(val)
64
- else:
65
- raise ValueError(f"Unrecognized date format: {val}")
66
-
67
- yrstart = int(np.floor(timevector.min() / 1000))
68
- yrend = int(np.floor(timevector.max() / 1000))
69
- yr = yrend - yrstart + 1
70
- return timevector, yr, yrstart, yrend
71
-
72
-
73
- def _unique_by_timevector(flist: List[str], qlist: List[str], timevector):
74
- tv_unique, indices = np.unique(timevector, return_index=True)
75
- flist2 = [flist[i] for i in indices]
76
- qlist2 = [qlist[i] for i in indices] if qlist else []
77
- return tv_unique, flist2, qlist2
78
-
79
-
80
- def read_file_lists(
81
- tlist: str, data_list: str, qa_list: str
82
- ) -> Tuple[np.ndarray, List[str], List[str], int, int, int]:
83
- qlist: List[str] | str = ""
84
- with open(data_list, "r") as f:
85
- flist = f.read().splitlines()
86
- if qa_list != "":
87
- with open(qa_list, "r") as f:
88
- qlist = f.read().splitlines()
89
- if len(flist) != len(qlist):
90
- raise ValueError("No. of Data and QA are not consistent")
91
-
92
- timevector, yr, yrstart, yrend = _read_time_vector(tlist, flist)
93
- timevector, flist, qlist = _unique_by_timevector(flist, qlist, timevector)
94
- return (
95
- timevector,
96
- flist,
97
- (qlist if isinstance(qlist, list) else []),
98
- yr,
99
- yrstart,
100
- yrend,
101
- )
102
-
103
-
104
- def open_image_data(
105
- x_map: int,
106
- y_map: int,
107
- x: int,
108
- y: int,
109
- yflist: List[str],
110
- wflist: List[str] | str,
111
- lcfile: str,
112
- data_type: str,
113
- p_a,
114
- para_check: int,
115
- layer: int,
116
- s3: dict | None = None,
117
- ):
118
- """Read VI, QA, and LC blocks as arrays."""
119
- z = len(yflist)
120
- vi = np.ndarray((y, x, z), order="F", dtype=data_type)
121
- qa = np.ndarray((y, x, z), order="F", dtype=data_type)
122
- lc = np.ndarray((y, x, z), order="F", dtype=np.uint8)
123
-
124
- # VI stack
125
- if para_check > 1 and ray is not None:
126
- vi_para = np.ndarray((y, x), order="F", dtype=data_type)
127
-
128
- @ray.remote
129
- def _readimgpara_(yfname, s3=s3):
130
- if s3 is not None:
131
- with rasterio.Env(**s3):
132
- with rasterio.open(yfname, "r") as temp:
133
- vi_para[:, :] = temp.read(
134
- layer, window=Window(x_map, y_map, x, y)
135
- )
136
- else:
137
- with rasterio.open(yfname, "r") as temp:
138
- vi_para[:, :] = temp.read(layer, window=Window(x_map, y_map, x, y))
139
- return vi_para
140
-
141
- futures = [_readimgpara_.remote(i) for i in yflist]
142
- vi = np.stack(ray.get(futures), axis=2)
143
- else:
144
- for i, yfname in enumerate(yflist):
145
- if s3 is not None:
146
- with rasterio.Env(**s3):
147
- with rasterio.open(yfname, "r") as temp:
148
- vi[:, :, i] = temp.read(
149
- layer, window=Window(x_map, y_map, x, y)
150
- )
151
- else:
152
- with rasterio.open(yfname, "r") as temp:
153
- vi[:, :, i] = temp.read(layer, window=Window(x_map, y_map, x, y))
154
-
155
- # QA stack
156
- if wflist == "" or wflist == []:
157
- qa = np.ones((y, x, z))
158
- else:
159
- if para_check > 1 and ray is not None:
160
- qa_para = np.ndarray((y, x), order="F", dtype=data_type)
161
-
162
- @ray.remote
163
- def _readqapara_(wfname, s3=s3):
164
- if s3 is not None:
165
- with rasterio.Env(**s3):
166
- with rasterio.open(wfname, "r") as temp:
167
- qa_para[:, :] = temp.read(
168
- layer, window=Window(x_map, y_map, x, y)
169
- )
170
- else:
171
- with rasterio.open(wfname, "r") as temp:
172
- qa_para[:, :] = temp.read(
173
- layer, window=Window(x_map, y_map, x, y)
174
- )
175
- return qa_para
176
-
177
- futures = [_readqapara_.remote(i) for i in wflist]
178
- qa = np.stack(ray.get(futures), axis=2)
179
- else:
180
- for i, wfname in enumerate(wflist):
181
- if s3 is not None:
182
- with rasterio.Env(**s3):
183
- with rasterio.open(wfname, "r") as temp2:
184
- qa[:, :, i] = temp2.read(
185
- 1, window=Window(x_map, y_map, x, y)
186
- )
187
- else:
188
- with rasterio.open(wfname, "r") as temp2:
189
- qa[:, :, i] = temp2.read(1, window=Window(x_map, y_map, x, y))
190
- qa = assign_qa_weight(p_a, qa)
191
-
192
- # LC
193
- if lcfile == "":
194
- lc = np.ones((y, x))
195
- else:
196
- if s3 is not None:
197
- with rasterio.Env(**s3):
198
- with rasterio.open(lcfile, "r") as temp3:
199
- lc = temp3.read(1, window=Window(x_map, y_map, x, y))
200
- else:
201
- with rasterio.open(lcfile, "r") as temp3:
202
- lc = temp3.read(1, window=Window(x_map, y_map, x, y))
203
-
204
- return vi, qa, lc
@@ -1,35 +0,0 @@
1
- from __future__ import annotations
2
- from typing import List, Tuple
3
- import rasterio
4
- from rasterio.windows import Window
5
-
6
- __all__ = ["prepare_profiles", "write_vpp_layers", "write_st_layers"]
7
-
8
-
9
- def prepare_profiles(img_profile, p_nodata: float, scale: float, offset: float):
10
- import copy
11
- img_profile_st = copy.deepcopy(img_profile)
12
- img_profile_st.update(compress='lzw')
13
- if scale != 0 or offset != 0:
14
- img_profile_st.update(dtype=rasterio.float32)
15
-
16
- img_profile_vpp = copy.deepcopy(img_profile)
17
- img_profile_vpp.update(nodata=p_nodata, dtype=rasterio.float32, compress='lzw')
18
-
19
- img_profile_ns = copy.deepcopy(img_profile)
20
- img_profile_ns.update(nodata=255, compress='lzw')
21
- return img_profile_st, img_profile_vpp, img_profile_ns
22
-
23
-
24
- def write_vpp_layers(paths: List[str], arrays, window: Tuple[int, int, int, int], img_profile_vpp):
25
- x_map, y_map, x, y = window
26
- for i, arr in enumerate(arrays, 1):
27
- with rasterio.open(paths[i - 1], 'r+', **img_profile_vpp) as outvppfile:
28
- outvppfile.write(arr, window=Window(x_map, y_map, x, y), indexes=1)
29
-
30
-
31
- def write_st_layers(paths: List[str], arrays, window: Tuple[int, int, int, int], img_profile_st):
32
- x_map, y_map, x, y = window
33
- for i, arr in enumerate(arrays, 1):
34
- with rasterio.open(paths[i - 1], 'r+', **img_profile_st) as outstfile:
35
- outstfile.write(arr, window=Window(x_map, y_map, x, y), indexes=1)
File without changes
File without changes