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 frompyroServer.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.
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 addressestime
– 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
(incl. VAT)