ign-pdal-tools 1.15.4__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.4 → ign_pdal_tools-1.15.5}/PKG-INFO +1 -1
  2. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/PKG-INFO +1 -1
  3. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/_version.py +1 -1
  4. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/replace_area_in_pointcloud.py +56 -5
  5. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_replace_area_in_pointcloud.py +19 -1
  6. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/LICENSE.md +0 -0
  7. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/README.md +0 -0
  8. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/SOURCES.txt +0 -0
  9. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/dependency_links.txt +0 -0
  10. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/ign_pdal_tools.egg-info/top_level.txt +0 -0
  11. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/add_points_in_pointcloud.py +0 -0
  12. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/color.py +0 -0
  13. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/create_random_laz.py +0 -0
  14. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/download_image.py +0 -0
  15. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_add_buffer.py +0 -0
  16. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_clip.py +0 -0
  17. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_comparison.py +0 -0
  18. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_info.py +0 -0
  19. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_merge.py +0 -0
  20. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_remove_dimensions.py +0 -0
  21. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/las_rename_dimension.py +0 -0
  22. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/pcd_info.py +0 -0
  23. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/replace_attribute_in_las.py +0 -0
  24. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/standardize_format.py +0 -0
  25. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pdaltools/unlock_file.py +0 -0
  26. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/pyproject.toml +0 -0
  27. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/setup.cfg +0 -0
  28. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_add_points_in_pointcloud.py +0 -0
  29. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_color.py +0 -0
  30. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_create_random_laz.py +0 -0
  31. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_download_image.py +0 -0
  32. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_add_buffer.py +0 -0
  33. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_clip.py +0 -0
  34. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_comparison.py +0 -0
  35. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_info.py +0 -0
  36. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_merge.py +0 -0
  37. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_remove_dimensions.py +0 -0
  38. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_las_rename_dimension.py +0 -0
  39. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_pcd_info.py +0 -0
  40. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_pdal.py +0 -0
  41. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_replace_attribute_in_las.py +0 -0
  42. {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.5}/test/test_standardize_format.py +0 -0
  43. {ign_pdal_tools-1.15.4 → 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.4
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.4
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.4"
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()
@@ -160,7 +200,9 @@ def replace_area(
160
200
  # Keep only points out of the area
161
201
  pipeline_target |= pdal.Filter.expression(expression="geometryFid==-1", tag="A")
162
202
  target_count = pipeline_target.execute()
163
- print("Step 1: target count: ", target_count)
203
+
204
+ t1 = time.time()
205
+ print(f"Step 1: target count: {target_count} points in {t1-start:.2f} seconds")
164
206
 
165
207
  # get input dimensions dtype from target
166
208
  if pipeline_target.arrays:
@@ -171,7 +213,10 @@ def replace_area(
171
213
  pipeline_target2 |= pdal.Reader.las(filename=target_cloud)
172
214
  pipeline_target2.execute()
173
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")
174
218
 
219
+ t2 = time.time()
175
220
  # get input dimensions names
176
221
  input_dimensions = list(input_dim_dtype.fields.keys())
177
222
 
@@ -179,7 +224,7 @@ def replace_area(
179
224
  output_dimensions = [dim for dim in input_dimensions if dim not in "geometryFid"]
180
225
 
181
226
  # add target to the result after keeping only the expected dimensions
182
- if pipeline_target.arrays:
227
+ if target_count:
183
228
  target_cloud_pruned = pipeline_target.arrays[0][output_dimensions]
184
229
  crops.append(target_cloud_pruned)
185
230
 
@@ -192,7 +237,9 @@ def replace_area(
192
237
  # Keep only points in the area
193
238
  pipeline_source |= pdal.Filter.expression(expression="geometryFid>=0", tag="B")
194
239
  source_count = pipeline_source.execute()
195
- print("Step 2: source count: ", source_count)
240
+
241
+ t3 = time.time()
242
+ print(f"Step 2: source count: {source_count} points in {t3-t2:.2f} seconds")
196
243
 
197
244
  # add source to the result
198
245
  if source_count:
@@ -221,7 +268,11 @@ def replace_area(
221
268
 
222
269
  writer_params = get_writer_params(target_cloud)
223
270
  pipeline |= pdal.Writer.las(filename=output_cloud, **writer_params)
224
- 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")
225
276
 
226
277
 
227
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}