Base LiDAR Module

codebase.lidar_base API documentation

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}')
class BaseLiDAR:
 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}')
controls_fp: pathlib.Path
def get_controls(self) -> dict[str, typing.Any]:
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]

def list_controls(self) -> None:
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.

def datasets_dir(self, subdir: str = 'NA') -> pathlib.Path:
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

def validate_las(self, filepath: str, clean: bool = True, export: bool = False) -> None:
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

def report_las_schema(self, filepath: pathlib.Path) -> None:
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

def report_las_npts(self, filepath: pathlib.Path, first_n: int = 0) -> None:
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

def report_las_stats(self, filepath: pathlib.Path) -> 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

def report_las_summary(self, filepath: pathlib.Path) -> 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

def report_las_metadata(self, filepath: pathlib.Path) -> 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