Skip to content

samstars.data.tiling

samstars.data.tiling

make_tiles.py – split IMG / CHM / COST / SEEDS into 500 m tiles (+20 m overlap) with optional spatial thinning of seeds (0.7 m)

build_single_tile(*, img, chm, den, crowns=None, rgb_bands=None, slic=None, out=Path('tiles'), tile_name='tile_000_000', cost_weights=(0.4, 0.3, 0.3, 0.0), seed_params=None, thin_seeds=True, thin_dist=0.8, seed_d_min=4.5, seed_min_dist_px=4, seed_gauss_sigma=2, seed_min_samples=1, seed_merge_radius=3.0, seed_min_canopy_height=2.0, write_den=False, skip_existing=True)

Prepare a single tile-style input directory from one scene.

Source code in samstars/data/tiling.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
def build_single_tile(*,
                      img: Path,
                      chm: Path,
                      den: Path,
                      crowns: Path | None = None,
                      rgb_bands: tuple[int, int, int] | None = None,
                      slic: Path | None = None,
                      out: Path = Path("tiles"),
                      tile_name: str = "tile_000_000",
                      cost_weights=(0.4, 0.3, 0.3, 0.0),
                      seed_params: SeedParams | None = None,
                      thin_seeds: bool = True,
                      thin_dist: float = 0.8,
                      seed_d_min: float = 4.5,
                      seed_min_dist_px: int = 4,
                      seed_gauss_sigma: int = 2,
                      seed_min_samples: int = 1,
                      seed_merge_radius: float = 3.0,
                      seed_min_canopy_height: float = 2.0,
                      write_den: bool = False,
                      skip_existing: bool = True) -> Path:
    """Prepare a single tile-style input directory from one scene."""
    out = Path(out)
    out.mkdir(parents=True, exist_ok=True)

    with rasterio.open(img) as img_ds:
        res = abs(img_ds.transform.a)
        assert abs(res - abs(img_ds.transform.e)) < 1e-6, "non-square pixels"

        rgb_bands = tuple(rgb_bands) if rgb_bands is not None else ((7, 5, 3) if img_ds.count >= 7 else (1, 2, 3))
        if len(rgb_bands) != 3:
            raise ValueError("rgb_bands must contain exactly 3 band indices.")
        if any(b < 1 or b > img_ds.count for b in rgb_bands):
            raise ValueError(f"rgb_bands must be between 1 and {img_ds.count}.")
        base_meta = img_ds.profile | {
            "driver": "GTiff",
            "count": 3,
            "compress": "deflate",
            "predictor": 2,
            "zlevel": 6,
        }
        img_tfm, img_crs = img_ds.transform, img_ds.crs
        chm_ds, chm_raw = _align_to_img(chm, img_ds)
        den_ds, den_raw = _align_to_img(den, img_ds)
        try:
            tile_dir = out / tile_name
            win = Window(0, 0, img_ds.width, img_ds.height)
            written = _prepare_tile(
                tile_dir=tile_dir,
                img_ds=img_ds,
                chm_ds=chm_ds,
                den_ds=den_ds,
                img_tfm=img_tfm,
                img_crs=img_crs,
                rgb_bands=rgb_bands,
                base_meta=base_meta,
                win=win,
                h=img_ds.height,
                w=img_ds.width,
                res=res,
                slic=slic,
                cost_weights=cost_weights,
                seed_params=seed_params,
                seed_d_min=seed_d_min,
                seed_min_dist_px=seed_min_dist_px,
                seed_gauss_sigma=seed_gauss_sigma,
                seed_min_samples=seed_min_samples,
                seed_merge_radius=seed_merge_radius,
                seed_min_canopy_height=seed_min_canopy_height,
                thin_seeds=thin_seeds,
                thin_dist=thin_dist,
                write_den=write_den,
                skip_existing=skip_existing,
            )
        finally:
            chm_ds.close()
            den_ds.close()
            if chm_raw is not chm_ds:
                chm_raw.close()
            if den_raw is not den_ds:
                den_raw.close()

    if not written:
        raise RuntimeError("No valid tile outputs generated from the provided inputs.")

    tile_dir = out / tile_name
    if crowns is not None:
        crowns = Path(crowns)
        if not crowns.exists():
            raise FileNotFoundError(f"Missing crowns file: {crowns}")
        shutil.copy2(crowns, tile_dir / "crowns.gpkg")

    print("✓ Single-tile inputs written to", out.resolve())
    return out

build_tiles(*, img, chm, den, rgb_bands=None, slic=None, out=Path('tiles'), tile=500.0, overlap=20.0, cost_weights=(0.4, 0.3, 0.3, 0.0), seed_params=None, thin_seeds=True, thin_dist=0.8, seed_d_min=4.5, seed_min_dist_px=4, seed_gauss_sigma=2, seed_min_samples=1, seed_merge_radius=3.0, seed_min_canopy_height=2.0, write_den=False, skip_existing=True)

Build tiles.

Source code in samstars/data/tiling.py
211
212
213
214
215
216
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def build_tiles(*,
                img: Path,
                chm: Path,
                den: Path,
                rgb_bands: tuple[int, int, int] | None = None,
                slic: Path | None = None,
                out: Path = Path("tiles"),
                tile: float = 500.0,
                overlap: float = 20.0,
                cost_weights=(0.4, 0.3, 0.3, 0.0),
                seed_params: SeedParams | None = None,
                thin_seeds: bool = True,                    # ← NEW
                thin_dist: float = 0.8,
                seed_d_min: float = 4.5,
                seed_min_dist_px: int = 4,
                seed_gauss_sigma: int = 2,
                seed_min_samples: int = 1,
                seed_merge_radius: float = 3.0,
                seed_min_canopy_height: float = 2.0,
                write_den: bool = False,                    # ← NEW
                skip_existing: bool = True) -> None:

    """Build tiles."""
    out.mkdir(parents=True, exist_ok=True)
    with rasterio.open(img) as img_ds:
        res = abs(img_ds.transform.a)
        assert abs(res - abs(img_ds.transform.e)) < 1e-6, "non‑square pixels"

        img_h, img_w = img_ds.height, img_ds.width
        img_tfm, img_crs = img_ds.transform, img_ds.crs
        rgb_bands = tuple(rgb_bands) if rgb_bands is not None else ((7, 5, 3) if img_ds.count >= 7 else (1, 2, 3))
        if len(rgb_bands) != 3:
            raise ValueError("rgb_bands must contain exactly 3 band indices.")
        if any(b < 1 or b > img_ds.count for b in rgb_bands):
            raise ValueError(f"rgb_bands must be between 1 and {img_ds.count}.")

        base_meta = img_ds.profile | {"driver": "GTiff", "count": 3,
                                      "compress": "deflate", "predictor": 2,
                                      "zlevel": 6}

        chm_ds, chm_raw = _align_to_img(chm, img_ds)
        den_ds, den_raw = _align_to_img(den, img_ds)

        tile_px   = int(round(tile / res))
        stride_px = int(round((tile - overlap) / res))
        tiles = [(ty, tx, r0, c0,
                  min(tile_px, img_h - r0),
                  min(tile_px, img_w - c0))
                 for ty, r0 in enumerate(range(0, img_h, stride_px))
                 for tx, c0 in enumerate(range(0, img_w, stride_px))
                 if r0 < img_h and c0 < img_w]

        pbar = tqdm(total=len(tiles), desc="Tiling", unit="tile")
        for ty, tx, r0, c0, h, w in tiles:
            tile_dir = out / f"tile_{ty:03d}_{tx:03d}"
            win = Window(c0, r0, w, h)
            _prepare_tile(
                tile_dir=tile_dir,
                img_ds=img_ds,
                chm_ds=chm_ds,
                den_ds=den_ds,
                img_tfm=img_tfm,
                img_crs=img_crs,
                rgb_bands=rgb_bands,
                base_meta=base_meta,
                win=win,
                h=h,
                w=w,
                res=res,
                slic=slic,
                cost_weights=cost_weights,
                seed_params=seed_params,
                seed_d_min=seed_d_min,
                seed_min_dist_px=seed_min_dist_px,
                seed_gauss_sigma=seed_gauss_sigma,
                seed_min_samples=seed_min_samples,
                seed_merge_radius=seed_merge_radius,
                seed_min_canopy_height=seed_min_canopy_height,
                thin_seeds=thin_seeds,
                thin_dist=thin_dist,
                write_den=write_den,
                skip_existing=skip_existing,
            )
            pbar.update(1)

        pbar.close()
        chm_ds.close(); den_ds.close()
        if chm_raw is not chm_ds: chm_raw.close()
        if den_raw is not den_ds: den_raw.close()
    print("✓ Tiles written to", out.resolve())