diff --git a/README.md b/README.md index 63fdcd3e9585e6db4630506276c1031628269884..7f2bedde4d04044fcd03df163e7011707690df71 100644 --- a/README.md +++ b/README.md @@ -123,5 +123,11 @@ Folder `input_folder/results` contain three subfolders: # Orinal repo For the orignal repo, please take a look there: https://github.com/philwilkes/FSCT and https://github.com/philwilkes/TLS2trees +# Orinal repo +Sematic labels expected: +1 - ground +2 - vegetation +3 - cwd +4 - trunk diff --git a/bash_helper_scripts/get_small_data_for_playground.sh b/bash_helper_scripts/get_small_data_for_playground.sh index a0c5e0f2b06007b807018a42cf8a9201507e01eb..8b37daf4a4204bcf6e486d0fd30770ea8149a431 100755 --- a/bash_helper_scripts/get_small_data_for_playground.sh +++ b/bash_helper_scripts/get_small_data_for_playground.sh @@ -1,16 +1,22 @@ -TARGET_FOLDER=/home/nibio/mutable-outside-world/code/gitlab_fsct/instance_segmentation_classic/sample_playground +#!/bin/bash + +# Define target folder +TARGET_FOLDER="/home/nibio/mutable-outside-world/code/gitlab_fsct/instance_segmentation_classic/maciek" + +# Number of files you want to create +NUM_FILES=$1 + +# Delete old files in the target folder rm -rf $TARGET_FOLDER/* +# Copy the original file to the target folder cp /home/nibio/mutable-outside-world/data/small_file_pipeline_test/small_file_pipeline_test.las $TARGET_FOLDER -# change name of the file to first.laz +# Change the name of the original file to first.las mv $TARGET_FOLDER/small_file_pipeline_test.las $TARGET_FOLDER/first.las -# make a copy of the file -# cp $TARGET_FOLDER/first.las $TARGET_FOLDER/second.las - -# # make a copy of the file -# cp $TARGET_FOLDER/first.las $TARGET_FOLDER/third.las - -# # make a copy of the file -# cp $TARGET_FOLDER/first.las $TARGET_FOLDER/fourth.las \ No newline at end of file +# Make copies of the original file based on the number provided +for (( i=2; i<=NUM_FILES; i++ )) +do + cp $TARGET_FOLDER/first.las $TARGET_FOLDER/file_$i.las +done diff --git a/nibio_postprocessing/las_to_pandas.py b/nibio_postprocessing/las_to_pandas.py index e4945429b0ccfff2fe7fe03513a7af9337d8d705..a82bd7cca9937e67ecd98c590d3b31c1840bf840 100644 --- a/nibio_postprocessing/las_to_pandas.py +++ b/nibio_postprocessing/las_to_pandas.py @@ -4,7 +4,7 @@ import laspy # works with laspy 2.1.2 -def las_to_pandas(las_file_path, csv_file_path, save_csv=True): +def las_to_pandas(las_file_path, csv_file_path=None): """ Reads a LAS file and converts it to a pandas dataframe, then saves it to a CSV file. @@ -32,7 +32,7 @@ def las_to_pandas(las_file_path, csv_file_path, save_csv=True): points_df = pd.DataFrame(all_points, columns=all_columns) # Save pandas dataframe to csv - if save_csv: + if csv_file_path is not None: points_df.to_csv(csv_file_path, index=False, header=True, sep=',') return points_df diff --git a/nibio_postprocessing/modification_pipeline.py b/nibio_postprocessing/modification_pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..7dd72f9933761188d1a6fe10fb6576e1f1374c0d --- /dev/null +++ b/nibio_postprocessing/modification_pipeline.py @@ -0,0 +1,61 @@ +import os +from nibio_postprocessing.ply_to_pandas import ply_to_pandas +from nibio_postprocessing.pandas_to_las import pandas_to_las + +def las_modification_pipeline(input_file_path, output_file_path): + # read the input file + points_df = ply_to_pandas(input_file_path, csv_file_path=None) + + ### steps to do ######### + + # rename column from semantic_seg to label + points_df.rename(columns={'semantic_seg': 'label'}, inplace=True) + + # remove all the columns except x, y, z, and label, and treeID + all_columns = list(points_df.columns) + columns_to_keep = ['x', 'y', 'z', 'label', 'treeID'] + columns_to_remove = list(set(all_columns) - set(columns_to_keep)) + points_df.drop(columns_to_remove, axis=1, inplace=True) + + # for label change rows with value 2 to 1 (low vegetation is mapped to ground) + points_df.loc[points_df['label'] == 2, 'label'] = 1 + + # remove rows with label 0 (unclassified) + points_df = points_df[points_df['label'] != 0] + + # for label change rows with value 4 and 5 change to 2 (map branches and live-branches to vegetation) + points_df.loc[points_df['label'] == 4, 'label'] = 2 + points_df.loc[points_df['label'] == 5, 'label'] = 2 + + # for label change rows with value 3 change to 4 (remap trunk) + points_df.loc[points_df['label'] == 3, 'label'] = 4 + + ### end of steps to do ### + + # save the input file as output file + pandas_to_las(points_df, output_file_path) + + return None + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='Process ply files and save results as las files.') + parser.add_argument('-i', '--input_folder', type=str, help='Path to the input folder containing ply files.') + parser.add_argument('-o', '--output_folder', type=str, help='Path to the output folder to save las files.') + + args = parser.parse_args() + + # Ensure output directory exists + if not os.path.exists(args.output_folder): + os.makedirs(args.output_folder) + + # Loop through each file in the input directory + for filename in os.listdir(args.input_folder): + if filename.endswith(".ply"): + input_file_path = os.path.join(args.input_folder, filename) + output_file_name = os.path.splitext(filename)[0] + "_out.las" + output_file_path = os.path.join(args.output_folder, output_file_name) + + # run the pipeline + las_modification_pipeline(input_file_path, output_file_path) diff --git a/nibio_postprocessing/pandas_to_las.py b/nibio_postprocessing/pandas_to_las.py index 71e7e5ba1193b31b47a011afd7426a800976c10a..c87c3ae74d64ab144c9251abca5e70d132f1328d 100644 --- a/nibio_postprocessing/pandas_to_las.py +++ b/nibio_postprocessing/pandas_to_las.py @@ -1,10 +1,11 @@ +import collections import laspy import pandas as pd import numpy as np # works with laspy 2.1.2 -def pandas_to_las(csv, las_file_path, csv_file_provided=True, verbose=False): +def pandas_to_las(csv, las_file_path, csv_file_provided=False, verbose=False): """ Convert a pandas DataFrame to a .las file. @@ -35,7 +36,17 @@ def pandas_to_las(csv, las_file_path, csv_file_provided=True, verbose=False): raise ValueError(f'Column {col} not found in {csv}') # Create a new .las file - las_header = laspy.LasHeader() + las_header = laspy.LasHeader(point_format=3, version="1.2") + + standard_columns = list(las_header.point_format.dimension_names) + + # check which columns in the csv file are in the standard columns + csv_standard_columns = list(set(standard_columns) & set(df.columns)) + + # add them to required columns if they are not already there + for col in csv_standard_columns: + if col not in required_columns: + required_columns.append(col) # read all the colum names from the csv file csv_columns = list(df.columns) @@ -58,7 +69,11 @@ def pandas_to_las(csv, las_file_path, csv_file_provided=True, verbose=False): # Assign extra dimensions for col in gt_extra_dimensions: - outfile[col] = df[col].values + # if you are processing col=return_num limit the values to 0-7 + if col == 'return_num': + outfile[col] = df[col].values % 8 + else: + outfile[col] = df[col].values # Write the file outfile.write(las_file_path) diff --git a/nibio_postprocessing/ply_to_pandas.py b/nibio_postprocessing/ply_to_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..ba05d539c97abbb1f9bf408ceaf1da8091d3f17c --- /dev/null +++ b/nibio_postprocessing/ply_to_pandas.py @@ -0,0 +1,52 @@ +import numpy as np +import pandas as pd +from plyfile import PlyData + +def ply_to_pandas(ply_file_path, csv_file_path=None): + """ + Reads a PLY file and converts its vertex data to a numpy array, then saves it to a CSV file. + + Args: + ply_file_path (str): The path to the PLY file to be read. + csv_file_path (str, optional): The path to the CSV file to be saved. If not provided, + the output CSV will have the same name as the input PLY file. + + Returns: + numpy.ndarray: A 2D array containing the vertex data from the PLY file. + """ + # Load the PLY file + ply_content = PlyData.read(ply_file_path) + + # Identify available elements in the PLY file + available_elements = [elem.name for elem in ply_content.elements] + print("Available elements in the PLY file:", available_elements) + + # Determine the element that represents the point cloud data + if 'vertex' in available_elements: + point_element_name = 'vertex' + elif 'point' in available_elements: + point_element_name = 'point' + else: + print("Could not find a suitable element for point cloud data.") + return + + # Extract data from the chosen element + point_data = ply_content[point_element_name].data + property_names = point_data.dtype.names + data_arrays = [point_data[prop_name] for prop_name in property_names] + all_points = np.vstack(data_arrays).T + + # Save the data to a CSV file + if csv_file_path is not None: + np.savetxt(csv_file_path, all_points, delimiter=',', header=','.join(property_names), comments='', fmt='%s') + + # put the data into a pandas dataframe + points_df = pd.DataFrame(all_points, columns=property_names) + + return points_df + +if __name__ == "__main__": + ply_path = "/home/nibio/mutable-outside-world/code/gitlab_fsct/instance_segmentation_classic/Binbin_data_paper/TUWIEN_test_test.ply" + csv_path = "/home/nibio/mutable-outside-world/code/gitlab_fsct/instance_segmentation_classic/Binbin_data_paper/TUWIEN_test_test.csv" + + ply_to_pandas(ply_path, csv_path)