ign-pdal-tools 1.15.4__py3-none-any.whl → 1.15.6__py3-none-any.whl

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.
@@ -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.6
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
@@ -0,0 +1,22 @@
1
+ ign_pdal_tools-1.15.6.dist-info/licenses/LICENSE.md,sha256=iVzCFZTUXeiqP8bP474iuWZiWO_kDCD4SPh1Wiw125Y,1120
2
+ pdaltools/_version.py,sha256=rbwL37ozKUuMooO5Jv8RL7L5YP6krgkxEC3KNUvyY6c,75
3
+ pdaltools/add_points_in_pointcloud.py,sha256=lDxePBBRTKSEKC3BgrVLWUEMN8vCM4xNgsJfbIz4GRw,12988
4
+ pdaltools/color.py,sha256=yVSP1IjFsV5yAXYudaD76a8BM0hZAmMLMzjUjd7CNS0,9595
5
+ pdaltools/create_random_laz.py,sha256=w0P4e3-bzaiKl_osmyFdOcKODRUadU7G4ez9fLvCDrs,6028
6
+ pdaltools/download_image.py,sha256=RgR9iHbw1kr1rb5aumbNwNyLk3BgM86EV3MssXiVaGU,7869
7
+ pdaltools/las_add_buffer.py,sha256=WXUkMSRX8T-Xj5il9F_uv7uKSqwUmv5P3AUcHXQVQDE,11343
8
+ pdaltools/las_clip.py,sha256=GvEOYu8RXV68e35kU8i42GwSkbo4P9TvmS6rkrdPmFM,1034
9
+ pdaltools/las_comparison.py,sha256=AsmbdH8HF61lWHroZkez5x1h7B681RLWWZCHibDc3M4,4506
10
+ pdaltools/las_info.py,sha256=xZlTsdLS3I9_xeqGJyOOpJNJrqF82JBhlMhtYabOuw0,9845
11
+ pdaltools/las_merge.py,sha256=tcFVueV9X9nNEaoAl5zCduY5DETlBg63MAgP2SuKiNo,4121
12
+ pdaltools/las_remove_dimensions.py,sha256=f8imGhN6LNTuQ1GMJQRzIIV3Wab_oRPOyEnKi1CgfiM,2318
13
+ pdaltools/las_rename_dimension.py,sha256=AWYx0Jd5YHWng-CY2yIV8iRTR_bMxhvwGz1MO5sYTWc,2889
14
+ pdaltools/pcd_info.py,sha256=NIAH5KGikVDQLlbCcw9FuaPqe20UZvRfkHsDZd5kmZA,3210
15
+ pdaltools/replace_area_in_pointcloud.py,sha256=V8aFRSxrqJtsUufoA_5g9ysPl-Wp17_TXjezvSERdmU,10537
16
+ pdaltools/replace_attribute_in_las.py,sha256=MHpIizSupgWtbizteoRH8FKDE049hrAh4v_OhmRmSPU,4318
17
+ pdaltools/standardize_format.py,sha256=-ukrz5gY0mq071fN7EXbB9ANS44IEmgpKQrrjzOnqhE,4455
18
+ pdaltools/unlock_file.py,sha256=3BplGrcKJ7lpPj1lHTG4ODeuGDXjmeoMeSl3q2Qn2XA,1980
19
+ ign_pdal_tools-1.15.6.dist-info/METADATA,sha256=DRXg0EtK94BnLSbhA-7okBqdGRZbSPcVhC2HSNtLIh4,6146
20
+ ign_pdal_tools-1.15.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ ign_pdal_tools-1.15.6.dist-info/top_level.txt,sha256=KvGW0ZzqQbhCKzB5_Tp_buWMZyIgiO2M2krWF_ecOZc,10
22
+ ign_pdal_tools-1.15.6.dist-info/RECORD,,
pdaltools/_version.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "1.15.4"
1
+ __version__ = "1.15.6"
2
2
 
3
3
 
4
4
  if __name__ == "__main__":
@@ -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
 
pdaltools/color.py CHANGED
@@ -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 pyproj import CRS
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", type=float, nargs=2, default=[650000.0, 6810000.0],
130
- help="Center coordinates (x y) of the area to generate points in (space-separated)"
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
- from pdaltools.las_remove_dimensions import remove_dimensions_from_points
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 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__":
@@ -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, output_file: str, params_from_parser: Dict, classes_to_remove: List = [], rename_dims: List = []
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
- # Create temporary file for dimension renaming if needed
86
- if rename_dims:
87
- with tempfile.NamedTemporaryFile(suffix=".laz", delete=False) as tmp_file:
88
- tmp_file_name = tmp_file.name
89
-
90
- # Rename dimensions
91
- old_dims = rename_dims[::2]
92
- new_dims = rename_dims[1::2]
93
- rename_dimension(input_file, tmp_file_name, old_dims, new_dims)
94
-
95
- # Use renamed file as input
96
- input_file = tmp_file_name
97
- else:
98
- tmp_file_name = input_file
99
-
100
- pipeline = pdal.Pipeline()
101
- pipeline |= pdal.Reader.las(tmp_file_name)
102
- if classes_to_remove:
103
- expression = "&&".join([f"Classification != {c}" for c in classes_to_remove])
104
- pipeline |= pdal.Filter.expression(expression=expression)
105
- pipeline |= pdal.Writer(filename=output_file, forward="all", **params)
106
- pipeline.execute()
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():
pdaltools/unlock_file.py CHANGED
@@ -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,22 +0,0 @@
1
- ign_pdal_tools-1.15.4.dist-info/licenses/LICENSE.md,sha256=iVzCFZTUXeiqP8bP474iuWZiWO_kDCD4SPh1Wiw125Y,1120
2
- pdaltools/_version.py,sha256=v1-P-Ov-24Bdx7-zaJ7QUAVXDpuCxPkaM1l4VOXLOSA,75
3
- pdaltools/add_points_in_pointcloud.py,sha256=UJTwoOjC0WKnp_ynNHpwUh1fmbIgw7hq5xoNN8_FxQQ,12965
4
- pdaltools/color.py,sha256=s-_rmLK6fIK3UwkUzHVZPEkm6r1LliG5ftGr-jkqyjM,9549
5
- pdaltools/create_random_laz.py,sha256=XuHH4G8Nrs8DB-F8bkcIeto7JtmrlrNGF_R66oxGCbQ,6069
6
- pdaltools/download_image.py,sha256=DG9PunQsjw7Uyyf4YMVp8LMH0G3Uo4cahx5EZbdi3so,7846
7
- pdaltools/las_add_buffer.py,sha256=rnFExAfi0KqlQpL4hDMh2aC08AcYdSHSB6WPG5RyFIc,11274
8
- pdaltools/las_clip.py,sha256=GvEOYu8RXV68e35kU8i42GwSkbo4P9TvmS6rkrdPmFM,1034
9
- pdaltools/las_comparison.py,sha256=B9hFGbmD0x4JEN4oHbiQFNbd0T-9P3mnAN67Czu0pZk,4505
10
- pdaltools/las_info.py,sha256=xZlTsdLS3I9_xeqGJyOOpJNJrqF82JBhlMhtYabOuw0,9845
11
- pdaltools/las_merge.py,sha256=tcFVueV9X9nNEaoAl5zCduY5DETlBg63MAgP2SuKiNo,4121
12
- pdaltools/las_remove_dimensions.py,sha256=f8imGhN6LNTuQ1GMJQRzIIV3Wab_oRPOyEnKi1CgfiM,2318
13
- pdaltools/las_rename_dimension.py,sha256=FEWIcq0ZZiv9xWbCLDRE9Hzb5K0YYfoi3Z8IZFEs-uU,2887
14
- pdaltools/pcd_info.py,sha256=NIAH5KGikVDQLlbCcw9FuaPqe20UZvRfkHsDZd5kmZA,3210
15
- pdaltools/replace_area_in_pointcloud.py,sha256=4JyWWDtPUqtzE3zMh7eHwjGAupLclvjxKg8ScgIC4-4,8714
16
- pdaltools/replace_attribute_in_las.py,sha256=MHpIizSupgWtbizteoRH8FKDE049hrAh4v_OhmRmSPU,4318
17
- pdaltools/standardize_format.py,sha256=I2oNiwhSMtr4e5ZK9qbB_yKmy3twOoO6QLiSFu4_AaI,3905
18
- pdaltools/unlock_file.py,sha256=G2odk0cpp_X9r49Y90oK88v3qlihaMfg6acwmWqblik,1958
19
- ign_pdal_tools-1.15.4.dist-info/METADATA,sha256=kBr1Y0CwOciH9Gjw4HxpYiYy5eQpcHqbSxQ_zXnZr3M,6146
20
- ign_pdal_tools-1.15.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- ign_pdal_tools-1.15.4.dist-info/top_level.txt,sha256=KvGW0ZzqQbhCKzB5_Tp_buWMZyIgiO2M2krWF_ecOZc,10
22
- ign_pdal_tools-1.15.4.dist-info/RECORD,,