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 EdgeFilter117 """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 136408138 """Basic type which implements the header of a EGM file.""" 141 144182146 return self.__str__()147149 """Return a hash value for this value. 150 151 :return: An immutable object that can be used as a hash. 152 """ 153 return None154156 """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)167169 """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"))175186 189 192 195 198211 212 @staticmethod200 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 208214 """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 -1231233 """A class to contain the actual egm data."""352235 self.header = EgmFormat.Header() 236 self.header.num_vertices = num_vertices 237 self.sym_morphs = [] 238 self.asym_morphs = [] 239 self.version = version240242 """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 258260 """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 273275 """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?')296298 """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)311313 """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 morph318320 """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 morph325327 """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 340342 for morph in self.sym_morphs: 343 yield morph 344 for morph in self.asym_morphs: 345 yield morph346354 """ 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)373375 # 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)392394 """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
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:05 2011 | http://epydoc.sourceforge.net |