ign-pdal-tools 1.15.3__tar.gz → 1.15.5__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.
Files changed (43) hide show
  1. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/PKG-INFO +1 -1
  2. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/PKG-INFO +1 -1
  3. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/_version.py +1 -1
  4. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/replace_area_in_pointcloud.py +59 -6
  5. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_replace_area_in_pointcloud.py +19 -1
  6. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/LICENSE.md +0 -0
  7. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/README.md +0 -0
  8. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/SOURCES.txt +0 -0
  9. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/dependency_links.txt +0 -0
  10. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/top_level.txt +0 -0
  11. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/add_points_in_pointcloud.py +0 -0
  12. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/color.py +0 -0
  13. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/create_random_laz.py +0 -0
  14. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/download_image.py +0 -0
  15. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_add_buffer.py +0 -0
  16. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_clip.py +0 -0
  17. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_comparison.py +0 -0
  18. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_info.py +0 -0
  19. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_merge.py +0 -0
  20. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_remove_dimensions.py +0 -0
  21. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/las_rename_dimension.py +0 -0
  22. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/pcd_info.py +0 -0
  23. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/replace_attribute_in_las.py +0 -0
  24. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/standardize_format.py +0 -0
  25. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pdaltools/unlock_file.py +0 -0
  26. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/pyproject.toml +0 -0
  27. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/setup.cfg +0 -0
  28. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_add_points_in_pointcloud.py +0 -0
  29. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_color.py +0 -0
  30. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_create_random_laz.py +0 -0
  31. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_download_image.py +0 -0
  32. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_add_buffer.py +0 -0
  33. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_clip.py +0 -0
  34. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_comparison.py +0 -0
  35. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_info.py +0 -0
  36. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_merge.py +0 -0
  37. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_remove_dimensions.py +0 -0
  38. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_las_rename_dimension.py +0 -0
  39. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_pcd_info.py +0 -0
  40. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_pdal.py +0 -0
  41. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_replace_attribute_in_las.py +0 -0
  42. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_standardize_format.py +0 -0
  43. {ign_pdal_tools-1.15.3 → ign_pdal_tools-1.15.5}/test/test_unlock.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ign-pdal-tools
3
- Version: 1.15.3
3
+ Version: 1.15.5
4
4
  Summary: Library for common LAS files manipulation with PDAL
5
5
  Author-email: Guillaume Liegard <guillaume.liegard@ign.fr>
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ign-pdal-tools
3
- Version: 1.15.3
3
+ Version: 1.15.5
4
4
  Summary: Library for common LAS files manipulation with PDAL
5
5
  Author-email: Guillaume Liegard <guillaume.liegard@ign.fr>
6
6
  Description-Content-Type: text/markdown
@@ -1,4 +1,4 @@
1
- __version__ = "1.15.3"
1
+ __version__ = "1.15.5"
2
2
 
3
3
 
4
4
  if __name__ == "__main__":
@@ -1,12 +1,22 @@
1
1
  import argparse
2
+ import os
3
+ import shutil
4
+ import tempfile
5
+ import time
2
6
  import warnings
3
7
 
8
+ import geopandas as gpd
4
9
  import numpy as np
5
10
  import pdal
6
11
  from numpy.lib import recfunctions as rfn
7
12
  from osgeo import gdal
13
+ from shapely.geometry import box
8
14
 
9
- from pdaltools.las_info import get_writer_parameters_from_reader_metadata
15
+ from pdaltools.las_info import (
16
+ get_bounds_from_header_info,
17
+ get_writer_parameters_from_reader_metadata,
18
+ las_info_metadata,
19
+ )
10
20
 
11
21
 
12
22
  def argument_parser():
@@ -139,6 +149,18 @@ def pipeline_read_from_DSM(dsm, ground_mask, classification):
139
149
  return pipeline
140
150
 
141
151
 
152
+ def clip_area(area_input, area_output, las_file):
153
+ start = time.time()
154
+ gdf = gpd.read_file(area_input)
155
+ minx, maxx, miny, maxy = get_bounds_from_header_info(las_info_metadata(las_file))
156
+ selection = gdf[gdf.intersects(box(minx, miny, maxx, maxy))]
157
+ num_polygon = len(selection)
158
+ selection.to_file(area_output)
159
+ end = time.time()
160
+ print(f"Create replacement area cropped: {num_polygon} polygons in {end-start:.2f} seconds")
161
+ return num_polygon
162
+
163
+
142
164
  def replace_area(
143
165
  target_cloud, pipeline_source, replacement_area, output_cloud, source_pdal_filter="", target_pdal_filter=""
144
166
  ):
@@ -147,6 +169,24 @@ def replace_area(
147
169
  print("output cloud: ", output_cloud)
148
170
  print("source pdal filter: ", source_pdal_filter)
149
171
  print("target pdal filter: ", target_pdal_filter)
172
+
173
+ start = time.time()
174
+ tmpdir = tempfile.mkdtemp()
175
+ replacement_name, ext = os.path.splitext(os.path.basename(replacement_area))
176
+ las_name, _ = os.path.splitext(os.path.basename(target_cloud))
177
+ replacement_area_crop = os.path.join(tmpdir, f"{replacement_name}_{las_name}{ext}")
178
+ print("replacement area crop: ", replacement_area_crop)
179
+
180
+ num_polygon = clip_area(replacement_area, replacement_area_crop, target_cloud)
181
+ if num_polygon == 0:
182
+ print("No polygon inside target cloud, output file is target cloud. We copy target in output.")
183
+
184
+ shutil.copy2(src=target_cloud, dst=output_cloud)
185
+ end = time.time()
186
+ print("all steps done in ", f"{end-start:.2f}", " seconds")
187
+ return
188
+
189
+ replacement_area = replacement_area_crop
150
190
  crops = []
151
191
  # pipeline to read target_cloud and remove points inside the polygon
152
192
  pipeline_target = pdal.Pipeline()
@@ -159,7 +199,10 @@ def replace_area(
159
199
  pipeline_target |= pdal.Filter.overlay(column="fid", dimension="geometryFid", datasource=replacement_area)
160
200
  # Keep only points out of the area
161
201
  pipeline_target |= pdal.Filter.expression(expression="geometryFid==-1", tag="A")
162
- pipeline_target.execute()
202
+ target_count = pipeline_target.execute()
203
+
204
+ t1 = time.time()
205
+ print(f"Step 1: target count: {target_count} points in {t1-start:.2f} seconds")
163
206
 
164
207
  # get input dimensions dtype from target
165
208
  if pipeline_target.arrays:
@@ -170,7 +213,10 @@ def replace_area(
170
213
  pipeline_target2 |= pdal.Reader.las(filename=target_cloud)
171
214
  pipeline_target2.execute()
172
215
  input_dim_dtype = pipeline_target2.arrays[0].dtype
216
+ t1_bis = time.time()
217
+ print(f"Step 1-bis: re-read to have dimensions: {t1_bis-t1:.2f} seconds")
173
218
 
219
+ t2 = time.time()
174
220
  # get input dimensions names
175
221
  input_dimensions = list(input_dim_dtype.fields.keys())
176
222
 
@@ -178,7 +224,7 @@ def replace_area(
178
224
  output_dimensions = [dim for dim in input_dimensions if dim not in "geometryFid"]
179
225
 
180
226
  # add target to the result after keeping only the expected dimensions
181
- if pipeline_target.arrays:
227
+ if target_count:
182
228
  target_cloud_pruned = pipeline_target.arrays[0][output_dimensions]
183
229
  crops.append(target_cloud_pruned)
184
230
 
@@ -190,10 +236,13 @@ def replace_area(
190
236
  pipeline_source |= pdal.Filter.overlay(column="fid", dimension="geometryFid", datasource=replacement_area)
191
237
  # Keep only points in the area
192
238
  pipeline_source |= pdal.Filter.expression(expression="geometryFid>=0", tag="B")
193
- pipeline_source.execute()
239
+ source_count = pipeline_source.execute()
240
+
241
+ t3 = time.time()
242
+ print(f"Step 2: source count: {source_count} points in {t3-t2:.2f} seconds")
194
243
 
195
244
  # add source to the result
196
- if pipeline_source.arrays:
245
+ if source_count:
197
246
  # eventually add dimensions in source to have same dimensions as target cloud
198
247
  # we do that in numpy (instead of PDAL filter) to keep dimension types
199
248
  source_cloud_crop = pipeline_source.arrays[0]
@@ -219,7 +268,11 @@ def replace_area(
219
268
 
220
269
  writer_params = get_writer_params(target_cloud)
221
270
  pipeline |= pdal.Writer.las(filename=output_cloud, **writer_params)
222
- pipeline.execute()
271
+ points = pipeline.execute()
272
+
273
+ end = time.time()
274
+ print(f"Step 3: merge: { points }, points in {end-t3:.2f} seconds")
275
+ print("all steps done in ", f"{end-start:.2f}", " seconds")
223
276
 
224
277
 
225
278
  if __name__ == "__main__":
@@ -325,7 +325,7 @@ def test_main_from_cloud_with_filter():
325
325
  assert get_nb_points(output_file) == 6390
326
326
 
327
327
 
328
- def test_main_from_DSM():
328
+ def test_main_from_DSM_light():
329
329
  output_file = os.path.join(TMP_PATH, "main_from_DSM", "output_main_from_DSM.laz")
330
330
  os.makedirs(os.path.dirname(output_file))
331
331
  cmd = (
@@ -338,3 +338,21 @@ def test_main_from_DSM():
338
338
  # same result as test_from_DMS
339
339
  counts = compute_count_one_file(output_file, "Classification")
340
340
  assert counts == {"1": 3841, "2": 2355, str(SOURCE_CLASSIF): 45}
341
+
342
+
343
+ def test_main_from_DSM_light_replace_all():
344
+ output_file = os.path.join(TMP_PATH, "main_from_DSM_replace_around", "output_main_from_DSM.laz")
345
+ os.makedirs(os.path.dirname(output_file))
346
+
347
+ # use replacement area bigger than the tile
348
+ REPLACE_AREA_2 = os.path.join(INPUT_DIR, "replace_area_2.geojson")
349
+ cmd = (
350
+ f"from_DSM -d {SOURCE_DSM} -g {SOURCE_GROUND_MASK} -c {SOURCE_CLASSIF} -t {TARGET_CLOUD} -r {REPLACE_AREA_2}"
351
+ f" -o {output_file}"
352
+ ).split()
353
+ args = argument_parser().parse_args(cmd)
354
+ args.func(args)
355
+
356
+ # only have points from source
357
+ counts = compute_count_one_file(output_file, "Classification")
358
+ assert counts == {str(SOURCE_CLASSIF): 81}