ign-pdal-tools 1.15.1__tar.gz → 1.15.3__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.
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/PKG-INFO +1 -1
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/ign_pdal_tools.egg-info/PKG-INFO +1 -1
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/_version.py +1 -1
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/add_points_in_pointcloud.py +5 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/replace_area_in_pointcloud.py +25 -11
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_add_points_in_pointcloud.py +51 -5
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_replace_area_in_pointcloud.py +5 -4
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/LICENSE.md +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/README.md +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/ign_pdal_tools.egg-info/SOURCES.txt +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/ign_pdal_tools.egg-info/dependency_links.txt +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/ign_pdal_tools.egg-info/top_level.txt +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/color.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/create_random_laz.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/download_image.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_add_buffer.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_clip.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_comparison.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_info.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_merge.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_remove_dimensions.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/las_rename_dimension.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/pcd_info.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/replace_attribute_in_las.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/standardize_format.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pdaltools/unlock_file.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/pyproject.toml +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/setup.cfg +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_color.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_create_random_laz.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_download_image.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_add_buffer.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_clip.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_comparison.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_info.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_merge.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_remove_dimensions.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_las_rename_dimension.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_pcd_info.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_pdal.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_replace_attribute_in_las.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_standardize_format.py +0 -0
- {ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/test/test_unlock.py +0 -0
|
@@ -6,6 +6,7 @@ import geopandas as gpd
|
|
|
6
6
|
import laspy
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pdal
|
|
9
|
+
from shapely import set_precision
|
|
9
10
|
from shapely.geometry import MultiPoint, Point, box
|
|
10
11
|
|
|
11
12
|
from pdaltools.las_info import get_epsg_from_las, get_tile_bbox
|
|
@@ -327,6 +328,10 @@ def add_points_from_geometry_to_las(
|
|
|
327
328
|
# Clip points from GeoJSON by LIDAR tile
|
|
328
329
|
points_clipped = clip_3d_points_to_tile(points_gdf, input_las, spatial_ref, tile_width)
|
|
329
330
|
|
|
331
|
+
# Remove duplicate points (due to precision issue) - las file have centrimetric precision
|
|
332
|
+
points_clipped.geometry = set_precision(points_clipped.geometry, grid_size=0.01)
|
|
333
|
+
points_clipped = points_clipped.drop_duplicates()
|
|
334
|
+
|
|
330
335
|
# Add points by LIDAR tile and save the result
|
|
331
336
|
add_points_to_las(points_clipped, input_las, output_las, spatial_ref, virtual_points_classes)
|
|
332
337
|
|
|
@@ -33,13 +33,14 @@ def argument_parser():
|
|
|
33
33
|
help="path of the source digital surface model (DSM), used to generate source points",
|
|
34
34
|
)
|
|
35
35
|
from_DSM.add_argument(
|
|
36
|
-
"--
|
|
36
|
+
"--source_ground_mask",
|
|
37
37
|
"-g",
|
|
38
38
|
required=True,
|
|
39
39
|
type=str,
|
|
40
40
|
help=(
|
|
41
|
-
"
|
|
42
|
-
"
|
|
41
|
+
"ground mask, a raster file used to filter source cloud. "
|
|
42
|
+
"Pixel with value > 0 is considered as ground, and define the source cloud we keep. "
|
|
43
|
+
"(tif or other raster format readable by GDAL)"
|
|
43
44
|
),
|
|
44
45
|
)
|
|
45
46
|
from_DSM.add_argument(
|
|
@@ -65,7 +66,7 @@ def add_common_options(parser):
|
|
|
65
66
|
"-r",
|
|
66
67
|
required=True,
|
|
67
68
|
type=str,
|
|
68
|
-
help="area to replace (shapefile, geojson or other format readable by GDAL)",
|
|
69
|
+
help="area to replace (shapefile, geojson or other vector format readable by GDAL)",
|
|
69
70
|
)
|
|
70
71
|
parser.add_argument("--output_cloud", "-o", required=True, type=str, help="output cloud file")
|
|
71
72
|
|
|
@@ -84,7 +85,7 @@ def from_DSM_func(args):
|
|
|
84
85
|
replace_area(
|
|
85
86
|
target_cloud=args.target_cloud,
|
|
86
87
|
pipeline_source=pipeline_read_from_DSM(
|
|
87
|
-
dsm=args.source_dsm,
|
|
88
|
+
dsm=args.source_dsm, ground_mask=args.source_ground_mask, classification=args.source_classification
|
|
88
89
|
),
|
|
89
90
|
replacement_area=args.replacement_area,
|
|
90
91
|
output_cloud=args.output_cloud,
|
|
@@ -101,27 +102,35 @@ def get_writer_params(input_file):
|
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
def pipeline_read_from_cloud(filename):
|
|
105
|
+
print("source cloud: ", filename)
|
|
104
106
|
pipeline_source = pdal.Pipeline()
|
|
105
107
|
pipeline_source |= pdal.Reader.las(filename=filename)
|
|
106
108
|
return pipeline_source
|
|
107
109
|
|
|
108
110
|
|
|
109
|
-
def pipeline_read_from_DSM(dsm,
|
|
111
|
+
def pipeline_read_from_DSM(dsm, ground_mask, classification):
|
|
112
|
+
print("DSM: ", dsm)
|
|
113
|
+
print("ground_mask: ", ground_mask)
|
|
114
|
+
print("classification: ", classification)
|
|
115
|
+
|
|
110
116
|
# get nodata value
|
|
111
117
|
ds = gdal.Open(dsm)
|
|
112
118
|
band = ds.GetRasterBand(1)
|
|
113
119
|
nodata_value = band.GetNoDataValue()
|
|
120
|
+
print("DSM: nodata:", nodata_value)
|
|
114
121
|
ds.Close()
|
|
115
122
|
|
|
116
123
|
pipeline = pdal.Pipeline()
|
|
117
124
|
pipeline |= pdal.Reader.gdal(filename=dsm, header="Z")
|
|
118
|
-
pipeline |= pdal.Filter.expression(expression=f"Z != {nodata_value}")
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
if nodata_value is not None: # nodata_value may be None and cause bugs
|
|
127
|
+
pipeline |= pdal.Filter.expression(expression=f"Z != {nodata_value}")
|
|
128
|
+
|
|
129
|
+
pipeline |= pdal.Filter.ferry(dimensions="=> ground")
|
|
130
|
+
pipeline |= pdal.Filter.assign(assignment="ground[:]=-1")
|
|
131
|
+
pipeline |= pdal.Filter.colorization(dimensions="ground:1:1.0", raster=ground_mask)
|
|
123
132
|
# Keep only points in the area
|
|
124
|
-
pipeline |= pdal.Filter.expression(expression="
|
|
133
|
+
pipeline |= pdal.Filter.expression(expression="ground>0")
|
|
125
134
|
|
|
126
135
|
# assign class
|
|
127
136
|
pipeline |= pdal.Filter.ferry(dimensions="=>Classification")
|
|
@@ -133,6 +142,11 @@ def pipeline_read_from_DSM(dsm, ground_area, classification):
|
|
|
133
142
|
def replace_area(
|
|
134
143
|
target_cloud, pipeline_source, replacement_area, output_cloud, source_pdal_filter="", target_pdal_filter=""
|
|
135
144
|
):
|
|
145
|
+
print("target cloud: ", target_cloud)
|
|
146
|
+
print("replacement area: ", replacement_area)
|
|
147
|
+
print("output cloud: ", output_cloud)
|
|
148
|
+
print("source pdal filter: ", source_pdal_filter)
|
|
149
|
+
print("target pdal filter: ", target_pdal_filter)
|
|
136
150
|
crops = []
|
|
137
151
|
# pipeline to read target_cloud and remove points inside the polygon
|
|
138
152
|
pipeline_target = pdal.Pipeline()
|
|
@@ -109,7 +109,7 @@ def test_add_points_to_las(input_file, epsg, input_points_2d, expected_nb_points
|
|
|
109
109
|
metadata_out = las_info_metadata(OUTPUT_FILE)
|
|
110
110
|
output_dimensions = metadata_out["dimensions"]
|
|
111
111
|
|
|
112
|
-
assert input_dimensions == output_dimensions
|
|
112
|
+
assert input_dimensions == output_dimensions # All dimension should be preserve
|
|
113
113
|
|
|
114
114
|
point_count = compute_count_one_file(OUTPUT_FILE)["68"]
|
|
115
115
|
assert point_count == expected_nb_points # Add all points from geojson
|
|
@@ -261,7 +261,7 @@ def test_generate_3d_points_from_lines(lines_gdf, spacing, altitude_column, expe
|
|
|
261
261
|
INPUT_PCD,
|
|
262
262
|
INPUT_LIGNES_2D_GEOJSON,
|
|
263
263
|
"EPSG:2154",
|
|
264
|
-
|
|
264
|
+
677,
|
|
265
265
|
0.25,
|
|
266
266
|
"RecupZ",
|
|
267
267
|
), # should add only lines (.GeoJSON) within tile extend
|
|
@@ -269,16 +269,16 @@ def test_generate_3d_points_from_lines(lines_gdf, spacing, altitude_column, expe
|
|
|
269
269
|
INPUT_PCD,
|
|
270
270
|
INPUT_LIGNES_SHAPE,
|
|
271
271
|
"EPSG:2154",
|
|
272
|
-
|
|
272
|
+
677,
|
|
273
273
|
0.25,
|
|
274
274
|
"RecupZ",
|
|
275
275
|
), # should add only lines (.shp) within tile extend
|
|
276
|
-
(INPUT_PCD, INPUT_LIGNES_SHAPE, None,
|
|
276
|
+
(INPUT_PCD, INPUT_LIGNES_SHAPE, None, 677, 0.25, "RecupZ"), # Should work with or with an input epsg
|
|
277
277
|
(
|
|
278
278
|
INPUT_PCD,
|
|
279
279
|
INPUT_LIGNES_3D_GEOJSON,
|
|
280
280
|
None,
|
|
281
|
-
|
|
281
|
+
677,
|
|
282
282
|
0.25,
|
|
283
283
|
None,
|
|
284
284
|
), # Should work with or without an input epsg and without altitude_column
|
|
@@ -353,6 +353,52 @@ def test_add_points_from_geometry_to_las_nok(input_file, input_points, epsg, spa
|
|
|
353
353
|
)
|
|
354
354
|
|
|
355
355
|
|
|
356
|
+
@pytest.mark.parametrize(
|
|
357
|
+
"spacing",
|
|
358
|
+
[
|
|
359
|
+
0.1,
|
|
360
|
+
0.01,
|
|
361
|
+
0.001,
|
|
362
|
+
0.25,
|
|
363
|
+
0.5,
|
|
364
|
+
1,
|
|
365
|
+
],
|
|
366
|
+
)
|
|
367
|
+
def test_add_points_from_geometry_to_las_no_dupplicate(spacing):
|
|
368
|
+
# there should have no duplicate in final las
|
|
369
|
+
|
|
370
|
+
input_las_file = os.path.join(TEST_PATH, "data/crop_duplicate.laz")
|
|
371
|
+
input_geo_file = os.path.join(TEST_PATH, "data/crop_duplicate.geojson")
|
|
372
|
+
|
|
373
|
+
add_points_in_pointcloud.add_points_from_geometry_to_las(
|
|
374
|
+
input_geo_file, input_las_file, OUTPUT_FILE, 68, "EPSG:2154", 1000, spacing, None
|
|
375
|
+
)
|
|
376
|
+
assert Path(OUTPUT_FILE).exists() # check output exists
|
|
377
|
+
|
|
378
|
+
las = laspy.read(OUTPUT_FILE)
|
|
379
|
+
|
|
380
|
+
# Get all points with classification 68
|
|
381
|
+
class_68_points = las.points[las.classification == 68]
|
|
382
|
+
num_class_68_points = len(class_68_points)
|
|
383
|
+
|
|
384
|
+
# Print some information
|
|
385
|
+
print(f"Total points in file: {len(las.points)}")
|
|
386
|
+
print(f"Points with class 68: {num_class_68_points}")
|
|
387
|
+
|
|
388
|
+
# Verify we have some points with class 68
|
|
389
|
+
assert num_class_68_points > 0, "Expected to find points with class 68"
|
|
390
|
+
|
|
391
|
+
# Check for duplicate points (same X, Y, Z coordinates)
|
|
392
|
+
points_array = np.column_stack((class_68_points.x, class_68_points.y, class_68_points.z))
|
|
393
|
+
unique_points = np.unique(points_array, axis=0)
|
|
394
|
+
|
|
395
|
+
print(f"Number of unique points: {len(unique_points)}")
|
|
396
|
+
print(f"Number of total points: {len(points_array)}")
|
|
397
|
+
|
|
398
|
+
# Verify no duplicates
|
|
399
|
+
assert len(unique_points) == len(points_array), "Found duplicate points in class 68"
|
|
400
|
+
|
|
401
|
+
|
|
356
402
|
def test_parse_args():
|
|
357
403
|
# sanity check for arguments parsing
|
|
358
404
|
args = add_points_in_pointcloud.parse_args(
|
|
@@ -30,7 +30,7 @@ SOURCE_CLOUD = os.path.join(INPUT_DIR, "source_cloud_crop.laz")
|
|
|
30
30
|
|
|
31
31
|
# source may be a digital surface model
|
|
32
32
|
SOURCE_DSM = os.path.join(INPUT_DIR, "DSM.tif")
|
|
33
|
-
|
|
33
|
+
SOURCE_GROUND_MASK = os.path.join(INPUT_DIR, "ground_mask.tif")
|
|
34
34
|
SOURCE_CLASSIF = 68
|
|
35
35
|
|
|
36
36
|
TMP_EXTRA_DIMS = os.path.join(TMP_PATH, "input_with_extra_dims")
|
|
@@ -285,7 +285,7 @@ def test_replace_area_with_no_output_point_with_extra_dims():
|
|
|
285
285
|
def test_pipeline_read_from_DSM():
|
|
286
286
|
cloud_from_DSM = os.path.join(TMP_PATH, "las_from_DSM.laz")
|
|
287
287
|
|
|
288
|
-
pipeline = pipeline_read_from_DSM(dsm=SOURCE_DSM,
|
|
288
|
+
pipeline = pipeline_read_from_DSM(dsm=SOURCE_DSM, ground_mask=SOURCE_GROUND_MASK, classification=SOURCE_CLASSIF)
|
|
289
289
|
pipeline |= pdal.Writer.las(cloud_from_DSM, forward="all", extra_dims="all")
|
|
290
290
|
pipeline.execute()
|
|
291
291
|
|
|
@@ -326,9 +326,10 @@ def test_main_from_cloud_with_filter():
|
|
|
326
326
|
|
|
327
327
|
|
|
328
328
|
def test_main_from_DSM():
|
|
329
|
-
output_file = os.path.join(TMP_PATH, "
|
|
329
|
+
output_file = os.path.join(TMP_PATH, "main_from_DSM", "output_main_from_DSM.laz")
|
|
330
|
+
os.makedirs(os.path.dirname(output_file))
|
|
330
331
|
cmd = (
|
|
331
|
-
f"from_DSM -d {SOURCE_DSM} -g {
|
|
332
|
+
f"from_DSM -d {SOURCE_DSM} -g {SOURCE_GROUND_MASK} -c {SOURCE_CLASSIF} -t {TARGET_CLOUD} -r {REPLACE_AREA}"
|
|
332
333
|
f" -o {output_file}"
|
|
333
334
|
).split()
|
|
334
335
|
args = argument_parser().parse_args(cmd)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ign_pdal_tools-1.15.1 → ign_pdal_tools-1.15.3}/ign_pdal_tools.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|