observational science with python and a webcam

115
Observational Science With Python And a Webcam By: Eric Floehr Observational Science With Python and a Webcam by Eric Floehr is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Upload: intellovations-llc

Post on 25-May-2015

167 views

Category:

Technology


2 download

DESCRIPTION

This talk is a "how I did it" talk about how I took an idea, a web cam, Python, Django, and the Python Imaging Library and created art, explored science, and illustrated concepts that our ancestors knew by watching the sky but we have lost.

TRANSCRIPT

Page 1: Observational Science With Python and a Webcam

Observational ScienceWith Python

And a Webcam

By: Eric Floehr

Observational Science With Python and a Webcam by Eric Floehr is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Page 2: Observational Science With Python and a Webcam

So I had a webcam...

Page 3: Observational Science With Python and a Webcam

and I made a time-lapse video.

http://bit.ly/ospw-1

Page 4: Observational Science With Python and a Webcam

I wanted to do better.

Longer

Inside location

Automated

Once a minute

Page 5: Observational Science With Python and a Webcam

So this is what I did.

Second floor window

Little-used room

Pointing west

Pull pictures down with cronjob

That runs once per minute

Page 6: Observational Science With Python and a Webcam

And it looks like this.

Page 7: Observational Science With Python and a Webcam

Two years later...

Page 8: Observational Science With Python and a Webcam

I have...

● 896,309 image files● 11,809,972,880 bytes● 10.9989 gigabytes● From 2:14pm on August 29, 2010● To 4:11pm on July 25, 2012● Still going...

Page 9: Observational Science With Python and a Webcam

Tenet #1:

Don't be afraid of big data.

Page 10: Observational Science With Python and a Webcam

What can we do?

● Moar Time-lapse!● Explore phenomenon that occurs

over long periods● Unique visualizations● Have lots of fun!

Page 11: Observational Science With Python and a Webcam

First...

We need to organize the data.

Page 12: Observational Science With Python and a Webcam

Database!

Page 13: Observational Science With Python and a Webcam

How will we access the data?Let's use Django!

Page 14: Observational Science With Python and a Webcam

Why Django?

● It has a good ORM● It has a command framework● Makes extending to the web later

easy● I already am familiar with it● Django isn't just for web :-)

Page 15: Observational Science With Python and a Webcam

Quick setup

● Create your virtualenv (with site packages)● PIL is a pain to compile● Install Django● django-admin.py startproject● Edit settings.py● createdb pics● django-admin.py startapp pics● django-admin.py syncdb

Page 16: Observational Science With Python and a Webcam

Tenet #2:

Don't let small things keep you from your big picture.

Page 17: Observational Science With Python and a Webcam

What do we need to store?

● Image file location and filename● Time the image was taken● Interesting information about the

image● Is it a valid image?

Page 18: Observational Science With Python and a Webcam

class Picture(models.Model):

filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024)

timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, don't manually enter hour = models.SmallIntegerField(null=True, db_index=True) minute = models.SmallIntegerField(null=True, db_index=True) file_hour = models.SmallIntegerField(null=True) file_minute = models.SmallIntegerField(null=True) # End auto-populated fields

size = models.IntegerField(default=0) center_color = models.IntegerField(null=True) mean_color = models.IntegerField(null=True) median_color = models.IntegerField(null=True)

stddev_red = models.IntegerField(null=True) stddev_green = models.IntegerField(null=True) stddev_blue = models.IntegerField(null=True)

min_color = models.IntegerField(null=True) max_color = models.IntegerField(null=True) valid = models.BooleanField(default=False)

class Meta: ordering = ['timestamp']

Page 19: Observational Science With Python and a Webcam

class Picture(models.Model):

filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024)

timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, don't manually enter hour = models.SmallIntegerField(null=True, db_index=True) minute = models.SmallIntegerField(null=True, db_index=True) file_hour = models.SmallIntegerField(null=True) file_minute = models.SmallIntegerField(null=True) # End auto-populated fields

size = models.IntegerField(default=0) center_color = models.IntegerField(null=True) mean_color = models.IntegerField(null=True) median_color = models.IntegerField(null=True)

stddev_red = models.IntegerField(null=True) stddev_green = models.IntegerField(null=True) stddev_blue = models.IntegerField(null=True)

min_color = models.IntegerField(null=True) max_color = models.IntegerField(null=True) valid = models.BooleanField(default=False)

class Meta: ordering = ['timestamp']

<-- File location and name

Page 20: Observational Science With Python and a Webcam

class Picture(models.Model):

filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024)

timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, don't manually enter hour = models.SmallIntegerField(null=True, db_index=True) minute = models.SmallIntegerField(null=True, db_index=True) file_hour = models.SmallIntegerField(null=True) file_minute = models.SmallIntegerField(null=True) # End auto-populated fields

size = models.IntegerField(default=0) center_color = models.IntegerField(null=True) mean_color = models.IntegerField(null=True) median_color = models.IntegerField(null=True)

stddev_red = models.IntegerField(null=True) stddev_green = models.IntegerField(null=True) stddev_blue = models.IntegerField(null=True)

min_color = models.IntegerField(null=True) max_color = models.IntegerField(null=True) valid = models.BooleanField(default=False)

class Meta: ordering = ['timestamp']

<-- Image Timestamp

Page 21: Observational Science With Python and a Webcam

class Picture(models.Model):

filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024)

timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, don't manually enter hour = models.SmallIntegerField(null=True, db_index=True) minute = models.SmallIntegerField(null=True, db_index=True) file_hour = models.SmallIntegerField(null=True) file_minute = models.SmallIntegerField(null=True) # End auto-populated fields

size = models.IntegerField(default=0) center_color = models.IntegerField(null=True) mean_color = models.IntegerField(null=True) median_color = models.IntegerField(null=True)

stddev_red = models.IntegerField(null=True) stddev_green = models.IntegerField(null=True) stddev_blue = models.IntegerField(null=True)

min_color = models.IntegerField(null=True) max_color = models.IntegerField(null=True) valid = models.BooleanField(default=False)

class Meta: ordering = ['timestamp']

<-- Interesting image data

Page 22: Observational Science With Python and a Webcam

class Picture(models.Model):

filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024)

timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, don't manually enter hour = models.SmallIntegerField(null=True, db_index=True) minute = models.SmallIntegerField(null=True, db_index=True) file_hour = models.SmallIntegerField(null=True) file_minute = models.SmallIntegerField(null=True) # End auto-populated fields

size = models.IntegerField(default=0) center_color = models.IntegerField(null=True) mean_color = models.IntegerField(null=True) median_color = models.IntegerField(null=True)

stddev_red = models.IntegerField(null=True) stddev_green = models.IntegerField(null=True) stddev_blue = models.IntegerField(null=True)

min_color = models.IntegerField(null=True) max_color = models.IntegerField(null=True) valid = models.BooleanField(default=False)

class Meta: ordering = ['timestamp']

<-- Is it valid? How do we order?

Page 23: Observational Science With Python and a Webcam

Loading the Data

Page 24: Observational Science With Python and a Webcam

How do we populate the table?

● Iterate through directories of image files

● Parse the file name to get timestamp

● Get file timestamp to compare● Load image to collect information

about the image

Page 25: Observational Science With Python and a Webcam

Find the image filesimport os

import fnmatch

import datetime

for dirpath, dirs, files in os.walk(directory):

for f in sorted(fnmatch.filter(files, 'image-*.jpg')):

# Pull out timestamp

timestamp = f.split('.')[0].split('-')[1]

date = datetime.datetime.fromtimestamp(int(timestamp), utc)

Page 26: Observational Science With Python and a Webcam

Find the image filesimport os

import fnmatch

import datetime

for dirpath, dirs, files in os.walk(directory):

for f in sorted(fnmatch.filter(files, 'image-*.jpg')):

# Pull out timestamp

timestamp = f.split('.')[0].split('-')[1]

date = datetime.datetime.fromtimestamp(int(timestamp), utc)

Page 27: Observational Science With Python and a Webcam

Find the image filesimport os

import fnmatch

import datetime

for dirpath, dirs, files in os.walk(directory):

for f in sorted(fnmatch.filter(files, 'image-*.jpg')):

# Pull out timestamp

timestamp = f.split('.')[0].split('-')[1]

date = datetime.datetime.fromtimestamp(int(timestamp), utc)

Page 28: Observational Science With Python and a Webcam

Find the image filesimport os

import fnmatch

import datetime

for dirpath, dirs, files in os.walk(directory):

for f in sorted(fnmatch.filter(files, 'image-*.jpg')):

# Pull out timestamp

timestamp = f.split('.')[0].split('-')[1]

date = datetime.datetime.fromtimestamp(int(timestamp), utc)

Page 29: Observational Science With Python and a Webcam

Find the image filesimport os

import fnmatch

import datetime

for dirpath, dirs, files in os.walk(directory):

for f in sorted(fnmatch.filter(files, 'image-*.jpg')):

# Pull out timestamp

timestamp = f.split('.')[0].split('-')[1]

date = datetime.datetime.fromtimestamp(int(timestamp), utc)

Page 30: Observational Science With Python and a Webcam

Use PIL to get image infoimport Image, ImageStat

From util.color import rgb_to_int

try:

im = Image.open(pic.filepath)

stats = ImageStat.Stat(im)

pic.center_color = rgb_to_int(im.getpixel((320,240)))

pic.mean_color = rgb_to_int(stats.mean)

pic.median_color = rgb_to_int(stats.median)

if im.size <> (640,480):

pic.valid = False

except:

pic.valid = False

Page 31: Observational Science With Python and a Webcam

Use PIL to get image infoimport Image, ImageStat

From util.color import rgb_to_int

try:

im = Image.open(pic.filepath)

stats = ImageStat.Stat(im)

pic.center_color = rgb_to_int(im.getpixel((320,240)))

pic.mean_color = rgb_to_int(stats.mean)

pic.median_color = rgb_to_int(stats.median)

if im.size <> (640,480):

pic.valid = False

except:

pic.valid = False

Page 32: Observational Science With Python and a Webcam

Use PIL to get image infoimport Image, ImageStat

From util.color import rgb_to_int

try:

im = Image.open(pic.filepath)

stats = ImageStat.Stat(im)

pic.center_color = rgb_to_int(im.getpixel((320,240)))

pic.mean_color = rgb_to_int(stats.mean)

pic.median_color = rgb_to_int(stats.median)

if im.size <> (640,480):

pic.valid = False

except:

pic.valid = False

Page 33: Observational Science With Python and a Webcam

Use PIL to get image infoimport Image, ImageStat

From util.color import rgb_to_int

try:

im = Image.open(pic.filepath)

stats = ImageStat.Stat(im)

pic.center_color = rgb_to_int(im.getpixel((320,240)))

pic.mean_color = rgb_to_int(stats.mean)

pic.median_color = rgb_to_int(stats.median)

if im.size <> (640,480):

pic.valid = False

except:

pic.valid = False

Page 34: Observational Science With Python and a Webcam

It took a an hour or two using six threads on my desktop

Page 35: Observational Science With Python and a Webcam

Tenet #3:

You have a 1990's supercomputer on your lap or

under your desk.Use it!

Page 36: Observational Science With Python and a Webcam

Let's Explore the Data

Page 37: Observational Science With Python and a Webcam

Size Indicates Complexity in jpegpics=# select timestamp, filepath, size from pics_picture where size=(select max(size) from pics_picture);

timestamp | filepath | size ------------------------+--------------------------------------+------- 2011-10-10 18:26:01-04 | /data/pics/1318/image-1318285561.jpg | 64016

http://bit.ly/ospw-2

Page 38: Observational Science With Python and a Webcam

High Stddev Means Color Variationpics=# select timestamp, filepath, size from pics_picture where stddev_red+stddev_green+stddev_blue = (select max(stddev_red+stddev_green+stddev_blue) from pics_picture);

Timestamp | filepath | size ------------------------+--------------------------------------+------- 2011-09-29 17:20:01-04 | /data/pics/1317/image-1317331201.jpg | 22512

http://bit.ly/ospw-3

Page 39: Observational Science With Python and a Webcam

About the Datasetpics=# select min(timestamp) as start, max(timestamp) as end from pics_picture;

start | end

------------------------+------------------------

2010-08-29 14:14:01-04 | 2012-07-25 16:11:01-04

(1 row)

pics=# select count(*), sum(case when valid=false then 1 else 0 end) as invalid from pics_picture;

count | invalid

--------+---------

896309 | 29555

(1 row)

pics=# select timestamp, filepath, size from pics_picture where size>0 and valid=false order by size desc limit 1;

timestamp | filepath | size

------------------------+--------------------------------------+-------

2012-04-25 08:16:01-04 | /data/pics/1335/image-1335356161.jpg | 39287

(1 row)

http://bit.ly/ospw-4

Page 40: Observational Science With Python and a Webcam

Yuck! This Data Sucks!

● 29,555 invalid image files● That's 3.3% of all image files● Worse yet, there isn't a file every minute● Based on start and end times, we should

have 1,002,358 images● We are missing 10.6% of all minutes

between start and end times

Page 41: Observational Science With Python and a Webcam

It's Worse...I Forgot The Bolts!

http://bit.ly/ospw-5

http://bit.ly/ospw-6

http://bit.ly/ospw-7

Page 42: Observational Science With Python and a Webcam

* Interestingly, I was listening to the Serious Business remix of “All Is Not Lost” by OK Go from the Humble Music Bundle as I made the previous slide.

Page 43: Observational Science With Python and a Webcam

Tenet #4:

Real world data is messy.That's ok. Just work around it.

Page 44: Observational Science With Python and a Webcam

How we can work around it?

● Create table of all minutes● Accept images “near” missing

minutes● Use empty images when

acceptable images can't be found

Page 45: Observational Science With Python and a Webcam

Let's Make Time-lapse Movies

Page 46: Observational Science With Python and a Webcam

How do we make movies?

● Collect a set of images in frame order.

● Send that list of images to a movie maker.

● Wait for movie to be made.● Watch movie!

Page 47: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezone

ims = ImageSequence()

ettz = timezone('US/Eastern')

start = datetime.datetime(2011,4,7).replace(tzinfo=ettz)end = start + datetime.timedelta(days=1)

pics = NormalizedPicture.objects.filter(timestamp__gte=start, timestamp__lt=end)

ims = ImageSequence()

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) else: ims.add_image('/tmp/no_image.png')

ims.make_timelapse('/tmp/{0}'.format(start.strftime("%Y%m%d")), fps=24)

The previous bullet points in code

Page 48: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezone

ims = ImageSequence()

ettz = timezone('US/Eastern')

start = datetime.datetime(2011,4,7).replace(tzinfo=ettz)end = start + datetime.timedelta(days=1)

pics = NormalizedPicture.objects.filter(timestamp__gte=start, timestamp__lt=end)

ims = ImageSequence()

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) else: ims.add_image('/tmp/no_image.png')

ims.make_timelapse('/tmp/{0}'.format(start.strftime("%Y%m%d")), fps=24)

Collect a set of Images

Page 49: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezone

ims = ImageSequence()

ettz = timezone('US/Eastern')

start = datetime.datetime(2011,4,7).replace(tzinfo=ettz)end = start + datetime.timedelta(days=1)

pics = NormalizedPicture.objects.filter(timestamp__gte=start, timestamp__lt=end)

ims = ImageSequence()

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) else: ims.add_image('/tmp/no_image.png')

ims.make_timelapse('/tmp/{0}'.format(start.strftime("%Y%m%d")), fps=24)

Send Images to Movie Maker

Page 50: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezone

ims = ImageSequence()

ettz = timezone('US/Eastern')

start = datetime.datetime(2011,4,7).replace(tzinfo=ettz)end = start + datetime.timedelta(days=1)

pics = NormalizedPicture.objects.filter(timestamp__gte=start, timestamp__lt=end)

ims = ImageSequence()

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) else: ims.add_image('/tmp/no_image.png')

ims.make_timelapse('/tmp/{0}'.format(start.strftime("%Y%m%d")), fps=24)

Wait For Movie To Be Made

Page 51: Observational Science With Python and a Webcam

class ImageSequence(object): def __init__(self): self.images = []

def add_picture(self, picture): self.images.append(picture.filepath)

def write_to_file(self, fileobj): for image in self.images: fileobj.write(image)

def make_timelapse(self, name, fps=25): tmpfile = tempfile.NamedTemporaryFile() self.write_to_file(tmpfile)

bitrate = int(round(60 * fps * 640 * 480 / 256.0))

execall = [] execall.append(mencoder_exe) execall.extend(mencoder_options) execall.extend(["-lavcopts", "vcodec=mpeg4:vbitrate={0}:mbd=2:keyint=132:v4mv:vqmin=3:lumi_mask=0.07:dark_mask=0.2:mpeg_quant:scplx_mask=0.1:tcplx_mask=0.1:naq".format(bitrate)]) execall.append("mf://@{0}".format(tmpfile.name)) execall.extend(["-mf", "type=jpeg:fps={0}".format(fps)]) execall.extend(["-o", "{name}.mp4".format(name=name)])

return subprocess.call(execall)

Wait For Movie To Be Made

Page 52: Observational Science With Python and a Webcam

Watch Movie!

http://bit.ly/ospw-8

http://bit.ly/ospw-8-raw

Page 53: Observational Science With Python and a Webcam

We aren't limited to consecutive minutes...

Page 54: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPicture

ims = ImageSequence()

Hour = 22 # UTC Timeminute = 0

last_pic = None

pics = NormalizedPicture.objects.filter(hour=hour, minute=minute)

last_pic = None

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) last_pic = pic.picture else: # Use yesterdays' image if last_pic is not None: ims.add_picture(last_pic) else: # Give up ims.add_image('/tmp/no_image.jpg')

ims.make_timelapse('/tmp/time_{0:02d}{1:02d}'.format(hour,minute),fps=12)

Movie of a Specific Time

Page 55: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPicture

ims = ImageSequence()

Hour = 22 # UTC Timeminute = 0

last_pic = None

pics = NormalizedPicture.objects.filter(hour=hour, minute=minute)

last_pic = None

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) last_pic = pic.picture else: # Use yesterdays' image if last_pic is not None: ims.add_picture(last_pic) else: # Give up ims.add_image('/tmp/no_image.jpg')

ims.make_timelapse('/tmp/time_{0:02d}{1:02d}'.format(hour,minute),fps=12)

Movie of a Specific Time

Page 56: Observational Science With Python and a Webcam

from movies import ImageSequencefrom pics.models import NormalizedPicture

ims = ImageSequence()

hour = 22 # UTC timeminute = 0

last_pic = None

pics = NormalizedPicture.objects.filter(hour=hour, minute=minute)

last_pic = None

for pic in pics: if pic.picture is not None: ims.add_picture(pic.picture) last_pic = pic.picture else: # Use yesterdays' image if last_pic is not None: ims.add_picture(last_pic) else: # Give up ims.add_image('/tmp/no_image.jpg')

ims.make_timelapse('/tmp/time_{0:02d}{1:02d}'.format(hour,minute),fps=12)

Movie of a Specific Time

Page 57: Observational Science With Python and a Webcam

Watch Movie!

http://bit.ly/ospw-9

http://bit.ly/ospw-9-raw

Page 58: Observational Science With Python and a Webcam

Now what if we want the Sun the same height above the

horizon, so we can better see the seasonal progression of

the Sun?

Page 59: Observational Science With Python and a Webcam

We can anchor on sunset. At a given time before sunset, the Sun will be at relatively the

same height above the horizon.

Page 60: Observational Science With Python and a Webcam

Where I thank Brandon Craig Rhodes for pyephem

Page 61: Observational Science With Python and a Webcam

import sky

obs = sky.webcam()

while current < end: # Get today's sunset time, but 70 minutes before pic_time = sky.sunset(obs, current) - datetime.timedelta(minutes=70) pic_time = pic_time.replace(second=0, microsecond=0)

pic = NormalizedPicture.objects.get(timestamp=pic_time)

if pic.picture is not None: ims.add_picture(pic.picture) else: piclist = NormalizedPicture.objects.get( timestamp__gte=pic_time - datetime.timedelta(minutes=20), timestamp__lte=pic_time + datetime.timedelta(minutes=20), picture__id__isnull=False)

if len(piclist) > 0: ims.add_picture(piclist[0].picture) else: # Use yesterdays' image if last_pic is not None: ims.add_picture(last_pic) else: # Give up ims.add_image('/tmp/no_image.jpg') current = current + datetime.timedelta(days=1)

Movie at Specific Time Before Sunset

Page 62: Observational Science With Python and a Webcam

import sky

obs = sky.webcam()

while current < end: # Get today's sunset time, but 70 minutes before pic_time = sky.sunset(obs, current) - datetime.timedelta(minutes=70) pic_time = pic_time.replace(second=0, microsecond=0)

pic = NormalizedPicture.objects.get(timestamp=pic_time)

if pic.picture is not None: ims.add_picture(pic.picture) else: piclist = NormalizedPicture.objects.get( timestamp__gte=pic_time - datetime.timedelta(minutes=20), timestamp__lte=pic_time + datetime.timedelta(minutes=20), picture__id__isnull=False)

if len(piclist) > 0: ims.add_picture(piclist[0].picture) else: # Use yesterdays' image if last_pic is not None: ims.add_picture(last_pic) else: # Give up ims.add_image('/tmp/no_image.jpg') current = current + datetime.timedelta(days=1)

Movie at Specific Time Before Sunset

Page 63: Observational Science With Python and a Webcam

import ephemfrom pytz import timezone, utc

sun = ephem.Sun()moon = ephem.Moon()est = timezone('EST')

def observer(): location = ephem.Observer() location.lon = '-83:23:24' location.lat = '40:13:48' location.elevation = 302 return location

def sunrise(observer, date): observer.date = date.astimezone(utc) return observer.next_rising(sun).datetime().replace(tzinfo=utc).astimezone(est)

def sunset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(sun).datetime().replace(tzinfo=utc).astimezone(est)

def moonset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(moon).datetime().replace(tzinfo=utc).astimezone(est)

Our sky module that uses pyephem

Page 64: Observational Science With Python and a Webcam

import ephemfrom pytz import timezone, utc

sun = ephem.Sun()moon = ephem.Moon()est = timezone('EST')

def observer(): location = ephem.Observer() location.lon = '-83:23:24' location.lat = '40:13:48' location.elevation = 302 return location

def sunrise(observer, date): observer.date = date.astimezone(utc) return observer.next_rising(sun).datetime().replace(tzinfo=utc).astimezone(est)

def sunset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(sun).datetime().replace(tzinfo=utc).astimezone(est)

def moonset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(moon).datetime().replace(tzinfo=utc).astimezone(est)

Our sky module that uses pyephem

Page 65: Observational Science With Python and a Webcam

import ephemfrom pytz import timezone, utc

sun = ephem.Sun()moon = ephem.Moon()est = timezone('EST')

def observer(): location = ephem.Observer() location.lon = '-83:23:24' location.lat = '40:13:48' location.elevation = 302 return location

def sunrise(observer, date): observer.date = date.astimezone(utc) return observer.next_rising(sun).datetime().replace(tzinfo=utc).astimezone(est)

def sunset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(sun).datetime().replace(tzinfo=utc).astimezone(est)

def moonset(observer, date): observer.date = date.astimezone(utc) return observer.next_setting(moon).datetime().replace(tzinfo=utc).astimezone(est)

Our sky module that uses pyephem

Page 66: Observational Science With Python and a Webcam

Watch Movie!

http://bit.ly/ospw-10

http://bit.ly/ospw-10-raw

Page 67: Observational Science With Python and a Webcam

August 29, 2010 6:59pm EDT

http://bit.ly/ospw-11

October 22, 20105:33pm EDT

http://bit.ly/ospw-12

Page 68: Observational Science With Python and a Webcam

Ancient Astronomical Alignmentshttp://bit.ly/ospw-14

Photo credits:http://en.wikipedia.org/wiki/File:ChichenItzaEquinox.jpghttp://www.colorado.edu/Conferences/chaco/tour/images/dagger.jpg

Page 69: Observational Science With Python and a Webcam

Tenet #5:

Once you have a foundation (data or code), however messy,

you can build higher.

Page 70: Observational Science With Python and a Webcam

So let's build higher!

Page 71: Observational Science With Python and a Webcam

We Also Take Pictures At Night

● Could I have captured a UFO in an image?

● Or a fireball?● Some other phenomenon?● What track does the moon take

through the sky?● How about Venus or Jupiter?

Page 72: Observational Science With Python and a Webcam

Moon Tracks and UFOs

● We want to find interesting things● How do we easily identify “interesting things”● At night, bright things are interesting things● Brightness is a good proxy for “interesting”● It makes it easy to identify interesting things● As it is easier to spot black spots on white images,

we'll invert the images

Page 73: Observational Science With Python and a Webcam

Making Night Tracks

● Process each image● Stack images into a single “all

night” image● From one hour after sunset to one

hour before sunrise the next day● Black splotches are interesting

things

Page 74: Observational Science With Python and a Webcam

def make_nighttrack(rootdir, day): obs = sky.observer()

# One hour after sunset start_time = sky.sunset(obs, day) + datetime.timedelta(hours=1) # Until one hour before sunrise the next day end_time = sky.sunrise(obs, tomorrow) - datetime.timedelta(hours=1)

pics = NormalizedPicture.objects.filter( timestamp__gte=start_time, timestamp__lt=end_time, picture__id__isnull=False)

print "Drawing image with {0} images".format(len(pics)) im = Image.new("L", (640,480), background_color)

for picture in pics: source = Image.open(picture.picture.filepath) # Get the negative source_gray = ImageOps.grayscale(source) source_neg = ImageOps.invert(source_gray)

# Threshold white source_thresh = Image.eval(source_neg, lambda x: 255*(x>224)) # Merge in the new image im = ImageChops.multiply(im, source_thresh)

# Put a date on the image canvas = ImageDraw.Draw(im) canvas.text((10,460), day.strftime("%Y-%m-%d"))

# And save im.save('{root}/{date}_mt.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Page 75: Observational Science With Python and a Webcam

def make_nighttrack(rootdir, day): obs = sky.observer()

# One hour after sunset start_time = sky.sunset(obs, day) + datetime.timedelta(hours=1) # Until one hour before sunrise the next day end_time = sky.sunrise(obs, tomorrow) - datetime.timedelta(hours=1)

pics = NormalizedPicture.objects.filter( timestamp__gte=start_time, timestamp__lt=end_time, picture__id__isnull=False)

print "Drawing image with {0} images".format(len(pics)) im = Image.new("L", (640,480), background_color)

for picture in pics: source = Image.open(picture.picture.filepath) # Get the negative source_gray = ImageOps.grayscale(source) source_neg = ImageOps.invert(source_gray)

# Threshold white source_thresh = Image.eval(source_neg, lambda x: 255*(x>224)) # Merge in the new image im = ImageChops.multiply(im, source_thresh)

# Put a date on the image canvas = ImageDraw.Draw(im) canvas.text((10,460), day.strftime("%Y-%m-%d"))

# And save im.save('{root}/{date}_mt.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Page 76: Observational Science With Python and a Webcam

def make_nighttrack(rootdir, day): obs = sky.observer()

# One hour after sunset start_time = sky.sunset(obs, day) + datetime.timedelta(hours=1) # Until one hour before sunrise the next day end_time = sky.sunrise(obs, tomorrow) - datetime.timedelta(hours=1)

pics = NormalizedPicture.objects.filter( timestamp__gte=start_time, timestamp__lt=end_time, picture__id__isnull=False)

print "Drawing image with {0} images".format(len(pics)) im = Image.new("L", (640,480), background_color)

for picture in pics: source = Image.open(picture.picture.filepath) # Get the negative source_gray = ImageOps.grayscale(source) source_neg = ImageOps.invert(source_gray)

# Threshold white source_thresh = Image.eval(source_neg, lambda x: 255*(x>224)) # Merge in the new image im = ImageChops.multiply(im, source_thresh)

# Put a date on the image canvas = ImageDraw.Draw(im) canvas.text((10,460), day.strftime("%Y-%m-%d"))

# And save im.save('{root}/{date}_mt.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Page 77: Observational Science With Python and a Webcam

def make_nighttrack(rootdir, day): obs = sky.observer()

# One hour after sunset start_time = sky.sunset(obs, day) + datetime.timedelta(hours=1) # Until one hour before sunrise the next day end_time = sky.sunrise(obs, tomorrow) - datetime.timedelta(hours=1)

pics = NormalizedPicture.objects.filter( timestamp__gte=start_time, timestamp__lt=end_time, picture__id__isnull=False)

print "Drawing image with {0} images".format(len(pics)) im = Image.new("L", (640,480), background_color)

for picture in pics: source = Image.open(picture.picture.filepath) # Get the negative source_gray = ImageOps.grayscale(source) source_neg = ImageOps.invert(source_gray)

# Threshold white source_thresh = Image.eval(source_neg, lambda x: 255*(x>224)) # Merge in the new image im = ImageChops.multiply(im, source_thresh)

# Put a date on the image canvas = ImageDraw.Draw(im) canvas.text((10,460), day.strftime("%Y-%m-%d"))

# And save im.save('{root}/{date}_mt.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Page 78: Observational Science With Python and a Webcam

Venus, Jupiter, and the Moon

http://bit.ly/ospw-15

Page 79: Observational Science With Python and a Webcam

Lighting Up the Sky

http://bit.ly/ospw-16

Page 80: Observational Science With Python and a Webcam

Lighting Up the Sky

http://bit.ly/ospw-17

Page 81: Observational Science With Python and a Webcam

Lightning Bug? Aircraft? UFO!

http://bit.ly/ospw-18http://bit.ly/ospw-19http://bit.ly/ospw-20

Moon

Venus

?

Page 82: Observational Science With Python and a Webcam

Shows up 3 weeks later

http://bit.ly/ospw-21http://bit.ly/ospw-22http://bit.ly/ospw-23

Page 83: Observational Science With Python and a Webcam

Last Oddity: 9/2/2011

http://bit.ly/ospw-34

Page 84: Observational Science With Python and a Webcam

Last Oddity: 9/2/2011

Single-Frame Oddity

http://bit.ly/ospw-30http://bit.ly/ospw-31

Crescent Moon That Night

http://bit.ly/ospw-32http://bit.ly/ospw-33

Page 85: Observational Science With Python and a Webcam

Let's make some science art!

Page 86: Observational Science With Python and a Webcam

Daystrips

● So far we've been using whole images...let's change that

● Instead of using a whole image, let's just use one line

● Kind of like a scanner● Start at midnight, stacking lines

Page 87: Observational Science With Python and a Webcam

Daystrip Scanner

http://bit.ly/ospw-24

12:06pm

11:40pm

Page 88: Observational Science With Python and a Webcam

Daystrips

● There are 1440 minutes in a day● Images will be 1440 pixels high● We place image line at the current

minute in the day● HOUR * 60 + MINUTE

Page 89: Observational Science With Python and a Webcam

def make_daystrip(rootdir, day): end = day + datetime.timedelta(days=1)

print "Getting data for {0}".format(day) pics = NormalizedPicture.objects.filter( timestamp__gte=day, timestamp__lt=end, picture__id__isnull=False)

im = Image.new('RGB', (640,1440), background_color) for picture in pics: source = Image.open(picture.picture.filepath) # Get row 1/3 of the way down, painting on proper row of canvas timestamp = picture.timestamp.astimezone(esttz)

y = timestamp.hour * 60 + timestamp.minute

im.paste(source.crop((0,160,640,161)),(0,y))

im.save('{root}/{date}_daystrip.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

This is beginning to look familiar

Page 90: Observational Science With Python and a Webcam

def make_daystrip(rootdir, day): end = day + datetime.timedelta(days=1)

print "Getting data for {0}".format(day) pics = NormalizedPicture.objects.filter( timestamp__gte=day, timestamp__lt=end, picture__id__isnull=False)

im = Image.new('RGB', (640,1440), background_color) for picture in pics: source = Image.open(picture.picture.filepath) # Get row 1/3 of the way down, painting on proper row of canvas timestamp = picture.timestamp.astimezone(esttz)

y = timestamp.hour * 60 + timestamp.minute

im.paste(source.crop((0,160,640,161)),(0,y))

im.save('{root}/{date}_daystrip.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Create Our New Image

Page 91: Observational Science With Python and a Webcam

def make_daystrip(rootdir, day): end = day + datetime.timedelta(days=1)

print "Getting data for {0}".format(day) pics = NormalizedPicture.objects.filter( timestamp__gte=day, timestamp__lt=end, picture__id__isnull=False)

im = Image.new('RGB', (640,1440), background_color) for picture in pics: source = Image.open(picture.picture.filepath) # Get row 1/3 of the way down, painting on proper row of canvas timestamp = picture.timestamp.astimezone(esttz)

y = timestamp.hour * 60 + timestamp.minute

im.paste(source.crop((0,160,640,161)),(0,y))

im.save('{root}/{date}_daystrip.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Iterate Though Our Images

Page 92: Observational Science With Python and a Webcam

def make_daystrip(rootdir, day): end = day + datetime.timedelta(days=1)

print "Getting data for {0}".format(day) pics = NormalizedPicture.objects.filter( timestamp__gte=day, timestamp__lt=end, picture__id__isnull=False)

im = Image.new('RGB', (640,1440), background_color) for picture in pics: source = Image.open(picture.picture.filepath) # Get row 1/3 of the way down, painting on proper row of canvas timestamp = picture.timestamp.astimezone(esttz)

y = timestamp.hour * 60 + timestamp.minute

im.paste(source.crop((0,160,640,161)),(0,y))

im.save('{root}/{date}_daystrip.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

And Paste The Single Row

Page 93: Observational Science With Python and a Webcam

def make_daystrip(rootdir, day): end = day + datetime.timedelta(days=1)

print "Getting data for {0}".format(day) pics = NormalizedPicture.objects.filter( timestamp__gte=day, timestamp__lt=end, picture__id__isnull=False)

im = Image.new('RGB', (640,1440), background_color) for picture in pics: source = Image.open(picture.picture.filepath) # Get row 1/3 of the way down, painting on proper row of canvas timestamp = picture.timestamp.astimezone(esttz)

y = timestamp.hour * 60 + timestamp.minute

im.paste(source.crop((0,160,640,161)),(0,y))

im.save('{root}/{date}_daystrip.png'.format(root=rootdir,date=day.strftime("%Y%m%d")))

Finally, save the image

Page 94: Observational Science With Python and a Webcam

Daystrip Example – March 17, 2011

http://bit.ly/ospw-25

Midnight

Moon Crossing

Sunrise

More cloudy (gray)More cloudy (gray)

Less cloudy (blue)

Sun Crossing

Midnight

Sunset

Page 95: Observational Science With Python and a Webcam

Shortest and Longest Day

Dec 22, 2011

http://bit.ly/ospw-26

June 20, 2012

http://bit.ly/ospw-27

Page 96: Observational Science With Python and a Webcam

Tenet #6:Don't be afraid to be creative.

Science can be beautiful.

Page 97: Observational Science With Python and a Webcam

Everything we've made so far spans a day or less.

Page 98: Observational Science With Python and a Webcam

What we've done so far

● Viewed full images of interest● Combined full images in movies● Stacked full images to find UFOs● Took a full line from a day's worth

of images● Everything is a day or less of data

Page 99: Observational Science With Python and a Webcam

Let's use ALL the days!

Page 100: Observational Science With Python and a Webcam

What if...

Page 101: Observational Science With Python and a Webcam

Instead of an image row being a single minute...

it was a whole day...

Page 102: Observational Science With Python and a Webcam

And each pixel in the row was a minute in the day.

Page 103: Observational Science With Python and a Webcam

Therefore, each of our 896,309 webcam images would

comprise a single pixel in our über-image.

Page 104: Observational Science With Python and a Webcam

A Visual Representation

Minutes in a day (1440)

Day

s (e

ach

row

is o

ne d

ay)

Image

Mid

nigh

t

Mid

nigh

t

Noo

n

Start Day

End Day

Each pixel is one minute in the day

Page 105: Observational Science With Python and a Webcam

pics = NormalizedPicture.objects.all()

im = Image.new('RGB', (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)

for picture in pics: # Get XY timestamp = picture.timestamp.astimezone(est)

y_timedelta = timestamp - start y = y_timedelta.days x = timestamp.hour * 60 + timestamp.minute

# Paint pixel if picture.picture is not None: color = int_to_rgb(picture.picture.median_color) else: color = background_color

canvas.point((x,y), fill=color)

# All done, saveim.save(filepath)

Page 106: Observational Science With Python and a Webcam

pics = NormalizedPicture.objects.all()

im = Image.new('RGB', (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)

for picture in pics: # Get XY timestamp = picture.timestamp.astimezone(est)

y_timedelta = timestamp - start y = y_timedelta.days x = timestamp.hour * 60 + timestamp.minute

# Paint pixel if picture.picture is not None: color = int_to_rgb(picture.picture.median_color) else: color = background_color

canvas.point((x,y), fill=color)

# All done, saveim.save(filepath)

Page 107: Observational Science With Python and a Webcam

pics = NormalizedPicture.objects.all()

im = Image.new('RGB', (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)

for picture in pics: # Get XY timestamp = picture.timestamp.astimezone(est)

y_timedelta = timestamp - start y = y_timedelta.days x = timestamp.hour * 60 + timestamp.minute

# Paint pixel if picture.picture is not None: color = int_to_rgb(picture.picture.median_color) else: color = background_color

canvas.point((x,y), fill=color)

# All done, saveim.save(filepath)

Page 108: Observational Science With Python and a Webcam

pics = NormalizedPicture.objects.all()

im = Image.new('RGB', (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)

for picture in pics: # Get XY timestamp = picture.timestamp.astimezone(est)

y_timedelta = timestamp - start y = y_timedelta.days x = timestamp.hour * 60 + timestamp.minute

# Paint pixel if picture.picture is not None: color = int_to_rgb(picture.picture.median_color) else: color = background_color

canvas.point((x,y), fill=color)

# All done, saveim.save(filepath)

Page 109: Observational Science With Python and a Webcam

The Result

http://bit.ly/ospw-28

Page 110: Observational Science With Python and a Webcam

What Exactly does DST Do?

http://bit.ly/ospw-29

Page 111: Observational Science With Python and a Webcam

Basically It Normalizes Sunrise TimeBy Making Summer Sunrise Later

http://bit.ly/ospw-29

Page 112: Observational Science With Python and a Webcam

At The Expense Of Wider Sunset Time Variation, Because Of Later Summer Sunset

http://bit.ly/ospw-29

Page 113: Observational Science With Python and a Webcam

Python makes it easy to answer “What If?”

So...

Page 114: Observational Science With Python and a Webcam

Tenet #7:Don't be afraid to ask

“What if?”, and don't be afraid of where it takes you.

Page 115: Observational Science With Python and a Webcam

Presentation:http://bit.ly/ospw-talk-pdf

http://bit.ly/ospw-talk (raw)

Code:http://bit.ly/ospw-code

Picture Set (9.1GB):http://bit.ly/ospw-pics

Links and more (soon):http://intellovations.com/ospw