Base LiDAR Module
codebase.lidar_base
1from codebase.py_helpers.basic_helpers import BasicHelpers 2from codebase.py_helpers.basic_lookups import ColourLookups 3from pathlib import Path 4 5from laspy import read as las_read, LasData 6from numpy import where 7from typing import Any 8from tomllib import load 9from os import walk 10from subprocess import run 11 12""" 13Open3D Requirements: 14- libegl1 15- libgl1 16- libgomp1 17- python3-pip 18 19 20""" 21 22 23class BaseLiDAR: 24 25 def __init__(self): 26 self.controls_fp: Path = Path(Path.cwd(), 'controls.toml') 27 28 @staticmethod 29 def _list_files(directory: Path = None) -> list[Any]: 30 """ 31 Fast method for producing a list of files in a Directory. 32 TODO add to helpers instead 33 :param directory: Directory Path 34 :return: list[Any] 35 """ 36 ff = [] 37 if directory is not None: 38 for (dirpath, dirnames, filenames) in walk(Path(directory)): 39 ff.extend(filenames) 40 break 41 else: 42 for (dirpath, dirnames, filenames) in walk(Path.cwd()): 43 ff.extend(filenames) 44 break 45 return ff 46 47 @staticmethod 48 def _las_file_split(filepath: Path) -> tuple[str, str, str]: 49 """ 50 Checks that a given filepath exists, and splits the filepath into three strings, as such: 51 Path to Directory, Filename (stem), File Extension 52 53 The reason for this logic is that las files may come with extensions such as: 54 .las, copc.las, copc.las.tar.gz 55 56 :param filepath: Input Filepath 57 :return: tuple[str, str, str] --> Directory Path, Filename, Extension(s) 58 """ 59 assert filepath.exists() 60 las_fp: Path = filepath 61 dataset_dir: Path = las_fp.parent 62 file_name: str = las_fp.stem 63 file_ext: str = ''.join(Path(las_fp).suffixes) 64 return str(dataset_dir), str(file_name), str(file_ext) 65 66 def get_controls(self) -> dict[str, Any]: 67 """ 68 Gets the parameters and constants stored in controls.toml. Returns a dict[str, Any] 69 :return: dict[str, Any] 70 """ 71 assert self.controls_fp.exists() 72 with open(self.controls_fp, "rb") as f: 73 controls: dict[str, Any] = load(f) 74 return controls 75 76 def list_controls(self) -> None: 77 """ 78 Prints all variables and constants listed in controls.toml to console. 79 :return: None. Prints to console. 80 """ 81 for var_name, var_val in self.get_controls().items(): 82 print(f"{var_name}:{var_val}") 83 84 @staticmethod 85 def _las_bad_indices(raw_las: LasData) -> Any: 86 """ 87 Uses laspy to find all invalid points in a given LasData set. 88 :param raw_las: Loaded LAS data as LasData type 89 :return: Any 90 """ 91 x_invalid = (raw_las.header.mins[0] > raw_las.x) | (raw_las.header.maxs[0] < raw_las.x) 92 y_invalid = (raw_las.header.mins[1] > raw_las.y) | (raw_las.header.maxs[1] < raw_las.y) 93 z_invalid = (raw_las.header.mins[2] > raw_las.z) | (raw_las.header.maxs[2] < raw_las.z) 94 bad = where(x_invalid | y_invalid | z_invalid) 95 return bad 96 97 @staticmethod 98 def _las_good_indices(raw_las: LasData) -> Any: 99 """ 100 Uses laspy to find all valid points in a given LasData set. Filters out all bad points. 101 :param raw_las: Loaded LAS data as LasData type 102 :return: Any 103 """ 104 x_valid = (raw_las.header.min[0] <= raw_las.x) & (raw_las.header.max[0] >= raw_las.x) 105 y_valid = (raw_las.header.min[1] <= raw_las.y) & (raw_las.header.max[1] >= raw_las.y) 106 z_valid = (raw_las.header.min[2] <= raw_las.z) & (raw_las.header.max[2] >= raw_las.z) 107 good = where(x_valid & y_valid & z_valid) 108 return good 109 110 def datasets_dir(self, subdir: str = "NA") -> Path: 111 """ 112 Logic to determine the base datasets directory. 113 Uses the control boolean CONTROL_CONTAINER_MODE to determine where the volume has been mounted. 114 115 If subdir is set to NA, the root path of the datasets directory is used. 116 TODO add override option 117 :param subdir: Subdirectory that can be found in the datasets directory 118 :return: Path to datasets directory 119 """ 120 121 container_mode: bool = self.get_controls().get('CONTROL_CONTAINER_MODE') 122 123 if (subdir == "NA") and (container_mode is False): 124 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')) 125 elif (subdir == "NA") and (container_mode is True): 126 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_CONTAINER')) 127 elif (subdir != "NA") and (container_mode is False): 128 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')), subdir) 129 else: 130 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_CONTAINER')), subdir) 131 132 return base_dir 133 134 135 def validate_las(self, filepath: str, clean: bool = True, export: bool = False) -> None: 136 """ 137 Validates a las file found in the datasets_dir. Prints bad points to console. 138 :param filepath: Filename of the .las file in datasets_dir 139 :param clean: Removes bad points from the file and saves a new .las file with _valid at the end of the filename 140 :param export: exports bad points to their own file in the datasets directory 141 :return: None, exports files to dataset directory 142 """ 143 assert Path(filepath).exists() 144 145 las_fp: Path = Path(filepath) 146 las_raw = las_read(las_fp) 147 148 print(f"Validating las file {las_fp.stem}...") 149 150 bad_indices = self._las_bad_indices(raw_las=las_raw) 151 152 if len(bad_indices) > 0: 153 print(f"Found Bad Points in {las_fp.stem}: ") 154 print(bad_indices) 155 else: 156 print("No bad points found!") 157 158 if (clean is True) or (export is True): 159 filename_ext: str = (''.join(las_fp.suffixes)) 160 filename_stem: str = las_fp.stem 161 162 if clean is True: 163 good_indices = self._las_good_indices(raw_las=las_raw) 164 if len(good_indices) > 0: 165 points_good = las_raw.points[[good_indices]].copy() 166 else: 167 raise ValueError("Dataset has no good points!") 168 valid_export_fp = Path(las_fp.parent, filename_stem + "_valid" + filename_ext) 169 valid_export_las = LasData(las_raw.header) 170 valid_export_las.points = points_good 171 valid_export_las.write(str(valid_export_fp)) 172 print(f"Exported Valid points to {filename_stem}_valid{filename_ext}") 173 else: 174 pass 175 176 if export is True: 177 if len(bad_indices) > 0: 178 points_bad = las_raw.points[[bad_indices]].copy() 179 invalid_export_fp: Path = Path(las_fp.parent, filename_stem + "_invalid" + filename_ext) 180 invalid_export_las = LasData(las_raw.header) 181 invalid_export_las.points = points_bad 182 invalid_export_las.write(str(invalid_export_fp)) 183 print(f"Exported Invalid points to {filename_stem}_invalid{filename_ext}") 184 else: 185 pass 186 else: 187 pass 188 else: 189 pass 190 191 def report_las_schema(self, filepath: Path) -> None: 192 """ 193 Exports a LAS dataset's schema to a file in the same directory as the original file. 194 Uses PDAL. 195 TODO add logic for creating a report file from given raw dataset for client 196 :param filepath: las data filepath 197 :return: None. Exports json reports 198 """ 199 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 200 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 201 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --schema > {dataset_file[1]}_schema.json" 202 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 203 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 204 205 def report_las_npts(self, filepath: Path, first_n: int = 0) -> None: 206 """ 207 Gets the statistics of the first_n points and exports a JSON file in the original location *_npts.json 208 :param filepath: 209 :param first_n: Number of rows, from the top of the las file, to generate statistics on. Defaults to 0 210 :return: None 211 """ 212 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 213 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 214 215 if first_n == 0: 216 points_str: str = '0' 217 else: 218 points_str: str = '0-' + str(int(first_n)) 219 220 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} -p {points_str} > {dataset_file[1]}_npts.json" 221 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 222 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 223 224 def report_las_stats(self, filepath: Path) -> None: 225 """ 226 Gets the ENTIRE LAS file's statistics and exports a JSON file in the original Directory *_stats.json 227 :param filepath: las Dataset filepath 228 :return: None 229 """ 230 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 231 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 232 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --stats > {dataset_file[1]}_stats.json" 233 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 234 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 235 236 def report_las_summary(self, filepath: Path) -> None: 237 """ 238 Summerized the given LAS dataset and exports a JSON to the original location *_summary.json 239 :param filepath: las Dataset filepath 240 :return: None 241 """ 242 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 243 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 244 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --summary > {dataset_file[1]}_summary.json" 245 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 246 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 247 248 def report_las_metadata(self, filepath: Path) -> None: 249 """ 250 Summerized the given LAS dataset's metadata and exports a JSON to the original location *_meta.json 251 :param filepath: las Dataset filepath 252 :return: None 253 """ 254 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 255 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 256 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --metadata > {dataset_file[1]}_meta.json" 257 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 258 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
24class BaseLiDAR: 25 26 def __init__(self): 27 self.controls_fp: Path = Path(Path.cwd(), 'controls.toml') 28 29 @staticmethod 30 def _list_files(directory: Path = None) -> list[Any]: 31 """ 32 Fast method for producing a list of files in a Directory. 33 TODO add to helpers instead 34 :param directory: Directory Path 35 :return: list[Any] 36 """ 37 ff = [] 38 if directory is not None: 39 for (dirpath, dirnames, filenames) in walk(Path(directory)): 40 ff.extend(filenames) 41 break 42 else: 43 for (dirpath, dirnames, filenames) in walk(Path.cwd()): 44 ff.extend(filenames) 45 break 46 return ff 47 48 @staticmethod 49 def _las_file_split(filepath: Path) -> tuple[str, str, str]: 50 """ 51 Checks that a given filepath exists, and splits the filepath into three strings, as such: 52 Path to Directory, Filename (stem), File Extension 53 54 The reason for this logic is that las files may come with extensions such as: 55 .las, copc.las, copc.las.tar.gz 56 57 :param filepath: Input Filepath 58 :return: tuple[str, str, str] --> Directory Path, Filename, Extension(s) 59 """ 60 assert filepath.exists() 61 las_fp: Path = filepath 62 dataset_dir: Path = las_fp.parent 63 file_name: str = las_fp.stem 64 file_ext: str = ''.join(Path(las_fp).suffixes) 65 return str(dataset_dir), str(file_name), str(file_ext) 66 67 def get_controls(self) -> dict[str, Any]: 68 """ 69 Gets the parameters and constants stored in controls.toml. Returns a dict[str, Any] 70 :return: dict[str, Any] 71 """ 72 assert self.controls_fp.exists() 73 with open(self.controls_fp, "rb") as f: 74 controls: dict[str, Any] = load(f) 75 return controls 76 77 def list_controls(self) -> None: 78 """ 79 Prints all variables and constants listed in controls.toml to console. 80 :return: None. Prints to console. 81 """ 82 for var_name, var_val in self.get_controls().items(): 83 print(f"{var_name}:{var_val}") 84 85 @staticmethod 86 def _las_bad_indices(raw_las: LasData) -> Any: 87 """ 88 Uses laspy to find all invalid points in a given LasData set. 89 :param raw_las: Loaded LAS data as LasData type 90 :return: Any 91 """ 92 x_invalid = (raw_las.header.mins[0] > raw_las.x) | (raw_las.header.maxs[0] < raw_las.x) 93 y_invalid = (raw_las.header.mins[1] > raw_las.y) | (raw_las.header.maxs[1] < raw_las.y) 94 z_invalid = (raw_las.header.mins[2] > raw_las.z) | (raw_las.header.maxs[2] < raw_las.z) 95 bad = where(x_invalid | y_invalid | z_invalid) 96 return bad 97 98 @staticmethod 99 def _las_good_indices(raw_las: LasData) -> Any: 100 """ 101 Uses laspy to find all valid points in a given LasData set. Filters out all bad points. 102 :param raw_las: Loaded LAS data as LasData type 103 :return: Any 104 """ 105 x_valid = (raw_las.header.min[0] <= raw_las.x) & (raw_las.header.max[0] >= raw_las.x) 106 y_valid = (raw_las.header.min[1] <= raw_las.y) & (raw_las.header.max[1] >= raw_las.y) 107 z_valid = (raw_las.header.min[2] <= raw_las.z) & (raw_las.header.max[2] >= raw_las.z) 108 good = where(x_valid & y_valid & z_valid) 109 return good 110 111 def datasets_dir(self, subdir: str = "NA") -> Path: 112 """ 113 Logic to determine the base datasets directory. 114 Uses the control boolean CONTROL_CONTAINER_MODE to determine where the volume has been mounted. 115 116 If subdir is set to NA, the root path of the datasets directory is used. 117 TODO add override option 118 :param subdir: Subdirectory that can be found in the datasets directory 119 :return: Path to datasets directory 120 """ 121 122 container_mode: bool = self.get_controls().get('CONTROL_CONTAINER_MODE') 123 124 if (subdir == "NA") and (container_mode is False): 125 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')) 126 elif (subdir == "NA") and (container_mode is True): 127 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_CONTAINER')) 128 elif (subdir != "NA") and (container_mode is False): 129 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')), subdir) 130 else: 131 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_CONTAINER')), subdir) 132 133 return base_dir 134 135 136 def validate_las(self, filepath: str, clean: bool = True, export: bool = False) -> None: 137 """ 138 Validates a las file found in the datasets_dir. Prints bad points to console. 139 :param filepath: Filename of the .las file in datasets_dir 140 :param clean: Removes bad points from the file and saves a new .las file with _valid at the end of the filename 141 :param export: exports bad points to their own file in the datasets directory 142 :return: None, exports files to dataset directory 143 """ 144 assert Path(filepath).exists() 145 146 las_fp: Path = Path(filepath) 147 las_raw = las_read(las_fp) 148 149 print(f"Validating las file {las_fp.stem}...") 150 151 bad_indices = self._las_bad_indices(raw_las=las_raw) 152 153 if len(bad_indices) > 0: 154 print(f"Found Bad Points in {las_fp.stem}: ") 155 print(bad_indices) 156 else: 157 print("No bad points found!") 158 159 if (clean is True) or (export is True): 160 filename_ext: str = (''.join(las_fp.suffixes)) 161 filename_stem: str = las_fp.stem 162 163 if clean is True: 164 good_indices = self._las_good_indices(raw_las=las_raw) 165 if len(good_indices) > 0: 166 points_good = las_raw.points[[good_indices]].copy() 167 else: 168 raise ValueError("Dataset has no good points!") 169 valid_export_fp = Path(las_fp.parent, filename_stem + "_valid" + filename_ext) 170 valid_export_las = LasData(las_raw.header) 171 valid_export_las.points = points_good 172 valid_export_las.write(str(valid_export_fp)) 173 print(f"Exported Valid points to {filename_stem}_valid{filename_ext}") 174 else: 175 pass 176 177 if export is True: 178 if len(bad_indices) > 0: 179 points_bad = las_raw.points[[bad_indices]].copy() 180 invalid_export_fp: Path = Path(las_fp.parent, filename_stem + "_invalid" + filename_ext) 181 invalid_export_las = LasData(las_raw.header) 182 invalid_export_las.points = points_bad 183 invalid_export_las.write(str(invalid_export_fp)) 184 print(f"Exported Invalid points to {filename_stem}_invalid{filename_ext}") 185 else: 186 pass 187 else: 188 pass 189 else: 190 pass 191 192 def report_las_schema(self, filepath: Path) -> None: 193 """ 194 Exports a LAS dataset's schema to a file in the same directory as the original file. 195 Uses PDAL. 196 TODO add logic for creating a report file from given raw dataset for client 197 :param filepath: las data filepath 198 :return: None. Exports json reports 199 """ 200 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 201 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 202 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --schema > {dataset_file[1]}_schema.json" 203 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 204 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 205 206 def report_las_npts(self, filepath: Path, first_n: int = 0) -> None: 207 """ 208 Gets the statistics of the first_n points and exports a JSON file in the original location *_npts.json 209 :param filepath: 210 :param first_n: Number of rows, from the top of the las file, to generate statistics on. Defaults to 0 211 :return: None 212 """ 213 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 214 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 215 216 if first_n == 0: 217 points_str: str = '0' 218 else: 219 points_str: str = '0-' + str(int(first_n)) 220 221 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} -p {points_str} > {dataset_file[1]}_npts.json" 222 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 223 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 224 225 def report_las_stats(self, filepath: Path) -> None: 226 """ 227 Gets the ENTIRE LAS file's statistics and exports a JSON file in the original Directory *_stats.json 228 :param filepath: las Dataset filepath 229 :return: None 230 """ 231 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 232 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 233 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --stats > {dataset_file[1]}_stats.json" 234 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 235 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 236 237 def report_las_summary(self, filepath: Path) -> None: 238 """ 239 Summerized the given LAS dataset and exports a JSON to the original location *_summary.json 240 :param filepath: las Dataset filepath 241 :return: None 242 """ 243 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 244 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 245 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --summary > {dataset_file[1]}_summary.json" 246 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 247 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}') 248 249 def report_las_metadata(self, filepath: Path) -> None: 250 """ 251 Summerized the given LAS dataset's metadata and exports a JSON to the original location *_meta.json 252 :param filepath: las Dataset filepath 253 :return: None 254 """ 255 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 256 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 257 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --metadata > {dataset_file[1]}_meta.json" 258 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 259 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
67 def get_controls(self) -> dict[str, Any]: 68 """ 69 Gets the parameters and constants stored in controls.toml. Returns a dict[str, Any] 70 :return: dict[str, Any] 71 """ 72 assert self.controls_fp.exists() 73 with open(self.controls_fp, "rb") as f: 74 controls: dict[str, Any] = load(f) 75 return controls
Gets the parameters and constants stored in controls.toml. Returns a dict[str, Any]
Returns
dict[str, Any]
77 def list_controls(self) -> None: 78 """ 79 Prints all variables and constants listed in controls.toml to console. 80 :return: None. Prints to console. 81 """ 82 for var_name, var_val in self.get_controls().items(): 83 print(f"{var_name}:{var_val}")
Prints all variables and constants listed in controls.toml to console.
Returns
None. Prints to console.
111 def datasets_dir(self, subdir: str = "NA") -> Path: 112 """ 113 Logic to determine the base datasets directory. 114 Uses the control boolean CONTROL_CONTAINER_MODE to determine where the volume has been mounted. 115 116 If subdir is set to NA, the root path of the datasets directory is used. 117 TODO add override option 118 :param subdir: Subdirectory that can be found in the datasets directory 119 :return: Path to datasets directory 120 """ 121 122 container_mode: bool = self.get_controls().get('CONTROL_CONTAINER_MODE') 123 124 if (subdir == "NA") and (container_mode is False): 125 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')) 126 elif (subdir == "NA") and (container_mode is True): 127 base_dir: Path = Path(self.get_controls().get('DIR_DATASETS_CONTAINER')) 128 elif (subdir != "NA") and (container_mode is False): 129 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_EXTERNAL')), subdir) 130 else: 131 base_dir: Path = Path(Path(self.get_controls().get('DIR_DATASETS_CONTAINER')), subdir) 132 133 return base_dir
Logic to determine the base datasets directory. Uses the control boolean CONTROL_CONTAINER_MODE to determine where the volume has been mounted.
If subdir is set to NA, the root path of the datasets directory is used. TODO add override option
Parameters
- subdir: Subdirectory that can be found in the datasets directory
Returns
Path to datasets directory
136 def validate_las(self, filepath: str, clean: bool = True, export: bool = False) -> None: 137 """ 138 Validates a las file found in the datasets_dir. Prints bad points to console. 139 :param filepath: Filename of the .las file in datasets_dir 140 :param clean: Removes bad points from the file and saves a new .las file with _valid at the end of the filename 141 :param export: exports bad points to their own file in the datasets directory 142 :return: None, exports files to dataset directory 143 """ 144 assert Path(filepath).exists() 145 146 las_fp: Path = Path(filepath) 147 las_raw = las_read(las_fp) 148 149 print(f"Validating las file {las_fp.stem}...") 150 151 bad_indices = self._las_bad_indices(raw_las=las_raw) 152 153 if len(bad_indices) > 0: 154 print(f"Found Bad Points in {las_fp.stem}: ") 155 print(bad_indices) 156 else: 157 print("No bad points found!") 158 159 if (clean is True) or (export is True): 160 filename_ext: str = (''.join(las_fp.suffixes)) 161 filename_stem: str = las_fp.stem 162 163 if clean is True: 164 good_indices = self._las_good_indices(raw_las=las_raw) 165 if len(good_indices) > 0: 166 points_good = las_raw.points[[good_indices]].copy() 167 else: 168 raise ValueError("Dataset has no good points!") 169 valid_export_fp = Path(las_fp.parent, filename_stem + "_valid" + filename_ext) 170 valid_export_las = LasData(las_raw.header) 171 valid_export_las.points = points_good 172 valid_export_las.write(str(valid_export_fp)) 173 print(f"Exported Valid points to {filename_stem}_valid{filename_ext}") 174 else: 175 pass 176 177 if export is True: 178 if len(bad_indices) > 0: 179 points_bad = las_raw.points[[bad_indices]].copy() 180 invalid_export_fp: Path = Path(las_fp.parent, filename_stem + "_invalid" + filename_ext) 181 invalid_export_las = LasData(las_raw.header) 182 invalid_export_las.points = points_bad 183 invalid_export_las.write(str(invalid_export_fp)) 184 print(f"Exported Invalid points to {filename_stem}_invalid{filename_ext}") 185 else: 186 pass 187 else: 188 pass 189 else: 190 pass
Validates a las file found in the datasets_dir. Prints bad points to console.
Parameters
- filepath: Filename of the .las file in datasets_dir
- clean: Removes bad points from the file and saves a new .las file with _valid at the end of the filename
- export: exports bad points to their own file in the datasets directory
Returns
None, exports files to dataset directory
192 def report_las_schema(self, filepath: Path) -> None: 193 """ 194 Exports a LAS dataset's schema to a file in the same directory as the original file. 195 Uses PDAL. 196 TODO add logic for creating a report file from given raw dataset for client 197 :param filepath: las data filepath 198 :return: None. Exports json reports 199 """ 200 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 201 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 202 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --schema > {dataset_file[1]}_schema.json" 203 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 204 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
Exports a LAS dataset's schema to a file in the same directory as the original file. Uses PDAL. TODO add logic for creating a report file from given raw dataset for client
Parameters
- filepath: las data filepath
Returns
None. Exports json reports
206 def report_las_npts(self, filepath: Path, first_n: int = 0) -> None: 207 """ 208 Gets the statistics of the first_n points and exports a JSON file in the original location *_npts.json 209 :param filepath: 210 :param first_n: Number of rows, from the top of the las file, to generate statistics on. Defaults to 0 211 :return: None 212 """ 213 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 214 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 215 216 if first_n == 0: 217 points_str: str = '0' 218 else: 219 points_str: str = '0-' + str(int(first_n)) 220 221 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} -p {points_str} > {dataset_file[1]}_npts.json" 222 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 223 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
Gets the statistics of the first_n points and exports a JSON file in the original location *_npts.json
Parameters
- filepath:
- first_n: Number of rows, from the top of the las file, to generate statistics on. Defaults to 0
Returns
None
225 def report_las_stats(self, filepath: Path) -> None: 226 """ 227 Gets the ENTIRE LAS file's statistics and exports a JSON file in the original Directory *_stats.json 228 :param filepath: las Dataset filepath 229 :return: None 230 """ 231 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 232 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 233 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --stats > {dataset_file[1]}_stats.json" 234 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 235 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
Gets the ENTIRE LAS file's statistics and exports a JSON file in the original Directory *_stats.json
Parameters
- filepath: las Dataset filepath
Returns
None
237 def report_las_summary(self, filepath: Path) -> None: 238 """ 239 Summerized the given LAS dataset and exports a JSON to the original location *_summary.json 240 :param filepath: las Dataset filepath 241 :return: None 242 """ 243 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 244 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 245 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --summary > {dataset_file[1]}_summary.json" 246 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 247 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
Summerized the given LAS dataset and exports a JSON to the original location *_summary.json
Parameters
- filepath: las Dataset filepath
Returns
None
249 def report_las_metadata(self, filepath: Path) -> None: 250 """ 251 Summerized the given LAS dataset's metadata and exports a JSON to the original location *_meta.json 252 :param filepath: las Dataset filepath 253 :return: None 254 """ 255 dataset_file: tuple[str, str, str] = self._las_file_split(filepath=filepath) 256 use_utf: str = self.get_controls().get('CONTROL_ENCODING') 257 cli_command_string: str = f"cd {str(dataset_file[0])} && pdal info {dataset_file[1]}{dataset_file[2]} --metadata > {dataset_file[1]}_meta.json" 258 process = run(cli_command_string, shell=True, check=True, capture_output=True, encoding=use_utf) 259 print(f'Command {process.args} exited with {process.returncode} code, output: \n{process.stdout}')
Summerized the given LAS dataset's metadata and exports a JSON to the original location *_meta.json
Parameters
- filepath: las Dataset filepath
Returns
None