[GRASS-SVN] r61763 - in grass/trunk/lib/python: imaging pydispatch
svn_grass at osgeo.org
svn_grass at osgeo.org
Wed Aug 27 14:16:03 PDT 2014
Author: lucadelu
Date: 2014-08-27 14:16:03 -0700 (Wed, 27 Aug 2014)
New Revision: 61763
Modified:
grass/trunk/lib/python/imaging/images2avi.py
grass/trunk/lib/python/imaging/images2gif.py
grass/trunk/lib/python/imaging/images2ims.py
grass/trunk/lib/python/imaging/images2swf.py
grass/trunk/lib/python/pydispatch/dispatcher.py
grass/trunk/lib/python/pydispatch/errors.py
grass/trunk/lib/python/pydispatch/robust.py
grass/trunk/lib/python/pydispatch/robustapply.py
grass/trunk/lib/python/pydispatch/saferef.py
grass/trunk/lib/python/pydispatch/signal.py
Log:
lib python: improve documentation; PEP8 cleaning
Modified: grass/trunk/lib/python/imaging/images2avi.py
===================================================================
--- grass/trunk/lib/python/imaging/images2avi.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/imaging/images2avi.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -13,11 +13,11 @@
# * 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"
+#
+# 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
+# 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
@@ -26,7 +26,7 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#
-# changes of this file GRASS (PNG instead of JPG) by Anna Petrasova 2013
+# changes of this file GRASS (PNG instead of JPG) by Anna Petrasova 2013
""" Module images2avi
@@ -38,8 +38,10 @@
"""
-import os, time
-import subprocess, shutil
+import os
+import time
+import subprocess
+import shutil
from grass.imaging import images2ims
@@ -48,42 +50,45 @@
try:
shutil.rmtree(tempDir)
except Exception:
- time.sleep(0.2) # Give OS time to free sources
+ 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
+def writeAvi(filename, images, 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
+
+ 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
-
+
+ :param str filename: output filename
+ :param images:
+ :param float duration:
+ :param str encoding: the encoding type
+ :param inputOptions:
+ :param outputOptions:
"""
-
+
# 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)
-
+ tempDir = os.path.join(os.path.expanduser('~'), '.tempIms')
+ images2ims.writeIms(os.path.join(tempDir, 'im*.png'), images)
+
# Determine formatter
N = len(images)
formatter = '%04d'
@@ -93,21 +98,21 @@
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 += "-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)
-
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
# Show what ffmpeg has to say
outPut = S.stdout.read()
-
- if S.wait():
+
+ if S.wait():
# An error occured, show
print(outPut)
print(S.stderr.read())
@@ -122,37 +127,37 @@
def readAvi(filename, asNumpy=True):
- """ readAvi(filename, asNumpy=True)
-
- Read images from an AVI (or MPG) movie.
-
+ """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
-
+
+ :param str filename: name of input movie file
+ :param bool asNumpy:
"""
-
+
# 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')
+ 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)
-
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
# Show what mencodec has to say
outPut = S.stdout.read()
-
- if S.wait():
+
+ if S.wait():
# An error occured, show
print(outPut)
print(S.stderr.read())
@@ -164,6 +169,6 @@
images = images2ims.readIms(os.path.join(tempDir, 'im*.jpg'), asNumpy)
# Clean up
_cleanDir(tempDir)
-
+
# Done
return images
Modified: grass/trunk/lib/python/imaging/images2gif.py
===================================================================
--- grass/trunk/lib/python/imaging/images2gif.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/imaging/images2gif.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -13,11 +13,11 @@
# * 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"
+#
+# 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
+# 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
@@ -28,7 +28,7 @@
""" Module images2gif
Provides functionality for reading and writing animated GIF images.
-Use writeGif to write a series of numpy arrays or PIL images as an
+Use writeGif to write a series of numpy arrays or PIL images as an
animated GIF. Use readGif to read an animated gif as a series of numpy
arrays.
@@ -40,11 +40,11 @@
Many thanks to Ant1 for:
* noting the use of "palette=PIL.Image.ADAPTIVE", which significantly
- improves the results.
+ improves the results.
* the modifications to save each image with its own palette, or optionally
the global palette (if its the same).
-Many thanks to Marius van Voorden for porting the NeuQuant quantization
+Many thanks to Marius van Voorden for porting the NeuQuant quantization
algorithm of Anthony Dekker to Python (See the NeuQuant class for its
license).
@@ -52,7 +52,7 @@
which (depening on image content) can give a very significant reduction in
file size.
-This code is based on gifmaker (in the scripts folder of the source
+This code is based on gifmaker (in the scripts folder of the source
distribution of PIL)
@@ -65,7 +65,8 @@
"""
# todo: This module should be part of imageio (or at least based on)
-import os, time
+import os
+import time
try:
import PIL
@@ -82,8 +83,9 @@
try:
import numpy as np
except ImportError:
- np = None
+ np = None
+
def get_cKDTree():
try:
from scipy.spatial import cKDTree
@@ -92,126 +94,121 @@
return cKDTree
-# getheader gives a 87a header and a color palette (two elements in a list).
-# getdata()[0] gives the Image Descriptor up to (including) "LZW min code size".
+# getheader gives a 87a header and a color palette (two elements in a list)
+# getdata()[0] gives the Image Descriptor up to (including) "LZW min code size"
# getdatas()[1:] is the image data itself in chuncks of 256 bytes (well
# technically the first byte says how many bytes follow, after which that
-# amount (max 255) follows).
+# amount (max 255) follows)
def checkImages(images):
""" checkImages(images)
Check numpy images and correct intensity range etc.
The same for all movie formats.
- """
+
+ :param images:
+ """
# 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
+ images2.append(im) # Ok
elif im.dtype in [np.float32, np.float64]:
im = im.copy()
- im[im<0] = 0
- im[im>1] = 1
+ im[im < 0] = 0
+ im[im > 1] = 1
im *= 255
- images2.append( im.astype(np.uint8) )
+ images2.append(im.astype(np.uint8))
else:
im = im.astype(np.uint8)
images2.append(im)
# Check size
if im.ndim == 2:
- pass # ok
+ pass # ok
elif im.ndim == 3:
- if im.shape[2] not in [3,4]:
+ 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 intToBin(i):
- """ Integer to two bytes """
+ """Integer to two bytes"""
# devide in two parts (bytes)
i1 = i % 256
- i2 = int( i/256)
+ i2 = int(i / 256)
# make string (little endian)
return chr(i1) + chr(i2)
class GifWriter:
- """ GifWriter()
-
- Class that contains methods for helping write the animated GIF file.
-
+ """Class that contains methods for helping write the animated GIF file.
"""
-
+
def getheaderAnim(self, im):
- """ getheaderAnim(im)
-
- Get animation header. To replace PILs getheader()[0]
-
+ """Get animation header. To replace PILs getheader()[0]
+
+ :param im:
"""
bb = "GIF89a"
bb += intToBin(im.size[0])
bb += intToBin(im.size[1])
bb += "\x87\x00\x00"
return bb
-
-
+
def getImageDescriptor(self, im, xy=None):
- """ getImageDescriptor(im, xy=None)
-
- Used for the local color table properties per image.
+ """Used for the local color table properties per image.
Otherwise global color table applies to all frames irrespective of
whether additional colors comes in play that require a redefined
palette. Still a maximum of 256 color per frame, obviously.
-
+
Written by Ant1 on 2010-08-22
Modified by Alex Robinson in Janurari 2011 to implement subrectangles.
-
+
+ :param im:
+ :param xy:
"""
-
+
# Defaule use full image and place at upper left
if xy is None:
- xy = (0,0)
-
+ xy = (0, 0)
+
# Image separator,
- bb = '\x2C'
-
+ bb = '\x2C'
+
# Image position and size
- bb += intToBin( xy[0] ) # Left position
- bb += intToBin( xy[1] ) # Top position
- bb += intToBin( im.size[0] ) # image width
- bb += intToBin( im.size[1] ) # image height
-
- # packed field: local color table flag1, interlace0, sorted table0,
- # reserved00, lct size111=7=2^(7+1)=256.
- bb += '\x87'
-
- # LZW minimum size code now comes later, begining of [image data] blocks
+ bb += intToBin(xy[0]) # Left position
+ bb += intToBin(xy[1]) # Top position
+ bb += intToBin(im.size[0]) # image width
+ bb += intToBin(im.size[1]) # image height
+
+ # packed field: local color table flag1, interlace0, sorted table0,
+ # reserved00, lct size111=7=2^(7 + 1)=256.
+ bb += '\x87'
+
+ # LZW min size code now comes later, begining of [image data] blocks
return bb
-
-
+
def getAppExt(self, loops=float('inf')):
- """ getAppExt(loops=float('inf'))
-
- Application extention. This part specifies the amount of loops.
+ """Application extention. This part specifies the amount of loops.
If loops is 0 or inf, it goes on infinitely.
-
+
+ :param float loops:
"""
-
- if loops==0 or loops==float('inf'):
- loops = 2**16-1
+
+ if loops == 0 or loops == float('inf'):
+ loops = 2 ** 16 - 1
#bb = "" # application extension should not be used
# (the extension interprets zero loops
# to mean an infinite number of loops)
@@ -223,53 +220,49 @@
bb += intToBin(loops)
bb += '\x00' # end
return bb
-
-
+
def getGraphicsControlExt(self, duration=0.1, dispose=2):
- """ getGraphicsControlExt(duration=0.1, dispose=2)
-
- Graphics Control Extension. A sort of header at the start of
- each image. Specifies duration and transparancy.
-
+ """Graphics Control Extension. A sort of header at the start of
+ each image. Specifies duration and transparancy.
+
Dispose
-------
* 0 - No disposal specified.
* 1 - Do not dispose. The graphic is to be left in place.
- * 2 - Restore to background color. The area used by the graphic
+ * 2 - Restore to background color. The area used by the graphic
must be restored to the background color.
* 3 - Restore to previous. The decoder is required to restore the
- area overwritten by the graphic with what was there prior to
+ area overwritten by the graphic with what was there prior to
rendering the graphic.
- * 4-7 -To be defined.
-
+ * 4-7 -To be defined.
+
+ :param double duration:
+ :param dispose:
"""
-
+
bb = '\x21\xF9\x04'
bb += chr((dispose & 3) << 2) # low bit 1 == transparency,
# 2nd bit 1 == user input , next 3 bits, the low two of which are used,
# are dispose.
- bb += intToBin( int(duration*100) ) # in 100th of seconds
+ bb += intToBin(int(duration * 100)) # in 100th of seconds
bb += '\x00' # no transparant color
bb += '\x00' # end
return bb
-
-
+
def handleSubRectangles(self, images, subRectangles):
- """ handleSubRectangles(images)
-
- Handle the sub-rectangle stuff. If the rectangles are given by the
+ """Handle the sub-rectangle stuff. If the rectangles are given by the
user, the values are checked. Otherwise the subrectangles are
calculated automatically.
-
- """
-
- if isinstance(subRectangles, (tuple,list)):
+
+ """
+
+ if isinstance(subRectangles, (tuple, list)):
# xy given directly
-
+
# Check xy
xy = subRectangles
if xy is None:
- xy = (0,0)
+ xy = (0, 0)
if hasattr(xy, '__len__'):
if len(xy) == len(images):
xy = [xxyy for xxyy in xy]
@@ -277,122 +270,121 @@
raise ValueError("len(xy) doesn't match amount of images.")
else:
xy = [xy for im in images]
- xy[0] = (0,0)
-
+ xy[0] = (0, 0)
+
else:
# Calculate xy using some basic image processing
-
+
# Check Numpy
if np is None:
raise RuntimeError("Need Numpy to use auto-subRectangles.")
-
+
# First make numpy arrays if required
for i in range(len(images)):
im = images[i]
if isinstance(im, Image.Image):
- tmp = im.convert() # Make without palette
+ tmp = im.convert() # Make without palette
a = np.asarray(tmp)
- if len(a.shape)==0:
+ if len(a.shape) == 0:
raise MemoryError("Too little memory to convert PIL image to array")
images[i] = a
-
+
# Determine the sub rectangles
images, xy = self.getSubRectangles(images)
-
+
# Done
return images, xy
-
-
+
def getSubRectangles(self, ims):
""" getSubRectangles(ims)
-
+
Calculate the minimal rectangles that need updating each frame.
Returns a two-element tuple containing the cropped images and a
list of x-y positions.
-
+
Calculating the subrectangles takes extra time, obviously. However,
if the image sizes were reduced, the actual writing of the GIF
goes faster. In some cases applying this method produces a GIF faster.
-
+
"""
-
+
# Check image count
if len(ims) < 2:
- return ims, [(0,0) for i in ims]
-
+ return ims, [(0, 0) for i in ims]
+
# We need numpy
if np is None:
raise RuntimeError("Need Numpy to calculate sub-rectangles. ")
-
+
# Prepare
ims2 = [ims[0]]
- xy = [(0,0)]
+ xy = [(0, 0)]
t0 = time.time()
-
+
# Iterate over images
prev = ims[0]
for im in ims[1:]:
-
+
# Get difference, sum over colors
diff = np.abs(im-prev)
- if diff.ndim==3:
- diff = diff.sum(2)
+ if diff.ndim == 3:
+ diff = diff.sum(2)
# Get begin and end for both dimensions
X = np.argwhere(diff.sum(0))
Y = np.argwhere(diff.sum(1))
# Get rect coordinates
if X.size and Y.size:
- x0, x1 = X[0], X[-1]+1
- y0, y1 = Y[0], Y[-1]+1
- else: # No change ... make it minimal
+ x0, x1 = X[0], X[-1] + 1
+ y0, y1 = Y[0], Y[-1] + 1
+ else: # No change ... make it minimal
x0, x1 = 0, 2
y0, y1 = 0, 2
-
+
# Cut out and store
- im2 = im[y0:y1,x0:x1]
+ im2 = im[y0:y1, x0:x1]
prev = im
ims2.append(im2)
- xy.append((x0,y0))
-
+ xy.append((x0, y0))
+
# Done
- #print('%1.2f seconds to determine subrectangles of %i images' %
- # (time.time()-t0, len(ims2)) )
+ # print('%1.2f seconds to determine subrectangles of %i images' %
+ # (time.time()-t0, len(ims2)))
return ims2, xy
-
-
+
def convertImagesToPIL(self, images, dither, nq=0):
""" convertImagesToPIL(images, nq=0)
-
- Convert images to Paletted PIL images, which can then be
+
+ Convert images to Paletted PIL images, which can then be
written to a single animaged GIF.
-
+
"""
-
+
# Convert to PIL images
images2 = []
for im in images:
if isinstance(im, Image.Image):
images2.append(im)
elif np and isinstance(im, np.ndarray):
- if im.ndim==3 and im.shape[2]==3:
- im = Image.fromarray(im,'RGB')
- elif im.ndim==3 and im.shape[2]==4:
- im = Image.fromarray(im[:,:,:3],'RGB')
- elif im.ndim==2:
- im = Image.fromarray(im,'L')
+ if im.ndim == 3 and im.shape[2] == 3:
+ im = Image.fromarray(im, 'RGB')
+ elif im.ndim == 3 and im.shape[2] == 4:
+ im = Image.fromarray(im[:, :, :3], 'RGB')
+ elif im.ndim == 2:
+ im = Image.fromarray(im, 'L')
images2.append(im)
-
+
# Convert to paletted PIL images
images, images2 = images2, []
if nq >= 1:
# NeuQuant algorithm
for im in images:
- im = im.convert("RGBA") # NQ assumes RGBA
- nqInstance = NeuQuant(im, int(nq)) # Learn colors from image
+ im = im.convert("RGBA") # NQ assumes RGBA
+ nqInstance = NeuQuant(im, int(nq)) # Learn colors from image
if dither:
im = im.convert("RGB").quantize(palette=nqInstance.paletteImage())
else:
- im = nqInstance.quantize(im) # Use to quantize the image itself
+ # Use to quantize the image itself
+ im = nqInstance.quantize(im)
images2.append(im)
else:
# Adaptive PIL algorithm
@@ -400,11 +392,10 @@
for im in images:
im = im.convert('P', palette=AD, dither=dither)
images2.append(im)
-
+
# Done
return images2
-
-
+
def writeGifToFile(self, fp, images, durations, loops, xys, disposes):
""" writeGifToFile(fp, images, durations, loops, xys, disposes)
@@ -414,7 +405,7 @@
code/python/external/images2gif.py
"""
-
+
# Obtain palette for all images and count each occurance
palettes, occur = [], []
for im in images:
@@ -426,78 +417,70 @@
palette = im.palette.tobytes()
palettes.append(palette)
for palette in palettes:
- occur.append( palettes.count( palette ) )
-
+ occur.append(palettes.count(palette))
+
# Select most-used palette as the global one (or first in case no max)
- globalPalette = palettes[ occur.index(max(occur)) ]
-
+ globalPalette = palettes[occur.index(max(occur))]
+
# Init
frames = 0
firstFrame = True
-
-
+
for im, palette in zip(images, palettes):
-
+
if firstFrame:
# Write header
-
+
# Gather info
header = self.getheaderAnim(im)
appext = self.getAppExt(loops)
-
+
# Write
fp.write(header)
fp.write(globalPalette)
fp.write(appext)
-
+
# Next frame is not the first
firstFrame = False
-
+
if True:
# Write palette and image data
-
+
# Gather info
data = getdata(im)
imdes, data = data[0], data[1:]
graphext = self.getGraphicsControlExt(durations[frames],
- disposes[frames])
+ disposes[frames])
# Make image descriptor suitable for using 256 local color palette
lid = self.getImageDescriptor(im, xys[frames])
-
+
# Write local header
if (palette != globalPalette) or (disposes[frames] != 2):
# Use local color palette
fp.write(graphext)
- fp.write(lid) # write suitable image descriptor
- fp.write(palette) # write local color table
- fp.write('\x08') # LZW minimum size code
+ fp.write(lid) # write suitable image descriptor
+ fp.write(palette) # write local color table
+ fp.write('\x08') # LZW minimum size code
else:
# Use global color palette
fp.write(graphext)
- fp.write(imdes) # write suitable image descriptor
-
+ fp.write(imdes) # write suitable image descriptor
+
# Write image data
for d in data:
fp.write(d)
-
+
# Prepare for next round
frames = frames + 1
-
+
fp.write(";") # end gif
return frames
-
-
-
## Exposed functions
+def writeGif(filename, images, duration=0.1, repeat=True, dither=False,
+ nq=0, subRectangles=True, dispose=None):
+ """Write an animated gif from the specified images.
-def writeGif(filename, images, duration=0.1, repeat=True, dither=False,
- nq=0, subRectangles=True, dispose=None):
- """ writeGif(filename, images, duration=0.1, repeat=True, dither=False,
- nq=0, subRectangles=True, dispose=None)
-
- Write an animated gif from the specified images.
-
Parameters
----------
filename : string
@@ -517,13 +500,13 @@
the color palette. This algorithm is superior, but slower than
the standard PIL algorithm. The value of nq is the quality
parameter. 1 represents the best quality. 10 is in general a
- good tradeoff between quality and speed. When using this option,
+ good tradeoff between quality and speed. When using this option,
better results are usually obtained when subRectangles is False.
subRectangles : False, True, or a list of 2-element tuples
Whether to use sub-rectangles. If True, the minimal rectangle that
is required to update each frame is automatically detected. This
can give significant reductions in file size, particularly if only
- a part of the image changes. One can also give a list of x-y
+ a part of the image changes. One can also give a list of x-y
coordinates if you want to do the cropping yourself. The default
is True.
dispose : int
@@ -531,27 +514,27 @@
in place. 2 means the background color should be restored after
each frame. 3 means the decoder should restore the previous frame.
If subRectangles==False, the default is 2, otherwise it is 1.
-
+
"""
-
+
# Check PIL
if PIL is None:
raise RuntimeError("Need PIL to write animated gif files.")
-
+
# Check images
images = checkImages(images)
-
+
# Instantiate writer object
gifWriter = GifWriter()
-
+
# Check loops
if repeat is False:
loops = 1
elif repeat is True:
- loops = 0 # zero means infinite
+ loops = 0 # zero means infinite
else:
loops = int(repeat)
-
+
# Check duration
if hasattr(duration, '__len__'):
if len(duration) == len(images):
@@ -560,16 +543,16 @@
raise ValueError("len(duration) doesn't match amount of images.")
else:
duration = [duration for im in images]
-
+
# Check subrectangles
if subRectangles:
images, xy = gifWriter.handleSubRectangles(images, subRectangles)
- defaultDispose = 1 # Leave image in place
+ defaultDispose = 1 # Leave image in place
else:
# Normal mode
- xy = [(0,0) for im in images]
- defaultDispose = 2 # Restore to background color.
-
+ xy = [(0, 0) for im in images]
+ defaultDispose = 2 # Restore to background color.
+
# Check dispose
if dispose is None:
dispose = defaultDispose
@@ -578,11 +561,10 @@
raise ValueError("len(xy) doesn't match amount of images.")
else:
dispose = [dispose for im in images]
-
-
+
# Make images in a format that we can write easy
images = gifWriter.convertImagesToPIL(images, dither, nq)
-
+
# Write
fp = open(filename, 'wb')
try:
@@ -591,72 +573,69 @@
fp.close()
-
def readGif(filename, asNumpy=True):
- """ readGif(filename, asNumpy=True)
-
- Read images from an animated GIF file. Returns a list of numpy
+ """Read images from an animated GIF file. 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 animated gif files.")
-
+
# Check Numpy
if np is None:
raise RuntimeError("Need Numpy to read animated gif files.")
-
+
# Check whether it exists
if not os.path.isfile(filename):
- raise IOError('File not found: '+str(filename))
-
+ raise IOError('File not found: ' + str(filename))
+
# Load file using PIL
- pilIm = PIL.Image.open(filename)
+ pilIm = PIL.Image.open(filename)
pilIm.seek(0)
-
+
# Read all images inside
images = []
try:
while True:
# Get image as numpy array
- tmp = pilIm.convert() # Make without palette
+ tmp = pilIm.convert() # Make without palette
a = np.asarray(tmp)
- if len(a.shape)==0:
+ if len(a.shape) == 0:
raise MemoryError("Too little memory to convert PIL image to array")
# Store, and next
images.append(a)
- pilIm.seek(pilIm.tell()+1)
+ pilIm.seek(pilIm.tell() + 1)
except EOFError:
pass
-
+
# Convert to normal PIL images if needed
if not asNumpy:
images2 = images
images = []
- for im in images2:
- images.append( PIL.Image.fromarray(im) )
-
+ for im in images2:
+ images.append(PIL.Image.fromarray(im))
+
# Done
return images
class NeuQuant:
""" NeuQuant(image, samplefac=10, colors=256)
-
- samplefac should be an integer number of 1 or higher, 1
- being the highest quality, but the slowest performance.
- With avalue of 10, one tenth of all pixels are used during
+
+ samplefac should be an integer number of 1 or higher, 1
+ being the highest quality, but the slowest performance.
+ With avalue of 10, one tenth of all pixels are used during
training. This value seems a nice tradeof between speed
and quality.
-
+
colors is the amount of colors to reduce the image to. This
should best be a power of two.
-
+
See also:
http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
-
+
License of the NeuQuant Neural-Net Quantization Algorithm
---------------------------------------------------------
@@ -670,46 +649,46 @@
See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
Any party obtaining a copy of these files from the author, directly or
- indirectly, is granted, free of charge, a full and unrestricted irrevocable,
- world-wide, paid up, royalty-free, nonexclusive right and license to deal
- in this software and documentation files (the "Software"), including without
- limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons who receive
- copies from any such party to do so, with the only requirement being
- that this copyright notice remain intact.
-
+ indirectly, is granted, free of charge, a full and unrestricted
+ irrevocable, world-wide, paid up, royalty-free, nonexclusive right and
+ license to deal in this software and documentation files (the "Software"),
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software, and
+ to permit persons who receive copies from any such party to do so, with
+ the only requirement being that this copyright notice remain intact.
+
"""
-
- NCYCLES = None # Number of learning cycles
- NETSIZE = None # Number of colours used
- SPECIALS = None # Number of reserved colours used
- BGCOLOR = None # Reserved background colour
+
+ NCYCLES = None # Number of learning cycles
+ NETSIZE = None # Number of colours used
+ SPECIALS = None # Number of reserved colours used
+ BGCOLOR = None # Reserved background colour
CUTNETSIZE = None
MAXNETPOS = None
-
- INITRAD = None # For 256 colours, radius starts at 32
+
+ INITRAD = None # For 256 colours, radius starts at 32
RADIUSBIASSHIFT = None
RADIUSBIAS = None
INITBIASRADIUS = None
- RADIUSDEC = None # Factor of 1/30 each cycle
-
+ RADIUSDEC = None # Factor of 1/30 each cycle
+
ALPHABIASSHIFT = None
- INITALPHA = None # biased by 10 bits
-
+ INITALPHA = None # biased by 10 bits
+
GAMMA = None
BETA = None
BETAGAMMA = None
-
- network = None # The network itself
- colormap = None # The network itself
-
- netindex = None # For network lookup - really 256
-
- bias = None # Bias and freq arrays for learning
+
+ network = None # The network itself
+ colormap = None # The network itself
+
+ netindex = None # For network lookup - really 256
+
+ bias = None # Bias and freq arrays for learning
freq = None
-
+
pimage = None
-
+
# Four primes near 500 - assume no image has a length so large
# that it is divisible by all four primes
PRIME1 = 499
@@ -717,121 +696,120 @@
PRIME3 = 487
PRIME4 = 503
MAXPRIME = PRIME4
-
+
pixels = None
samplefac = None
-
+
a_s = None
-
-
+
def setconstants(self, samplefac, colors):
- self.NCYCLES = 100 # Number of learning cycles
- self.NETSIZE = colors # Number of colours used
- self.SPECIALS = 3 # Number of reserved colours used
- self.BGCOLOR = self.SPECIALS-1 # Reserved background colour
+ self.NCYCLES = 100 # Number of learning cycles
+ self.NETSIZE = colors # Number of colours used
+ self.SPECIALS = 3 # Number of reserved colours used
+ self.BGCOLOR = self.SPECIALS-1 # Reserved background colour
self.CUTNETSIZE = self.NETSIZE - self.SPECIALS
self.MAXNETPOS = self.NETSIZE - 1
-
- self.INITRAD = self.NETSIZE/8 # For 256 colours, radius starts at 32
+
+ self.INITRAD = self.NETSIZE/8 # For 256 colours, radius starts at 32
self.RADIUSBIASSHIFT = 6
self.RADIUSBIAS = 1 << self.RADIUSBIASSHIFT
self.INITBIASRADIUS = self.INITRAD * self.RADIUSBIAS
- self.RADIUSDEC = 30 # Factor of 1/30 each cycle
-
- self.ALPHABIASSHIFT = 10 # Alpha starts at 1
- self.INITALPHA = 1 << self.ALPHABIASSHIFT # biased by 10 bits
-
+ self.RADIUSDEC = 30 # Factor of 1/30 each cycle
+
+ self.ALPHABIASSHIFT = 10 # Alpha starts at 1
+ self.INITALPHA = 1 << self.ALPHABIASSHIFT # biased by 10 bits
+
self.GAMMA = 1024.0
self.BETA = 1.0/1024.0
self.BETAGAMMA = self.BETA * self.GAMMA
-
- self.network = np.empty((self.NETSIZE, 3), dtype='float64') # The network itself
- self.colormap = np.empty((self.NETSIZE, 4), dtype='int32') # The network itself
-
+
+ self.network = np.empty((self.NETSIZE, 3), dtype='float64') # The network itself
+ self.colormap = np.empty((self.NETSIZE, 4), dtype='int32') # The network itself
+
self.netindex = np.empty(256, dtype='int32') # For network lookup - really 256
-
+
self.bias = np.empty(self.NETSIZE, dtype='float64') # Bias and freq arrays for learning
self.freq = np.empty(self.NETSIZE, dtype='float64')
-
+
self.pixels = None
self.samplefac = samplefac
-
+
self.a_s = {}
-
+
def __init__(self, image, samplefac=10, colors=256):
-
+
# Check Numpy
if np is None:
raise RuntimeError("Need Numpy for the NeuQuant algorithm.")
-
+
# Check image
if image.size[0] * image.size[1] < NeuQuant.MAXPRIME:
raise IOError("Image is too small")
if image.mode != "RGBA":
raise IOError("Image mode should be RGBA.")
-
+
# Initialize
self.setconstants(samplefac, colors)
self.pixels = np.fromstring(image.tostring(), np.uint32)
self.setUpArrays()
-
+
self.learn()
self.fix()
self.inxbuild()
-
+
def writeColourMap(self, rgb, outstream):
for i in range(self.NETSIZE):
- bb = self.colormap[i,0];
- gg = self.colormap[i,1];
- rr = self.colormap[i,2];
+ bb = self.colormap[i, 0]
+ gg = self.colormap[i, 1]
+ rr = self.colormap[i, 2]
outstream.write(rr if rgb else bb)
outstream.write(gg)
outstream.write(bb if rgb else rr)
return self.NETSIZE
-
+
def setUpArrays(self):
- self.network[0,0] = 0.0 # Black
- self.network[0,1] = 0.0
- self.network[0,2] = 0.0
-
- self.network[1,0] = 255.0 # White
- self.network[1,1] = 255.0
- self.network[1,2] = 255.0
-
+ self.network[0, 0] = 0.0 # Black
+ self.network[0, 1] = 0.0
+ self.network[0, 2] = 0.0
+
+ self.network[1, 0] = 255.0 # White
+ self.network[1, 1] = 255.0
+ self.network[1, 2] = 255.0
+
# RESERVED self.BGCOLOR # Background
-
+
for i in range(self.SPECIALS):
self.freq[i] = 1.0 / self.NETSIZE
self.bias[i] = 0.0
-
+
for i in range(self.SPECIALS, self.NETSIZE):
p = self.network[i]
p[:] = (255.0 * (i-self.SPECIALS)) / self.CUTNETSIZE
-
+
self.freq[i] = 1.0 / self.NETSIZE
self.bias[i] = 0.0
-
+
# Omitted: setPixels
-
+
def altersingle(self, alpha, i, b, g, r):
- """Move neuron i towards biased (b,g,r) by factor alpha"""
- n = self.network[i] # Alter hit neuron
- n[0] -= (alpha*(n[0] - b))
- n[1] -= (alpha*(n[1] - g))
- n[2] -= (alpha*(n[2] - r))
-
+ """Move neuron i towards biased (b, g, r) by factor alpha"""
+ n = self.network[i] # Alter hit neuron
+ n[0] -= (alpha * (n[0] - b))
+ n[1] -= (alpha * (n[1] - g))
+ n[2] -= (alpha * (n[2] - r))
+
def geta(self, alpha, rad):
try:
return self.a_s[(alpha, rad)]
except KeyError:
- length = rad*2-1
+ length = rad * 2-1
mid = length/2
- q = np.array(list(range(mid-1,-1,-1))+list(range(-1,mid)))
- a = alpha*(rad*rad - q*q)/(rad*rad)
+ q = np.array(list(range(mid-1, -1, -1)) + list(range(-1, mid)))
+ a = alpha * (rad * rad - q * q)/(rad * rad)
a[mid] = 0
self.a_s[(alpha, rad)] = a
return a
-
+
def alterneigh(self, alpha, rad, i, b, g, r):
if i-rad >= self.SPECIALS-1:
lo = i-rad
@@ -839,28 +817,28 @@
else:
lo = self.SPECIALS-1
start = (self.SPECIALS-1 - (i-rad))
-
- if i+rad <= self.NETSIZE:
- hi = i+rad
- end = rad*2-1
+
+ if i + rad <= self.NETSIZE:
+ hi = i + rad
+ end = rad * 2-1
else:
hi = self.NETSIZE
- end = (self.NETSIZE - (i+rad))
-
+ end = (self.NETSIZE - (i + rad))
+
a = self.geta(alpha, rad)[start:end]
-
- p = self.network[lo+1:hi]
+
+ p = self.network[lo + 1:hi]
p -= np.transpose(np.transpose(p - np.array([b, g, r])) * a)
-
+
#def contest(self, b, g, r):
# """ Search for biased BGR values
# Finds closest neuron (min dist) and updates self.freq
# finds best neuron (min dist-self.bias) and returns position
# for frequently chosen neurons, self.freq[i] is high and self.bias[i] is negative
- # self.bias[i] = self.GAMMA*((1/self.NETSIZE)-self.freq[i])"""
+ # self.bias[i] = self.GAMMA * ((1/self.NETSIZE)-self.freq[i])"""
#
# i, j = self.SPECIALS, self.NETSIZE
- # dists = abs(self.network[i:j] - np.array([b,g,r])).sum(1)
+ # dists = abs(self.network[i:j] - np.array([b, g, r])).sum(1)
# bestpos = i + np.argmin(dists)
# biasdists = dists - self.bias[i:j]
# bestbiaspos = i + np.argmin(biasdists)
@@ -870,13 +848,14 @@
# self.bias[bestpos] -= self.BETAGAMMA
# return bestbiaspos
def contest(self, b, g, r):
- """ Search for biased BGR values
- Finds closest neuron (min dist) and updates self.freq
- finds best neuron (min dist-self.bias) and returns position
- for frequently chosen neurons, self.freq[i] is high and self.bias[i] is negative
- self.bias[i] = self.GAMMA*((1/self.NETSIZE)-self.freq[i])"""
+ """Search for biased BGR values
+ Finds closest neuron (min dist) and updates self.freq
+ finds best neuron (min dist-self.bias) and returns position
+ for frequently chosen neurons, self.freq[i] is high and self.bias[i]
+ is negative self.bias[i] = self.GAMMA * ((1/self.NETSIZE)-self.freq[i])
+ """
i, j = self.SPECIALS, self.NETSIZE
- dists = abs(self.network[i:j] - np.array([b,g,r])).sum(1)
+ dists = abs(self.network[i:j] - np.array([b, g, r])).sum(1)
bestpos = i + np.argmin(dists)
biasdists = dists - self.bias[i:j]
bestbiaspos = i + np.argmin(biasdists)
@@ -885,17 +864,14 @@
self.freq[bestpos] += self.BETA
self.bias[bestpos] -= self.BETAGAMMA
return bestbiaspos
-
-
-
def specialFind(self, b, g, r):
for i in range(self.SPECIALS):
n = self.network[i]
if n[0] == b and n[1] == g and n[2] == r:
return i
return -1
-
+
def learn(self):
biasRadius = self.INITBIASRADIUS
alphadec = 30 + ((self.samplefac-1)/3)
@@ -903,72 +879,72 @@
samplepixels = lengthcount / self.samplefac
delta = samplepixels / self.NCYCLES
alpha = self.INITALPHA
-
- i = 0;
+
+ i = 0
rad = biasRadius >> self.RADIUSBIASSHIFT
if rad <= 1:
rad = 0
-
+
print("Beginning 1D learning: samplepixels = %1.2f rad = %i" %
- (samplepixels, rad) )
+ (samplepixels, rad))
step = 0
pos = 0
- if lengthcount%NeuQuant.PRIME1 != 0:
+ if lengthcount % NeuQuant.PRIME1 != 0:
step = NeuQuant.PRIME1
- elif lengthcount%NeuQuant.PRIME2 != 0:
+ elif lengthcount % NeuQuant.PRIME2 != 0:
step = NeuQuant.PRIME2
- elif lengthcount%NeuQuant.PRIME3 != 0:
+ elif lengthcount % NeuQuant.PRIME3 != 0:
step = NeuQuant.PRIME3
else:
step = NeuQuant.PRIME4
-
+
i = 0
printed_string = ''
while i < samplepixels:
- if i%100 == 99:
- tmp = '\b'*len(printed_string)
- printed_string = str((i+1)*100/samplepixels)+"%\n"
+ if i % 100 == 99:
+ tmp = '\b' * len(printed_string)
+ printed_string = str((i + 1) * 100/samplepixels) + "%\n"
print(tmp + printed_string)
p = self.pixels[pos]
r = (p >> 16) & 0xff
- g = (p >> 8) & 0xff
- b = (p ) & 0xff
-
- if i == 0: # Remember background colour
+ g = (p >> 8) & 0xff
+ b = (p) & 0xff
+
+ if i == 0: # Remember background colour
self.network[self.BGCOLOR] = [b, g, r]
-
+
j = self.specialFind(b, g, r)
if j < 0:
j = self.contest(b, g, r)
-
- if j >= self.SPECIALS: # Don't learn for specials
+
+ if j >= self.SPECIALS: # Don't learn for specials
a = (1.0 * alpha) / self.INITALPHA
self.altersingle(a, j, b, g, r)
if rad > 0:
self.alterneigh(a, rad, j, b, g, r)
-
- pos = (pos+step)%lengthcount
-
+
+ pos = (pos + step) % lengthcount
+
i += 1
- if i%delta == 0:
+ if i % delta == 0:
alpha -= alpha / alphadec
biasRadius -= biasRadius / self.RADIUSDEC
rad = biasRadius >> self.RADIUSBIASSHIFT
if rad <= 1:
rad = 0
-
- finalAlpha = (1.0*alpha)/self.INITALPHA
+
+ finalAlpha = (1.0 * alpha)/self.INITALPHA
print("Finished 1D learning: final alpha = %1.2f!" % finalAlpha)
-
+
def fix(self):
for i in range(self.NETSIZE):
for j in range(3):
- x = int(0.5 + self.network[i,j])
+ x = int(0.5 + self.network[i, j])
x = max(0, x)
x = min(255, x)
- self.colormap[i,j] = x
- self.colormap[i,3] = i
-
+ self.colormap[i, j] = x
+ self.colormap[i, 3] = i
+
def inxbuild(self):
previouscol = 0
startpos = 0
@@ -976,107 +952,107 @@
p = self.colormap[i]
q = None
smallpos = i
- smallval = p[1] # Index on g
+ smallval = p[1] # Index on g
# Find smallest in i..self.NETSIZE-1
- for j in range(i+1, self.NETSIZE):
+ for j in range(i + 1, self.NETSIZE):
q = self.colormap[j]
- if q[1] < smallval: # Index on g
+ if q[1] < smallval: # Index on g
smallpos = j
- smallval = q[1] # Index on g
-
+ smallval = q[1] # Index on g
+
q = self.colormap[smallpos]
# Swap p (i) and q (smallpos) entries
if i != smallpos:
- p[:],q[:] = q, p.copy()
-
+ p[:], q[:] = q, p.copy()
+
# smallval entry is now in position i
if smallval != previouscol:
- self.netindex[previouscol] = (startpos+i) >> 1
- for j in range(previouscol+1, smallval):
+ self.netindex[previouscol] = (startpos + i) >> 1
+ for j in range(previouscol + 1, smallval):
self.netindex[j] = i
previouscol = smallval
startpos = i
- self.netindex[previouscol] = (startpos+self.MAXNETPOS) >> 1
- for j in range(previouscol+1, 256): # Really 256
+ self.netindex[previouscol] = (startpos + self.MAXNETPOS) >> 1
+ for j in range(previouscol + 1, 256): # Really 256
self.netindex[j] = self.MAXNETPOS
-
-
+
def paletteImage(self):
- """ PIL weird interface for making a paletted image: create an image which
- already has the palette, and use that in Image.quantize. This function
- returns this palette image. """
+ """PIL weird interface for making a paletted image: create an image
+ which already has the palette, and use that in Image.quantize. This
+ function returns this palette image."""
if self.pimage is None:
palette = []
for i in range(self.NETSIZE):
palette.extend(self.colormap[i][:3])
-
- palette.extend([0]*(256-self.NETSIZE)*3)
-
+
+ palette.extend([0] * (256-self.NETSIZE) * 3)
+
# a palette image to use for quant
self.pimage = Image.new("P", (1, 1), 0)
self.pimage.putpalette(palette)
return self.pimage
-
-
+
def quantize(self, image):
- """ Use a kdtree to quickly find the closest palette colors for the pixels """
+ """Use a kdtree to quickly find the closest palette colors for the
+ pixels
+
+ :param image:
+ """
if get_cKDTree():
return self.quantize_with_scipy(image)
else:
print('Scipy not available, falling back to slower version.')
return self.quantize_without_scipy(image)
-
-
+
def quantize_with_scipy(self, image):
- w,h = image.size
+ w, h = image.size
px = np.asarray(image).copy()
- px2 = px[:,:,:3].reshape((w*h,3))
-
+ px2 = px[:, :, :3].reshape((w * h, 3))
+
cKDTree = get_cKDTree()
- kdtree = cKDTree(self.colormap[:,:3],leafsize=10)
+ kdtree = cKDTree(self.colormap[:, :3], leafsize=10)
result = kdtree.query(px2)
colorindex = result[1]
- print("Distance: %1.2f" % (result[0].sum()/(w*h)) )
- px2[:] = self.colormap[colorindex,:3]
-
+ print("Distance: %1.2f" % (result[0].sum()/(w * h)))
+ px2[:] = self.colormap[colorindex, :3]
+
return Image.fromarray(px).convert("RGB").quantize(palette=self.paletteImage())
-
-
+
def quantize_without_scipy(self, image):
- """" This function can be used if no scipy is availabe.
+ """" This function can be used if no scipy is availabe.
It's 7 times slower though.
+
+ :param image:
"""
- w,h = image.size
+ w, h = image.size
px = np.asarray(image).copy()
memo = {}
for j in range(w):
for i in range(h):
- key = (px[i,j,0],px[i,j,1],px[i,j,2])
+ key = (px[i, j, 0], px[i, j, 1], px[i, j, 2])
try:
val = memo[key]
except KeyError:
val = self.convert(*key)
memo[key] = val
- px[i,j,0],px[i,j,1],px[i,j,2] = val
+ px[i, j, 0], px[i, j, 1], px[i, j, 2] = val
return Image.fromarray(px).convert("RGB").quantize(palette=self.paletteImage())
-
+
def convert(self, *color):
i = self.inxsearch(*color)
- return self.colormap[i,:3]
-
+ return self.colormap[i, :3]
+
def inxsearch(self, r, g, b):
"""Search for BGR values 0..255 and return colour index"""
- dists = (self.colormap[:,:3] - np.array([r,g,b]))
- a= np.argmin((dists*dists).sum(1))
+ dists = (self.colormap[:, :3] - np.array([r, g, b]))
+ a = np.argmin((dists * dists).sum(1))
return a
-
-
if __name__ == '__main__':
- im = np.zeros((200,200), dtype=np.uint8)
- im[10:30,:] = 100
- im[:,80:120] = 255
- im[-50:-40,:] = 50
-
- images = [im*1.0, im*0.8, im*0.6, im*0.4, im*0]
- writeGif('lala3.gif',images, duration=0.5, dither=0)
+ im = np.zeros((200, 200), dtype=np.uint8)
+ im[10: 30, :] = 100
+ im[:, 80: 120] = 255
+ im[-50: -40, :] = 50
+
+ images = [im * 1.0, im * 0.8, im * 0.6, im * 0.4, im * 0]
+ writeGif('lala3.gif', images, duration=0.5, dither=0)
Modified: grass/trunk/lib/python/imaging/images2ims.py
===================================================================
--- grass/trunk/lib/python/imaging/images2ims.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/imaging/images2ims.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -13,11 +13,11 @@
# * 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"
+#
+# 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
+# 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
@@ -36,69 +36,69 @@
try:
import numpy as np
except ImportError:
- np = None
+ 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.
+ """Check numpy images and correct intensity range etc.
The same for all movie formats.
- """
+
+ :param images:
+ """
# 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
+ 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
+ pass # assume 0:255
else:
im = im.copy()
- im[im<0] = 0
- im[im>1] = 1
+ im[im < 0] = 0
+ im[im > 1] = 1
im *= 255
- images2.append( im.astype(np.uint8) )
+ images2.append(im.astype(np.uint8))
else:
im = im.astype(np.uint8)
images2.append(im)
# Check size
if im.ndim == 2:
- pass # ok
+ pass # ok
elif im.ndim == 3:
- if im.shape[2] not in [3,4]:
+ 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) )
+ return tuple(filename.split('*', 1))
else:
return os.path.splitext(filename)
def _getFilenameWithFormatter(filename, N):
-
+
# Determine sequence number formatter
formatter = '%04i'
if N < 10:
@@ -107,12 +107,12 @@
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)]
@@ -128,83 +128,84 @@
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
+ """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,
+
+ 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
+
+ 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.
-
+
+ :param filename:
+ :param images:
"""
-
+
# 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)
+ fname = os.path.join(dirname, filename % seq)
# Write image
if np and isinstance(frame, np.ndarray):
- frame = PIL.Image.fromarray(frame)
+ 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
+
+ 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.
-
+
+ :param filename:
+ :param bool asNumpy:
"""
-
+
# 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):
@@ -213,11 +214,11 @@
# 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])
+
+ # 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
@@ -228,10 +229,10 @@
im = im.convert()
# Make numpy array
a = np.asarray(im)
- if len(a.shape)==0:
+ if len(a.shape) == 0:
raise MemoryError("Too little memory to convert PIL image to array")
# Add
images.append(a)
-
+
# Done
return images
Modified: grass/trunk/lib/python/imaging/images2swf.py
===================================================================
--- grass/trunk/lib/python/imaging/images2swf.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/imaging/images2swf.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -13,11 +13,11 @@
# * 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"
+#
+# 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
+# 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
@@ -27,29 +27,29 @@
""" Module images2swf
-Provides a function (writeSwf) to store a series of PIL images or numpy
-arrays in an SWF movie, that can be played on a wide range of OS's.
+Provides a function (writeSwf) to store a series of PIL images or numpy
+arrays in an SWF movie, that can be played on a wide range of OS's.
This module came into being because I wanted to store a series of images
-in a movie that can be viewed by other people, and which I can embed in
-flash presentations. For writing AVI or MPEG you really need a c/c++
-library, and allthough the filesize is then very small, the quality is
-sometimes not adequate. Besides I'd like to be independant of yet another
-package. I tried writing animated gif using PIL (which is widely available),
+in a movie that can be viewed by other people, and which I can embed in
+flash presentations. For writing AVI or MPEG you really need a c/c++
+library, and allthough the filesize is then very small, the quality is
+sometimes not adequate. Besides I'd like to be independant of yet another
+package. I tried writing animated gif using PIL (which is widely available),
but the quality is so poor because it only allows for 256 different colors.
[EDIT: thanks to Ant1, now the quality of animated gif isn't so bad!]
I also looked into MNG and APNG, two standards similar to the PNG stanard.
Both standards promise exactly what I need. However, hardly any application
-can read those formats, and I cannot import them in flash.
+can read those formats, and I cannot import them in flash.
Therefore I decided to check out the swf file format, which is very well
documented. This is the result: a pure python module to create an SWF file
that shows a series of images. The images are stored using the DEFLATE
algorithm (same as PNG and ZIP and which is included in the standard Python
-distribution). As this compression algorithm is much more effective than
+distribution). As this compression algorithm is much more effective than
that used in GIF images, we obtain better quality (24 bit colors + alpha
channel) while still producesing smaller files (a test showed ~75%).
-Although SWF also allows for JPEG compression, doing so would probably
+Although SWF also allows for JPEG compression, doing so would probably
require a third party library (because encoding JPEG is much harder).
This module requires Python 2.x and numpy.
@@ -62,9 +62,8 @@
- iwisoft swf2avi can be used to convert swf to avi/mpg/flv with really
good quality, while file size is reduced with factors 20-100.
A good program in my opinion. The free version has the limitation
- of a watermark in the upper left corner.
+ of a watermark in the upper left corner.
-
"""
import os, sys, time
@@ -73,9 +72,9 @@
try:
import numpy as np
except ImportError:
- np = None
+ np = None
-try:
+try:
import PIL.Image
except ImportError:
PIL = None
@@ -106,43 +105,43 @@
""" 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
+ 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
+ pass # assume 0:255
else:
im = im.copy()
- im[im<0] = 0
- im[im>1] = 1
+ im[im < 0] = 0
+ im[im > 1] = 1
im *= 255
- images2.append( im.astype(np.uint8) )
+ images2.append(im.astype(np.uint8))
else:
im = im.astype(np.uint8)
images2.append(im)
# Check size
if im.ndim == 2:
- pass # ok
+ pass # ok
elif im.ndim == 3:
- if im.shape[2] not in [3,4]:
+ 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
@@ -151,24 +150,24 @@
class BitArray:
- """ Dynamic array of bits that automatically resizes
- with factors of two.
- Append bits using .Append() or +=
+ """Dynamic array of bits that automatically resizes
+ with factors of two.
+ Append bits using .Append() or +=
You can reverse bits using .Reverse()
"""
-
+
def __init__(self, initvalue=None):
self.data = np.zeros((16,), dtype=np.uint8)
self._len = 0
if initvalue is not None:
self.Append(initvalue)
-
+
def __len__(self):
- return self._len #self.data.shape[0]
-
+ return self._len # self.data.shape[0]
+
def __repr__(self):
return self.data[:self._len].tostring().decode('ascii')
-
+
def _checkSize(self):
# check length... grow if necessary
arraylen = self.data.shape[0]
@@ -176,13 +175,13 @@
tmp = np.zeros((arraylen*2,), dtype=np.uint8)
tmp[:self._len] = self.data[:self._len]
self.data = tmp
-
+
def __add__(self, value):
self.Append(value)
return self
-
+
def Append(self, bits):
-
+
# check input
if isinstance(bits, BitArray):
bits = str(bits)
@@ -190,211 +189,217 @@
bits = str(bits)
if not isinstance(bits, string_types):
raise ValueError("Append bits as strings or integers!")
-
+
# add bits
for bit in bits:
self.data[self._len] = ord(bit)
self._len += 1
self._checkSize()
-
+
def Reverse(self):
""" In-place reverse. """
tmp = self.data[:self._len].copy()
self.data[:self._len] = np.flipud(tmp)
-
+
def ToBytes(self):
""" Convert to bytes. If necessary,
zeros are padded to the end (right side).
"""
bits = str(self)
-
+
# determine number of bytes
nbytes = 0
- while nbytes*8 < len(bits):
- nbytes +=1
+ while nbytes * 8 < len(bits):
+ nbytes += 1
# pad
- bits = bits.ljust(nbytes*8, '0')
-
+ bits = bits.ljust(nbytes * 8, '0')
+
# go from bits to bytes
bb = binary_type()
for i in range(nbytes):
- tmp = int( bits[i*8:(i+1)*8], 2)
+ tmp = int(bits[i * 8: (i + 1) * 8], 2)
bb += intToUint8(tmp)
-
+
# done
return bb
if PY3:
def intToUint32(i):
- return int(i).to_bytes(4,'little')
+ return int(i).to_bytes(4, 'little')
+
def intToUint16(i):
- return int(i).to_bytes(2,'little')
+ return int(i).to_bytes(2, 'little')
+
def intToUint8(i):
- return int(i).to_bytes(1,'little')
+ return int(i).to_bytes(1, 'little')
else:
def intToUint32(i):
number = int(i)
- n1, n2, n3, n4 = 1, 256, 256*256, 256*256*256
+ n1, n2, n3, n4 = 1, 256, 256 * 256, 256 * 256 * 256
b4, number = number // n4, number % n4
b3, number = number // n3, number % n3
b2, number = number // n2, number % n2
b1 = number
return chr(b1) + chr(b2) + chr(b3) + chr(b4)
+
def intToUint16(i):
i = int(i)
- # devide in two parts (bytes)
+ # devide in two parts (bytes)
i1 = i % 256
- i2 = int( i//256)
+ i2 = int(i // 256)
# make string (little endian)
return chr(i1) + chr(i2)
+
def intToUint8(i):
return chr(int(i))
-def intToBits(i,n=None):
- """ convert int to a string of bits (0's and 1's in a string),
+def intToBits(i, n=None):
+ """ convert int to a string of bits (0's and 1's in a string),
pad to n elements. Convert back using int(ss,2). """
ii = i
-
- # make bits
+
+ # make bits
bb = BitArray()
while ii > 0:
bb += str(ii % 2)
ii = ii >> 1
bb.Reverse()
-
+
# justify
if n is not None:
if len(bb) > n:
raise ValueError("intToBits fail: len larger than padlength.")
- bb = str(bb).rjust(n,'0')
-
+ bb = str(bb).rjust(n, '0')
+
# done
return BitArray(bb)
+
def bitsToInt(bb, n=8):
# Init
value = ''
-
+
# Get value in bits
for i in range(len(bb)):
b = bb[i:i+1]
tmp = bin(ord(b))[2:]
#value += tmp.rjust(8,'0')
- value = tmp.rjust(8,'0') + value
-
+ value = tmp.rjust(8, '0') + value
+
# Make decimal
- return( int(value[:n], 2) )
+ return(int(value[:n], 2))
+
def getTypeAndLen(bb):
""" bb should be 6 bytes at least
Return (type, length, length_of_full_tag)
"""
# Init
value = ''
-
+
# Get first 16 bits
for i in range(2):
- b = bb[i:i+1]
+ b = bb[i:i + 1]
tmp = bin(ord(b))[2:]
#value += tmp.rjust(8,'0')
- value = tmp.rjust(8,'0') + value
-
+ value = tmp.rjust(8, '0') + value
+
# Get type and length
- type = int( value[:10], 2)
- L = int( value[10:], 2)
+ type = int(value[:10], 2)
+ L = int(value[10:], 2)
L2 = L + 2
-
+
# Long tag header?
- if L == 63: # '111111'
+ if L == 63: # '111111'
value = ''
- for i in range(2,6):
- b = bb[i:i+1] # becomes a single-byte bytes() on both PY3 and PY2
+ for i in range(2, 6):
+ b = bb[i:i + 1] # becomes a single-byte bytes() on both PY3 and PY2
tmp = bin(ord(b))[2:]
#value += tmp.rjust(8,'0')
- value = tmp.rjust(8,'0') + value
- L = int( value, 2)
+ value = tmp.rjust(8, '0') + value
+ L = int(value, 2)
L2 = L + 6
-
- # Done
+
+ # Done
return type, L, L2
-def signedIntToBits(i,n=None):
- """ convert signed int to a string of bits (0's and 1's in a string),
+def signedIntToBits(i, n=None):
+ """ convert signed int to a string of bits (0's and 1's in a string),
pad to n elements. Negative numbers are stored in 2's complement bit
patterns, thus positive numbers always start with a 0.
"""
-
+
# negative number?
- ii = i
- if i<0:
+ ii = i
+ if i < 0:
# A negative number, -n, is represented as the bitwise opposite of
- ii = abs(ii) -1 # the positive-zero number n-1.
-
- # make bits
+ ii = abs(ii) - 1 # the positive-zero number n-1.
+
+ # make bits
bb = BitArray()
while ii > 0:
bb += str(ii % 2)
ii = ii >> 1
bb.Reverse()
-
+
# justify
- bb = '0' + str(bb) # always need the sign bit in front
+ bb = '0' + str(bb) # always need the sign bit in front
if n is not None:
if len(bb) > n:
raise ValueError("signedIntToBits fail: len larger than padlength.")
- bb = bb.rjust(n,'0')
-
+ bb = bb.rjust(n, '0')
+
# was it negative? (then opposite bits)
- if i<0:
- bb = bb.replace('0','x').replace('1','0').replace('x','1')
-
+ if i < 0:
+ bb = bb.replace('0', 'x').replace('1', '0').replace('x', '1')
+
# done
return BitArray(bb)
def twitsToBits(arr):
- """ Given a few (signed) numbers, store them
+ """ Given a few (signed) numbers, store them
as compactly as possible in the wat specifief by the swf format.
The numbers are multiplied by 20, assuming they
are twits.
Can be used to make the RECT record.
"""
-
+
# first determine length using non justified bit strings
maxlen = 1
for i in arr:
tmp = len(signedIntToBits(i*20))
if tmp > maxlen:
maxlen = tmp
-
+
# build array
- bits = intToBits(maxlen,5)
+ bits = intToBits(maxlen, 5)
for i in arr:
- bits += signedIntToBits(i*20, maxlen)
-
+ bits += signedIntToBits(i * 20, maxlen)
+
return bits
def floatsToBits(arr):
- """ Given a few (signed) numbers, convert them to bits,
- stored as FB (float bit values). We always use 16.16.
+ """ Given a few (signed) numbers, convert them to bits,
+ stored as FB (float bit values). We always use 16.16.
Negative numbers are not (yet) possible, because I don't
know how the're implemented (ambiguity).
"""
- bits = intToBits(31, 5) # 32 does not fit in 5 bits!
+ bits = intToBits(31, 5) # 32 does not fit in 5 bits!
for i in arr:
- if i<0:
+ if i < 0:
raise ValueError("Dit not implement negative floats!")
i1 = int(i)
i2 = i - i1
bits += intToBits(i1, 15)
- bits += intToBits(i2*2**16, 16)
+ bits += intToBits(i2 * 2 ** 16, 16)
return bits
-
+
def _readFrom(fp, n):
bb = binary_type()
try:
@@ -411,68 +416,68 @@
## Base Tag
class Tag:
-
+
def __init__(self):
- self.bytes = binary_type()
+ self.bytes = binary_type()
self.tagtype = -1
-
+
def ProcessTag(self):
""" Implement this to create the tag. """
raise NotImplemented()
-
+
def GetTag(self):
""" Calls processTag and attaches the header. """
self.ProcessTag()
-
+
# tag to binary
- bits = intToBits(self.tagtype,10)
-
+ bits = intToBits(self.tagtype, 10)
+
# complete header uint16 thing
- bits += '1'*6 # = 63 = 0x3f
+ bits += '1' * 6 # = 63 = 0x3f
# make uint16
- bb = intToUint16( int(str(bits),2) )
-
+ bb = intToUint16(int(str(bits), 2))
+
# now add 32bit length descriptor
bb += intToUint32(len(self.bytes))
-
+
# done, attach and return
bb += self.bytes
return bb
-
+
def MakeRectRecord(self, xmin, xmax, ymin, ymax):
""" Simply uses makeCompactArray to produce
a RECT Record. """
return twitsToBits([xmin, xmax, ymin, ymax])
def MakeMatrixRecord(self, scale_xy=None, rot_xy=None, trans_xy=None):
-
+
# empty matrix?
if scale_xy is None and rot_xy is None and trans_xy is None:
return "0"*8
-
+
# init
bits = BitArray()
-
+
# scale
- if scale_xy:
+ if scale_xy:
bits += '1'
bits += floatsToBits([scale_xy[0], scale_xy[1]])
- else:
+ else:
bits += '0'
-
+
# rotation
- if rot_xy:
+ if rot_xy:
bits += '1'
bits += floatsToBits([rot_xy[0], rot_xy[1]])
- else:
+ else:
bits += '0'
-
+
# translation (no flag here)
- if trans_xy:
+ if trans_xy:
bits += twitsToBits([trans_xy[0], trans_xy[1]])
- else:
- bits += twitsToBits([0,0])
-
+ else:
+ bits += twitsToBits([0, 0])
+
# done
return bits
@@ -488,7 +493,7 @@
def __init__(self):
ControlTag.__init__(self)
self.tagtype = 69
-
+
def ProcessTag(self):
self.bytes = '\x00'.encode('ascii') * (1+3)
@@ -497,20 +502,22 @@
def __init__(self):
ControlTag.__init__(self)
self.tagtype = 1
+
def ProcessTag(self):
self.bytes = binary_type()
+
class SetBackgroundTag(ControlTag):
""" Set the color in 0-255, or 0-1 (if floats given). """
def __init__(self, *rgb):
self.tagtype = 9
- if len(rgb)==1:
+ if len(rgb) == 1:
rgb = rgb[0]
self.rgb = rgb
-
+
def ProcessTag(self):
bb = binary_type()
- for i in range(3):
+ for i in range(3):
clr = self.rgb[i]
if isinstance(clr, float):
clr = clr * 255
@@ -523,31 +530,30 @@
Tag.__init__(self)
self.tagtype = 12
self.actions = [action]
-
+
def Append(self, action):
- self.actions.append( action )
-
+ self.actions.append(action)
+
def ProcessTag(self):
bb = binary_type()
-
+
for action in self.actions:
- action = action.lower()
+ action = action.lower()
if action == 'stop':
bb += '\x07'.encode('ascii')
elif action == 'play':
bb += '\x06'.encode('ascii')
else:
print("warning, unkown action: %s" % action)
-
+
bb += intToUint8(0)
self.bytes = bb
-
## Definition tags
+class DefinitionTag(Tag):
+ counter = 0 # to give automatically id's
-class DefinitionTag(Tag):
- counter = 0 # to give automatically id's
def __init__(self):
Tag.__init__(self)
DefinitionTag.counter += 1
@@ -555,80 +561,80 @@
class BitmapTag(DefinitionTag):
-
+
def __init__(self, im):
DefinitionTag.__init__(self)
- self.tagtype = 36 # DefineBitsLossless2
-
+ self.tagtype = 36 # DefineBitsLossless2
+
# convert image (note that format is ARGB)
# even a grayscale image is stored in ARGB, nevertheless,
# the fabilous deflate compression will make it that not much
# more data is required for storing (25% or so, and less than 10%
# when storing RGB as ARGB).
-
- if len(im.shape)==3:
+
+ if len(im.shape) == 3:
if im.shape[2] in [3, 4]:
- tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8)*255
- for i in range(3):
- tmp[:,:,i+1] = im[:,:,i]
- if im.shape[2]==4:
- tmp[:,:,0] = im[:,:,3] # swap channel where alpha is in
+ tmp = np.ones((im.shape[0], im.shape[1], 4),
+ dtype=np.uint8) * 255
+ for i in range(3):
+ tmp[:, :, i + 1] = im[:, :, i]
+ if im.shape[2] == 4:
+ tmp[:, :, 0] = im[:, :, 3] # swap channel where alpha is in
else:
raise ValueError("Invalid shape to be an image.")
-
- elif len(im.shape)==2:
+
+ elif len(im.shape) == 2:
tmp = np.ones((im.shape[0], im.shape[1], 4), dtype=np.uint8)*255
for i in range(3):
- tmp[:,:,i+1] = im[:,:]
+ tmp[:, :, i + 1] = im[:, :]
else:
raise ValueError("Invalid shape to be an image.")
-
+
# we changed the image to uint8 4 channels.
# now compress!
self._data = zlib.compress(tmp.tostring(), zlib.DEFLATED)
self.imshape = im.shape
-
-
+
def ProcessTag(self):
-
+
# build tag
- bb = binary_type()
- bb += intToUint16(self.id) # CharacterID
+ bb = binary_type()
+ bb += intToUint16(self.id) # CharacterID
bb += intToUint8(5) # BitmapFormat
bb += intToUint16(self.imshape[1]) # BitmapWidth
- bb += intToUint16(self.imshape[0]) # BitmapHeight
+ bb += intToUint16(self.imshape[0]) # BitmapHeight
bb += self._data # ZlibBitmapData
-
+
self.bytes = bb
class PlaceObjectTag(ControlTag):
- def __init__(self, depth, idToPlace=None, xy=(0,0), move=False):
+ def __init__(self, depth, idToPlace=None, xy=(0, 0), move=False):
ControlTag.__init__(self)
self.tagtype = 26
self.depth = depth
self.idToPlace = idToPlace
self.xy = xy
self.move = move
-
+
def ProcessTag(self):
# retrieve stuff
depth = self.depth
xy = self.xy
id = self.idToPlace
-
+
# build PlaceObject2
bb = binary_type()
if self.move:
bb += '\x07'.encode('ascii')
else:
bb += '\x06'.encode('ascii') # (8 bit flags): 4:matrix, 2:character, 1:move
- bb += intToUint16(depth) # Depth
- bb += intToUint16(id) # character id
- bb += self.MakeMatrixRecord(trans_xy=xy).ToBytes() # MATRIX record
+ bb += intToUint16(depth) # Depth
+ bb += intToUint16(id) # character id
+ bb += self.MakeMatrixRecord(trans_xy=xy).ToBytes() # MATRIX record
self.bytes = bb
-
+
class ShapeTag(DefinitionTag):
def __init__(self, bitmapId, xy, wh):
DefinitionTag.__init__(self)
@@ -636,168 +642,170 @@
self.bitmapId = bitmapId
self.xy = xy
self.wh = wh
-
+
def ProcessTag(self):
""" Returns a defineshape tag. with a bitmap fill """
-
+
bb = binary_type()
bb += intToUint16(self.id)
xy, wh = self.xy, self.wh
- tmp = self.MakeRectRecord(xy[0],wh[0],xy[1],wh[1]) # ShapeBounds
+ tmp = self.MakeRectRecord(xy[0], wh[0], xy[1], wh[1]) # ShapeBounds
bb += tmp.ToBytes()
-
+
# make SHAPEWITHSTYLE structure
-
+
# first entry: FILLSTYLEARRAY with in it a single fill style
bb += intToUint8(1) # FillStyleCount
- bb += '\x41'.encode('ascii') # FillStyleType (0x41 or 0x43, latter is non-smoothed)
+ bb += '\x41'.encode('ascii') # FillStyleType (0x41 or 0x43, latter is non-smoothed)
bb += intToUint16(self.bitmapId) # BitmapId
#bb += '\x00' # BitmapMatrix (empty matrix with leftover bits filled)
- bb += self.MakeMatrixRecord(scale_xy=(20,20)).ToBytes()
-
+ bb += self.MakeMatrixRecord(scale_xy=(20, 20)).ToBytes()
+
# # first entry: FILLSTYLEARRAY with in it a single fill style
# bb += intToUint8(1) # FillStyleCount
# bb += '\x00' # solid fill
# bb += '\x00\x00\xff' # color
-
-
+
# second entry: LINESTYLEARRAY with a single line style
bb += intToUint8(0) # LineStyleCount
#bb += intToUint16(0*20) # Width
#bb += '\x00\xff\x00' # Color
-
+
# third and fourth entry: NumFillBits and NumLineBits (4 bits each)
- bb += '\x44'.encode('ascii') # I each give them four bits, so 16 styles possible.
-
+ # I each give them four bits, so 16 styles possible.
+ bb += '\x44'.encode('ascii')
+
self.bytes = bb
-
+
# last entries: SHAPERECORDs ... (individual shape records not aligned)
# STYLECHANGERECORD
bits = BitArray()
- bits += self.MakeStyleChangeRecord(0,1,moveTo=(self.wh[0],self.wh[1]))
+ bits += self.MakeStyleChangeRecord(0, 1, moveTo=(self.wh[0],
+ self.wh[1]))
# STRAIGHTEDGERECORD 4x
bits += self.MakeStraightEdgeRecord(-self.wh[0], 0)
bits += self.MakeStraightEdgeRecord(0, -self.wh[1])
bits += self.MakeStraightEdgeRecord(self.wh[0], 0)
bits += self.MakeStraightEdgeRecord(0, self.wh[1])
-
+
# ENDSHAPRECORD
bits += self.MakeEndShapeRecord()
-
+
self.bytes += bits.ToBytes()
-
+
# done
#self.bytes = bb
- def MakeStyleChangeRecord(self, lineStyle=None, fillStyle=None, moveTo=None):
-
+ def MakeStyleChangeRecord(self, lineStyle=None, fillStyle=None,
+ moveTo=None):
+
# first 6 flags
# Note that we use FillStyle1. If we don't flash (at least 8) does not
# recognize the frames properly when importing to library.
-
+
bits = BitArray()
- bits += '0' # TypeFlag (not an edge record)
- bits += '0' # StateNewStyles (only for DefineShape2 and Defineshape3)
- if lineStyle: bits += '1' # StateLineStyle
- else: bits += '0'
- if fillStyle: bits += '1' # StateFillStyle1
- else: bits += '0'
- bits += '0' # StateFillStyle0
- if moveTo: bits += '1' # StateMoveTo
- else: bits += '0'
-
+ bits += '0' # TypeFlag (not an edge record)
+ bits += '0' # StateNewStyles (only for DefineShape2 and Defineshape3)
+ if lineStyle:
+ bits += '1' # StateLineStyle
+ else:
+ bits += '0'
+ if fillStyle:
+ bits += '1' # StateFillStyle1
+ else:
+ bits += '0'
+ bits += '0' # StateFillStyle0
+ if moveTo:
+ bits += '1' # StateMoveTo
+ else:
+ bits += '0'
+
# give information
# todo: nbits for fillStyle and lineStyle is hard coded.
-
+
if moveTo:
bits += twitsToBits([moveTo[0], moveTo[1]])
if fillStyle:
- bits += intToBits(fillStyle,4)
+ bits += intToBits(fillStyle, 4)
if lineStyle:
- bits += intToBits(lineStyle,4)
-
+ bits += intToBits(lineStyle, 4)
+
return bits
#return bitsToBytes(bits)
-
def MakeStraightEdgeRecord(self, *dxdy):
- if len(dxdy)==1:
+ if len(dxdy) == 1:
dxdy = dxdy[0]
-
+
# determine required number of bits
- xbits, ybits = signedIntToBits(dxdy[0]*20), signedIntToBits(dxdy[1]*20)
- nbits = max([len(xbits),len(ybits)])
-
+ xbits = signedIntToBits(dxdy[0] * 20)
+ ybits = signedIntToBits(dxdy[1] * 20)
+ nbits = max([len(xbits), len(ybits)])
+
bits = BitArray()
bits += '11' # TypeFlag and StraightFlag
- bits += intToBits(nbits-2,4)
- bits += '1' # GeneralLineFlag
- bits += signedIntToBits(dxdy[0]*20,nbits)
- bits += signedIntToBits(dxdy[1]*20,nbits)
-
+ bits += intToBits(nbits-2, 4)
+ bits += '1' # GeneralLineFlag
+ bits += signedIntToBits(dxdy[0] * 20, nbits)
+ bits += signedIntToBits(dxdy[1] * 20, nbits)
+
# note: I do not make use of vertical/horizontal only lines...
-
+
return bits
#return bitsToBytes(bits)
-
def MakeEndShapeRecord(self):
bits = BitArray()
- bits += "0" # TypeFlag: no edge
+ bits += "0" # TypeFlag: no edge
bits += "0"*5 # EndOfShape
return bits
#return bitsToBytes(bits)
## Last few functions
+def buildFile(fp, taglist, nframes=1, framesize=(500, 500), fps=10, version=8):
+ """ Give the given file (as bytes) a header. """
-
-
-def buildFile(fp, taglist, nframes=1, framesize=(500,500), fps=10, version=8):
- """ Give the given file (as bytes) a header. """
-
# compose header
bb = binary_type()
- bb += 'F'.encode('ascii') # uncompressed
+ bb += 'F'.encode('ascii') # uncompressed
bb += 'WS'.encode('ascii') # signature bytes
- bb += intToUint8(version) # version
- bb += '0000'.encode('ascii') # FileLength (leave open for now)
- bb += Tag().MakeRectRecord(0,framesize[0], 0, framesize[1]).ToBytes()
- bb += intToUint8(0) + intToUint8(fps) # FrameRate
- bb += intToUint16(nframes)
+ bb += intToUint8(version) # version
+ bb += '0000'.encode('ascii') # FileLength (leave open for now)
+ bb += Tag().MakeRectRecord(0, framesize[0], 0, framesize[1]).ToBytes()
+ bb += intToUint8(0) + intToUint8(fps) # FrameRate
+ bb += intToUint16(nframes)
fp.write(bb)
-
- # produce all tags
+
+ # produce all tags
for tag in taglist:
- fp.write( tag.GetTag() )
-
+ fp.write(tag.GetTag())
+
# finish with end tag
- fp.write( '\x00\x00'.encode('ascii') )
-
+ fp.write('\x00\x00'.encode('ascii'))
+
# set size
- sze = fp.tell()
+ sze = fp.tell()
fp.seek(4)
- fp.write( intToUint32(sze) )
+ fp.write(intToUint32(sze))
def writeSwf(filename, images, duration=0.1, repeat=True):
- """ writeSwf(filename, images, duration=0.1, repeat=True)
-
- Write an swf-file from the specified images. If repeat is False,
+ """Write an swf-file from the specified images. If repeat is False,
the movie is finished with a stop action. Duration may also
be a list with durations for each frame (note that the duration
for each frame is always an integer amount of the minimum duration.)
-
- Images should be a list consisting of PIL images or numpy arrays.
- The latter should be between 0 and 255 for integer types, and
+
+ 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 Numpy
if np is None:
raise RuntimeError("Need Numpy to write an SWF file.")
-
+
# Check images (make all Numpy)
images2 = []
images = checkImages(images)
@@ -808,13 +816,13 @@
if im.mode == 'P':
im = im.convert()
im = np.asarray(im)
- if len(im.shape)==0:
+ if len(im.shape) == 0:
raise MemoryError("Too little memory to convert PIL image to array")
images2.append(im)
-
- # Init
- taglist = [ FileAttributesTag(), SetBackgroundTag(0,0,0) ]
-
+
+ # Init
+ taglist = [FileAttributesTag(), SetBackgroundTag(0, 0, 0)]
+
# Check duration
if hasattr(duration, '__len__'):
if len(duration) == len(images2):
@@ -823,71 +831,66 @@
raise ValueError("len(duration) doesn't match amount of images.")
else:
duration = [duration for im in images2]
-
+
# Build delays list
minDuration = float(min(duration))
delays = [round(d/minDuration) for d in duration]
- delays = [max(1,int(d)) for d in delays]
-
+ delays = [max(1, int(d)) for d in delays]
+
# Get FPS
fps = 1.0/minDuration
-
+
# Produce series of tags for each image
- t0 = time.time()
nframes = 0
for im in images2:
bm = BitmapTag(im)
wh = (im.shape[1], im.shape[0])
- sh = ShapeTag(bm.id, (0,0), wh)
- po = PlaceObjectTag(1,sh.id, move=nframes>0)
- taglist.extend( [bm, sh, po] )
+ sh = ShapeTag(bm.id, (0, 0), wh)
+ po = PlaceObjectTag(1, sh.id, move=nframes > 0)
+ taglist.extend([bm, sh, po])
for i in range(delays[nframes]):
- taglist.append( ShowFrameTag() )
+ taglist.append(ShowFrameTag())
nframes += 1
-
+
if not repeat:
taglist.append(DoActionTag('stop'))
-
+
# Build file
- t1 = time.time()
- fp = open(filename,'wb')
+ fp = open(filename, 'wb')
try:
buildFile(fp, taglist, nframes=nframes, framesize=wh, fps=fps)
except Exception:
raise
finally:
fp.close()
- t2 = time.time()
-
- #print("Writing SWF took %1.2f and %1.2f seconds" % (t1-t0, t2-t1) )
-
+
def _readPixels(bb, i, tagType, L1):
""" With pf's seed after the recordheader, reads the pixeldata.
"""
-
+
# Check Numpy
if np is None:
raise RuntimeError("Need Numpy to read an SWF file.")
-
+
# Get info
- charId = bb[i:i+2]; i+=2
- format = ord(bb[i:i+1]); i+=1
- width = bitsToInt( bb[i:i+2], 16 ); i+=2
- height = bitsToInt( bb[i:i+2], 16 ); i+=2
-
+ charId = bb[i:i + 2]; i += 2
+ format = ord(bb[i:i + 1]); i += 1
+ width = bitsToInt(bb[i:i + 2], 16); i += 2
+ height = bitsToInt(bb[i:i + 2], 16); i += 2
+
# If we can, get pixeldata and make nunmpy array
if format != 5:
print("Can only read 24bit or 32bit RGB(A) lossless images.")
else:
# Read byte data
- offset = 2+1+2+2 # all the info bits
+ offset = 2 + 1 + 2 + 2 # all the info bits
bb2 = bb[i:i+(L1-offset)]
-
+
# Decompress and make numpy array
data = zlib.decompress(bb2)
a = np.frombuffer(data, dtype=np.uint8)
-
+
# Set shape
if tagType == 20:
# DefineBitsLossless - RGB data
@@ -902,81 +905,79 @@
# Swap alpha channel to make RGBA
b = a
a = np.zeros_like(a)
- a[:,:,0] = b[:,:,1]
- a[:,:,1] = b[:,:,2]
- a[:,:,2] = b[:,:,3]
- a[:,:,3] = b[:,:,0]
-
+ a[:, :, 0] = b[:, :, 1]
+ a[:, :, 1] = b[:, :, 2]
+ a[:, :, 2] = b[:, :, 3]
+ a[:, :, 3] = b[:, :, 0]
+
return a
def readSwf(filename, asNumpy=True):
- """ readSwf(filename, asNumpy=True)
-
- Read all images from an SWF (shockwave flash) file. Returns a list
+ """Read all images from an SWF (shockwave flash) file. Returns a list
of numpy arrays, or, if asNumpy is false, a list if PIL images.
-
+
Limitation: only read the PNG encoded images (not the JPG encoded ones).
-
+
"""
-
+
# Check whether it exists
if not os.path.isfile(filename):
raise IOError('File not found: '+str(filename))
-
+
# Check PIL
if (not asNumpy) and (PIL is None):
raise RuntimeError("Need PIL to return as PIL images.")
-
+
# Check Numpy
if np is None:
raise RuntimeError("Need Numpy to read SWF files.")
-
+
# Init images
images = []
-
+
# Open file and read all
fp = open(filename, 'rb')
bb = fp.read()
-
+
try:
# Check opening tag
tmp = bb[0:3].decode('ascii', 'ignore')
if tmp.upper() == 'FWS':
- pass # ok
+ pass # ok
elif tmp.upper() == 'CWS':
# Decompress movie
bb = bb[:8] + zlib.decompress(bb[8:])
else:
raise IOError('Not a valid SWF file: ' + str(filename))
-
+
# Set filepointer at first tag (skipping framesize RECT and two uin16's
i = 8
- nbits = bitsToInt(bb[i:i+1], 5) # skip FrameSize
+ nbits = bitsToInt(bb[i: i + 1], 5) # skip FrameSize
nbits = 5 + nbits * 4
Lrect = nbits / 8.0
- if Lrect%1:
- Lrect += 1
+ if Lrect % 1:
+ Lrect += 1
Lrect = int(Lrect)
i += Lrect+4
-
+
# Iterate over the tags
counter = 0
while True:
counter += 1
-
+
# Get tag header
head = bb[i:i+6]
if not head:
- break # Done (we missed end tag)
-
+ break # Done (we missed end tag)
+
# Determine type and length
- T, L1, L2 = getTypeAndLen( head )
+ T, L1, L2 = getTypeAndLen(head)
if not L2:
print('Invalid tag length, could not proceed')
break
#print(T, L2)
-
+
# Read image if we can
if T in [20, 36]:
im = _readPixels(bb, i+6, T, L1)
@@ -985,24 +986,24 @@
elif T in [6, 21, 35, 90]:
print('Ignoring JPEG image: cannot read JPEG.')
else:
- pass # Not an image tag
-
+ pass # Not an image tag
+
# Detect end tag
- if T==0:
+ if T == 0:
break
-
+
# Next tag!
i += L2
-
+
finally:
fp.close()
-
+
# Convert to normal PIL images if needed
if not asNumpy:
images2 = images
images = []
- for im in images2:
- images.append( PIL.Image.fromarray(im) )
-
+ for im in images2:
+ images.append(PIL.Image.fromarray(im))
+
# Done
return images
Modified: grass/trunk/lib/python/pydispatch/dispatcher.py
===================================================================
--- grass/trunk/lib/python/pydispatch/dispatcher.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/dispatcher.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -6,24 +6,24 @@
Module attributes of note:
- Any -- Singleton used to signal either "Any Sender" or
- "Any Signal". See documentation of the _Any class.
- Anonymous -- Singleton used to signal "Anonymous Sender"
- See documentation of the _Anonymous class.
+ Any -- Singleton used to signal either "Any Sender" or
+ "Any Signal". See documentation of the _Any class.
+ Anonymous -- Singleton used to signal "Anonymous Sender"
+ See documentation of the _Anonymous class.
Internal attributes:
- WEAKREF_TYPES -- tuple of types/classes which represent
- weak references to receivers, and thus must be de-
- referenced on retrieval to retrieve the callable
- object
- connections -- { senderkey (id) : { signal : [receivers...]}}
- senders -- { senderkey (id) : weakref(sender) }
- used for cleaning up sender references on sender
- deletion
- sendersBack -- { receiverkey (id) : [senderkey (id)...] }
- used for cleaning up receiver references on receiver
- deletion, (considerably speeds up the cleanup process
- vs. the original code.)
+ WEAKREF_TYPES -- tuple of types/classes which represent
+ weak references to receivers, and thus must be de-
+ referenced on retrieval to retrieve the callable
+ object
+ connections -- { senderkey (id) : { signal : [receivers...]}}
+ senders -- { senderkey (id) : weakref(sender) }
+ used for cleaning up sender references on sender
+ deletion
+ sendersBack -- { receiverkey (id) : [senderkey (id)...] }
+ used for cleaning up receiver references on receiver
+ deletion, (considerably speeds up the cleanup process
+ vs. the original code.)
"""
from __future__ import generators
import weakref
@@ -33,39 +33,42 @@
__cvsid__ = "Id: dispatcher.py,v 1.1 2010/03/30 15:45:55 mcfletch Exp"
__version__ = "Revision: 1.1"
+
class _Parameter:
- """Used to represent default parameter values."""
- def __repr__(self):
- return self.__class__.__name__
+ """Used to represent default parameter values."""
+ def __repr__(self):
+ return self.__class__.__name__
+
class _Any(_Parameter):
- """Singleton used to signal either "Any Sender" or "Any Signal"
+ """Singleton used to signal either "Any Sender" or "Any Signal"
- The Any object can be used with connect, disconnect,
- send, or sendExact to signal that the parameter given
- Any should react to all senders/signals, not just
- a particular sender/signal.
- """
+ The Any object can be used with connect, disconnect,
+ send, or sendExact to signal that the parameter given
+ Any should react to all senders/signals, not just
+ a particular sender/signal.
+ """
Any = _Any()
+
class _Anonymous(_Parameter):
- """Singleton used to signal "Anonymous Sender"
+ """Singleton used to signal "Anonymous Sender"
- The Anonymous object is used to signal that the sender
- of a message is not specified (as distinct from being
- "any sender"). Registering callbacks for Anonymous
- will only receive messages sent without senders. Sending
- with anonymous will only send messages to those receivers
- registered for Any or Anonymous.
+ The Anonymous object is used to signal that the sender
+ of a message is not specified (as distinct from being
+ "any sender"). Registering callbacks for Anonymous
+ will only receive messages sent without senders. Sending
+ with anonymous will only send messages to those receivers
+ registered for Any or Anonymous.
- Note:
- The default sender for connect is Any, while the
- default sender for send is Anonymous. This has
- the effect that if you do not specify any senders
- in either function then all messages are routed
- as though there was a single sender (Anonymous)
- being used everywhere.
- """
+ Note:
+ The default sender for connect is Any, while the
+ default sender for send is Anonymous. This has
+ the effect that if you do not specify any senders
+ in either function then all messages are routed
+ as though there was a single sender (Anonymous)
+ being used everywhere.
+ """
Anonymous = _Anonymous()
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
@@ -76,419 +79,428 @@
def connect(receiver, signal=Any, sender=Any, weak=True):
- """Connect receiver to sender for signal
+ """Connect receiver to sender for signal
- receiver -- a callable Python object which is to receive
- messages/signals/events. Receivers must be hashable
- objects.
+ receiver -- a callable Python object which is to receive
+ messages/signals/events. Receivers must be hashable
+ objects.
- if weak is True, then receiver must be weak-referencable
- (more precisely saferef.safeRef() must be able to create
- a reference to the receiver).
-
- Receivers are fairly flexible in their specification,
- as the machinery in the robustApply module takes care
- of most of the details regarding figuring out appropriate
- subsets of the sent arguments to apply to a given
- receiver.
+ if weak is True, then receiver must be weak-referencable
+ (more precisely saferef.safeRef() must be able to create
+ a reference to the receiver).
- Note:
- if receiver is itself a weak reference (a callable),
- it will be de-referenced by the system's machinery,
- so *generally* weak references are not suitable as
- receivers, though some use might be found for the
- facility whereby a higher-level library passes in
- pre-weakrefed receiver references.
+ Receivers are fairly flexible in their specification,
+ as the machinery in the robustApply module takes care
+ of most of the details regarding figuring out appropriate
+ subsets of the sent arguments to apply to a given
+ receiver.
- signal -- the signal to which the receiver should respond
-
- if Any, receiver will receive any signal from the
- indicated sender (which might also be Any, but is not
- necessarily Any).
-
- Otherwise must be a hashable Python object other than
- None (DispatcherError raised on None).
-
- sender -- the sender to which the receiver should respond
-
- if Any, receiver will receive the indicated signals
- from any sender.
-
- if Anonymous, receiver will only receive indicated
- signals from send/sendExact which do not specify a
- sender, or specify Anonymous explicitly as the sender.
+ Note:
+ if receiver is itself a weak reference (a callable),
+ it will be de-referenced by the system's machinery,
+ so *generally* weak references are not suitable as
+ receivers, though some use might be found for the
+ facility whereby a higher-level library passes in
+ pre-weakrefed receiver references.
- Otherwise can be any python object.
-
- weak -- whether to use weak references to the receiver
- By default, the module will attempt to use weak
- references to the receiver objects. If this parameter
- is false, then strong references will be used.
+ signal -- the signal to which the receiver should respond
- returns None, may raise DispatcherTypeError
- """
- if signal is None:
- raise errors.DispatcherTypeError(
- 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
- )
- if weak:
- receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
- senderkey = id(sender)
- if senderkey in connections:
- signals = connections[senderkey]
- else:
- connections[senderkey] = signals = {}
- # Keep track of senders for cleanup.
- # Is Anonymous something we want to clean up?
- if sender not in (None, Anonymous, Any):
- def remove(object, senderkey=senderkey):
- _removeSender(senderkey=senderkey)
- # Skip objects that can not be weakly referenced, which means
- # they won't be automatically cleaned up, but that's too bad.
- try:
- weakSender = weakref.ref(sender, remove)
- senders[senderkey] = weakSender
- except:
- pass
-
- receiverID = id(receiver)
- # get current set, remove any current references to
- # this receiver in the set, including back-references
- if signal in signals:
- receivers = signals[signal]
- _removeOldBackRefs(senderkey, signal, receiver, receivers)
- else:
- receivers = signals[signal] = []
- try:
- current = sendersBack.get( receiverID )
- if current is None:
- sendersBack[ receiverID ] = current = []
- if senderkey not in current:
- current.append(senderkey)
- except:
- pass
+ if Any, receiver will receive any signal from the
+ indicated sender (which might also be Any, but is not
+ necessarily Any).
- receivers.append(receiver)
+ Otherwise must be a hashable Python object other than
+ None (DispatcherError raised on None).
+ sender -- the sender to which the receiver should respond
+ if Any, receiver will receive the indicated signals
+ from any sender.
+ if Anonymous, receiver will only receive indicated
+ signals from send/sendExact which do not specify a
+ sender, or specify Anonymous explicitly as the sender.
+
+ Otherwise can be any python object.
+
+ weak -- whether to use weak references to the receiver
+ By default, the module will attempt to use weak
+ references to the receiver objects. If this parameter
+ is false, then strong references will be used.
+
+ returns None, may raise DispatcherTypeError
+ """
+ if signal is None:
+ raise errors.DispatcherTypeError(
+ 'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
+ sender)
+ )
+ if weak:
+ receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
+ senderkey = id(sender)
+ if senderkey in connections:
+ signals = connections[senderkey]
+ else:
+ connections[senderkey] = signals = {}
+ # Keep track of senders for cleanup.
+ # Is Anonymous something we want to clean up?
+ if sender not in (None, Anonymous, Any):
+ def remove(object, senderkey=senderkey):
+ _removeSender(senderkey=senderkey)
+ # Skip objects that can not be weakly referenced, which means
+ # they won't be automatically cleaned up, but that's too bad.
+ try:
+ weakSender = weakref.ref(sender, remove)
+ senders[senderkey] = weakSender
+ except:
+ pass
+
+ receiverID = id(receiver)
+ # get current set, remove any current references to
+ # this receiver in the set, including back-references
+ if signal in signals:
+ receivers = signals[signal]
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
+ else:
+ receivers = signals[signal] = []
+ try:
+ current = sendersBack.get(receiverID)
+ if current is None:
+ sendersBack[receiverID] = current = []
+ if senderkey not in current:
+ current.append(senderkey)
+ except:
+ pass
+
+ receivers.append(receiver)
+
+
def disconnect(receiver, signal=Any, sender=Any, weak=True):
- """Disconnect receiver from sender for signal
+ """Disconnect receiver from sender for signal
- receiver -- the registered receiver to disconnect
- signal -- the registered signal to disconnect
- sender -- the registered sender to disconnect
- weak -- the weakref state to disconnect
+ receiver -- the registered receiver to disconnect
+ signal -- the registered signal to disconnect
+ sender -- the registered sender to disconnect
+ weak -- the weakref state to disconnect
- disconnect reverses the process of connect,
- the semantics for the individual elements are
- logically equivalent to a tuple of
- (receiver, signal, sender, weak) used as a key
- to be deleted from the internal routing tables.
- (The actual process is slightly more complex
- but the semantics are basically the same).
+ disconnect reverses the process of connect,
+ the semantics for the individual elements are
+ logically equivalent to a tuple of
+ (receiver, signal, sender, weak) used as a key
+ to be deleted from the internal routing tables.
+ (The actual process is slightly more complex
+ but the semantics are basically the same).
- Note:
- Using disconnect is not required to cleanup
- routing when an object is deleted, the framework
- will remove routes for deleted objects
- automatically. It's only necessary to disconnect
- if you want to stop routing to a live object.
-
- returns None, may raise DispatcherTypeError or
- DispatcherKeyError
- """
- if signal is None:
- raise errors.DispatcherTypeError(
- 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
- )
- if weak: receiver = saferef.safeRef(receiver)
- senderkey = id(sender)
- try:
- signals = connections[senderkey]
- receivers = signals[signal]
- except KeyError:
- raise errors.DispatcherKeyError(
- """No receivers found for signal %r from sender %r""" %(
- signal,
- sender
- )
- )
- try:
- # also removes from receivers
- _removeOldBackRefs(senderkey, signal, receiver, receivers)
- except ValueError:
- raise errors.DispatcherKeyError(
- """No connection to receiver %s for signal %s from sender %s""" %(
- receiver,
- signal,
- sender
- )
- )
- _cleanupConnections(senderkey, signal)
+ Note:
+ Using disconnect is not required to cleanup
+ routing when an object is deleted, the framework
+ will remove routes for deleted objects
+ automatically. It's only necessary to disconnect
+ if you want to stop routing to a live object.
-def getReceivers( sender = Any, signal = Any ):
- """Get list of receivers from global tables
+ returns None, may raise DispatcherTypeError or
+ DispatcherKeyError
+ """
+ if signal is None:
+ raise errors.DispatcherTypeError(
+ 'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
+ sender)
+ )
+ if weak: receiver = saferef.safeRef(receiver)
+ senderkey = id(sender)
+ try:
+ signals = connections[senderkey]
+ receivers = signals[signal]
+ except KeyError:
+ raise errors.DispatcherKeyError(
+ """No receivers found for signal %r from sender %r""" % (
+ signal,
+ sender
+ )
+ )
+ try:
+ # also removes from receivers
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
+ except ValueError:
+ raise errors.DispatcherKeyError(
+ """No connection to receiver %s for signal %s from sender %s""" % (
+ receiver,
+ signal,
+ sender
+ )
+ )
+ _cleanupConnections(senderkey, signal)
- This utility function allows you to retrieve the
- raw list of receivers from the connections table
- for the given sender and signal pair.
- Note:
- there is no guarantee that this is the actual list
- stored in the connections table, so the value
- should be treated as a simple iterable/truth value
- rather than, for instance a list to which you
- might append new records.
+def getReceivers(sender=Any, signal=Any):
+ """Get list of receivers from global tables
- Normally you would use liveReceivers( getReceivers( ...))
- to retrieve the actual receiver objects as an iterable
- object.
- """
- try:
- return connections[id(sender)][signal]
- except KeyError:
- return []
+ This utility function allows you to retrieve the
+ raw list of receivers from the connections table
+ for the given sender and signal pair.
+ Note:
+ there is no guarantee that this is the actual list
+ stored in the connections table, so the value
+ should be treated as a simple iterable/truth value
+ rather than, for instance a list to which you
+ might append new records.
+
+ Normally you would use liveReceivers(getReceivers(...))
+ to retrieve the actual receiver objects as an iterable
+ object.
+ """
+ try:
+ return connections[id(sender)][signal]
+ except KeyError:
+ return []
+
+
def liveReceivers(receivers):
- """Filter sequence of receivers to get resolved, live receivers
+ """Filter sequence of receivers to get resolved, live receivers
- This is a generator which will iterate over
- the passed sequence, checking for weak references
- and resolving them, then returning all live
- receivers.
- """
- for receiver in receivers:
- if isinstance( receiver, WEAKREF_TYPES):
- # Dereference the weak reference.
- receiver = receiver()
- if receiver is not None:
- yield receiver
- else:
- yield receiver
+ This is a generator which will iterate over
+ the passed sequence, checking for weak references
+ and resolving them, then returning all live
+ receivers.
+ """
+ for receiver in receivers:
+ if isinstance(receiver, WEAKREF_TYPES):
+ # Dereference the weak reference.
+ receiver = receiver()
+ if receiver is not None:
+ yield receiver
+ else:
+ yield receiver
+def getAllReceivers(sender=Any, signal=Any):
+ """Get list of all receivers from global tables
-def getAllReceivers( sender = Any, signal = Any ):
- """Get list of all receivers from global tables
+ This gets all receivers which should receive
+ the given signal from sender, each receiver should
+ be produced only once by the resulting generator
+ """
+ receivers = {}
+ for set in (
+ # Get receivers that receive *this* signal from *this* sender.
+ getReceivers(sender, signal),
+ # Add receivers that receive *any* signal from *this* sender.
+ getReceivers(sender, Any),
+ # Add receivers that receive *this* signal from *any* sender.
+ getReceivers(Any, signal),
+ # Add receivers that receive *any* signal from *any* sender.
+ getReceivers(Any, Any),
+ ):
+ for receiver in set:
+ if receiver: # filter out dead instance-method weakrefs
+ try:
+ if receiver not in receivers:
+ receivers[receiver] = 1
+ yield receiver
+ except TypeError:
+ # dead weakrefs raise TypeError on hash...
+ pass
- This gets all receivers which should receive
- the given signal from sender, each receiver should
- be produced only once by the resulting generator
- """
- receivers = {}
- for set in (
- # Get receivers that receive *this* signal from *this* sender.
- getReceivers( sender, signal ),
- # Add receivers that receive *any* signal from *this* sender.
- getReceivers( sender, Any ),
- # Add receivers that receive *this* signal from *any* sender.
- getReceivers( Any, signal ),
- # Add receivers that receive *any* signal from *any* sender.
- getReceivers( Any, Any ),
- ):
- for receiver in set:
- if receiver: # filter out dead instance-method weakrefs
- try:
- if receiver not in receivers:
- receivers[receiver] = 1
- yield receiver
- except TypeError:
- # dead weakrefs raise TypeError on hash...
- pass
def send(signal=Any, sender=Anonymous, *arguments, **named):
- """Send signal from sender to all connected receivers.
-
- signal -- (hashable) signal value, see connect for details
+ """Send signal from sender to all connected receivers.
- sender -- the sender of the signal
-
- if Any, only receivers registered for Any will receive
- the message.
+ signal -- (hashable) signal value, see connect for details
- if Anonymous, only receivers registered to receive
- messages from Anonymous or Any will receive the message
+ sender -- the sender of the signal
- Otherwise can be any python object (normally one
- registered with a connect if you actually want
- something to occur).
+ if Any, only receivers registered for Any will receive
+ the message.
- arguments -- positional arguments which will be passed to
- *all* receivers. Note that this may raise TypeErrors
- if the receivers do not allow the particular arguments.
- Note also that arguments are applied before named
- arguments, so they should be used with care.
+ if Anonymous, only receivers registered to receive
+ messages from Anonymous or Any will receive the message
- named -- named arguments which will be filtered according
- to the parameters of the receivers to only provide those
- acceptable to the receiver.
+ Otherwise can be any python object (normally one
+ registered with a connect if you actually want
+ something to occur).
- Return a list of tuple pairs [(receiver, response), ... ]
+ arguments -- positional arguments which will be passed to
+ *all* receivers. Note that this may raise TypeErrors
+ if the receivers do not allow the particular arguments.
+ Note also that arguments are applied before named
+ arguments, so they should be used with care.
- if any receiver raises an error, the error propagates back
- through send, terminating the dispatch loop, so it is quite
- possible to not have all receivers called if a raises an
- error.
- """
- # Call each receiver with whatever arguments it can accept.
- # Return a list of tuple pairs [(receiver, response), ... ].
- responses = []
- for receiver in liveReceivers(getAllReceivers(sender, signal)):
- response = robustapply.robustApply(
- receiver,
- signal=signal,
- sender=sender,
- *arguments,
- **named
- )
- responses.append((receiver, response))
- return responses
-def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
- """Send signal only to those receivers registered for exact message
+ named -- named arguments which will be filtered according
+ to the parameters of the receivers to only provide those
+ acceptable to the receiver.
- sendExact allows for avoiding Any/Anonymous registered
- handlers, sending only to those receivers explicitly
- registered for a particular signal on a particular
- sender.
- """
- responses = []
- for receiver in liveReceivers(getReceivers(sender, signal)):
- response = robustapply.robustApply(
- receiver,
- signal=signal,
- sender=sender,
- *arguments,
- **named
- )
- responses.append((receiver, response))
- return responses
-
+ Return a list of tuple pairs [(receiver, response), ... ]
+ if any receiver raises an error, the error propagates back
+ through send, terminating the dispatch loop, so it is quite
+ possible to not have all receivers called if a raises an
+ error.
+ """
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ responses = []
+ for receiver in liveReceivers(getAllReceivers(sender, signal)):
+ response = robustapply.robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ responses.append((receiver, response))
+ return responses
+
+
+def sendExact(signal=Any, sender=Anonymous, *arguments, **named):
+ """Send signal only to those receivers registered for exact message
+
+ sendExact allows for avoiding Any/Anonymous registered
+ handlers, sending only to those receivers explicitly
+ registered for a particular signal on a particular
+ sender.
+ """
+ responses = []
+ for receiver in liveReceivers(getReceivers(sender, signal)):
+ response = robustapply.robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ responses.append((receiver, response))
+ return responses
+
+
def _removeReceiver(receiver):
- """Remove receiver from connections."""
- if not sendersBack or not connections:
- # During module cleanup the objects will be replaced with None
+ """Remove receiver from connections."""
+ if not sendersBack or not connections:
+ # During module cleanup the objects will be replaced with None
# The order of replacing many chnage, so both variables need
# to be checked.
- return False
- backKey = id(receiver)
- try:
- backSet = sendersBack.pop(backKey)
- except KeyError:
- return False
- else:
- for senderkey in backSet:
- try:
- signals = connections[senderkey].keys()
- except KeyError:
- pass
- else:
- for signal in signals:
- try:
- receivers = connections[senderkey][signal]
- except KeyError:
- pass
- else:
- try:
- receivers.remove( receiver )
- except Exception:
- pass
- _cleanupConnections(senderkey, signal)
+ return False
+ backKey = id(receiver)
+ try:
+ backSet = sendersBack.pop(backKey)
+ except KeyError:
+ return False
+ else:
+ for senderkey in backSet:
+ try:
+ signals = connections[senderkey].keys()
+ except KeyError:
+ pass
+ else:
+ for signal in signals:
+ try:
+ receivers = connections[senderkey][signal]
+ except KeyError:
+ pass
+ else:
+ try:
+ receivers.remove(receiver)
+ except Exception:
+ pass
+ _cleanupConnections(senderkey, signal)
+
def _cleanupConnections(senderkey, signal):
- """Delete any empty signals for senderkey. Delete senderkey if empty."""
- try:
- receivers = connections[senderkey][signal]
- except:
- pass
- else:
- if not receivers:
- # No more connected receivers. Therefore, remove the signal.
- try:
- signals = connections[senderkey]
- except KeyError:
- pass
- else:
- del signals[signal]
- if not signals:
- # No more signal connections. Therefore, remove the sender.
- _removeSender(senderkey)
+ """Delete any empty signals for senderkey. Delete senderkey if empty."""
+ try:
+ receivers = connections[senderkey][signal]
+ except:
+ pass
+ else:
+ if not receivers:
+ # No more connected receivers. Therefore, remove the signal.
+ try:
+ signals = connections[senderkey]
+ except KeyError:
+ pass
+ else:
+ del signals[signal]
+ if not signals:
+ # No more signal connections. Therefore, remove the sender.
+ _removeSender(senderkey)
+
def _removeSender(senderkey):
- """Remove senderkey from connections."""
- _removeBackrefs(senderkey)
- try:
- del connections[senderkey]
- except KeyError:
- pass
- # Senderkey will only be in senders dictionary if sender
- # could be weakly referenced.
- try:
- del senders[senderkey]
- except:
- pass
+ """Remove senderkey from connections."""
+ _removeBackrefs(senderkey)
+ try:
+ del connections[senderkey]
+ except KeyError:
+ pass
+ # Senderkey will only be in senders dictionary if sender
+ # could be weakly referenced.
+ try:
+ del senders[senderkey]
+ except:
+ pass
-def _removeBackrefs( senderkey):
- """Remove all back-references to this senderkey"""
- try:
- signals = connections[senderkey]
- except KeyError:
- signals = None
- else:
- items = signals.items()
- def allReceivers( ):
- for signal,set in items:
- for item in set:
- yield item
- for receiver in allReceivers():
- _killBackref( receiver, senderkey )
+def _removeBackrefs(senderkey):
+ """Remove all back-references to this senderkey"""
+ try:
+ signals = connections[senderkey]
+ except KeyError:
+ signals = None
+ else:
+ items = signals.items()
+ def allReceivers():
+ for signal, set in items:
+ for item in set:
+ yield item
+ for receiver in allReceivers():
+ _killBackref(receiver, senderkey)
+
+
def _removeOldBackRefs(senderkey, signal, receiver, receivers):
- """Kill old sendersBack references from receiver
+ """Kill old sendersBack references from receiver
- This guards against multiple registration of the same
- receiver for a given signal and sender leaking memory
- as old back reference records build up.
+ This guards against multiple registration of the same
+ receiver for a given signal and sender leaking memory
+ as old back reference records build up.
- Also removes old receiver instance from receivers
- """
- try:
- index = receivers.index(receiver)
- # need to scan back references here and remove senderkey
- except ValueError:
- return False
- else:
- oldReceiver = receivers[index]
- del receivers[index]
- found = 0
- signals = connections.get(signal)
- if signals is not None:
- for sig,recs in connections.get(signal,{}).iteritems():
- if sig != signal:
- for rec in recs:
- if rec is oldReceiver:
- found = 1
- break
- if not found:
- _killBackref( oldReceiver, senderkey )
- return True
- return False
-
-
-def _killBackref( receiver, senderkey ):
- """Do the actual removal of back reference from receiver to senderkey"""
- receiverkey = id(receiver)
- set = sendersBack.get( receiverkey, () )
- while senderkey in set:
- try:
- set.remove( senderkey )
- except:
- break
- if not set:
- try:
- del sendersBack[ receiverkey ]
- except KeyError:
- pass
- return True
+ Also removes old receiver instance from receivers
+ """
+ try:
+ index = receivers.index(receiver)
+ # need to scan back references here and remove senderkey
+ except ValueError:
+ return False
+ else:
+ oldReceiver = receivers[index]
+ del receivers[index]
+ found = 0
+ signals = connections.get(signal)
+ if signals is not None:
+ for sig, recs in connections.get(signal, {}).iteritems():
+ if sig != signal:
+ for rec in recs:
+ if rec is oldReceiver:
+ found = 1
+ break
+ if not found:
+ _killBackref(oldReceiver, senderkey)
+ return True
+ return False
+
+
+def _killBackref(receiver, senderkey):
+ """Do the actual removal of back reference from receiver to senderkey"""
+ receiverkey = id(receiver)
+ set = sendersBack.get(receiverkey, ())
+ while senderkey in set:
+ try:
+ set.remove(senderkey)
+ except:
+ break
+ if not set:
+ try:
+ del sendersBack[receiverkey]
+ except KeyError:
+ pass
+ return True
Modified: grass/trunk/lib/python/pydispatch/errors.py
===================================================================
--- grass/trunk/lib/python/pydispatch/errors.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/errors.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -1,10 +1,14 @@
"""Error types for dispatcher mechanism
"""
+
class DispatcherError(Exception):
- """Base class for all Dispatcher errors"""
+ """Base class for all Dispatcher errors"""
+
+
class DispatcherKeyError(KeyError, DispatcherError):
- """Error raised when unknown (sender,signal) set specified"""
+ """Error raised when unknown (sender,signal) set specified"""
+
+
class DispatcherTypeError(TypeError, DispatcherError):
- """Error raised when inappropriate signal-type specified (None)"""
-
+ """Error raised when inappropriate signal-type specified (None)"""
Modified: grass/trunk/lib/python/pydispatch/robust.py
===================================================================
--- grass/trunk/lib/python/pydispatch/robust.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/robust.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -2,56 +2,57 @@
from grass.pydispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
from grass.pydispatch.robustapply import robustApply
+
def sendRobust(
- signal=Any,
- sender=Anonymous,
- *arguments, **named
+ signal=Any,
+ sender=Anonymous,
+ *arguments, **named
):
- """Send signal from sender to all connected receivers catching errors
-
- signal -- (hashable) signal value, see connect for details
+ """Send signal from sender to all connected receivers catching errors
- sender -- the sender of the signal
-
- if Any, only receivers registered for Any will receive
- the message.
+ signal -- (hashable) signal value, see connect for details
- if Anonymous, only receivers registered to receive
- messages from Anonymous or Any will receive the message
+ sender -- the sender of the signal
- Otherwise can be any python object (normally one
- registered with a connect if you actually want
- something to occur).
+ if Any, only receivers registered for Any will receive
+ the message.
- arguments -- positional arguments which will be passed to
- *all* receivers. Note that this may raise TypeErrors
- if the receivers do not allow the particular arguments.
- Note also that arguments are applied before named
- arguments, so they should be used with care.
+ if Anonymous, only receivers registered to receive
+ messages from Anonymous or Any will receive the message
- named -- named arguments which will be filtered according
- to the parameters of the receivers to only provide those
- acceptable to the receiver.
+ Otherwise can be any python object (normally one
+ registered with a connect if you actually want
+ something to occur).
- Return a list of tuple pairs [(receiver, response), ... ]
+ arguments -- positional arguments which will be passed to
+ *all* receivers. Note that this may raise TypeErrors
+ if the receivers do not allow the particular arguments.
+ Note also that arguments are applied before named
+ arguments, so they should be used with care.
- if any receiver raises an error (specifically any subclass of Exception),
- the error instance is returned as the result for that receiver.
- """
- # Call each receiver with whatever arguments it can accept.
- # Return a list of tuple pairs [(receiver, response), ... ].
- responses = []
- for receiver in liveReceivers(getAllReceivers(sender, signal)):
- try:
- response = robustApply(
- receiver,
- signal=signal,
- sender=sender,
- *arguments,
- **named
- )
- except Exception as err:
- responses.append((receiver, err))
- else:
- responses.append((receiver, response))
- return responses
+ named -- named arguments which will be filtered according
+ to the parameters of the receivers to only provide those
+ acceptable to the receiver.
+
+ Return a list of tuple pairs [(receiver, response), ... ]
+
+ if any receiver raises an error (specifically any subclass of Exception),
+ the error instance is returned as the result for that receiver.
+ """
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ responses = []
+ for receiver in liveReceivers(getAllReceivers(sender, signal)):
+ try:
+ response = robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ except Exception as err:
+ responses.append((receiver, err))
+ else:
+ responses.append((receiver, response))
+ return responses
Modified: grass/trunk/lib/python/pydispatch/robustapply.py
===================================================================
--- grass/trunk/lib/python/pydispatch/robustapply.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/robustapply.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -17,41 +17,44 @@
im_code = 'im_code'
func_code = 'func_code'
-def function( receiver ):
- """Get function-like callable object for given receiver
- returns (function_or_method, codeObject, fromMethod)
+def function(receiver):
+ """Get function-like callable object for given receiver
- If fromMethod is true, then the callable already
- has its first argument bound
- """
- if hasattr(receiver, '__call__'):
- # Reassign receiver to the actual method that will be called.
- if hasattr( receiver.__call__, im_func) or hasattr( receiver.__call__, im_code):
- receiver = receiver.__call__
- if hasattr( receiver, im_func ):
- # an instance-method...
- return receiver, getattr(getattr(receiver, im_func), func_code), 1
- elif not hasattr( receiver, func_code):
- raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
- return receiver, getattr(receiver,func_code), 0
+ returns (function_or_method, codeObject, fromMethod)
+ If fromMethod is true, then the callable already
+ has its first argument bound
+ """
+ if hasattr(receiver, '__call__'):
+ # Reassign receiver to the actual method that will be called.
+ if hasattr(receiver.__call__, im_func) or hasattr(receiver.__call__,
+ im_code):
+ receiver = receiver.__call__
+ if hasattr(receiver, im_func):
+ # an instance-method...
+ return receiver, getattr(getattr(receiver, im_func), func_code), 1
+ elif not hasattr(receiver, func_code):
+ raise ValueError('unknown reciever type %s %s' % (receiver,
+ type(receiver)))
+ return receiver, getattr(receiver, func_code), 0
+
+
def robustApply(receiver, *arguments, **named):
- """Call receiver with arguments and an appropriate subset of named
- """
- receiver, codeObject, startIndex = function( receiver )
- acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
- for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
- if name in named:
- raise TypeError(
- """Argument %r specified both positionally and as a keyword for calling %r"""% (
- name, receiver,
- )
- )
- if not (codeObject.co_flags & 8):
- # fc does not have a **kwds type parameter, therefore
- # remove unacceptable arguments.
- for arg in named.keys():
- if arg not in acceptable:
- del named[arg]
- return receiver(*arguments, **named)
+ """Call receiver with arguments and an appropriate subset of named
+ """
+ receiver, codeObject, startIndex = function(receiver)
+ acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
+ for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
+ if name in named:
+ raise TypeError(
+ """Argument %r specified both positionally and as a keyword"""
+ """ for calling %r""" % (name, receiver)
+ )
+ if not (codeObject.co_flags & 8):
+ # fc does not have a **kwds type parameter, therefore
+ # remove unacceptable arguments.
+ for arg in named.keys():
+ if arg not in acceptable:
+ del named[arg]
+ return receiver(*arguments, **named)
Modified: grass/trunk/lib/python/pydispatch/saferef.py
===================================================================
--- grass/trunk/lib/python/pydispatch/saferef.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/saferef.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -7,165 +7,178 @@
else:
im_func = 'im_func'
im_self = 'im_self'
-def safeRef(target, onDelete = None):
- """Return a *safe* weak reference to a callable target
- target -- the object to be weakly referenced, if it's a
- bound method reference, will create a BoundMethodWeakref,
- otherwise creates a simple weakref.
- onDelete -- if provided, will have a hard reference stored
- to the callable to be called after the safe reference
- goes out of scope with the reference object, (either a
- weakref or a BoundMethodWeakref) as argument.
- """
- if hasattr(target, im_self):
- if getattr(target, im_self) is not None:
- # Turn a bound method into a BoundMethodWeakref instance.
- # Keep track of these instances for lookup by disconnect().
- assert hasattr(target, im_func), """safeRef target %r has %s, but no %s, don't know how to create reference"""%( target,im_self,im_func)
- reference = BoundMethodWeakref(
- target=target,
- onDelete=onDelete
- )
- return reference
- if onDelete is not None:
- return weakref.ref(target, onDelete)
- else:
- return weakref.ref( target )
+def safeRef(target, onDelete=None):
+ """Return a *safe* weak reference to a callable target
+
+ target -- the object to be weakly referenced, if it's a
+ bound method reference, will create a BoundMethodWeakref,
+ otherwise creates a simple weakref.
+ onDelete -- if provided, will have a hard reference stored
+ to the callable to be called after the safe reference
+ goes out of scope with the reference object, (either a
+ weakref or a BoundMethodWeakref) as argument.
+ """
+ if hasattr(target, im_self):
+ if getattr(target, im_self) is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ assert hasattr(target, im_func), """safeRef target %r has %s, """ \
+ """but no %s, don't know how """ \
+ """to create reference""" % (target,
+ im_self,
+ im_func)
+ reference = BoundMethodWeakref(
+ target=target,
+ onDelete=onDelete
+ )
+ return reference
+ if onDelete is not None:
+ return weakref.ref(target, onDelete)
+ else:
+ return weakref.ref(target)
+
+
class BoundMethodWeakref(object):
- """'Safe' and reusable weak references to instance methods
+ """'Safe' and reusable weak references to instance methods
- BoundMethodWeakref objects provide a mechanism for
- referencing a bound method without requiring that the
- method object itself (which is normally a transient
- object) is kept alive. Instead, the BoundMethodWeakref
- object keeps weak references to both the object and the
- function which together define the instance method.
+ BoundMethodWeakref objects provide a mechanism for
+ referencing a bound method without requiring that the
+ method object itself (which is normally a transient
+ object) is kept alive. Instead, the BoundMethodWeakref
+ object keeps weak references to both the object and the
+ function which together define the instance method.
- Attributes:
- key -- the identity key for the reference, calculated
- by the class's calculateKey method applied to the
- target instance method
- deletionMethods -- sequence of callable objects taking
- single argument, a reference to this object which
- will be called when *either* the target object or
- target function is garbage collected (i.e. when
- this object becomes invalid). These are specified
- as the onDelete parameters of safeRef calls.
- weakSelf -- weak reference to the target object
- weakFunc -- weak reference to the target function
+ Attributes:
+ key -- the identity key for the reference, calculated
+ by the class's calculateKey method applied to the
+ target instance method
+ deletionMethods -- sequence of callable objects taking
+ single argument, a reference to this object which
+ will be called when *either* the target object or
+ target function is garbage collected (i.e. when
+ this object becomes invalid). These are specified
+ as the onDelete parameters of safeRef calls.
+ weakSelf -- weak reference to the target object
+ weakFunc -- weak reference to the target function
- Class Attributes:
- _allInstances -- class attribute pointing to all live
- BoundMethodWeakref objects indexed by the class's
- calculateKey(target) method applied to the target
- objects. This weak value dictionary is used to
- short-circuit creation so that multiple references
- to the same (object, function) pair produce the
- same BoundMethodWeakref instance.
+ Class Attributes:
+ _allInstances -- class attribute pointing to all live
+ BoundMethodWeakref objects indexed by the class's
+ calculateKey(target) method applied to the target
+ objects. This weak value dictionary is used to
+ short-circuit creation so that multiple references
+ to the same (object, function) pair produce the
+ same BoundMethodWeakref instance.
- """
- _allInstances = weakref.WeakValueDictionary()
- def __new__( cls, target, onDelete=None, *arguments,**named ):
- """Create new instance or return current instance
+ """
+ _allInstances = weakref.WeakValueDictionary()
- Basically this method of construction allows us to
- short-circuit creation of references to already-
- referenced instance methods. The key corresponding
- to the target is calculated, and if there is already
- an existing reference, that is returned, with its
- deletionMethods attribute updated. Otherwise the
- new instance is created and registered in the table
- of already-referenced methods.
- """
- key = cls.calculateKey(target)
- current =cls._allInstances.get(key)
- if current is not None:
- current.deletionMethods.append( onDelete)
- return current
- else:
- base = super( BoundMethodWeakref, cls).__new__( cls )
- cls._allInstances[key] = base
- base.__init__( target, onDelete, *arguments,**named)
- return base
- def __init__(self, target, onDelete=None):
- """Return a weak-reference-like instance for a bound method
+ def __new__(cls, target, onDelete=None, *arguments, **named):
+ """Create new instance or return current instance
- target -- the instance-method target for the weak
- reference, must have <im_self> and <im_func> attributes
- and be reconstructable via:
- target.<im_func>.__get__( target.<im_self> )
- which is true of built-in instance methods.
- onDelete -- optional callback which will be called
- when this weak reference ceases to be valid
- (i.e. either the object or the function is garbage
- collected). Should take a single argument,
- which will be passed a pointer to this object.
- """
- def remove(weak, self=self):
- """Set self.isDead to true when method or instance is destroyed"""
- methods = self.deletionMethods[:]
- del self.deletionMethods[:]
- try:
- del self.__class__._allInstances[ self.key ]
- except KeyError:
- pass
- for function in methods:
- try:
- if hasattr(function, '__call__' ):
- function( self )
- except Exception as e:
- try:
- traceback.print_exc()
- except AttributeError:
- print '''Exception during saferef %s cleanup function %s: %s'''%(
- self, function, e
- )
- self.deletionMethods = [onDelete]
- self.key = self.calculateKey( target )
- self.weakSelf = weakref.ref(getattr(target,im_self), remove)
- self.weakFunc = weakref.ref(getattr(target,im_func), remove)
- self.selfName = getattr(target,im_self).__class__.__name__
- self.funcName = str(getattr(target,im_func).__name__)
- def calculateKey( cls, target ):
- """Calculate the reference key for this reference
+ Basically this method of construction allows us to
+ short-circuit creation of references to already-
+ referenced instance methods. The key corresponding
+ to the target is calculated, and if there is already
+ an existing reference, that is returned, with its
+ deletionMethods attribute updated. Otherwise the
+ new instance is created and registered in the table
+ of already-referenced methods.
+ """
+ key = cls.calculateKey(target)
+ current = cls._allInstances.get(key)
+ if current is not None:
+ current.deletionMethods.append(onDelete)
+ return current
+ else:
+ base = super(BoundMethodWeakref, cls).__new__(cls)
+ cls._allInstances[key] = base
+ base.__init__(target, onDelete, *arguments, **named)
+ return base
- Currently this is a two-tuple of the id()'s of the
- target object and the target function respectively.
- """
- return (id(getattr(target,im_self)),id(getattr(target,im_func)))
- calculateKey = classmethod( calculateKey )
- def __str__(self):
- """Give a friendly representation of the object"""
- return """%s( %s.%s )"""%(
- self.__class__.__name__,
- self.selfName,
- self.funcName,
- )
- __repr__ = __str__
- def __nonzero__( self ):
- """Whether we are still a valid reference"""
- return self() is not None
- def __cmp__( self, other ):
- """Compare with another reference"""
- if not isinstance (other,self.__class__):
- return cmp( self.__class__, type(other) )
- return cmp( self.key, other.key)
- def __call__(self):
- """Return a strong reference to the bound method
+ def __init__(self, target, onDelete=None):
+ """Return a weak-reference-like instance for a bound method
- If the target cannot be retrieved, then will
- return None, otherwise returns a bound instance
- method for our object and function.
+ target -- the instance-method target for the weak
+ reference, must have <im_self> and <im_func> attributes
+ and be reconstructable via:
+ target.<im_func>.__get__( target.<im_self> )
+ which is true of built-in instance methods.
+ onDelete -- optional callback which will be called
+ when this weak reference ceases to be valid
+ (i.e. either the object or the function is garbage
+ collected). Should take a single argument,
+ which will be passed a pointer to this object.
+ """
+ def remove(weak, self=self):
+ """Set self.isDead to true when method or instance is destroyed"""
+ methods = self.deletionMethods[:]
+ del self.deletionMethods[:]
+ try:
+ del self.__class__._allInstances[self.key]
+ except KeyError:
+ pass
+ for function in methods:
+ try:
+ if hasattr(function, '__call__'):
+ function(self)
+ except Exception as e:
+ try:
+ traceback.print_exc()
+ except AttributeError:
+ print '''Exception during saferef %s cleanup ''' \
+ '''function %s: %s''' % (self, function, e)
+ self.deletionMethods = [onDelete]
+ self.key = self.calculateKey(target)
+ self.weakSelf = weakref.ref(getattr(target, im_self), remove)
+ self.weakFunc = weakref.ref(getattr(target, im_func), remove)
+ self.selfName = getattr(target, im_self).__class__.__name__
+ self.funcName = str(getattr(target, im_func).__name__)
- Note:
- You may call this method any number of times,
- as it does not invalidate the reference.
- """
- target = self.weakSelf()
- if target is not None:
- function = self.weakFunc()
- if function is not None:
- return function.__get__(target)
- return None
+ def calculateKey(cls, target):
+ """Calculate the reference key for this reference
+
+ Currently this is a two-tuple of the id()'s of the
+ target object and the target function respectively.
+ """
+ return (id(getattr(target, im_self)), id(getattr(target, im_func)))
+ calculateKey = classmethod(calculateKey)
+
+ def __str__(self):
+ """Give a friendly representation of the object"""
+ return """%s( %s.%s )""" % (
+ self.__class__.__name__,
+ self.selfName,
+ self.funcName,
+ )
+ __repr__ = __str__
+
+ def __nonzero__(self):
+ """Whether we are still a valid reference"""
+ return self() is not None
+
+ def __cmp__(self, other):
+ """Compare with another reference"""
+ if not isinstance(other, self.__class__):
+ return cmp(self.__class__, type(other))
+ return cmp(self.key, other.key)
+
+ def __call__(self):
+ """Return a strong reference to the bound method
+
+ If the target cannot be retrieved, then will
+ return None, otherwise returns a bound instance
+ method for our object and function.
+
+ Note:
+ You may call this method any number of times,
+ as it does not invalidate the reference.
+ """
+ target = self.weakSelf()
+ if target is not None:
+ function = self.weakFunc()
+ if function is not None:
+ return function.__get__(target)
+ return None
Modified: grass/trunk/lib/python/pydispatch/signal.py
===================================================================
--- grass/trunk/lib/python/pydispatch/signal.py 2014-08-27 19:48:57 UTC (rev 61762)
+++ grass/trunk/lib/python/pydispatch/signal.py 2014-08-27 21:16:03 UTC (rev 61763)
@@ -15,7 +15,7 @@
Should work on the most of Python implementations where name of lambda
function is not unique.
-
+
>>> mylambda = lambda x: x*x
>>> _islambda(mylambda)
True
@@ -173,7 +173,7 @@
DispatcherKeyError: 'No receivers found for signal <__main__.Signal object at 0x...> from sender _Any'
Disconnecting the non-exiting or unknown handler will result in error.
-
+
>>> signal1.disconnect(some_function)
Traceback (most recent call last):
NameError: name 'some_function' is not defined
More information about the grass-commit
mailing list