Skip to content

Commit 7c7fc15

Browse files
authored
Merge pull request #39 from iosefa/feat/improve-process-tiles
process: add `skip_existing` and `verbose` options to tile processing
2 parents 80961e1 + c9a38b9 commit 7c7fc15

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

pyforestscan/process.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ def _crop_dtm(dtm_path, tile_min_x, tile_min_y, tile_max_x, tile_max_y):
5151
def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
5252
voxel_height=1, buffer_size=0.1, srs=None, hag=False,
5353
hag_dtm=False, dtm=None, bounds=None, interpolation=None, remove_outliers=False,
54-
cover_min_height: float = 2.0, cover_k: float = 0.5) -> None:
54+
cover_min_height: float = 2.0, cover_k: float = 0.5,
55+
skip_existing: bool = False, verbose: bool = False) -> None:
5556
"""
5657
Process a large EPT point cloud by tiling, compute CHM or other metrics for each tile,
5758
and write the results to the specified output directory.
@@ -75,6 +76,8 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
7576
remove_outliers (bool, optional): Whether to remove statistical outliers before calculating metrics. Defaults to False.
7677
cover_min_height (float, optional): Height threshold (in meters) for canopy cover (used when metric == "cover"). Defaults to 2.0.
7778
cover_k (float, optional): Beer–Lambert extinction coefficient for canopy cover. Defaults to 0.5.
79+
skip_existing (bool, optional): If True, skip tiles whose output file already exists. Defaults to False.
80+
verbose (bool, optional): If True, print warnings for empty/invalid tiles and buffer adjustments. Defaults to False.
7881
7982
Returns:
8083
None
@@ -126,7 +129,17 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
126129
tile_max_y = min(max_y, tile_max_y)
127130

128131
if tile_max_x <= tile_min_x or tile_max_y <= tile_min_y:
129-
print(f"Warning: Skipping tile ({i}, {j}) due to invalid spatial extent.")
132+
if verbose:
133+
print(f"Warning: Skipping tile ({i}, {j}) due to invalid spatial extent.")
134+
pbar.update(1)
135+
continue
136+
137+
if metric == "chm":
138+
result_file = os.path.join(output_path, f"tile_{i}_{j}_chm.tif")
139+
else:
140+
result_file = os.path.join(output_path, f"tile_{i}_{j}_{metric}.tif")
141+
142+
if skip_existing and os.path.isfile(result_file):
130143
pbar.update(1)
131144
continue
132145

@@ -164,7 +177,8 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
164177
tile_points = tile_pipeline.arrays[0]
165178

166179
if tile_points.size == 0:
167-
print(f"Warning: No data in tile ({i}, {j}). Skipping.")
180+
if verbose:
181+
print(f"Warning: No data in tile ({i}, {j}). Skipping.")
168182
pbar.update(1)
169183
continue
170184

@@ -175,12 +189,29 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
175189
chm, extent = calculate_chm(tile_points, voxel_size, interpolation=interpolation)
176190

177191
if buffer_pixels_x * 2 >= chm.shape[1] or buffer_pixels_y * 2 >= chm.shape[0]:
178-
print(
179-
f"Warning: Buffer size exceeds CHM dimensions for tile ({i}, {j}). Adjusting buffer size.")
192+
if verbose:
193+
print(
194+
f"Warning: Buffer size exceeds CHM dimensions for tile ({i}, {j}). Adjusting buffer size.")
180195
buffer_pixels_x = max(0, chm.shape[1] // 2 - 1)
181196
buffer_pixels_y = max(0, chm.shape[0] // 2 - 1)
182197

183-
chm = chm[buffer_pixels_y:-buffer_pixels_y, buffer_pixels_x:-buffer_pixels_x]
198+
# Safe crop: avoid Python's -0 slice behavior producing empty arrays.
199+
if buffer_pixels_x < 0 or buffer_pixels_y < 0:
200+
raise ValueError("Computed negative buffer pixels; check voxel and buffer sizes.")
201+
202+
start_x = buffer_pixels_x
203+
end_x = chm.shape[1] - buffer_pixels_x if buffer_pixels_x > 0 else chm.shape[1]
204+
start_y = buffer_pixels_y
205+
end_y = chm.shape[0] - buffer_pixels_y if buffer_pixels_y > 0 else chm.shape[0]
206+
207+
# If cropping would yield an empty array due to small tiles, skip cropping
208+
if end_x <= start_x or end_y <= start_y:
209+
# Fall back to no buffer crop for this tile
210+
core = chm
211+
else:
212+
core = chm[start_y:end_y, start_x:end_x]
213+
214+
chm = core
184215

185216
core_extent = (
186217
tile_min_x + buffer_x,
@@ -189,7 +220,6 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
189220
tile_max_y - buffer_y,
190221
)
191222

192-
result_file = os.path.join(output_path, f"tile_{i}_{j}_chm.tif")
193223
create_geotiff(chm, result_file, srs, core_extent)
194224
elif metric in ["fhd", "pai", "cover"]:
195225
voxels, spatial_extent = assign_voxels(tile_points, voxel_size)
@@ -243,11 +273,11 @@ def process_with_tiles(ept_file, tile_size, output_path, metric, voxel_size,
243273
)
244274

245275
if core_extent[1] <= core_extent[0] or core_extent[3] <= core_extent[2]:
246-
print(f"Warning: Invalid core extent for tile ({i}, {j}): {core_extent}. Skipping.")
276+
if verbose:
277+
print(f"Warning: Invalid core extent for tile ({i}, {j}): {core_extent}. Skipping.")
247278
pbar.update(1)
248279
continue
249280

250-
result_file = os.path.join(output_path, f"tile_{i}_{j}_{metric}.tif")
251281
create_geotiff(result, result_file, srs, core_extent)
252282
else:
253283
raise ValueError(f"Unsupported metric: {metric}")

0 commit comments

Comments
 (0)