Observational Science     With Python    And a Webcam                                  By: Eric FloehrObservational Scienc...
So I had a webcam...
and I made a time-lapse video.       http://bit.ly/ospw-1
I wanted to do better.        Longer     Inside location      Automated    Once a minute
So this is what I did.     Second floor window       Little-used room        Pointing westPull pictures down with cronjob ...
And it looks like this.
Two years later...
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...
Tenet #1:Dont be afraid of big data.
What can we do?● Moar Time-lapse!● Explore phenomenon that occurs  over long periods● Unique visualizations● Have lots of ...
First...We need to organize the data.
Database!
How will we access the data?     Lets use Django!
Why Django?● It has a good ORM● It has a command framework● Makes extending to the web later  easy● I already am familiar ...
Quick setup● Create your virtualenv (with site packages)● PIL is a pain to compile● Install Django● django-admin.py startp...
Tenet #2:Dont let small things keep you    from your big picture.
What do we need to store?● Image file location and filename● Time the image was taken● Interesting information about the  ...
class Picture(models.Model):   filepath = models.CharField(max_length=1024)   filename = models.CharField(max_length=1024)...
class Picture(models.Model):   filepath = models.CharField(max_length=1024)   filename = models.CharField(max_length=1024)...
class Picture(models.Model):   filepath = models.CharField(max_length=1024)   filename = models.CharField(max_length=1024)...
class Picture(models.Model):   filepath = models.CharField(max_length=1024)   filename = models.CharField(max_length=1024)...
class Picture(models.Model):   filepath = models.CharField(max_length=1024)   filename = models.CharField(max_length=1024)...
Loading the Data
How do we populate the table?● Iterate through directories of  image files● Parse the file name to get  timestamp● Get fil...
Find the image filesimport osimport fnmatchimport datetimefor dirpath, dirs, files in os.walk(directory):    for f in sort...
Find the image filesimport osimport fnmatchimport datetimefor dirpath, dirs, files in os.walk(directory):    for f in sort...
Find the image filesimport osimport fnmatchimport datetimefor dirpath, dirs, files in os.walk(directory):    for f in sort...
Find the image filesimport osimport fnmatchimport datetimefor dirpath, dirs, files in os.walk(directory):    for f in sort...
Find the image filesimport osimport fnmatchimport datetimefor dirpath, dirs, files in os.walk(directory):    for f in sort...
Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry:       im = Image.open(pic.filepath) ...
Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry:       im = Image.open(pic.filepath) ...
Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry:       im = Image.open(pic.filepath) ...
Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry:       im = Image.open(pic.filepath) ...
It took a an hour or two using  six threads on my desktop
Tenet #3:     You have a 1990ssupercomputer on your lap or      under your desk.          Use it!
Lets Explore the Data
Size Indicates Complexity in jpegpics=# select timestamp, filepath, size from pics_picture where size=(select       max(si...
High Stddev Means Color Variationpics=# select timestamp, filepath, size from pics_picture where       stddev_red+stddev_g...
About the Datasetpics=# select min(timestamp) as start, max(timestamp) as end from pics_picture;         start          | ...
Yuck! This Data Sucks!● 29,555 invalid image files● Thats 3.3% of all image files● Worse yet, there isnt a file every minu...
Its Worse...I Forgot The Bolts!                  http://bit.ly/ospw-5                  http://bit.ly/ospw-6               ...
* Interestingly, I was listening to the Serious Business remix of “All Is Not  Lost” by OK Go from the Humble Music Bundle...
Tenet #4:  Real world data is messy.Thats ok. Just work around it.
How we can work around it?● Create table of all minutes● Accept images “near” missing  minutes● Use empty images when  acc...
Lets Make Time-lapse Movies
How do we make movies?● Collect a set of images in frame  order.● Send that list of images to a movie  maker.● Wait for mo...
The previous bullet points in codefrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetime...
Collect a set of Imagesfrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz i...
Send Images to Movie Makerfrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pyt...
Wait For Movie To Be Madefrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz...
Wait For Movie To Be Madeclass ImageSequence(object):    def __init__(self):        self.images = []    def add_picture(se...
Watch Movie!  http://bit.ly/ospw-8  http://bit.ly/ospw-8-raw
We arent limited toconsecutive minutes...
Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()Hour...
Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()Hour...
Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()hour...
Watch Movie!  http://bit.ly/ospw-9  http://bit.ly/ospw-9-raw
Now what if we want the Sun  the same height above thehorizon, so we can better see the seasonal progression of          t...
We can anchor on sunset. At agiven time before sunset, the Sun will be at relatively the   same height above the          ...
Where I thank Brandon Craig   Rhodes for pyephem
Movie at Specific Time Before Sunsetimport skyobs = sky.webcam()while current < end:    # Get todays sunset time, but 70 m...
Movie at Specific Time Before Sunsetimport skyobs = sky.webcam()while current < end:    # Get todays sunset time, but 70 m...
Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = ephem.Sun()moon = ephem.Moon()est = timezo...
Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = ephem.Sun()moon = ephem.Moon()est = timezo...
Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = ephem.Sun()moon = ephem.Moon()est = timezo...
Watch Movie!  http://bit.ly/ospw-10  http://bit.ly/ospw-10-raw
August 29, 20106:59pm EDThttp://bit.ly/ospw-11October 22, 20105:33pm EDThttp://bit.ly/ospw-12
Ancient Astronomical Alignmentshttp://bit.ly/ospw-14Photo credits:http://en.wikipedia.org/wiki/File:ChichenItzaEquinox.jpg...
Tenet #5:  Once you have a foundation(data or code), however messy,     you can build higher.
So lets build higher!
We Also Take Pictures At Night● Could I have captured a UFO in an  image?● Or a fireball?● Some other phenomenon?● What tr...
Moon Tracks and UFOs● We want to find interesting things● How do we easily identify “interesting things”● At night, bright...
Making Night Tracks● Process each image● Stack images into a single “all  night” image● From one hour after sunset to one ...
def make_nighttrack(rootdir, day):    obs = sky.observer()    # One hour after sunset    start_time = sky.sunset(obs, day)...
def make_nighttrack(rootdir, day):    obs = sky.observer()    # One hour after sunset    start_time = sky.sunset(obs, day)...
def make_nighttrack(rootdir, day):    obs = sky.observer()    # One hour after sunset    start_time = sky.sunset(obs, day)...
def make_nighttrack(rootdir, day):    obs = sky.observer()    # One hour after sunset    start_time = sky.sunset(obs, day)...
Venus, Jupiter, and the Moon         http://bit.ly/ospw-15
Lighting Up the Sky     http://bit.ly/ospw-16
Lighting Up the Sky     http://bit.ly/ospw-17
Lightning Bug? Aircraft? UFO! Venus                         ? Moon           http://bit.ly/ospw-18           http://bit.ly...
Shows up 3 weeks later       http://bit.ly/ospw-21       http://bit.ly/ospw-22       http://bit.ly/ospw-23
Last Oddity: 9/2/2011      http://bit.ly/ospw-34
Last Oddity: 9/2/2011Single-Frame Oddity      Crescent Moon That Night http://bit.ly/ospw-30      http://bit.ly/ospw-32 ht...
Lets make some science art!
Daystrips● So far weve been using whole  images...lets change that● Instead of using a whole image,  lets just use one lin...
Daystrip Scanner     12:06pm     11:40pm   http://bit.ly/ospw-24
Daystrips● There are 1440 minutes in a day● Images will be 1440 pixels high● We place image line at the current  minute in...
This is beginning to look familiardef make_daystrip(rootdir, day):    end = day + datetime.timedelta(days=1)    print "Get...
Create Our New Imagedef make_daystrip(rootdir, day):    end = day + datetime.timedelta(days=1)    print "Getting data for ...
Iterate Though Our Imagesdef make_daystrip(rootdir, day):    end = day + datetime.timedelta(days=1)    print "Getting data...
And Paste The Single Rowdef make_daystrip(rootdir, day):    end = day + datetime.timedelta(days=1)    print "Getting data ...
Finally, save the imagedef make_daystrip(rootdir, day):    end = day + datetime.timedelta(days=1)    print "Getting data f...
Daystrip Example – March 17, 2011             Midnight       Moon Crossing              Sunrise    More cloudy (gray)    L...
Shortest and Longest DayDec 22, 2011                      June 20, 2012http://bit.ly/ospw-26             http://bit.ly/osp...
Tenet #6:Dont be afraid to be creative. Science can be beautiful.
Everything weve made so far    spans a day or less.
What weve done so far● Viewed full images of interest● Combined full images in movies● Stacked full images to find UFOs● T...
Lets use ALL the days!
What if...
Instead of an image row being      a single minute...    it was a whole day...
And each pixel in the row was    a minute in the day.
Therefore, each of our 896,309   webcam images wouldcomprise a single pixel in our         über-image.
A Visual Representation                                                                              Midnight             ...
pics = NormalizedPicture.objects.all()im = Image.new(RGB, (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)for...
pics = NormalizedPicture.objects.all()im = Image.new(RGB, (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)for...
pics = NormalizedPicture.objects.all()im = Image.new(RGB, (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)for...
pics = NormalizedPicture.objects.all()im = Image.new(RGB, (1440,num_days), background_color)Canvas = ImageDraw.Draw(im)for...
The Result http://bit.ly/ospw-28
What Exactly does DST Do?         http://bit.ly/ospw-29
Basically It Normalizes Sunrise Time By Making Summer Sunrise Later              http://bit.ly/ospw-29
At The Expense Of Wider Sunset TimeVariation, Because Of Later Summer Sunset                http://bit.ly/ospw-29
Python makes it easy to  answer “What If?”         So...
Tenet #7:   Dont be afraid to ask“What if?”, and dont be afraid   of where it takes you.
Presentation:  http://bit.ly/ospw-talk-pdf http://bit.ly/ospw-talk (raw)           Code:   http://bit.ly/ospw-code   Pictu...
Upcoming SlideShare
Loading in …5
×

Observational Science With Python and a Webcam

311 views
234 views

Published on

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.

Published in: Technology
2 Comments
0 Likes
Statistics
Notes
  • Be the first to like this

No Downloads
Views
Total views
311
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
8
Comments
2
Likes
0
Embeds 0
No embeds

No notes for slide

Observational Science With Python and a Webcam

  1. 1. Observational Science With Python And a Webcam By: Eric FloehrObservational Science With Python and a Webcam by Eric Floehr is licensed under aCreative Commons Attribution-ShareAlike 3.0 Unported License.
  2. 2. So I had a webcam...
  3. 3. and I made a time-lapse video. http://bit.ly/ospw-1
  4. 4. I wanted to do better. Longer Inside location Automated Once a minute
  5. 5. So this is what I did. Second floor window Little-used room Pointing westPull pictures down with cronjob That runs once per minute
  6. 6. And it looks like this.
  7. 7. Two years later...
  8. 8. 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...
  9. 9. Tenet #1:Dont be afraid of big data.
  10. 10. What can we do?● Moar Time-lapse!● Explore phenomenon that occurs over long periods● Unique visualizations● Have lots of fun!
  11. 11. First...We need to organize the data.
  12. 12. Database!
  13. 13. How will we access the data? Lets use Django!
  14. 14. 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 isnt just for web :-)
  15. 15. 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
  16. 16. Tenet #2:Dont let small things keep you from your big picture.
  17. 17. 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?
  18. 18. 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, dont 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]
  19. 19. class Picture(models.Model): filepath = models.CharField(max_length=1024) filename = models.CharField(max_length=1024) <-- File location and name timestamp = models.DateTimeField(db_index=True) file_timestamp = models.DateTimeField(null=True) # Auto-populated, dont 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]
  20. 20. 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, dont manually enter hour = models.SmallIntegerField(null=True, db_index=True) <-- Image Timestamp 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]
  21. 21. 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, dont 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) <-- Interesting image data 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]
  22. 22. 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, dont 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) <-- Is it valid? How do we order? class Meta: ordering = [timestamp]
  23. 23. Loading the Data
  24. 24. 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
  25. 25. Find the image filesimport osimport fnmatchimport datetimefor 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)
  26. 26. Find the image filesimport osimport fnmatchimport datetimefor 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)
  27. 27. Find the image filesimport osimport fnmatchimport datetimefor 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)
  28. 28. Find the image filesimport osimport fnmatchimport datetimefor 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)
  29. 29. Find the image filesimport osimport fnmatchimport datetimefor 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)
  30. 30. Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry: 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 = Falseexcept: pic.valid = False
  31. 31. Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry: 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 = Falseexcept: pic.valid = False
  32. 32. Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry: 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 = Falseexcept: pic.valid = False
  33. 33. Use PIL to get image infoimport Image, ImageStatFrom util.color import rgb_to_inttry: 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 = Falseexcept: pic.valid = False
  34. 34. It took a an hour or two using six threads on my desktop
  35. 35. Tenet #3: You have a 1990ssupercomputer on your lap or under your desk. Use it!
  36. 36. Lets Explore the Data
  37. 37. 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
  38. 38. 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
  39. 39. 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 andvalid=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
  40. 40. Yuck! This Data Sucks!● 29,555 invalid image files● Thats 3.3% of all image files● Worse yet, there isnt 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
  41. 41. Its Worse...I Forgot The Bolts! http://bit.ly/ospw-5 http://bit.ly/ospw-6 http://bit.ly/ospw-7
  42. 42. * 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.
  43. 43. Tenet #4: Real world data is messy.Thats ok. Just work around it.
  44. 44. How we can work around it?● Create table of all minutes● Accept images “near” missing minutes● Use empty images when acceptable images cant be found
  45. 45. Lets Make Time-lapse Movies
  46. 46. 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!
  47. 47. The previous bullet points in codefrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezoneims = 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)
  48. 48. Collect a set of Imagesfrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezoneims = 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)
  49. 49. Send Images to Movie Makerfrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezoneims = 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)
  50. 50. Wait For Movie To Be Madefrom movies import ImageSequencefrom pics.models import NormalizedPictureimport datetimefrom pytz import timezoneims = 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)
  51. 51. Wait For Movie To Be Madeclass 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)
  52. 52. Watch Movie! http://bit.ly/ospw-8 http://bit.ly/ospw-8-raw
  53. 53. We arent limited toconsecutive minutes...
  54. 54. Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()Hour = 22 # UTC Timeminute = 0last_pic = Nonepics = NormalizedPicture.objects.filter(hour=hour, minute=minute)last_pic = Nonefor 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)
  55. 55. Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()Hour = 22 # UTC Timeminute = 0last_pic = Nonepics = NormalizedPicture.objects.filter(hour=hour, minute=minute)last_pic = Nonefor 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)
  56. 56. Movie of a Specific Timefrom movies import ImageSequencefrom pics.models import NormalizedPictureims = ImageSequence()hour = 22 # UTC timeminute = 0last_pic = Nonepics = NormalizedPicture.objects.filter(hour=hour, minute=minute)last_pic = Nonefor 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)
  57. 57. Watch Movie! http://bit.ly/ospw-9 http://bit.ly/ospw-9-raw
  58. 58. Now what if we want the Sun the same height above thehorizon, so we can better see the seasonal progression of the Sun?
  59. 59. We can anchor on sunset. At agiven time before sunset, the Sun will be at relatively the same height above the horizon.
  60. 60. Where I thank Brandon Craig Rhodes for pyephem
  61. 61. Movie at Specific Time Before Sunsetimport skyobs = sky.webcam()while current < end: # Get todays 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)
  62. 62. Movie at Specific Time Before Sunsetimport skyobs = sky.webcam()while current < end: # Get todays 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)
  63. 63. Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = 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 locationdef 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)
  64. 64. Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = 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 locationdef 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)
  65. 65. Our sky module that uses pyephemimport ephemfrom pytz import timezone, utcsun = 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 locationdef 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)
  66. 66. Watch Movie! http://bit.ly/ospw-10 http://bit.ly/ospw-10-raw
  67. 67. August 29, 20106:59pm EDThttp://bit.ly/ospw-11October 22, 20105:33pm EDThttp://bit.ly/ospw-12
  68. 68. Ancient Astronomical Alignmentshttp://bit.ly/ospw-14Photo credits:http://en.wikipedia.org/wiki/File:ChichenItzaEquinox.jpghttp://www.colorado.edu/Conferences/chaco/tour/images/dagger.jpg
  69. 69. Tenet #5: Once you have a foundation(data or code), however messy, you can build higher.
  70. 70. So lets build higher!
  71. 71. 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?
  72. 72. 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, well invert the images
  73. 73. 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
  74. 74. 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")))
  75. 75. 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")))
  76. 76. 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")))
  77. 77. 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")))
  78. 78. Venus, Jupiter, and the Moon http://bit.ly/ospw-15
  79. 79. Lighting Up the Sky http://bit.ly/ospw-16
  80. 80. Lighting Up the Sky http://bit.ly/ospw-17
  81. 81. Lightning Bug? Aircraft? UFO! Venus ? Moon http://bit.ly/ospw-18 http://bit.ly/ospw-19 http://bit.ly/ospw-20
  82. 82. Shows up 3 weeks later http://bit.ly/ospw-21 http://bit.ly/ospw-22 http://bit.ly/ospw-23
  83. 83. Last Oddity: 9/2/2011 http://bit.ly/ospw-34
  84. 84. Last Oddity: 9/2/2011Single-Frame Oddity Crescent Moon That Night http://bit.ly/ospw-30 http://bit.ly/ospw-32 http://bit.ly/ospw-31 http://bit.ly/ospw-33
  85. 85. Lets make some science art!
  86. 86. Daystrips● So far weve been using whole images...lets change that● Instead of using a whole image, lets just use one line● Kind of like a scanner● Start at midnight, stacking lines
  87. 87. Daystrip Scanner 12:06pm 11:40pm http://bit.ly/ospw-24
  88. 88. 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
  89. 89. This is beginning to look familiardef 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")))
  90. 90. Create Our New Imagedef 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")))
  91. 91. Iterate Though Our Imagesdef 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")))
  92. 92. And Paste The Single Rowdef 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")))
  93. 93. Finally, save the imagedef 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")))
  94. 94. Daystrip Example – March 17, 2011 Midnight Moon Crossing Sunrise More cloudy (gray) Less cloudy (blue) Sun Crossing Sunset Midnight http://bit.ly/ospw-25
  95. 95. Shortest and Longest DayDec 22, 2011 June 20, 2012http://bit.ly/ospw-26 http://bit.ly/ospw-27
  96. 96. Tenet #6:Dont be afraid to be creative. Science can be beautiful.
  97. 97. Everything weve made so far spans a day or less.
  98. 98. What weve 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 days worth of images● Everything is a day or less of data
  99. 99. Lets use ALL the days!
  100. 100. What if...
  101. 101. Instead of an image row being a single minute... it was a whole day...
  102. 102. And each pixel in the row was a minute in the day.
  103. 103. Therefore, each of our 896,309 webcam images wouldcomprise a single pixel in our über-image.
  104. 104. A Visual Representation Midnight Midnight Noon Minutes in a day (1440)Start Day Days (each row is one day) Each pixel is one minute in the day ImageEnd Day
  105. 105. 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)
  106. 106. 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)
  107. 107. 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)
  108. 108. 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)
  109. 109. The Result http://bit.ly/ospw-28
  110. 110. What Exactly does DST Do? http://bit.ly/ospw-29
  111. 111. Basically It Normalizes Sunrise Time By Making Summer Sunrise Later http://bit.ly/ospw-29
  112. 112. At The Expense Of Wider Sunset TimeVariation, Because Of Later Summer Sunset http://bit.ly/ospw-29
  113. 113. Python makes it easy to answer “What If?” So...
  114. 114. Tenet #7: Dont be afraid to ask“What if?”, and dont be afraid of where it takes you.
  115. 115. 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

×