Package pyffi :: Package formats :: Package egm
[hide private]
[frames] | no frames]

Source Code for Package pyffi.formats.egm

  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 
115 116 -class EgmFormat(pyffi.object_models.xml.FileFormat):
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
137 - class FileSignature(BasicBase):
138 """Basic type which implements the header of a EGM file."""
139 - def __init__(self, **kwargs):
140 BasicBase.__init__(self, **kwargs)
141
142 - def __str__(self):
143 return 'FREGM'
144
145 - def get_detail_display(self):
146 return self.__str__()
147
148 - def get_hash(self, data=None):
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
155 - def read(self, stream, data):
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
168 - def write(self, stream, data):
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
176 - def get_size(self, data=None):
177 """Return number of bytes the header string occupies in a file. 178 179 :return: Number of bytes. 180 """ 181 return 5
182
183 - class FileVersion(BasicBase):
184 - def get_value(self):
185 raise NotImplementedError
186
187 - def set_value(self, value):
188 raise NotImplementedError
189
190 - def __str__(self):
191 return 'XXX'
192
193 - def get_size(self, data=None):
194 return 3
195
196 - def get_hash(self, data=None):
197 return None
198
199 - def read(self, stream, data):
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
206 - def write(self, stream, data):
207 stream.write(('%03i' % data.version).encode("ascii"))
208
209 - def get_detail_display(self):
210 return 'XXX'
211 212 @staticmethod
213 - def version_number(version_str):
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
232 - class Data(pyffi.object_models.FileFormat.Data):
233 """A class to contain the actual egm data."""
234 - def __init__(self, version=2, num_vertices=0):
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
241 - def inspect_quick(self, stream):
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
259 - def inspect(self, stream):
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
274 - def read(self, stream):
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
297 - def write(self, stream):
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
312 - def add_sym_morph(self):
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
319 - def add_asym_morph(self):
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
326 - def apply_scale(self, scale):
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
333 - def get_detail_child_nodes(self, edge_filter=EdgeFilter()):
334 return self.header.get_detail_child_nodes(edge_filter=edge_filter)
335
336 - def get_detail_child_names(self, edge_filter=EdgeFilter()):
337 return self.header.get_detail_child_names(edge_filter=edge_filter)
338 339 # GlobalNode 340
341 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
342 for morph in self.sym_morphs: 343 yield morph 344 for morph in self.asym_morphs: 345 yield morph
346
347 - def get_global_child_names(self, edge_filter=EdgeFilter()):
348 for morph in self.sym_morphs: 349 yield "Sym Morph" 350 for morph in self.asym_morphs: 351 yield "Asym Morph"
352
353 - class MorphRecord:
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 """
368 - def get_relative_vertices(self):
369 for vert in self.vertices: 370 yield (vert.x * self.scale, 371 vert.y * self.scale, 372 vert.z * self.scale)
373
374 - def set_relative_vertices(self, vertices):
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
393 - def apply_scale(self, scale):
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