Skip to main content

PLY 文件读取器/写入器

项目描述

构建状态

欢迎使用plyfilePython 模块,它为读写 ASCII 和二进制 PLY 文件提供了一个简单的工具。

PLY 格式记录 在别处。

安装

依赖项

  • python2 >= 2.7 或 python3
  • numpy >= 1.11

plyfile可能适用于旧版本,也可能不适用于旧numpy版本。例如,请注意1.9.2 版本之前的 1.9 有一个错误,该错误会通过操作实例的字段numpy来破坏字节交换。(作为一种解决方法,除了更改 属性之外,您还可以使用手动字节交换数组。)byte_orderPlyDatael.data = el.data.byteswap().newbyteorder()byte_order

可选依赖项

  • setuptools(通过 setup.py 安装)
  • tox(用于测试套件)
  • py.test 和 py(用于测试套件)

安装 plyfile

快捷方式:

pip install plyfile

或者克隆存储库并从项目根目录运行:

python setup.py install

或者只是复制plyfile.py到您的 GPL 兼容项目中。

运行测试套件

首选(更全面;需要 tox 和 setuptools):

tox -v

备用(需要 py.test 和 py):

py.test test -v

用法

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

>>> 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

(此文件在examples目录下可用。)

读取 PLY 文件

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

或者

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

静态方法PlyData.read返回一个PlyData实例,它是 plyfilePLY 文件中数据的表示形式。一个PlyData 实例有一个属性elements,它是一个PlyElement 实例列表,每个实例都有一个属性,它是data一个numpy 包含数值数据的结构化数组。PLY 文件元素以一种非常明显的方式映射到numpy结构化数组。对于元素中的列表属性,对应的numpy字段类型是object,成员是numpy数组(参见 vertex_indices下面的示例)。

具体来说:

>>> plydata.elements[0].name
'vertex'
>>> plydata.elements[0].data[0]
(0.0, 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)

和元素可以直接索引而无需显式通过data属性:

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

上面的表达式等价于plydata['vertex'].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')

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

创建 PLY 文件

第一步是将您的数据放入numpy结构化数组中。注意有一些限制:一般来说,如果你知道一个 PLY 文件元素可以包含的属性类型,你可以很容易地推断出这些限制。例如,PLY 文件不包含 64 位整数或复数数据,因此这些是不允许的。

为方便起见,允许使用非标量字段;它们将被序列化为列表属性。例如,在构造“face”元素时,如果所有的面都是三角形(很常见),可以使用 type 和 shape 的“vertex_indices”字段'i4'而(3,) 不是 typeobject和 shape ()。但是,如果在 using 中读回序列化的 PLY 文件plyfile,“vertex_indices”属性将表示为object-typed 字段,每个字段的值都是类型和长度为 3 的数组。'i4'原因很简单,PLY 格式不提供在没有实际读取所有数据的情况下找出每个“vertex_indices”字段的长度为 3 的方法,因此plyfile必须假设这是一个可变长度属性。 examples/plot.py) 用于从列表属性中恢复二维数组的简单方法。

例如,如果我们想将tet.ply数据的“顶点”和“面”PLY 元素直接创建为numpy数组以进行序列化,我们可以这样做(如中所示test/test.py):

>>> 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.describe就可以用来创建必要的 PlyElement实例:

>>> el = PlyElement.describe(some_array, 'some_name')

或者

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

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

>>> el = PlyElement.describe(some_array, 'some_name',
...                          val_dtypes={'some_property': 'f8'},
...                          len_dtypes={'some_property': 'u4'})

现在您可以实例化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)

各种各样的

注释

支持标题注释:

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

从 0.3 版开始,还支持“obj_info”注释:

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

编写时,它们将放置在“格式”行之后的常规注释之后。

注释可以有前导空格,但尾随空格可能会被剥离,不应依赖。评论可能不包含嵌入的换行符。

从列表属性中获取二维数组

PLY 格式无法断言给定列表属性的所有数据都具有相同的长度,但这是相对常见的情况。例如,对于三角形网格,“面”元素上的所有“顶点索引”数据的长度都为 3。在这种情况下,将数据放在二维数组中通常要方便得多,而不是类型为 的一维数组object。假设我们事先知道行长,这是获取二维数组的一种非常简单的方法:

>>> plydata = PlyData.read('tet.ply')
>>>  tri_data  =  plydata [ 'face' ] 。数据[ 'vertex_indices' ] 
>>> 三角形 =  numpy 。vstack ( tri_data )

实例可变性

一个合理的代码模式是将 PLY 文件读入一个PlyData 实例,对其执行一些操作,可能修改数据和元数据,然后将结果写入一个新文件。部分支持此模式。从 0.4 版开始,支持以下就地突变:

  • 仅修改数值数组数据。
  • 直接分配给PlyData实例的elements.
  • 通过更改实例的text和byte_order属性来切换格式。PlyData这将在ascii、 binary_little_endian和binary_big_endianPLY 格式之间切换。
  • 修改PlyData实例的commentsandobj_info和修改PlyElement实例的comments.
  • 分配给元素的data. 请注意,其中的属性元数据 properties不受此影响,因此对于 实例properties列表中的每个属性,数组必须具有同名的字段(但可能类型不同,并且可能顺序不同)。数组也可以有其他字段,但在将元素写入 PLY 文件时不会输出它们。输出文件中的属性将显示在 列表中。如果数组字段的类型与对应的实例不同,则在写入时将被强制转换。PlyElementdatapropertiesPlyProperty
  • 直接分配给元素的properties. 请注意, data数组没有被触及,之前关于 和 之间关系的注释properties仍然data适用: 的字段名称data必须是 中的属性名称的子集 properties,但它们可以按不同的顺序并指定不同的类型。
  • 更改 a PlyPropertyor PlyListPropertyinstance's val_dtype or a PlyListPropertyinstance's len_dtype,这将在写入时执行强制转换。

不支持修改、或 实例name的,并且会引发错误。要重命名实例的属性,您可以从 中删除属性,重命名 中的字段,然后通过创建新的 或实例来使用新名称重新添加属性:PlyElementPlyPropertyPlyListPropertyPlyElementpropertiesdatapropertiesPlyPropertyPlyListProperty

>>> 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实例。

常问问题

如何从二维数组初始化列表属性?

>>> # Here's a two-dimensional array containing vertex indices.
>>> face_data = numpy.array([[0, 1, 2], [3, 4, 5]], dtype='i4')

>>> # PlyElement.describe requires a one-dimensional structured array.
>>> ply_faces = numpy.empty(len(faces),
...                         dtype=[('vertex_indices', 'i4', (3,))])
>>> ply_faces['vertex_indices'] = face_data
>>> face = PlyElement.describe(ply_faces, 'face')

我可以将 PLY 文件直接保存到sys.stdout?

在 Python 3 上,您可能会遇到问题,因为sys.stdout它是文本模式流并plyfile输出二进制数据,即使对于 ASCII 格式的 PLY 文件也是如此:

>>> import sys
>>> plydata.write(sys.stdout)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    File ".../python-plyfile/plyfile.py", line 411, in write
        stream.write(self.header.encode('ascii'))
        TypeError: write() argument must be str, not bytes

有几种方法可以解决这个问题。

  • 改为写入命名文件。在 Linux 和其他一些类 Unix 上,您可以stdout通过命名文件访问/dev/stdout:

    >>> plydata.write('/dev/stdout')
    
  • 使用sys.stdout.buffer:

    >>> plydata.write(sys.stdout.buffer)
    

    (来源:https ://bugs.python.org/issue4571 )

设计理念和基本原理

的设计理念plyfile可以总结如下。

  • 尽可能让用户熟悉numpy并重用现有的习语和概念。
  • 倾向于简单而不是功能或用户友好性。
  • 支持所有有效的 PLY 文件。

熟悉度

在大多数情况下,PLY 概念很好地映射到 Python,特别是映射到 numpy,并利用它强烈影响了这个包的设计。实例的elements属性PlyData只是实例的一个 list,PlyElement实例的data属性 PlyElement是一个numpy数组,PLY元素数据的列表属性字段在data属性中被一个类型 引用object,值为另一个numpy数组等。

简单

在适用的情况下,我们更倾向于简单而不是功能或用户友好性。因此,列表属性类型PlyElement.describe始终默认为相同,而不是通过查看数组元素获得。(哪个元素?如果数组的长度为零怎么办?如果用户不警惕,我们在这种情况下可以选择的任何默认值都可能导致微妙的边缘情况错误。)此外,所有输入和输出都是“一次性”完成的:所有数组都必须预先创建,而不是以流方式处理。

一般性和解释问题

我们的目标是支持所有有效的 PLY 文件。然而,究竟是什么构成了“有效”文件并不明显,因为似乎没有一个完整且一致的 PLY 格式描述。即使是 Greg Turk 的“权威” Ply.txt 也存在一些问题。

评论位置

注释可以出现在页眉的什么位置?似乎在所有“官方”示例中,所有评论都紧跟在“格式”行之后,但文档的语言既没有设置任何此类限制,也没有明确允许将评论放在其他任何地方。因此,尚不清楚注释是否可以出现在标题中的任何位置或必须紧跟在“格式”行之后。至少有一位流行的 PLY 文件阅读器在“格式”行之前的评论中窒息。 plyfile 接受输入标题中任何位置的注释,但仅将它们放置在输出中的几个有限位置,即紧跟在“格式”和“元素”行之后。

元素和属性名称

另一个歧义是名称:允许哪些字符串作为 PLY 元素和属性名称? plyfile接受任何不包含空格的名称作为输入,但这肯定太慷慨了。不过,这可能不是什么大问题:尽管名称在理论上是任意的,但在实践中,大多数 PLY 元素和属性名称可能来自一个小的有限集(“face”、“x”、“nx”、“green “, ETC。)。

属性语法

一个更严重的问题是 PLY 格式规范在属性定义的语法方面似乎不一致。在某些示例中,它使用语法

property {type} {name}

在其他人中,

property {name} {type}

plyfile仅支持前者,这似乎是事实上的标准。

标题行结尾

规范明确规定标题中的行必须以回车符结尾,但似乎没有人遵循这条规则,包括该格式的作者 Greg Turk 的 C 语言 PLY 实现。在这里,我们坚持常规做法并输出 Unix 样式的行尾(没有回车),但接受输入文件中的任何行尾样式。

更多示例

超出本文档范围的示例和测试在 examples目录中。

执照

版权所有 Darsh Ranjan。

该软件是根据 GNU 通用公共许可证第 3 版的条款发布的。有关COPYING详细信息,请参阅文件。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

plyfile-0.7.4.tar.gz (39.5 kB 查看哈希)

已上传 source

内置分布

plyfile-0.7.4-py3-none-any.whl (39.6 kB 查看哈希)

已上传 py3