* Create README.md for fix_ply

* Create fix_ply.py

* Update fix_ply.py to match current

* Update README.md to match Jaime's description

* Add more description
pull/1888/head
Stephen Mather 2025-07-15 21:42:56 -04:00 zatwierdzone przez GitHub
rodzic 4aa9cae393
commit c6d94d6a64
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
2 zmienionych plików z 87 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,19 @@
# Fix Ply
Use to translate a modified ply into a compatible format for subsequent steps in ODM. Via Jaime Chacoff, https://community.opendronemap.org/t/edited-point-cloud-with-cloudcompare-wont-rerun-from-odm-meshing/21449/6
The basic idea is to process through ODM until the point cloud is created, use a 3rd party tool, like CloudCompare to edit the point cloud, and then continue processing in OpenDroneMap.
This useful bit of python will convert the PLY exported from CloudCompare back into a compatible format for continued processing in OpenDroneMap.
1. Run project in WebODM and add this to your settings: `end-with: odm-filterpoints`
1. Once complete, go to your NodeODM container and copy `/var/www/data/[Task ID]/odm-filterpoints` directory
1. Open CloudCompare and from `odm-filterpoints` directory you've copied, open `point_cloud.ply`
1. In the box that pops up, add a scalar field `vertex - views`
1. To see the actual colours again - select the point cloud, then in properties change colours from "Scalar field" to "RGB"
1. Make your changes to the point cloud
1. Compute normals (Edit > Normals > Compute)
1. Save PLY file as ASCII
1. Run Python file above to fix PLY file and convert to binary
1. Copy `odm_filterpoints` directory (or just `point_cloud.ply`) back into NodeODM container
1. Restart project in WebODM "From Meshing" (don't forget to edit settings to remove `end-with: odm-filterpoints` or it's not going to do anything).

Wyświetl plik

@ -0,0 +1,68 @@
import os
import logging
from plyfile import PlyData, PlyElement
import numpy as np
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def pcd_ascii_to_binary_ply(ply_file: str, binary_ply: str) -> None:
"""Converts ASCII PLY to binary, ensuring 'views' is present and of type uchar.
Raises ValueError if neither 'scalar_views' nor 'views' is found.
"""
try:
logging.info(f"Reading ASCII PLY file: {ply_file}")
ply_data: PlyData = PlyData.read(ply_file)
except FileNotFoundError:
logging.error(f"File not found: {ply_file}")
return
except Exception as e:
logging.error(f"Error reading PLY file: {e}")
return
new_elements: list[PlyElement] = []
for element in ply_data.elements:
new_data = element.data.copy()
if 'scalar_views' in element.data.dtype.names:
new_data['views'] = new_data['scalar_views'].astype('u1')
del new_data['scalar_views']
elif 'views' in element.data.dtype.names:
new_data['views'] = new_data['views'].astype('u1')
else:
raise ValueError(f"Neither 'scalar_views' nor 'views' found - did you import them when opened the file in CloudCompare?")
new_element = PlyElement.describe(new_data, element.name)
new_elements.append(new_element)
new_ply_data = PlyData(new_elements, text=False)
try:
logging.info(f"Writing binary PLY file: {binary_ply}")
new_ply_data.write(binary_ply)
except Exception as e:
logging.error(f"Error writing PLY file: {e}")
return
logging.info("PLY conversion complete.")
if __name__ == '__main__':
# Parameters
base: str = os.path.dirname(os.path.abspath(__file__))
ply_file: str = os.path.join(base, 'point_cloud_ascii.ply')
binary_ply_file: str = os.path.join(base, 'point_cloud.ply')
if not os.path.exists(ply_file):
logging.error(f"Input file not found: {ply_file}")
exit(1) # Exit with error code
try:
pcd_ascii_to_binary_ply(ply_file, binary_ply_file)
except ValueError as e:
logging.error(f"PLY conversion failed: {e}")
exit(1) # Exit with error code to indicate failure