From 8c2077f7d3edf9d3a2e3c29f8111a086d5aeefc0 Mon Sep 17 00:00:00 2001 From: Maciej Wielgosz <maciej.wielgosz@nibio.no> Date: Tue, 5 Sep 2023 14:42:11 +0200 Subject: [PATCH] sparsification implemented --- .../sparsify_las_based_sq_m.py | 41 +++++---- .../sparsify_las_based_sq_m_in_folder.py | 87 +++++++++++++++++++ 2 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 nibio_preprocessing/sparsify_las_based_sq_m_in_folder.py diff --git a/nibio_preprocessing/sparsify_las_based_sq_m.py b/nibio_preprocessing/sparsify_las_based_sq_m.py index 86fa4dc..d6e44b7 100644 --- a/nibio_preprocessing/sparsify_las_based_sq_m.py +++ b/nibio_preprocessing/sparsify_las_based_sq_m.py @@ -7,15 +7,24 @@ from scipy.spatial import ConvexHull import logging -class SparsifyPointCloudToDensity: +class SparsifyLasBasedSqM: def __init__(self, input_file, output_folder=None, target_density=10, verbose=False): self.input_file = input_file if output_folder is None: self.output_folder = os.path.dirname(input_file) self.target_density = target_density + self.desity = None + self.new_point_cloud_density = None self.verbose = verbose + + # Initialize logging + self.logger = logging.getLogger(__name__) if self.verbose: - logging.info(f"Initialized with input file: {self.input_file}, target density: {self.target_density}") + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.WARNING) + + self.logger.info(f"Initialized with input file: {self.input_file}, target density: {self.target_density}") def calculate_density_convex_hull(self, las): points_3D = np.vstack((las.x, las.y, las.z)).transpose() @@ -27,20 +36,18 @@ class SparsifyPointCloudToDensity: return density def sparsify(self, point_cloud): - density = self.calculate_density_convex_hull(point_cloud) - if self.verbose: - logging.info(f"Point cloud density: {density} points per square meter.") + + + self.density = self.calculate_density_convex_hull(point_cloud) + self.logger.info(f"Point cloud density: {self.density} points per square meter.") x = point_cloud.x - keep_count = int(len(x) * (self.target_density / density)) + keep_count = int(len(x) * (self.target_density / self.density)) sampled_indices = random.sample(range(len(x)), keep_count) filtered_point_cloud = point_cloud.points[sampled_indices] - if self.verbose: - logging.info(f"Reduced point cloud size from {len(x)} to {len(filtered_point_cloud)} points.") - logging.info(f"Reduced point cloud by {(1 - len(filtered_point_cloud) / len(x)) * 100}%.") - density = self.calculate_density_convex_hull(filtered_point_cloud) - density = round(density, 2) - logging.info(f"Filtered point cloud density: {density} points per square meter.") - + self.new_point_cloud_density =self.calculate_density_convex_hull(filtered_point_cloud) + self.logger.info(f"Reduced point cloud size from {len(x)} to {len(filtered_point_cloud)} points.") + self.logger.info(f"Reduced point cloud by {(1 - len(filtered_point_cloud) / len(x)) * 100}%.") + self.logger.info(f"New point cloud density: {self.new_point_cloud_density} points per square meter.") return filtered_point_cloud def process(self): @@ -48,18 +55,18 @@ class SparsifyPointCloudToDensity: os.makedirs(self.output_folder) inFile = laspy.read(self.input_file) filtered_points = self.sparsify(inFile) - if self.verbose: - logging.info("Creating output laspy object") + self.logger.info("Creating output laspy object") outFile = laspy.create(point_format=inFile.point_format, file_version=inFile.header.version) outFile.header = inFile.header outFile.points = filtered_points - # save the point cloud to the output folder with the same name as the input file but suffixed with _sparse and desired density output_file_path = os.path.join(self.output_folder, os.path.basename(self.input_file).replace(".las", f"_sparse_{self.target_density}.las")) outFile.write(output_file_path) if __name__ == "__main__": + # Configure logging logging.basicConfig(level=logging.INFO) + parser = argparse.ArgumentParser() parser.add_argument("-i", "--input_file", help="The .las file to sparsify.") parser.add_argument("--output_folder", default=None, help="The folder where the sparse point cloud will be saved.") @@ -67,5 +74,5 @@ if __name__ == "__main__": parser.add_argument("-v", "--verbose", help="Enable verbose logging", action="store_true") args = parser.parse_args() - sparsifier = SparsifyPointCloudToDensity(args.input_file, args.output_folder, args.target_density, args.verbose) + sparsifier = SparsifyLasBasedSqM(args.input_file, args.output_folder, args.target_density, args.verbose) sparsifier.process() diff --git a/nibio_preprocessing/sparsify_las_based_sq_m_in_folder.py b/nibio_preprocessing/sparsify_las_based_sq_m_in_folder.py new file mode 100644 index 0000000..801cc41 --- /dev/null +++ b/nibio_preprocessing/sparsify_las_based_sq_m_in_folder.py @@ -0,0 +1,87 @@ +import argparse +import os +import logging + +import laspy +from tqdm import tqdm +from nibio_preprocessing.sparsify_las_based_sq_m import SparsifyLasBasedSqM + +class SparsifyLasBasedSqMInFolder(SparsifyLasBasedSqM): + def __init__(self, input_folder, output_folder=None, target_density=10, verbose=False): + super().__init__(input_file=None, output_folder=output_folder, target_density=target_density, verbose=verbose) + self.directory_with_point_clouds = input_folder + self.output_folder = output_folder + self.report = None + + # Initialize logging for the subclass + self.logger = logging.getLogger(__name__) + if self.verbose: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.WARNING) + + def reduce_point_clouds(self): + # get paths to all point clouds in the directory and subdirectories + point_cloud_paths = [] + + # create output folder if it doesn't exist, if it does, delete all files in it + if not os.path.exists(self.output_folder): + os.makedirs(self.output_folder) + else: + files = os.listdir(self.output_folder) + for f in files: + os.remove(os.path.join(self.output_folder, f)) + + for root, dirs, files in os.walk(self.directory_with_point_clouds): + for file in files: + if file.endswith(".las"): + point_cloud_paths.append(os.path.join(root, file)) + + self.logger.info(f"Found {len(point_cloud_paths)} point clouds.") + + # iterate over all point clouds and save outputs to the output folder + for point_cloud_path in tqdm(point_cloud_paths): + self.input_file = point_cloud_path + self.process() + # append information about the point cloud to a report as a dictionary + if self.report is None: + self.report = { + "input_file": [self.input_file], + "density": [self.density], + "target_density": [self.target_density], + "new_density": [self.new_point_cloud_density] + } + else: + self.report["input_file"].append(self.input_file) + self.report["density"].append(self.density), + self.report["target_density"].append(self.target_density), + self.report["new_density"].append(self.new_point_cloud_density) + + + + if self.verbose: + # print the dictionary as a pandas dataframe + import pandas as pd + df = pd.DataFrame.from_dict(self.report, orient="index").transpose() + print(df) + + +if __name__ == "__main__": + # Configure logging + logging.basicConfig(level=logging.INFO) + + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--input_folder", help="The folder with .las files to sparsify.") + parser.add_argument("-o", "--output_folder", default=None, help="The folder where the sparse point clouds will be saved.") + parser.add_argument("-d", "--target_density", help="The target density in points per square meter.", default=10, type=int) + parser.add_argument("-v", "--verbose", action="store_true", help="Print information about the process") + + args = parser.parse_args() + + sparsifier = SparsifyLasBasedSqMInFolder( + args.input_folder, + args.output_folder, + args.target_density, + args.verbose + ) + sparsifier.reduce_point_clouds() -- GitLab