瀏覽代碼

libpython: add library for animations (from visvis project)

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@58181 15284696-431f-4ddb-bdfa-cd5b030d7da7
Anna Petrášová 11 年之前
父節點
當前提交
01e28f6a24

+ 32 - 0
lib/python/imaging/Makefile

@@ -0,0 +1,32 @@
+MODULE_TOPDIR = ../../..
+
+include $(MODULE_TOPDIR)/include/Make/Other.make
+include $(MODULE_TOPDIR)/include/Make/Python.make
+include $(MODULE_TOPDIR)/include/Make/Doxygen.make
+
+PYDIR = $(ETC)/python
+GDIR = $(PYDIR)/grass
+DSTDIR = $(GDIR)/imaging
+
+MODULES = images2avi images2gif images2ims images2swf
+
+
+PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
+PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
+
+default: $(PYFILES) $(PYCFILES) $(GDIR)/__init__.py $(GDIR)/__init__.pyc
+
+$(PYDIR):
+	$(MKDIR) $@
+
+$(GDIR): | $(PYDIR)
+	$(MKDIR) $@
+
+$(DSTDIR): | $(GDIR)
+	$(MKDIR) $@
+
+$(DSTDIR)/%: % | $(DSTDIR)
+	$(INSTALL_DATA) $< $@
+
+#doxygen:
+DOXNAME = imaginglib

+ 40 - 0
lib/python/imaging/README

@@ -0,0 +1,40 @@
+General
+=======
+This is a part of visvis library [1], specifically visvis.vvmovie.
+The files are from the visvis-1.8 version.
+
+[1] https://code.google.com/p/visvis/
+
+Changes
+=======
+In images2avi, the image format for temporary files is changed
+from JPG to PNG, to improve the resulting AVI. This makes the size larger,
+however the JPG format is unsuitable for maps in general.
+
+--- imaging/images2avi.py	2013-11-09 21:46:28.000000000 -0500
++++ visvis-1.8/vvmovie/images2avi.py	2012-04-25 17:15:40.000000000 -0400
+@@ -79,7 +79,7 @@
+     
+     # Determine temp dir and create images
+     tempDir = os.path.join( os.path.expanduser('~'), '.tempIms')
+-    images2ims.writeIms( os.path.join(tempDir, 'im*.png'), images)
++    images2ims.writeIms( os.path.join(tempDir, 'im*.jpg'), images)
+     
+     # Determine formatter
+     N = len(images)
+@@ -93,7 +93,7 @@
+     
+     # Compile command to create avi
+     command = "ffmpeg -r %i %s " % (int(fps), inputOptions)
+-    command += "-i im%s.png " % (formatter,)
++    command += "-i im%s.jpg " % (formatter,)
+     command += "-g 1 -vcodec %s %s " % (encoding, outputOptions) 
+     command += "output.avi"
+     
+Questions
+=========
+Should we make these files PEP8 compliant? This would make
+merging of possible changes from visvis more difficult.
+
+
+

+ 4 - 0
lib/python/imaging/__init__.py

@@ -0,0 +1,4 @@
+from grass.imaging.images2gif import readGif, writeGif
+from grass.imaging.images2swf import readSwf, writeSwf
+from grass.imaging.images2avi import readAvi, writeAvi
+from grass.imaging.images2ims import readIms, writeIms

+ 169 - 0
lib/python/imaging/images2avi.py

@@ -0,0 +1,169 @@
+#   Copyright (C) 2012, Almar Klein
+#   All rights reserved.
+#
+#   This code is subject to the (new) BSD license:
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the <organization> nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY 
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+#
+# changes of this file GRASS (PNG instead of JPG) by Anna Petrasova 2013 
+
+""" Module images2avi
+
+Uses ffmpeg to read and write AVI files. Requires PIL
+
+I found these sites usefull:
+http://www.catswhocode.com/blog/19-ffmpeg-commands-for-all-needs
+http://linux.die.net/man/1/ffmpeg
+
+"""
+
+import os, time
+import subprocess, shutil
+from grass.imaging import images2ims
+
+
+def _cleanDir(tempDir):
+    for i in range(3):
+        try:
+            shutil.rmtree(tempDir)
+        except Exception:
+            time.sleep(0.2) # Give OS time to free sources
+        else:
+            break
+    else:
+        print("Oops, could not fully clean up temporary files.")
+
+
+def writeAvi(filename, images, duration=0.1, encoding='mpeg4', 
+                                        inputOptions='', outputOptions='' ):
+    """ writeAvi(filename, duration=0.1, encoding='mpeg4',
+                    inputOptions='', outputOptions='')
+    
+    Export movie to a AVI file, which is encoded with the given 
+    encoding. Hint for Windows users: the 'msmpeg4v2' codec is 
+    natively supported on Windows.
+    
+    Images should be a list consisting of PIL images or numpy arrays. 
+    The latter should be between 0 and 255 for integer types, and 
+    between 0 and 1 for float types.
+    
+    Requires the "ffmpeg" application:
+      * Most linux users can install using their package manager
+      * There is a windows installer on the visvis website
+    
+    """
+    
+    # Get fps
+    try:
+        fps = float(1.0/duration)
+    except Exception:
+        raise ValueError("Invalid duration parameter for writeAvi.")
+    
+    # Determine temp dir and create images
+    tempDir = os.path.join( os.path.expanduser('~'), '.tempIms')
+    images2ims.writeIms( os.path.join(tempDir, 'im*.png'), images)
+    
+    # Determine formatter
+    N = len(images)
+    formatter = '%04d'
+    if N < 10:
+        formatter = '%d'
+    elif N < 100:
+        formatter = '%02d'
+    elif N < 1000:
+        formatter = '%03d'
+    
+    # Compile command to create avi
+    command = "ffmpeg -r %i %s " % (int(fps), inputOptions)
+    command += "-i im%s.png " % (formatter,)
+    command += "-g 1 -vcodec %s %s " % (encoding, outputOptions) 
+    command += "output.avi"
+    
+    # Run ffmpeg
+    S = subprocess.Popen(command, shell=True, cwd=tempDir,
+                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    
+    # Show what ffmpeg has to say
+    outPut = S.stdout.read()
+    
+    if S.wait():    
+        # An error occured, show
+        print(outPut)
+        print(S.stderr.read())
+        # Clean up
+        _cleanDir(tempDir)
+        raise RuntimeError("Could not write avi.")
+    else:
+        # Copy avi
+        shutil.copy(os.path.join(tempDir, 'output.avi'), filename)
+        # Clean up
+        _cleanDir(tempDir)
+
+
+def readAvi(filename, asNumpy=True):
+    """ readAvi(filename, asNumpy=True)
+    
+    Read images from an AVI (or MPG) movie.
+    
+    Requires the "ffmpeg" application:
+      * Most linux users can install using their package manager
+      * There is a windows installer on the visvis website
+    
+    """
+    
+    # Check whether it exists
+    if not os.path.isfile(filename):
+        raise IOError('File not found: '+str(filename))
+    
+    # Determine temp dir, make sure it exists
+    tempDir = os.path.join( os.path.expanduser('~'), '.tempIms')
+    if not os.path.isdir(tempDir):
+        os.makedirs(tempDir)
+    
+    # Copy movie there
+    shutil.copy(filename, os.path.join(tempDir, 'input.avi'))
+    
+    # Run ffmpeg
+    command = "ffmpeg -i input.avi im%d.jpg"
+    S = subprocess.Popen(command, shell=True, cwd=tempDir,
+                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    
+    # Show what mencodec has to say
+    outPut = S.stdout.read()
+    
+    if S.wait():    
+        # An error occured, show
+        print(outPut)
+        print(S.stderr.read())
+        # Clean up
+        _cleanDir(tempDir)
+        raise RuntimeError("Could not read avi.")
+    else:
+        # Read images
+        images = images2ims.readIms(os.path.join(tempDir, 'im*.jpg'), asNumpy)
+        # Clean up
+        _cleanDir(tempDir)
+    
+    # Done
+    return images

文件差異過大導致無法顯示
+ 1068 - 0
lib/python/imaging/images2gif.py


+ 237 - 0
lib/python/imaging/images2ims.py

@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+#   Copyright (C) 2012, Almar Klein
+#
+#   This code is subject to the (new) BSD license:
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the <organization> nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY 
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+""" Module images2ims
+
+Use PIL to create a series of images.
+
+"""
+
+import os
+
+try:
+    import numpy as np
+except ImportError:
+    np = None    
+
+try:
+    import PIL
+    from PIL import Image    
+except ImportError:
+    PIL = None
+
+
+def checkImages(images):
+    """ checkImages(images)
+    Check numpy images and correct intensity range etc.
+    The same for all movie formats.
+    """ 
+    # Init results
+    images2 = []
+    
+    for im in images:
+        if PIL and isinstance(im, PIL.Image.Image):
+            # We assume PIL images are allright
+            images2.append(im)
+        
+        elif np and isinstance(im, np.ndarray):
+            # Check and convert dtype
+            if im.dtype == np.uint8:
+                images2.append(im) # Ok
+            elif im.dtype in [np.float32, np.float64]:
+                theMax = im.max()
+                if theMax > 128 and theMax < 300:
+                    pass # assume 0:255
+                else:
+                    im = im.copy()
+                    im[im<0] = 0
+                    im[im>1] = 1
+                    im *= 255
+                images2.append( im.astype(np.uint8) )
+            else:
+                im = im.astype(np.uint8)
+                images2.append(im)
+            # Check size
+            if im.ndim == 2:
+                pass # ok
+            elif im.ndim == 3:
+                if im.shape[2] not in [3,4]:
+                    raise ValueError('This array can not represent an image.')
+            else:
+                raise ValueError('This array can not represent an image.')
+        else:
+            raise ValueError('Invalid image type: ' + str(type(im)))
+    
+    # Done
+    return images2
+
+
+def _getFilenameParts(filename):
+    if '*' in filename:
+        return tuple( filename.split('*',1) )
+    else:
+        return os.path.splitext(filename)
+
+
+def _getFilenameWithFormatter(filename, N):
+    
+    # Determine sequence number formatter
+    formatter = '%04i'
+    if N < 10:
+        formatter = '%i'
+    elif N < 100:
+        formatter = '%02i'
+    elif N < 1000:
+        formatter = '%03i'
+    
+    # Insert sequence number formatter
+    part1, part2 = _getFilenameParts(filename)
+    return part1 + formatter + part2
+    
+
+def _getSequenceNumber(filename, part1, part2):
+    # Get string bit
+    seq = filename[len(part1):-len(part2)]
+    # Get all numeric chars
+    seq2 = ''
+    for c in seq:
+        if c in '0123456789':
+            seq2 += c
+        else:
+            break
+    # Make int and return
+    return int(seq2)
+
+
+def writeIms(filename, images):
+    """ writeIms(filename, images)
+    
+    Export movie to a series of image files. If the filenenumber 
+    contains an asterix, a sequence number is introduced at its 
+    location. Otherwise the sequence number is introduced right 
+    before the final dot.
+    
+    To enable easy creation of a new directory with image files, 
+    it is made sure that the full path exists.
+    
+    Images should be a list consisting of PIL images or numpy arrays. 
+    The latter should be between 0 and 255 for integer types, and 
+    between 0 and 1 for float types.
+    
+    """
+    
+    # Check PIL
+    if PIL is None:
+        raise RuntimeError("Need PIL to write series of image files.")
+    
+    # Check images
+    images = checkImages(images)
+    
+    # Get dirname and filename
+    filename = os.path.abspath(filename)
+    dirname, filename = os.path.split(filename)
+    
+    # Create dir(s) if we need to
+    if not os.path.isdir(dirname):
+        os.makedirs(dirname)
+    
+    # Insert formatter
+    filename = _getFilenameWithFormatter(filename, len(images))
+    
+    # Write
+    seq = 0
+    for frame in images:
+        seq += 1
+        # Get filename
+        fname = os.path.join(dirname, filename%seq)
+        # Write image
+        if np and isinstance(frame, np.ndarray):
+            frame =  PIL.Image.fromarray(frame)        
+        frame.save(fname)
+
+
+
+def readIms(filename, asNumpy=True):
+    """ readIms(filename, asNumpy=True)
+    
+    Read images from a series of images in a single directory. Returns a 
+    list of numpy arrays, or, if asNumpy is false, a list if PIL images.
+    
+    """
+    
+    # Check PIL
+    if PIL is None:
+        raise RuntimeError("Need PIL to read a series of image files.")
+    
+    # Check Numpy
+    if asNumpy and np is None:
+        raise RuntimeError("Need Numpy to return numpy arrays.")
+    
+    # Get dirname and filename
+    filename = os.path.abspath(filename)
+    dirname, filename = os.path.split(filename)
+    
+    # Check dir exists
+    if not os.path.isdir(dirname):
+        raise IOError('Directory not found: '+str(dirname))
+    
+    # Get two parts of the filename
+    part1, part2 = _getFilenameParts(filename)
+    
+    # Init images
+    images = []
+    
+    # Get all files in directory
+    for fname in os.listdir(dirname):
+        if fname.startswith(part1) and fname.endswith(part2):
+            # Get sequence number
+            nr = _getSequenceNumber(fname, part1, part2)
+            # Get Pil image and store copy (to prevent keeping the file)
+            im = PIL.Image.open(os.path.join(dirname, fname))
+            images.append((im.copy(), nr))
+    
+    # Sort images 
+    images.sort(key=lambda x:x[1])    
+    images = [im[0] for im in images]
+    
+    # Convert to numpy if needed
+    if asNumpy:
+        images2 = images
+        images = []
+        for im in images2:
+            # Make without palette
+            if im.mode == 'P':
+                im = im.convert()
+            # Make numpy array
+            a = np.asarray(im)
+            if len(a.shape)==0:
+                raise MemoryError("Too little memory to convert PIL image to array")
+            # Add
+            images.append(a)
+    
+    # Done
+    return images

文件差異過大導致無法顯示
+ 1008 - 0
lib/python/imaging/images2swf.py


+ 33 - 0
lib/python/imaging/imaginglib.dox

@@ -0,0 +1,33 @@
+/*! \page imaging Python library for animations
+
+
+\tableofcontents
+
+
+\section imagingIntro Introduction
+Library python.imaging is a third-party python library for animations.
+It comes from visvis project (https://code.google.com/p/visvis/), version 1.8.
+Library contains 4 main files: images2avi.py, images2gif.py, images2ims.py, images2swf.py for exporting
+AVI video file, animated GIF, series of images and SWF file format, respectively.
+There are functions for reading/writing those formats (writeAvi(), readAvi(), writeGif(), readGif(),
+writeIms(), readIms(), writeSwf(), readSwf()). Library requires PIL (Python Imaging Library) and numpy packages.
+The input of all write functions are PIL images.
+
+Please read README in library's directory.
+
+Example:
+
+\code{.py}
+from grass.imaging import writeIms
+...
+writeIms(filename=path/to/directory/anim.png, images=myPILImagesList)
+\endcode
+
+This creates files anim1.png, anim2.png, ... in path/to/directory.
+
+
+\section imagingAuthors Authors
+
+Almar Klein (see licence in header of library files)
+
+*/