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