plaknit 0.3.2__tar.gz → 0.3.4__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.
- {plaknit-0.3.2/plaknit.egg-info → plaknit-0.3.4}/PKG-INFO +1 -1
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/__init__.py +1 -1
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/cli/ensemble_cli.py +13 -1
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/data/sampling.py +51 -21
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/models/brt.py +35 -4
- plaknit-0.3.4/plaknit/models/ensemble.py +1366 -0
- {plaknit-0.3.2 → plaknit-0.3.4/plaknit.egg-info}/PKG-INFO +1 -1
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit.egg-info/SOURCES.txt +1 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/pyproject.toml +2 -2
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_classify.py +33 -0
- plaknit-0.3.4/tests/test_ensemble_prediction.py +87 -0
- plaknit-0.3.2/plaknit/models/ensemble.py +0 -757
- {plaknit-0.3.2 → plaknit-0.3.4}/LICENSE +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/MANIFEST.in +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/README.md +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/acquisition/__init__.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/acquisition/geometry.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/acquisition/mosaic.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/acquisition/orders.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/acquisition/planner.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/classify.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/cli/__init__.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/cli/classify_cli.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/cli/main.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/data/__init__.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/data/raster.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/models/__init__.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/models/rf.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/processing/__init__.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit/processing/evaluation.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit.egg-info/dependency_links.txt +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit.egg-info/entry_points.txt +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit.egg-info/requires.txt +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/plaknit.egg-info/top_level.txt +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/requirements.txt +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/setup.cfg +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_ensemble_cli.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_geometry.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_mosaic.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_orders.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_plaknit.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_planner.py +0 -0
- {plaknit-0.3.2 → plaknit-0.3.4}/tests/test_shared_helpers.py +0 -0
|
@@ -263,7 +263,18 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
263
263
|
"--jobs",
|
|
264
264
|
type=int,
|
|
265
265
|
default=1,
|
|
266
|
-
help=
|
|
266
|
+
help=(
|
|
267
|
+
"Parallel workers for block prediction (default: 1). Use -1 for all "
|
|
268
|
+
"cores on CPU, or up to the number of visible GPUs with --gpu."
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
predict_parser.add_argument(
|
|
272
|
+
"--gpu",
|
|
273
|
+
action="store_true",
|
|
274
|
+
help=(
|
|
275
|
+
"Use CUDA for XGBoost prediction when available. With --jobs > 1, "
|
|
276
|
+
"models are distributed across persistent GPU workers."
|
|
277
|
+
),
|
|
267
278
|
)
|
|
268
279
|
predict_parser.add_argument(
|
|
269
280
|
"--block-overlap",
|
|
@@ -338,6 +349,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
338
349
|
feature_importance_out=args.feature_importance_out,
|
|
339
350
|
block_overlap=args.block_overlap,
|
|
340
351
|
jobs=args.jobs,
|
|
352
|
+
gpu=args.gpu,
|
|
341
353
|
)
|
|
342
354
|
return 0
|
|
343
355
|
|
|
@@ -189,33 +189,63 @@ def _collect_pseudo_absence_candidate_pool(
|
|
|
189
189
|
*,
|
|
190
190
|
buffer_meters: float,
|
|
191
191
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
192
|
-
"""Collect valid background features and coordinates eligible for sampling.
|
|
192
|
+
"""Collect valid background features and coordinates eligible for sampling.
|
|
193
|
+
|
|
194
|
+
Raster data is read in bounded windows so large VRTs do not require a full
|
|
195
|
+
raster-sized float32 allocation during candidate collection.
|
|
196
|
+
"""
|
|
193
197
|
|
|
194
198
|
if buffer_meters < 0:
|
|
195
199
|
raise ValueError("training buffer must be non-negative.")
|
|
196
200
|
|
|
197
|
-
full_window = windows.Window(0, 0, stack.width, stack.height)
|
|
198
|
-
data = stack.read(window=full_window, out_dtype="float32")
|
|
199
|
-
samples = data.reshape(stack.count, -1).T
|
|
200
|
-
valid = ~_nodata_pixel_mask(samples, stack.nodata_values)
|
|
201
|
-
|
|
202
201
|
geometries = _buffer_training_geometries(stack, gdf, buffer_meters)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
202
|
+
feature_chunks: List[np.ndarray] = []
|
|
203
|
+
row_chunks: List[np.ndarray] = []
|
|
204
|
+
col_chunks: List[np.ndarray] = []
|
|
205
|
+
block_size = 512
|
|
206
|
+
for row_off in range(0, stack.height, block_size):
|
|
207
|
+
for col_off in range(0, stack.width, block_size):
|
|
208
|
+
win = windows.Window(
|
|
209
|
+
col_off=col_off,
|
|
210
|
+
row_off=row_off,
|
|
211
|
+
width=min(block_size, stack.width - col_off),
|
|
212
|
+
height=min(block_size, stack.height - row_off),
|
|
213
|
+
)
|
|
214
|
+
data = stack.read(window=win, out_dtype="float32")
|
|
215
|
+
samples = data.reshape(stack.count, -1).T
|
|
216
|
+
valid = ~_nodata_pixel_mask(samples, stack.nodata_values)
|
|
217
|
+
|
|
218
|
+
if geometries:
|
|
219
|
+
block_transform = windows.transform(win, stack.transform)
|
|
220
|
+
excluded = features.rasterize(
|
|
221
|
+
[(geom, 1) for geom in geometries],
|
|
222
|
+
out_shape=(int(win.height), int(win.width)),
|
|
223
|
+
transform=block_transform,
|
|
224
|
+
fill=0,
|
|
225
|
+
dtype="uint8",
|
|
226
|
+
).reshape(-1)
|
|
227
|
+
valid &= excluded == 0
|
|
228
|
+
|
|
229
|
+
candidate_indices = np.flatnonzero(valid)
|
|
230
|
+
if candidate_indices.size == 0:
|
|
231
|
+
continue
|
|
232
|
+
rows, cols = np.unravel_index(
|
|
233
|
+
candidate_indices, (int(win.height), int(win.width))
|
|
234
|
+
)
|
|
235
|
+
feature_chunks.append(samples[candidate_indices])
|
|
236
|
+
row_chunks.append((rows + int(win.row_off)).astype("int32", copy=False))
|
|
237
|
+
col_chunks.append((cols + int(win.col_off)).astype("int32", copy=False))
|
|
238
|
+
|
|
239
|
+
if not feature_chunks:
|
|
240
|
+
return (
|
|
241
|
+
np.empty((0, stack.count), dtype="float32"),
|
|
242
|
+
np.empty(0, dtype="int32"),
|
|
243
|
+
np.empty(0, dtype="int32"),
|
|
244
|
+
)
|
|
215
245
|
return (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
246
|
+
np.vstack(feature_chunks),
|
|
247
|
+
np.concatenate(row_chunks),
|
|
248
|
+
np.concatenate(col_chunks),
|
|
219
249
|
)
|
|
220
250
|
|
|
221
251
|
|
|
@@ -9,6 +9,7 @@ import os
|
|
|
9
9
|
import warnings
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from functools import lru_cache
|
|
12
13
|
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
|
13
14
|
|
|
14
15
|
import geopandas as gpd
|
|
@@ -151,12 +152,37 @@ def _prepare_brt_training_data(
|
|
|
151
152
|
|
|
152
153
|
def _detect_gpu_available() -> bool:
|
|
153
154
|
"""Detect if GPU is available for XGBoost."""
|
|
155
|
+
return bool(_available_cuda_devices())
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@lru_cache(maxsize=1)
|
|
159
|
+
def _available_cuda_devices() -> List[int]:
|
|
160
|
+
"""Return logical CUDA device IDs visible to the current process."""
|
|
154
161
|
try:
|
|
155
|
-
import cupy as cp
|
|
162
|
+
import cupy as cp
|
|
163
|
+
|
|
164
|
+
count = int(cp.cuda.runtime.getDeviceCount())
|
|
165
|
+
except (ImportError, RuntimeError, AttributeError):
|
|
166
|
+
return []
|
|
167
|
+
return list(range(max(0, count)))
|
|
168
|
+
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
def _configure_prediction_device(
|
|
171
|
+
model: xgb.XGBClassifier, gpu: bool, device_id: Optional[int] = None
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Configure the loaded XGBoost model for CPU or CUDA inference."""
|
|
174
|
+
if not gpu:
|
|
175
|
+
return
|
|
176
|
+
if not _detect_gpu_available():
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
"GPU prediction requested, but CuPy/CUDA is unavailable. "
|
|
179
|
+
"Install the GPU dependencies or omit --gpu."
|
|
180
|
+
)
|
|
181
|
+
device = "cuda" if device_id is None else f"cuda:{device_id}"
|
|
182
|
+
try:
|
|
183
|
+
model.set_params(device=device, tree_method="hist", n_jobs=1)
|
|
184
|
+
except (AttributeError, TypeError, ValueError):
|
|
185
|
+
model.set_params(predictor="gpu_predictor", tree_method="gpu_hist", n_jobs=1)
|
|
160
186
|
|
|
161
187
|
|
|
162
188
|
def train_brt(
|
|
@@ -658,6 +684,7 @@ def _init_predict_worker(
|
|
|
658
684
|
out_dtype: str,
|
|
659
685
|
nodata_value: Union[int, float],
|
|
660
686
|
return_probs: bool,
|
|
687
|
+
gpu: bool,
|
|
661
688
|
) -> None:
|
|
662
689
|
global _PREDICT_STACK
|
|
663
690
|
global _PREDICT_MODEL
|
|
@@ -669,6 +696,7 @@ def _init_predict_worker(
|
|
|
669
696
|
stack.__enter__()
|
|
670
697
|
_PREDICT_STACK = stack
|
|
671
698
|
_PREDICT_MODEL = joblib.load(model_path)
|
|
699
|
+
_configure_prediction_device(_PREDICT_MODEL, gpu)
|
|
672
700
|
_PREDICT_OUT_DTYPE = out_dtype
|
|
673
701
|
_PREDICT_NODATA = nodata_value
|
|
674
702
|
_PREDICT_RETURN_PROBS = return_probs
|
|
@@ -706,6 +734,7 @@ def predict_brt(
|
|
|
706
734
|
binary_out: Optional[PathLike] = None,
|
|
707
735
|
block_overlap: int = 0,
|
|
708
736
|
jobs: int = 1,
|
|
737
|
+
gpu: bool = False,
|
|
709
738
|
) -> Path:
|
|
710
739
|
"""Apply a trained BRT (XGBoost) model to a raster stack and write a classified GeoTIFF.
|
|
711
740
|
|
|
@@ -724,6 +753,7 @@ def predict_brt(
|
|
|
724
753
|
|
|
725
754
|
_log("[bold cyan]Loading model...")
|
|
726
755
|
model: xgb.XGBClassifier = joblib.load(model_path)
|
|
756
|
+
_configure_prediction_device(model, gpu)
|
|
727
757
|
classes = getattr(model, "classes_", None)
|
|
728
758
|
classes_dtype = getattr(classes, "dtype", np.int32)
|
|
729
759
|
if np.issubdtype(classes_dtype, np.integer):
|
|
@@ -828,6 +858,7 @@ def predict_brt(
|
|
|
828
858
|
out_dtype,
|
|
829
859
|
nodata_value,
|
|
830
860
|
return_probs,
|
|
861
|
+
gpu,
|
|
831
862
|
),
|
|
832
863
|
) as executor:
|
|
833
864
|
futures: Dict[concurrent.futures.Future, windows.Window] = {}
|