Use Python to retrieve and display images from space
download.py
The job of the download.py
script (Listing 2) is to download new images from SDO as they become available. The first five imports are the same modules as in the standalone program already described. New here is xml.etree.ElementTree
, which is one of Python's libraries for parsing XML documents. RSS feeds are in an XML format, so I rely on the feed to tell me when a new image is available.
Listing 2
download.py
01 import os 02 import pygame 03 import StringIO 04 import urllib 05 import thread 06 import xml.etree.ElementTree as ET 07 08 class imageDownload: 09 def __init__ ( self ): 10 self.feeds = list() 11 self.feeds.append \ ( "http://feeds.feedburner.com/nasa/aia_094" ) 12 self.feeds.append \ ( "http://feeds.feedburner.com/nasa/aia_131" ) 13 self.feeds.append \ ( "http://feeds.feedburner.com/nasa/aia_171" ) 14 15 16 def download ( self ): 17 for feed in self.feeds: 18 fileList = urllib.urlopen ( feed ).read() 19 root = ET.fromstring ( fileList ) 20 imagePath = "images/" + feed.split ( "/" ) [ -1 ] + "/" 21 dirList = os.listdir ( imagePath ) 22 23 for channel in root.findall ( "channel" ): 24 title = channel.find ( "title" ).text 25 print title 26 for item in channel.findall ( "item" ): 27 for img in item.findall ( "guid" ): 28 if os.path.basename ( img.text ) in dirList: 29 print "already have it" 30 continue 31 32 print "downloading " + img.text 33 f = StringIO.StringIO(urllib.urlopen\ ( img.text ).read()) 34 sun = pygame.image.load ( f , "a.JPG" ) 35 sun = pygame.transform.scale \ ( sun , ( 768 , 768 ) ) 36 pygame.image.save \ ( sun , imagePath + os.path.basename ( img.text ) ) 37 38 sun = imageDownload() 39 while 1: 40 sun.download() 41 time.sleep ( 10 )
Unlike the standalone version, here the imageDownload
class is just downloading files. There's no graphical element to it. I do use PyGame here, but only to process image files; they are never drawn to the screen.
In the init
class, I create a list of feeds that I want to download. self.feeds
is initialized as a list (line 10) and then feeds are appended as needed. Next, the download
function gets the RSS feed, looks for image links, and downloads them. The first step is to get the RSS feed. That happens on line 18 using urllib
, just as in sunDisplayNTP.py
. Instead of stepping through the feed as text, however, I'm going to interpret the XML. To do that I use ET.fromstring
to pass the downloaded text into the XML parser.
Line 20 sets up imagePath
, which is where I save each image that I download. I use the channel name (the last part of the feed address) as a directory name. Line 21 gets a list of the files that are already there so I know what I've already downloaded.
Starting on line 23, the script steps through the XML file and searches for the "title"
tag, returning whatever is between the opening and closing tags. Lines 26 and 27 search deeper into the XML for "item"
and "guid"
. Remember that XML is a tree format, so starting with channel
or item
only searches inside that branch.
On line 28, I've found an image, so I check its file name to see if it's one I already have (i.e., is it in dirList
that I created on line 21?). If so, I skip it; otherwise, I download it. With StringIO
, I move the file through memory rather than writing it to disk to end up with a PyGame surface. Then, line 35 resizes the surface to 768x768 so it's ready for display. Line 36 saves the resized image to disk.
Line 38 creates an instance of the class, and then line 39 starts an infinite loop to process all the downloads before waiting 10 seconds and looping again. That way, I don't overload the server with a lot of requests before an update is available.
pyroServer.py
The pyroServer.py
script (Listing 3) accesses the directory populated by download.py
to provide information and images to the sun clients (the displays in the museum). The script uses four libraries: Pyro4
, os
, pygame
, and pickle
. I've already looked at os
and pygame
, so I'll focus on Pyro4
and pickle
. Pyro4 is the remote objects library I discussed earlier, which will make my class visible on the network.
Listing 3
pyroServer.py
01 import Pyro4 02 import os 03 import pygame 04 import cPickle as pickle 05 06 class sunImages: 07 def channelList ( self ): 08 channels = list() 09 directory = os.listdir ( "images" ) 10 for name in directory: 11 if os.path.isdir ( "images/" + name ): 12 channels.append ( name ) 13 return channels 14 15 def fileList ( self , channel ): 16 package = dict() 17 package [ "files" ] = sorted \ ( os.listdir ( "images/" + channel ) ) 18 package [ "count" ] = len ( package [ "files" ] ) 19 20 return package 21 22 def getFile ( self , channel , filename ): 23 package = dict() 24 25 package [ 'sun' ] = pygame.image.tostring \ ( pygame.image.load ( "images/" + channel + "/" + \ filename ) , "RGB" ) 26 img = pickle.dumps ( package [ 'sun' ] ) 27 28 return img 29 30 31 rem = sunImages() 32 daemon = Pyro4.Daemon() 33 url = daemon.register ( rem ) 34 print url 35 daemon.requestLoop()
pickle
is a serializer class. Its job is to take objects and convert them into a pure text form that can be sent over the network or saved to files without data loss. Here I say cPickle as pickle
. Although pickle
is a valid Python module, cPickle
uses a compiled version that runs much faster. The interface is the same.
Each function in the sunImages
class (lines 6-28) will be accessible on the network via the Pyro URL. I'll find out what that is at the end of the script. Next, channelList
scans the images
directory and makes a list of all of the subdirectories. Each subdirectory is a channel available to be viewed. To start, I initialize channels
as a list then get the directory listing.
A loop looks at each object in the directory and makes sure it is actually a directory; if so, it adds it to the channel list. With return channels
, Pyro sends the data across the network back to whoever requested it.
The fileList
function (lines 15-20) returns file names and the number of files in each directory. I create a dictionary to hold everything then add a directory listing, sorted by time, and the number of files in the directory. The dictionary is sent on its way with return package
.
Next, the getFile
function (lines 22-28) sends an image to the requestor. Again, I start by creating a dictionary. Line 25 has a couple of nested functions (line 25): pygame.image.load
loads the image from the disk. The channel
and filename
arguments are sourced from the lists returned from channelList
and fileList
. Next, pygame.image.tostring
converts the image to a string so it can be transmitted over the network. The "RGB"
at the end tells tostring
what format to use when creating the string.
When I originally wrote the server, I was intending to send more data in the dictionary than just the image itself. In that case I would have used pickle
to serialize the entire dictionary, but because I don't have anything else to send, I only pickle
the one element in line 26.
Now that the remote functions are defined, I can set up Pyro to make them available (lines 31-35). I start by creating an instance of my class on line 31; then, I create a daemon. Using daemon.register
allows me to pass the class instance rem
to the daemon to be network accessible. Here, url
on the front is the address I'll use to access it. I print the URL and then start the request loop, which will run indefinitely. It's listening for commands, running them locally, and returning the results.
Buy this article as PDF
(incl. VAT)