【plyfile库用法】读、写ply文件

官网文档:plyfile documentation

Usage

PLY 文件数据的反序列化和序列化都是通过 PlyData 和 PlyElement 实例完成的。

>>> import numpy
>>> from plyfile import PlyData, PlyElement
>>>

在下面的代码示例中,假设 tet.ply 文件包含以下文本:

ply
format ascii 1.0
comment single tetrahedron with colored faces
element vertex 4
comment tetrahedron vertices
property float x
property float y
property float z
element face 4
property list uchar int vertex_indices
property uchar red
property uchar green
property uchar blue
end_header
0 0 0
0 1 1
1 0 1
1 1 0
3 0 1 2 255 255 255
3 0 2 3 255 0 0
3 0 1 3 0 255 0
3 1 2 3 0 0 255

 (该文件位于示例目录下)。

读ply文件

>>> plydata = PlyData.read('tet.ply')
>>>

>>> with open('tet.ply', 'rb') as f:
...     plydata = PlyData.read(f)
>>>

静态方法 PlyData.read 返回一个 PlyData 实例,它是 plyfile 对 PLY 文件中数据的表示。PlyData 实例有一个属性 elements,这是一个 PlyElement 实例列表,每个 PlyElement 实例都有一个数据属性,这是一个包含数值数据的 numpy 结构数组。PLY 文件元素映射到 numpy 结构数组的方式非常明显。对于元素中的列表属性,默认情况下,相应的 numpy 字段类型是对象,其成员是 numpy 数组(参见下面的顶点指数示例)1。

具体来说:

>>> plydata.elements[0].name
'vertex'
>>> plydata.elements[0].data[0]
(0., 0., 0.)
>>> plydata.elements[0].data['x']
array([0., 0., 1., 1.], dtype=float32)
>>> plydata['face'].data['vertex_indices'][0]
array([0, 1, 2], dtype=int32)
>>>

为方便起见,可按名称查找元素和属性:

>>> plydata['vertex']['x']
array([0., 0., 1., 1.], dtype=float32)
>>>

和元素可以直接索引,而无需明确地通过数据属性:

>>> plydata['vertex'][0]
(0., 0., 0.)
>>>

上述表达式等同于 plydata['顶点'].data[0]。

PlyElement 实例还包含元数据:

>>> plydata.elements[0].properties
(PlyProperty('x', 'float'), PlyProperty('y', 'float'), PlyProperty('z', 'float'))
>>> plydata.elements[0].count
4
>>>

PlyProperty 和 PlyListProperty 实例在内部被用作 PLY 元素属性的方便中间表示,可以很容易地序列化到 PLY 头(使用 str)或转换为与 numpy 兼容的类型描述(通过 dtype 方法)。直接操作它们并不常见,但如果需要,可以通过 properties 属性(如上图所示)以元组形式访问元素的属性元数据,或通过名称查找:

>>> plydata.elements[0].ply_property('x')
PlyProperty('x', 'float')
>>>

调用 PlyData.read 时,许多(但不一定是所有)类型的畸形输入文件都会引发 PlyParseError。如果适用,PlyParseError 实例的字符串值(以及 element、row 和 prop 属性)会为错误提供额外的上下文。

Faster reading via memory mapping 通过内存映射加快读取速度

情况 1:无列表属性的元素 - elements with no list properties

如果二进制 PLY 文件中的元素没有列表属性,那么默认情况下将对其进行内存映射,但这取决于底层文件对象的能力。可以使用 mmap 参数禁用内存映射:

>>> plydata.text = False
>>> plydata.byte_order = '<'
>>> plydata.write('tet_binary.ply')
>>>
>>> # `mmap=True` is the default:
>>> plydata = PlyData.read('tet_binary.ply')
>>> isinstance(plydata['vertex'].data, numpy.memmap)
True
>>> plydata = PlyData.read('tet_binary.ply', mmap=False)
>>> isinstance(plydata['vertex'].data, numpy.memmap)
False
>>>
情况 2:具有列表属性的元素 - elements with list properties

在一般情况下,具有列表属性的元素不能作为 numpy 数组进行内存映射,但一种重要情况除外:当所有列表属性都具有固定且已知的长度时。在这种情况下,可以向 PlyData.read 提供 known_list_len 参数:

>>> plydata = PlyData.read('tet_binary.ply',
...                        known_list_len={'face': {'vertex_indices': 3}})
>>> isinstance(plydata['face'].data, numpy.memmap)
True
>>>

实现将验证数据:如果列表属性的任何实例的长度与指定值不符,则将引发 PlyParseError 错误。

请注意,为了启用给定元素的内存映射,元素中的所有列表属性都必须在 known_list_len 字典中指定长度。如果任何列表属性的长度不在 known_list_len 字典中,则不会尝试内存映射,也不会引发错误。

写ply文件

第一步是将数据转换为 numpy 结构数组


请注意有一些限制:一般来说,如果你知道 PLY 文件元素可以包含的属性类型,你就可以很容易地推断出这些限制。例如,PLY 文件不包含 64 位整数或复杂数据,因此不允许使用这些数据。

为方便起见,允许使用非标量字段,并将其序列化为列表属性。例如,在构建 "面 "元素时,如果所有的面都是三角形(这种情况很常见),那么 "vertex_indices "字段的类型可以是 "i4 "和形状(3,),而不是对象类型和形状()。不过,如果使用 plyfile 回读序列化的 PLY 文件,"vertex_indices "属性将被表示为对象类型的字段,其每个值都是类型为 "i4"、长度为 3 的数组。 原因很简单,PLY 格式无法在不实际读取所有数据的情况下找出每个 "vertex_indices "字段的长度都是 3,因此 plyfile 必须假定这是一个长度可变的属性。不过,从列表属性中恢复二维数组的简便方法请参见常见问题,也请参见上文关于 known_list_len 参数的说明,以加快读取具有固定已知长度列表的文件。

例如,如果我们想将 tet.ply 数据中的 "顶点 "和 "面 "PLY 元素直接创建为 numpy 数组,以便进行序列化,我们可以这样做:

>>> vertex = numpy.array([(0, 0, 0),
...                       (0, 1, 1),
...                       (1, 0, 1),
...                       (1, 1, 0)],
...                      dtype=[('x', 'f4'), ('y', 'f4'),
...                             ('z', 'f4')])
>>> face = numpy.array([([0, 1, 2], 255, 255, 255),
...                     ([0, 2, 3], 255,   0,   0),
...                     ([0, 1, 3],   0, 255,   0),
...                     ([1, 2, 3],   0,   0, 255)],
...                    dtype=[('vertex_indices', 'i4', (3,)),
...                           ('red', 'u1'), ('green', 'u1'),
...                           ('blue', 'u1')])
>>>

创建PlyElement 实例


有了适当结构的数组后,使用静态方法 PlyElement.describe 来创建必要的 PlyElement 实例:

>>> el = PlyElement.describe(vertex, 'vertex')
>>>


或者

>>> el = PlyElement.describe(vertex, 'vertex',
...                          comments=['comment1',
...                                    'comment2'])
>>>


请注意,无需明确创建 PlyProperty 实例。这一切都是通过检查 some_array.dtype.descr 在幕后完成的。这里的一个小插曲是,numpy 数组(即我们对 PLY 列表属性的表示)中的可变长度字段必须具有对象类型,因此序列化 PLY 文件中列表长度和值的类型不能仅从数组的 dtype 属性中获取。为简化和可预测性起见,长度默认为 8 位无符号整数,值默认为 32 位有符号整数,这涵盖了大多数使用情况。例外情况必须明确说明:

>>> el = PlyElement.describe(face, 'face',
...                          val_types={'vertex_indices': 'u2'},
...                          len_types={'vertex_indices': 'u4'})
>>>


实例化 PlyData 并进行序列化


 

现在,您可以实例化 PlyData 并进行序列化:

>>> PlyData([el]).write('some_binary.ply')
>>> PlyData([el], text=True).write('some_ascii.ply')
>>>
>>> # Force the byte order of the output to big-endian, independently of
>>> # the machine's native byte order
>>> PlyData([el],
...         byte_order='>').write('some_big_endian_binary.ply')
>>>
>>> # Use a file object. Binary mode is used here, which will cause
>>> # Unix-style line endings to be written on all systems.
>>> with open('some_ascii.ply', mode='wb') as f:
...     PlyData([el], text=True).write(f)
>>>

其他操作

注释 - Comments

支持标题注释:

>>> ply = PlyData([el], comments=['header comment'])
>>> ply.comments
['header comment']
>>>

也支持 obj_info 注释:

>>> ply = PlyData([el], obj_info=['obj_info1', 'obj_info2'])
>>> ply.obj_info
['obj_info1', 'obj_info2']
>>>

写入时,它们将放在 "格式 "行后的常规注释之后。

注释可以有前导空白,但尾部空白可能会被删除,因此不应依赖尾部空白。注释不得包含内嵌换行符。

从列表中获取二维数组 property

PLY 格式无法保证给定列表属性的所有数据长度相同,但这种情况比较常见。例如,对于三角形网格,"面 "元素上的所有 "顶点指数 "数据长度都是 3。在这种情况下,通常将数据放在二维数组中要比对象类型的一维数组方便得多。下面是一个获取二维数组的简单方法:

>>> plydata = PlyData.read('tet.ply')
>>> tri_data = plydata['face'].data['vertex_indices']
>>> triangles = numpy.vstack(tri_data)
>>>

(如果预先知道所有列表属性的行长,也可以使用 known_list_len 参数)。

实例可变性 - Instance mutability

一种可行的代码模式是将 PLY 文件读入 PlyData 实例,对其执行一些操作(可能会修改数据和元数据),然后将结果写入新文件。这种模式得到了部分支持。可以进行以下就地修改:

  • 仅修改数字数组数据。
  • 直接为 PlyData 实例的元素赋值。
  • 通过更改 PlyData 实例的 text 和 byte_order 属性来切换格式。这将在 ascii、二进制_little_endian 和二进制_big_endian PLY 格式之间切换。
  • 修改 PlyData 实例的注释和 obj_info,以及修改 PlyElement 实例的注释。
  • 为元素数据赋值。请注意,properties 中的属性元数据不会受到影响,因此对于 PlyElement 实例属性列表中的每个属性,数据数组都必须有一个相同名称的字段(但可能类型不同,顺序也可能不同)。数组还可以有其他字段,但在将元素写入 PLY 文件时不会输出。输出文件中的属性将显示在属性列表中。如果一个数组字段的类型与相应的 PlyProperty 实例不同,那么在写入时它将被转换。
  • 直接为元素属性赋值。请注意,数据数组不会被触及,前面关于属性和数据之间关系的说明仍然适用:数据的字段名必须是属性中的属性名的超集,但它们的顺序可以不同,指定的类型也可以不同。
  • 更改 PlyProperty 或 PlyListProperty 实例的 val_dtype 或 PlyListProperty 实例的 len_dtype,这将在写入时执行转换。

不支持修改 PlyElement、PlyProperty 或 PlyListProperty 实例的名称,否则会引发错误。要重命名 PlyElement 实例的属性,可以从属性中移除该属性,重命名数据中的字段,然后通过创建新的 PlyProperty 或 PlyListProperty 实例,用新名称将该属性重新添加到属性中:

>>> from plyfile import PlyProperty, PlyListProperty
>>> face = plydata['face']
>>> face.properties = ()
>>> face.data.dtype.names = ['idx', 'r', 'g', 'b']
>>> face.properties = (PlyListProperty('idx', 'uchar', 'int'),
...                    PlyProperty('r', 'uchar'),
...                    PlyProperty('g', 'uchar'),
...                    PlyProperty('b', 'uchar'))
>>>

请注意,创建一个新的 PlyElement 或 PlyData 实例总是安全的,而不是就地修改一个实例,这也是推荐的样式:

>>> # Recommended:
>>> plydata = PlyData([plydata['face'], plydata['vertex']],
...                   text=False, byte_order='<')
>>>
>>> # Also supported:
>>> plydata.elements = [plydata['face'], plydata['vertex']]
>>> plydata.text = False
>>> plydata.byte_order = '<'
>>> plydata.comments = []
>>> plydata.obj_info = []
>>>

本库创建的对象并不拥有其引用的其他对象的所有权,这对两种方式(创建新实例和就地修改)都有影响。例如,一个 PlyElement 实例可以包含多个 PlyData 实例,但修改该实例将影响所有包含 PlyData 的实例。

文本模式数据流 - Text-mode streams

ASCII 格式的 PLY 文件支持文本模式数据流的输入和输出,但不支持二进制格式的 PLY 文件。支持所有有效 PLY 文件在二进制流上的输入和输出。请注意,sys.stdout 和 sys.stdin 是文本流,因此只能直接用于 ASCII 格式的 PLY 文件。

[1] Also see the section on known_list_len.

  • 50
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值