Use Python to retrieve and display images from space

Software

Four scripts make up the sun display system (Figure 3). The standalone script is not interactive, whereas the other three scripts work together to provide an interactive version of the sun displays. You can find all of the listings online [4]:

  • sunDisplayNTP.py – The standalone version of the script fetches its own updates on a user-defined interval and doesn't allow for any external control. The channel to view is defined at the top of the script.
  • download.py – A list of RSS feeds represents different channels available from SDO. It sits in a loop and checks each file name provided by the feed. If the file already exists on the disk, then it is skipped. Once an RSS feed has been processed completely, the program moves to the next one in the list. When the entire list has been processed, it starts again from the top.
  • pyroServer.py – Listens for requests from the sun displays. Clients can ask for a list of channels, a list of images available from each channel, and a particular image to be sent. These are all based on the files fetched by the download script.
  • pyroClient.py – Runs the display. When this script starts, it will request the first channel available and start fetching its images. Once the number of requested images has been downloaded, it starts looping them on the display. A separate thread runs a simple web interface that shows the channels available from pyroServer.py. A web browser can select the channel name, which will start a new download of those images.

Pyro. Additionally, I use Pyro (Python Remote Objects) so the different scripts can talk to each other. With Pyro, I define a set of remotely available functions in a class. Then, I tell Pyro which class I want to be accessible remotely. Pyro handles all of the networking in the background. In the client script, I provide the URL of the Pyro server and it returns an instance of the class to use. As far as the program is concerned, it's a local function, but it actually runs on the server machine.

CherryPy. CherryPy turns a Python class into a web server. Calls that come back from the web server can call other Python functions and return customized HTML responses. CherryPy is especially useful if you want Python to be a back end for your Ajax projects. The museum displays use CherryPy so that a tablet can be used to control which channel of data is currently being shown.

Figure 3: The scripts for the interactive display and how they interact.

sunDisplayNTP.py

The first lines of the sunDisplayNTP.py script (Listing 1) handles imports, which are Python's way of bringing in external modules and libraries. For the sun displays, the script imports the following:

  • datetime – Supplies functions to manipulate dates and times.
  • os – Has functions for interacting with whatever OS the program is running under. Although this library is cross-platform, some functions are only available when running on a specific OS.
  • pygame – Provides the drawing and image manipulation functions to draw the graphical display.
  • StringIO – Accesses strings as if they are files.
  • urllib – Fetches resources that have Internet addresses
  • time – Provides functions specific to times of day, lengths of time, and adding and subtracting between times, to name a few.
  • ntplib – Accesses NTP servers, specifically to acquire the US official time.

Listing 1

sunDisplayNTP.py

001 import datetime
002 import os
003 import pygame
004 import StringIO
005 import urllib
006 import time
007 import ntplib
008
009 class imageDownload:
010   def __init__ ( self , screen ):
011     pygame.font.init()
012     self.museumFont = pygame.font.Font \
        ( "fontsGotham-Book.otf" , 20 )
013
014     self.screen = screen
015     self.images = list()
016     self.imageNames = list()
017     self.downloadCount = 0
018     self.logo = pygame.image.load ( "LogoWhiteText200.png" \
        ).convert_alpha()
019     self.standby = pygame.image.load ( "standby.jpg" \
        ).convert()
020
021     self.screen.blit ( self.logo , ( screen.get_width() - \
        self.logo.get_width() - 25 , screen.get_height() - \
        self.logo.get_height() - 25 ) )
022     self.text = "The AIA composite image combines three \
        camera images to highlight different portions of the \
        sun's corona. Solar flares are often visible in this \
        image which is obtained in near-real-time, within 10 \
        minutes of downlink from the Solar Dynamic Observatory \
        currently orbiting the Sun"
023     today = datetime.date.today()
024     self.oldDate = time.localtime()
025
026   def text ( self , txt ):
027     box = pygame.surface.Surface ( ( 200 , 600 ) )
028     textParts = txt.split ( " " )
029     chunk = ""
030     y = 25
031     for word in textParts:
032       size = self.museumFont.size ( chunk + " " + word )
033       if size [ 0 ] < box.get_width():
034         chunk += word + " "
035       else:
036         lineSurf = self.museumFont.render ( chunk , True , \
            ( 250 , 250 , 250 ) )
037         box.blit ( lineSurf , ( box.get_width() / 2 - \
            lineSurf.get_width() / 2 , y ) )
038         chunk = word + " "
039         y += 25
040
041     lineSurf = self.museumFont.render ( chunk , True , \
        ( 250 , 250 , 250 ) )
042     box.blit ( lineSurf , ( box.get_width() / 2 - \
        lineSurf.get_width() / 2 , y ) )
043
044     self.screen.blit ( box , ( 800 , 25 ) )
045     pygame.display.flip()
046
047   def download ( self ):
048     c = ntplib.NTPClient()
049     today = time.localtime()
050     while 1:
051       try:
052         response = c.request('time.nist.gov', version=3)
053         today = time.gmtime ( response.tx_time )
054         if today != None:
055           break
056         else:
057           time.sleep ( 5 )
058       except:
059         today = self.oldDate
060
061     self.baseAddress = r"http://sdo.gsfc.nasa.gov\
        /assets/img/browse/{0}/{1:02d}/{2:02d}".format ( \
        today.tm_year , today.tm_mon , today.tm_mday )
062
063     filter = "HMIBC.jpg"
064
065     if self.oldDate.tm_mday != today.tm_mday:
066       self.images = list()
067       self.imageNames = list()
068       self.oldDate = today
069
070     while 1:
071       try:
072         fileList = urllib.urlopen ( self.baseAddress ).read()
073         break
074       except:
075         time.sleep ( 5 )
076
077     fileList = fileList.split ( "<tr>" )
078
079     for line in fileList:
080       if "<a href" in line:
081         index = line.index ( "<a" )
082         filename = line [ index: ].split ( "\"" ) [ 1 ]
083         if filter in filename and "1024" in filename:
084           print "found a new image"
085           if filename not in self.imageNames:
086             print "downloading " + filename
087             square = pygame.surface.Surface ( ( 768 , 768 ) )
088             try:
089               f = StringIO.StringIO(urllib.urlopen\
                  ( self.baseAddress + "/" + filename ).read())
090               img = pygame.image.load \
                  ( f , "a.JPG" ).convert()
091               pygame.transform.scale ( img , ( 768 , 768 ) \
                  , square )
092               self.images.append ( square )
093               self.imageNames.append ( filename )
094             except:
095               pass
096           else:
097             print "all files downloaded"
098     print len ( self.images )
099     return
100
101   def slides ( self ):
102     imgIndex = 0
103     pygame.time.set_timer ( pygame.USEREVENT , 100 )
104     downloadCounter = 0
105
106     while 1:
107       for event in pygame.event.get():
108         if event.type == pygame.USEREVENT:
109           if ( len ( self.images ) - 1 ) > imgIndex:
110             self.screen.blit ( self.images [ imgIndex ] , \
                ( 0 , 0 ) )
111             pygame.display.flip()
112             imgIndex += 1
113             if imgIndex == ( len ( self.images ) - 1 ): \
                imgIndex = 0
114         downloadCounter += 1
115         if downloadCounter == 3000 or len \
            ( self.images ) == 0:
116           self.screen.blit ( self.standby , ( 0 , 0 ) )
117           pygame.display.flip()
118           self.download()
119           imgIndex = 0
120           downloadCounter = 0
121         if event.type == pygame.KEYDOWN:
122           pygame.quit()
123           break
124
125 pygame.display.init()
126 screen = pygame.display.set_mode ( ( 1024 , 768 ) )
127
128 sun = imageDownload ( screen )
129 sun.slides()

The imageDownload class (lines 9-123) contains all of the functions for the sun display. The first function sets up the text font (lines 11-12), initializes variables (lines 14-19), and draws a PyGame surface onto the display with self.screen.blit (line 21). The first argument, self.logo, is the surface to be drawn. The second argument is a tuple for the x and y position to start drawing the image.

The number returned in screen.get_width is the width of the screen in pixels. If I were to stop there, the left edge of the image would line up on the right edge of the screen and be out of view. By subtracting get_width of self.logo, I shift the position of the image by its size. Now the image will appear against the right edge of the screen. Finally, I subtract 25 to move it over 25 more pixels to add a little bit of margin. The second part of the tuple works exactly the same way with height.

Lines 23 and 24 define today and a date adjusted for the local time zone, and the text function (lines 26-45) accepts txt as an argument and creates a surface, with txt broken up into multiple lines as needed to fit on the side of the screen.

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