ign-pdal-tools 1.15.4__tar.gz → 1.15.6__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.4 → ign_pdal_tools-1.15.6}/PKG-INFO +1 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/ign_pdal_tools.egg-info/PKG-INFO +1 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/_version.py +1 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/add_points_in_pointcloud.py +1 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/color.py +2 -2
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/create_random_laz.py +14 -18
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/download_image.py +1 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_add_buffer.py +3 -3
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_comparison.py +4 -3
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_rename_dimension.py +4 -2
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/replace_area_in_pointcloud.py +56 -5
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/standardize_format.py +42 -25
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/unlock_file.py +1 -2
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_add_points_in_pointcloud.py +48 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_create_random_laz.py +30 -17
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_comparison.py +6 -4
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_remove_dimensions.py +4 -4
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_rename_dimension.py +37 -36
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_pdal.py +4 -4
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_replace_area_in_pointcloud.py +19 -1
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_standardize_format.py +4 -3
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/LICENSE.md +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/README.md +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/ign_pdal_tools.egg-info/SOURCES.txt +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/ign_pdal_tools.egg-info/dependency_links.txt +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/ign_pdal_tools.egg-info/top_level.txt +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_clip.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_info.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_merge.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/las_remove_dimensions.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/pcd_info.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pdaltools/replace_attribute_in_las.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/pyproject.toml +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/setup.cfg +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_color.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_download_image.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_add_buffer.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_clip.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_info.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_las_merge.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_pcd_info.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_replace_attribute_in_las.py +0 -0
- {ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/test/test_unlock.py +0 -0
|
@@ -156,7 +156,7 @@ def add_points_to_las(
|
|
|
156
156
|
new_points.z = z_coords.astype(new_points.z.dtype)
|
|
157
157
|
new_points.classification = classes.astype(new_points.classification.dtype)
|
|
158
158
|
|
|
159
|
-
with tempfile.NamedTemporaryFile(suffix="_new_points.las") as tmp:
|
|
159
|
+
with tempfile.NamedTemporaryFile(suffix="_new_points.las", delete_on_close=False) as tmp:
|
|
160
160
|
with laspy.open(tmp.name, mode="w", header=header) as las_file:
|
|
161
161
|
las_file.write_points(new_points)
|
|
162
162
|
|
|
@@ -101,7 +101,7 @@ def color(
|
|
|
101
101
|
|
|
102
102
|
tmp_ortho = None
|
|
103
103
|
if color_rvb_enabled:
|
|
104
|
-
tmp_ortho = tempfile.NamedTemporaryFile(suffix="_rvb.tif")
|
|
104
|
+
tmp_ortho = tempfile.NamedTemporaryFile(suffix="_rvb.tif", delete_on_close=False)
|
|
105
105
|
download_image(
|
|
106
106
|
proj,
|
|
107
107
|
stream_RGB,
|
|
@@ -124,7 +124,7 @@ def color(
|
|
|
124
124
|
|
|
125
125
|
tmp_ortho_irc = None
|
|
126
126
|
if color_ir_enabled:
|
|
127
|
-
tmp_ortho_irc = tempfile.NamedTemporaryFile(suffix="_irc.tif")
|
|
127
|
+
tmp_ortho_irc = tempfile.NamedTemporaryFile(suffix="_irc.tif", delete_on_close=False)
|
|
128
128
|
download_image(
|
|
129
129
|
proj,
|
|
130
130
|
stream_IRC,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import laspy
|
|
3
|
-
from pathlib import Path
|
|
4
1
|
import argparse
|
|
5
|
-
from
|
|
2
|
+
from pathlib import Path
|
|
6
3
|
from typing import List, Tuple
|
|
7
4
|
|
|
5
|
+
import laspy
|
|
6
|
+
import numpy as np
|
|
7
|
+
from pyproj import CRS
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
def create_random_laz(
|
|
10
11
|
output_file: str,
|
|
@@ -27,7 +28,7 @@ def create_random_laz(
|
|
|
27
28
|
(default: (650000, 6810000) ; around Paris)
|
|
28
29
|
extra_dims: List of tuples (dimension_name, dimension_type) where type can be:
|
|
29
30
|
'float32', 'float64', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'
|
|
30
|
-
classifications: Optional list of classification values.
|
|
31
|
+
classifications: Optional list of classification values.
|
|
31
32
|
"""
|
|
32
33
|
|
|
33
34
|
# Create a new point cloud
|
|
@@ -103,7 +104,6 @@ def create_random_laz(
|
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
def test_output_file(result: dict, output_file: str):
|
|
106
|
-
|
|
107
107
|
# Validate output file path
|
|
108
108
|
output_path = Path(output_file)
|
|
109
109
|
if not output_path.exists():
|
|
@@ -126,12 +126,14 @@ def parse_args():
|
|
|
126
126
|
)
|
|
127
127
|
parser.add_argument("--crs", type=int, default=2154, help="Projection code")
|
|
128
128
|
parser.add_argument(
|
|
129
|
-
"--center",
|
|
130
|
-
|
|
129
|
+
"--center",
|
|
130
|
+
type=float,
|
|
131
|
+
nargs=2,
|
|
132
|
+
default=[650000.0, 6810000.0],
|
|
133
|
+
help="Center coordinates (x y) of the area to generate points in (space-separated)",
|
|
131
134
|
)
|
|
132
135
|
parser.add_argument(
|
|
133
|
-
"--classifications", type=int, nargs=
|
|
134
|
-
help="List of classification values (space-separated)"
|
|
136
|
+
"--classifications", type=int, nargs="+", help="List of classification values (space-separated)"
|
|
135
137
|
)
|
|
136
138
|
return parser.parse_args()
|
|
137
139
|
|
|
@@ -145,19 +147,13 @@ def main():
|
|
|
145
147
|
|
|
146
148
|
# Parse center
|
|
147
149
|
center = tuple(args.center[:2]) # Only take first 2 values if more are provided
|
|
148
|
-
|
|
150
|
+
|
|
149
151
|
# Parse classifications if provided
|
|
150
152
|
classifications = args.classifications
|
|
151
153
|
|
|
152
154
|
# Call create_random_laz
|
|
153
155
|
result = create_random_laz(
|
|
154
|
-
args.output_file,
|
|
155
|
-
args.point_format,
|
|
156
|
-
args.num_points,
|
|
157
|
-
args.crs,
|
|
158
|
-
center,
|
|
159
|
-
extra_dims,
|
|
160
|
-
classifications
|
|
156
|
+
args.output_file, args.point_format, args.num_points, args.crs, center, extra_dims, classifications
|
|
161
157
|
)
|
|
162
158
|
|
|
163
159
|
# Test output file
|
|
@@ -185,7 +185,7 @@ def download_image(proj, layer, minx, miny, maxx, maxy, pixel_per_meter, outfile
|
|
|
185
185
|
tmp_gpg_ortho.append(cells_ortho_paths)
|
|
186
186
|
|
|
187
187
|
# merge the cells
|
|
188
|
-
with tempfile.NamedTemporaryFile(suffix="_gpf.vrt") as tmp_vrt:
|
|
188
|
+
with tempfile.NamedTemporaryFile(suffix="_gpf.vrt", delete_on_close=False) as tmp_vrt:
|
|
189
189
|
gdal.BuildVRT(tmp_vrt.name, tmp_gpg_ortho)
|
|
190
190
|
gdal.Translate(outfile, tmp_vrt.name)
|
|
191
191
|
|
|
@@ -153,7 +153,7 @@ def remove_points_from_buffer(input_file: str, output_file: str):
|
|
|
153
153
|
input_file (str): path to the input file containing the "is_in_original" dimension
|
|
154
154
|
output_file (str): path to the output_file
|
|
155
155
|
"""
|
|
156
|
-
with tempfile.NamedTemporaryFile(suffix="_with_additional_dim.las") as tmp_las:
|
|
156
|
+
with tempfile.NamedTemporaryFile(suffix="_with_additional_dim.las", delete_on_close=False) as tmp_las:
|
|
157
157
|
pipeline = pdal.Pipeline() | pdal.Reader.las(input_file)
|
|
158
158
|
pipeline |= pdal.Filter.range(limits=f"{ORIGINAL_TILE_TAG}[1:1]")
|
|
159
159
|
pipeline |= pdal.Writer.las(filename=tmp_las.name, forward="all", extra_dims="all")
|
|
@@ -217,8 +217,8 @@ def run_on_buffered_las(
|
|
|
217
217
|
)
|
|
218
218
|
|
|
219
219
|
with (
|
|
220
|
-
tempfile.NamedTemporaryFile(suffix="_buffered_input.laz", dir=".") as buf_in,
|
|
221
|
-
tempfile.NamedTemporaryFile(suffix="_buffered_output.laz", dir=".") as buf_out,
|
|
220
|
+
tempfile.NamedTemporaryFile(suffix="_buffered_input.laz", dir=".", delete_on_close=False) as buf_in,
|
|
221
|
+
tempfile.NamedTemporaryFile(suffix="_buffered_output.laz", dir=".", delete_on_close=False) as buf_out,
|
|
222
222
|
):
|
|
223
223
|
create_las_with_buffer(
|
|
224
224
|
Path(input_file).parent,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import laspy
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import numpy as np
|
|
4
1
|
import argparse
|
|
2
|
+
from pathlib import Path
|
|
5
3
|
from typing import Tuple
|
|
6
4
|
|
|
5
|
+
import laspy
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
def compare_las_dimensions(file1: Path, file2: Path, dimensions: list = None) -> Tuple[bool, int, float]:
|
|
9
10
|
"""
|
|
@@ -6,11 +6,13 @@ This script allows renaming dimensions in a LAS file while preserving all other
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
-
import pdal
|
|
10
9
|
import sys
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
import pdal
|
|
13
|
+
|
|
13
14
|
from pdaltools.las_info import las_info_metadata
|
|
15
|
+
from pdaltools.las_remove_dimensions import remove_dimensions_from_points
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def rename_dimension(input_file: str, output_file: str, old_dims: list[str], new_dims: list[str]):
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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__":
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import argparse
|
|
12
|
+
import os
|
|
12
13
|
import tempfile
|
|
13
14
|
from typing import Dict, List
|
|
14
15
|
|
|
15
16
|
import pdal
|
|
16
17
|
|
|
17
|
-
from pdaltools.unlock_file import copy_and_hack_decorator
|
|
18
18
|
from pdaltools.las_rename_dimension import rename_dimension
|
|
19
|
+
from pdaltools.unlock_file import copy_and_hack_decorator
|
|
19
20
|
|
|
20
21
|
# Standard parameters to pass to the pdal writer
|
|
21
22
|
STANDARD_PARAMETERS = dict(
|
|
@@ -76,34 +77,50 @@ def get_writer_parameters(new_parameters: Dict) -> Dict:
|
|
|
76
77
|
params = STANDARD_PARAMETERS | new_parameters
|
|
77
78
|
return params
|
|
78
79
|
|
|
80
|
+
|
|
79
81
|
@copy_and_hack_decorator
|
|
80
82
|
def standardize(
|
|
81
|
-
input_file: str,
|
|
83
|
+
input_file: str,
|
|
84
|
+
output_file: str,
|
|
85
|
+
params_from_parser: Dict,
|
|
86
|
+
classes_to_remove: List = [],
|
|
87
|
+
rename_dims: List = []
|
|
82
88
|
) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Standardize a LAS/LAZ file with improved error handling and resource management.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
input_file: Input file path
|
|
94
|
+
output_file: Output file path
|
|
95
|
+
params_from_parser: Parameters for the PDAL writer
|
|
96
|
+
classes_to_remove: List of classification classes to remove
|
|
97
|
+
rename_dims: List of dimension names to rename (pairs of old_name, new_name)
|
|
98
|
+
"""
|
|
83
99
|
params = get_writer_parameters(params_from_parser)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
tmp_file_name = None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Create temporary file for dimension renaming if needed
|
|
104
|
+
if rename_dims:
|
|
105
|
+
with tempfile.NamedTemporaryFile(suffix=".laz", delete=False) as tmp_file:
|
|
106
|
+
tmp_file_name = tmp_file.name
|
|
107
|
+
old_dims = rename_dims[::2]
|
|
108
|
+
new_dims = rename_dims[1::2]
|
|
109
|
+
rename_dimension(input_file, tmp_file_name, old_dims, new_dims)
|
|
110
|
+
input_file = tmp_file_name
|
|
111
|
+
|
|
112
|
+
pipeline = pdal.Pipeline()
|
|
113
|
+
pipeline |= pdal.Reader.las(input_file)
|
|
114
|
+
if classes_to_remove:
|
|
115
|
+
expression = "&&".join([f"Classification != {c}" for c in classes_to_remove])
|
|
116
|
+
pipeline |= pdal.Filter.expression(expression=expression)
|
|
117
|
+
pipeline |= pdal.Writer(filename=output_file, forward="all", **params)
|
|
118
|
+
pipeline.execute()
|
|
119
|
+
|
|
120
|
+
finally:
|
|
121
|
+
# Clean up temporary file
|
|
122
|
+
if tmp_file_name and os.path.exists(tmp_file_name):
|
|
123
|
+
os.remove(tmp_file_name)
|
|
107
124
|
|
|
108
125
|
|
|
109
126
|
def main():
|
|
@@ -39,10 +39,9 @@ def copy_and_hack_decorator(func):
|
|
|
39
39
|
if "readers.las: Global encoding WKT flag not set for point format 6 - 10." in str(e):
|
|
40
40
|
args = list(args)
|
|
41
41
|
in_file = args[0]
|
|
42
|
-
with tempfile.NamedTemporaryFile(suffix=os.path.splitext(in_file)[-1]) as tmp:
|
|
42
|
+
with tempfile.NamedTemporaryFile(suffix=os.path.splitext(in_file)[-1], delete_on_close=False) as tmp:
|
|
43
43
|
copy_and_hack_input_file(in_file, tmp.name)
|
|
44
44
|
args[0] = tmp.name
|
|
45
|
-
|
|
46
45
|
return func(*args, **kwargs)
|
|
47
46
|
|
|
48
47
|
else:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import os
|
|
3
|
+
import tempfile
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import geopandas as gpd
|
|
6
7
|
import laspy
|
|
7
8
|
import numpy as np
|
|
9
|
+
import pdal
|
|
8
10
|
import pytest
|
|
9
11
|
from shapely.geometry import LineString, MultiPoint, Point
|
|
10
12
|
|
|
@@ -424,3 +426,49 @@ def test_parse_args():
|
|
|
424
426
|
parsed_args_keys = args.__dict__.keys()
|
|
425
427
|
main_parameters = inspect.signature(add_points_in_pointcloud.add_points_from_geometry_to_las).parameters.keys()
|
|
426
428
|
assert set(parsed_args_keys) == set(main_parameters)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def test_namedtemporaryfile_delete_on_close_false():
|
|
432
|
+
"""Test that NamedTemporaryFile could be used on windows in the context with delete_on_close=False"""
|
|
433
|
+
temp_file_path = None
|
|
434
|
+
|
|
435
|
+
with tempfile.NamedTemporaryFile(suffix="_test.las", delete_on_close=False) as tmp:
|
|
436
|
+
temp_file_path = tmp.name
|
|
437
|
+
# Verify that the file exists during the context
|
|
438
|
+
assert os.path.exists(temp_file_path)
|
|
439
|
+
|
|
440
|
+
# Write some data to the temporary file
|
|
441
|
+
with open(temp_file_path, "w") as f:
|
|
442
|
+
f.write("test data")
|
|
443
|
+
f.close()
|
|
444
|
+
|
|
445
|
+
# Verify that the file still exists after exiting the context
|
|
446
|
+
assert not os.path.exists(
|
|
447
|
+
temp_file_path
|
|
448
|
+
), f"Temporary file {temp_file_path} should not exist oustside the context with delete_on_close=False"
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def test_namedtemporaryfile_delete_false_with_pdal():
|
|
452
|
+
"""Test that NamedTemporaryFile could be used on windows in the context
|
|
453
|
+
with delete_on_close=False and some pdal operations"""
|
|
454
|
+
|
|
455
|
+
def read_las(input_las, tmp):
|
|
456
|
+
pipeline = pdal.Pipeline()
|
|
457
|
+
pipeline |= pdal.Reader.las(filename=input_las)
|
|
458
|
+
pipeline |= pdal.Writer.las(filename=tmp.name, forward="all", extra_dims="all")
|
|
459
|
+
pipeline.execute()
|
|
460
|
+
|
|
461
|
+
input_las = os.path.join(TEST_PATH, "data/crop_duplicate.laz")
|
|
462
|
+
temp_file_path = None
|
|
463
|
+
with tempfile.NamedTemporaryFile(suffix="_test.las", delete_on_close=False) as tmp:
|
|
464
|
+
temp_file_path = tmp.name
|
|
465
|
+
read_las(input_las, tmp)
|
|
466
|
+
pipeline = pdal.Pipeline()
|
|
467
|
+
pipeline |= pdal.Reader.las(filename=tmp.name)
|
|
468
|
+
pipeline.execute()
|
|
469
|
+
assert os.path.exists(tmp.name)
|
|
470
|
+
|
|
471
|
+
# Verify that the file still exists after exiting the context
|
|
472
|
+
assert not os.path.exists(
|
|
473
|
+
temp_file_path
|
|
474
|
+
), f"Temporary file {temp_file_path} should not exist oustside the context with delete_on_close=False"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import pytest
|
|
3
|
-
import numpy as np
|
|
4
|
-
import laspy
|
|
5
2
|
import sys
|
|
6
3
|
|
|
4
|
+
import laspy
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
7
8
|
from pdaltools.create_random_laz import create_random_laz, main
|
|
8
9
|
|
|
9
10
|
TEST_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -138,13 +139,14 @@ def test_create_random_laz_data_ranges():
|
|
|
138
139
|
assert np.all(las.uint_dim >= 0)
|
|
139
140
|
assert np.all(las.uint_dim <= 100)
|
|
140
141
|
|
|
142
|
+
|
|
141
143
|
@pytest.mark.parametrize(
|
|
142
144
|
"classifications",
|
|
143
145
|
[
|
|
144
146
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
145
147
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
|
146
|
-
[1, 2, 3, 66, 68]
|
|
147
|
-
]
|
|
148
|
+
[1, 2, 3, 66, 68],
|
|
149
|
+
],
|
|
148
150
|
)
|
|
149
151
|
def test_create_random_laz_classifications(classifications):
|
|
150
152
|
"""Test that generated data is within expected ranges for different types"""
|
|
@@ -158,18 +160,18 @@ def test_create_random_laz_classifications(classifications):
|
|
|
158
160
|
|
|
159
161
|
with laspy.open(output_file) as las_file:
|
|
160
162
|
las = las_file.read()
|
|
161
|
-
|
|
163
|
+
|
|
162
164
|
# Convert to set for faster lookups
|
|
163
165
|
valid_classifications = set(classifications)
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
# Check that all classification values are in the provided list
|
|
166
168
|
unique_classes = set(np.unique(las.classification))
|
|
167
|
-
assert unique_classes.issubset(
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
assert unique_classes.issubset(
|
|
170
|
+
valid_classifications
|
|
171
|
+
), f"Found unexpected classification values: {unique_classes - valid_classifications}"
|
|
172
|
+
|
|
170
173
|
# Also check that we have the expected number of points
|
|
171
|
-
assert len(las.classification) == 1000,
|
|
172
|
-
f"Expected 1000 points, got {len(las.classification)}"
|
|
174
|
+
assert len(las.classification) == 1000, f"Expected 1000 points, got {len(las.classification)}"
|
|
173
175
|
|
|
174
176
|
|
|
175
177
|
def test_main():
|
|
@@ -191,11 +193,21 @@ def test_main():
|
|
|
191
193
|
"--crs",
|
|
192
194
|
"2154",
|
|
193
195
|
"--center",
|
|
194
|
-
"650000",
|
|
196
|
+
"650000",
|
|
197
|
+
"6810000",
|
|
195
198
|
"--extra_dims",
|
|
196
199
|
"height:float32",
|
|
197
200
|
"--classifications",
|
|
198
|
-
"1",
|
|
201
|
+
"1",
|
|
202
|
+
"2",
|
|
203
|
+
"3",
|
|
204
|
+
"4",
|
|
205
|
+
"5",
|
|
206
|
+
"6",
|
|
207
|
+
"7",
|
|
208
|
+
"8",
|
|
209
|
+
"9",
|
|
210
|
+
"10",
|
|
199
211
|
]
|
|
200
212
|
|
|
201
213
|
# Run main function
|
|
@@ -209,12 +221,13 @@ def test_main():
|
|
|
209
221
|
las = las_file.read()
|
|
210
222
|
assert len(las.points) == 50
|
|
211
223
|
assert "height" in las.point_format.dimension_names
|
|
212
|
-
|
|
224
|
+
|
|
213
225
|
# Verify classifications are within the provided range
|
|
214
226
|
unique_classes = set(np.unique(las.classification))
|
|
215
227
|
expected_classes = set(range(1, 11)) # 1-10
|
|
216
|
-
assert unique_classes.issubset(
|
|
217
|
-
|
|
228
|
+
assert unique_classes.issubset(
|
|
229
|
+
expected_classes
|
|
230
|
+
), f"Found unexpected classification values: {unique_classes - expected_classes}"
|
|
218
231
|
|
|
219
232
|
finally:
|
|
220
233
|
# Restore original sys.argv
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import pytest
|
|
2
1
|
import tempfile
|
|
3
|
-
import numpy as np
|
|
4
2
|
from pathlib import Path
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
5
|
import laspy
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
6
9
|
from pdaltools.las_comparison import compare_las_dimensions, main
|
|
7
|
-
from typing import Tuple
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def create_test_las_file(x: np.ndarray, y: np.ndarray, z: np.ndarray, dimensions: dict = None) -> Path:
|
|
@@ -236,8 +238,8 @@ def test_single_point():
|
|
|
236
238
|
def test_main_function():
|
|
237
239
|
"""Test the main function with direct sys.argv"""
|
|
238
240
|
import sys
|
|
239
|
-
from io import StringIO
|
|
240
241
|
from contextlib import redirect_stdout
|
|
242
|
+
from io import StringIO
|
|
241
243
|
|
|
242
244
|
# Test with identical files
|
|
243
245
|
points = 100
|
|
@@ -37,9 +37,9 @@ def test_remove_all_dimension():
|
|
|
37
37
|
# get initial data
|
|
38
38
|
points_ini = get_points(ini_las)
|
|
39
39
|
|
|
40
|
-
with tempfile.NamedTemporaryFile(suffix="_add.las") as tmp_las:
|
|
40
|
+
with tempfile.NamedTemporaryFile(suffix="_add.las", delete_on_close=False) as tmp_las:
|
|
41
41
|
append_dimension(ini_las, tmp_las.name)
|
|
42
|
-
with tempfile.NamedTemporaryFile(suffix="_rm.las") as tmp_las_rm:
|
|
42
|
+
with tempfile.NamedTemporaryFile(suffix="_rm.las", delete_on_close=False) as tmp_las_rm:
|
|
43
43
|
# remove all dimensions
|
|
44
44
|
las_remove_dimensions.remove_dimensions_from_las(tmp_las.name, added_dimensions, tmp_las_rm.name)
|
|
45
45
|
points_end = get_points(tmp_las_rm.name)
|
|
@@ -50,9 +50,9 @@ def test_remove_one_dimension():
|
|
|
50
50
|
# get initial data
|
|
51
51
|
points_ini = get_points(ini_las)
|
|
52
52
|
|
|
53
|
-
with tempfile.NamedTemporaryFile(suffix="_add.las") as tmp_las:
|
|
53
|
+
with tempfile.NamedTemporaryFile(suffix="_add.las", delete_on_close=False) as tmp_las:
|
|
54
54
|
append_dimension(ini_las, tmp_las.name)
|
|
55
|
-
with tempfile.NamedTemporaryFile(suffix="_rm.las") as tmp_las_rm:
|
|
55
|
+
with tempfile.NamedTemporaryFile(suffix="_rm.las", delete_on_close=False) as tmp_las_rm:
|
|
56
56
|
# remove one dimension
|
|
57
57
|
las_remove_dimensions.remove_dimensions_from_las(tmp_las.name, ["DIM_1"], tmp_las_rm.name)
|
|
58
58
|
points_end = get_points(tmp_las_rm.name)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
|
-
import
|
|
3
|
+
import sys
|
|
3
4
|
import tempfile
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
import laspy
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
from pdaltools.las_rename_dimension import rename_dimension, main
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
9
|
from pyproj import CRS
|
|
10
10
|
|
|
11
|
+
from pdaltools.las_rename_dimension import main, rename_dimension
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def create_test_las_file():
|
|
13
15
|
"""Create a temporary LAS file with test data."""
|
|
@@ -72,24 +74,24 @@ def test_rename_dimension():
|
|
|
72
74
|
def test_rename_nonexistent_dimension(caplog):
|
|
73
75
|
"""Test attempting to rename a dimension that doesn't exist."""
|
|
74
76
|
input_file = create_test_las_file()
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
with tempfile.NamedTemporaryFile(suffix=".las", delete=False) as tmp_file:
|
|
77
79
|
output_file = tmp_file.name
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Clear any existing log records
|
|
83
|
+
caplog.clear()
|
|
84
|
+
|
|
85
|
+
# Set the logging level to capture WARNING
|
|
86
|
+
with caplog.at_level(logging.WARNING):
|
|
87
|
+
rename_dimension(input_file, output_file, ["nonexistent_dim"], ["new_dim"])
|
|
88
|
+
|
|
89
|
+
# Check that the warning was logged
|
|
90
|
+
assert len(caplog.records) == 1
|
|
91
|
+
assert "Dimension nonexistent_dim not found in input file" in caplog.records[0].message
|
|
92
|
+
finally:
|
|
93
|
+
os.unlink(input_file)
|
|
94
|
+
os.unlink(output_file)
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def test_rename_to_existing_dimension():
|
|
@@ -113,22 +115,21 @@ def test_rename_dimension_case_sensitive(caplog):
|
|
|
113
115
|
|
|
114
116
|
with tempfile.NamedTemporaryFile(suffix=".las", delete=False) as tmp_file:
|
|
115
117
|
output_file = tmp_file.name
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
# Clear any existing log records
|
|
119
|
-
caplog.clear()
|
|
120
|
-
|
|
121
|
-
# Set the logging level to capture WARNING
|
|
122
|
-
with caplog.at_level(logging.WARNING):
|
|
123
|
-
rename_dimension(input_file, output_file, ["TEST_DIM"], ["new_dim"])
|
|
124
|
-
|
|
125
|
-
# Check that the warning was logged
|
|
126
|
-
assert len(caplog.records) == 1
|
|
127
|
-
assert "Dimension TEST_DIM not found in input file" in caplog.records[0].message
|
|
128
|
-
finally:
|
|
129
|
-
os.unlink(input_file)
|
|
130
|
-
os.unlink(output_file)
|
|
131
118
|
|
|
119
|
+
try:
|
|
120
|
+
# Clear any existing log records
|
|
121
|
+
caplog.clear()
|
|
122
|
+
|
|
123
|
+
# Set the logging level to capture WARNING
|
|
124
|
+
with caplog.at_level(logging.WARNING):
|
|
125
|
+
rename_dimension(input_file, output_file, ["TEST_DIM"], ["new_dim"])
|
|
126
|
+
|
|
127
|
+
# Check that the warning was logged
|
|
128
|
+
assert len(caplog.records) == 1
|
|
129
|
+
assert "Dimension TEST_DIM not found in input file" in caplog.records[0].message
|
|
130
|
+
finally:
|
|
131
|
+
os.unlink(input_file)
|
|
132
|
+
os.unlink(output_file)
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
def test_rename_dimension_main():
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
import pdal
|
|
4
|
-
import pytest
|
|
5
4
|
|
|
6
5
|
TEST_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
7
6
|
|
|
8
|
-
#this test files concatenate somes tests on PDAL features
|
|
9
|
-
#it allows us to test the PDAL version used in the library is modern enough
|
|
7
|
+
# this test files concatenate somes tests on PDAL features
|
|
8
|
+
# it allows us to test the PDAL version used in the library is modern enough
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
def test_pdal_read_severals_extra_dims():
|
|
12
|
-
# test that we can read a las file with several extra dims
|
|
12
|
+
# test that we can read a las file with several extra dims
|
|
13
13
|
test_file = os.path.join(TEST_PATH, "data/las_with_several_extra_byte_bloc.laz")
|
|
14
14
|
|
|
15
15
|
pipeline = pdal.Reader.las(filename=test_file).pipeline()
|
|
@@ -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
|
|
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}
|
|
@@ -3,17 +3,18 @@ import os
|
|
|
3
3
|
import platform
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess as sp
|
|
6
|
+
import sys
|
|
6
7
|
from test.utils import EXPECTED_DIMS_BY_DATAFORMAT, get_pdal_infos_summary
|
|
8
|
+
|
|
7
9
|
import laspy
|
|
8
10
|
import pdal
|
|
9
11
|
import pytest
|
|
10
|
-
import sys
|
|
11
12
|
|
|
12
13
|
from pdaltools.count_occurences.count_occurences_for_attribute import (
|
|
13
14
|
compute_count_one_file,
|
|
14
15
|
)
|
|
15
|
-
from pdaltools.standardize_format import standardize, main
|
|
16
16
|
from pdaltools.las_comparison import compare_las_dimensions
|
|
17
|
+
from pdaltools.standardize_format import main, standardize
|
|
17
18
|
|
|
18
19
|
TEST_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
19
20
|
TMP_PATH = os.path.join(TEST_PATH, "tmp")
|
|
@@ -277,7 +278,7 @@ def test_standardize_with_extra_dims_origin_and_dxm_marker():
|
|
|
277
278
|
|
|
278
279
|
# check output file is same as input file for origin and dxm_marker
|
|
279
280
|
result, _, _ = compare_las_dimensions(input_file, output_file, ["origin", "dsm_marker", "dtm_marker"])
|
|
280
|
-
assert result
|
|
281
|
+
assert result is True
|
|
281
282
|
|
|
282
283
|
|
|
283
284
|
def test_main_with_rename_dimensions():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ign_pdal_tools-1.15.4 → ign_pdal_tools-1.15.6}/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
|