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

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content