Spaces:
Running
Running
| # | |
| #CREDIT TO https://github.com/daavoo/pyntcloud/blob/master/pyntcloud/io/ply.py | |
| # | |
| #User daavoo | |
| # | |
| # HAKUNA MATATA | |
| import sys | |
| import numpy as np | |
| import pandas as pd | |
| from collections import defaultdict | |
| sys_byteorder = ('>', '<')[sys.byteorder == 'little'] | |
| ply_dtypes = dict([ | |
| (b'int8', 'i1'), | |
| (b'char', 'i1'), | |
| (b'uint8', 'u1'), | |
| (b'uchar', 'b1'), | |
| (b'uchar', 'u1'), | |
| (b'int16', 'i2'), | |
| (b'short', 'i2'), | |
| (b'uint16', 'u2'), | |
| (b'ushort', 'u2'), | |
| (b'int32', 'i4'), | |
| (b'int', 'i4'), | |
| (b'uint32', 'u4'), | |
| (b'uint', 'u4'), | |
| (b'float32', 'f4'), | |
| (b'float', 'f4'), | |
| (b'float64', 'f8'), | |
| (b'double', 'f8') | |
| ]) | |
| valid_formats = {'ascii': '', 'binary_big_endian': '>', | |
| 'binary_little_endian': '<'} | |
| def read_ply(filename, allow_bool=False): | |
| """ Read a .ply (binary or ascii) file and store the elements in pandas DataFrame. | |
| Parameters | |
| ---------- | |
| filename: str | |
| Path to the filename | |
| allow_bool: bool | |
| flag to allow bool as a valid PLY dtype. False by default to mirror original PLY specification. | |
| Returns | |
| ------- | |
| data: dict | |
| Elements as pandas DataFrames; comments and ob_info as list of string | |
| """ | |
| if allow_bool: | |
| ply_dtypes[b'bool'] = '?' | |
| with open(filename, 'rb') as ply: | |
| if b'ply' not in ply.readline(): | |
| raise ValueError('The file does not start with the word ply') | |
| # get binary_little/big or ascii | |
| fmt = ply.readline().split()[1].decode() | |
| # get extension for building the numpy dtypes | |
| ext = valid_formats[fmt] | |
| line = [] | |
| dtypes = defaultdict(list) | |
| count = 2 | |
| points_size = None | |
| mesh_size = None | |
| has_texture = False | |
| comments = [] | |
| while b'end_header' not in line and line != b'': | |
| line = ply.readline() | |
| if b'element' in line: | |
| line = line.split() | |
| name = line[1].decode() | |
| size = int(line[2]) | |
| if name == "vertex": | |
| points_size = size | |
| elif name == "face": | |
| mesh_size = size | |
| elif b'property' in line: | |
| line = line.split() | |
| # element mesh | |
| if b'list' in line: | |
| if b"vertex_indices" in line[-1] or b"vertex_index" in line[-1]: | |
| mesh_names = ["n_points", "v1", "v2", "v3"] | |
| else: | |
| has_texture = True | |
| mesh_names = ["n_coords"] + ["v1_u", "v1_v", "v2_u", "v2_v", "v3_u", "v3_v"] | |
| if fmt == "ascii": | |
| # the first number has different dtype than the list | |
| dtypes[name].append( | |
| (mesh_names[0], ply_dtypes[line[2]])) | |
| # rest of the numbers have the same dtype | |
| dt = ply_dtypes[line[3]] | |
| else: | |
| # the first number has different dtype than the list | |
| dtypes[name].append( | |
| (mesh_names[0], ext + ply_dtypes[line[2]])) | |
| # rest of the numbers have the same dtype | |
| dt = ext + ply_dtypes[line[3]] | |
| for j in range(1, len(mesh_names)): | |
| dtypes[name].append((mesh_names[j], dt)) | |
| else: | |
| if fmt == "ascii": | |
| dtypes[name].append( | |
| (line[2].decode(), ply_dtypes[line[1]])) | |
| else: | |
| dtypes[name].append( | |
| (line[2].decode(), ext + ply_dtypes[line[1]])) | |
| elif b'comment' in line: | |
| line = line.split(b" ", 1) | |
| comment = line[1].decode().rstrip() | |
| comments.append(comment) | |
| count += 1 | |
| # for bin | |
| end_header = ply.tell() | |
| data = {} | |
| if comments: | |
| data["comments"] = comments | |
| if fmt == 'ascii': | |
| top = count | |
| bottom = 0 if mesh_size is None else mesh_size | |
| names = [x[0] for x in dtypes["vertex"]] | |
| data["points"] = pd.read_csv(filename, sep=" ", header=None, engine="python", | |
| skiprows=top, skipfooter=bottom, usecols=names, names=names) | |
| for n, col in enumerate(data["points"].columns): | |
| data["points"][col] = data["points"][col].astype( | |
| dtypes["vertex"][n][1]) | |
| if mesh_size : | |
| top = count + points_size | |
| names = np.array([x[0] for x in dtypes["face"]]) | |
| usecols = [1, 2, 3, 5, 6, 7, 8, 9, 10] if has_texture else [1, 2, 3] | |
| names = names[usecols] | |
| data["mesh"] = pd.read_csv( | |
| filename, sep=" ", header=None, engine="python", skiprows=top, usecols=usecols, names=names) | |
| for n, col in enumerate(data["mesh"].columns): | |
| data["mesh"][col] = data["mesh"][col].astype( | |
| dtypes["face"][n + 1][1]) | |
| else: | |
| with open(filename, 'rb') as ply: | |
| ply.seek(end_header) | |
| points_np = np.fromfile(ply, dtype=dtypes["vertex"], count=points_size) | |
| if ext != sys_byteorder: | |
| points_np = points_np.byteswap().newbyteorder() | |
| data["points"] = pd.DataFrame(points_np) | |
| if mesh_size: | |
| mesh_np = np.fromfile(ply, dtype=dtypes["face"], count=mesh_size) | |
| if ext != sys_byteorder: | |
| mesh_np = mesh_np.byteswap().newbyteorder() | |
| data["mesh"] = pd.DataFrame(mesh_np) | |
| data["mesh"].drop('n_points', axis=1, inplace=True) | |
| return data | |
| def write_ply(filename, points=None, mesh=None, as_text=False, comments=None): | |
| """Write a PLY file populated with the given fields. | |
| Parameters | |
| ---------- | |
| filename: str | |
| The created file will be named with this | |
| points: ndarray | |
| mesh: ndarray | |
| as_text: boolean | |
| Set the write mode of the file. Default: binary | |
| comments: list of string | |
| Returns | |
| ------- | |
| boolean | |
| True if no problems | |
| """ | |
| if not filename.endswith('ply'): | |
| filename += '.ply' | |
| # open in text mode to write the header | |
| with open(filename, 'w') as ply: | |
| header = ['ply'] | |
| if as_text: | |
| header.append('format ascii 1.0') | |
| else: | |
| header.append('format binary_' + sys.byteorder + '_endian 1.0') | |
| if comments: | |
| for comment in comments: | |
| header.append('comment ' + comment) | |
| if points is not None: | |
| header.extend(describe_element('vertex', points)) | |
| if mesh is not None: | |
| mesh = mesh.copy() | |
| mesh.insert(loc=0, column="n_points", value=3) | |
| mesh["n_points"] = mesh["n_points"].astype("u1") | |
| header.extend(describe_element('face', mesh)) | |
| header.append('end_header') | |
| for line in header: | |
| ply.write("%s\n" % line) | |
| if as_text: | |
| if points is not None: | |
| points.to_csv(filename, sep=" ", index=False, header=False, mode='a', | |
| encoding='ascii') | |
| if mesh is not None: | |
| mesh.to_csv(filename, sep=" ", index=False, header=False, mode='a', | |
| encoding='ascii') | |
| else: | |
| with open(filename, 'ab') as ply: | |
| if points is not None: | |
| points.to_records(index=False).tofile(ply) | |
| if mesh is not None: | |
| mesh.to_records(index=False).tofile(ply) | |
| return True | |
| def describe_element(name, df): | |
| """ Takes the columns of the dataframe and builds a ply-like description | |
| Parameters | |
| ---------- | |
| name: str | |
| df: pandas DataFrame | |
| Returns | |
| ------- | |
| element: list[str] | |
| """ | |
| property_formats = {'f': 'float', 'u': 'uchar', 'i': 'int', 'b': 'bool'} | |
| element = ['element ' + name + ' ' + str(len(df))] | |
| if name == 'face': | |
| element.append("property list uchar int vertex_indices") | |
| else: | |
| for i in range(len(df.columns)): | |
| # get first letter of dtype to infer format | |
| f = property_formats[str(df.dtypes[i])[0]] | |
| element.append('property ' + f + ' ' + df.columns.values[i]) | |
| return element | |
| def write_ply_float(filename, points=None, mesh=None, as_text=False, comments=None): | |
| """Write a PLY file populated with the given fields. | |
| Parameters | |
| ---------- | |
| filename: str | |
| The created file will be named with this | |
| points: ndarray | |
| mesh: ndarray | |
| as_text: boolean | |
| Set the write mode of the file. Default: binary | |
| comments: list of string | |
| Returns | |
| ------- | |
| boolean | |
| True if no problems | |
| """ | |
| if not filename.endswith('ply'): | |
| filename += '.ply' | |
| # open in text mode to write the header | |
| with open(filename, 'w') as ply: | |
| header = ['ply'] | |
| #header.append('format ascii 1.0') | |
| header.append('format binary_' + sys.byteorder + '_endian 1.0') | |
| if comments: | |
| for comment in comments: | |
| header.append('comment ' + comment) | |
| if points is not None: | |
| header.extend(describe_element('vertex', points)) | |
| if mesh is not None: | |
| mesh = mesh.copy() | |
| mesh.insert(loc=0, column="n_points", value=3) | |
| mesh["n_points"] = mesh["n_points"].astype("u1") | |
| header.extend(describe_element('face', mesh)) | |
| header.append('end_header') | |
| for line in header: | |
| ply.write("%s\n" % line) | |
| with open(filename, 'ab') as ply: | |
| if points is not None: | |
| pointsNumPy = points.to_numpy() | |
| pointsNumPy.tofile(filename,format='<f4') | |
| #points.tofile(ply,format='<f4') | |
| return True | |