#PIoneers team - ESA Astro Pi Mission Space Lab 2020-2021 Phase 2
#Theme: Life on Earth
#---OUR CHOSEN TOPIC---:
#The aim of our project is to analyse terrestial(including forest fires) and aquatic vegetation(severe eutrophication, frequent algal blooms & more).
#Depending on the areas crossed by the ISS while our code is running, one of the 2 types of vegetation may have a higher degree of detail.
#---CODE DESCRIPTION---:
#1. We used Izzy’s NoIR camera with a blue optical filter for taking pictures at (2592x1944) resolution and also saved the current lat & long as EXIF data.
# The sleep time after processing an image is 13 seconds.
#2. We alternated in between 6 different algorithms
# (5 based on picture pixels analysis and one algorithm based exclusively on the position of the ISS compared to the Sun)
# to decide whether to keep or delete the taken picture (we kept the pictures taken at daytime and deleted those at night which cannot be analysed).
# By analysing every keep/delete algorithm accuracy & the time it takes, we will conclude which the best algorithm is
# and further use that one when we participate again in the Astro Pi Mission Space Lab in the years to come.
#3. Our code will produce at most (178*60)/14=762 pictures (in the ideal case where all pictures are kept in short processing time [1 second]) with EXIF data,
# as well as the logfile, and PIoneers_data.csv file, with the following header:
# Date/time, Latitude, Longitude,
# Pressure, Temperature, Humidity,
# Picture brightness, Keep/delete algorithm, Keep/delete period, Picture verdict, Picture number.
import csv
import os
import math
import ephem
import cv2 as cv
from ephem import readtle, degree
from sense_hat import SenseHat
from datetime import datetime, timedelta
from time import sleep
from logzero import logger, logfile
from picamera import PiCamera
#The Python Imaging Library- Pillow (PIL) adds image processing capabilities to our Python main program.
#This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.
from PIL import Image #The Image module provides a class with the same name which is used to represent a PIL image.
#The Image module also provides a number of factory functions, including functions to load images from files, and to create new images.
from PIL import ImageStat #The ImageStat module calculates global statistics for an image, or for a region of an image.
#Recording the starting time (& also current time):
start_time=datetime.now()
current_time=datetime.now()
SIZE_MAX=2900000000 #bytes - maximum size of the files produced by our program, in order not to exceed the 3 GB memory limit
TIME_MAX=178 #minutes - maximum run time, in order not to exceed the 3-hour time limit
TRESHOLD_ALG1=40 #if the brightness is lower than this keeping treshold, the picture is deleted
TRESHOLD_ALG2=40
TRESHOLD_ALG3=40
TRESHOLD_ALG4=40
TRESHOLD_ALG5=40
#Getting the latest location of the ISS - TLE data:
name="ISS (ZARYA)"
line1="1 25544U 98067A 21044.55421846 .00000643 00000-0 19848-4 0 9993"
line2="2 25544 51.6436 234.2256 0002888 7.8795 97.0787 15.48961922269432"
iss=readtle(name, line1, line2) #Preparing to read the location of the ISS
#Saving the current path to main.py:
dir_path=os.path.dirname(os.path.realpath(__file__))
#Setting the logfile:
logfile(dir_path + "/PIoneers.log")
initial_size=0
#Initialising the picture counter:
picture_counter=1
#Setting restrictive custom twilight angle in degrees for day/night detection, which will be used for the 6th keep/delete picture algorithm:
sun=ephem.Sun()
twilight_deg=round(float(0))
#Creating a new csv file and defining its header:
def create_csv(data_file):
with open(data_file, 'w') as f:
writer=csv.writer(f)
header=("Date/time",
"Latitude","Longitude",
"Pressure","Temperature","Humidity",
"Picture brightness","Keep/delete algorithm",
"Keep/delete period","Picture verdict","Picture number")
writer.writerow(header)
#Adding a new line in an existing csv file:
def add_csv_data(data_file, data):
with open(data_file, 'a') as f:
writer=csv.writer(f)
writer.writerow(data)
#Algorithm 1 - Converting the image to greyscale, and working with the average pixel brightness:
def alg1_keep_delete(file):
try:
initial_time=datetime.now()
#When translating a color image to greyscale (mode 'L'), the library uses the ITU-R 601-2 luma transform
#L = R * 299/1000 + G * 587/1000 + B * 114/1000:
picture=Image.open(file).convert('L')
stat=ImageStat.Stat(picture)
brightness1=stat.mean[0] #mean=average (arithmetic mean) pixel level for each band in the image
if brightness1<TRESHOLD_ALG1:
keep_delete1="D"
else:
keep_delete1="K"
final_time=datetime.now()
delta1=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg1_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness1=-1
keep_delete1="D"
delta1=start_time-start_time #workaround so as to return 0 in the required time format
return brightness1, keep_delete1, delta1
#Algorithm 2 - Converting the image to greyscale, and working with the RMS pixel brightness:
def alg2_keep_delete(file):
try:
initial_time=datetime.now()
picture=Image.open(file).convert('L')
stat=ImageStat.Stat(picture)
brightness2=stat.rms[0] #rms=root-mean-square for each band in the image
if brightness2<TRESHOLD_ALG2:
keep_delete2="D"
else:
keep_delete2="K"
final_time=datetime.now()
delta2=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg2_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness2=-1
keep_delete2="D"
delta2=start_time-start_time #workaround so as to return 0 in the required time format
return brightness2, keep_delete2, delta2
#Algorithm 3 - Based on average pixels, then transforming to "perceived brightness":
def alg3_keep_delete(file):
try:
initial_time=datetime.now()
picture=Image.open(file)
stat=ImageStat.Stat(picture)
r, g, b=stat.mean
brightness3=math.sqrt(0.241*(r**2) + 0.691*(g**2) + 0.068*(b**2))
if brightness3<TRESHOLD_ALG3:
keep_delete3="D"
else:
keep_delete3="K"
final_time=datetime.now()
delta3=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg3_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness3=-1
keep_delete3="D"
delta3=start_time-start_time #workaround so as to return 0 in the required time format
return brightness3, keep_delete3, delta3
#Algorithm 4 - Based on the RMS of pixels, then transforming to "perceived brightness":
def alg4_keep_delete(file):
try:
initial_time=datetime.now()
picture=Image.open(file)
stat=ImageStat.Stat(picture)
r, g, b=stat.rms
brightness4=math.sqrt(0.241*(r**2) + 0.691*(g**2) + 0.068*(b**2))
if brightness4<TRESHOLD_ALG4:
keep_delete4="D"
else:
keep_delete4="K"
final_time=datetime.now()
delta4=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg4_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness4=-1
keep_delete4="D"
delta4=start_time-start_time #workaround so as to return 0 in the required time format
return brightness4, keep_delete4, delta4
#Algorithm 5 - Reference algorithm based by analysing 1 in 4 pixels, inspired by 2020 Team Reforesting-Entrepreneurs:
def alg5_keep_delete(file, height, width):
try:
initial_time=datetime.now()
file1=cv.imread(file)
B_total=0
R_total=0
G_total=0
#Totalling BGR values of every pixel:
for line in range(0, height, 4):
for column in range(0, width, 4):
RGB=list(file1[line, column])
B_total+=RGB[0]
G_total+=RGB[1]
R_total+=RGB[2]
B_avg=B_total/((width*height)/16)
G_avg=G_total/((width*height)/16)
R_avg=R_total/((width*height)/16)
#Translating the individual BGR values to a unified greyscale value:
brightness5=(B_avg+G_avg+R_avg)/3
if brightness5<TRESHOLD_ALG5:
keep_delete5="D"
else:
keep_delete5="K"
final_time=datetime.now()
delta5=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg5_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness5=-1
keep_delete5="D"
delta5=start_time-start_time #workaround so as to return 0 in the required time format
return brightness5, keep_delete5, delta5
#Algorithm 6 - Based exclusively on the position of the ISS from the Sun (not on the picture):
def alg6_keep_delete():
try:
initial_time=datetime.now()
iss.compute()
observer=ephem.Observer() #setting a Sun observer with ISS coordinates and zero elevation
observer.lat, observer.long, observer.elevation=iss.sublat, iss.sublong, 0
sun.compute(observer)
sun_angle_deg=float(round(math.degrees(sun.alt),6)) #Sun altitude - degrees only, rounded to 6 decimals
if sun_angle_deg>twilight_deg:
keep_delete6="K"
brightness6=255 #covention so as to return a value for brightness (255=day/keep)
else:
keep_delete6="D"
brightness6=0 #covention so as to return a value for brightness (0=night/delete)
final_time=datetime.now()
delta6=final_time-initial_time
#If an error occurs, the picture is deleted in the main program
#and the brightness and delta are set on -1, respectively 0
#(conventional error values):
except Exception as e:
logger.error(("alg6_keep_delete function error: {}: {}").format(e.__class__.__name__, e))
brightness6=-1
keep_delete6="D"
delta6=start_time-start_time #workaround so as to return 0 in the required time format
return brightness6, keep_delete6, delta6
#Calculating the used memory space so as not to exceed 3 GB:
def used_memory_space(initial_path="."): #i_size=the initial size
try:
size=0
for path, folders, files in os.walk(initial_path):
for f in files:
fp=os.path.join(path, f)
size+=os.path.getsize(fp)
return size
except Exception as e:
logger.error('{}: {})'.format(e.__class__.__name__, e))
return initial_size+(picture_counter-1)*3500000
#Converting an ephem angle(degrees, minutes, seconds) to an EXIF-appropriate representation(rationals)
#For instance, '51:35:19.7' is converted to '51/1,35/1,197/10':
def convert(angle):
degrees, minutes, seconds = (float(field) for field in str(angle).split(":"))
exif_angle = f'{abs(degrees):.0f}/1,{minutes:.0f}/1,{seconds*10:.0f}/10'
return degrees<0, exif_angle #the first returned value(boolean) is 0 if the angle is negative, and 1 otherwise
#Using the camera to capture a picture file with lat/long EXIF data:
def take_photo_lat_long(camera, image):
#Getting the lat/long values from ephem:
iss.compute()
#Converting the latitude and longitude to EXIF-appropriate representations:
south, exif_latitude = convert(iss.sublat)
west, exif_longitude = convert(iss.sublong)
#Setting the EXIF tags specifying the current location:
camera.exif_tags['GPS.GPSLatitude'] = exif_latitude
camera.exif_tags['GPS.GPSLatitudeRef'] = "S" if south else "N"
camera.exif_tags['GPS.GPSLongitude'] = exif_longitude
camera.exif_tags['GPS.GPSLongitudeRef'] = "W" if west else "E"
#Capturing the image:
camera.capture(image)
#Returning the current latitude and longitude in degrees:
return iss.sublat/degree, iss.sublong/degree
#Used memory before the program starts
#=>The space used by the program at a certain time will be the current size minus the initial size:
initial_size=used_memory_space(dir_path)
#Using the Sense HAT set of environmental sensors in order to monitor the surrounding conditions
#(pressure, temperature, and humidity) inside the Columbus Module:
sense=SenseHat()
create_csv("PIoneers_data.csv")
#Setting up the camera:
cam=PiCamera()
cam.resolution=(2592, 1944)
#Starting out with the first keep/delete algorithm:
used_algorithm=1
#Running a loop for approximately 3 hours
#and checking that the size of the produced data does not exceed 3 GB:
while(current_time<start_time+timedelta(minutes=TIME_MAX) and used_memory_space(dir_path)-initial_size<SIZE_MAX):
try:
#Taking a picture with EXIF lat & long
#And recording the current lat & long from ephem library, in degrees,
#coordinates which will further be added to the csv file:
picture_file= dir_path + "/PIoneers_picture_" + str(picture_counter).zfill(4) + ".jpg"
lat, long=take_photo_lat_long(cam, picture_file)
if used_algorithm==1:
brightness, keep_delete, delta=alg1_keep_delete(picture_file)
elif used_algorithm==2:
brightness, keep_delete, delta=alg2_keep_delete(picture_file)
elif used_algorithm==3:
brightness, keep_delete, delta=alg3_keep_delete(picture_file)
elif used_algorithm==4:
brightness, keep_delete, delta=alg4_keep_delete(picture_file)
elif used_algorithm==5:
brightness, keep_delete, delta=alg5_keep_delete(picture_file, 1944, 2592)
elif used_algorithm==6:
brightness, keep_delete, delta=alg6_keep_delete()
if keep_delete=="D":
picture_number=0 #the picture was deleted and does not exist
else:
picture_number=picture_counter
#Adding the data to the csv file:
row=(datetime.now(),
lat, long,
sense.pressure, sense.temperature, sense.humidity,
brightness, used_algorithm,
delta, keep_delete, picture_number
)
add_csv_data("PIoneers_data.csv", row)
if keep_delete=="K":
picture_counter=picture_counter+1 #keeping the picture, thus increasing the counter
else:
os.remove(picture_file) #deleting the picture
used_algorithm=used_algorithm+1
if used_algorithm==7:
used_algorithm=1
sleep(13)
#Updating the current time:
current_time=datetime.now()
except Exception as e:
logger.error('{}: {})'.format(e.__class__.__name__, e))
%d bloggers like this: