| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 :mod:`pyffi.formats.egm` --- EGM (.egm)
3 =======================================
4
5 An .egm file contains facial shape modifiers, that is, morphs that modify
6 static properties of the face, such as nose size, chin shape, and so on.
7
8 Implementation
9 --------------
10
11 .. autoclass:: EgmFormat
12 :show-inheritance:
13 :members:
14
15 Regression tests
16 ----------------
17
18 Read a EGM file
19 ^^^^^^^^^^^^^^^
20
21 >>> # check and read egm file
22 >>> stream = open('tests/egm/mmouthxivilai.egm', 'rb')
23 >>> data = EgmFormat.Data()
24 >>> data.inspect_quick(stream)
25 >>> data.version
26 2
27 >>> data.inspect(stream)
28 >>> data.header.num_vertices
29 89
30 >>> data.header.num_sym_morphs
31 50
32 >>> data.header.num_asym_morphs
33 30
34 >>> data.header.time_date_stamp
35 2001060901
36 >>> data.read(stream)
37 >>> data.sym_morphs[0].vertices[0].x
38 17249
39
40 Parse all EGM files in a directory tree
41 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42
43 >>> for stream, data in EgmFormat.walkData('tests/egm'):
44 ... print(stream.name)
45 tests/egm/mmouthxivilai.egm
46
47 Create an EGM file from scratch and write to file
48 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49
50 >>> data = EgmFormat.Data(num_vertices=10)
51 >>> data.header.num_vertices
52 10
53 >>> morph = data.add_sym_morph()
54 >>> len(morph.vertices)
55 10
56 >>> morph.scale = 0.4
57 >>> morph.vertices[0].z = 123
58 >>> morph.vertices[9].x = -30000
59 >>> morph = data.add_asym_morph()
60 >>> morph.scale = 2.3
61 >>> morph.vertices[3].z = -5
62 >>> morph.vertices[4].x = 99
63 >>> from tempfile import TemporaryFile
64 >>> stream = TemporaryFile()
65 >>> data.write(stream)
66 """
67
68 # ***** BEGIN LICENSE BLOCK *****
69 #
70 # Copyright (c) 2007-2011, Python File Format Interface
71 # All rights reserved.
72 #
73 # Redistribution and use in source and binary forms, with or without
74 # modification, are permitted provided that the following conditions
75 # are met:
76 #
77 # * Redistributions of source code must retain the above copyright
78 # notice, this list of conditions and the following disclaimer.
79 #
80 # * Redistributions in binary form must reproduce the above
81 # copyright notice, this list of conditions and the following
82 # disclaimer in the documentation and/or other materials provided
83 # with the distribution.
84 #
85 # * Neither the name of the Python File Format Interface
86 # project nor the names of its contributors may be used to endorse
87 # or promote products derived from this software without specific
88 # prior written permission.
89 #
90 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
91 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
92 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
93 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
94 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
95 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
96 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
97 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
98 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
99 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
100 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
101 # POSSIBILITY OF SUCH DAMAGE.
102 #
103 # ***** END LICENSE BLOCK *****
104
105 from itertools import izip
106 import struct
107 import os
108 import re
109
110 import pyffi.object_models.xml
111 import pyffi.object_models.common
112 from pyffi.object_models.xml.basic import BasicBase
113 import pyffi.object_models
114 from pyffi.utils.graph import EdgeFilter
117 """This class implements the EGM format."""
118 xml_file_name = 'egm.xml'
119 # where to look for egm.xml and in what order:
120 # EGMXMLPATH env var, or EgmFormat module directory
121 xml_file_path = [os.getenv('EGMXMLPATH'), os.path.dirname(__file__)]
122 # file name regular expression match
123 RE_FILENAME = re.compile(r'^.*\.egm$', re.IGNORECASE)
124
125 # basic types
126 int = pyffi.object_models.common.Int
127 uint = pyffi.object_models.common.UInt
128 byte = pyffi.object_models.common.Byte
129 ubyte = pyffi.object_models.common.UByte
130 char = pyffi.object_models.common.Char
131 short = pyffi.object_models.common.Short
132 ushort = pyffi.object_models.common.UShort
133 float = pyffi.object_models.common.Float
134
135 # implementation of egm-specific basic types
136
138 """Basic type which implements the header of a EGM file."""
141
144
146 return self.__str__()
147
149 """Return a hash value for this value.
150
151 :return: An immutable object that can be used as a hash.
152 """
153 return None
154
156 """Read header string from stream and check it.
157
158 :param stream: The stream to read from.
159 :type stream: file
160 """
161 hdrstr = stream.read(5)
162 # check if the string is correct
163 if hdrstr != "FREGM".encode("ascii"):
164 raise ValueError(
165 "invalid EGM header: expected 'FREGM' but got '%s'"
166 % hdrstr)
167
169 """Write the header string to stream.
170
171 :param stream: The stream to write to.
172 :type stream: file
173 """
174 stream.write("FREGM".encode("ascii"))
175
182
186
189
192
195
198
200 ver = stream.read(3)
201 if ver != ('%03i' % data.version).encode("ascii"):
202 raise ValueError(
203 "Invalid version number: expected b'%03i' but got %s."
204 % (data.version, ver))
205
208
211
212 @staticmethod
214 """Converts version string into an integer.
215
216 :param version_str: The version string.
217 :type version_str: str
218 :return: A version integer.
219
220 >>> EgmFormat.version_number('002')
221 2
222 >>> EgmFormat.version_number('XXX')
223 -1
224 """
225 try:
226 # note: always '002' in all files seen so far
227 return int(version_str)
228 except ValueError:
229 # not supported
230 return -1
231
233 """A class to contain the actual egm data."""
235 self.header = EgmFormat.Header()
236 self.header.num_vertices = num_vertices
237 self.sym_morphs = []
238 self.asym_morphs = []
239 self.version = version
240
242 """Quickly checks if stream contains EGM data, and gets the
243 version, by looking at the first 8 bytes.
244
245 :param stream: The stream to inspect.
246 :type stream: file
247 """
248 pos = stream.tell()
249 try:
250 hdrstr = stream.read(5)
251 if hdrstr != "FREGM".encode("ascii"):
252 raise ValueError("Not an EGM file.")
253 self.version = EgmFormat.version_number(stream.read(3))
254 finally:
255 stream.seek(pos)
256
257 # overriding pyffi.object_models.FileFormat.Data methods
258
260 """Quickly checks if stream contains EGM data, and reads the
261 header.
262
263 :param stream: The stream to inspect.
264 :type stream: file
265 """
266 pos = stream.tell()
267 try:
268 self.inspect_quick(stream)
269 self.header.read(stream, self)
270 finally:
271 stream.seek(pos)
272
273
275 """Read a egm file.
276
277 :param stream: The stream from which to read.
278 :type stream: ``file``
279 """
280 # read the file
281 self.inspect_quick(stream)
282 self.header.read(stream, self)
283 self.sym_morphs = [
284 EgmFormat.MorphRecord(argument=self.header.num_vertices)
285 for i in xrange(self.header.num_sym_morphs)]
286 self.asym_morphs = [
287 EgmFormat.MorphRecord(argument=self.header.num_vertices)
288 for i in xrange(self.header.num_asym_morphs)]
289 for morph in self.sym_morphs + self.asym_morphs:
290 morph.read(stream, self)
291
292 # check if we are at the end of the file
293 if stream.read(1):
294 raise ValueError(
295 'end of file not reached: corrupt egm file?')
296
298 """Write a egm file.
299
300 :param stream: The stream to which to write.
301 :type stream: ``file``
302 """
303 # write the file
304 self.header.num_sym_morphs = len(self.sym_morphs)
305 self.header.num_asym_morphs = len(self.asym_morphs)
306 self.header.write(stream, self)
307 for morph in self.sym_morphs + self.asym_morphs:
308 if morph.arg != self.header.num_vertices:
309 raise ValueError("invalid morph length")
310 morph.write(stream, self)
311
313 """Add a symmetric morph, and return it."""
314 morph = EgmFormat.MorphRecord(argument=self.header.num_vertices)
315 self.sym_morphs.append(morph)
316 self.header.num_sym_morphs = len(self.sym_morphs)
317 return morph
318
320 """Add an asymmetric morph, and return it."""
321 morph = EgmFormat.MorphRecord(argument=self.header.num_vertices)
322 self.asym_morphs.append(morph)
323 self.header.num_asym_morphs = len(self.asym_morphs)
324 return morph
325
327 """Apply scale factor to all morphs."""
328 for morph in self.sym_morphs + self.asym_morphs:
329 morph.apply_scale(scale)
330
331 # DetailNode
332
335
338
339 # GlobalNode
340
342 for morph in self.sym_morphs:
343 yield morph
344 for morph in self.asym_morphs:
345 yield morph
346
352
354 """
355 >>> # create morph with 3 vertices.
356 >>> morph = EgmFormat.MorphRecord(argument=3)
357 >>> morph.set_relative_vertices(
358 ... [(3, 5, 2), (1, 3, 2), (-9, 3, -1)])
359 >>> # scale should be 9/32768.0 = 0.0002746...
360 >>> morph.scale # doctest: +ELLIPSIS
361 0.0002746...
362 >>> for vert in morph.get_relative_vertices():
363 ... print([int(1000 * x + 0.5) for x in vert])
364 [3000, 5000, 2000]
365 [1000, 3000, 2000]
366 [-8999, 3000, -999]
367 """
369 for vert in self.vertices:
370 yield (vert.x * self.scale,
371 vert.y * self.scale,
372 vert.z * self.scale)
373
375 # copy to list
376 vertices = list(vertices)
377 # check length
378 if len(vertices) != self.arg:
379 raise ValueError("expected %i vertices, but got %i"
380 % (self.arg, len(vertices)))
381 # get extreme values of morph
382 max_value = max(max(abs(value) for value in vert)
383 for vert in vertices)
384 # calculate scale
385 self.scale = max_value / 32767.0
386 inv_scale = 1 / self.scale
387 # set vertices
388 for vert, self_vert in izip(vertices, self.vertices):
389 self_vert.x = int(vert[0] * inv_scale)
390 self_vert.y = int(vert[1] * inv_scale)
391 self_vert.z = int(vert[2] * inv_scale)
392
394 """Apply scale factor to data.
395
396 >>> # create morph with 3 vertices.
397 >>> morph = EgmFormat.MorphRecord(argument=3)
398 >>> morph.set_relative_vertices(
399 ... [(3, 5, 2), (1, 3, 2), (-9, 3, -1)])
400 >>> morph.apply_scale(2)
401 >>> for vert in morph.get_relative_vertices():
402 ... print([int(1000 * x + 0.5) for x in vert])
403 [6000, 10000, 4000]
404 [2000, 6000, 4000]
405 [-17999, 6000, -1999]
406 """
407 self.scale *= scale
408
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:05 2011 | http://epydoc.sourceforge.net |