METADATA 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. Metadata-Version: 2.1
  2. Name: plyfile
  3. Version: 0.8.1
  4. Summary: PLY file reader/writer
  5. Keywords: ply,numpy
  6. Author-email: Darsh Ranjan <dranjan@berkeley.edu>
  7. Requires-Python: >=3.7
  8. Classifier: Development Status :: 4 - Beta
  9. Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
  10. Classifier: Operating System :: OS Independent
  11. Classifier: Programming Language :: Python :: 3
  12. Classifier: Programming Language :: Python :: 3.10
  13. Classifier: Programming Language :: Python :: 3.7
  14. Classifier: Programming Language :: Python :: 3.8
  15. Classifier: Programming Language :: Python :: 3.9
  16. Classifier: Topic :: Scientific/Engineering
  17. Requires-Dist: numpy>=1.17
  18. Project-URL: Homepage, https://github.com/dranjan/python-plyfile
  19. Description-Content-Type: text/markdown
  20. ![Build Status](https://github.com/dranjan/python-plyfile/actions/workflows/python-package.yml/badge.svg)
  21. Welcome to the `plyfile` Python module, which provides a simple facility
  22. for reading and writing ASCII and binary PLY files.
  23. The PLY format is documented
  24. [elsewhere](https://web.archive.org/web/20161221115231/http://www.cs.virginia.edu/~gfx/Courses/2001/Advanced.spring.01/plylib/Ply.txt).
  25. # Installation
  26. ## Dependencies
  27. - python3 >= 3.7
  28. - numpy >= 1.17
  29. (`plyfile` may or may not work on older versions.)
  30. ### Optional dependencies
  31. - tox (for test suite)
  32. - pytest (for test suite)
  33. ## Installing plyfile
  34. Quick way:
  35. pip3 install plyfile
  36. Or clone the repository and run from the project root:
  37. pip3 install .
  38. Or just copy `plyfile.py` into your GPL-compatible project.
  39. ## Running test suite
  40. Preferred (more comprehensive; requires tox):
  41. tox -v --skip-missing-interpreters
  42. Alternate (requires pytest):
  43. pytest test -v
  44. # Usage
  45. Both deserialization and serialization of PLY file data is done through
  46. `PlyData` and `PlyElement` instances.
  47. ```Python Console
  48. >>> from plyfile import PlyData, PlyElement
  49. ```
  50. For the code examples that follow, assume the file `tet.ply` contains
  51. the following text:
  52. ply
  53. format ascii 1.0
  54. comment single tetrahedron with colored faces
  55. element vertex 4
  56. comment tetrahedron vertices
  57. property float x
  58. property float y
  59. property float z
  60. element face 4
  61. property list uchar int vertex_indices
  62. property uchar red
  63. property uchar green
  64. property uchar blue
  65. end_header
  66. 0 0 0
  67. 0 1 1
  68. 1 0 1
  69. 1 1 0
  70. 3 0 1 2 255 255 255
  71. 3 0 2 3 255 0 0
  72. 3 0 1 3 0 255 0
  73. 3 1 2 3 0 0 255
  74. (This file is available under the `examples` directory.)
  75. ## Reading a PLY file
  76. ```Python Console
  77. >>> plydata = PlyData.read('tet.ply')
  78. ```
  79. or
  80. ```Python Console
  81. >>> with open('tet.ply', 'rb') as f:
  82. ... plydata = PlyData.read(f)
  83. ```
  84. The static method `PlyData.read` returns a `PlyData` instance, which is
  85. `plyfile`'s representation of the data in a PLY file. A `PlyData`
  86. instance has an attribute `elements`, which is a list of `PlyElement`
  87. instances, each of which has a `data` attribute which is a `numpy`
  88. structured array containing the numerical data. PLY file elements map
  89. onto `numpy` structured arrays in a pretty obvious way. For a list
  90. property in an element, the corresponding `numpy` field type
  91. is `object`, with the members being `numpy` arrays (see the
  92. `vertex_indices` example below).
  93. Concretely:
  94. ```Python Console
  95. >>> plydata.elements[0].name
  96. 'vertex'
  97. >>> plydata.elements[0].data[0]
  98. (0.0, 0.0, 0.0)
  99. >>> plydata.elements[0].data['x']
  100. array([ 0., 0., 1., 1.], dtype=float32)
  101. >>> plydata['face'].data['vertex_indices'][0]
  102. array([0, 1, 2], dtype=int32)
  103. ```
  104. For convenience, elements and properties can be looked up by name:
  105. ```Python Console
  106. >>> plydata['vertex']['x']
  107. array([ 0., 0., 1., 1.], dtype=float32)
  108. ```
  109. and elements can be indexed directly without explicitly going through
  110. the `data` attribute:
  111. ```Python Console
  112. >>> plydata['vertex'][0]
  113. (0.0, 0.0, 0.0)
  114. ```
  115. The above expression is equivalent to `plydata['vertex'].data[0]`.
  116. `PlyElement` instances also contain metadata:
  117. ```Python Console
  118. >>> plydata.elements[0].properties
  119. (PlyProperty('x', 'float'), PlyProperty('y', 'float'),
  120. PlyProperty('z', 'float'))
  121. >>> plydata.elements[0].count
  122. 4
  123. ```
  124. `PlyProperty` and `PlyListProperty` instances are used internally as a
  125. convenient intermediate representation of PLY element properties that
  126. can easily be serialized to a PLY header (using `str`) or converted to
  127. `numpy`-compatible type descriptions (via the `dtype` method). It's not
  128. extremely common to manipulate them directly, but if needed, the
  129. property metadata of an element can be accessed as a tuple via the
  130. `properties` attribute (as illustrated above) or looked up by name:
  131. ```Python Console
  132. >>> plydata.elements[0].ply_property('x')
  133. PlyProperty('x', 'float')
  134. ```
  135. Many (but not necessarily all) types of malformed input files will raise
  136. `PlyParseError` when `PlyData.read` is called. The string value of the
  137. `PlyParseError` instance (as well as attributes `element`, `row`, and
  138. `prop`) provides additional context for the error if applicable.
  139. ### Faster reading via memory mapping
  140. To accelerate parsing of binary data, `plyfile` can make use of
  141. `numpy`'s memory mapping facilities. The decision to memory map or not
  142. is made on a per-element basis. To make this determination, there are
  143. two cases to consider.
  144. #### Case 1: elements with no list properties
  145. If an element in a binary PLY file has no list properties, then it will
  146. be memory-mapped by default, subject to the capabilities of the
  147. underlying file object. Memory mapping can be disabled using the
  148. `mmap` argument:
  149. ```Python Console
  150. >>> plydata.text = False
  151. >>> plydata.byte_order = '<'
  152. >>> plydata.write('tet_binary.ply')
  153. >>>
  154. >>> # `mmap=True` is the default:
  155. >>> plydata = PlyData.read('tet_binary_ply')
  156. >>> isinstance(plydata['vertex'].data, numpy.memmap)
  157. True
  158. >>> plydata = PlyData.read('tet_binary_ply', mmap=False)
  159. >>> isinstance(plydata['vertex'].data, numpy.memmap)
  160. False
  161. ```
  162. #### Case 2: elements with list properties
  163. In the general case, elements with list properties cannot be
  164. memory-mapped as `numpy` arrays, except in one important case: when
  165. all list properties have fixed and known lengths. In that case, the
  166. `known_list_len` argument can be given to `PlyData.read`:
  167. ```Python Console
  168. >>> plydata = PlyData.read('tet_binary.ply',
  169. ... known_list_len={'face': {'vertex_indices': 3}})
  170. >>> isinstance(plydata['face'].data, numpy.memmap)
  171. True
  172. ```
  173. The implementation will validate the data: if any instance of the list
  174. property has a length other than the value specified, then
  175. `PlyParseError` will be raised.
  176. Note that in order to enable memory mapping for a given element,
  177. *all* list properties in the element must have their lengths in the
  178. `known_list_len` dictionary. If any list property does not have its
  179. length given in `known_list_len`, then memory mapping will not be
  180. attempted, and no error will be raised.
  181. ## Creating a PLY file
  182. The first step is to get your data into `numpy` structured arrays. Note
  183. that there are some restrictions: generally speaking, if you know the
  184. types of properties a PLY file element can contain, you can easily
  185. deduce the restrictions. For example, PLY files don't contain 64-bit
  186. integer or complex data, so these aren't allowed.
  187. For convenience, non-scalar fields **are** allowed; they will be
  188. serialized as list properties. For example, when constructing a "face"
  189. element, if all the faces are triangles (a common occurrence), it's okay
  190. to have a "vertex_indices" field of type `'i4'` and shape `(3,)`
  191. instead of type `object` and shape `()`. However, if the serialized PLY
  192. file is read back in using `plyfile`, the "vertex_indices" property will
  193. be represented as an `object`-typed field, each of whose values is an
  194. array of type `'i4'` and length 3. The reason is simply that the PLY
  195. format provides no way to find out that each "vertex_indices" field has
  196. length 3 without actually reading all the data, so `plyfile` has to
  197. assume that this is a variable-length property. However, see below (and
  198. `examples/plot.py`) for an easy way to recover a two-dimensional array
  199. from a list property, and also see the notes above about the
  200. `known_list_len` kwarg to speed up the reading of files with lists of
  201. fixed, known length.
  202. For example, if we wanted to create the "vertex" and "face" PLY elements
  203. of the `tet.ply` data directly as `numpy` arrays for the purpose of
  204. serialization, we could do (as in `test/test.py`):
  205. ```Python Console
  206. >>> vertex = numpy.array([(0, 0, 0),
  207. ... (0, 1, 1),
  208. ... (1, 0, 1),
  209. ... (1, 1, 0)],
  210. ... dtype=[('x', 'f4'), ('y', 'f4'),
  211. ... ('z', 'f4')])
  212. >>> face = numpy.array([([0, 1, 2], 255, 255, 255),
  213. ... ([0, 2, 3], 255, 0, 0),
  214. ... ([0, 1, 3], 0, 255, 0),
  215. ... ([1, 2, 3], 0, 0, 255)],
  216. ... dtype=[('vertex_indices', 'i4', (3,)),
  217. ... ('red', 'u1'), ('green', 'u1'),
  218. ... ('blue', 'u1')])
  219. ```
  220. Once you have suitably structured array, the static method
  221. `PlyElement.describe` can then be used to create the necessary
  222. `PlyElement` instances:
  223. ```Python Console
  224. >>> el = PlyElement.describe(some_array, 'some_name')
  225. ```
  226. or
  227. ```Python Console
  228. >>> el = PlyElement.describe(some_array, 'some_name',
  229. ... comments=['comment1',
  230. ... 'comment2'])
  231. ```
  232. Note that there's no need to create `PlyProperty` instances explicitly.
  233. This is all done behind the scenes by examining `some_array.dtype.descr`.
  234. One slight hiccup here is that variable-length fields in a `numpy` array
  235. (i.e., our representation of PLY list properties)
  236. must have a type of `object`, so the types of the list length and values
  237. in the serialized PLY file can't be obtained from the array's `dtype`
  238. attribute alone. For simplicity and predictability, the length
  239. defaults to 8-bit unsigned integer, and the value defaults to 32-bit
  240. signed integer, which covers the majority of use cases. Exceptions must
  241. be stated explicitly:
  242. ```Python Console
  243. >>> el = PlyElement.describe(some_array, 'some_name',
  244. ... val_dtypes={'some_property': 'f8'},
  245. ... len_dtypes={'some_property': 'u4'})
  246. ```
  247. Now you can instantiate `PlyData` and serialize:
  248. ```Python Console
  249. >>> PlyData([el]).write('some_binary.ply')
  250. >>> PlyData([el], text=True).write('some_ascii.ply')
  251. >>>
  252. >>> # Force the byte order of the output to big-endian, independently of
  253. >>> # the machine's native byte order
  254. >>> PlyData([el],
  255. ... byte_order='>').write('some_big_endian_binary.ply')
  256. >>>
  257. >>> # Use a file object. Binary mode is used here, which will cause
  258. >>> # Unix-style line endings to be written on all systems.
  259. >>> with open('some_ascii.ply', mode='wb') as f:
  260. ... PlyData([el], text=True).write(f)
  261. ```
  262. ## Miscellaneous
  263. ### Comments
  264. Header comments are supported:
  265. ```Python Console
  266. >>> ply = PlyData([el], comments=['header comment'])
  267. >>> ply.comments
  268. ['header comment']
  269. ```
  270. As of version 0.3, "obj_info" comments are supported as well:
  271. ```Python Console
  272. >>> ply = PlyData([el], obj_info=['obj_info1', 'obj_info2'])
  273. >>> ply.obj_info
  274. ['obj_info1', 'obj_info2']
  275. ```
  276. When written, they will be placed after regular comments after the
  277. "format" line.
  278. Comments can have leading whitespace, but trailing whitespace may be
  279. stripped and should not be relied upon. Comments may not contain
  280. embedded newlines.
  281. ### Getting a two-dimensional array from a list property
  282. The PLY format provides no way to assert that all the data for a given
  283. list property is of the same length, yet this is a relatively common
  284. occurrence. For example, all the "vertex_indices" data on a "face"
  285. element will have length three for a triangular mesh. In such cases,
  286. it's usually much more convenient to have the data in a two-dimensional
  287. array, as opposed to a one-dimensional array of type `object`. Here's a
  288. pretty easy way to obtain a two dimensional array, assuming we know the
  289. row length in advance:
  290. ```Python Console
  291. >>> plydata = PlyData.read('tet.ply')
  292. >>> tri_data = plydata['face'].data['vertex_indices']
  293. >>> triangles = numpy.vstack(tri_data)
  294. ```
  295. ### Instance mutability
  296. A plausible code pattern is to read a PLY file into a `PlyData`
  297. instance, perform some operations on it, possibly modifying data and
  298. metadata in place, and write the result to a new file. This pattern is
  299. partially supported. As of version 0.4, the following in-place
  300. mutations are supported:
  301. - Modifying numerical array data only.
  302. - Assigning directly to a `PlyData` instance's `elements`.
  303. - Switching format by changing the `text` and `byte_order` attributes of
  304. a `PlyData` instance. This will switch between `ascii`,
  305. `binary_little_endian`, and `binary_big_endian` PLY formats.
  306. - Modifying a `PlyData` instance's `comments` and `obj_info`, and
  307. modifying a `PlyElement` instance's `comments`.
  308. - Assigning to an element's `data`. Note that the property metadata in
  309. `properties` is not touched by this, so for every property in the
  310. `properties` list of the `PlyElement` instance, the `data` array must
  311. have a field with the same name (but possibly different type, and
  312. possibly in different order). The array can have additional fields as
  313. well, but they won't be output when writing the element to a PLY file.
  314. The properties in the output file will appear as they are in the
  315. `properties` list. If an array field has a different type than the
  316. corresponding `PlyProperty` instance, then it will be cast when
  317. writing.
  318. - Assigning directly to an element's `properties`. Note that the
  319. `data` array is not touched, and the previous note regarding the
  320. relationship between `properties` and `data` still applies: the field
  321. names of `data` must be a subset of the property names in
  322. `properties`, but they can be in a different order and specify
  323. different types.
  324. - Changing a `PlyProperty` or `PlyListProperty` instance's `val_dtype`
  325. or a `PlyListProperty` instance's `len_dtype`, which will perform
  326. casting when writing.
  327. Modifying the `name` of a `PlyElement`, `PlyProperty`, or
  328. `PlyListProperty` instance is not supported and will raise an error. To
  329. rename a property of a `PlyElement` instance, you can remove the
  330. property from `properties`, rename the field in `data`, and re-add the
  331. property to `properties` with the new name by creating a new
  332. `PlyProperty` or `PlyListProperty` instance:
  333. ```Python Console
  334. >>> from plyfile import PlyProperty, PlyListProperty
  335. >>> face = plydata['face']
  336. >>> face.properties = ()
  337. >>> face.data.dtype.names = ['idx', 'r', 'g', 'b']
  338. >>> face.properties = (PlyListProperty('idx', 'uchar', 'int'),
  339. ... PlyProperty('r', 'uchar'),
  340. ... PlyProperty('g', 'uchar'),
  341. ... PlyProperty('b', 'uchar'))
  342. ```
  343. Note that it is always safe to create a new `PlyElement` or `PlyData`
  344. instance instead of modifying one in place, and this is the recommended
  345. style:
  346. ```Python Console
  347. >>> # Recommended:
  348. >>> plydata = PlyData([plydata['face'], plydata['vertex']],
  349. ... text=False, byte_order='<')
  350. >>>
  351. >>> # Also supported:
  352. >>> plydata.elements = [plydata['face'], plydata['vertex']]
  353. >>> plydata.text = False
  354. >>> plydata.byte_order = '<'
  355. >>> plydata.comments = []
  356. >>> plydata.obj_info = []
  357. ```
  358. Objects created by this library don't claim ownership of the other
  359. objects they refer to, which has implications for both styles (creating
  360. new instances and modifying in place). For example, a single
  361. `PlyElement` instance can be contained by multiple `PlyData` instances,
  362. but modifying that instance will then affect all of those containing
  363. `PlyData` instances.
  364. # FAQ
  365. ## How do I initialize a list property from two-dimensional array?
  366. ```Python Console
  367. >>> # Here's a two-dimensional array containing vertex indices.
  368. >>> face_data = numpy.array([[0, 1, 2], [3, 4, 5]], dtype='i4')
  369. >>>
  370. >>> # PlyElement.describe requires a one-dimensional structured array.
  371. >>> ply_faces = numpy.empty(len(faces),
  372. ... dtype=[('vertex_indices', 'i4', (3,))])
  373. >>> ply_faces['vertex_indices'] = face_data
  374. >>> face = PlyElement.describe(ply_faces, 'face')
  375. ```
  376. ## Can I save a PLY file directly to `sys.stdout`?
  377. On Python 3, you will probably run into issues because `sys.stdout` is a
  378. text-mode stream and `plyfile` outputs binary data, even for
  379. ASCII-format PLY files:
  380. ```Python Console
  381. >>> import sys
  382. >>> plydata.write(sys.stdout)
  383. Traceback (most recent call last):
  384. File "<stdin>", line 1, in <module>
  385. File ".../python-plyfile/plyfile.py", line 411, in write
  386. stream.write(self.header.encode('ascii'))
  387. TypeError: write() argument must be str, not bytes
  388. ```
  389. There are a few ways around this.
  390. - Write to a named file instead. On Linux and some other Unix-likes, you
  391. can access `stdout` via the named file `/dev/stdout`:
  392. ```Python Console
  393. >>> plydata.write('/dev/stdout')
  394. ```
  395. - Use `sys.stdout.buffer`:
  396. ```Python Console
  397. >>> plydata.write(sys.stdout.buffer)
  398. ```
  399. (source: https://bugs.python.org/issue4571)
  400. # Design philosophy and rationale
  401. The design philosophy of `plyfile` can be summed up as follows.
  402. - Be familiar to users of `numpy` and reuse existing idioms and concepts
  403. when possible.
  404. - Favor simplicity over power or user-friendliness.
  405. - Support all valid PLY files.
  406. ## Familiarity
  407. For the most part, PLY concepts map nicely to Python and specifically to
  408. `numpy`, and leveraging that has strongly influenced the design of this
  409. package. The `elements` attribute of a `PlyData` instance is simply a
  410. `list` of `PlyElement` instances, and the `data` attribute of a
  411. `PlyElement` instance is a `numpy` array, and a list property field of a
  412. PLY element datum is referred to in the `data` attribute by a type of
  413. `object` with the value being another `numpy` array, etc.
  414. ## Simplicity
  415. When applicable, we favor simplicity over power or user-friendliness.
  416. Thus, list property types in `PlyElement.describe` always default to the
  417. same, rather than, say, being obtained by looking at an array element.
  418. (Which element? What if the array has length zero? Whatever default we
  419. could choose in that case could lead to subtle edge-case bugs if the
  420. user isn't vigilant.) Also, all input and output is done in "one shot":
  421. all the arrays must be created up front rather than being processed in a
  422. streaming fashion.
  423. ## Generality and interpretation issues
  424. We aim to support all valid PLY files. However, exactly what constitutes
  425. a "valid" file isn't obvious, since there doesn't seem to be a single
  426. complete and consistent description of the PLY format. Even the
  427. "authoritative"
  428. [Ply.txt](https://web.archive.org/web/20161221115231/http://www.cs.virginia.edu/~gfx/Courses/2001/Advanced.spring.01/plylib/Ply.txt)
  429. by Greg Turk has some issues.
  430. ### Comment placement
  431. Where can comments appear in the header? It appears that in all the
  432. "official" examples, all comments immediately follow the "format" line,
  433. but the language of the document neither places any such restrictions
  434. nor explicitly allows comments to be placed anywhere else. Thus, it
  435. isn't clear whether comments can appear anywhere in the header or must
  436. immediately follow the "format" line. At least one popular reader of
  437. PLY files chokes on comments before the "format" line. `plyfile`
  438. accepts comments anywhere in the header in input but only places them in
  439. a few limited places in output, namely immediately after "format" and
  440. "element" lines.
  441. ### Element and property names
  442. Another ambiguity is names: what strings are allowed as PLY element and
  443. property names? `plyfile` accepts as input any name that doesn't
  444. contain spaces, but this is surely too generous. This may not be such
  445. a big deal, though: although names are theoretically arbitrary, in
  446. practice, the majority of PLY element and property names probably come
  447. from a small finite set ("face", "x", "nx", "green", etc.).
  448. ### Property syntax
  449. A more serious problem is that the PLY format specification appears to
  450. be inconsistent regarding the syntax of property definitions. In
  451. some examples, it uses the syntax
  452. property {type} {name}
  453. and in others,
  454. property {name} {type}
  455. `plyfile` only supports the former, which appears to be standard _de
  456. facto_.
  457. ### Header line endings
  458. The specification explicitly states that lines in the header must
  459. end with carriage returns, but this rule doesn't seem to be followed by
  460. anyone, including the C-language PLY implementation by Greg Turk, the
  461. author of the format. Here, we stick to common practice and output
  462. Unix-style line endings (with no carriage returns) but accept any line
  463. ending style in input files.
  464. # More examples
  465. Examples beyond the scope of this document and the tests are in the
  466. `examples` directory.
  467. # License
  468. Copyright Darsh Ranjan.
  469. This software is released under the terms of the GNU General Public
  470. License, version 3. See the file `COPYING` for details.