1. bjoern
  2. whiteboard-cam

Commits

bjoern  committed 0fb8fa5

Initial import of whiteboard-cam

  • Participants
  • Branches default

Comments (0)

Files changed (13)

File .hgignore

View file
+uploadr.history.db
+images
+.DS_Store
+.flickrToken
+.*.pyc
+secret.sh

File README.txt

View file
+
+1) plug in camera, power up
+2) run ./config.sh once (needs to be run once after each plug-in of camera before capturing)
+3) run ./capture.sh to capture and upload one new image
+
+Script written for Canon SX100IS
+Relies on macports:
+* gphoto2 @2.4.5_0  (sudo port install gphoto2), 
+* ImageMagick @6.6.1-5_0+no_x11+perl+q16 
+
+And http://github.com/ept/uploadr.py

File capture.sh

View file
+#!/bin/bash
+# Take a picture of the whiteboard using USB-attached Canon camera
+# correct distortion and upload to flickr
+# assumes that we've already configured camera after plugging in w/ config.sh
+
+echo "(1) Capture Image"
+gphoto2 --capture-image-and-download \
+        --filename raw.jpg \
+        --force-overwrite
+
+dat=`date +%Y-%m-%d_%H-%M-%S`
+echo "(2) Correct barrel, perspective, contrast with ImageMagick"
+echo "    Write to images/whiteboard-${dat}.jpg"
+# corner coordinates starting top-left CCW, for 2048x1536 image size
+x1=29
+y1=334
+x2=32
+y2=1315
+x3=1986
+y3=1282
+x4=1976
+y4=337
+convert raw.jpg -matte -virtual-pixel black -distort Barrel "0.0 -0.01 0.0 1.01" -distort Perspective "$x1,$y1 0,0  $x2,$y2 0,1000   $x3,$y3 2000,1000   $x4,$y4 2000,0" -crop 2000x1000+0+0 -contrast-stretch 2%x80% images/whiteboard-${dat}.jpg
+
+echo "(3) Delete raw file now that we're done with it"
+rm -f raw.jpg
+
+echo "(4) Upload to Flickr"
+#Now upload using github.com/ept/uploadr.py which looks for new files in /images subdirectory
+#api keys have to be set up in config.sh
+ept-uploadr/uploadr/uploadr.py

File config-info.txt

View file
+# Configuration setting tree obtained with gphoto2 --list-config
+# Individual setting options obtained with gphoto2 --getconfig=/path/to/item
+/main/settings/owner
+/main/settings/model
+/main/settings/firmwareversion
+/main/settings/time
+/main/settings/synctime
+/main/settings/output
+/main/settings/orientation
+/main/settings/capturetarget
+/main/settings/capture
+
+/main/imgsettings/canonimgquality
+Label: Canon Image Quality                                                     
+Type: RADIO
+Current: superfine
+Choice: 0 superfine
+Choice: 1 fine
+Choice: 2 normal
+
+/main/imgsettings/canonimgformat
+NULL
+
+/main/imgsettings/canonimgsize
+Label: Canon Image Size                                                        
+Type: RADIO
+Current: large
+Choice: 0 large
+Choice: 1 medium 1
+Choice: 2 medium 2 #2048x1536
+Choice: 3 medium 3 #1600x1200
+Choice: 4 small    
+
+
+/main/imgsettings/iso
+Label: ISO Speed                                                               
+Type: RADIO
+Current: 80
+Choice: 0 Auto
+Choice: 1 80
+Choice: 2 100
+Choice: 3 200
+Choice: 4 400
+Choice: 5 800
+Choice: 6 1600
+
+/main/imgsettings/whitebalance
+Label: WhiteBalance                                                            
+Type: RADIO
+Current: Auto
+Choice: 0 Auto
+Choice: 1 Daylight
+Choice: 2 Cloudy
+Choice: 3 Tungsten
+Choice: 4 Fluorescent
+Choice: 5 Fluorescent H
+Choice: 6 Custom
+
+/main/capturesettings/zoom
+Label: Zoom                                                                    
+Type: RANGE
+Current: 0
+Bottom: 0
+Top: 28
+Step: 1
+
+/main/capturesettings/assistlight
+/main/capturesettings/autorotation
+
+/main/capturesettings/exposurecompensation
+Label: Exposure Compensation                                                   
+Type: RADIO
+Current: 0
+Choice: 0 +2
+Choice: 1 +1 2/3
+Choice: 2 +1 1/3
+Choice: 3 +1
+Choice: 4 +2/3
+Choice: 5 +1/3
+Choice: 6 0
+Choice: 7 -1/3
+Choice: 8 -2/3
+Choice: 9 -1
+Choice: 10 -1 1/3
+Choice: 11 -1 2/3
+Choice: 12 -2
+
+
+/main/capturesettings/flashcompensation
+/main/capturesettings/canonflashmode
+/main/capturesettings/shootingmode
+Label: Canon Shooting Mode                                                     
+Type: RADIO
+Current: Auto
+Choice: 0 Auto
+Choice: 1 TV
+Choice: 2 AV
+Choice: 3 Manual
+
+/main/capturesettings/aperture
+Label: Aperture                                                                
+Type: RADIO
+Current: 5.6
+Choice: 0 auto
+Choice: 1 1.0
+Choice: 2 1.1
+Choice: 3 1.2
+Choice: 4 1.2 (1/3)
+Choice: 5 1.4
+Choice: 6 1.6
+Choice: 7 1.8
+Choice: 8 1.8 (1/3)
+Choice: 9 2.0
+Choice: 10 2.2
+Choice: 11 2.5
+Choice: 12 2.5 (1/3)
+Choice: 13 2.8
+Choice: 14 3.2
+Choice: 15 3.5
+Choice: 16 3.5 (1/3)
+Choice: 17 4.0
+Choice: 18 4.5 (1/3)
+Choice: 19 4.5
+Choice: 20 5.6 (1/3)
+Choice: 21 5.6
+Choice: 22 6.3
+Choice: 23 7.1
+Choice: 24 8.0
+Choice: 25 9.0
+Choice: 26 9.5
+Choice: 27 10
+Choice: 28 11
+Choice: 29 13 (1/3)
+Choice: 30 13
+Choice: 31 14
+Choice: 32 16
+Choice: 33 18
+Choice: 34 19
+Choice: 35 20
+Choice: 36 22
+Choice: 37 25
+Choice: 38 27
+Choice: 39 29
+Choice: 40 32
+Choice: 41 36
+Choice: 42 38
+Choice: 43 40
+Choice: 44 45
+Choice: 45 51
+Choice: 46 54
+Choice: 47 57
+Choice: 48 64
+Choice: 49 72
+Choice: 50 76
+Choice: 51 81
+Choice: 52 91
+
+/main/capturesettings/focusingpoint
+/main/capturesettings/shutterspeed
+/main/capturesettings/meteringmode
+Label: Metering Mode                                                           
+Type: RADIO
+Current: evaluative
+Choice: 0 evaluative
+Choice: 1 center-weighted
+Choice: 2 spot
+
+/main/capturesettings/afdistance
+/main/capturesettings/focuslock

File config.sh

View file
+#!/bin/bash
+# Configure camera for gphoto2 access and later capture:
+
+echo "(1) Find process id of Apple PTP and kill it"
+# Apple PTP process blocks USB access to camera by default - kill it
+# pid is first field in whitespace separated line of ps output; 
+#  - sometimes there's a space in front of the pid
+#  - sometimes the grep itself matches
+# the sed line matches: (whitespace)(number)(rest) and prints only (number)
+pid=`ps -e | grep PTP | head -n 1 | sed 's_\([ \t]*\)\([0-9][0-9]*\)\(.*\)_\2_'`
+kill -9 $pid
+
+echo "(2) Switch to capture mode by taking an image and throwing it away"
+gphoto2 --capture-image-and-download \
+        --filename junk.jpg \
+        --force-overwrite
+
+rm -f junk.jpg
+
+echo "(3) Configure camera settings"
+# Assumes camera is in Aperture Priority mode
+# Sets ISO to 80, Exposure comp to +1, Zoom to 7.3mm=2, 2048x1536 image size
+# see config.txt for options
+gphoto2 --set-config /main/imgsettings/iso=1 \
+        --set-config /main/imgsettings/canonimgsize=2 \
+        --set-config /main/capturesettings/exposurecompensation=3 \
+        --set-config /main/capturesettings/zoom=2 
+
+echo "(4) Set up Flickr API Keys"
+# not included in hg repository. this has the form
+# export FLICKR_UPLOADR_PY_API_KEY=...
+# export FLICKR_UPLOADR_PY_SECRET=...
+./secret.sh
+
+echo "Done."

File ept-uploadr/.gitignore

View file
+.loadpath
+.project
+.pydevproject
+.settings
+.DS_Store
+*.pyc

File ept-uploadr/COPYRIGHT

View file
+The original Uploadr.py is (c) Cameron Mallory, September 2005; license: 
+"You may use this code however you see fit in any form whatsoever."
+
+Includes the xmltramp library, (c) Aaron Swartz, 2003; licensed under the GNU GPL 2.
+
+Further changes made to this program are (c) Martin Kleppmann, 2009; to preserve
+licence compatibility, these and all future contributions are licensed under the
+GNU GPL 2.
+
+This program is free software; you can redistribute it and/or modify it under the
+terms Version 2 of the GNU General Public License as published by the Free Software
+Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.

File ept-uploadr/README.rst

View file
+Uploadr.py
+==========
+
+Uploadr.py is a simple Python script for uploading your photos to Flickr. Unlike
+many GUI applications out there, it lends itself to automation; and because it's
+free and open source, you can just change it if you don't like it.
+
+
+Authentication
+--------------
+
+To use this application, you need to obtain your own Flickr API key and secret
+key. You can apply for keys `on the Flickr website
+<http://www.flickr.com/services/api/keys/apply/>`_.
+
+When you have got those keys, you need to set environment variables so that they
+can be used by this application. For example, if you use Bash, add the following
+lines to your ``$HOME/.bash_profile``::
+
+    export FLICKR_UPLOADR_PY_API_KEY=0123456789abcdef0123456789abcdef
+    export FLICKR_UPLOADR_PY_SECRET=0123456789abcdef
+
+
+License
+-------
+
+Uploadr.py consists of code by Cameron Mallory, Martin Kleppmann, Aaron Swartz and
+others. See ``COPYRIGHT`` for details.

File ept-uploadr/uploadr/__init__.py

Empty file added.

File ept-uploadr/uploadr/uploadr.py

View file
+#!/usr/bin/env python
+
+import sys, time, os, urllib2, shelve, string, xmltramp, mimetools, mimetypes, md5, webbrowser
+#
+#   uploadr.py
+#
+#   Upload images placed within a directory to your Flickr account.
+#
+#   Requires:
+#       xmltramp http://www.aaronsw.com/2002/xmltramp/
+#       flickr account http://flickr.com
+#
+#   Inspired by:
+#        http://micampe.it/things/flickruploadr
+#
+#   Usage:
+#
+#   The best way to use this is to just fire this up in the background and forget about it.
+#   If you find you have CPU/Process limits, then setup a cron job.
+#
+#   %nohup python uploadr.py -d &
+#
+#   cron entry (runs at the top of every hour )
+#   0  *  *   *   * /full/path/to/uploadr.py > /dev/null 2>&1
+#
+#   September 2005
+#   Cameron Mallory   cmallory/berserk.org
+#
+#   This code has been updated to use the new Auth API from flickr.
+#
+#   You may use this code however you see fit in any form whatsoever.
+#
+##
+##  Items you will want to change
+## 
+
+#
+# Location to scan for new images
+#   
+IMAGE_DIR = "images/"  
+#
+#   Flickr settings
+#
+FLICKR = {"title": "Whiteboard Capture 629 Soda",
+        "description": "A Whiteboard Image",
+        "tags": "auto-upload",
+        "is_public": "0",
+        "is_friend": "0",
+        "is_family": "0" }
+#
+#   How often to check for new images to upload  (in seconds )
+#
+SLEEP_TIME = 1 * 60
+#
+#   File we keep the history of uploaded images in.
+#
+HISTORY_FILE = "uploadr.history"
+
+##
+##  You shouldn't need to modify anything below here
+##
+FLICKR["api_key" ] = os.environ['FLICKR_UPLOADR_PY_API_KEY']
+FLICKR["secret" ] = os.environ['FLICKR_UPLOADR_PY_SECRET']
+
+class APIConstants:
+    base = "http://flickr.com/services/"
+    rest   = base + "rest/"
+    auth   = base + "auth/"
+    upload = base + "upload/"
+    
+    token = "auth_token"
+    secret = "secret"
+    key = "api_key"
+    sig = "api_sig"
+    frob = "frob"
+    perms = "perms"
+    method = "method"
+    
+    def __init__( self ):
+       pass
+       
+api = APIConstants()
+
+class Uploadr:
+    token = None
+    perms = ""
+    TOKEN_FILE = ".flickrToken"
+    
+    def __init__( self ):
+        self.token = self.getCachedToken()
+
+
+
+    """
+    Signs args via md5 per http://www.flickr.com/services/api/auth.spec.html (Section 8)
+    """
+    def signCall( self, data):
+        keys = data.keys()
+        keys.sort()
+        foo = ""
+        for a in keys:
+            foo += (a + data[a])
+        
+        f = FLICKR[ api.secret ] + api.key + FLICKR[ api.key ] + foo
+        #f = api.key + FLICKR[ api.key ] + foo
+        return md5.new( f ).hexdigest()
+   
+    def urlGen( self , base,data, sig ):
+        foo = base + "?"
+        for d in data: 
+            foo += d + "=" + data[d] + "&"
+        return foo + api.key + "=" + FLICKR[ api.key ] + "&" + api.sig + "=" + sig
+        
+ 
+    #
+    #   Authenticate user so we can upload images
+    #
+    def authenticate( self ):
+        print "Getting new Token"
+        self.getFrob()
+        self.getAuthKey()
+        self.getToken()   
+        self.cacheToken()
+
+    """
+    flickr.auth.getFrob
+    
+    Returns a frob to be used during authentication. This method call must be 
+    signed.
+    
+    This method does not require authentication.
+    Arguments
+    
+    api.key (Required)
+    Your API application key. See here for more details.     
+    """
+    def getFrob( self ):
+        d = { 
+            api.method  : "flickr.auth.getFrob"
+            }
+        sig = self.signCall( d )
+        url = self.urlGen( api.rest, d, sig )
+        try:
+            response = self.getResponse( url )
+            if ( self.isGood( response ) ):
+                FLICKR[ api.frob ] = str(response.frob)
+            else:
+                self.reportError( response )
+        except:
+            print "Error getting frob:" , str( sys.exc_info() )
+
+    """
+    Checks to see if the user has authenticated this application
+    """
+    def getAuthKey( self ): 
+        d =  {
+            api.frob : FLICKR[ api.frob ], 
+            api.perms : "write"  
+            }
+        sig = self.signCall( d )
+        url = self.urlGen( api.auth, d, sig )
+        ans = ""
+        try:
+            webbrowser.open( url )
+            ans = raw_input("Have you authenticated this application? (Y/N): ")
+        except:
+            print str(sys.exc_info())
+        if ( ans.lower() == "n" ):
+            print "You need to allow this program to access your Flickr site."
+            print "A web browser should pop open with instructions."
+            print "After you have allowed access restart uploadr.py"
+            sys.exit()    
+
+    """
+    http://www.flickr.com/services/api/flickr.auth.getToken.html
+    
+    flickr.auth.getToken
+    
+    Returns the auth token for the given frob, if one has been attached. This method call must be signed.
+    Authentication
+    
+    This method does not require authentication.
+    Arguments
+    
+    NTC: We need to store the token in a file so we can get it and then check it insted of
+    getting a new on all the time.
+        
+    api.key (Required)
+       Your API application key. See here for more details.
+    frob (Required)
+       The frob to check.         
+    """   
+    def getToken( self ):
+        d = {
+            api.method : "flickr.auth.getToken",
+            api.frob : str(FLICKR[ api.frob ])
+        }
+        sig = self.signCall( d )
+        url = self.urlGen( api.rest, d, sig )
+        try:
+            res = self.getResponse( url )
+            if ( self.isGood( res ) ):
+                self.token = str(res.auth.token)
+                self.perms = str(res.auth.perms)
+                self.cacheToken()
+            else :
+                self.reportError( res )
+        except:
+            print str( sys.exc_info() )
+
+    """
+    Attempts to get the flickr token from disk.
+    """
+    def getCachedToken( self ): 
+        if ( os.path.exists( self.TOKEN_FILE )):
+            return open( self.TOKEN_FILE ).read()
+        else :
+            return None
+        
+
+
+    def cacheToken( self ):
+        try:
+            open( self.TOKEN_FILE , "w").write( str(self.token) )
+        except:
+            print "Issue writing token to local cache " , str(sys.exc_info())
+
+    """
+    flickr.auth.checkToken
+
+    Returns the credentials attached to an authentication token.
+    Authentication
+    
+    This method does not require authentication.
+    Arguments
+    
+    api.key (Required)
+        Your API application key. See here for more details.
+    auth_token (Required)
+        The authentication token to check. 
+    """
+    def checkToken( self ):    
+        if ( self.token == None ):
+            return False
+        else :
+            d = {
+                api.token  :  str(self.token) ,
+                api.method :  "flickr.auth.checkToken"
+            }
+            sig = self.signCall( d )
+            url = self.urlGen( api.rest, d, sig )     
+            try:
+                res = self.getResponse( url ) 
+                if ( self.isGood( res ) ):
+                    self.token = res.auth.token
+                    self.perms = res.auth.perms
+                    return True
+                else :
+                    self.reportError( res )
+            except:
+                print str( sys.exc_info() )          
+            return False
+     
+             
+    def upload( self ):
+        newImages = self.grabNewImages()
+        if ( not self.checkToken() ):
+            self.authenticate()
+        self.uploaded = shelve.open( HISTORY_FILE )
+        for image in newImages:
+            self.uploadImage( image )
+        self.uploaded.close()
+        
+    def grabNewImages( self ):
+        images = []
+        foo = os.walk( IMAGE_DIR )
+        for data in foo:
+            (dirpath, dirnames, filenames) = data
+            for f in filenames :
+                ext = f.lower().split(".")[-1]
+                if ( ext == "jpg" or ext == "gif" or ext == "png" ):
+                    images.append( os.path.normpath( dirpath + "/" + f ) )
+        images.sort()
+        return images
+                   
+    
+    def uploadImage( self, image ):
+        if ( not self.uploaded.has_key( image ) ):
+            print "Uploading ", image , "...",
+            try:
+                photo = ('photo', image, open(image,'rb').read())
+                d = {
+                    api.token   : str(self.token),
+                    api.perms   : str(self.perms),
+                    "tags"      : str( FLICKR["tags"] ),
+                    "is_public" : str( FLICKR["is_public"] ),
+                    "is_friend" : str( FLICKR["is_friend"] ),
+                    "is_family" : str( FLICKR["is_family"] )
+                }
+                sig = self.signCall( d )
+                d[ api.sig ] = sig
+                d[ api.key ] = FLICKR[ api.key ]        
+                url = self.build_request(api.upload, d, (photo,))    
+                xml = urllib2.urlopen( url ).read()
+                res = xmltramp.parse(xml)
+                if ( self.isGood( res ) ):
+                    print "successful."
+                    self.logUpload( res.photoid, image )
+                else :
+                    print "problem.."
+                    self.reportError( res )
+            except:
+                print str(sys.exc_info())
+
+
+    def logUpload( self, photoID, imageName ):
+        photoID = str( photoID )
+        imageName = str( imageName )
+        self.uploaded[ imageName ] = photoID
+        self.uploaded[ photoID ] = imageName
+            
+    #
+    #
+    # build_request/encode_multipart_formdata code is from www.voidspace.org.uk/atlantibots/pythonutils.html
+    #
+    #
+    def build_request(self, theurl, fields, files, txheaders=None):
+        """
+        Given the fields to set and the files to encode it returns a fully formed urllib2.Request object.
+        You can optionally pass in additional headers to encode into the opject. (Content-type and Content-length will be overridden if they are set).
+        fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
+        files is a sequence of (name, filename, value) elements for data to be uploaded as files.    
+        """
+        content_type, body = self.encode_multipart_formdata(fields, files)
+        if not txheaders: txheaders = {}
+        txheaders['Content-type'] = content_type
+        txheaders['Content-length'] = str(len(body))
+
+        return urllib2.Request(theurl, body, txheaders)     
+
+    def encode_multipart_formdata(self,fields, files, BOUNDARY = '-----'+mimetools.choose_boundary()+'-----'):
+        """ Encodes fields and files for uploading.
+        fields is a sequence of (name, value) elements for regular form fields - or a dictionary.
+        files is a sequence of (name, filename, value) elements for data to be uploaded as files.
+        Return (content_type, body) ready for urllib2.Request instance
+        You can optionally pass in a boundary string to use or we'll let mimetools provide one.
+        """    
+        CRLF = '\r\n'
+        L = []
+        if isinstance(fields, dict):
+            fields = fields.items()
+        for (key, value) in fields:   
+            L.append('--' + BOUNDARY)
+            L.append('Content-Disposition: form-data; name="%s"' % key)
+            L.append('')
+            L.append(value)
+        for (key, filename, value) in files:
+            filetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+            L.append('--' + BOUNDARY)
+            L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
+            L.append('Content-Type: %s' % filetype)
+            L.append('')
+            L.append(value)
+        L.append('--' + BOUNDARY + '--')
+        L.append('')
+        body = CRLF.join(L)
+        content_type = 'multipart/form-data; boundary=%s' % BOUNDARY        # XXX what if no files are encoded
+        return content_type, body
+    
+    
+    def isGood( self, res ):
+        if ( not res == "" and res('stat') == "ok" ):
+            return True
+        else :
+            return False
+            
+            
+    def reportError( self, res ):
+        try:
+            print "Error:", str( res.err('code') + " " + res.err('msg') )
+        except:
+            print "Error: " + str( res )
+
+    """
+    Send the url and get a response.  Let errors float up
+    """
+    def getResponse( self, url ):
+        xml = urllib2.urlopen( url ).read()
+        return xmltramp.parse( xml )
+            
+
+    def run( self ):
+        while ( True ):
+            self.upload()
+            print "Last check: " , str( time.asctime(time.localtime()))
+            time.sleep( SLEEP_TIME )
+      
+if __name__ == "__main__":
+    flick = Uploadr()
+    
+    if ( len(sys.argv) >= 2  and sys.argv[1] == "-d"):
+        flick.run()
+    else:
+        flick.upload()

File ept-uploadr/uploadr/xmltramp.py

View file
+"""xmltramp: Make XML documents easily accessible."""
+
+__version__ = "2.16"
+__author__ = "Aaron Swartz"
+__credits__ = "Many thanks to pjz, bitsko, and DanC."
+__copyright__ = "(C) 2003 Aaron Swartz. GNU GPL 2."
+
+if not hasattr(__builtins__, 'True'): True, False = 1, 0
+def isstr(f): return isinstance(f, type('')) or isinstance(f, type(u''))
+def islst(f): return isinstance(f, type(())) or isinstance(f, type([]))
+
+empty = {'http://www.w3.org/1999/xhtml': ['img', 'br', 'hr', 'meta', 'link', 'base', 'param', 'input', 'col', 'area']}
+
+def quote(x, elt=True):
+	if elt and '<' in x and len(x) > 24 and x.find(']]>') == -1: return "<![CDATA["+x+"]]>"
+	else: x = x.replace('&', '&amp;').replace('<', '&lt;').replace(']]>', ']]&gt;')
+	if not elt: x = x.replace('"', '&quot;')
+	return x
+
+class Element:
+	def __init__(self, name, attrs=None, children=None, prefixes=None):
+		if islst(name) and name[0] == None: name = name[1]
+		if attrs:
+			na = {}
+			for k in attrs.keys():
+				if islst(k) and k[0] == None: na[k[1]] = attrs[k]
+				else: na[k] = attrs[k]
+			attrs = na
+		
+		self._name = name
+		self._attrs = attrs or {}
+		self._dir = children or []
+		
+		prefixes = prefixes or {}
+		self._prefixes = dict(zip(prefixes.values(), prefixes.keys()))
+		
+		if prefixes: self._dNS = prefixes.get(None, None)
+		else: self._dNS = None
+	
+	def __repr__(self, recursive=0, multiline=0, inprefixes=None):
+		def qname(name, inprefixes): 
+			if islst(name):
+				if inprefixes[name[0]] is not None:
+					return inprefixes[name[0]]+':'+name[1]
+				else:
+					return name[1]
+			else:
+				return name
+		
+		def arep(a, inprefixes, addns=1):
+			out = ''
+
+			for p in self._prefixes.keys():
+				if not p in inprefixes.keys():
+					if addns: out += ' xmlns'
+					if addns and self._prefixes[p]: out += ':'+self._prefixes[p]
+					if addns: out += '="'+quote(p, False)+'"'
+					inprefixes[p] = self._prefixes[p]
+			
+			for k in a.keys():
+				out += ' ' + qname(k, inprefixes)+ '="' + quote(a[k], False) + '"'
+			
+			return out
+		
+		inprefixes = inprefixes or {u'http://www.w3.org/XML/1998/namespace':'xml'}
+		
+		# need to call first to set inprefixes:
+		attributes = arep(self._attrs, inprefixes, recursive) 
+		out = '<' + qname(self._name, inprefixes)  + attributes 
+		
+		if not self._dir and (self._name[0] in empty.keys() 
+		  and self._name[1] in empty[self._name[0]]):
+			out += ' />'
+			return out
+		
+		out += '>'
+
+		if recursive:
+			content = 0
+			for x in self._dir: 
+				if isinstance(x, Element): content = 1
+				
+			pad = '\n' + ('\t' * recursive)
+			for x in self._dir:
+				if multiline and content: out +=  pad 
+				if isstr(x): out += quote(x)
+				elif isinstance(x, Element):
+					out += x.__repr__(recursive+1, multiline, inprefixes.copy())
+				else:
+					raise TypeError, "I wasn't expecting "+`x`+"."
+			if multiline and content: out += '\n' + ('\t' * (recursive-1))
+		else:
+			if self._dir: out += '...'
+		
+		out += '</'+qname(self._name, inprefixes)+'>'
+			
+		return out
+	
+	def __unicode__(self):
+		text = ''
+		for x in self._dir:
+			text += unicode(x)
+		return ' '.join(text.split())
+		
+	def __str__(self):
+		return self.__unicode__().encode('utf-8')
+	
+	def __getattr__(self, n):
+		if n[0] == '_': raise AttributeError, "Use foo['"+n+"'] to access the child element."
+		if self._dNS: n = (self._dNS, n)
+		for x in self._dir:
+			if isinstance(x, Element) and x._name == n: return x
+		raise AttributeError, 'No child element named \''+n+"'"
+		
+	def __hasattr__(self, n):
+		for x in self._dir:
+			if isinstance(x, Element) and x._name == n: return True
+		return False
+		
+ 	def __setattr__(self, n, v):
+		if n[0] == '_': self.__dict__[n] = v
+		else: self[n] = v
+ 
+
+	def __getitem__(self, n):
+		if isinstance(n, type(0)): # d[1] == d._dir[1]
+			return self._dir[n]
+		elif isinstance(n, slice(0).__class__):
+			# numerical slices
+			if isinstance(n.start, type(0)): return self._dir[n.start:n.stop]
+			
+			# d['foo':] == all <foo>s
+			n = n.start
+			if self._dNS and not islst(n): n = (self._dNS, n)
+			out = []
+			for x in self._dir:
+				if isinstance(x, Element) and x._name == n: out.append(x) 
+			return out
+		else: # d['foo'] == first <foo>
+			if self._dNS and not islst(n): n = (self._dNS, n)
+			for x in self._dir:
+				if isinstance(x, Element) and x._name == n: return x
+			raise KeyError
+	
+	def __setitem__(self, n, v):
+		if isinstance(n, type(0)): # d[1]
+			self._dir[n] = v
+		elif isinstance(n, slice(0).__class__):
+			# d['foo':] adds a new foo
+			n = n.start
+			if self._dNS and not islst(n): n = (self._dNS, n)
+
+			nv = Element(n)
+			self._dir.append(nv)
+			
+		else: # d["foo"] replaces first <foo> and dels rest
+			if self._dNS and not islst(n): n = (self._dNS, n)
+
+			nv = Element(n); nv._dir.append(v)
+			replaced = False
+
+			todel = []
+			for i in range(len(self)):
+				if self[i]._name == n:
+					if replaced:
+						todel.append(i)
+					else:
+						self[i] = nv
+						replaced = True
+			if not replaced: self._dir.append(nv)
+			for i in todel: del self[i]
+
+	def __delitem__(self, n):
+		if isinstance(n, type(0)): del self._dir[n]
+		elif isinstance(n, slice(0).__class__):
+			# delete all <foo>s
+			n = n.start
+			if self._dNS and not islst(n): n = (self._dNS, n)
+			
+			for i in range(len(self)):
+				if self[i]._name == n: del self[i]
+		else:
+			# delete first foo
+			for i in range(len(self)):
+				if self[i]._name == n: del self[i]
+				break
+	
+	def __call__(self, *_pos, **_set): 
+		if _set:
+			for k in _set.keys(): self._attrs[k] = _set[k]
+		if len(_pos) > 1:
+			for i in range(0, len(_pos), 2):
+				self._attrs[_pos[i]] = _pos[i+1]
+		if len(_pos) == 1 is not None:
+			return self._attrs[_pos[0]]
+		if len(_pos) == 0:
+			return self._attrs
+
+	def __len__(self): return len(self._dir)
+
+class Namespace:
+	def __init__(self, uri): self.__uri = uri
+	def __getattr__(self, n): return (self.__uri, n)
+	def __getitem__(self, n): return (self.__uri, n)
+
+from xml.sax.handler import EntityResolver, DTDHandler, ContentHandler, ErrorHandler
+
+class Seeder(EntityResolver, DTDHandler, ContentHandler, ErrorHandler):
+	def __init__(self):
+		self.stack = []
+		self.ch = ''
+		self.prefixes = {}
+		ContentHandler.__init__(self)
+		
+	def startPrefixMapping(self, prefix, uri):
+		if not self.prefixes.has_key(prefix): self.prefixes[prefix] = []
+		self.prefixes[prefix].append(uri)
+	def endPrefixMapping(self, prefix):
+		self.prefixes[prefix].pop()
+	
+	def startElementNS(self, name, qname, attrs):
+		ch = self.ch; self.ch = ''	
+		if ch and not ch.isspace(): self.stack[-1]._dir.append(ch)
+
+		attrs = dict(attrs)
+		newprefixes = {}
+		for k in self.prefixes.keys(): newprefixes[k] = self.prefixes[k][-1]
+		
+		self.stack.append(Element(name, attrs, prefixes=newprefixes.copy()))
+	
+	def characters(self, ch):
+		self.ch += ch
+	
+	def endElementNS(self, name, qname):
+		ch = self.ch; self.ch = ''
+		if ch and not ch.isspace(): self.stack[-1]._dir.append(ch)
+	
+		element = self.stack.pop()
+		if self.stack:
+			self.stack[-1]._dir.append(element)
+		else:
+			self.result = element
+
+from xml.sax import make_parser
+from xml.sax.handler import feature_namespaces
+
+def seed(fileobj):
+	seeder = Seeder()
+	parser = make_parser()
+	parser.setFeature(feature_namespaces, 1)
+	parser.setContentHandler(seeder)
+	parser.parse(fileobj)
+	return seeder.result
+
+def parse(text):
+	from StringIO import StringIO
+	return seed(StringIO(text))
+
+def load(url): 
+	import urllib
+	return seed(urllib.urlopen(url))
+
+def unittest():
+	parse('<doc>a<baz>f<b>o</b>ob<b>a</b>r</baz>a</doc>').__repr__(1,1) == \
+	  '<doc>\n\ta<baz>\n\t\tf<b>o</b>ob<b>a</b>r\n\t</baz>a\n</doc>'
+	
+	assert str(parse("<doc />")) == ""
+	assert str(parse("<doc>I <b>love</b> you.</doc>")) == "I love you."
+	assert parse("<doc>\nmom\nwow\n</doc>")[0].strip() == "mom\nwow"
+	assert str(parse('<bing>  <bang> <bong>center</bong> </bang>  </bing>')) == "center"
+	assert str(parse('<doc>\xcf\x80</doc>')) == '\xcf\x80'
+	
+	d = Element('foo', attrs={'foo':'bar'}, children=['hit with a', Element('bar'), Element('bar')])
+	
+	try: 
+		d._doesnotexist
+		raise "ExpectedError", "but found success. Damn."
+	except AttributeError: pass
+	assert d.bar._name == 'bar'
+	try:
+		d.doesnotexist
+		raise "ExpectedError", "but found success. Damn."
+	except AttributeError: pass
+	
+	assert hasattr(d, 'bar') == True
+	
+	assert d('foo') == 'bar'
+	d(silly='yes')
+	assert d('silly') == 'yes'
+	assert d() == d._attrs
+	
+	assert d[0] == 'hit with a'
+	d[0] = 'ice cream'
+	assert d[0] == 'ice cream'
+	del d[0]
+	assert d[0]._name == "bar"
+	assert len(d[:]) == len(d._dir)
+	assert len(d[1:]) == len(d._dir) - 1
+	assert len(d['bar':]) == 2
+	d['bar':] = 'baz'
+	assert len(d['bar':]) == 3
+	assert d['bar']._name == 'bar'
+	
+	d = Element('foo')
+	
+	doc = Namespace("http://example.org/bar")
+	bbc = Namespace("http://example.org/bbc")
+	dc = Namespace("http://purl.org/dc/elements/1.1/")
+	d = parse("""<doc version="2.7182818284590451"
+	  xmlns="http://example.org/bar" 
+	  xmlns:dc="http://purl.org/dc/elements/1.1/"
+	  xmlns:bbc="http://example.org/bbc">
+		<author>John Polk and John Palfrey</author>
+		<dc:creator>John Polk</dc:creator>
+		<dc:creator>John Palfrey</dc:creator>
+		<bbc:show bbc:station="4">Buffy</bbc:show>
+	</doc>""")
+
+	assert repr(d) == '<doc version="2.7182818284590451">...</doc>'
+	assert d.__repr__(1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar" version="2.7182818284590451"><author>John Polk and John Palfrey</author><dc:creator>John Polk</dc:creator><dc:creator>John Palfrey</dc:creator><bbc:show bbc:station="4">Buffy</bbc:show></doc>'
+	assert d.__repr__(1,1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar" version="2.7182818284590451">\n\t<author>John Polk and John Palfrey</author>\n\t<dc:creator>John Polk</dc:creator>\n\t<dc:creator>John Palfrey</dc:creator>\n\t<bbc:show bbc:station="4">Buffy</bbc:show>\n</doc>'
+
+	assert repr(parse("<doc xml:lang='en' />")) == '<doc xml:lang="en"></doc>'
+
+	assert str(d.author) == str(d['author']) == "John Polk and John Palfrey"
+	assert d.author._name == doc.author
+	assert str(d[dc.creator]) == "John Polk"
+	assert d[dc.creator]._name == dc.creator
+	assert str(d[dc.creator:][1]) == "John Palfrey"
+	d[dc.creator] = "Me!!!"
+	assert str(d[dc.creator]) == "Me!!!"
+	assert len(d[dc.creator:]) == 1
+	d[dc.creator:] = "You!!!"
+	assert len(d[dc.creator:]) == 2
+	
+	assert d[bbc.show](bbc.station) == "4"
+	d[bbc.show](bbc.station, "5")
+	assert d[bbc.show](bbc.station) == "5"
+
+	e = Element('e')
+	e.c = '<img src="foo">'
+	assert e.__repr__(1) == '<e><c>&lt;img src="foo"></c></e>'
+	e.c = '2 > 4'
+	assert e.__repr__(1) == '<e><c>2 > 4</c></e>'
+	e.c = 'CDATA sections are <em>closed</em> with ]]>.'
+	assert e.__repr__(1) == '<e><c>CDATA sections are &lt;em>closed&lt;/em> with ]]&gt;.</c></e>'
+	e.c = parse('<div xmlns="http://www.w3.org/1999/xhtml">i<br /><span></span>love<br />you</div>')
+	assert e.__repr__(1) == '<e><c><div xmlns="http://www.w3.org/1999/xhtml">i<br /><span></span>love<br />you</div></c></e>'	
+	
+	e = Element('e')
+	e('c', 'that "sucks"')
+	assert e.__repr__(1) == '<e c="that &quot;sucks&quot;"></e>'
+
+	
+	assert quote("]]>") == "]]&gt;"
+	assert quote('< dkdkdsd dkd sksdksdfsd fsdfdsf]]> kfdfkg >') == '&lt; dkdkdsd dkd sksdksdfsd fsdfdsf]]&gt; kfdfkg >'
+	
+	assert parse('<x a="&lt;"></x>').__repr__(1) == '<x a="&lt;"></x>'
+	assert parse('<a xmlns="http://a"><b xmlns="http://b"/></a>').__repr__(1) == '<a xmlns="http://a"><b xmlns="http://b"></b></a>'
+	
+if __name__ == '__main__': unittest()

File python/killPTP.py

View file
+from subprocess import Popen,PIPE
+
+# run ps -e | grep PTP
+p1 = Popen(["ps","-e"], stdout=PIPE)
+p2 = Popen(["grep", "PTP"], stdin=p1.stdout, stdout=PIPE)
+output = p2.communicate()[0]
+
+# look for process ID in output
+# pid of PTP daemon is first white-space delimited substring of first line of output
+lines = output.split('\n')
+pid = lines[0].strip().split(' ')[0]
+if (pid!=''):
+    print('PTP is running. Killing process...')
+    status = Popen(["kill", "-9",pid], stdout=PIPE).communicate()[0]
+else:
+    print ('PTP is NOT running. No action taken.')

File python/undistort.py

View file
+# barrel un-distortion
+# using Rsrc = r * ( A*r3 + B*r2 + C*r + D )
+from PIL import Image,ImageOps
+from math import *
+import itertools
+
+# perform barrel/pincushion distortion on imgIn, returning imgOut
+# this is SLOW: 8MP image took ~40seconds
+# does not even filter yet
+def barrel(imgIn):
+    A = 0.0
+    B = -0.02
+    C = 0
+    D = 1.02 # A + B + C + D = 1.0
+    imgOut = Image.new(imgIn.mode,imgIn.size)
+    (w,h) = imgOut.size
+    halfMin = min(w/2,h/2)
+    halfW =w/2
+    halfH = h/2
+    pixIn = imgIn.load()
+    pixOut = imgOut.load()
+    for x in xrange(w):
+        for y in xrange(h):
+            xOff = x-halfW
+            yOff = y-halfH
+            r = sqrt(xOff**2+yOff**2) / halfMin
+            rSrc = r*(A*r**3+B*r**2+C*r+D)
+            angle = atan2(yOff,xOff)
+            xSrc = (halfW)+rSrc*cos(angle)*halfMin
+            ySrc = (halfH)+rSrc*sin(angle)*halfMin
+            xSrc = max(0,min(w-1,xSrc)) #clamp to visible pixels
+            ySrc = max(0,min(h-1,ySrc))
+            pixOut[x,y]= pixIn[xSrc,ySrc]
+    return imgOut
+
+# 2nd version uses map - no faster
+# takes ~44 sec for a 8MP image
+def barrel2(imgIn):
+    
+    imgOut = Image.new(imgIn.mode,imgIn.size)
+    (w,h) = imgOut.size
+    halfMin = min(w/2,h/2)
+    halfW =w/2
+    halfH = h/2
+    pixIn = imgIn.load()
+    pixOut = imgOut.load()
+    args = itertools.product(xrange(w),xrange(h),[halfW],[halfH],[halfMin],[pixIn],[pixOut])
+    map(barrel2Aux,args)
+    return imgOut
+    
+def barrel2Aux((x,y,halfW,halfH,halfMin,pixIn,pixOut)):
+    A = 0.0
+    B = -0.02
+    C = 0
+    D = 1.02 # A + B + C + D = 1.0
+    xOff = x-halfW
+    yOff = y-halfH
+    r = sqrt(xOff**2+yOff**2) / halfMin
+    rSrc = r*(A*r**3+B*r**2+C*r+D)
+    angle = atan2(yOff,xOff)
+    xSrc = (halfW)+rSrc*cos(angle)*halfMin
+    ySrc = (halfH)+rSrc*sin(angle)*halfMin
+    xSrc = max(0,min((halfW*2)-1,xSrc)) #clamp to visible pixels
+    ySrc = max(0,min((halfH*2)-1,ySrc))
+    pixOut[x,y]= pixIn[xSrc,ySrc]
+
+    
+# then use px = itertools.product(xrange(w),xrange(h))
+# map(barrel2,px)
+    
+#quad perspective correction
+def quad(imgIn):
+    outSize = (2000,1000)
+    coords =(388,662, 392,1946, 2950,1912, 2938,656)
+    return imgIn.transform(outSize,Image.QUAD,coords)
+    
+file = "in.jpg"
+imgIn = Image.open(file)
+
+imgOut = quad(barrel2(imgIn))
+imgOut = ImageOps.autocontrast(imgOut,cutoff=10)
+imgOut.save("out.jpg","JPEG")