<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mike Renfro's Blog &#187; Python</title>
	<atom:link href="http://blogs.cae.tntech.edu/mwr/category/python/feed/" rel="self" type="application/rss+xml" />
	<link>http://blogs.cae.tntech.edu/mwr</link>
	<description>A partial repository of whatever comes to mind</description>
	<lastBuildDate>Sat, 31 Oct 2009 23:02:35 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Capturing an Image from a WIA-compatible Digital Camera</title>
		<link>http://blogs.cae.tntech.edu/mwr/2009/09/03/capturing-an-image-from-a-wia-compatible-digital-camera/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2009/09/03/capturing-an-image-from-a-wia-compatible-digital-camera/#comments</comments>
		<pubDate>Thu, 03 Sep 2009 23:14:28 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
				<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=126</guid>
		<description><![CDATA[We&#8217;ve had a research project requiring a fair amount of image acquisition and processing, requiring  higher resolutions than most industrial cameras can offer. As a result, we&#8217;ve tried at least three different digital cameras (Canon PowerShot S3is, Nikon D40, and Canon PowerShot SD780is). Each of them has their own advantages and disadvantages:

S3is advantages:  good control [...]]]></description>
			<content:encoded><![CDATA[<p>We&#8217;ve had a research project requiring a fair amount of image acquisition and processing, requiring  higher resolutions than most industrial cameras can offer. As a result, we&#8217;ve tried at least three different digital cameras (Canon PowerShot S3is, Nikon D40, and Canon PowerShot SD780is). Each of them has their own advantages and disadvantages:</p>
<ul>
<li>S3is advantages:  good control with Breezesys&#8217; PSRemote, including a pretty complete DLL that we can call from our LabVIEW code. Disadvantages: larger aperture sizes reduce the depth of field, and PSRemote can&#8217;t toggle macro and super-macro modes.</li>
<li>D40 advantages: complete manual control when needed, huge range of apertures including ones that allow for good depth of field, easy to grab pictures in PTP mode from Windows Explorer. Disadvantages: Breezesys&#8217; NKRemote for Nikon doesn&#8217;t support the D40.</li>
<li>SD780is advantages: ridiculously high resolution (12MP) in a tiny camera. Disadvantages: no PSRemote support, and little manual control of settings.</li>
</ul>
<p>So it came down to needing LabVIEW to acquire images from whatever camera automatically. We had gotten it working with PSRemote some time back for a different class of pictures, but the ones we needed now went beyond PSRemote&#8217;s and the S3is&#8217; ability to focus in on close distances. And the SD780is was out entirely. So that left the Nikon.</p>
<p>Previous testing with the Nikon generally consisted of putting it in PTP mode, opening up Windows Explorer, hitting the &#8220;Take a new picture&#8221; link, and then copying over the newest image to the local drive. Great, except for the clicking and dragging. The obvious solution would be to automate the process via Win32 COM programming. After a few hours with the Python docs and MSDN, a workable Python script was born:</p>
<pre>import win32com.client, time, os

WIA_COM = "WIA.CommonDialog"

WIA_DEVICE_UNSPECIFIED = 0
WIA_DEVICE_CAMERA = 2

WIA_INTENT_UNSPECIFIED = 0

WIA_BIAS_MIN_SIZE = 65536
WIA_BIAS_MAX_QUALITY = 131072

WIA_IMG_FORMAT_PNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}"

WIA_COMMAND_TAKE_PICTURE="{AF933CAC-ACAD-11D2-A093-00C04F72DC3C}"

def acquire_image_wia():
    wia = win32com.client.Dispatch(WIA_COM) # wia is a CommonDialog object
    dev = wia.ShowSelectDevice()
    for command in dev.Commands:
        if command.CommandID==WIA_COMMAND_TAKE_PICTURE:
            foo=dev.ExecuteCommand(WIA_COMMAND_TAKE_PICTURE)

    i=1
    for item in dev.Items:
        if i==dev.Items.Count:
            image=item.Transfer(WIA_IMG_FORMAT_PNG)
            break
        i=i+1

    fname = 'wia-test.jpg'
    if os.path.exists(fname):
        os.remove(fname)
    image.SaveFile(fname)

os.chdir('c:/temp')
acquire_image_wia()</pre>
<p>Things I like about this:</p>
<ul>
<li>snaps the camera shutter, grabs the last image from the card, and stashes it on the local drive, no questions asked. Since I&#8217;ll probably set the camera settings once and leave it on manual focus, this is all I needed.</li>
<li>easily converted into an executable with py2exe.</li>
<li>roughly 3.2 seconds to acquire and save the image, with around 2 seconds of that spent on the ExecuteCommand() line with a 0.25 second shutter speed.</li>
</ul>
<p>Things I don&#8217;t like about this:</p>
<ul>
<li>Windows COM programming makes my brain hurt.</li>
<li>To make things entirely hands-off, I had to disable my Webcam. I&#8217;m sure there&#8217;s a way to make WIA connect to a named device, but ShowSelectDevice() was all I found ready documentation for. With multiple cameras available, it always asked which one I wanted to acquire from. With only one camera available, it just went on and snapped the picture.</li>
<li>I couldn&#8217;t find a good way of jumping to the end of the list of items stored on the camera. I can count them, I can iterate over them, but I&#8217;m having to iterate over each element until I get to the last one, and <strong>then</strong> I can transfer it over.</li>
</ul>
<p>Someone may have a better solution to the last two problems, but this should get people started.</p>
<p><strong>Update</strong> &#8212; Leaner, meaner code to grab the last image off one specified camera &#8212; thanks to Janzert in the comments below:</p>
<pre>import win32com.client, time, os

MY_CAMERA="D40"
WIA_IMG_FORMAT_PNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}"
WIA_COMMAND_TAKE_PICTURE="{AF933CAC-ACAD-11D2-A093-00C04F72DC3C}"

def acquire_image_wia():
    # Find the camera
    devman=win32com.client.Dispatch("WIA.DeviceManager")
    for info in devman.DeviceInfos:
        for prop in info.Properties:
            if prop.Name=="Name" and prop.Value==MY_CAMERA:
                dev = info.Connect()

    # Snap picture
    foo=dev.ExecuteCommand(WIA_COMMAND_TAKE_PICTURE)
    # Transfer last image (doesn't actually use PNG format, but this
    # still is valid syntax).
    image=dev.Items[dev.Items.count].Transfer(WIA_IMG_FORMAT_PNG)
    # Save into file
    fname = 'wia-test.jpg'
    if os.path.exists(fname):
        os.remove(fname)
    image.SaveFile(fname)

os.chdir('c:/temp')
acquire_image_wia()</pre>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2009/09/03/capturing-an-image-from-a-wia-compatible-digital-camera/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Where&#8217;s MarcoPolo for Windows?</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/#comments</comments>
		<pubDate>Sat, 26 Jul 2008 04:26:40 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
				<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=85</guid>
		<description><![CDATA[MarcoPolo, &#8220;context-aware computing for OS X&#8221; appeals to all three great virtues of the programmer: Laziness, Impatience, and Hubris. It makes me want a Mac notebook. Unfortunately, Pro/E, ANSYS, and other necessary tools for my regular work would mean I&#8217;d end up dual-booting the Mac all the time. And I&#8217;d miss my port replicator and [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.symonds.id.au/marcopolo/">MarcoPolo</a>, &#8220;context-aware computing for OS X&#8221; appeals to all three great virtues of the programmer: Laziness, Impatience, and Hubris. It makes me want a Mac notebook. Unfortunately, Pro/E, ANSYS, and other necessary tools for my regular work would mean I&#8217;d end up dual-booting the Mac all the time. And I&#8217;d miss my port replicator and its enabling me to have one connection to the monitor, keyboard, mouse, network, etc.</p>
<p>So, why not build a MarcoPolo for Windows? I&#8217;ve not yet found one, but I&#8217;d be more than happy to be proven wrong there. In the meantime, I&#8217;m working on a Python/Windows proof-of-concept that could be the groundwork for a Windows analogue to MarcoPolo.<br />
<span id="more-85"></span></p>
<pre name="code" class="py">

# Idea for Win32 version of MarcoPolo (Context-aware computing for Mac OS X)

# Contexts
contexts = {
    &#039;Home&#039;: 1,
    &#039;Work&#039;: 1,
    &#039;pre-Noon&#039;: 1,
    &#039;post-Noon&#039;: 1,
    &#039;Unknown&#039;: 1,
}

import wmi, pybonjour, time, IP4Range
# wmi: http://tgolden.sc.sabren.com/python/wmi.html
# pybonjour: http://o2s.csail.mit.edu/o2s-wiki/pybonjour
# IP4Range: http://code.activestate.com/recipes/466298/

def main():
    c=wmi.WMI()
    # Evidence sources via WMI (direct from MarcoPolo features, some
    # sources may not be available in Win32)

    # Current audio output device (headphones/speakers) -- may not be
    # discoverable on XP and earlier.

    # Discoverable Bluetooth devices -- can find a Bluetooth adapter via
    # USB, but not a connected end-user device

    # Advertised Bonjour (Zeroconf) services -- &#039;browse_and_resolve.py
    # _daap._tcp&#039; finds a remote iTunes share, but no idea how to look for
    # random services.

    # Attached FireWire devices -- no FireWire-capable computers on hand

    # Assigned IP addresses
    ipList=[]
    for adapter in c.Win32_NetworkAdapterConfiguration(IPEnabled=&quot;True&quot;):
        ipList.append(adapter.IPAddress[0])

    # Ambient light level — may not be any standard sensors available to
    # Windows

    # Attached monitors
    monitorList=[]
    for monitor in c.Win32_DesktopMonitor(Availability=3):
        monitorList.append(monitor.Name)

    # Active network links
    networkAdapterList=[]
    for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2):
        networkAdapterList.append(adapter.Description)

    # Power source (power adapter/battery) — need to test on laptop

    # Running Applications
    applicationList=[]
    for process in c.Win32_Process():
        applicationList.append(process.Name)

    # Time of day
    timeOfDay=time.strftime(&quot;%H:%M&quot;)

    # Attached USB devices
    usbDeviceList=[]
    for device in c.Win32_USBControllerDevice():
        usbDeviceList.append(device.Dependent.Description)

    # Visible WiFi networks — need to test on laptop

    # Rules (type, criteria, context, confidence)
    ruleList = [
        (&#039;usb&#039;, &#039;Brother HL-5140 series&#039;, &#039;Home&#039;, 0.80),
        (&#039;usb&#039;, &#039;Visioneer OneTouch 7300&#039;, &#039;Home&#039;, 0.99),
        (&#039;monitor&#039;, &#039;Plug and Play Monitor&#039;, &#039;Home&#039;, 0.60),
        (&#039;ip&#039;, &#039;192.168.254.4&#039;, &#039;Home&#039;, 0.50),
        (&#039;ip&#039;, &#039;149.149.254.0/24&#039;, &#039;Work&#039;, 0.99),
        (&#039;ssid&#039;, &#039;mike-and-carolyn&#039;, &#039;Home&#039;, 0.99),
        (&#039;time&#039;, [&#039;00:00&#039;, &#039;11:59&#039;], &#039;pre-Noon&#039;, 0.99),
        (&#039;time&#039;, [&#039;12:00&#039;, &#039;23:59&#039;], &#039;post-Noon&#039;, 0.99),
        ]

    # Decision
    for rule in ruleList:
        itemFound = -1
        (ruleType, ruleItem, ruleContext, ruleStrength) = rule
        if ruleType==&#039;ip&#039;:
            ipRange=IP4Range.IP4Range(ruleItem)
            for ip in ipList:
                currentIP=IP4Range.IP4Range(ip.encode(&#039;ascii&#039;))
                if currentIP.issubset(ipRange):
                    itemFound = 1

        elif ruleType==&#039;monitor&#039;:
            try:
                itemFound = monitorList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;adapter&#039;:
            try:
                itemFound = networkAdapterList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;application&#039;:
            try:
                itemFound = applicationList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;usb&#039;:
            try:
                itemFound = usbDeviceList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;time&#039;:
            if cmp(timeOfDay,ruleItem[0])&gt;=0 and cmp(timeOfDay,ruleItem[1])&lt;=0:
                itemFound=1
         else:
            print &quot;Unknown rule type %s&quot; % (ruleType)
            continue

        if itemFound!=-1:
            contexts[ruleContext]=contexts[ruleContext]*(1-ruleStrength)

    myLocation=&#039;Unknown&#039;
    myConfidence=1-contexts[&#039;Unknown&#039;]
    for context in contexts:
        if contexts[context]!=1:
            if (1-contexts[context])&gt;myConfidence:
                myLocation=context
                myConfidence = 1-contexts[context]

    print &quot;Your location: %s (confidence %f)&quot; % (myLocation, myConfidence)
    print

if __name__ == &quot;__main__&quot;:
    main()
</pre>
<p>I know my Python is mediocre at best. But this does work so far in my limited testing (&#8221;Unknown rule type ssid, Your location: Home (confidence 0.999600)&#8221;). Thanks to David Symonds for his assistance in <a href="http://groups.google.com/group/marcopolo-discuss/browse_thread/thread/c4c4e7f6521af282/396bbcff92029b7b">this thread</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Better Inventory Management Through Python</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/#comments</comments>
		<pubDate>Tue, 01 Jul 2008 21:58:50 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
				<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/</guid>
		<description><![CDATA[One good thing about the switch from the legacy administrative computing system to the new Banner setup is now I can more easily get an unformatted table of my department&#8217;s equipment inventory instead of a printed report. This matters to me for a few reasons:

The default inventory reports always come out sorted by inventory tag. [...]]]></description>
			<content:encoded><![CDATA[<p>One good thing about the switch from the legacy administrative computing system to the new Banner setup is now I can more easily get an unformatted table of my department&#8217;s equipment inventory instead of a printed report. This matters to me for a few reasons:</p>
<ul>
<li>The default inventory reports always come out sorted by inventory tag. I have no idea why. If you have anything more than a handful of items to inventory, you would sort them by room, just like you&#8217;d identify them when walking around. Since I have somewhere around 100 items to inventory across 6-7 buildings, it&#8217;s a pain and rather error-prone to manually mark the ones in a particular building.</li>
<li>Even when I got a report sorted by location, that doesn&#8217;t help a great deal with items that I only see during inventory time, or during a random audit. My memory just isn&#8217;t that good, and occasionally an item will move from one room to another without my knowledge. Photos of the items would help greatly in finding them, but the default reports don&#8217;t include any.</li>
</ul>
<p>So this year, within a few hours of first asking for it, I got a nice CSV file of our inventory from the Business Office (thanks, Matt). It included lines like<br />
<code><br />
74396,"Fork Lift, Electric",SU 803647,F30,CATERPILLAR,5/1/2001,CH106,13900,Center for Manufacturing Research,6700<br />
</code><br />
that I could work with. I had text files from previous years on the old system, but they were much weirder to parse. This opened right up in Excel without issue, so I had hope for a much cleaner implementation of my old Python inventory scripts.</p>
<p>Man, I love Python. There&#8217;s <a href="http://docs.python.org/lib/module-csv.html">a built-in CSV module</a> that parses lines into lists, and separates values on each line into separate items in those lists. No parsing required on my part. There&#8217;s a nice third-party module for generating HTML markup (original site is down, but see <a href="http://web.archive.org/web/20070409120933/http://dustman.net/andy/python/HyperText/1.0.1/HyperText-1.0.1.tar.gz">this link from archive.org</a> for the module). And it&#8217;s a mature enough language to where <a href="http://groups.google.com/group/comp.lang.python/msg/42b5a84450278ab2">a years-old Usenet post on sorting</a> is still useful.</p>
<p>On a procedural rather than technical front, I started going around a couple of years ago and snapping pictures of items as I inventoried them. The old camera-phone pictures aren&#8217;t great, but they&#8217;re good enough to identify an item and find its property tag. So now I had a table of inventory data, and a big folder of JPGs named according to the item&#8217;s property tag. So now to convert them into useful web pages:<br />
<span id="more-73"></span></p>
<pre>
import csv
import os
import string
from HyperText.XHTML import TABLE, TR, TH, TD, IMG, DIV, HEAD, STYLE, BR
from HyperText.Documents import Document

notesDict = {
    '064286':'(2007) Surplused',
    }

"""A set of comparer classes."""
## http://groups.google.com/group/comp.lang.python/msg/42b5a84450278ab2

class CmpComposite:
    """Takes a list of compare functions and sorts in that order."""
    def __init__(self,*comparers):
        self.comparers=comparers
    def __call__(self,a,b):
        for cmp in self.comparers:
            c=cmp(a,b)
            if c:
                return c
        return 0

class CmpInverse:
    """Inverses the effect of a cmp."""
    def __init__(self,cmp):
        self.cmp=cmp
    def __call__(self,a,b):
        return -self.cmp(a,b)

class CmpColumn:
    """Sorts on an index of a sequence."""
    def __init__(self,column):
        self.column=column
    def __call__(self,a,b):
        return cmp(a[self.column],b[self.column])

class CmpAttr:
    """Sorts on an attribute."""
    def __init__(self, attr):
        self.attr = attr
    def __call__(self, x, y):
        return cmp(getattr(x, self.attr), getattr(y, self.attr))

reader=csv.reader(open('inventory_control_listing_13900.csv','rb'))
equipmentList = []
for row in reader:
    (tag, description, serial, model, manufacturer, arrival,
     location, code, department, cost) = row
    equipmentList.append((tag, description, serial, model, manufacturer,
                          arrival, location, cost))

for sortBy in ['tag', 'location']:
    if sortBy=='tag':
        equipmentList.sort(CmpColumn(0))
    elif sortBy=='location':
        equipmentList.sort(CmpColumn(6))

    outFilename = "by-%s.html" % (sortBy)
    print
    print "Would print to %s" % (outFile)
    print

    n=0
    seenTags = []
    inventoryTableList=DIV()
    for equipmentItem in equipmentList:
        (itemTag, itemDescription, itemSerial, itemModel, itemManufacturer,
         itemArrival, itemLocation, itemCost) = equipmentItem

        try:
            seenTags.index(itemTag)
        except ValueError:
            seenTags.append(itemTag)
            n=n+1
            if (n%2)==0:
                inventoryTable=TABLE(border='1',style='page-break-after: always')
            else:
                inventoryTable=TABLE(border='1')

            headerRow=TR()
            headerRow.append(TH('Tag'))
            headerRow.append(TH('Item Description'))
            headerRow.append(TH('Serial Number'))
            headerRow.append(TH('Model Number'))
            headerRow.append(TH('Arrival'))
            headerRow.append(TH('Manufacturer'))
            headerRow.append(TH('Location'))
            headerRow.append(TH('Cost'))
            inventoryTable.append(headerRow)

            itemRow=TR()
            if itemTag[0] in string.digits:
                itemTag=''.join(['0',itemTag])
            itemRow.append(TD(itemTag,align='right'))
            itemRow.append(TD(itemDescription))
            itemRow.append(TD(itemSerial))
            itemRow.append(TD(itemModel))
            itemRow.append(TD(itemArrival,align='right'))
            itemRow.append(TD(itemManufacturer))
            itemRow.append(TD(itemLocation,align='center'))
            itemRow.append(TD("$%.2f" % (float(itemCost)),align='right'))
            inventoryTable.append(itemRow)

            imageRow=TR()
            if os.path.exists("%s.jpg" % itemTag):
                imageRow.append(TD(IMG(src="%s.jpg" % itemTag),
                                   colspan=4,align='center',width='640'))
            else:
                imageRow.append(TD(IMG(src="missing.gif"),
                                   colspan=4,align='center',width='640'))

            if os.path.exists("%s-detail.jpg" % itemTag):
                imageRow.append(TD(IMG(src="%s-detail.jpg" % itemTag),
                                   colspan=4,align='center',width='640'))
            else:
                imageRow.append(TD(IMG(src="missing.gif"),
                                   colspan=4,align='center',width='640'))

            inventoryTable.append(imageRow)

            notesRow=TR()
            if notesDict.has_key(itemTag):
                notesRow.append(TD("Notes: %s" % notesDict[itemTag],colspan=8))
            else:
                notesRow.append(TD("Notes:",colspan=8))

            inventoryTable.append(notesRow)

            inventoryTableList.append(inventoryTable)
            inventoryTableList.append(BR())

    head=HEAD()
    head.append(STYLE(type='text/css'))
    htmlOutput=Document(head,inventoryTableList)
    htmlFile=open(outFilename,'w+')
    htmlOutput.writeto(htmlFile)
    htmlFile.close()
</pre>
<p>Once this script has run, I have two web pages of the same inventory data: one sorted by location, and another by property tag. I generally only use the location-sorted one, though. The CSS tries to force page breaks every two items, and judicious use of landscape printing and a bit of scaling in Firefox means I can carry around 40 pages of my report and find everything I need pretty quickly.<br />
<img src='http://blogs.cae.tntech.edu/mwr/files/2008/07/inventory-screenshot.png' alt='Screenshot of Inventory Report in Firefox' /></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python!</title>
		<link>http://blogs.cae.tntech.edu/mwr/2007/12/05/python/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2007/12/05/python/#comments</comments>
		<pubDate>Wed, 05 Dec 2007 16:16:50 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
				<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2007/12/05/python/</guid>
		<description><![CDATA[I also love python.

&#8211; From XKCD, 2007/12/05
]]></description>
			<content:encoded><![CDATA[<p>I also love python.</p>
<p><img src="http://imgs.xkcd.com/comics/python.png" alt="Python" /></p>
<p>&#8211; From <a href="http://xkcd.com/353/">XKCD, 2007/12/05</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2007/12/05/python/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grabbing Stills and Making FLV Movies from Axis IP Cameras</title>
		<link>http://blogs.cae.tntech.edu/mwr/2007/11/07/grabbing-stills-and-making-flv-movies-from-axis-ip-cameras/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2007/11/07/grabbing-stills-and-making-flv-movies-from-axis-ip-cameras/#comments</comments>
		<pubDate>Wed, 07 Nov 2007 22:58:04 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2007/11/07/grabbing-stills-and-making-flv-movies-from-axis-ip-cameras/</guid>
		<description><![CDATA[About a week and a half ago, I was reminded of a long-dormant project to archive still images from an Axis IP camera. I started this up a few years ago as a favor to a coworker, but it never really got finished. Previously, it was a pretty simple cron job that would just authenticate [...]]]></description>
			<content:encoded><![CDATA[<p>About a week and a half ago, I was reminded of a long-dormant project to archive still images from an Axis IP camera. I started this up a few years ago as a favor to a coworker, but it never really got finished. Previously, it was a pretty simple cron job that would just authenticate to the camera and download the current still. At some point, it would also use ImageMagick to convert the captured JPEGs to an MPEG or similar, but it was decidedly non-optimal.</p>
<p>So now that I was reminded by people who were <strong>very</strong> interested in seeing the results (i.e., monitoring their remotely-located labs), I took another stab at it. Much better results this time. Now my customers get:</p>
<ul>
<li>Still images captured every 30 seconds</li>
<li>FLV movies of the day&#8217;s stills made every 5 minutes</li>
<li>A playlist that lets them browse through previous days&#8217; activity for as long as we keep the movies around</li>
</ul>
<p>The programs and pages that make this mini-site follow below:<br />
<span id="more-50"></span><br />
axisgrab.py &#8212; extracts current image from the designated Axis camera, encodes today&#8217;s images into an FLV</p>
<pre>
#!/usr/bin/python

basename='denso'
axisip='192.168.0.1'
axisuser='someuser'
axispass='somepassword'
flvcreator='TTU ME Department'

import urllib2, time, os, re, getopt, sys

def usage():
    print """axisgrab.py -- grab or encode images from an Axis IP camera
Usage: axisgrab.py --grab
       axisgrab.py --encode
"""

def main():

    try:
        optlist, args = getopt.getopt(sys.argv[1:], 'g:e:',
                                      [ 'grab', 'encode' ])
    except getopt.GetoptError:
        usage()
        sys.exit(2)

    for opt, junk in optlist:
        if (opt=='--grab'):
            auth_handler = urllib2.HTTPBasicAuthHandler()
            auth_handler.add_password('/',axisip,axisuser,axispass)
            opener = urllib2.build_opener(auth_handler)
            urllib2.install_opener(opener)
            axis_jpg=urllib2.urlopen('http://%s/axis-cgi/jpg/image.cgi' % ( axisip ) )

            filename='%s_%s_%s.jpg' % ( basename, time.strftime('%Y%m%d'), time.strftime('%H%M%S') )
            local_jpg=open(filename,'w')
            local_jpg.write(axis_jpg.read())
            local_jpg.close()
            os.chmod(filename,0644)

        if (opt=='--encode'):
            os.system('mencoder -really-quiet -ovc lavc -lavcopts vcodec=flv -mf fps=25:type=jpg \\'mf://%s_%s*.jpg\\' -of lavf -lavfopts i_certify_that_my_video_stream_does_not_use_b_frames -o %s_%s.flv &gt;&amp;/dev/null' % ( basename, time.strftime('%Y%m%d'), basename, time.strftime('%Y%m%d') ))

            playlistname="playlist-%s.xml" % ( basename )
            playlist=open(playlistname,'w');
            playlist.write("""
            &lt;?xml version="1.0" encoding="utf-8"?&gt;
            &lt;playlist version="1" xmlns="http://xspf.org/ns/0/"&gt;
            &lt;trackList&gt;
            """)

            files=os.listdir(".")
            files.sort()
            repattern="%s.+flv" % ( basename )
            recompiled=re.compile(repattern)
            for file in files:
                if recompiled.search(file):
                    playlist.write("    &lt;track&gt;\n")
                    playlist.write("      &lt;title&gt;%s&lt;/title&gt;\n" % (file))
                    playlist.write("      &lt;creator&gt;%s&lt;/creator&gt;\n" % (flvcreator))
                    playlist.write("      &lt;location&gt;%s&lt;/location&gt;\n" % (file))
                    playlist.write("      &lt;info&gt;force_download.php?file=%s&lt;/info&gt;\n" % (file))
                    playlist.write("    &lt;/track&gt;\n")

            playlist.write("""
            &lt;/trackList&gt;
            &lt;/playlist&gt;
            """)

if __name__ == "__main__":
    main()
</pre>
<p>index.php &#8212; main web interface to the images and movies</p>
<pre>
&lt;?php
  // If I did all this correctly, you shouldn't have to change
  // anything but the basename variable.
$basename = "denso";

$today = date("Ymd"); // e.g., 20070901
$mediabase = $basename."_".$today; // axisgrab.py stores images with this naming scheme
$imagewidth = 480; // Controlled on the camera video/image settings
$imageheight = 360; // Controlled on the camera video/image settings
?&gt;

&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Denso Lab Camera Feed&lt;title&gt;
&lt;script type="text/javascript" src="swfobject.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
var browser = navigator.appName;
var version = parseInt(navigator.appVersion);

function showImage(object) {
  var imageToShow = object.options[object.selectedIndex].value;

  if (browser == 'Netscape') {
    if (version &gt;= 3) document.dropImage.src = imageToShow;
    else              alert('Your browser does not support the image object - sorry');
  }
  else if (browser == 'Microsoft Internet Explorer') {
    if (version &gt;= 4) document.dropImage.src = imageToShow;
    else              frames[0].location.href = imageToShow;
  }
}
//--&gt;&lt;/script&gt;
&lt;meta http-equiv="refresh" content="300"&gt;

&lt;/head&gt;
&lt;body&gt;
&lt;table&gt;
&lt;tr valign="top"&gt;
&lt;td&gt;
&lt;h1&gt;Denso Lab Camera Footage&lt;/h1&gt;

&lt;p&gt;Select a day from the list below to play, or click the icon at the right of the filename to download.&lt;/p&gt;
&lt;p id="player1"&gt;&lt;a href="http://www.macromedia.com/go/getflashplayer"&gt;Get the Flash Player&lt;/a&gt; to see this player.&lt;/p&gt;
&lt;script type="text/javascript"&gt;
   var s1 = new SWFObject("mediaplayer.swf","single","480","480","7");
s1.addParam("allowfullscreen","true");
s1.addParam('allowscriptaccess','always');
s1.addVariable("file","&lt;?php echo "playlist-".$basename.".xml"; ?&gt;");
s1.addVariable('displayheight','360');
s1.write("player1");
&lt;/script&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;h1&gt;Still Images&lt;/h1&gt;
&lt;form name="imageForm"&gt;
&lt;select name="imageSelect"&gt;
&lt;?php
if ($dh = opendir('./')) {
  $files = array();
  while (($file = readdir($dh)) !== false) {
    if (substr($file, strlen($file) - 4) == '.jpg') {
      array_push($files, $file);
    }
  }
  closedir($dh);
}

// Sort the files and display
sort($files);

foreach ($files as $file) {
  echo "&lt;option value=$file&gt;$file&lt;/option&gt;\n";
}
?&gt;
&lt;/select&gt;
&lt;input name="submitName" type="button" value="Show" onClick="showImage(document.imageForm.imageSelect)"&gt;
&lt;/form&gt;

&lt;SCRIPT LANGUAGE="JavaScript"&gt;&lt;!--
if (browser == 'Netscape')
  document.write('&lt;IMG SRC="&lt;?php echo $mediabase."_0000.jpg"; ?&gt;" NAME="dropImage" WIDTH=&lt;?php echo $imagewidth;?&gt; HEIGHT=&lt;?php echo $imageheight;?&gt;&gt;');
 else if (browser == 'Microsoft Internet Explorer') {
   if (version &gt;= 4)
     document.write('&lt;IMG SRC="&lt;?php echo $mediabase."_0000.jpg"; ?&gt;" NAME="dropImage" WIDTH=&lt;?php echo $imagewidth;?&gt; HEIGHT=&lt;?php echo $imageheight;?&gt;&gt;');
    else
      document.write('&lt;IFRAME FRAMEBORDER=0 WIDTH=&lt;?php echo $imagewidth;?&gt; HEIGHT=&lt;?php echo $imageheight
;?&gt; MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING=no SRC="&lt;?php echo $mediabase."_0000.jpg"; ?&gt;"&gt;&lt;/IFRAME&gt;');
 }
//--&gt;&lt;/SCRIPT&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
</pre>
<p>swfobject.js and mediaplayer.swf are part of <a href="http://www.jeroenwijering.com/?item=JW_Media_Player">Jeroen Wijering&#8217;s JW Media Player</a>.</p>
<p>Cron jobs on the webserver (to grab 2 images per minute):</p>
<pre>
* * * * * (cd /path/to/axis/folder &amp;&amp; ./axisgrab.py --grab &amp;&amp; sleep 30 &amp;&amp; ./axisgrab.py --grab)
0,5,10,15,20,25,30,35,40,45,50,55 * * * * (cd /path/to/axis/folder &amp;&amp; ./axisgrab.py --encode)
</pre>
<p>The results:<br />
<a href='http://blogs.cae.tntech.edu/mwr/files/2007/11/axisscreenshot.png' title='axisscreenshot.png'><img src='http://blogs.cae.tntech.edu/mwr/files/2007/11/axisscreenshot.thumbnail.png' alt='axisscreenshot.png' /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2007/11/07/grabbing-stills-and-making-flv-movies-from-axis-ip-cameras/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
