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


Top Related