<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Fox Row</title><link href="https://foxrow.com/" rel="alternate"></link><link href="https://foxrow.com/feeds/all.atom.xml" rel="self"></link><id>https://foxrow.com/</id><updated>2023-12-13T12:00:00-06:00</updated><subtitle>Imaging, astronomy, rowing</subtitle><entry><title>Titles on Netflix</title><link href="https://foxrow.com/titles-on-netflix" rel="alternate"></link><published>2023-12-13T12:00:00-06:00</published><updated>2023-12-13T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-12-13:/titles-on-netflix</id><content type="html">&lt;p&gt;&lt;img alt="word cloud of titles of netflix content" class="m-image" src="https://foxrow.com/assets/netflix_titles.jpg" title="netflix titles word cloud"/&gt;
&lt;em&gt;A word cloud generated from &lt;a href="https://about.netflix.com/en/news/what-we-watched-a-netflix-engagement-report"&gt;Netflix viewing data,&lt;/a&gt; January-June 2023&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="histogram of netflix watch totals by show" class="m-image" src="https://foxrow.com/assets/netflix_hours_watched.png" title="netflix watch time per show"/&gt;
&lt;em&gt;A histogram of Netflix watch time by show, January-June 2023&lt;/em&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="movie"></category><category term="python"></category><category term="stats"></category><category term="dataviz"></category></entry><entry><title>NaMoGenMo 2023 report</title><link href="https://foxrow.com/namogenmo-2023" rel="alternate"></link><published>2023-12-02T12:00:00-06:00</published><updated>2023-12-02T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-12-02:/namogenmo-2023</id><summary type="html">&lt;p&gt;Over the month of November, I worked on a project to &lt;a href="https://namogenmo.github.io/"&gt;create code that generates a movie.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My project this year: see most exciting parts of a movie right away, and leave all those pesky slow, boring parts for the end. &lt;a href="https://en.wikipedia.org/wiki/BLUF_(communication)"&gt;Bottom Line Up Front.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What parts of a movie …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over the month of November, I worked on a project to &lt;a href="https://namogenmo.github.io/"&gt;create code that generates a movie.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My project this year: see most exciting parts of a movie right away, and leave all those pesky slow, boring parts for the end. &lt;a href="https://en.wikipedia.org/wiki/BLUF_(communication)"&gt;Bottom Line Up Front.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What parts of a movie are the most exciting? The one with the most on-screen motion, of course!  We can BLUF-ize a movie by extracting the segments with the most movement in the whole video. Then the next most action, and so on until you are left with the slowest, most sedate parts.&lt;/p&gt;
&lt;p&gt;So whenever you choose to stop, everything you've already seen is more exciting than what's left.&lt;/p&gt;
&lt;p&gt;As a subject, I chose &lt;a href="https://en.wikipedia.org/wiki/The_Phantom_of_the_Opera_(1925_film)"&gt;The Phantom of the Opera (1925),&lt;/a&gt; a silent film starring Lon Chaney as the Phantom. It was released in 1925, meaning it's in the public domain.&lt;/p&gt;
&lt;p&gt;The code I wrote to generate the video is &lt;a href="https://github.com/ryanfox/bluffer/"&gt;available on GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I am very pleased with how it turned out! It's choppy, but more or less intelligible. And having &lt;em&gt;all&lt;/em&gt; dynamic shots front-loaded is pretty exciting. The final result:&lt;/p&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/cOuy6GmQRJw?si=qytmAJNRKf_L6Otj" title="YouTube video player" width="560"&gt;&lt;/iframe&gt;</content><category term="programming"></category><category term="programming"></category><category term="video"></category><category term="movie"></category><category term="namogenmo"></category><category term="ffmpeg"></category></entry><entry><title>Thought about Cosmopolitan C</title><link href="https://foxrow.com/thought-about-cosmopolitan-c" rel="alternate"></link><published>2023-11-05T12:00:00-06:00</published><updated>2023-11-05T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-11-05:/thought-about-cosmopolitan-c</id><summary type="html">&lt;p&gt;&lt;a href="https://justine.lol/cosmo3/"&gt;The Cosmopolitan C project&lt;/a&gt; just released v3.0 of the library. It's a technical marvel - compile a single binary that runs on 6 OSes, across 2 architectures, and also on bare metal.&lt;/p&gt;
&lt;p&gt;It occurs to me - since BIOS is supported, one could probably compile Firefox with &lt;code&gt;cosmoc&lt;/code&gt;, and get the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://justine.lol/cosmo3/"&gt;The Cosmopolitan C project&lt;/a&gt; just released v3.0 of the library. It's a technical marvel - compile a single binary that runs on 6 OSes, across 2 architectures, and also on bare metal.&lt;/p&gt;
&lt;p&gt;It occurs to me - since BIOS is supported, one could probably compile Firefox with &lt;code&gt;cosmoc&lt;/code&gt;, and get the METAL operating system from &lt;a href="https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript"&gt;Birth &amp;amp; Death of JavaScript.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you haven't seem that talk, do yourself a favor and watch it, and then click around on the cosmo c project website.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="c"></category></entry><entry><title>Querying a power outage database</title><link href="https://foxrow.com/querying-a-power-outage-database" rel="alternate"></link><published>2023-07-16T12:00:00-05:00</published><updated>2023-07-16T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-07-16:/querying-a-power-outage-database</id><summary type="html">&lt;p&gt;I have been tracking power outage data using a technique I learned about from Simon Willison called &lt;a href="https://simonwillison.net/2020/Oct/9/git-scraping/"&gt;git-scraping.&lt;/a&gt; The outages are broken down by city, a stylized version of the data looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;300&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tacoma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;150&lt;/span&gt;
&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="n"&gt;tacoma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;140&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;implied&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I have been tracking power outage data using a technique I learned about from Simon Willison called &lt;a href="https://simonwillison.net/2020/Oct/9/git-scraping/"&gt;git-scraping.&lt;/a&gt; The outages are broken down by city, a stylized version of the data looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;300&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tacoma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;150&lt;/span&gt;
&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="n"&gt;tacoma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;140&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;implied&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;
&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;50&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tacoma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seattle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Mr. Willison also has written a library to convert a git-scraped repo into an SQLite database, called &lt;a href="https://simonwillison.net/2021/Dec/7/git-history/"&gt;git-history.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some simple information I'd like to query the database for: what's the total number of customers experiencing an outage over time? My previous solution parses the JSON in each commit in the git repo, but it's kind of slow, and I'd like to use &lt;code&gt;git-history&lt;/code&gt; for more complex queries later on. However, I ran into two issues.&lt;/p&gt;
&lt;h2&gt;Data format&lt;/h2&gt;
&lt;p&gt;The first problem was my data format. If a city drops from the list of outages between two commits, that implies the outages there were fixed. (See the 3pm line in the example above.) By default, &lt;code&gt;git-history&lt;/code&gt; interprets an entry disappearing from the data as "no change".&lt;/p&gt;
&lt;p&gt;I solved this in a straightforward, brute-force fashion: at each commit, query the DB for all known cities. Set their outage count to 0, and then overwrite with any cities actually in the current commit.&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;--convert&lt;/code&gt; option in &lt;code&gt;git-history&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;outages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;outages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s2"&gt;"outage"&lt;/span&gt; \
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite_utils&lt;/span&gt; \
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt; \
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlite_utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"outages.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# get the name of every city in the DB so far&lt;/span&gt;
&lt;span class="n"&gt;known_cities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT DISTINCT name FROM outage"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="c1"&gt;# overwrite 0 for cities actually in this commit&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;known_cities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"outage_count"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;known_cities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's not pretty, but it works!&lt;/p&gt;
&lt;h2&gt;Querying the database&lt;/h2&gt;
&lt;p&gt;The second issue was actually querying the DB. I have technically found a solution... but it's terribly slow. 2 nested subqueries! 😬
This is the general idea:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;there&lt;/span&gt;&lt;span class="err"&gt;'s a commit at:&lt;/span&gt;
&lt;span class="err"&gt;    sum the outage counts for:&lt;/span&gt;
&lt;span class="err"&gt;        select the most recent row for each city (not necessarily in the "current" commit!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here's the query I have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;
&lt;span class="nv"&gt;select&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;distinct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;_commit_at&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;outages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;given&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;select&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;sum&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;customers_affected&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;outages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;city&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;given&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;select&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;inner&lt;/span&gt;.&lt;span class="nv"&gt;_commit_at&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;,
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;customers_affected&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;outage_version_detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;inner&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;inner&lt;/span&gt;.&lt;span class="nv"&gt;_commit_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;outer&lt;/span&gt;.&lt;span class="nv"&gt;_commit_at&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;_item&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;from&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;outage_version_detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;outer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I believe it runs correctly, but at ~6 seconds per row returned, it would be faster to iterate over the original git repo and parse the JSONs. Surely there's a better way to structure the two inner queries, but I haven't gotten there yet. If anyone has ideas, please get in touch!&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="database"></category><category term="sqlite"></category><category term="python"></category><category term="git"></category></entry><entry><title>TIL: converting JSON using git-history</title><link href="https://foxrow.com/til-converting-json-using-git-history" rel="alternate"></link><published>2023-06-23T12:00:00-05:00</published><updated>2023-06-23T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-06-23:/til-converting-json-using-git-history</id><summary type="html">&lt;p&gt;&lt;code&gt;git-history&lt;/code&gt; is &lt;a href="https://datasette.io/tools/git-history"&gt;a tool&lt;/a&gt; created by Simon Willison, that makes it easy to work with data collected via &lt;a href="https://simonwillison.net/2020/Oct/9/git-scraping/"&gt;git scraping.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It expects data in a particular format - a flat JSON array. If your data isn't already there, you can use the &lt;code&gt;--convert&lt;/code&gt; option to run any python code to make …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;git-history&lt;/code&gt; is &lt;a href="https://datasette.io/tools/git-history"&gt;a tool&lt;/a&gt; created by Simon Willison, that makes it easy to work with data collected via &lt;a href="https://simonwillison.net/2020/Oct/9/git-scraping/"&gt;git scraping.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It expects data in a particular format - a flat JSON array. If your data isn't already there, you can use the &lt;code&gt;--convert&lt;/code&gt; option to run any python code to make it the right shape.&lt;/p&gt;
&lt;p&gt;For example, if your JSON looks like &lt;a href="https://github.com/simonw/git-history/blob/0.6/README.md#custom-conversions-using---convert"&gt;this:&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    "incidents": [
        {
            "id": "552",
            "name": "Hawthorne Fire",
            "engines": 3
        },
        {
            "id": "556",
            "name": "Merlin Fire",
            "engines": 1
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You might ingest it via a command like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;incidents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;incidents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nb"&gt;convert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json.load(content)['incidents']"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you're using git scraping to track any data, it's useful for working with the data efficiently, so try it out!&lt;/p&gt;</content><category term="programming"></category><category term="til"></category><category term="programming"></category><category term="database"></category><category term="python"></category><category term="git"></category></entry><entry><title>APSE updates</title><link href="https://foxrow.com/apse-updates" rel="alternate"></link><published>2023-04-30T12:00:00-05:00</published><updated>2023-04-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-04-30:/apse-updates</id><summary type="html">&lt;p&gt;It has been a while since I wrote about &lt;a href="https://apse.io"&gt;APSE&lt;/a&gt; here, and over the past few months I've made a number of big updates to it in version 4.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;First of all, there have been &lt;strong&gt;major&lt;/strong&gt; performance improvements. Users can expect much faster (i.e. lower CPU usage) processing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It has been a while since I wrote about &lt;a href="https://apse.io"&gt;APSE&lt;/a&gt; here, and over the past few months I've made a number of big updates to it in version 4.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;First of all, there have been &lt;strong&gt;major&lt;/strong&gt; performance improvements. Users can expect much faster (i.e. lower CPU usage) processing of snapshots. Along with that is an improvement to OCR accuracy.&lt;/p&gt;
&lt;h2&gt;Search parsing&lt;/h2&gt;
&lt;p&gt;Searches are more forgiving. For example, searching &lt;code&gt;bike&lt;/code&gt; will match &lt;code&gt;bikes&lt;/code&gt;, &lt;code&gt;biker&lt;/code&gt;, &lt;code&gt;biked&lt;/code&gt;, &lt;code&gt;biking&lt;/code&gt;, etc. You can still specify exact matches "with quotes." URL components are also handled better - &lt;code&gt;wikipedia&lt;/code&gt; will match anything with that in a web address, you don't need to specify all of &lt;code&gt;en.wikipedia.org&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Archive stuff&lt;/h2&gt;
&lt;p&gt;PDF imports are now supported.&lt;/p&gt;
&lt;p&gt;You can now merge indexes from multiple machines. (Business licenses only.)&lt;/p&gt;
&lt;h2&gt;Fun stuff&lt;/h2&gt;
&lt;p&gt;Latest snapshot: you can jump directly to the most recent snapshot taken, by using the special search &lt;code&gt;latest&lt;/code&gt; or the &lt;code&gt;Go&lt;/code&gt; menu in the app. You can still search for the word latest by combining it with another keyword, using quotes, or adding a space afterward: &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Starred snapshots: you can star snapshots you want easy access to. You can filter on them in searches using the format &lt;code&gt;starred:true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;More Like This: find a snapshot that's not exactly what you're looking for? Click "More Like This" to see snapshots with similar content.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;I think it's a really fantastic app, and this round of updates makes it even better. So if you've read this far, &lt;a href="https://apse.io/buy/"&gt;I encourage you to try it out!&lt;/a&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="apse"></category><category term="search engine"></category></entry><entry><title>TIL: API for saving webpages in the wayback machine</title><link href="https://foxrow.com/til-api-for-saving-webpages-in-the-wayback-machine" rel="alternate"></link><published>2023-04-08T12:00:00-05:00</published><updated>2023-04-08T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-04-08:/til-api-for-saving-webpages-in-the-wayback-machine</id><summary type="html">&lt;p&gt;The &lt;a href="https://archive.org"&gt;Internet Archive's&lt;/a&gt; Wayback machine is a way to save a copy of a public webpage at a point in time. There is an API to submit URLs for them to crawl, but the documentation I found is fragmented and some is out of date. Here is what I have …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://archive.org"&gt;Internet Archive's&lt;/a&gt; Wayback machine is a way to save a copy of a public webpage at a point in time. There is an API to submit URLs for them to crawl, but the documentation I found is fragmented and some is out of date. Here is what I have got working:&lt;/p&gt;
&lt;p&gt;First, you need an account with &lt;a href="https://archive.org/account/signup"&gt;archive.org.&lt;/a&gt; Some of the documentation you can find via Google contradicts this, but I've tried with and without credentials, and unauthenticated doesn't work for me. Signing up is free.&lt;/p&gt;
&lt;p&gt;Once you have an account, log in. You need to generate S3 credentials (as in AWS? It's unclear to me). You do that at &lt;a href="https://archive.org/account/s3.php"&gt;https://archive.org/account/s3.php&lt;/a&gt;. There's two parts to the credentials, an access key and secret key. Both are a medium-ish length random string. These will be used for authentication, via a request header later.&lt;/p&gt;
&lt;p&gt;Now that you have credentials, you can submit save requests for webpages on the wild internet. You do so by submitting an HTTP request to &lt;code&gt;https://web.archive.org/save/&lt;/code&gt;. The full request I used looks like this in cURL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl -X POST -H "Accept: application/json" -H "Authorization: LOW &amp;lt;access key&amp;gt;:&amp;lt;secret key&amp;gt;" -d"url=https://example.org/url-to-save&amp;amp;capture_all=1&amp;amp;delay_wb_availability=1&amp;amp;skip_first_archive=1" "https://web.archive.org/save"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;curl -X POST [...] "https://web.archive.org/save"&lt;/code&gt;: we are submitting an HTTP POST request to archive.org via the command line, using cURL.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-H "Accept: application/json" -H "Authorization: LOW &amp;lt;access_key&amp;gt;:&amp;lt;secret_key&amp;gt;"&lt;/code&gt;: Headers to put in the request. The &lt;code&gt;Authorization&lt;/code&gt; header is required, as far as I can tell, despite some documentation you can google to the contrary. Replace &lt;code&gt;&amp;lt;access_key&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;secret_key&amp;gt;&lt;/code&gt; with your credentials. Note the colon between them. Don't include the literal &lt;code&gt;&amp;lt;&lt;/code&gt; or &lt;code&gt;&amp;gt;&lt;/code&gt; characters in your request. Specifying a JSON response is not required.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-d"url=https://example.org/url-to-save&amp;amp;capture_all=1&amp;amp;delay_wb_availability=1&amp;amp;skip_first_archive=1"&lt;/code&gt;: the payload for the request. &lt;code&gt;url&lt;/code&gt; is well, the URL you want to save. &lt;code&gt;capture_all=1&lt;/code&gt; tells the crawler you want to capture all HTTP responses, even 4XX or 5XX errors. Without this, only 200 OK responses are saved. &lt;code&gt;delay_wb_availability&lt;/code&gt; tells Wayback that you don't need the page saved immediately. If the crawler is overloaded, it will go onto the end of the queue for indexing when load is lower. &lt;code&gt;skip_first_archive&lt;/code&gt; tells Wayback to skip checking if this is the first time the URL has been archived. They say it increases archiving speed, so I went with it.&lt;/p&gt;
&lt;h2&gt;How I got there&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://webapps.stackexchange.com/a/151360/310605"&gt;This stack exchange answer&lt;/a&gt; indicates that the Save Page Now API is how you can programmatically submit URLs for saving. I kind of had to google around for a while, and eventually stumbled on the info that "Save Page Now API" is the magic words. There are other IA API docs, but don't mention anything about Save Page Now that I can find.&lt;/p&gt;
&lt;p&gt;That stackexchange answer links to &lt;a href="https://docs.google.com/document/d/19RJsRncGUw2qHqGGg9lqYZYf7KKXMDL1Mro5o1Qw6QI/edit"&gt;this documentation&lt;/a&gt; in Google Docs (curious to me that it's not on an archive.org URL, or the existing IA API docs). &lt;em&gt;That&lt;/em&gt; page links to &lt;a href="https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA/edit"&gt;more Google Docs&lt;/a&gt; with the actual API specification.&lt;/p&gt;
&lt;h2&gt;Further research&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://web.archive.org/save"&gt;page to manually submit a save request&lt;/a&gt; to Wayback has an option to "Save also in my web archive". Your user account has a (public!) list of pages you have personally saved.&lt;/p&gt;
&lt;p&gt;It seems the form on that page POSTs to the same web.archive.org URL. As best I can tell, after you submit the form, the webpage waits for the indexing request to begin, receives some job ID from Wayback, and then submits another request using that ID and your account info. I have been unable to get this working with the Save Page Now API via any combination of the &lt;code&gt;name&lt;/code&gt; field corresponding to that checkbox.&lt;/p&gt;
&lt;p&gt;If anyone knows if saving to My Web Archives is possible via API, please get in touch!&lt;/p&gt;</content><category term="programming"></category><category term="til"></category><category term="programming"></category><category term="database"></category><category term="internet archive"></category></entry><entry><title>video-to-sqlite</title><link href="https://foxrow.com/video-to-sqlite" rel="alternate"></link><published>2023-01-21T12:00:00-06:00</published><updated>2023-01-21T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2023-01-21:/video-to-sqlite</id><summary type="html">&lt;p&gt;Inspired by Simon Willison's &lt;a href="https://datasette.io/tools"&gt;collection of sqlite tools,&lt;/a&gt; I created
a library for extracting information from a video, and storing it in an sqlite database. It's
called &lt;a href="https://github.com/ryanfox/video-to-sqlite"&gt;video-to-sqlite.&lt;/a&gt; You can also install it
via &lt;code&gt;pip install video-to-sqlite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can use Simon's very nifty tool &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt; to explore the resulting DB …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Inspired by Simon Willison's &lt;a href="https://datasette.io/tools"&gt;collection of sqlite tools,&lt;/a&gt; I created
a library for extracting information from a video, and storing it in an sqlite database. It's
called &lt;a href="https://github.com/ryanfox/video-to-sqlite"&gt;video-to-sqlite.&lt;/a&gt; You can also install it
via &lt;code&gt;pip install video-to-sqlite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can use Simon's very nifty tool &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt; to explore the resulting DB.
Let's use this exploration of John Green's powers of discernment with regard to soda brands:&lt;/p&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/rcOM3H06KI4" title="YouTube video player" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;Create the database like so, and begin serving it locally:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;video-to-sqlite vlogbrothers.db green.mp4
datasette vlogbrothers.db
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we visit &lt;code&gt;localhost:8001&lt;/code&gt; in a browser, we can see there's a little over 9,000 frames in
the video, and metadata about each:&lt;/p&gt;
&lt;p&gt;&lt;img alt="data about individual frames from a vlogbrothers video" class="m-image" src="https://foxrow.com/assets/video-to-sqlite-demo.png"/&gt;&lt;/p&gt;
&lt;p&gt;We can also run custom sql against the database. For example, counting how many keyframes,
predicted frames, and bidirectional predicted frames there are:&lt;/p&gt;
&lt;p&gt;&lt;img alt="custom SQL run against data from a vlogbrothers video" class="m-image" src="https://foxrow.com/assets/video-to-sqlite-custom-sql.png"/&gt;&lt;/p&gt;
&lt;p&gt;This is already pretty interesting. However, we can do even more with this.&lt;/p&gt;
&lt;h2&gt;Creating a custom search engine for video contents&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;video-to-sqlite&lt;/code&gt; supports running a custom function against every frame during extraction.
One could use that hook to OCR the frame and store that along with the rest of the metadata,
for example. You might write that something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;video_to_sqlite&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ocr_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;video_to_sqlite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'vlogbrothers.db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'green.mp4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ocr_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You would then get a db with those text contents associated with the frame:&lt;/p&gt;
&lt;p&gt;&lt;img alt="extracted text from every frame of the vlogbrothers video" class="m-image" src="https://foxrow.com/assets/video-to-sqlite-ocr.png"/&gt;&lt;/p&gt;
&lt;p&gt;But we can still do one better. Datasette has a plugin called &lt;code&gt;datasette-media&lt;/code&gt;.
It lets you serve static files alongside regular datasette responses. For example, viewing images
associated with a URL, like &lt;a href="https://laion-aesthetic.datasette.io/laion-aesthetic-6pls/images"&gt;the imageset used to train Stable Diffusion.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://github.com/ryanfox/datasette-media"&gt;hacked up datasette-media&lt;/a&gt; to return individual
frames from a video on request. Combined with datasette, this can show you the frame in
question for each row. You need to specify some config values for datasette, in a file usually
called &lt;code&gt;metadata.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    "plugins": {
        "datasette-media": {
            "video": {
                "sql": "select filename as filepath from frames where filename=:key",
                "enable_transform": true
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now if you run &lt;code&gt;datasette -m metadata.json vlogbrothers.db&lt;/code&gt; you can get something like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="extracted text and the accompanying image from every frame of the vlogbrothers video" class="m-image" src="https://foxrow.com/assets/video-to-sqlite-frames.png"/&gt;&lt;/p&gt;
&lt;p&gt;Additionally, datasette also functions as a &lt;a href="https://docs.datasette.io/en/stable/json_api.html"&gt;JSON API.&lt;/a&gt;
Combining everything, we can create an API to grep video contents and get the corresponding frames:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/vlogbrothers.json?_shape=arrayfirst&amp;amp;sql=select+%22http%3A%2F%2Flocalhost%3A8001%2F-%2Fmedia%2Fvideo%2F%22+||+filename+||+%22%3Fframe_no%3D%22+||+frame_no+||+%22%26w%3D200%22+as+frame+from+frames+where+pict_type+%3D+%22I%22+order+by+filename%2C+frame_no+limit+101"&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/-/media/video/green.mp4?frame_no=0&amp;amp;w=200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/-/media/video/green.mp4?frame_no=71&amp;amp;w=200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/-/media/video/green.mp4?frame_no=94&amp;amp;w=200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/-/media/video/green.mp4?frame_no=188&amp;amp;w=200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8001/-/media/video/green.mp4?frame_no=300&amp;amp;w=200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sound useful? Interesting? Let me know if you use it for something cool.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Video from &lt;a href="https://www.youtube.com/@vlogbrothers"&gt;Vlogbrothers&lt;/a&gt; used under CC-BY&lt;/em&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="python"></category><category term="sqlite"></category><category term="database"></category><category term="video"></category></entry><entry><title>Remapping keys in windows</title><link href="https://foxrow.com/remapping-keys-in-windows" rel="alternate"></link><published>2022-12-19T12:00:00-06:00</published><updated>2022-12-19T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-12-19:/remapping-keys-in-windows</id><summary type="html">&lt;p&gt;Today I learned about &lt;a href="https://learn.microsoft.com/en-us/windows/powertoys/"&gt;Powertoys,&lt;/a&gt;, a utility application from Microsoft.&lt;/p&gt;
&lt;p&gt;Caps lock isn't a very useful key, but it occupies very valuable keyboard real estate, sitting on home row. Some programmers like mapping ctrl to the caps lock key. Some keyboards even have hardware settings to switch them. Personally, I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I learned about &lt;a href="https://learn.microsoft.com/en-us/windows/powertoys/"&gt;Powertoys,&lt;/a&gt;, a utility application from Microsoft.&lt;/p&gt;
&lt;p&gt;Caps lock isn't a very useful key, but it occupies very valuable keyboard real estate, sitting on home row. Some programmers like mapping ctrl to the caps lock key. Some keyboards even have hardware settings to switch them. Personally, I find it awful for trying to hit ctrl-z, x, c or v.&lt;/p&gt;
&lt;p&gt;But the escape key, I hit a lot. I've known MacOS allows mapping escape to caps lock, but the last time I looked for windows, it seemed like everything was registry hacks and shady applications.&lt;/p&gt;
&lt;p&gt;But with powertoys, I feel good that it's an officially supported, first-party app. And it lets you map arbitrary keys, not just a few modifiers. Powertoys offers a lot other features (the stay-awake one seems like I'll use it on my laptop), but the keyboard remapping alone is good enough to get me on board.&lt;/p&gt;</content><category term="programming"></category><category term="til"></category><category term="programming"></category><category term="windows"></category></entry><entry><title>Disabling the extensions button on Firefox toolbar</title><link href="https://foxrow.com/disabling-the-extensions-button-on-firefox-toolbar" rel="alternate"></link><published>2022-12-13T12:00:00-06:00</published><updated>2022-12-13T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-12-13:/disabling-the-extensions-button-on-firefox-toolbar</id><summary type="html">&lt;p&gt;In Firefox 109, Mozilla introduced a "unified extensions button". Normally, you can fully customize your toolbar, but this button cannot be moved or removed from the toolbar via the usual "Customize Toolbar" method.&lt;/p&gt;
&lt;p&gt;However, it can be removed with an edit to your Firefox config. To remove it, open &lt;code&gt;about …&lt;/code&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;In Firefox 109, Mozilla introduced a "unified extensions button". Normally, you can fully customize your toolbar, but this button cannot be moved or removed from the toolbar via the usual "Customize Toolbar" method.&lt;/p&gt;
&lt;p&gt;However, it can be removed with an edit to your Firefox config. To remove it, open &lt;code&gt;about:config&lt;/code&gt;. There is a boolean setting &lt;code&gt;extensions.unifiedExtensions.enabled&lt;/code&gt; that defaults to true. Change it to false, restart Firefox, and you shouldn't see it in your &lt;/p&gt;
&lt;p&gt;Hopefully Firefox makes it normally customizable in the future, like every other item on the toolbar.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to "Unified extensions toolbar button" in &lt;a href="https://old.reddit.com/r/firefox/comments/zhd40b/weekly_discussion_for_nightly_builds_for_20221210/"&gt;this Reddit post.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="programming"></category><category term="til"></category><category term="programming"></category><category term="firefox"></category></entry><entry><title>Crossfading in MoviePy</title><link href="https://foxrow.com/crossfading-in-moviepy" rel="alternate"></link><published>2022-12-05T12:00:00-06:00</published><updated>2022-12-05T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-12-05:/crossfading-in-moviepy</id><summary type="html">&lt;p&gt;Taking a cue from &lt;a href="https://simonwillison.net/2022/Nov/6/what-to-blog-about/"&gt;Simon Willison,&lt;/a&gt;
a TIL (today I learned) about the &lt;a href="https://github.com/Zulko/moviepy/"&gt;moviepy&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;The library is very useful, but sometimes I have difficulty finding documentation on how to do specific things. For example, recently for NaMoGenMo I wanted to crossfade between 2 clips.&lt;/p&gt;
&lt;p&gt;Suppose you have two 10-second …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Taking a cue from &lt;a href="https://simonwillison.net/2022/Nov/6/what-to-blog-about/"&gt;Simon Willison,&lt;/a&gt;
a TIL (today I learned) about the &lt;a href="https://github.com/Zulko/moviepy/"&gt;moviepy&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;The library is very useful, but sometimes I have difficulty finding documentation on how to do specific things. For example, recently for NaMoGenMo I wanted to crossfade between 2 clips.&lt;/p&gt;
&lt;p&gt;Suppose you have two 10-second video clips, and you want to cross-fade from clip A to clip B over 3 seconds. So clip A starts playing at 00:00, starts fading out at 00:07, and is completely faded out at 00:10. Clip B starts playing at 00:07, immediately starts fading in, and is finished fading in at 00:10. How you could accomplish that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;overlap = 3
final_clip = CompositeVideoClip([
    clip_a.crossfadeout(overlap),
    clip_b.set_start(clip_a.duration - overlap).crossfadein(overlap)
])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="programming"></category><category term="til"></category><category term="programming"></category><category term="video"></category><category term="movie"></category><category term="python"></category><category term="moviepy"></category></entry><entry><title>NaMoGenMo 2022 report</title><link href="https://foxrow.com/namogenmo-2022" rel="alternate"></link><published>2022-11-30T12:00:00-06:00</published><updated>2022-11-30T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-11-30:/namogenmo-2022</id><summary type="html">&lt;p&gt;This November, I worked on a project to &lt;a href="https://namogenmo.github.io/"&gt;create code to generate a movie.&lt;/a&gt; I got the idea from &lt;a href="https://nanogenmo.github.io/"&gt;NaNoGenMo,&lt;/a&gt; which itself is based on &lt;a href="https://nanowrimo.org/"&gt;NaNoWriMo.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My idea was to create a Memento-ized version of an existing movie. If you haven't seen &lt;em&gt;Memento&lt;/em&gt;, it's structured where the order of scenes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This November, I worked on a project to &lt;a href="https://namogenmo.github.io/"&gt;create code to generate a movie.&lt;/a&gt; I got the idea from &lt;a href="https://nanogenmo.github.io/"&gt;NaNoGenMo,&lt;/a&gt; which itself is based on &lt;a href="https://nanowrimo.org/"&gt;NaNoWriMo.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My idea was to create a Memento-ized version of an existing movie. If you haven't seen &lt;em&gt;Memento&lt;/em&gt;, it's structured where the order of scenes is interleaved, so the beginning of the movie is the last thing to happen to the characters chronologically.&lt;/p&gt;
&lt;p&gt;If the order of events happening to the characters is A, B, C... Y, Z, then what you watch is Z, A, Y, B, X, C, etc etc. The end of the movie you watch is when the backwards and forwards scenes meet in the middle. The first-half scenes are in black and white to help the audience a little.&lt;/p&gt;
&lt;p&gt;To accomplish this in a generative fashion, I wrote a library to assist me. It's called &lt;a href="https://pypi.org/project/mementoizer"&gt;mementoizer,&lt;/a&gt; and it's available on PyPI.&lt;/p&gt;
&lt;p&gt;I wanted to use it on a color film to take advantage of the alternating color/black and white structure. Unfortunately, there are not a lot of color feature films in the public domain/permissively licensed. (I do see a few places that indicate the 1952 version of &lt;em&gt;The Snows of Kilimanjaro&lt;/em&gt; is in the public domain in the US, but I am not sure of the validity of those claims. If any copyright lawyers out there can clarify things, please &lt;a href="/about"&gt;get in touch&lt;/a&gt; as I think that would be an interesting movie to memento-ize.)&lt;/p&gt;
&lt;p&gt;Given that constraint, and my desire to have something at least 40 minutes, I chose &lt;a href="https://en.wikipedia.org/wiki/Memphis_Belle:_A_Story_of_a_Flying_Fortress"&gt;Memphis Belle: A Story of a Flying Fortress,&lt;/a&gt; a 1944 film produced by the US government.&lt;/p&gt;
&lt;p&gt;I created the mementoized version with a minimum scene length of 3 minutes. If you install &lt;code&gt;mementoizer&lt;/code&gt;, here is the command I used if you want to replicate the results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mementoize memphis_belle.mp4 --min-scene-length 180
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's not human-perfect edited, but I am very pleased with how well it worked out! It obviously can't tell the difference between a shot cut, and a scene ending, but for a first cut, min-scene-length works pretty well. I think the result is effective and surprising in a good way. The final product:&lt;/p&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/vkgGLhG3ccI" title="YouTube video player" width="560"&gt;&lt;/iframe&gt;</content><category term="programming"></category><category term="programming"></category><category term="video"></category><category term="movie"></category><category term="python"></category><category term="namogenmo"></category></entry><entry><title>Slay the Spire First Run Winrate</title><link href="https://foxrow.com/slay-the-spire-first-run-winrate" rel="alternate"></link><published>2022-11-26T12:00:00-06:00</published><updated>2022-11-26T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-11-26:/slay-the-spire-first-run-winrate</id><summary type="html">&lt;p&gt;A question came up in a discord for Slay the Spire - what is the win percentage of players in their first time playing the game? I thought that was an interesting question, so I looked into it using the big dataset from Mega Crit, the creators of the game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overall …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;A question came up in a discord for Slay the Spire - what is the win percentage of players in their first time playing the game? I thought that was an interesting question, so I looked into it using the big dataset from Mega Crit, the creators of the game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overall, players on a first-time run win about 9.6% of the time.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Details&lt;/h2&gt;
&lt;p&gt;My database is a subset of the full 77 million+ run set, containing mostly runs from after Watcher was released, when the game was near its current state. My database contains 18,209,495 runs total.&lt;/p&gt;
&lt;p&gt;From there, I removed runs that are from beta builds, endless mode, daily mode, or chosen-seed runs. The vast majority of the remaining runs are from builds 2020-01-27 (Watcher update) and 2020-07-30 (the language patch).&lt;/p&gt;
&lt;p&gt;From there, I counted runs where &lt;code&gt;player_experience = 0&lt;/code&gt;. One thing to note is that this means the first time an account has played a run, not necessarily the first time &lt;em&gt;a person&lt;/em&gt; has played a run.&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Patch&lt;/th&gt;
&lt;th&gt;Wins&lt;/th&gt;
&lt;th&gt;Losses&lt;/th&gt;
&lt;th&gt;Winrate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2020-01-27&lt;/td&gt;
&lt;td&gt;2,069&lt;/td&gt;
&lt;td&gt;32,357&lt;/td&gt;
&lt;td&gt;6.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020-07-30&lt;/td&gt;
&lt;td&gt;8,357&lt;/td&gt;
&lt;td&gt;76,709&lt;/td&gt;
&lt;td&gt;9.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overall&lt;/td&gt;
&lt;td&gt;10,426&lt;/td&gt;
&lt;td&gt;109,066&lt;/td&gt;
&lt;td&gt;9.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The all-character winrate at ascension 0 is 9.3%, so I find it interesting that the "average" 
A0 player across all characters is a hair &lt;em&gt;worse&lt;/em&gt; than the average player on their first run.&lt;/p&gt;
&lt;p&gt;I think this is due to at least two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first run is simpler, in that there are less card options in play, and the boss events are scripted.&lt;/li&gt;
&lt;li&gt;The first run is with Ironclad, which has a higher A0 winrate than the other characters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Ironclad winrate at A0 is 10.7%, so at least that right-side up.&lt;/p&gt;
&lt;h3&gt;Questions&lt;/h3&gt;
&lt;h4&gt;Winrate difference&lt;/h4&gt;
&lt;p&gt;The winrate difference between the builds is surprising to me - I think the language patch didn't affect gameplay. If the game hasn't changed, this is very unlikely to have occurred by chance. A chi-squared test puts the odds of that difference occurring randomly at less than .001% (p-value &amp;lt; 0.00001)&lt;/p&gt;
&lt;h4&gt;Non-Ironclad runs&lt;/h4&gt;
&lt;p&gt;Normally, the first run when you play the game is with Ironclad, since none of the other characters are unlocked yet. However, the dataset contains 337 Silent runs, 105 Defect runs, and 60 Watcher runs where &lt;code&gt;player_experience = 0&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Other builds&lt;/h4&gt;
&lt;p&gt;My dataset only encompasses post-Watcher Spire. The consensus is that for high-level play, the game has gotten easier as patches and tweaks have made their way into the game. It would be interesting to see if the winrate for beginners has changed over time as well.&lt;/p&gt;
&lt;p&gt;If you have any ideas, please get in touch to let me know!&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="games"></category><category term="spire"></category></entry><entry><title>Summing an array in SQLite</title><link href="https://foxrow.com/summing-an-array-in-sqlite" rel="alternate"></link><published>2022-08-30T00:00:00-05:00</published><updated>2022-08-30T00:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-08-30:/summing-an-array-in-sqlite</id><summary type="html">&lt;p&gt;Summing the values in an array in SQLite&lt;/p&gt;</summary><content type="html">&lt;p&gt;Say you have a table with an array column in SQLite, and want to calculate the sum of that array for each row.
Suppose your table is named &lt;code&gt;my_data&lt;/code&gt; in SQLite and looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id  |   my_list
-----------------
1   | [ 1, 2, 3 ]
2   | [ 4, 5, 6 ]
3   | [ 7, 8, 9 ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can sum up the values in the &lt;code&gt;my_list&lt;/code&gt; column via the following SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;select t1.id, sum(t2.value) from my_list as t1 join json_each((select my_list from my_data where id = t1.id)) as t2 group by id order by id;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It should return the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;
&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hope it saves you some time.&lt;/p&gt;</content><category term="programming"></category><category term="sql"></category><category term="sqlite"></category><category term="programming"></category></entry><entry><title>SGDQ 2022</title><link href="https://foxrow.com/sgdq-2022" rel="alternate"></link><published>2022-08-16T00:00:00-05:00</published><updated>2022-08-16T00:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-08-16:/sgdq-2022</id><summary type="html">&lt;p&gt;Word clouds generated from runs during the SGDQ 2022 marathon&lt;/p&gt;</summary><content type="html">&lt;p&gt;During a Games Done Quick marathon, runners, and commentators... commentate on the game currently
being run. Commentary for different games has different character - some jokey, some focused on
technical explanation, some rapid-fire play-by-play.&lt;/p&gt;
&lt;p&gt;I wanted to see if you could get a flavor of the commentary for each run from a visualization,
so I generated a word cloud from the transcript of each run.&lt;/p&gt;
&lt;p&gt;A word cloud for each game from SGDQ 2022, in run order:
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Shadow of the Colossus by RUBIEHART in 47:29&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Shadow of the Colossus by RUBIEHART in 47:29 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Shadow of the Colossus by RUBIEHART in 47:29 - Summer Games Done Quick 2022.png" title="Shadow of the Colossus by RUBIEHART in 47:29 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sonic Generations by Allegro in 54:21&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Sonic Generations by Allegro in 54:21 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Sonic Generations by Allegro in 54:21 - Summer Games Done Quick 2022.png" title="Sonic Generations by Allegro in 54:21 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Warioware: Touched! by Mr_Shasta in 31:31&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Warioware: Touched! by Mr_Shasta in 31:31 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Warioware: Touched! by Mr_Shasta in 31:31 - Summer Games Done Quick 2022.png" title="Warioware: Touched! by Mr_Shasta in 31:31 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Rolled Out! by Helix in 20:26&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Rolled Out! by Helix in 20:26 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Rolled Out! by Helix in 20:26 - Summer Games Done Quick 2022.png" title="Rolled Out! by Helix in 20:26 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mass Effect by MikeWave in 1:30:02&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mass Effect by MikeWave in 1:30:02 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mass Effect by MikeWave in 1:30:02 - Summer Games Done Quick 2022.png" title="Mass Effect by MikeWave in 1:30:02 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Kirby and the Forgotten Land by Mr_Shasta in 1:51:20&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Kirby and the Forgotten Land by Mr_Shasta in 1:51:20 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Kirby and the Forgotten Land by Mr_Shasta in 1:51:20 - Summer Games Done Quick 2022.png" title="Kirby and the Forgotten Land by Mr_Shasta in 1:51:20 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pokémon Snap by quo in 30:31&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Pokémon Snap by quo in 30:31 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Pokémon Snap by quo in 30:31 - Summer Games Done Quick 2022.png" title="Pokémon Snap by quo in 30:31 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Spyro the Dragon by Dayoman in 1:29:10&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Spyro the Dragon by Dayoman in 1:29:10 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Spyro the Dragon by Dayoman in 1:29:10 - Summer Games Done Quick 2022.png" title="Spyro the Dragon by Dayoman in 1:29:10 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Perfect Dark by Zero_IF in 51:12&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Perfect Dark by Zero_IF in 51:12 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Perfect Dark by Zero_IF in 51:12 - Summer Games Done Quick 2022.png" title="Perfect Dark by Zero_IF in 51:12 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Crash Bandicoot 2: Cortex Strikes Back by Potty in 1:14:49&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Crash Bandicoot 2: Cortex Strikes Back by Potty in 1:14:49 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Crash Bandicoot 2: Cortex Strikes Back by Potty in 1:14:49 - Summer Games Done Quick 2022.png" title="Crash Bandicoot 2: Cortex Strikes Back by Potty in 1:14:49 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Perspective by AChocolateOrange in 11:40&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Perspective by AChocolateOrange in 11:40 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Perspective by AChocolateOrange in 11:40 - Summer Games Done Quick 2022.png" title="Perspective by AChocolateOrange in 11:40 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Dream Dasher by Bullets in 28:05&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Dream Dasher by Bullets in 28:05 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Dream Dasher by Bullets in 28:05 - Summer Games Done Quick 2022.png" title="Super Dream Dasher by Bullets in 28:05 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Warden: Melody of the Undergrowth by Jaxler in 10:54&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Warden: Melody of the Undergrowth by Jaxler in 10:54 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Warden: Melody of the Undergrowth by Jaxler in 10:54 - Summer Games Done Quick 2022.png" title="Warden: Melody of the Undergrowth by Jaxler in 10:54 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Bonk by LeadOstrich in 25:22&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Bonk by LeadOstrich in 25:22 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Bonk by LeadOstrich in 25:22 - Summer Games Done Quick 2022.png" title="Super Bonk by LeadOstrich in 25:22 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bulk Slash by Aquas in 17:45&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Bulk Slash by Aquas in 17:45 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Bulk Slash by Aquas in 17:45 - Summer Games Done Quick 2022.png" title="Bulk Slash by Aquas in 17:45 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Alien Cabal by tbcr in 6:09&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Alien Cabal by tbcr in 6:09 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Alien Cabal by tbcr in 6:09 - Summer Games Done Quick 2022.png" title="Alien Cabal by tbcr in 6:09 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bubble Bobble Part 2 by JaekRock in 27:24&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Bubble Bobble Part 2 by JaekRock in 27:24 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Bubble Bobble Part 2 by JaekRock in 27:24 - Summer Games Done Quick 2022.png" title="Bubble Bobble Part 2 by JaekRock in 27:24 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Blaster Master by UraniumAnchor and davidtki in 45:31&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Blaster Master by UraniumAnchor and davidtki in 45:31 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Blaster Master by UraniumAnchor and davidtki in 45:31 - Summer Games Done Quick 2022.png" title="Blaster Master by UraniumAnchor and davidtki in 45:31 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ninja Gaiden by The Retro Runner in 11:44&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ninja Gaiden by The Retro Runner in 11:44 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ninja Gaiden by The Retro Runner in 11:44 - Summer Games Done Quick 2022.png" title="Ninja Gaiden by The Retro Runner in 11:44 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Altered Beast by JaekRock and Space Coast Gaming in 6:15&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Altered Beast by JaekRock and Space Coast Gaming in 6:15 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Altered Beast by JaekRock and Space Coast Gaming in 6:15 - Summer Games Done Quick 2022.png" title="Altered Beast by JaekRock and Space Coast Gaming in 6:15 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;McDonald's Treasure Land Adventure by Lizstar in 21:32&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/McDonald's Treasure Land Adventure by Lizstar in 21:32 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/McDonald's Treasure Land Adventure by Lizstar in 21:32 - Summer Games Done Quick 2022.png" title="McDonald's Treasure Land Adventure by Lizstar in 21:32 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;NiGHTS into dreams... by pochilin in 23:10&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/NiGHTS into dreams... by pochilin in 23:10 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/NiGHTS into dreams... by pochilin in 23:10 - Summer Games Done Quick 2022.png" title="NiGHTS into dreams"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Pathless by MaeTheDKC in 1:52:40&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Pathless by MaeTheDKC in 1:52:40 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Pathless by MaeTheDKC in 1:52:40 - Summer Games Done Quick 2022.png" title="The Pathless by MaeTheDKC in 1:52:40 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ty The Tasmanian Tiger by KamaCrimson in 29:20&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ty The Tasmanian Tiger by KamaCrimson in 29:20 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ty The Tasmanian Tiger by KamaCrimson in 29:20 - Summer Games Done Quick 2022.png" title="Ty The Tasmanian Tiger by KamaCrimson in 29:20 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Castlevania 4 by Crunan and DrunkenDraconian in 41:38&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Castlevania 4 by Crunan and DrunkenDraconian in 41:38 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Castlevania 4 by Crunan and DrunkenDraconian in 41:38 - Summer Games Done Quick 2022.png" title="Super Castlevania 4 by Crunan and DrunkenDraconian in 41:38 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Castlevania: Harmony of Dissonance by JupiterClimb in 40:50&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Castlevania: Harmony of Dissonance by JupiterClimb in 40:50 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Castlevania: Harmony of Dissonance by JupiterClimb in 40:50 - Summer Games Done Quick 2022.png" title="Castlevania: Harmony of Dissonance by JupiterClimb in 40:50 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;FAITH: Chapter III by Vynn in 9:48&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/FAITH: Chapter III by Vynn in 9:48 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/FAITH: Chapter III by Vynn in 9:48 - Summer Games Done Quick 2022.png" title="FAITH: Chapter III by Vynn in 9:48 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sly 2: Band of Thieves by Nave357 in 27:38&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Sly 2: Band of Thieves by Nave357 in 27:38 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Sly 2: Band of Thieves by Nave357 in 27:38 - Summer Games Done Quick 2022.png" title="Sly 2: Band of Thieves by Nave357 in 27:38 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Legend of Zelda: Link's Awakening (2019) by TGH, Glan in 1:40:32&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Legend of Zelda: Link's Awakening (2019) by TGH, Glan in 1:40:32 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Legend of Zelda: Link's Awakening (2019) by TGH, Glan in 1:40:32 - Summer Games Done Quick 2022.png" title="The Legend of Zelda: Link's Awakening (2019) by TGH, Glan in 1:40:32 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ICO by SanchoPanda in 1:37:05&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/ICO by SanchoPanda in 1:37:05 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/ICO by SanchoPanda in 1:37:05 - Summer Games Done Quick 2022.png" title="ICO by SanchoPanda in 1:37:05 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Grand Theft Auto: Vice City by KZ_FREW in 58:13&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Grand Theft Auto: Vice City by KZ_FREW in 58:13 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Grand Theft Auto: Vice City by KZ_FREW in 58:13 - Summer Games Done Quick 2022.png" title="Grand Theft Auto: Vice City by KZ_FREW in 58:13 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Phasmophobia by Brossentia, NPC, FlannelKat and Peace Egg in 29:17&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Phasmophobia by Brossentia, NPC, FlannelKat and Peace Egg in 29:17 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Phasmophobia by Brossentia, NPC, FlannelKat and Peace Egg in 29:17 - Summer Games Done Quick 2022.png" title="Phasmophobia by Brossentia, NPC, FlannelKat and Peace Egg in 29:17 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Silent Hill 4: The Room by AnEternalEnigma in 1:03:38&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Silent Hill 4: The Room by AnEternalEnigma in 1:03:38 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Silent Hill 4: The Room by AnEternalEnigma in 1:03:38 - Summer Games Done Quick 2022.png" title="Silent Hill 4: The Room by AnEternalEnigma in 1:03:38 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Five Nights at Freddy's: Security Breach by Ace Delusional in 1:05:30&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Five Nights at Freddy's: Security Breach by Ace Delusional in 1:05:30 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Five Nights at Freddy's: Security Breach by Ace Delusional in 1:05:30 - Summer Games Done Quick 2022.png" title="Five Nights at Freddy's: Security Breach by Ace Delusional in 1:05:30 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Outlast by sharkhat87 in 37:15&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Outlast by sharkhat87 in 37:15 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Outlast by sharkhat87 in 37:15 - Summer Games Done Quick 2022.png" title="Outlast by sharkhat87 in 37:15 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;OMORI by starsmiley in 2:44:22&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/OMORI by starsmiley in 2:44:22 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/OMORI by starsmiley in 2:44:22 - Summer Games Done Quick 2022.png" title="OMORI by starsmiley in 2:44:22 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Greak: Memories of Azur by SaberaMesia in 34:00&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Greak: Memories of Azur by SaberaMesia in 34:00 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Greak: Memories of Azur by SaberaMesia in 34:00 - Summer Games Done Quick 2022.png" title="Greak: Memories of Azur by SaberaMesia in 34:00 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Infernax by DanTheVP in 51:52&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Infernax by DanTheVP in 51:52 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Infernax by DanTheVP in 51:52 - Summer Games Done Quick 2022.png" title="Infernax by DanTheVP in 51:52 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Majuu Ou by Omnigamer in 20:55&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Majuu Ou by Omnigamer in 20:55 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Majuu Ou by Omnigamer in 20:55 - Summer Games Done Quick 2022.png" title="Majuu Ou by Omnigamer in 20:55 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;KAMIKO by cicada in 22:46&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/KAMIKO by cicada in 22:46 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/KAMIKO by cicada in 22:46 - Summer Games Done Quick 2022.png" title="KAMIKO by cicada in 22:46 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Earnest Evans by ZELLLOOO in 9:07&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Earnest Evans by ZELLLOOO in 9:07 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Earnest Evans by ZELLLOOO in 9:07 - Summer Games Done Quick 2022.png" title="Earnest Evans by ZELLLOOO in 9:07 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ninja Warriors by Mega RetroMan in 29:47&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ninja Warriors by Mega RetroMan in 29:47 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ninja Warriors by Mega RetroMan in 29:47 - Summer Games Done Quick 2022.png" title="Ninja Warriors by Mega RetroMan in 29:47 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Battle Axe by Jimmy_Diamonds in 10:11&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Battle Axe by Jimmy_Diamonds in 10:11 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Battle Axe by Jimmy_Diamonds in 10:11 - Summer Games Done Quick 2022.png" title="Battle Axe by Jimmy_Diamonds in 10:11 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Gunvolt Chronicles: Luminous Avenger iX 2 by Benja in 33:19&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Gunvolt Chronicles: Luminous Avenger iX 2 by Benja in 33:19 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Gunvolt Chronicles: Luminous Avenger iX 2 by Benja in 33:19 - Summer Games Done Quick 2022.png" title="Gunvolt Chronicles: Luminous Avenger iX 2 by Benja in 33:19 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mega Man Powered Up by WhiteHat94 in 31:43&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mega Man Powered Up by WhiteHat94 in 31:43 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mega Man Powered Up by WhiteHat94 in 31:43 - Summer Games Done Quick 2022.png" title="Mega Man Powered Up by WhiteHat94 in 31:43 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mega Man Xtreme 2 by FocusSight in 37:55&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mega Man Xtreme 2 by FocusSight in 37:55 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mega Man Xtreme 2 by FocusSight in 37:55 - Summer Games Done Quick 2022.png" title="Mega Man Xtreme 2 by FocusSight in 37:55 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mega Man: The Wily Wars by The Blacktastic in 31:49&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mega Man: The Wily Wars by The Blacktastic in 31:49 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mega Man: The Wily Wars by The Blacktastic in 31:49 - Summer Games Done Quick 2022.png" title="Mega Man: The Wily Wars by The Blacktastic in 31:49 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mega Man 5 by Ppotdot1 in 37:14&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mega Man 5 by Ppotdot1 in 37:14  - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mega Man 5 by Ppotdot1 in 37:14  - Summer Games Done Quick 2022.png" title="Mega Man 5 by Ppotdot1 in 37:14  - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Yakuza: Like a Dragon by Froob in 4:09:52&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Yakuza: Like a Dragon by Froob in 4:09:52 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Yakuza: Like a Dragon by Froob in 4:09:52 - Summer Games Done Quick 2022.png" title="Yakuza: Like a Dragon by Froob in 4:09:52 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Star Wars: Jedi Knight II - Jedi Outcast by CovertMuffin in 46:31&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Star Wars: Jedi Knight II - Jedi Outcast by CovertMuffin in 46:31 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Star Wars: Jedi Knight II - Jedi Outcast by CovertMuffin in 46:31 - Summer Games Done Quick 2022.png" title="Star Wars: Jedi Knight II - Jedi Outcast by CovertMuffin in 46:31 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Halo Infinite by WaifuRuns in 1:26:12&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Halo Infinite by WaifuRuns in 1:26:12 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Halo Infinite by WaifuRuns in 1:26:12 - Summer Games Done Quick 2022.png" title="Halo Infinite by WaifuRuns in 1:26:12 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario Odyssey by -ganon- in 3:21:30&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario Odyssey by -ganon- in 3:21:30 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario Odyssey by -ganon- in 3:21:30 - Summer Games Done Quick 2022.png" title="Super Mario Odyssey by -ganon- in 3:21:30 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mario Party by duck in 39:15&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mario Party by duck in 39:15 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mario Party by duck in 39:15 - Summer Games Done Quick 2022.png" title="Mario Party by duck in 39:15 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Final Fantasy VI Randomizer by drewlith, DoctorDT, seto kiaba and Jexvrok in 1:44:23 - SGDQ 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Final Fantasy VI Randomizer by drewlith, DoctorDT, seto kiaba and Jexvrok in 1:44:23 - SGDQ 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Final Fantasy VI Randomizer by drewlith, DoctorDT, seto kiaba and Jexvrok in 1:44:23 - SGDQ 2022.png" title="Final Fantasy VI Randomizer by drewlith, DoctorDT, seto kiaba and Jexvrok in 1:44:23 - SGDQ 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Klonoa: Door to Phantomile by amoser in 42:43&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Klonoa: Door to Phantomile by amoser in 42:43 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Klonoa: Door to Phantomile by amoser in 42:43 - Summer Games Done Quick 2022.png" title="Klonoa: Door to Phantomile by amoser in 42:43 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Lord of the Rings: The Return of the King by Maxylobes in 1:38:30&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Lord of the Rings: The Return of the King by Maxylobes in 1:38:30 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Lord of the Rings: The Return of the King by Maxylobes in 1:38:30 - Summer Games Done Quick 2022.png" title="The Lord of the Rings: The Return of the King by Maxylobes in 1:38:30 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pac-Man World 2 by Katie4 and McKiddy in 45:37&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Pac-Man World 2 by Katie4 and McKiddy in 45:37 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Pac-Man World 2 by Katie4 and McKiddy in 45:37 - Summer Games Done Quick 2022.png" title="Pac-Man World 2 by Katie4 and McKiddy in 45:37 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;COGEN: SWORD OF REWIND by DemonchildElise in 44:52&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/COGEN: SWORD OF REWIND by DemonchildElise in 44:52 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/COGEN: SWORD OF REWIND by DemonchildElise in 44:52 - Summer Games Done Quick 2022.png" title="COGEN: SWORD OF REWIND by DemonchildElise in 44:52 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PowerSlave Exhumed by Kaos_Wulf in 28:23&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/PowerSlave Exhumed by Kaos_Wulf in 28:23 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/PowerSlave Exhumed by Kaos_Wulf in 28:23 - Summer Games Done Quick 2022.png" title="PowerSlave Exhumed by Kaos_Wulf in 28:23 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Knuckles' Chaotix by Hibnotix in 58:19&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Knuckles' Chaotix by Hibnotix in 58:19 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Knuckles' Chaotix by Hibnotix in 58:19 - Summer Games Done Quick 2022.png" title="Knuckles' Chaotix by Hibnotix in 58:19 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sonic Adventure DX: Director's Cut by Katie4 in 10:07&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Sonic Adventure DX: Director's Cut by Katie4 in 10:07 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Sonic Adventure DX: Director's Cut by Katie4 in 10:07 - Summer Games Done Quick 2022.png" title="Sonic Adventure DX: Director's Cut by Katie4 in 10:07 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sonic Advance by Kirbymastah in 19:03&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Sonic Advance by Kirbymastah in 19:03 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Sonic Advance by Kirbymastah in 19:03 - Summer Games Done Quick 2022.png" title="Sonic Advance by Kirbymastah in 19:03 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;TUNIC by Sunnymuffin in 49:28&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/TUNIC by Sunnymuffin in 49:28 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/TUNIC by Sunnymuffin in 49:28 - Summer Games Done Quick 2022.png" title="TUNIC by Sunnymuffin in 49:28 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Advance Wars by legrandgrand in 54:28&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Advance Wars by legrandgrand in 54:28 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Advance Wars by legrandgrand in 54:28 - Summer Games Done Quick 2022.png" title="Advance Wars by legrandgrand in 54:28 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pokémon Emerald by Keizaron, 360Chrism, Shenanagans, adef in 3:14:04&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Pokémon Emerald by Keizaron, 360Chrism, Shenanagans, adef in 3:14:04 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Pokémon Emerald by Keizaron, 360Chrism, Shenanagans, adef in 3:14:04 - Summer Games Done Quick 2022.png" title="Pokémon Emerald by Keizaron, 360Chrism, Shenanagans, adef in 3:14:04 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Donkey Kong Country: Tropical Freeze by spikevegeta in 1:27:30&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Donkey Kong Country: Tropical Freeze by spikevegeta in 1:27:30 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Donkey Kong Country: Tropical Freeze by spikevegeta in 1:27:30 - Summer Games Done Quick 2022.png" title="Donkey Kong Country: Tropical Freeze by spikevegeta in 1:27:30 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Minecraft: Java Edition by Illumina in 53:32&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Minecraft: Java Edition by Illumina in 53:32 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Minecraft: Java Edition by Illumina in 53:32 - Summer Games Done Quick 2022.png" title="Minecraft: Java Edition by Illumina in 53:32 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario Sunshine by SB_runs in 2:59:24&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario Sunshine by SB_runs in 2:59:24 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario Sunshine by SB_runs in 2:59:24 - Summer Games Done Quick 2022.png" title="Super Mario Sunshine by SB_runs in 2:59:24 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Banjo-Tooie by duck in 36:25&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Banjo-Tooie by duck in 36:25 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Banjo-Tooie by duck in 36:25 - Summer Games Done Quick 2022.png" title="Banjo-Tooie by duck in 36:25 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Devil May Cry by WaifuRuns in 1:31:13&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Devil May Cry by WaifuRuns in 1:31:13 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Devil May Cry by WaifuRuns in 1:31:13 - Summer Games Done Quick 2022.png" title="Devil May Cry by WaifuRuns in 1:31:13 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;DEEEER Simulator: Your Average Everyday Deer Game by Shockwve in 21:20- Summer Games Done Quick 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/DEEEER Simulator: Your Average Everyday Deer Game by Shockwve in 21:20- Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/DEEEER Simulator: Your Average Everyday Deer Game by Shockwve in 21:20- Summer Games Done Quick 2022.png" title="DEEEER Simulator: Your Average Everyday Deer Game by Shockwve in 21:20- Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mi Scusi by Bullets in 13:43&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Mi Scusi by Bullets in 13:43 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Mi Scusi by Bullets in 13:43 - Summer Games Done Quick 2022.png" title="Mi Scusi by Bullets in 13:43 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Turnip Boy Commits Tax Evasion by EldritchWolfie in 34:06&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Turnip Boy Commits Tax Evasion by EldritchWolfie in 34:06 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Turnip Boy Commits Tax Evasion by EldritchWolfie in 34:06 - Summer Games Done Quick 2022.png" title="Turnip Boy Commits Tax Evasion by EldritchWolfie in 34:06 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Jimmie Johnson's Anything With an Engine by Ghoul02, GhostKumo in 16:49-Summer Games Done Quick 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Jimmie Johnson's Anything With an Engine by Ghoul02, GhostKumo in 16:49-Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Jimmie Johnson's Anything With an Engine by Ghoul02, GhostKumo in 16:49-Summer Games Done Quick 2022.png" title="Jimmie Johnson's Anything With an Engine by Ghoul02, GhostKumo in 16:49-Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Gourmet Warriors by Ritzblues in 23:56&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Gourmet Warriors by Ritzblues in 23:56 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Gourmet Warriors by Ritzblues in 23:56 - Summer Games Done Quick 2022.png" title="Gourmet Warriors by Ritzblues in 23:56 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thunder In Paradise by sharif in 34:04&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Thunder In Paradise by sharif in 34:04 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Thunder In Paradise by sharif in 34:04 - Summer Games Done Quick 2022.png" title="Thunder In Paradise by sharif in 34:04 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Incredible Crisis by bramhallthefifth in 55:19&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Incredible Crisis by bramhallthefifth in 55:19 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Incredible Crisis by bramhallthefifth in 55:19 - Summer Games Done Quick 2022.png" title="Incredible Crisis by bramhallthefifth in 55:19 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;King's Quest V: Absence Makes the Heart Go Yonder! by davidtki in 27:46-Summer Games Done Quick 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/King's Quest V: Absence Makes the Heart Go Yonder! by davidtki in 27:46-Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/King's Quest V: Absence Makes the Heart Go Yonder! by davidtki in 27:46-Summer Games Done Quick 2022.png" title="King's Quest V: Absence Makes the Heart Go Yonder! by davidtki in 27:46-Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Little Mermaid by SteveZero01 in 7:08&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Little Mermaid by SteveZero01 in 7:08 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Little Mermaid by SteveZero01 in 7:08 - Summer Games Done Quick 2022.png" title="The Little Mermaid by SteveZero01 in 7:08 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Metal Gear by JosephJoestar316 in 23:22&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Metal Gear by JosephJoestar316 in 23:22 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Metal Gear by JosephJoestar316 in 23:22 - Summer Games Done Quick 2022.png" title="Metal Gear by JosephJoestar316 in 23:22 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Lumione by Breakdown and Elipsis in 38:00&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Lumione by Breakdown and Elipsis in 38:00 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Lumione by Breakdown and Elipsis in 38:00 - Summer Games Done Quick 2022.png" title="Lumione by Breakdown and Elipsis in 38:00 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Kirby Tilt 'n' Tumble by swordsmankirby in 42:41&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Kirby Tilt 'n' Tumble by swordsmankirby in 42:41 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Kirby Tilt 'n' Tumble by swordsmankirby in 42:41 - Summer Games Done Quick 2022.png" title="Kirby Tilt 'n' Tumble by swordsmankirby in 42:41 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Xenoblade Chronicles 2: Torna by Enel in 2:40:53&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Xenoblade Chronicles 2: Torna by Enel in 2:40:53 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Xenoblade Chronicles 2: Torna by Enel in 2:40:53 - Summer Games Done Quick 2022.png" title="Xenoblade Chronicles 2: Torna by Enel in 2:40:53 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Slime Rancher by MaddyInc in 16:36&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Slime Rancher by MaddyInc in 16:36 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Slime Rancher by MaddyInc in 16:36 - Summer Games Done Quick 2022.png" title="Slime Rancher by MaddyInc in 16:36 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Solar Ash by bryonato in 1:03:27&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Solar Ash by bryonato in 1:03:27 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Solar Ash by bryonato in 1:03:27 - Summer Games Done Quick 2022.png" title="Solar Ash by bryonato in 1:03:27 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Monster Hunter Rise by JalBagel in 1:09:29&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Monster Hunter Rise by JalBagel in 1:09:29 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Monster Hunter Rise by JalBagel in 1:09:29 - Summer Games Done Quick 2022.png" title="Monster Hunter Rise by JalBagel in 1:09:29 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;StepMania_NotITG by SpootyBiscuit and WinDEU in 43:33&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/StepMania_NotITG by SpootyBiscuit and WinDEU in 43:33 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/StepMania_NotITG by SpootyBiscuit and WinDEU in 43:33 - Summer Games Done Quick 2022.png" title="StepMania_NotITG by SpootyBiscuit and WinDEU in 43:33 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;SOUND VOLTEX EXCEED GEAR by leviern in 1:19:00&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/SOUND VOLTEX EXCEED GEAR by leviern in 1:19:00 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/SOUND VOLTEX EXCEED GEAR by leviern in 1:19:00 - Summer Games Done Quick 2022.png" title="SOUND VOLTEX EXCEED GEAR by leviern in 1:19:00 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Celeste by GrosHiken in 56:18&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Celeste by GrosHiken in 56:18 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Celeste by GrosHiken in 56:18 - Summer Games Done Quick 2022.png" title="Celeste by GrosHiken in 56:18 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Borderlands 2 by Deceptix_ in 1:02:59&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Borderlands 2 by Deceptix_ in 1:02:59 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Borderlands 2 by Deceptix_ in 1:02:59 - Summer Games Done Quick 2022.png" title="Borderlands 2 by Deceptix_ in 1:02:59 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Doom Eternal by MuteD6 in 2:00:20&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Doom Eternal by MuteD6 in 2:00:20 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Doom Eternal by MuteD6 in 2:00:20 - Summer Games Done Quick 2022.png" title="Doom Eternal by MuteD6 in 2:00:20 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Control by DemonicRobots in 1:36:04&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Control by DemonicRobots in 1:36:04 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Control by DemonicRobots in 1:36:04 - Summer Games Done Quick 2022.png" title="Control by DemonicRobots in 1:36:04 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Liminal Ranger by Ozmourn in 10:07&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Liminal Ranger by Ozmourn in 10:07 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Liminal Ranger by Ozmourn in 10:07 - Summer Games Done Quick 2022.png" title="Liminal Ranger by Ozmourn in 10:07 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy's Humble Burger Farm by teddyras in 29:30&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Happy's Humble Burger Farm by teddyras in 29:30 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Happy's Humble Burger Farm by teddyras in 29:30 - Summer Games Done Quick 2022.png" title="Happy's Humble Burger Farm by teddyras in 29:30 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Alisa by juh0rse in 52:04&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Alisa by juh0rse in 52:04 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Alisa by juh0rse in 52:04 - Summer Games Done Quick 2022.png" title="Alisa by juh0rse in 52:04 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ato by Glitchiness in 24:37&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ato by Glitchiness in 24:37 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ato by Glitchiness in 24:37 - Summer Games Done Quick 2022.png" title="Ato by Glitchiness in 24:37 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Magic Trick by Biglaw in 7:54&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Magic Trick by Biglaw in 7:54 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Magic Trick by Biglaw in 7:54 - Summer Games Done Quick 2022.png" title="Magic Trick by Biglaw in 7:54 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Shovel Knight: Pocket Dungeon by DanTheVP in 13:56&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Shovel Knight: Pocket Dungeon by DanTheVP in 13:56 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Shovel Knight: Pocket Dungeon by DanTheVP in 13:56 - Summer Games Done Quick 2022.png" title="Shovel Knight: Pocket Dungeon by DanTheVP in 13:56 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Satisfactory by Epiphane in 22:38&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Satisfactory by Epiphane in 22:38 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Satisfactory by Epiphane in 22:38 - Summer Games Done Quick 2022.png" title="Satisfactory by Epiphane in 22:38 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Chocobo GP by Obiyo and groggydog in 32:32&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Chocobo GP by Obiyo and groggydog in 32:32 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Chocobo GP by Obiyo and groggydog in 32:32 - Summer Games Done Quick 2022.png" title="Chocobo GP by Obiyo and groggydog in 32:32 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pokémon Shining Pearl by eddaket in 3:40:13&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Pokémon Shining Pearl by eddaket in 3:40:13 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Pokémon Shining Pearl by eddaket in 3:40:13 - Summer Games Done Quick 2022.png" title="Pokémon Shining Pearl by eddaket in 3:40:13 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Donkey Kong Country by Tonkotsu in 29:38&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Donkey Kong Country by Tonkotsu in 29:38 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Donkey Kong Country by Tonkotsu in 29:38 - Summer Games Done Quick 2022.png" title="Donkey Kong Country by Tonkotsu in 29:38 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;F-Zero GX by Artful Hobbes in 29:42&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/F-Zero GX by Artful Hobbes in 29:42 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/F-Zero GX by Artful Hobbes in 29:42 - Summer Games Done Quick 2022.png" title="F-Zero GX by Artful Hobbes in 29:42 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Legend of Zelda: Ocarina of Time by spikevegeta,Nukkuler in 2:25:23-Summer Games Done Quick 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Legend of Zelda: Ocarina of Time by spikevegeta,Nukkuler in 2:25:23-Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Legend of Zelda: Ocarina of Time by spikevegeta,Nukkuler in 2:25:23-Summer Games Done Quick 2022.png" title="The Legend of Zelda: Ocarina of Time by spikevegeta,Nukkuler in 2:25:23-Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Portal 2 TAS by Can't Even and mlugg in 47:13&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Portal 2 TAS by Can't Even and mlugg in 47:13 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Portal 2 TAS by Can't Even and mlugg in 47:13 - Summer Games Done Quick 2022.png" title="Portal 2 TAS by Can't Even and mlugg in 47:13 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ocarina of Time TAS by dwangoAC, TASBot, Savestate, Sauraen in 53:05&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ocarina of Time TAS by dwangoAC, TASBot, Savestate, Sauraen in 53:05 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ocarina of Time TAS by dwangoAC, TASBot, Savestate, Sauraen in 53:05 - Summer Games Done Quick 2022.png" title="Ocarina of Time TAS by dwangoAC, TASBot, Savestate, Sauraen in 53:05 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Celeste.SMC by MrMightyMouse_ in 22:20&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Celeste.SMC by MrMightyMouse_ in 22:20 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Celeste.SMC by MrMightyMouse_ in 22:20 - Summer Games Done Quick 2022.png" title="Celeste"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario World by Barbarian, juzcook, CarlSagan42, shovda, Shoujo, ThirdWall in 1:23:00- SGDQ2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario World by Barbarian, juzcook, CarlSagan42, shovda, Shoujo, ThirdWall in 1:23:00- SGDQ2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario World by Barbarian, juzcook, CarlSagan42, shovda, Shoujo, ThirdWall in 1:23:00- SGDQ2022.png" title="Super Mario World by Barbarian, juzcook, CarlSagan42, shovda, Shoujo, ThirdWall in 1:23:00- SGDQ2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ratchet and Clank: Going Commando by Xem in 1:15:48&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Ratchet and Clank: Going Commando by Xem in 1:15:48 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Ratchet and Clank: Going Commando by Xem in 1:15:48 - Summer Games Done Quick 2022.png" title="Ratchet and Clank: Going Commando by Xem in 1:15:48 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Left 4 Dead 2 by WaifuRuns, aciidz, RsKiller and JurasPatryk in 50:04&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Left 4 Dead 2 by WaifuRuns, aciidz, RsKiller and JurasPatryk in 50:04 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Left 4 Dead 2 by WaifuRuns, aciidz, RsKiller and JurasPatryk in 50:04 - Summer Games Done Quick 2022.png" title="Left 4 Dead 2 by WaifuRuns, aciidz, RsKiller and JurasPatryk in 50:04 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Final Fantasy X by FoxyJira in 3:53:48&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Final Fantasy X by FoxyJira in 3:53:48 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Final Fantasy X by FoxyJira in 3:53:48 - Summer Games Done Quick 2022.png" title="Final Fantasy X by FoxyJira in 3:53:48 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Metal Gear Solid 2: Sons of Liberty by dlimes13 in 1:11:52&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Metal Gear Solid 2: Sons of Liberty by dlimes13 in 1:11:52 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Metal Gear Solid 2: Sons of Liberty by dlimes13 in 1:11:52 - Summer Games Done Quick 2022.png" title="Metal Gear Solid 2: Sons of Liberty by dlimes13 in 1:11:52 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Meat Boy by shredberg in 1:07:14&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Meat Boy by shredberg in 1:07:14 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Meat Boy by shredberg in 1:07:14 - Summer Games Done Quick 2022.png" title="Super Meat Boy by shredberg in 1:07:14 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Legend of Zelda: The Wind Waker by CLG Linkus7 in 2:47:57&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/The Legend of Zelda: The Wind Waker by CLG Linkus7 in 2:47:57 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/The Legend of Zelda: The Wind Waker by CLG Linkus7 in 2:47:57 - Summer Games Done Quick 2022.png" title="The Legend of Zelda: The Wind Waker by CLG Linkus7 in 2:47:57 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario All-Stars + Super Mario World by SethBling, IsoFrieze in 11:55 - SGDQ 2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario All-Stars + Super Mario World by SethBling, IsoFrieze in 11:55 - SGDQ 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario All-Stars + Super Mario World by SethBling, IsoFrieze in 11:55 - SGDQ 2022.png" title="Super Mario All-Stars + Super Mario World by SethBling, IsoFrieze in 11:55 - SGDQ 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Metroid Dread by kekumanshoyu in 1:20:33&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Metroid Dread by kekumanshoyu in 1:20:33 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Metroid Dread by kekumanshoyu in 1:20:33 - Summer Games Done Quick 2022.png" title="Metroid Dread by kekumanshoyu in 1:20:33 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Kaizo Super Metroid by Oatsngoats in 2:28:19&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Kaizo Super Metroid by Oatsngoats in 2:28:19 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Kaizo Super Metroid by Oatsngoats in 2:28:19 - Summer Games Done Quick 2022.png" title="Kaizo Super Metroid by Oatsngoats in 2:28:19 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario Maker 2 by PangaeaPanga, juzcook, CarlSagan42, Aurateur, Shoujo, TanukiDan - SGDQ2022&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario Maker 2 by PangaeaPanga, juzcook, CarlSagan42, Aurateur, Shoujo, TanukiDan - SGDQ2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario Maker 2 by PangaeaPanga, juzcook, CarlSagan42, Aurateur, Shoujo, TanukiDan - SGDQ2022.png" title="Super Mario Maker 2 by PangaeaPanga, juzcook, CarlSagan42, Aurateur, Shoujo, TanukiDan - SGDQ2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Super Mario 64 by Simply in 1:40:17&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Super Mario 64 by Simply in 1:40:17 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Super Mario 64 by Simply in 1:40:17 - Summer Games Done Quick 2022.png" title="Super Mario 64 by Simply in 1:40:17 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pokémon Brilliant Diamond by eddaket in 17:34&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Pokémon Brilliant Diamond by eddaket in 17:34 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Pokémon Brilliant Diamond by eddaket in 17:34 - Summer Games Done Quick 2022.png" title="Pokémon Brilliant Diamond by eddaket in 17:34 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Elden Ring by catalystz in 1:53:24&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Elden Ring by catalystz in 1:53:24 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Elden Ring by catalystz in 1:53:24 - Summer Games Done Quick 2022.png" title="Elden Ring by catalystz in 1:53:24 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Elden Ring by HYP3RSOMNIAC in 33:58&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/Elden Ring by HYP3RSOMNIAC in 33:58 - Summer Games Done Quick 2022.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/Elden Ring by HYP3RSOMNIAC in 33:58 - Summer Games Done Quick 2022.png" title="Elden Ring by HYP3RSOMNIAC in 33:58 - Summer Games Done Quick 2022"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Non-run time. Runner intros, interviews, time between runs, etc.&lt;/em&gt;
&lt;a class="sgdq2022 cboxElement" href="/assets/gallery/sgdq2022/non_run_words.png"&gt;&lt;img class="m-image" loading="lazy" src="/assets/gallery/sgdq2022/non_run_words.png" title="non_run_words"/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;</content><category term="programming"></category><category term="photo"></category><category term="games"></category><category term="gdq"></category><category term="python"></category><category term="programming"></category></entry><entry><title>NaMoGenMo</title><link href="https://foxrow.com/namogenmo" rel="alternate"></link><published>2022-06-23T12:00:00-05:00</published><updated>2022-06-23T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-06-23:/namogenmo</id><summary type="html">&lt;p&gt;You may have heard of NaNoWriMo, aka &lt;a href="https://nanowrimo.org/"&gt;National Novel Writing Month.&lt;/a&gt; It's a challenge where every November, participants work to write a novel, defined as 50,000 words of fiction.&lt;/p&gt;
&lt;p&gt;You may not have heard of NaNoGenMo, &lt;a href="https://nanogenmo.github.io/"&gt;National Novel Generation Month,&lt;/a&gt;, where participants write code to &lt;em&gt;generate&lt;/em&gt; 50,000 words …&lt;/p&gt;</summary><content type="html">&lt;p&gt;You may have heard of NaNoWriMo, aka &lt;a href="https://nanowrimo.org/"&gt;National Novel Writing Month.&lt;/a&gt; It's a challenge where every November, participants work to write a novel, defined as 50,000 words of fiction.&lt;/p&gt;
&lt;p&gt;You may not have heard of NaNoGenMo, &lt;a href="https://nanogenmo.github.io/"&gt;National Novel Generation Month,&lt;/a&gt;, where participants write code to &lt;em&gt;generate&lt;/em&gt; 50,000 words of fiction. They define "novel" very loosely:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The "novel" is defined however you want. It could be 50,000 repetitions of the word "meow". It could literally grab a random novel from Project Gutenberg. It doesn't matter, as long as it's 50k+ words.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The idea occurred to me that the same is possible for video. "Movie" even works in the abbreviation - and so now we have &lt;a href="https://namogenmo.github.io/"&gt;NaMoGenMo.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The Academy defines a feature-length film as at least 40 minutes, so that is the definition I'm using for movie. (Though this is art, so it's hard to "do it wrong" - you could elect to create any length you want. Maybe we end up with a Short Film category as well.)&lt;/p&gt;
&lt;p&gt;I am planning to run it during November, since that is when the other two Mo's happen. It also gives time to prepare.&lt;/p&gt;
&lt;p&gt;As with the other events, there's really only one rule - that you share at least one movie and also your source code at the end.&lt;/p&gt;
&lt;p&gt;I have created &lt;a href="https://github.com/NaMoGenMo/2022"&gt;a repo&lt;/a&gt; for the 2022 edition. You can use &lt;a href="https://github.com/NaMoGenMo/2022/issues"&gt;Github's issues&lt;/a&gt; to track progress, register your participation, ask for help, etc.&lt;/p&gt;
&lt;p&gt;So this November, you are invited to join me in moviemaking. Break a leg.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="video"></category></entry><entry><title>Solr and WSL</title><link href="https://foxrow.com/solr-and-wsl" rel="alternate"></link><published>2022-06-21T12:00:00-05:00</published><updated>2022-06-21T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-06-21:/solr-and-wsl</id><summary type="html">&lt;p&gt;Recently, I was trying to use Apache Solr on my WSL install of Ubuntu 20.04. Following &lt;a href="https://solr.apache.org/guide/solr/latest/deployment-guide/installing-solr.html"&gt;the docs,&lt;/a&gt; I could download and start Solr, but visiting &lt;code&gt;127.0.0.1:8983/solr/&lt;/code&gt; from a browser in Windows gave me an "unable to connect" error. The solr logs showed no …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently, I was trying to use Apache Solr on my WSL install of Ubuntu 20.04. Following &lt;a href="https://solr.apache.org/guide/solr/latest/deployment-guide/installing-solr.html"&gt;the docs,&lt;/a&gt; I could download and start Solr, but visiting &lt;code&gt;127.0.0.1:8983/solr/&lt;/code&gt; from a browser in Windows gave me an "unable to connect" error. The solr logs showed no errors, and the access log didn't even show an incoming request. &lt;code&gt;curl&lt;/code&gt;ing the same URL from inside WSL gave a normal response.&lt;/p&gt;
&lt;p&gt;It turned out WSL was not opening the port to localhost on Windows. To fix it, specify the command line argument &lt;code&gt;-Dsolr.jetty.host&lt;/code&gt;. For example, to listen on all interfaces:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;solr-9.0.0$ bin/solr start -h 0.0.0.0 -Dsolr.jetty.host=0.0.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you prefer to not listen on all interfaces, you can also use the internal WSL IP address, in the &lt;code&gt;172.&lt;/code&gt; range. This can be found in WSL in the result of &lt;code&gt;ip addr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To verify it worked, look for &lt;code&gt;0.0.0.0&lt;/code&gt; (or the IP/hostname you specified) in the Solr log with something like,
&lt;code&gt;o.e.j.s.AbstractConnector Started ServerConnector@0a1b2c3d{HTTP/1.1, (http/1.1, h2c)}{0.0.0.0:8983}&lt;/code&gt;. You can also check Resource Monitor in Windows, in the Network tab, under "Listening Ports". Scroll down to 8983 and you should see your process.&lt;/p&gt;
&lt;p&gt;Neither the Solr 9 &lt;a href="https://solr.apache.org/guide/solr/latest/deployment-guide/installing-solr.html"&gt;getting started&lt;/a&gt; documentation nor the &lt;a href="https://solr.apache.org/guide/solr/latest/deployment-guide/solr-control-script-reference.html#start-parameters"&gt;command line reference&lt;/a&gt; mention this flag. They do list the &lt;code&gt;-h&lt;/code&gt; flag for setting a hostname, but it doesn't appear to be sufficient? I ran into the issue because I was using WSL, but surely there are Solr deploys in the wild that need to set a hostname. Anyone with inside information feel free to update me.&lt;/p&gt;
&lt;p&gt;Hope it saves you some time.&lt;/p&gt;
&lt;p&gt;Written for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10&lt;/li&gt;
&lt;li&gt;Ubuntu 20.04 on WSL 2&lt;/li&gt;
&lt;li&gt;Solr 9&lt;/li&gt;
&lt;/ul&gt;</content><category term="programming"></category><category term="programming"></category><category term="java"></category><category term="search"></category></entry><entry><title>vid2emoji</title><link href="https://foxrow.com/vid2emoji" rel="alternate"></link><published>2022-05-16T12:00:00-05:00</published><updated>2022-05-16T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-05-16:/vid2emoji</id><summary type="html">&lt;p&gt;A software tool for converting video to emojis&lt;/p&gt;</summary><content type="html">&lt;p&gt;Video quality on computers is getting better all the time. Higher-density displays, more bandwidth, better codecs.&lt;/p&gt;
&lt;p&gt;But what if you wanted video made out of emojis? Your wait is over - enter vid2emoji:&lt;/p&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/hLpJeg21-Iw" title="YouTube video player" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;It’s a tool to convert videos to… ok they’re still videos but now it’s made out of emojis.&lt;/p&gt;
&lt;p&gt;You can download it from pypi via &lt;code&gt;pip install vid2emoji&lt;/code&gt;. Once it's installed, you can use it with
&lt;code&gt;vid2emoji &amp;lt;your video file&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The source code is also available to check out &lt;a href="https://github.com/ryanfox/vid2emoji"&gt;on GitHub.&lt;/a&gt; If you put it to use, share what you make!&lt;/p&gt;</content><category term="programming"></category><category term="video"></category><category term="python"></category><category term="programming"></category></entry><entry><title>Yesterday's Wordle</title><link href="https://foxrow.com/wordle" rel="alternate"></link><published>2022-04-02T12:00:00-05:00</published><updated>2022-04-02T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-04-02:/wordle</id><summary type="html">&lt;p&gt;Yesterday's answer to Wordle, updated daily&lt;/p&gt;</summary><content type="html">&lt;h1 id="answer"&gt;&lt;/h1&gt;
&lt;p&gt;&lt;br/&gt;
&lt;em&gt;Update: The New York Times has an editor now who sometimes hand-selects words, e.g. FEAST on Thanksgiving.
Given the frequent changes, this list is now unmaintained, and the word above is probably incorrect.
Feel free to continue using it if you want a random-ish word for your first guess.&lt;/em&gt;
&lt;br/&gt;
That word ☝️ is yesterday's &lt;a href="https://www.nytimes.com/games/wordle/index.html"&gt;Wordle&lt;/a&gt; answer. Use it as your first guess for an extra challenge.&lt;/p&gt;
&lt;p&gt;This page updates at midnight your local time, so you always see yesterday's word.&lt;/p&gt;
&lt;script&gt;
var answers = ["cigar", "rebut", "sissy", "humph", "awake", "blush", "focal", "evade", "naval", "serve", "heath", "dwarf", "model", "karma", "stink", "grade", "quiet", "bench", "abate", "feign", "major", "death", "fresh", "crust", "stool", "colon", "abase", "marry", "react", "batty", "pride", "floss", "helix", "croak", "staff", "paper", "unfed", "whelp", "trawl", "outdo", "adobe", "crazy", "sower", "repay", "digit", "crate", "cluck", "spike", "mimic", "pound", "maxim", "linen", "unmet", "flesh", "booby", "forth", "first", "stand", "belly", "ivory", "seedy", "print", "yearn", "drain", "bribe", "stout", "panel", "crass", "flume", "offal", "agree", "error", "swirl", "argue", "bleed", "delta", "flick", "totem", "wooer", "front", "shrub", "parry", "biome", "lapel", "start", "greet", "goner", "golem", "lusty", "loopy", "round", "audit", "lying", "gamma", "labor", "islet", "civic", "forge", "corny", "moult", "basic", "salad", "agate", "spicy", "spray", "essay", "fjord", "spend", "kebab", "guild", "aback", "motor", "alone", "hatch", "hyper", "thumb", "dowry", "ought", "belch", "dutch", "pilot", "tweed", "comet", "jaunt", "enema", "steed", "abyss", "growl", "fling", "dozen", "boozy", "erode", "world", "gouge", "click", "briar", "great", "altar", "pulpy", "blurt", "coast", "duchy", "groin", "fixer", "group", "rogue", "badly", "smart", "pithy", "gaudy", "chill", "heron", "vodka", "finer", "surer", "radio", "rouge", "perch", "retch", "wrote", "clock", "tilde", "store", "prove", "bring", "solve", "cheat", "grime", "exult", "usher", "epoch", "triad", "break", "rhino", "viral", "conic", "masse", "sonic", "vital", "trace", "using", "peach", "champ", "baton", "brake", "pluck", "craze", "gripe", "weary", "picky", "acute", "ferry", "aside", "tapir", "troll", "unify", "rebus", "boost", "truss", "siege", "tiger", "banal", "slump", "crank", "gorge", "query", "drink", "favor", "abbey", "tangy", "panic", "solar", "shire", "proxy", "point", "robot", "prick", "wince", "crimp", "knoll", "sugar", "whack", "mount", "perky", "could", "wrung", "light", "those", "moist", "shard", "pleat", "aloft", "skill", "elder", "frame", "humor", "pause", "ulcer", "ultra", "robin", "cynic", "aroma", "caulk", "shake", "dodge", "swill", "tacit", "other", "thorn", "trove", "bloke", "vivid", "spill", "chant", "choke", "rupee", "nasty", "mourn", "ahead", "brine", "cloth", "hoard", "sweet", "month", "lapse", "watch", "today", "focus", "smelt", "tease", "cater", "movie", "saute", "allow", "renew", "their", "slosh", "purge", "chest", "depot", "epoxy", "nymph", "found", "shall", "stove", "lowly", "snout", "trope", "fewer", "shawl", "natal", "comma", "foray", "scare", "stair", "black", "squad", "royal", "chunk", "mince", "shame", "cheek", "ample", "flair", "foyer", "cargo", "oxide", "plant", "olive", "inert", "askew", "heist", "shown", "zesty", "trash", "larva", "forgo", "story", "hairy", "train", "homer", "badge", "midst", "canny", "shine", "gecko", "farce", "slung", "tipsy", "metal", "yield", "delve", "being", "scour", "glass", "gamer", "scrap", "money", "hinge", "album", "vouch", "asset", "tiara", "crept", "bayou", "atoll", "manor", "creak", "showy", "phase", "froth", "depth", "gloom", "flood", "trait", "girth", "piety", "goose", "float", "donor", "atone", "primo", "apron", "blown", "cacao", "loser", "input", "gloat", "awful", "brink", "smite", "beady", "rusty", "retro", "droll", "gawky", "hutch", "pinto", "egret", "lilac", "sever", "field", "fluff", "agape", "voice", "stead", "berth", "madam", "night", "bland", "liver", "wedge", "roomy", "wacky", "flock", "angry", "trite", "aphid", "tryst", "midge", "power", "elope", "cinch", "motto", "stomp", "upset", "bluff", "cramp", "quart", "coyly", "youth", "rhyme", "buggy", "alien", "smear", "unfit", "patty", "cling", "glean", "label", "hunky", "khaki", "poker", "gruel", "twice", "twang", "shrug", "treat", "waste", "merit", "woven", "needy", "clown", "irony", "ruder", "gauze", "chief", "onset", "prize", "fungi", "charm", "gully", "inter", "whoop", "taunt", "leery", "class", "theme", "lofty", "tibia", "booze", "alpha", "thyme", "doubt", "parer", "chute", "stick", "trice", "alike", "recap", "saint", "glory", "grate", "admit", "brisk", "soggy", "usurp", "scald", "scorn", "leave", "twine", "sting", "bough", "marsh", "sloth", "dandy", "vigor", "howdy", "enjoy", "valid", "ionic", "equal", "floor", "catch", "spade", "stein", "exist", "quirk", "denim", "grove", "spiel", "mummy", "fault", "foggy", "flout", "carry", "sneak", "libel", "waltz", "aptly", "piney", "inept", "aloud", "photo", "dream", "stale", "unite", "snarl", "baker", "there", "glyph", "pooch", "hippy", "spell", "folly", "louse", "gulch", "vault", "godly", "threw", "fleet", "grave", "inane", "shock", "crave", "spite", "valve", "skimp", "claim", "rainy", "musty", "pique", "daddy", "quasi", "arise", "aging", "valet", "opium", "avert", "stuck", "recut", "mulch", "genre", "plume", "rifle", "count", "incur", "total", "wrest", "mocha", "deter", "study", "lover", "safer", "rivet", "funny", "smoke", "mound", "undue", "sedan", "pagan", "swine", "guile", "gusty", "equip", "tough", "canoe", "chaos", "covet", "human", "udder", "lunch", "blast", "stray", "manga", "melee", "lefty", "quick", "paste", "given", "octet", "risen", "groan", "leaky", "grind", "carve", "loose", "sadly", "spilt", "apple", "slack", "honey", "final", "sheen", "eerie", "minty", "slick", "derby", "wharf", "spelt", "coach", "erupt", "singe", "price", "spawn", "fairy", "jiffy", "filmy", "stack", "chose", "sleep", "ardor", "nanny", "niece", "woozy", "handy", "grace", "ditto", "stank", "cream", "usual", "diode", "valor", "angle", "ninja", "muddy", "chase", "reply", "prone", "spoil", "heart", "shade", "diner", "arson", "onion", "sleet", "dowel", "couch", "palsy", "bowel", "smile", "evoke", "creek", "lance", "eagle", "idiot", "siren", "built", "embed", "award", "dross", "annul", "goody", "frown", "patio", "laden", "humid", "elite", "lymph", "edify", "might", "reset", "visit", "gusto", "purse", "vapor", "crock", "write", "sunny", "loath", "chaff", "slide", "queer", "venom", "stamp", "sorry", "still", "acorn", "aping", "pushy", "tamer", "hater", "mania", "awoke", "brawn", "swift", "exile", "birch", "lucky", "freer", "risky", "ghost", "plier", "lunar", "winch", "snare", "nurse", "house", "borax", "nicer", "lurch", "exalt", "about", "savvy", "toxin", "tunic", "pried", "inlay", "chump", "lanky", "cress", "eater", "elude", "cycle", "kitty", "boule", "moron", "tenet", "place", "lobby", "plush", "vigil", "index", "blink", "clung", "qualm", "croup", "clink", "juicy", "stage", "decay", "nerve", "flier", "shaft", "crook", "clean", "china", "ridge", "vowel", "gnome", "snuck", "icing", "spiny", "rigor", "snail", "flown", "rabid", "prose", "thank", "poppy", "budge", "fiber", "moldy", "dowdy", "kneel", "track", "caddy", "quell", "dumpy", "paler", "swore", "rebar", "scuba", "splat", "flyer", "horny", "mason", "doing", "ozone", "amply", "molar", "ovary", "beset", "queue", "cliff", "magic", "truce", "sport", "fritz", "edict", "twirl", "verse", "llama", "eaten", "range", "whisk", "hovel", "rehab", "macaw", "sigma", "spout", "verve", "sushi", "dying", "fetid", "brain", "buddy", "thump", "scion", "candy", "chord", "basin", "march", "crowd", "arbor", "gayly", "musky", "stain", "dally", "bless", "bravo", "stung", "title", "ruler", "kiosk", "blond", "ennui", "layer", "fluid", "tatty", "score", "cutie", "zebra", "barge", "matey", "bluer", "aider", "shook", "river", "privy", "betel", "frisk", "bongo", "begun", "azure", "weave", "genie", "sound", "glove", "braid", "scope", "wryly", "rover", "assay", "ocean", "bloom", "irate", "later", "woken", "silky", "wreck", "dwelt", "slate", "smack", "solid", "amaze", "hazel", "wrist", "jolly", "globe", "flint", "rouse", "civil", "vista", "relax", "cover", "alive", "beech", "jetty", "bliss", "vocal", "often", "dolly", "eight", "joker", "since", "event", "ensue", "shunt", "diver", "poser", "worst", "sweep", "alley", "creed", "anime", "leafy", "bosom", "dunce", "stare", "pudgy", "waive", "choir", "stood", "spoke", "outgo", "delay", "bilge", "ideal", "clasp", "seize", "hotly", "laugh", "sieve", "block", "meant", "grape", "noose", "hardy", "shied", "drawl", "daisy", "putty", "strut", "burnt", "tulip", "crick", "idyll", "vixen", "furor", "geeky", "cough", "naive", "shoal", "stork", "bathe", "aunty", "check", "prime", "brass", "outer", "furry", "razor", "elect", "evict", "imply", "demur", "quota", "haven", "cavil", "swear", "crump", "dough", "gavel", "wagon", "salon", "nudge", "harem", "pitch", "sworn", "pupil", "excel", "stony", "cabin", "unzip", "queen", "trout", "polyp", "earth", "storm", "until", "taper", "enter", "child", "adopt", "minor", "fatty", "husky", "brave", "filet", "slime", "glint", "tread", "steal", "regal", "guest", "every", "murky", "share", "spore", "hoist", "buxom", "inner", "otter", "dimly", "level", "sumac", "donut", "stilt", "arena", "sheet", "scrub", "fancy", "slimy", "pearl", "silly", "porch", "dingo", "sepia", "amble", "shady", "bread", "friar", "reign", "dairy", "quill", "cross", "brood", "tuber", "shear", "posit", "blank", "villa", "shank", "piggy", "freak", "which", "among", "fecal", "shell", "would", "algae", "large", "rabbi", "agony", "amuse", "bushy", "copse", "swoon", "knife", "pouch", "ascot", "plane", "crown", "urban", "snide", "relay", "abide", "viola", "rajah", "straw", "dilly", "crash", "amass", "third", "trick", "tutor", "woody", "blurb", "grief", "disco", "where", "sassy", "beach", "sauna", "comic", "clued", "creep", "caste", "graze", "snuff", "frock", "gonad", "drunk", "prong", "lurid", "steel", "halve", "buyer", "vinyl", "utile", "smell", "adage", "worry", "tasty", "local", "trade", "finch", "ashen", "modal", "gaunt", "clove", "enact", "adorn", "roast", "speck", "sheik", "missy", "grunt", "snoop", "party", "touch", "mafia", "emcee", "array", "south", "vapid", "jelly", "skulk", "angst", "tubal", "lower", "crest", "sweat", "cyber", "adore", "tardy", "swami", "notch", "groom", "roach", "hitch", "young", "align", "ready", "frond", "strap", "puree", "realm", "venue", "swarm", "offer", "seven", "dryer", "diary", "dryly", "drank", "acrid", "heady", "theta", "junto", "pixie", "quoth", "bonus", "shalt", "penne", "amend", "datum", "build", "piano", "shelf", "lodge", "suing", "rearm", "coral", "ramen", "worth", "psalm", "infer", "overt", "mayor", "ovoid", "glide", "usage", "poise", "randy", "chuck", "prank", "fishy", "tooth", "ether", "drove", "idler", "swath", "stint", "while", "begat", "apply", "slang", "tarot", "radar", "credo", "aware", "canon", "shift", "timer", "bylaw", "serum", "three", "steak", "iliac", "shirk", "blunt", "puppy", "penal", "joist", "bunny", "shape", "beget", "wheel", "adept", "stunt", "stole", "topaz", "chore", "fluke", "afoot", "bloat", "bully", "dense", "caper", "sneer", "boxer", "jumbo", "lunge", "space", "avail", "short", "slurp", "loyal", "flirt", "pizza", "conch", "tempo", "droop", "plate", "bible", "plunk", "afoul", "savoy", "steep", "agile", "stake", "dwell", "knave", "beard", "arose", "motif", "smash", "broil", "glare", "shove", "baggy", "mammy", "swamp", "along", "rugby", "wager", "quack", "squat", "snaky", "debit", "mange", "skate", "ninth", "joust", "tramp", "spurn", "medal", "micro", "rebel", "flank", "learn", "nadir", "maple", "comfy", "remit", "gruff", "ester", "least", "mogul", "fetch", "cause", "oaken", "aglow", "meaty", "gaffe", "shyly", "racer", "prowl", "thief", "stern", "poesy", "rocky", "tweet", "waist", "spire", "grope", "havoc", "patsy", "truly", "forty", "deity", "uncle", "swish", "giver", "preen", "bevel", "lemur", "draft", "slope", "annoy", "lingo", "bleak", "ditty", "curly", "cedar", "dirge", "grown", "horde", "drool", "shuck", "crypt", "cumin", "stock", "gravy", "locus", "wider", "breed", "quite", "chafe", "cache", "blimp", "deign", "fiend", "logic", "cheap", "elide", "rigid", "false", "renal", "pence", "rowdy", "shoot", "blaze", "envoy", "posse", "brief", "never", "abort", "mouse", "mucky", "sulky", "fiery", "media", "trunk", "yeast", "clear", "skunk", "scalp", "bitty", "cider", "koala", "duvet", "segue", "creme", "super", "grill", "after", "owner", "ember", "reach", "nobly", "empty", "speed", "gipsy", "recur", "smock", "dread", "merge", "burst", "kappa", "amity", "shaky", "hover", "carol", "snort", "synod", "faint", "haunt", "flour", "chair", "detox", "shrew", "tense", "plied", "quark", "burly", "novel", "waxen", "stoic", "jerky", "blitz", "beefy", "lyric", "hussy", "towel", "quilt", "below", "bingo", "wispy", "brash", "scone", "toast", "easel", "saucy", "value", "spice", "honor", "route", "sharp", "bawdy", "radii", "skull", "phony", "issue", "lager", "swell", "urine", "gassy", "trial", "flora", "upper", "latch", "wight", "brick", "retry", "holly", "decal", "grass", "shack", "dogma", "mover", "defer", "sober", "optic", "crier", "vying", "nomad", "flute", "hippo", "shark", "drier", "obese", "bugle", "tawny", "chalk", "feast", "ruddy", "pedal", "scarf", "cruel", "bleat", "tidal", "slush", "semen", "windy", "dusty", "sally", "igloo", "nerdy", "jewel", "shone", "whale", "hymen", "abuse", "fugue", "elbow", "crumb", "pansy", "welsh", "syrup", "terse", "suave", "gamut", "swung", "drake", "freed", "afire", "shirt", "grout", "oddly", "tithe", "plaid", "dummy", "broom", "blind", "torch", "enemy", "again", "tying", "pesky", "alter", "gazer", "noble", "ethos", "bride", "extol", "decor", "hobby", "beast", "idiom", "utter", "these", "sixth", "alarm", "erase", "elegy", "spunk", "piper", "scaly", "scold", "hefty", "chick", "sooty", "canal", "whiny", "slash", "quake", "joint", "swept", "prude", "heavy", "wield", "femme", "lasso", "maize", "shale", "screw", "spree", "smoky", "whiff", "scent", "glade", "spent", "prism", "stoke", "riper", "orbit", "cocoa", "guilt", "humus", "shush", "table", "smirk", "wrong", "noisy", "alert", "shiny", "elate", "resin", "whole", "hunch", "pixel", "polar", "hotel", "sword", "cleat", "mango", "rumba", "puffy", "filly", "billy", "leash", "clout", "dance", "ovate", "facet", "chili", "paint", "liner", "curio", "salty", "audio", "snake", "fable", "cloak", "navel", "spurt", "pesto", "balmy", "flash", "unwed", "early", "churn", "weedy", "stump", "lease", "witty", "wimpy", "spoof", "saner", "blend", "salsa", "thick", "warty", "manic", "blare", "squib", "spoon", "probe", "crepe", "knack", "force", "debut", "order", "haste", "teeth", "agent", "widen", "icily", "slice", "ingot", "clash", "juror", "blood", "abode", "throw", "unity", "pivot", "slept", "troop", "spare", "sewer", "parse", "morph", "cacti", "tacky", "spool", "demon", "moody", "annex", "begin", "fuzzy", "patch", "water", "lumpy", "admin", "omega", "limit", "tabby", "macho", "aisle", "skiff", "basis", "plank", "verge", "botch", "crawl", "lousy", "slain", "cubic", "raise", "wrack", "guide", "foist", "cameo", "under", "actor", "revue", "fraud", "harpy", "scoop", "climb", "refer", "olden", "clerk", "debar", "tally", "ethic", "cairn", "tulle", "ghoul", "hilly", "crude", "apart", "scale", "older", "plain", "sperm", "briny", "abbot", "rerun", "quest", "crisp", "bound", "befit", "drawn", "suite", "itchy", "cheer", "bagel", "guess", "broad", "axiom", "chard", "caput", "leant", "harsh", "curse", "proud", "swing", "opine", "taste", "lupus", "gumbo", "miner", "green", "chasm", "lipid", "topic", "armor", "brush", "crane", "mural", "abled", "habit", "bossy", "maker", "dusky", "dizzy", "lithe", "brook", "jazzy", "fifty", "sense", "giant", "surly", "legal", "fatal", "flunk", "began", "prune", "small", "slant", "scoff", "torus", "ninny", "covey", "viper", "taken", "moral", "vogue", "owing", "token", "entry", "booth", "voter", "chide", "elfin", "ebony", "neigh", "minim", "melon", "kneed", "decoy", "voila", "ankle", "arrow", "mushy", "tribe", "cease", "eager", "birth", "graph", "odder", "terra", "weird", "tried", "clack", "color", "rough", "weigh", "uncut", "ladle", "strip", "craft", "minus", "dicey", "titan", "lucid", "vicar", "dress", "ditch", "gypsy", "pasta", "taffy", "flame", "swoop", "aloof", "sight", "broke", "teary", "chart", "sixty", "wordy", "sheer", "leper", "nosey", "bulge", "savor", "clamp", "funky", "foamy", "toxic", "brand", "plumb", "dingy", "butte", "drill", "tripe", "bicep", "tenor", "krill", "worse", "drama", "hyena", "think", "ratio", "cobra", "basil", "scrum", "bused", "phone", "court", "camel", "proof", "heard", "angel", "petal", "pouty", "throb", "maybe", "fetal", "sprig", "spine", "shout", "cadet", "macro", "dodgy", "satyr", "rarer", "binge", "trend", "nutty", "leapt", "amiss", "split", "myrrh", "width", "sonar", "tower", "baron", "fever", "waver", "spark", "belie", "sloop", "expel", "smote", "baler", "above", "north", "wafer", "scant", "frill", "awash", "snack", "scowl", "frail", "drift", "limbo", "fence", "motel", "ounce", "wreak", "revel", "talon", "prior", "knelt", "cello", "flake", "debug", "anode", "crime", "salve", "scout", "imbue", "pinky", "stave", "vague", "chock", "fight", "video", "stone", "teach", "cleft", "frost", "prawn", "booty", "twist", "apnea", "stiff", "plaza", "ledge", "tweak", "board", "grant", "medic", "bacon", "cable", "brawl", "slunk", "raspy", "forum", "drone", "women", "mucus", "boast", "toddy", "coven", "tumor", "truer", "wrath", "stall", "steam", "axial", "purer", "daily", "trail", "niche", "mealy", "juice", "nylon", "plump", "merry", "flail", "papal", "wheat", "berry", "cower", "erect", "brute", "leggy", "snipe", "sinew", "skier", "penny", "jumpy", "rally", "umbra", "scary", "modem", "gross", "avian", "greed", "satin", "tonic", "parka", "sniff", "livid", "stark", "trump", "giddy", "reuse", "taboo", "avoid", "quote", "devil", "liken", "gloss", "gayer", "beret", "noise", "gland", "dealt", "sling", "rumor", "opera", "thigh", "tonga", "flare", "wound", "white", "bulky", "etude", "horse", "circa", "paddy", "inbox", "fizzy", "grain", "exert", "surge", "gleam", "belle", "salvo", "crush", "fruit", "sappy", "taker", "tract", "ovine", "spiky", "frank", "reedy", "filth", "spasm", "heave", "mambo", "right", "clank", "trust", "lumen", "borne", "spook", "sauce", "amber", "lathe", "carat", "corer", "dirty", "slyly", "affix", "alloy", "taint", "sheep", "kinky", "wooly", "mauve", "flung", "yacht", "fried", "quail", "brunt", "grimy", "curvy", "cagey", "rinse", "deuce", "state", "grasp", "milky", "bison", "graft", "sandy", "baste", "flask", "hedge", "girly", "swash", "boney", "coupe", "endow", "abhor", "welch", "blade", "tight", "geese", "miser", "mirth", "cloud", "cabal", "leech", "close", "tenth", "pecan", "droit", "grail", "clone", "guise", "ralph", "tango", "biddy", "smith", "mower", "payee", "serif", "drape", "fifth", "spank", "glaze", "allot", "truck", "kayak", "virus", "testy", "tepee", "fully", "zonal", "metro", "curry", "grand", "banjo", "axion", "bezel", "occur", "chain", "nasal", "gooey", "filer", "brace", "allay", "pubic", "raven", "plead", "gnash", "flaky", "munch", "dully", "eking", "thing", "slink", "hurry", "theft", "shorn", "pygmy", "ranch", "wring", "lemon", "shore", "mamma", "froze", "newer", "style", "moose", "antic", "drown", "vegan", "chess", "guppy", "union", "lever", "lorry", "image", "cabby", "druid", "exact", "truth", "dopey", "spear", "cried", "chime", "crony", "stunk", "timid", "batch", "gauge", "rotor", "crack", "curve", "latte", "witch", "bunch", "repel", "anvil", "soapy", "meter", "broth", "madly", "dried", "scene", "known", "magma", "roost", "woman", "thong", "punch", "pasty", "downy", "knead", "whirl", "rapid", "clang", "anger", "drive", "goofy", "email", "music", "stuff", "bleep", "rider", "mecca", "folio", "setup", "verso", "quash", "fauna", "gummy", "happy", "newly", "fussy", "relic", "guava", "ratty", "fudge", "femur", "chirp", "forte", "alibi", "whine", "petty", "golly", "plait", "fleck", "felon", "gourd", "brown", "thrum", "ficus", "stash", "decry", "wiser", "junta", "visor", "daunt", "scree", "impel", "await", "press", "whose", "turbo", "stoop", "speak", "mangy", "eying", "inlet", "crone", "pulse", "mossy", "staid", "hence", "pinch", "teddy", "sully", "snore", "ripen", "snowy", "attic", "going", "leach", "mouth", "hound", "clump", "tonal", "bigot", "peril", "piece", "blame", "haute", "spied", "undid", "intro", "basal", "rodeo", "guard", "steer", "loamy", "scamp", "scram", "manly", "hello", "vaunt", "organ", "feral", "knock", "extra", "condo", "adapt", "willy", "polka", "rayon", "skirt", "faith", "torso", "match", "mercy", "tepid", "sleek", "riser", "twixt", "peace", "flush", "catty", "login", "eject", "roger", "rival", "untie", "refit", "aorta", "adult", "judge", "rower", "artsy", "rural", "shave", "bobby", "eclat", "fella", "gaily", "harry", "hasty", "hydro", "liege", "octal", "ombre", "payer", "sooth", "unset", "unlit", "vomit", "fanny", "fetus", "butch", "stalk", "flack", "widow", "augur"];

const now = new Date();
const start = new Date(2021, 05, 19, now.getHours(), now.getMinutes(), now.getSeconds());
const delta = Math.floor((now - start) / 3600 / 24 / 1000)
const answer = answers[delta - 1];

const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
const dateString = yesterday.toLocaleDateString('en-US', {year: 'numeric', month: 'short', day: 'numeric'});
document.getElementById("answer").textContent = `${dateString}: ${answer.toUpperCase()}`;
&lt;/script&gt;</content><category term="misc"></category><category term="wordle"></category><category term="games"></category></entry><entry><title>Videosplatter</title><link href="https://foxrow.com/videosplatter" rel="alternate"></link><published>2022-02-28T12:00:00-06:00</published><updated>2022-02-28T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2022-02-28:/videosplatter</id><summary type="html">&lt;p&gt;The visual look and feel of movies is something that interests me. So I built a tool to visualize it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanfox/videosplatter"&gt;Videosplatter&lt;/a&gt; is a tool to splat a video file into a mosaic of individual frames.
It shows you at a glance the look and feel of the entire video.&lt;/p&gt;
&lt;p&gt;You …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The visual look and feel of movies is something that interests me. So I built a tool to visualize it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanfox/videosplatter"&gt;Videosplatter&lt;/a&gt; is a tool to splat a video file into a mosaic of individual frames.
It shows you at a glance the look and feel of the entire video.&lt;/p&gt;
&lt;p&gt;You can see color palettes, light and shadow, and scene transitions in a single image.&lt;/p&gt;
&lt;p&gt;It can generate mosaics:
&lt;img alt="mosaic generated from drone footage of a city at dusk" class="m-image m-fullwidth" src="/assets/city_splat.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Or "movie barcodes":
&lt;img alt="each frame in the source video condensed to its main color, 1 pixel wide" class="m-image m-fullwidth" src="/assets/city_barcode.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;It also can generate "colorfulness" and contrast stats about the video. 
Colorfulness is the definition from &lt;a href="https://www.researchgate.net/publication/243135534_Measuring_Colourfulness_in_Natural_Images"&gt;Hasler and Süsstrunk 2003.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Add up colorfulness and contrast scores to get a "Visual 🌶️ Factor". Now you can complain about bland, blue-gray, washed out movies with science!&lt;/p&gt;
&lt;p&gt;You can install it with &lt;code&gt;pip install videosplatter&lt;/code&gt;. Let me know if you use it for something cool!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;City footage used under CC-BY license from &lt;a href="https://www.youtube.com/c/theDronalist"&gt;The Dronalist&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="python"></category><category term="dataviz"></category><category term="video"></category></entry><entry><title>Missed shot leaders in the NBA</title><link href="https://foxrow.com/missed-shot-leaders-in-the-nba" rel="alternate"></link><published>2021-02-18T12:00:00-06:00</published><updated>2021-02-18T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2021-02-18:/missed-shot-leaders-in-the-nba</id><summary type="html">&lt;p&gt;It's widely known that Kareem Abdul-Jabbar is the all-time leading scorer in the NBA, and LeBron stands a good chance
of passing him when all is said and done.&lt;/p&gt;
&lt;p&gt;What I've never seen before, is who is the all-time &lt;em&gt;missed shots&lt;/em&gt; scoring leader? As in, who has missed the most …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's widely known that Kareem Abdul-Jabbar is the all-time leading scorer in the NBA, and LeBron stands a good chance
of passing him when all is said and done.&lt;/p&gt;
&lt;p&gt;What I've never seen before, is who is the all-time &lt;em&gt;missed shots&lt;/em&gt; scoring leader? As in, who has missed the most
points worth of shooting?&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;&lt;th&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Missed&lt;/td&gt;
&lt;td&gt;Scored&lt;/td&gt;
&lt;td&gt;Net&lt;/td&gt;
&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Kobe Bryant&lt;/td&gt;&lt;td&gt;34,314&lt;/td&gt;&lt;td&gt;33,643&lt;/td&gt;&lt;td&gt;-671&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;&lt;strong&gt;LeBron James&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;31,566&lt;/td&gt;&lt;td&gt;34,985&lt;/td&gt;&lt;td&gt;3,419&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Dirk Nowitzki&lt;/td&gt;&lt;td&gt;29,357&lt;/td&gt;&lt;td&gt;31,560&lt;/td&gt;&lt;td&gt;2,203&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;Elvin Hayes&lt;/td&gt;&lt;td&gt;29,264&lt;/td&gt;&lt;td&gt;27,313&lt;/td&gt;&lt;td&gt;-1,951&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Vince Carter&lt;/td&gt;&lt;td&gt;29,200&lt;/td&gt;&lt;td&gt;25,728&lt;/td&gt;&lt;td&gt;-3,472&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;Karl Malone&lt;/td&gt;&lt;td&gt;28,990&lt;/td&gt;&lt;td&gt;36,928&lt;/td&gt;&lt;td&gt;7,938&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;John Havlicek&lt;/td&gt;&lt;td&gt;28,054&lt;/td&gt;&lt;td&gt;26,395&lt;/td&gt;&lt;td&gt;-1,659&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Carmelo Anthony&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;27,897&lt;/td&gt;&lt;td&gt;26,794&lt;/td&gt;&lt;td&gt;-1,103&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;Kareem Abdul-Jabbar&lt;/td&gt;&lt;td&gt;27,549&lt;/td&gt;&lt;td&gt;38,387&lt;/td&gt;&lt;td&gt;10,838&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;Wilt Chamberlain&lt;/td&gt;&lt;td&gt;27,437&lt;/td&gt;&lt;td&gt;31,419&lt;/td&gt;&lt;td&gt;3,982&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;Michael Jordan&lt;/td&gt;&lt;td&gt;27,332&lt;/td&gt;&lt;td&gt;32,292&lt;/td&gt;&lt;td&gt;4,960&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;Allen Iverson&lt;/td&gt;&lt;td&gt;26,995&lt;/td&gt;&lt;td&gt;24,368&lt;/td&gt;&lt;td&gt;-2,627&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;Paul Pierce&lt;/td&gt;&lt;td&gt;26,927&lt;/td&gt;&lt;td&gt;26,397&lt;/td&gt;&lt;td&gt;-530&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;14&lt;/td&gt;&lt;td&gt;Dominique Wilkins&lt;/td&gt;&lt;td&gt;26,179&lt;/td&gt;&lt;td&gt;26,668&lt;/td&gt;&lt;td&gt;489&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;Ray Allen&lt;/td&gt;&lt;td&gt;25,754&lt;/td&gt;&lt;td&gt;24,505&lt;/td&gt;&lt;td&gt;-1,249&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;Julius Erving&lt;/td&gt;&lt;td&gt;25,215&lt;/td&gt;&lt;td&gt;30,026&lt;/td&gt;&lt;td&gt;4,811&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;Elgin Baylor&lt;/td&gt;&lt;td&gt;24,584&lt;/td&gt;&lt;td&gt;23,149&lt;/td&gt;&lt;td&gt;-1,435&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;Rick Barry&lt;/td&gt;&lt;td&gt;24,277&lt;/td&gt;&lt;td&gt;25,279&lt;/td&gt;&lt;td&gt;1,002&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;Jamal Crawford&lt;/td&gt;&lt;td&gt;24,180&lt;/td&gt;&lt;td&gt;19,419&lt;/td&gt;&lt;td&gt;-4,761&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;Moses Malone&lt;/td&gt;&lt;td&gt;23,867&lt;/td&gt;&lt;td&gt;29,580&lt;/td&gt;&lt;td&gt;5,713&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Through the beginning of this season, LeBron is averaging 1,815 missed points per year. At that rate, he will pass Kobe next season.&lt;/p&gt;
&lt;p&gt;Active players in the top 50:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;&lt;th&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Missed&lt;/td&gt;
&lt;td&gt;Scored&lt;/td&gt;
&lt;td&gt;Net&lt;/td&gt;
&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;LeBron James&lt;/td&gt;&lt;td&gt;31,566&lt;/td&gt;&lt;td&gt;34,985&lt;/td&gt;&lt;td&gt;3,419&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;Russell Westbrook&lt;/td&gt;&lt;td&gt;22,560&lt;/td&gt;&lt;td&gt;20,759&lt;/td&gt;&lt;td&gt;-1,801&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;43&lt;/td&gt;&lt;td&gt;James Harden&lt;/td&gt;&lt;td&gt;21,146&lt;/td&gt;&lt;td&gt;21,549&lt;/td&gt;&lt;td&gt;403&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;Kevin Durant&lt;/td&gt;&lt;td&gt;19,831&lt;/td&gt;&lt;td&gt;23,491&lt;/td&gt;&lt;td&gt;3,660&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;h2&gt;Surprises&lt;/h2&gt;
&lt;p&gt;I would have guessed that Kareem was the leader - it takes so many field goal attempts to score
that many, you're going to have a lot of missed shots too. But not having taken a lot of 3-pointers really bails
him out. They're lower-percentage shots, and worth more, so you're in double jeopardy if you take a lot of threes.&lt;/p&gt;
&lt;p&gt;Missed-shot-Kobe would be 4th on the regular scoring list.&lt;/p&gt;
&lt;p&gt;I was surprised with how many guys were underwater: 23 out of the top 50.
Since the NBA/ABA merger, the player deepest in the hole is #23 Jason Kidd, at -5,711.&lt;/p&gt;
&lt;p&gt;Kareem &lt;em&gt;is&lt;/em&gt; the leader in net score, and it seems unlikely anyone is going to catch him, at least without the game
changing dramatically - say, different three point rules.&lt;/p&gt;
&lt;p&gt;Information courtesy &lt;a href="https://basketball-reference.com"&gt;Basketball-Reference.com&lt;/a&gt;. Stats reflect games played through February 17, 2021.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="basketball"></category><category term="data-analysis"></category></entry><entry><title>Slay the Spire statistical analysis</title><link href="https://foxrow.com/slay-the-spire-statistical-analysis" rel="alternate"></link><published>2020-12-14T12:00:00-06:00</published><updated>2020-12-14T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-12-14:/slay-the-spire-statistical-analysis</id><summary type="html">&lt;p&gt;&lt;a href="https://www.megacrit.com/"&gt;Slay the Spire&lt;/a&gt; is a single-player strategy card game I enjoy. In it, you build a card deck to defeat successively harder enemies as you move up levels in the Spire.&lt;/p&gt;
&lt;p&gt;I recently learned that they released &lt;a href="https://discord.com/channels/309399445785673728/776576967688323102/776588020676689960"&gt;a huge data dump&lt;/a&gt; of runs from real-world play.&lt;/p&gt;
&lt;h1&gt;The data set&lt;/h1&gt;
&lt;p&gt;It's …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.megacrit.com/"&gt;Slay the Spire&lt;/a&gt; is a single-player strategy card game I enjoy. In it, you build a card deck to defeat successively harder enemies as you move up levels in the Spire.&lt;/p&gt;
&lt;p&gt;I recently learned that they released &lt;a href="https://discord.com/channels/309399445785673728/776576967688323102/776588020676689960"&gt;a huge data dump&lt;/a&gt; of runs from real-world play.&lt;/p&gt;
&lt;h1&gt;The data set&lt;/h1&gt;
&lt;p&gt;It's huge. 380 gigabytes huge. 75 million+ runs from October 2018 to November 2020.&lt;/p&gt;
&lt;p&gt;I am only analyzing the 2020 segment of the data. Reason 1: the devs have made some changes to the game over time - balance tweaks, removed relics, that kind of thing. Reason 2: the 4th character, Watcher, was introduced early in 2020.&lt;/p&gt;
&lt;p&gt;The 2020 data is fully after Watcher was introduced. It is still 18 million runs, totaling 90 gigabytes of raw JSON. I dumped it into an sqlite database, and it's still 77GB. On my SSD, running a &lt;code&gt;count()&lt;/code&gt; query takes 6 minutes.&lt;/p&gt;
&lt;p&gt;Reason 3: I don't need to deal with 4 times that.&lt;/p&gt;
&lt;h1&gt;Findings&lt;/h1&gt;
&lt;p&gt;Spire is hard. Out of the 18 million runs, there are 1.6 million victories, for an overall winrate of 9%.&lt;/p&gt;
&lt;h2&gt;Play speed&lt;/h2&gt;
&lt;p&gt;Which character plays the fastest? The slowest? Counting wins only,&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Avg. Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ironclad&lt;/td&gt;
&lt;td style="text-align: right;"&gt;58:32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silent&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1:09:11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defect&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1:06:44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watcher&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1:07:36&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Interesting that Ironclad is so much faster, given the other 3 are so close.&lt;/p&gt;
&lt;p&gt;The average win takes 64 minutes. The average loss takes 23. Well, of course if you stay alive longer, it takes... longer.&lt;/p&gt;
&lt;p&gt;Spire has something called Ascension mode - extra levels of difficulty after you beat the game with each character. The game out of the box is Ascension 0. It goes up to Ascension 20.&lt;/p&gt;
&lt;p&gt;We see that across the board from A1 to A20, runs between 60 and 80 minutes have the highest winrate. Notice the light line below the main cluster - that's Ascension 0.&lt;/p&gt;
&lt;p&gt;&lt;img alt="graph of winrate vs play time, grouped by ascension level" src="https://foxrow.com/assets/spire-playtime-vs-winrate.svg"/&gt;&lt;/p&gt;
&lt;p&gt;This is putting the cart in front of the horse somewhat - it's not as if playing faster &lt;strong&gt;makes&lt;/strong&gt; you better in a turn-based game. Rather, the easy wins tend to take around an hour, and the ones with maybe a narrower path to victory, requiring more planning and calculation, take longer.&lt;/p&gt;
&lt;p&gt;Suppose you reach a particular floor, what are your chances of winning?&lt;/p&gt;
&lt;p&gt;&lt;img alt="graph of winrate vs floor reached" src="https://foxrow.com/assets/spire-floor-reached-vs-winrate.svg"/&gt;&lt;/p&gt;
&lt;p&gt;The discontinuities at floors 17 and 34 are due to the boss fights at the end of act 1 and 2 - if you beat the boss, you get some powerful rewards, and you're likely substantially better off than you were 1 floor ago.&lt;/p&gt;
&lt;p&gt;Floors 50+ have some weird stuff going on - there's actually 4 different floors you can win on. One way is just beating the boss on floor 50. That accounts for the big spike on floor 51 - it's the "victory floor".&lt;/p&gt;
&lt;p&gt;On Ascension 20, instead of fighting just one boss on floor 50, you fight another on floor 51, so floor 52 is the victory floor.&lt;/p&gt;
&lt;p&gt;Once you unlock Ascension mode, you have the option throughout a run to collect 3 keys. If you do by the 50th floor, instead of winning the run then, you proceed to Act 4. On Ascensions 0-19, you end up fighting the Heart on floor 55. If you win, your victory floor is 56. If you fought two bosses (A20), your victory floor is 57.&lt;/p&gt;
&lt;p&gt;Unfortunately, there isn't really a way to filter these out. A run isn't committed to Act 4 until you collect that last key, so it's possible to bail up until floor 49. A player may have been intending to collect all 3, but some bad luck or forgetfulness looks the same as someone who was just playing for floor 50 the whole way.&lt;/p&gt;
&lt;h2&gt;Deadliest Enemies&lt;/h2&gt;
&lt;p&gt;Which enemies are the toughest? Rather than just counting the number of kills by each one, let's take a look at how often they kill the hero.&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ironclad&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Fatal%&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Silent&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Fatal%&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Defect&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Fatal%&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Watcher&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Fatal%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The Heart&lt;/td&gt;
&lt;td style="text-align: right;"&gt;51.12%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The Heart&lt;/td&gt;
&lt;td style="text-align: right;"&gt;47.64%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The Heart&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.75%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The Heart&lt;/td&gt;
&lt;td style="text-align: right;"&gt;51.96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automaton&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35.87%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Time Eater&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35.98%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Awakened One&lt;/td&gt;
&lt;td style="text-align: right;"&gt;33.22%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Time Eater&lt;/td&gt;
&lt;td style="text-align: right;"&gt;33.14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collector&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31.75%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Awakened One&lt;/td&gt;
&lt;td style="text-align: right;"&gt;33.08%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Automaton&lt;/td&gt;
&lt;td style="text-align: right;"&gt;28.13%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Awakened One&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27.71%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Awakened One&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.92%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Champ&lt;/td&gt;
&lt;td style="text-align: right;"&gt;32.85%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Collector&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27.25%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Automaton&lt;/td&gt;
&lt;td style="text-align: right;"&gt;24.19%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Champ&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.28%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Collector&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31.05%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slaver and Parasite&lt;/td&gt;
&lt;td style="text-align: right;"&gt;24.24%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Collector&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hexaghost&lt;/td&gt;
&lt;td style="text-align: right;"&gt;24.56%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Automaton&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30.79%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Champ&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23.95%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Champ&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time Eater&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23.53%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slime Boss&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30.65%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Time Eater&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23.67%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Donu and Deca&lt;/td&gt;
&lt;td style="text-align: right;"&gt;19.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Guardian&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22.22%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lagavulin Event&lt;/td&gt;
&lt;td style="text-align: right;"&gt;19.31%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slavers&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22.41%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Hexaghost&lt;/td&gt;
&lt;td style="text-align: right;"&gt;19.69%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Donu and Deca&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21.23%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Donu and Deca&lt;/td&gt;
&lt;td style="text-align: right;"&gt;19.21%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Gremlin Leader&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22.09%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slavers&lt;/td&gt;
&lt;td style="text-align: right;"&gt;18.11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slime Boss&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.86%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Gremlin Leader&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.62%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Book of Stabbing&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.42%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lagavulin Event&lt;/td&gt;
&lt;td style="text-align: right;"&gt;18.04%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gremlin Leader&lt;/td&gt;
&lt;td style="text-align: right;"&gt;16.84%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slavers&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.09%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Donu and Deca&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.04%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The Guardian&lt;/td&gt;
&lt;td style="text-align: right;"&gt;16.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lagavulin Event&lt;/td&gt;
&lt;td style="text-align: right;"&gt;16.73%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Hexaghost&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17.07%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Hexaghost&lt;/td&gt;
&lt;td style="text-align: right;"&gt;16.52%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Book of Stabbing&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.62%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slavers&lt;/td&gt;
&lt;td style="text-align: right;"&gt;16.16%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The Guardian&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.77%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Masked Bandits&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.44%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Gremlin Leader&lt;/td&gt;
&lt;td style="text-align: right;"&gt;13.95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Book of Stabbing&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.46%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Book of Stabbing&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12.89%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Slime Boss&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.4%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Masked Bandits&lt;/td&gt;
&lt;td style="text-align: right;"&gt;11.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slaver and Parasite&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15.06%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Gremlin Nob&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12.26%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Colosseum Nobs&lt;/td&gt;
&lt;td style="text-align: right;"&gt;13.85%&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2 Orb Walkers&lt;/td&gt;
&lt;td style="text-align: right;"&gt;11.07%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The usual suspects are at the top - the Heart and act bosses are as deadly as you would guess, followed by a few elite fights. Seems crazy that Masked Bandits ends 10+% of Defect and Watcher runs!&lt;/p&gt;
&lt;p&gt;That's enough for one round of analysis. If you find something cool in the dataset or have questions, let me know!&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="python"></category><category term="data analysis"></category><category term="dataviz"></category><category term="games"></category><category term="spire"></category></entry><entry><title>Shaq's finals streak</title><link href="https://foxrow.com/shaqs-finals-streak" rel="alternate"></link><published>2020-11-16T12:00:00-06:00</published><updated>2020-11-16T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-11-16:/shaqs-finals-streak</id><summary type="html">&lt;p&gt;I saw a statistic that at least one of Shaq's teammates has played in the NBA Finals every year for 37 years straight and counting. That sounds like a lot! For one thing, it starts 9 years before Shaq got in the league.&lt;/p&gt;
&lt;p&gt;It got me wondering what the longest …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I saw a statistic that at least one of Shaq's teammates has played in the NBA Finals every year for 37 years straight and counting. That sounds like a lot! For one thing, it starts 9 years before Shaq got in the league.&lt;/p&gt;
&lt;p&gt;It got me wondering what the longest streak was. What else can we find out?&lt;/p&gt;
&lt;h2&gt;The longest streak&lt;/h2&gt;
&lt;p&gt;Shaq isn't the leader, believe it or not. Kevin Willis currently holds that honor, with 38 consecutive years with a teammate in the finals, spanning 1977-2014.&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;&lt;th&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Streak&lt;/td&gt;&lt;td&gt;Years active&lt;/td&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Kevin Willis&lt;/td&gt;&lt;td&gt;38&lt;/td&gt;&lt;td&gt;1977-2014&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;Shaquille O'Neal&lt;/td&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;1984-&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Toni Kukoc&lt;/td&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;1984-2019&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;Sam Cassell&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;td&gt;1986-&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Steve Nash&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;1987-&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Wes Person&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;1984-2017&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Tree Rollins&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;1974-2007&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Tyronn Lue&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;1987-&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Don Chaney&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;1957-1990&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;7-way tie&lt;/td&gt;&lt;td&gt;33&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Shaq is well-poised to tie for the lead this season, given the number of active teammates he still has in the league (notably LeBron, who has gone to the finals 9 out of the last 10 years).&lt;/p&gt;
&lt;h2&gt;Most teammates in the finals&lt;/h2&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Shaq&lt;/td&gt;&lt;td&gt;201&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;James Edwards&lt;/td&gt;&lt;td&gt;201&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Chuck Nevitt&lt;/td&gt;&lt;td&gt;186&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;A.C. Green&lt;/td&gt;&lt;td&gt;186&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;Richard Jefferson&lt;/td&gt;&lt;td&gt;184&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;LeBron James&lt;/td&gt;&lt;td&gt;182&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;Joe Kleine&lt;/td&gt;&lt;td&gt;176&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;Willie Naulls&lt;/td&gt;&lt;td&gt;176&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;Theo Ratliff&lt;/td&gt;&lt;td&gt;174&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;Corie Blount&lt;/td&gt;&lt;td&gt;174&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;Mel Counts&lt;/td&gt;&lt;td&gt;174&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;It looks like LeBron will move up this season. If he makes the finals, that adds ~15 to his total. Even if he doesn't, he has a lot of former teammates around the league. It seems likely that he will be #1 by the time all is said and done.&lt;/p&gt;
&lt;h2&gt;Surprises&lt;/h2&gt;
&lt;p&gt;Almost everyone who has played in the league has at least one finals teammate. Of the 4,814 players in the dataset, only 80 have no teammates who ever made the finals. Just 1.6%! Essentially every one is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;From the 40's/50's, or&lt;/li&gt;
&lt;li&gt;only played a handful of career games (e.g. 10-day contract guys), or&lt;/li&gt;
&lt;li&gt;are rookies/sophomores currently in the league.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At first I thought that can't be right. How has almost every rookie played with someone already? I realized that's thinking about it backwards - instead of rookies who haven't played with many teammates yet, think of every finals team as 15 guys (x2 teams) who can spread out across the league, and 1 finals player counts for a whole team.&lt;/p&gt;
&lt;p&gt;For example, J.J. Redick went to the finals once with the Magic in 2009, and so every team he's been on since his rookie season in '06-'07 has at least 1 finalist. That's how Zion Williamson has a teammate, despite being a rookie who didn't even make the playoffs this year.&lt;/p&gt;
&lt;p&gt;So who are these guys with no teammate-finals appearances? Some of the names that jumped out at me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ja Morant&lt;/li&gt;
&lt;li&gt;Mo Bamba&lt;/li&gt;
&lt;li&gt;Dennis Smith Jr. (only player with at least 3 years that I noticed)&lt;/li&gt;
&lt;li&gt;Michael Porter Jr.&lt;/li&gt;
&lt;li&gt;Jaren Jackson Jr.&lt;/li&gt;
&lt;li&gt;Wendell Carter Jr. (what's with all the juniors?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You get the idea. Players in their first few years in the league, who just happened to not have anyone on the team with them. I think this is basically accurate.&lt;/p&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;There are obviously edge cases on what constitutes a "teammate" - for example, Memphis rostered Andre Iguodala the whole year up until the trade deadline, but he never played for them. Does he count for Ja Morant?&lt;/p&gt;
&lt;p&gt;I decided to go with the rosters as defined on Basketball-Reference. So in that particular case, no. Iguodala is only listed on the Heat for the 2019-2020 season, he doesn't count for the Grizzlies.&lt;/p&gt;
&lt;p&gt;It's possible this method under-counts some players who were traded, or gained or lost a teammate mid-season. If you find anyone notable, please let me know.&lt;/p&gt;
&lt;p&gt;Information courtesy &lt;a href="https://basketball-reference.com"&gt;Basketball-Reference.com&lt;/a&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="basketball"></category><category term="data-analysis"></category></entry><entry><title>Dark backgrounds, light backgrounds, and OCR</title><link href="https://foxrow.com/dark-backgrounds-light-backgrounds-and-ocr" rel="alternate"></link><published>2020-09-27T12:00:00-05:00</published><updated>2020-09-27T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-09-27:/dark-backgrounds-light-backgrounds-and-ocr</id><summary type="html">&lt;p&gt;One of my favorite parts to build in APSE was the way it handles light and dark backgrounds. Text recognition algorithms are tuned for black letters on a white background, like a printed page:&lt;/p&gt;
&lt;p&gt;&lt;img alt='"hello world", black text on white background' class="m-image" src="https://foxrow.com/assets/bg_regular.png" title="dark text on light background"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Usually this isn't a problem, because you can invert the colors if an image has light …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my favorite parts to build in APSE was the way it handles light and dark backgrounds. Text recognition algorithms are tuned for black letters on a white background, like a printed page:&lt;/p&gt;
&lt;p&gt;&lt;img alt='"hello world", black text on white background' class="m-image" src="https://foxrow.com/assets/bg_regular.png" title="dark text on light background"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Usually this isn't a problem, because you can invert the colors if an image has light text on a dark background. This image also scans as "hello world".&lt;/p&gt;
&lt;p&gt;&lt;img alt='"hello world", white text on black background' class="m-image" src="https://foxrow.com/assets/bg_inverted.png" title="light text on dark background"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;But this won't work for Apse, because your screen might have both light and dark areas at the same time. For example, a dark-themed text editor open next to your browser. This image scans as "hello world igrelifemuZeyale!"&lt;/p&gt;
&lt;p&gt;&lt;img alt='"hello world", both white and black text on alternating background' class="m-image" src="https://foxrow.com/assets/bg_double.png" title="light text and dark text on alternating background"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;So Apse segments the image into dark and light regions, and selectively inverts the dark areas before converting to text. With that preprocessing out of the way, we don't get junk like "igrelifemuZeyale!".&lt;/p&gt;
&lt;p&gt;If this was interesting, you can check out Apse in action at &lt;a href="https://apse.io"&gt;apse.io.&lt;/a&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="python"></category><category term="apse"></category><category term="debugging"></category></entry><entry><title>Shutting down projects</title><link href="https://foxrow.com/shutting-down-projects" rel="alternate"></link><published>2020-08-19T12:00:00-05:00</published><updated>2020-08-19T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-08-19:/shutting-down-projects</id><summary type="html">&lt;p&gt;Two of the older projects I have run for some time now are Needs More Lens Flare, a toy web app for automatically adding lens flare to photos, and Drone Stable, a more robust web application for automatically stabilizing aerial (or any kind of) video.&lt;/p&gt;
&lt;p&gt;I am afraid I don't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Two of the older projects I have run for some time now are Needs More Lens Flare, a toy web app for automatically adding lens flare to photos, and Drone Stable, a more robust web application for automatically stabilizing aerial (or any kind of) video.&lt;/p&gt;
&lt;p&gt;I am afraid I don't have the time to work on them any more. At the end of this month, I will be shutting both of them down.&lt;/p&gt;
&lt;p&gt;I especially enjoyed working on and running Drone Stable, but right now I am more focused on &lt;a href="https://apse.io"&gt;Apse.&lt;/a&gt; If you haven't seen it before, check it out!&lt;/p&gt;
&lt;p&gt;If you have questions or want to use software like that after the shutdown, please contact me.&lt;/p&gt;</content><category term="misc"></category><category term="programming"></category><category term="software"></category><category term="web"></category><category term="drone"></category><category term="image processing"></category><category term="photo"></category></entry><entry><title>Debugging dynamic querysets in Django</title><link href="https://foxrow.com/debugging-dynamic-querysets-in-django" rel="alternate"></link><published>2020-03-14T12:00:00-05:00</published><updated>2020-03-14T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-03-14:/debugging-dynamic-querysets-in-django</id><summary type="html">&lt;p&gt;Class-based views in Django are super handy. Among other things, you can create
a view listing all instances of a model with about two lines of code.&lt;/p&gt;
&lt;p&gt;One of the features of this ListView is the ability to customize the queryset
used to populate the view. Suppose you wanted a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Class-based views in Django are super handy. Among other things, you can create
a view listing all instances of a model with about two lines of code.&lt;/p&gt;
&lt;p&gt;One of the features of this ListView is the ability to customize the queryset
used to populate the view. Suppose you wanted a view to list all the &lt;code&gt;Foo&lt;/code&gt;s in
your database - no problem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;FooView&lt;/span&gt;(&lt;span class="n"&gt;ListView&lt;/span&gt;):
    &lt;span class="n"&gt;model&lt;/span&gt; = &lt;span class="n"&gt;Foo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Django also lets you customize the queryset, in case you don't want &lt;em&gt;all&lt;/em&gt; Foos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;FooView&lt;/span&gt;(&lt;span class="n"&gt;ListView&lt;/span&gt;):
    &lt;span class="s"&gt;"""List all the Foos that aren't bar"""&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; = &lt;span class="n"&gt;Foo&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; = &lt;span class="n"&gt;Foo&lt;/span&gt;.&lt;span class="n"&gt;objects&lt;/span&gt;.&lt;span class="n"&gt;filter&lt;/span&gt;(&lt;span class="n"&gt;bar&lt;/span&gt;=&lt;span class="nb"&gt;False&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had to debug a problem with this. Django lets you specify a dynamic queryset
by overriding the method &lt;code&gt;get_queryset()&lt;/code&gt;. I was trying to do so by just
overriding &lt;code&gt;queryset&lt;/code&gt;. If you do that, Django will only evaluate it once, on
startup. In my case, it was a time-based filter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FooView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;"""List all the Foos in the past"""&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foo&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Foo.objects.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timezone.now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# &amp;lt;- doesn't do what you think!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So my results would look right immediately after startup, but get more and more
out of whack the longer the app ran. This ended up fixing my problem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;FooView&lt;/span&gt;(&lt;span class="n"&gt;ListView&lt;/span&gt;):
    &lt;span class="n"&gt;model&lt;/span&gt; = &lt;span class="n"&gt;Foo&lt;/span&gt;

    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_queryset&lt;/span&gt;(&lt;span class="nb"&gt;self&lt;/span&gt;):
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Foo&lt;/span&gt;.&lt;span class="n"&gt;objects&lt;/span&gt;.&lt;span class="n"&gt;filter&lt;/span&gt;(&lt;span class="n"&gt;timestamp__lt&lt;/span&gt;=&lt;span class="nb"&gt;timezone&lt;/span&gt;.&lt;span class="nb"&gt;now&lt;/span&gt;())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Happy coding.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="python"></category><category term="django"></category><category term="debugging"></category></entry><entry><title>James Webb Space Telescope Countdown</title><link href="https://foxrow.com/webb-countdown" rel="alternate"></link><published>2020-02-25T12:00:00-06:00</published><updated>2020-02-25T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-02-25:/webb-countdown</id><summary type="html">&lt;p&gt;NASA's James Webb Space Telescope is the next big thing in space-based
observatories. The project will enable never-been-done-before research, revealing
astronomy and physics discoveries about dark matter, our galaxy, and more.&lt;/p&gt;
&lt;p&gt;It has been in the news for delays in its launch schedule. If you ever need to
know how …&lt;/p&gt;</summary><content type="html">&lt;p&gt;NASA's James Webb Space Telescope is the next big thing in space-based
observatories. The project will enable never-been-done-before research, revealing
astronomy and physics discoveries about dark matter, our galaxy, and more.&lt;/p&gt;
&lt;p&gt;It has been in the news for delays in its launch schedule. If you ever need to
know how long until it goes up, I created a tool for up-to-the-second updates
on when it's going to launch. You can check it at &lt;em&gt;(update: the countdown is retired, since it launched successfully on Christmas 2021. You can check out the telescope's current status at &lt;a href="https://jwst.nasa.gov"&gt;jwst.nasa.gov&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;</content><category term="space"></category><category term="programming"></category><category term="space"></category><category term="web"></category><category term="webb"></category></entry><entry><title>Apse 2.0 Release Announcement</title><link href="https://foxrow.com/apse-2" rel="alternate"></link><published>2020-01-27T12:00:00-06:00</published><updated>2020-01-27T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2020-01-27:/apse-2</id><summary type="html">&lt;p&gt;It's been a while since I have written about Apse here. I've still been working on it, and am happy to announce version 2.0 is now offically released! 😄&lt;/p&gt;
&lt;p&gt;This is a substantial improvement over version 1. Both performance and general usage have seen big upgrades. I'm very happy with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's been a while since I have written about Apse here. I've still been working on it, and am happy to announce version 2.0 is now offically released! 😄&lt;/p&gt;
&lt;p&gt;This is a substantial improvement over version 1. Both performance and general usage have seen big upgrades. I'm very happy with it! Here are the changes you'll see in v2.0:&lt;/p&gt;
&lt;h2&gt;The big features&lt;/h2&gt;
&lt;p&gt;💥 OCR is around 50% faster now. That means you can take snapshots more often, or keep the same frequency and see much lower CPU usage. This is thanks to both smarter preprocessing before OCR happens, and some performance gains in the OCR process itself.&lt;/p&gt;
&lt;p&gt;💥 Dramatically better layout detection. In previous versions, the OCR algorithm read straight across the image left to right. If you had multiple windows open, or multiple columns in a document, the OCR'd text would be interleaved. Now regions of text are grouped much more intelligently.&lt;/p&gt;
&lt;p&gt;💥 A visual indicator APSE is working during long running queries. Okay, it's a loading spinner. I've done some pretty convoluted advanced searches, and it's reassuring to see &lt;em&gt;yes, your results ARE coming, be patient for once would you?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Smaller changes&lt;/h2&gt;
&lt;p&gt;Various UI improvements. The manual snapshot interface is clearer. There is also a much better indication of when your license key is expired, and how to fix it.&lt;/p&gt;
&lt;p&gt;OSX Mavericks is no longer supported. Sorry. You'll have to upgrade to an OS released in the last three and a half years.&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;You can check out more information about Apse at &lt;a href="https://apse.io"&gt;apse.io&lt;/a&gt; or see a summary of the changes for all versions &lt;a href="https://apse.io/news"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I'm always happy to talk - if you have questions about it or any feedback, please &lt;a href="mailto:ryan@foxrow.com"&gt;get in touch!&lt;/a&gt; Happy searching.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="software"></category><category term="apse"></category><category term="search"></category><category term="release"></category><category term="ai"></category><category term="machine learning"></category></entry><entry><title>Detecting Fire Engines in Imagery</title><link href="https://foxrow.com/detecting-fire-engines-in-imagery" rel="alternate"></link><published>2019-09-30T12:00:00-05:00</published><updated>2019-09-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-09-30:/detecting-fire-engines-in-imagery</id><summary type="html">&lt;h2&gt;Finding fire trucks&lt;/h2&gt;
&lt;p&gt;One of the challenges in flying drones in public airspace is the need to peacefully coexist with everyone else. Emergency first responders like firefighters don't need quadcopters buzzing around, getting in their way.&lt;/p&gt;
&lt;p&gt;Some consumer-level drones already have a "return to home" feature, where the drone automatically …&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Finding fire trucks&lt;/h2&gt;
&lt;p&gt;One of the challenges in flying drones in public airspace is the need to peacefully coexist with everyone else. Emergency first responders like firefighters don't need quadcopters buzzing around, getting in their way.&lt;/p&gt;
&lt;p&gt;Some consumer-level drones already have a "return to home" feature, where the drone automatically flies up to a safe height, returns to the spot it launched, and lands. This can happen when the battery is low, or the pilot hits a button.&lt;/p&gt;
&lt;p&gt;What if we tried to automate returning to home when there is a fire engine nearby? For a first cut, we could simply try and detect if a fire engine is in the video feed from the drone. For our purposes here, we're not going to worry about where it is, if it's moving, how far away it is, what direction it's going, or anything like that. We're talking a pretty dumb autopilot, but if you're hanging around fires with your drone, maybe you need the help :)&lt;/p&gt;
&lt;p&gt;So our decision process would be something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;See&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Land&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;engines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sight&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flying&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Image classification with ML would be a good place to start. Essentially, given a video frame or image, we want to classify whether the image has a fire truck in it. For training ML models, more data is usually better, but you don't need a ton to get started. I compiled a dataset of around 1,000 images with fire engines in them, and another 1,000 without. The fire engines came from the &lt;a href="http://www.image-net.org/"&gt;Imagenet&lt;/a&gt; dataset, and the non-fire engines from the &lt;a href="https://ai.stanford.edu/~jkrause/cars/car_dataset.html"&gt;Stanford cars dataset.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://keras.io/"&gt;Keras&lt;/a&gt; is a machine learning toolkit that has some pre-built models vetted over large data sets. We could use InceptionV3, a neural net architecture from Google. Unfortunately, training on our 2k images doesn't give great results. After the net was trained, I picked a few images to measure predictions:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Predicted&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;52.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;52.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;52.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;52.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not good, and not confident! 50% is the same as random guessing. The poor quality was kind of to be expected - the loss measured by my validation image set never got below ~0.7. I suspect more example images would be needed to push that down.&lt;/p&gt;
&lt;p&gt;How can we improve on this? Keras can also supply models with pre-trained weights on 1,000 Imagenet object classes. One of them is fire engines. Using Keras, we can get these pre-trained weights.&lt;/p&gt;
&lt;p&gt;It's common to measure image classifiers by top-1 or top-5 accuracy. That is, what's the hit rate that the actual class is somewhere in the top 5 classes the algorithm predicts.&lt;/p&gt;
&lt;p&gt;So how does InceptionV3 fare with pre-trained weights? Not good! In a sample of 5 fire truck images, here's what the out-of-the-box pretrained model predicts:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1&lt;/td&gt;
&lt;td&gt;71.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;5.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not in top 5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#1&lt;/td&gt;
&lt;td&gt;18.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This confidence metric is a little different - it's split across 1,000 object types. So if the net predicts fire truck with 71% confidence, the other 999 add up to the remaining 29%.&lt;/p&gt;
&lt;p&gt;This still isn't good. We do have another trick up our sleeve though. Since we only care about two classes - fire truck and not-fire-truck, we can re-train the pre-trained model with our data set. This is transfer learning, and the particular version we're doing is called fine-tuning.&lt;/p&gt;
&lt;p&gt;We're going to take the original neural net and original weights, add some new layers, and train just our new layers. Once our new layers are "warmed up", we can tune the whole net to squeeze the last drops out of the model.&lt;/p&gt;
&lt;p&gt;How well does it work? Glad you asked! With another sample of 5 positives and 3 negatives, the fine-tuned net gets all 8 correct.&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fire engine&lt;/th&gt;
&lt;th&gt;confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;99.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;98.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;85.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;96.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;91.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;94.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;79.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;84.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On top of that, using the pre-trained weights decrased the training time per epoch from over 20 minutes to around 2 and a half. This was on an older desktop CPU as well - if you wanted to really juice the training speed, you could use a GPU for training.&lt;/p&gt;
&lt;h2&gt;Visualizing learning&lt;/h2&gt;
&lt;p&gt;You can plot the loss of the net over time, measured against a validation set of images, kept separate from the training set. Here's what that looked like for the nets I described here:&lt;/p&gt;
&lt;p&gt;&lt;img alt="neural net training loss by epoch" class="m-image" src="https://foxrow.com/assets/inceptionv3_fire_engines.png" title="training loss by epoch"/&gt;&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;So we've found fine-tuning the pre-trained network weights for our specific goals improves accuracy dramatically. There's still a lot of room for improvement. Some ideas on how to make it better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recognize other emergency vehicle types, like ambulances or police cars. Those are both classes in the Imagenet 1,000, so given a moderate training sample I would expect you could see similar accuracy improvements.&lt;/li&gt;
&lt;li&gt;Localize the vehicle in the image. This would let you determine relative positions of e.g. the drone and the emergency vehicle, and navigate safely away. This would mean using a different neural net architecture. R-CNN and family or YOLO would be a good place to start, especially YOLOv3.&lt;/li&gt;
&lt;li&gt;More efficient neural nets. There is always a balance between accuracy, speed, processing power, and memory usage for these applications. Typically the processors on drones are relatively low-powered, and time is somewhat critical, so you want to be as lean as possible. On the other hand, you want to actually detect emergency vehicles. These pre-trained nets are unlikely to be the most optimal for any given hardware configuration. I briefly tried training the same dataset with MobilenetV2, another pre-trained model in Keras, but the results were very poor - it only got 2/8 correct, with confidences all over the place to boot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I trained on my desktop machine, using CPU only. All training sessions pinned my CPU. That's good - it should be using all my cores. For the from-scratch training, RAM usage peaked just under 16GB. For the fine-tuning, it peaked around 10GB. I didn't look into why the difference - my guess would be tensorflow has some smarts about what layers are frozen/being trained. Fine tuning was also around 10x faster per epoch.&lt;/p&gt;
&lt;p&gt;The code I used to train the net is available on &lt;a href="https://github.com/ryanfox/fire-engine-detection"&gt;Github.&lt;/a&gt; Hope you found this useful!&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="software"></category><category term="ai"></category><category term="machine learning"></category></entry><entry><title>Memento Mori</title><link href="https://foxrow.com/memento-mori" rel="alternate"></link><published>2019-07-12T12:00:00-05:00</published><updated>2019-07-12T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-07-12:/memento-mori</id><summary type="html">&lt;p&gt;I have been working on &lt;a href="https://apse.io"&gt;Apse&lt;/a&gt; for about a year now. Up until a few days ago, I didn't know of any other software like it.&lt;/p&gt;
&lt;p&gt;Earlier this week, &lt;a href="https://pinboard.in"&gt;Pinboard&lt;/a&gt; turned 10. It was one of my inspirations for Apse! Maciej's writing is gold, and &lt;a href="https://blog.pinboard.in/2019/07/i_cant_stop_winning/"&gt;his post to commemorate the …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been working on &lt;a href="https://apse.io"&gt;Apse&lt;/a&gt; for about a year now. Up until a few days ago, I didn't know of any other software like it.&lt;/p&gt;
&lt;p&gt;Earlier this week, &lt;a href="https://pinboard.in"&gt;Pinboard&lt;/a&gt; turned 10. It was one of my inspirations for Apse! Maciej's writing is gold, and &lt;a href="https://blog.pinboard.in/2019/07/i_cant_stop_winning/"&gt;his post to commemorate the occasion&lt;/a&gt; doesn't disappoint:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What does the future hold for Pinboard? Death! The bus that one day comes for us all! The skeletal, icy hand on an unprepared shoulder! Pain, a flash of light, then numbing darkness. So back up your bookmarks. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just after I read that piece, I learned of a company called Atlas Informatics. In 2016, they raised $20 million in funding. The company folded later the same year. They were building... &lt;a href="https://www.youtube.com/watch?v=fGev9Zo6y-I"&gt;almost exactly Apse.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was almost like whiplash - in the span of 10 minutes, I went from thinking my app was alone in the world, to learning of another one. Except that company raised &lt;em&gt;20 million dollars&lt;/em&gt;, ** &lt;em&gt;and shut down less than a year later!&lt;/em&gt; ** And it all happened years ago.&lt;/p&gt;
&lt;p&gt;The website is gone, but the press coverage and marketing materials are still around. It was a little surreal reading them - there's almost verbatim quotes of things I've been telling people about my project.&lt;/p&gt;
&lt;p&gt;Companies and people come and go. Maybe I can learn from Atlas Recall. Their product was a little different - it had cloud features, and I have purposely decided against adding that to Apse. They only supported Mac OS, and Apse also runs on Windows and Linux. Apse still exists, which may be a point in its favor.&lt;/p&gt;
&lt;p&gt;It does make me feel good that other people recognize the usefulness of this kind of application. If there's any users out there that wish they still could use Atlas Recall, have I got a deal for you! 🙂&lt;/p&gt;
&lt;p&gt;So what does the future hold for Apse? Not death! Well yes, ultimately death. But hopefully not for a while. In the meantime, head on over to &lt;a href="https://apse.io"&gt;apse.io&lt;/a&gt; and check it out.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="software"></category><category term="apse"></category><category term="death"></category></entry><entry><title>Apse 1.6 Update</title><link href="https://foxrow.com/apse-16-update" rel="alternate"></link><published>2019-06-17T12:00:00-05:00</published><updated>2019-06-17T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-06-17:/apse-16-update</id><summary type="html">&lt;p&gt;I have been able to add a lot of great stuff to Apse lately. Version 1.5 lets you manually trigger a snapshot from the system tray, along with many bugfixes. &lt;/p&gt;
&lt;p&gt;Version 1.6 also has some great improvements. The UI has been made more clear in several places, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been able to add a lot of great stuff to Apse lately. Version 1.5 lets you manually trigger a snapshot from the system tray, along with many bugfixes. &lt;/p&gt;
&lt;p&gt;Version 1.6 also has some great improvements. The UI has been made more clear in several places, and now you can update settings without restarting the app. This is great for the first time you boot it up, so you can enter the activation key and go.&lt;/p&gt;
&lt;p&gt;I recommend everyone upgrade to the latest version - as of this writing, that is v1.6. Go ahead and get it at &lt;a href="https://apse.io"&gt;apse.io!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you like Apse, you can really help by spreading the word. Tell anyone you think would find it useful - coworkers, friends, enemies, you get the idea. Post on social media if you're into that kind of thing. You can follow Apse on Twitter at &lt;a href="https://twitter.com/apseio"&gt;@apseio.&lt;/a&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="software"></category><category term="computer vision"></category><category term="apse"></category></entry><entry><title>Video Codecs: More Than You Wanted To Know</title><link href="https://foxrow.com/video-codecs" rel="alternate"></link><published>2019-04-25T12:00:00-05:00</published><updated>2019-04-25T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-04-25:/video-codecs</id><summary type="html">&lt;p&gt;How do video codecs work? How do they compress video into such tiny filesizes? A modern video codec is pretty complex, but you can get a pretty good idea of how they work at a high level.&lt;/p&gt;
&lt;h1&gt;What is a codec anyway?&lt;/h1&gt;
&lt;p&gt;Codec is short for &lt;strong&gt;CO&lt;/strong&gt;der/&lt;strong&gt;DEC&lt;/strong&gt;oder …&lt;/p&gt;</summary><content type="html">&lt;p&gt;How do video codecs work? How do they compress video into such tiny filesizes? A modern video codec is pretty complex, but you can get a pretty good idea of how they work at a high level.&lt;/p&gt;
&lt;h1&gt;What is a codec anyway?&lt;/h1&gt;
&lt;p&gt;Codec is short for &lt;strong&gt;CO&lt;/strong&gt;der/&lt;strong&gt;DEC&lt;/strong&gt;oder. Codecs aren't video files by themselves - codecs are just a part of one. Video files like MP4 are called containers. Each container can hold multiple audio and video streams. (Think different audio tracks for the same video.)&lt;/p&gt;
&lt;p&gt;Today I'll just be talking about video codecs. The codec is what compresses the audio or video data, and decompresses it for playback. Each stream is separately encoded when the file is created, and decoded during playback.&lt;/p&gt;
&lt;p&gt;Encode/compress and decode/decompress mean the same thing; I use them interchangeably here.&lt;/p&gt;
&lt;aside class="m-block m-default"&gt;
&lt;h3&gt;Why isn't everything compressed?&lt;/h3&gt;
    Compression comes with a tradeoff. You can save a lot of space, but it takes time to compress and decompress. It takes longer to create the video file. You also need a fast enough processor during playback to decode each frame in time, otherwise you get choppy video or dropped frames.
&lt;/aside&gt;
&lt;h1&gt;Just how good are they?&lt;/h1&gt;
&lt;p&gt;High-resolution video has a problem. It takes up &lt;em&gt;so much space.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you were to store the raw pixel data for HD video, you're going to run out of hard drives pretty quickly. Standard color video stores 8 bits per RGB channel, 3 channels per pixel, per frame. &lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Framerate&lt;/th&gt;
&lt;th&gt;Filesize&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tr&gt;
&lt;td&gt;1080p&lt;/td&gt;
&lt;td&gt;30fps&lt;/td&gt;
&lt;td&gt;6 MB / frame&lt;/td&gt;
&lt;td&gt;186 MB / second&lt;/td&gt;
&lt;td&gt;11 GB / minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;30fps&lt;/td&gt;
&lt;td&gt;25 MB / frame&lt;/td&gt;
&lt;td&gt;746 MB / second&lt;/td&gt;
&lt;td&gt;44 GB / minute&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Double those numbers for 60fps. So how can we avoid filling our phones after a minute of footage?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.pexels.com/video/an-island-in-the-middle-of-the-sea-1812902/"&gt;Take this stock video for example.&lt;/a&gt; It is in an mp4 container, encoded with the H.264 codec, 4k, 30fps, 11 seconds long. Raw bitrate from the calculations above would give 8.2 GB. The actual file is 30.6 MB, including audio. That’s 0.4% of the raw data.&lt;/p&gt;
&lt;h1&gt;How do they do it?&lt;/h1&gt;
&lt;p&gt;How can we get that crazy level of compression? Your first thought might be to compress each frame, a la JPG. Taking this frame as an example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="video frame as jpg" class="m-image" src="https://foxrow.com/assets/pexels-demo.jpg" title="Sample video frame as jpg"/&gt;&lt;/p&gt;
&lt;p&gt;At high quality, it weighs 1 megabyte. At 30fps, this would be 30 MB/second, or 330 MB for the whole video. Still too high, 10x what the actual video file is.&lt;/p&gt;
&lt;h1&gt;So how do they actually work?&lt;/h1&gt;
&lt;p&gt;Codecs get such great compression by exploiting similarities between frames. Video frames are 1/30th (or 1/60th) of a second apart - they usually have a lot of overlap.&lt;/p&gt;
&lt;p&gt;Video codecs store frames in 3 ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I frames, or intra-coded frames, also called keyframes. These contain full pixel data for the entire image.&lt;/li&gt;
&lt;li&gt;P frames, or predicted frames. These don't carry full data for every pixel. They depend on the frame before, which may be a keyframe or another P frame.&lt;/li&gt;
&lt;li&gt;B frames, or bidirectional frame. These are like P frames, but depend on the frame both before and after.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Video files contain framerate data, but that is more of a suggestion than a hard rule. What actually determines how fast frames are shown is their timestamp. Timestamps are calculated from data in the video file called timebase and PTS, short for &lt;strong&gt;P&lt;/strong&gt;resentation &lt;strong&gt;T&lt;/strong&gt;ime&lt;strong&gt;S&lt;/strong&gt;tamp.&lt;/p&gt;
&lt;aside class="m-block m-default"&gt;
&lt;h3&gt;Why not just use fps?&lt;/h3&gt;
    If a video file specified fps and displayed every frame in order, 1/30th of a second apart, you would lose out on performance savings. For a contrived example, if you had a video of a static image, you would need to decode the same image every 1/30th of a second, no matter what. Using PTS and timebase allows you to have variable framerate video, showing frames less frequently when called for.
&lt;/aside&gt;
&lt;p&gt;Calculating the timestamp for a given frame is a bit complicated. Containers have a piece of data called the timebase. This is often a number like 1/90,000, for 1/90,000th of a second.&lt;/p&gt;
&lt;p&gt;Each frame also has a PTS, which indicates it should be Presented at that timebase. So your first few frames in a video might look something like&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frame&lt;/th&gt;
&lt;th&gt;PTS&lt;/th&gt;
&lt;th&gt;Timebase&lt;/th&gt;
&lt;th&gt;Time to display&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1/90,000&lt;/td&gt;
&lt;td&gt;0 * 1/90,000 = 0 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;1/90,000&lt;/td&gt;
&lt;td&gt;3,000 * 1/90,000 = 0.033 seconds (1/30th!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;td&gt;1/90,000&lt;/td&gt;
&lt;td&gt;6,000 * 1/90,000 = 0.066 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;and so on.&lt;/p&gt;
&lt;h2&gt;What was all that about frame predictions?&lt;/h2&gt;
&lt;p&gt;How do you capture similarities between frames? From our earlier example, there is still huge savings to be had there - around 10X over just compressing each frame by itself!&lt;/p&gt;
&lt;p&gt;Codecs handle this by splitting each frame into regions, and comparing differences between regions in adjacent frames. Two popular modern codecs are H.264 and H.265. H.264 uses sections called macroblocks, and H.265 uses something called coding tree units. These work like macroblocks but with more sizes available.&lt;/p&gt;
&lt;aside class="m-block m-default"&gt;
&lt;h3&gt;Which is better?&lt;/h3&gt;
    H.265 can compress video to about half as much space as H.264. But it comes at a cost! It takes longer to encode and decode.
&lt;/aside&gt;
&lt;p&gt;So now that we calculated block differences for 2 consecutive frames, we can store the full first frame, and only the delta for the second.&lt;/p&gt;
&lt;p&gt;That second frame is a P frame. Once the first frame is decoded, the second frame can be decoded by applying those changes to the first frame.&lt;/p&gt;
&lt;p&gt;B frames work the same way, except using differences between the frame before and after. Hence bidirectional.&lt;/p&gt;
&lt;p&gt;Remember when we said you need a powerful processor to decode frames fast enough? This is why. In addition to PTS, frames also have something called DTS - decode timestamp. This indicates to the decoder when frames should be decoded. Now your timestamps might look something like this:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frame&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;DTS&lt;/th&gt;
&lt;th&gt;PTS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;I frame&lt;/td&gt;
&lt;td&gt;0 (first decoded)&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;P frame&lt;/td&gt;
&lt;td&gt;1,000 (second decoded)&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;B frame&lt;/td&gt;
&lt;td&gt;7,000 (fourth decoded)&lt;/td&gt;
&lt;td&gt;9,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;I frame&lt;/td&gt;
&lt;td&gt;5,000 (third decoded)&lt;/td&gt;
&lt;td&gt;12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;The third frame, a B frame, must wait until frames 2 and 4 are decoded.&lt;/p&gt;
&lt;p&gt;Keyframes are the fastest to decode - they don't rely on any other frames. P frames rely on the frame before them, but that's not really an issue. It had to be displayed earlier, so it has necessarily already been decoded. B frames are another animal. They rely on &lt;em&gt;later frames&lt;/em&gt; being decoded, before they can be decoded themselves.&lt;/p&gt;
&lt;p&gt;The window between DTS and PTS is usually short. Decoded frames take up a lot of memory, so you don't want to have more sitting around than absolutely necessary. Doubling+ the work you need to display a frame on time can make for tight deadlines.&lt;/p&gt;
&lt;p&gt;B frames have the highest decoding performance requirements, but also generally the most compression. You can always compress less, and get faster decoding. But then you have much bigger filesizes.&lt;/p&gt;
&lt;p&gt;So now we can store keyframes occasionally, when the whole screen needs an update.
We can store P frames, storing only the differences between a frame and the last.
And we can store B frames, storing differences between the frames both before and after, for maximum space savings. And that’s how you get a video file 0.4% as big as the original.&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category><category term="software"></category><category term="video"></category></entry><entry><title>APSE Update</title><link href="https://foxrow.com/apse-update" rel="alternate"></link><published>2019-04-18T12:00:00-05:00</published><updated>2019-04-18T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-04-18:/apse-update</id><summary type="html">&lt;p&gt;I've been working on improving &lt;a href="https://apse.io"&gt;Apse&lt;/a&gt; since I initially &lt;a href="/apse-a-personal-search-engine"&gt;released it two months ago.&lt;/a&gt; Version 1.4 is now available. There are some exciting new features!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apse is self-aware. The QR-code-ish squares in the corners of the window are markers for Apse to locate itself. If Apse sees itself in …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I've been working on improving &lt;a href="https://apse.io"&gt;Apse&lt;/a&gt; since I initially &lt;a href="/apse-a-personal-search-engine"&gt;released it two months ago.&lt;/a&gt; Version 1.4 is now available. There are some exciting new features!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apse is self-aware. The QR-code-ish squares in the corners of the window are markers for Apse to locate itself. If Apse sees itself in a snapshot, it redacts that portion of the screen so you don't get Apse searches appearing in Apse results, appearing in Apse searches, &lt;em&gt;ad infinitum&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Smart image processing: Apse auto-detects dark backgrounds to drastically improve accuracy in e.g. dark-theme windows and text editors.&lt;/li&gt;
&lt;li&gt;Take Snapshot Now - you can manually trigger a snapshot at any time, in the app or with &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/apse-snapshot/"&gt;the Firefox extension.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Results are now sortable by relevance or date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These features were both a lot of fun to work on and have been really useful for me. The smart image processing in particular was quite technically challenging, and I am very pleased with the results.&lt;/p&gt;
&lt;p&gt;The changes for each version of Apse &lt;a href="https://apse.io/news"&gt;here.&lt;/a&gt;
You can sign up for a trial or upgrade your license &lt;a href="https://apse.io/buy"&gt;here!&lt;/a&gt;&lt;/p&gt;</content><category term="misc"></category><category term="programming"></category><category term="software"></category><category term="computer vision"></category><category term="apse"></category></entry><entry><title>FAA Part 107 Waiver Search</title><link href="https://foxrow.com/faa-part-107-waiver-search" rel="alternate"></link><published>2019-03-01T12:00:00-06:00</published><updated>2019-03-01T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-03-01:/faa-part-107-waiver-search</id><summary type="html">&lt;p&gt;The FAA allows pilots to fly drones for commercial operations if they obey part 107 rules. They also pilots who meet sufficient safety requirements to operate outside the normal regulations &lt;a href="https://www.faa.gov/uas/request_waiver/waivers_granted/"&gt;via a waiver.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had been &lt;a href="https://foxrow.com/drone-pilots-november-2018"&gt;compiling information&lt;/a&gt; on the number of part 107 pilots, and thought it would be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The FAA allows pilots to fly drones for commercial operations if they obey part 107 rules. They also pilots who meet sufficient safety requirements to operate outside the normal regulations &lt;a href="https://www.faa.gov/uas/request_waiver/waivers_granted/"&gt;via a waiver.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had been &lt;a href="https://foxrow.com/drone-pilots-november-2018"&gt;compiling information&lt;/a&gt; on the number of part 107 pilots, and thought it would be useful to find pilots with waivers nearby.&lt;/p&gt;
&lt;p&gt;The FAA publishes that information in a poor format for searching, so I had the idea to make an interface to find pilots with a particular waiver, or near a particular location. It's a geospatial-enabled waiver search.&lt;/p&gt;
&lt;p&gt;It is live at &lt;a href="https://waivers.foxrow.com"&gt;waivers.foxrow.com.&lt;/a&gt; If you're interested in using it &lt;a href="https://foxrow.com/about"&gt;let me know!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="waiver search screenshot" class="m-image" src="https://foxrow.com/assets/waiversearch.png" title="waiver screenshot"/&gt;&lt;/p&gt;</content><category term="Drone"></category><category term="programming"></category><category term="software"></category><category term="drone"></category><category term="gis"></category><category term="geospatial"></category></entry><entry><title>APSE - A Personal Search Engine</title><link href="https://foxrow.com/apse-a-personal-search-engine" rel="alternate"></link><published>2019-02-16T12:00:00-06:00</published><updated>2019-02-16T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2019-02-16:/apse-a-personal-search-engine</id><summary type="html">&lt;p&gt;&lt;a href="https://apse.io"&gt;APSE&lt;/a&gt; is an application I created to help me find things on my computer. It stands for A Personal Search Engine.&lt;/p&gt;
&lt;h2&gt;What it is&lt;/h2&gt;
&lt;p&gt;Apse is a desktop application that works as a search engine for anything that you see on your computer. As you use your computer, it captures …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://apse.io"&gt;APSE&lt;/a&gt; is an application I created to help me find things on my computer. It stands for A Personal Search Engine.&lt;/p&gt;
&lt;h2&gt;What it is&lt;/h2&gt;
&lt;p&gt;Apse is a desktop application that works as a search engine for anything that you see on your computer. As you use your computer, it captures text that appears anywhere on your screen - in a spreadsheet, text doc, web page, PDF, you name it. It even captures text inside videos and images. Everything is stored for you to search later.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Apse runs in the background of your desktop or laptop. You use your computer normally, and Apse archives your history in its search index.&lt;/p&gt;
&lt;p&gt;Apse periodically takes a screenshot of your monitor and extracts any text. That text is stored in a search index, so you can search it like any other search engine.&lt;/p&gt;
&lt;p&gt;Apse automatically suggests spelling corrections - &lt;em&gt;did you mean X?&lt;/em&gt;
Apse also lets you search forward or backward in time, if you know you are close but one hit doesn't have what you are looking for.&lt;/p&gt;
&lt;p&gt;When you find what you're looking for, Apse shows you what was on your screen at the time, along with all the text it extracted, and the timestamp:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sample search results page" class="m-image" src="https://apse.io/static/img/better.png" title="APSE search results"/&gt;
&lt;img alt="sample search hit" class="m-image" src="https://apse.io/static/img/serp.png" title="APSE search hit"/&gt;&lt;/p&gt;
&lt;h2&gt;Privacy&lt;/h2&gt;
&lt;p&gt;Apse doesn't run in the cloud at all - it all stays local on your machine. Screenshots of your computer are really sensitive information, so everything Apse records stays on your computer, where it belongs.&lt;/p&gt;
&lt;p&gt;If you want Apse to stop recording temporarily for whatever reason, you can pause and unpause it at any time. You can also delete a hit or range of hits if something gets recorded you would rather not have saved on your hard drive.&lt;/p&gt;
&lt;h2&gt;Get Apse&lt;/h2&gt;
&lt;p&gt;You can find out about Apse at &lt;a href="https://apse.io"&gt;apse.io.&lt;/a&gt;
The latest version adds a feature where Apse can recognize itself in screenshots, and it will disregard that region of the image so as to not pollute your history with the things you are searching for.&lt;/p&gt;
&lt;p&gt;You can sign up &lt;a href="https://apse.io/buy"&gt;here.&lt;/a&gt; I hope you find it as useful as I do!&lt;/p&gt;</content><category term="misc"></category><category term="programming"></category><category term="software"></category><category term="computer vision"></category><category term="apse"></category></entry><entry><title>Using reCAPTCHA v3 with Django</title><link href="https://foxrow.com/using-recaptcha-v3-with-django" rel="alternate"></link><published>2018-12-12T12:00:00-06:00</published><updated>2018-12-12T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-12-12:/using-recaptcha-v3-with-django</id><summary type="html">&lt;p&gt;&lt;a href="https://developers.google.com/recaptcha/"&gt;reCAPTCHA&lt;/a&gt; is a system to prevent
spam and abuse of web services. It's easy to integrate with Django. There are
three steps to using v3 reCAPTCHA in your web app:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the reCAPTCHA application&lt;/li&gt;
&lt;li&gt;Integrate with your frontend&lt;/li&gt;
&lt;li&gt;Validate the response from Google&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Create the reCAPTCHA application&lt;/h1&gt;
&lt;p&gt;First, register a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://developers.google.com/recaptcha/"&gt;reCAPTCHA&lt;/a&gt; is a system to prevent
spam and abuse of web services. It's easy to integrate with Django. There are
three steps to using v3 reCAPTCHA in your web app:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the reCAPTCHA application&lt;/li&gt;
&lt;li&gt;Integrate with your frontend&lt;/li&gt;
&lt;li&gt;Validate the response from Google&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Create the reCAPTCHA application&lt;/h1&gt;
&lt;p&gt;First, register a new reCAPTCHA application at
&lt;a href="https://www.google.com/recaptcha/admin"&gt;https://www.google.com/recaptcha/admin&lt;/a&gt;:
&lt;img alt="recaptcha admin page" class="m-image" src="https://foxrow.com/assets/recaptcha_register.png" title="recaptcha admin panel"/&gt;&lt;/p&gt;
&lt;p&gt;Copy the value in the Secret Key box. Put this in your Django settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;GOOGLE_RECAPTCHA_SECRET_KEY = &amp;lt;secret_key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You probably don't want to hardcode the value in your &lt;code&gt;settings.py&lt;/code&gt;. You can
store it in a separate config file, and read it in via &lt;code&gt;configparser&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Integrate with your frontend&lt;/h1&gt;
&lt;p&gt;In the "Client side integration" section of the reCAPTCHA admin page, copy the
first snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://www.google.com/recaptcha/api.js?render=&amp;lt;YOUR_SITE_KEY&amp;gt;'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Copy the one from your admin page, not the snippet above. Or replace &lt;code&gt;&amp;lt;YOUR_SITE_KEY&amp;gt;&lt;/code&gt; with your actual site key.
Put that tag in your template's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;p&gt;Copy the second snippet from the reCAPTCHA admin page:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;grecaptcha.ready(function()&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;        &lt;/span&gt;grecaptcha.execute('&lt;span class="nt"&gt;&amp;lt;YOUR_SITE_KEY&amp;gt;&lt;/span&gt;',&lt;span class="w"&gt; &lt;/span&gt;{action:&lt;span class="w"&gt; &lt;/span&gt;'&lt;span class="nt"&gt;&amp;lt;ACTION_NAME&amp;gt;&lt;/span&gt;'})
&lt;span class="w"&gt;        &lt;/span&gt;.then(function(token)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;RESPONSE_ACTION&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;});
&lt;span class="w"&gt;    &lt;/span&gt;});
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Put that in the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag of the page you want to use reCAPTCHA on. You'll
need to replace &lt;code&gt;&amp;lt;YOUR_SITE_KEY&amp;gt;&lt;/code&gt; with your actual site key. The version on
your reCAPTCHA admin page should already have it. You also need to replace
&lt;code&gt;&amp;lt;ACTION_NAME&amp;gt;&lt;/code&gt; with an appropriate value - if you are using it to validate
user signups, you could use &lt;code&gt;signup&lt;/code&gt; for example.&lt;/p&gt;
&lt;p&gt;The next step is to replace &lt;code&gt;&amp;lt;RESPONSE_ACTION&amp;gt;&lt;/code&gt; with some javascript. If you
are using reCAPTCHA to protect form submissions, you might have this form on the
same page:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Google will return a &lt;code&gt;token&lt;/code&gt; to the client. That token needs to be passed to
your server for validation. You can do that like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;grecaptcha.ready(function()&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;        &lt;/span&gt;grecaptcha.execute('&lt;span class="nt"&gt;&amp;lt;YOUR_SITE_KEY&amp;gt;&lt;/span&gt;',&lt;span class="w"&gt; &lt;/span&gt;{action:&lt;span class="w"&gt; &lt;/span&gt;'&lt;span class="nt"&gt;&amp;lt;ACTION_NAME&amp;gt;&lt;/span&gt;'})
&lt;span class="w"&gt;        &lt;/span&gt;.then(function(token)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;            &lt;/span&gt;document.getElementById("form").appendChild(document.CreateElement(`&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"g-recaptcha-response"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;${token}`);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;});&lt;/span&gt;
&lt;span class="err"&gt;&amp;lt;/script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The form will now contain an extra item named &lt;code&gt;g-recaptcha-response&lt;/code&gt; on submission.&lt;/p&gt;
&lt;h1&gt;Validate the response from Google&lt;/h1&gt;
&lt;p&gt;In the view that processes your form, you need to separately verify the
submitted token is valid. If you are using a class-based view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# get the token submitted in the form&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;recaptcha_response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g-recaptcha-response'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://www.google.com/recaptcha/api/siteverify'&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;'secret'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOOGLE_RECAPTCHA_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;'response'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recaptcha_response&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# verify the token submitted with the form is valid&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# result will be a dict containing 'success' and 'action'.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# it is important to verify both&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'signup'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# make sure action matches the one from your template&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Invalid reCAPTCHA. Please try again.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form_invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;processing&lt;/span&gt;&lt;span class="o"&gt;...&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it! Now you have integrated reCAPTCHA with your Django app.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This post was written using Python 3.7.0 and Django 2.1.4.&lt;/em&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category></entry><entry><title>Drone Pilots November 2018</title><link href="https://foxrow.com/drone-pilots-november-2018" rel="alternate"></link><published>2018-11-05T12:00:00-06:00</published><updated>2018-11-05T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-11-05:/drone-pilots-november-2018</id><summary type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of November 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of November …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of November 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of November 1 2018, there are at least 70,882 pilots with a part 107 UAS
(drone) certificate. Following the &lt;a href="https://foxrow.com/drone-pilots-august-2018"&gt;60% rule,&lt;/a&gt;
that gives 118,136 112,700, an increase of 5,400 in the last month.&lt;/p&gt;
&lt;p&gt;&lt;img alt="number of part 107 certificates granted" class="m-image" src="https://foxrow.com/assets/certs201811.png" title="number of part 107 certificates granted"/&gt;&lt;/p&gt;
&lt;h2&gt;Part 107 Waivers&lt;/h2&gt;
&lt;p&gt;As of today, there have been 2,201 waivers granted to 2,020 individuals at 1,732
companies. That represents an increase of 52 waivers since last post.&lt;/p&gt;
&lt;p&gt;The first waiver to expire does so November of this year. Breakdown of waivers
granted by type:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regulation&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;107.29&lt;/td&gt;
&lt;td&gt;Daylight operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.41&lt;/td&gt;
&lt;td&gt;Operation in controlled airspace&lt;/td&gt;
&lt;td style="text-align: right;"&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.35&lt;/td&gt;
&lt;td&gt;Operation of multiple small unmanned aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.31&lt;/td&gt;
&lt;td&gt;Visual line of sight aircraft operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(b)&lt;/td&gt;
&lt;td&gt;Altitude&lt;/td&gt;
&lt;td style="text-align: right;"&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(b)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(d)&lt;/td&gt;
&lt;td&gt;Minimum distance from clouds&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(c)&lt;/td&gt;
&lt;td&gt;Minimum visbility&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)(2)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.25(b)&lt;/td&gt;
&lt;td&gt;Operation from a moving vehicle or aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(a)&lt;/td&gt;
&lt;td&gt;Operation over human beings (part of operation)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(a)&lt;/td&gt;
&lt;td&gt;Maximum groundspeed&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(b)&lt;/td&gt;
&lt;td&gt;Operation over human beings (covered)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The number of total waivers granted over time:
&lt;img alt="number of part 107 waivers granted" class="m-image" src="https://foxrow.com/assets/waivers201811.png" title="number of part 107 waivers granted"/&gt;&lt;/p&gt;</content><category term="Drone"></category><category term="drone"></category><category term="dataviz"></category></entry><entry><title>Drone Pilots October 2018</title><link href="https://foxrow.com/drone-pilots-october-2018" rel="alternate"></link><published>2018-10-16T12:00:00-05:00</published><updated>2018-10-16T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-10-16:/drone-pilots-october-2018</id><summary type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of October 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of October …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of October 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of October 1 2018, there are at least 67,631 pilots with a part 107 UAS
(drone) certificate. Following the &lt;a href="https://foxrow.com/drone-pilots-august-2018"&gt;60% rule,&lt;/a&gt;
that gives 112,700, an increase of 4,700 in the last month.&lt;/p&gt;
&lt;p&gt;&lt;img alt="number of part 107 certificates granted" class="m-image" src="https://foxrow.com/assets/certs201810.png" title="number of part 107 certificates granted"/&gt;&lt;/p&gt;
&lt;h2&gt;Part 107 Waivers&lt;/h2&gt;
&lt;p&gt;As of today, there have been 2,149 waivers granted to 1,977 individuals at 1,685
companies. That represents an increase of 129 waivers since last post.&lt;/p&gt;
&lt;p&gt;The first waiver to expire does so November of this year. Breakdown of waivers
granted by type:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regulation&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;107.29&lt;/td&gt;
&lt;td&gt;Daylight operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1975&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.41&lt;/td&gt;
&lt;td&gt;Operation in controlled airspace&lt;/td&gt;
&lt;td style="text-align: right;"&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.35&lt;/td&gt;
&lt;td&gt;Operation of multiple small unmanned aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.31&lt;/td&gt;
&lt;td&gt;Visual line of sight aircraft operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(b)&lt;/td&gt;
&lt;td&gt;Altitude&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(b)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(d)&lt;/td&gt;
&lt;td&gt;Minimum distance from clouds&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(c)&lt;/td&gt;
&lt;td&gt;Minimum visbility&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)(2)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.25(b)&lt;/td&gt;
&lt;td&gt;Operation from a moving vehicle or aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(a)&lt;/td&gt;
&lt;td&gt;Maximum groundspeed&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(b)&lt;/td&gt;
&lt;td&gt;Operation over human beings (covered)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(a)&lt;/td&gt;
&lt;td&gt;Operation over human beings (part of operation)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The number of total waivers granted over time:
&lt;img alt="number of part 107 waivers granted" class="m-image" src="https://foxrow.com/assets/waivers201810.png" title="number of part 107 waivers granted"/&gt;&lt;/p&gt;</content><category term="Drone"></category><category term="drone"></category><category term="dataviz"></category></entry><entry><title>BOGO - An Image Compression Format</title><link href="https://foxrow.com/bogo-an-image-compression-format" rel="alternate"></link><published>2018-09-23T12:00:00-05:00</published><updated>2018-09-23T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-09-23:/bogo-an-image-compression-format</id><summary type="html">&lt;p&gt;BOGO (Basically Optimal Graphics Ontology) is a lossless image compression format. BOGO derives its name from &lt;a href="https://en.wikipedia.org/wiki/Bogosort"&gt;bogosort.&lt;/a&gt; Unlike traditional computationally-wasteful
image formats that let your underutilized CPU sit around idle, BOGO maximizes your computer's time spent doing what it loves: computing.&lt;/p&gt;
&lt;p&gt;Contrary to rumor, BOGO does not stand for Blockchain-Oriented …&lt;/p&gt;</summary><content type="html">&lt;p&gt;BOGO (Basically Optimal Graphics Ontology) is a lossless image compression format. BOGO derives its name from &lt;a href="https://en.wikipedia.org/wiki/Bogosort"&gt;bogosort.&lt;/a&gt; Unlike traditional computationally-wasteful
image formats that let your underutilized CPU sit around idle, BOGO maximizes your computer's time spent doing what it loves: computing.&lt;/p&gt;
&lt;p&gt;Contrary to rumor, BOGO does not stand for Blockchain-Oriented Graphics Orifice. Why would you use a blockchain to store an image? That would be silly.&lt;/p&gt;
&lt;p&gt;A proof-of-concept encoder and decoder are included in &lt;a href="https://github.com/ryanfox/bogo"&gt;the reference implementation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# encoding
$ python encode.py input.jpg output.bogo

# decoding
$ python decode.py input.bogo output.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;BOGO image spec v0.0.1&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",  "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
RFC 2119.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;BOGO files should use the file extension ".bogo".&lt;/p&gt;
&lt;p&gt;BOGO files must adhere to the following format:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Byte #&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0-3&lt;/td&gt;
&lt;td&gt;Magic number containing the ASCII for "BOGO" (Decimal &lt;code&gt;66&lt;/code&gt; &lt;code&gt;79&lt;/code&gt; &lt;code&gt;71&lt;/code&gt; &lt;code&gt;79&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Width of the image in pixels. Trust me, you don't want an image larger than 256x256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Height of the image in pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Image mode. 0 corresponds to black and white, 1 to grayscale, and 2 to RGB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7-22&lt;/td&gt;
&lt;td&gt;Image checksum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23-N&lt;/td&gt;
&lt;td&gt;Image data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All numerical values must be unsigned integers in big-endian format.&lt;/p&gt;
&lt;h3&gt;Image mode&lt;/h3&gt;
&lt;p&gt;BOGO supports 3 image modes: black and white, grayscale, and RGB.&lt;/p&gt;
&lt;p&gt;For black and white images, each pixel value is stored as 1 or 0.&lt;/p&gt;
&lt;p&gt;For grayscale images, each pixel is stored as 0-255, with 8 bits per pixel.&lt;/p&gt;
&lt;p&gt;For RGB images, each pixel is stored as a 3-tuple of 0-255 values. Red must be the 0th value in the tuple, followed by green, followed by blue.&lt;/p&gt;
&lt;h3&gt;Checksum&lt;/h3&gt;
&lt;p&gt;A BOGO header contains a checksum to verify that the image has been correctly reconstructed. To compute the checksum of a given image, concatenate each pixel value in raster order and compute the MD5 of the result. For multi-channel images, data shall be concatenated per-pixel rather than per-channel. That is, concatenate the RGB values of the 0th pixel, followed by the RGB values of the 1st pixel, etc.&lt;/p&gt;
&lt;h3&gt;Image data&lt;/h3&gt;
&lt;p&gt;To compress image data, BOGO pixel values are stored using run-length encoding. To maximize compression, pixel values must be sorted before encoding. An encoded pixel is stored using two bytes for the count, followed by the pixel value. For simplicity, black and white pixel values are stored using a full byte, rather than a single bit.&lt;/p&gt;
&lt;p&gt;For black/white and grayscale images, this gives encoded pixels a width of 3 bytes. For RGB images, 2 bytes for count plus 3 bytes for value gives an encoded pixel width of 5 bytes.&lt;/p&gt;
&lt;h3&gt;Decoding&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Reconstruct the list of pixel values by decompressing the run-length encoded image data.&lt;/li&gt;
&lt;li&gt;Randomly shuffle the pixel values.&lt;/li&gt;
&lt;li&gt;Compute the MD5 checksum.&lt;/li&gt;
&lt;li&gt;If it matches the checksum stored in the file header, you are done. If not, go to step 2. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The BOGO reference implementation is &lt;a href="https://github.com/ryanfox/bogo"&gt;available on Github.&lt;/a&gt;&lt;/p&gt;</content><category term="programming"></category><category term="programming"></category></entry><entry><title>Howe Ridge Fire</title><link href="https://foxrow.com/howe-ridge-fire" rel="alternate"></link><published>2018-09-18T00:00:00-05:00</published><updated>2018-09-18T00:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-09-18:/howe-ridge-fire</id><summary type="html">&lt;p&gt;Photos from the 2018 Howe Ridge fire in Glacier National Park&lt;/p&gt;</summary><content type="html">&lt;p&gt;I visited Glacier National Park the second week of August this summer.
We stayed on the south end of Lake McDonald. One morning after a couple days
(and a thunderstorm the previous night), we noticed smoke rising from behind a
ridge a few miles across the lake.&lt;/p&gt;
&lt;p&gt;It turned out lightning had ignited dry timber, and started a forest fire. We
saw several "Super Scoopers" and helicopters getting water from the lake to
slow the fire's spread. We watched the fire grow throughout the first day and
night. Farther north along the lake, all people were evacuated
the first night - those are the lights you can see driving down the road.&lt;/p&gt;
&lt;p&gt;We didn't need to evacuate, though the town we were staying in was under an
evacuation warning. The fire continued to grow through the time we left. As
of this writing, the fire has grown to 14,000+ acres.&lt;/p&gt;</content><category term="Photo"></category><category term="photo"></category><category term="fire"></category></entry><entry><title>Are Punters Forever?</title><link href="https://foxrow.com/are-punters-forever" rel="alternate"></link><published>2018-09-04T12:00:00-05:00</published><updated>2018-09-04T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-09-04:/are-punters-forever</id><summary type="html">&lt;p&gt;At 538, Benjamin Morris wrote &lt;a href="https://fivethirtyeight.com/features/kickers-are-forever/"&gt;kickers are forever.&lt;/a&gt;
He presents a compelling case that kickers of all ages have been improving
steadily at all distances since the 1960's.&lt;/p&gt;
&lt;p&gt;I wanted to see if punters have done the same. Using play-by-play data from
2009 to 2017, I calculated how much of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At 538, Benjamin Morris wrote &lt;a href="https://fivethirtyeight.com/features/kickers-are-forever/"&gt;kickers are forever.&lt;/a&gt;
He presents a compelling case that kickers of all ages have been improving
steadily at all distances since the 1960's.&lt;/p&gt;
&lt;p&gt;I wanted to see if punters have done the same. Using play-by-play data from
2009 to 2017, I calculated how much of the available yardage punters are
eating up. An ideal punt goes out of bounds at the 1 yard line, with no chance
for a return. Given that target outcome, I defined punting fraction as the
percentage of available yards a punt gains for the kicking team. A kick from
the 50 to the opponent's 1 gives 49 / 50, or 0.98. Notice you can have a
negative punting fraction after a long return.&lt;/p&gt;
&lt;h2&gt;Yardage&lt;/h2&gt;
&lt;p&gt;Here's what the punting fraction looks like for every year since 2009.
It seems that punters &lt;em&gt;are&lt;/em&gt; getting better - there is a modest upward trend.&lt;/p&gt;
&lt;p&gt;&lt;img alt="punt fraction" class="m-image" src="https://foxrow.com/assets/punt_fraction.png" title="NFL punt fraction"/&gt;&lt;/p&gt;
&lt;p&gt;Broken down by distance, it's slightly more dramatic. At virtually all ranges,
the punting fraction has improved.&lt;/p&gt;
&lt;p&gt;&lt;img alt="punt fraction by distance" class="m-image" src="https://foxrow.com/assets/punt_fraction_binned.png" title="NFL punt fraction by distance"/&gt;&lt;/p&gt;
&lt;p&gt;40-50 yards away is pretty noisy, but still the recent years come out on top.&lt;/p&gt;
&lt;h2&gt;Touchbacks&lt;/h2&gt;
&lt;p&gt;A touchback is a pretty bad outcome for a punter - you would rather have it
come up 5 yards short instead of give up another 15. Here's the touchback rate
by year:&lt;/p&gt;
&lt;p&gt;&lt;img alt="touchback rate" class="m-image" src="https://foxrow.com/assets/punt_tb_rate.png" title="NFL punt touchback rate"/&gt;&lt;/p&gt;
&lt;p&gt;Again, a modest decrease across the board. The breakdown by distance shows
similar improvement:&lt;/p&gt;
&lt;p&gt;&lt;img alt="touchback rate by distance" class="m-image" src="https://foxrow.com/assets/punt_tb_rate_binned.png" title="NFL punt touchback rate by distance"/&gt;&lt;/p&gt;
&lt;p&gt;It's a noisy signal, especially once your on the short side of the 50, but the
more recent years fall to the bottom of the pile.&lt;/p&gt;
&lt;h2&gt;Returns&lt;/h2&gt;
&lt;p&gt;In an ideal world, the returner fair catches the kick, or the coverage team
downs the ball with no chance for a return. What does  the return rate
progression look like?&lt;/p&gt;
&lt;p&gt;&lt;img alt="return rate" class="m-image" src="https://foxrow.com/assets/punt_return_rate.png" title="NFL punt return rate"/&gt;&lt;/p&gt;
&lt;p&gt;I'm not sure what caused the uptick in 2015 - I don't think there were any
rules changes relevant to punt returns. The breakdown on return rate by
distance:&lt;/p&gt;
&lt;p&gt;&lt;img alt="return rate" class="m-image" src="https://foxrow.com/assets/punt_return_rate_binned.png" title="NFL punt return rate by distance"/&gt;&lt;/p&gt;
&lt;p&gt;Same story, different chart. Slight improvements, at most distances. Return
yardage follows an interesting path:&lt;/p&gt;
&lt;p&gt;&lt;img alt="average return yardage" class="m-image" src="https://foxrow.com/assets/punt_return_yards.png" title="NFL average punt return yards"/&gt;&lt;/p&gt;
&lt;p&gt;I'm not sure what caused the uptick in 2010 and 2011. The rules changes don't
seem like they would affect punt returns dramatically. Since the high in 2011,
return yardage is down almost 20%.&lt;/p&gt;
&lt;p&gt;In the end, we see modest improvement across nearly all fronts - more yardage,
less touchbacks, fewer and shorter returns.&lt;/p&gt;
&lt;p&gt;I wasn't able to find any stats on hang time, I imagine that would be
interesting to chart as well.&lt;/p&gt;
&lt;p&gt;While their improvement may not be as dramatic as their kicking brethren, it
looks like punters are forever too.&lt;/p&gt;
&lt;p&gt;Here's every punt from 2009-2017. There are a lot of data points, but a couple
features jump out - the touchback line, a few penalty lines as well. Also
interesting that TD returns are a danger at pretty much any distance. That makes
sense - once the returner has beaten all 11 would-be tacklers, they can usually
run as long as they need to.&lt;/p&gt;
&lt;p&gt;&lt;img alt="NFL punt fraction" class="m-image" src="https://foxrow.com/assets/punt_fraction_2009_2017.png" title="NFL punt fraction for all punts"/&gt;&lt;/p&gt;</content><category term="misc"></category><category term="sports"></category><category term="dataviz"></category></entry><entry><title>Drone Pilots September 2018</title><link href="https://foxrow.com/drone-pilots-september-2018" rel="alternate"></link><published>2018-09-04T12:00:00-05:00</published><updated>2018-09-04T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-09-04:/drone-pilots-september-2018</id><summary type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of September 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of September …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; The data
is current as of September 1, 2018. If this is interesting or you want more
information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of September 1 2018, there are at least 64,867 pilots with a part 107 UAS
(drone) certificate. Following the &lt;a href="https://foxrow.com/drone-pilots-august-2018"&gt;60% rule from last month,&lt;/a&gt;
that gives 108,000, an increase of 8,000 in the last month.&lt;/p&gt;
&lt;p&gt;&lt;img alt="number of part 107 certificates granted" class="m-image" src="https://foxrow.com/assets/certs201809.png" title="number of part 107 certificates granted"/&gt;&lt;/p&gt;
&lt;h2&gt;Part 107 Waivers&lt;/h2&gt;
&lt;p&gt;As of today, there have been 2,020 waivers granted to 1,855 individuals at 1,565
companies. That represents an increase of 21 waivers since last post.&lt;/p&gt;
&lt;p&gt;The first waiver to expire does so November of this year. Breakdown of waivers
granted by type:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regulation&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;107.29&lt;/td&gt;
&lt;td&gt;Daylight operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.41&lt;/td&gt;
&lt;td&gt;Operation in controlled airspace&lt;/td&gt;
&lt;td style="text-align: right;"&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.35&lt;/td&gt;
&lt;td&gt;Operation of multiple small unmanned aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.31&lt;/td&gt;
&lt;td&gt;Visual line of sight aircraft operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(b)&lt;/td&gt;
&lt;td&gt;Altitude&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(b)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(d)&lt;/td&gt;
&lt;td&gt;Minimum distance from clouds&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(c)&lt;/td&gt;
&lt;td&gt;Minimum visibility&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)(2)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.25(b)&lt;/td&gt;
&lt;td&gt;Operation from a moving vehicle or aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(a)&lt;/td&gt;
&lt;td&gt;Maximum groundspeed&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(a)&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The number of total waivers granted over time:
&lt;img alt="number of part 107 waivers granted" class="m-image" src="https://foxrow.com/assets/waivers201809.png" title="number of part 107 waivers granted"/&gt;&lt;/p&gt;</content><category term="Drone"></category><category term="drone"></category><category term="dataviz"></category></entry><entry><title>Graph Perturbation</title><link href="https://foxrow.com/graph-perturbation" rel="alternate"></link><published>2018-08-29T12:00:00-05:00</published><updated>2018-08-29T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-08-29:/graph-perturbation</id><summary type="html">&lt;p&gt;Tom7 has an interesting video about portmanteaus. I took the idea to make a program to draw a graph as you move around nodes to shorten the lines (called edges) between them.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Tom7 has an &lt;a href="https://www.youtube.com/watch?v=QVn2PZGZxaI"&gt;interesting video about portmanteaus.&lt;/a&gt;
I took the idea to make a program to draw a graph as you move around nodes to
shorten the lines (called edges) between them. Here's what a small run looks like:&lt;/p&gt;
&lt;video class="m-image" controls="" muted="" src="/assets/perturbation.mp4"&gt;&lt;/video&gt;
&lt;p&gt;Source on github &lt;a href="https://github.com/ryanfox/doodles/blob/master/graph_perturbation.py"&gt;here.&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="dataviz"></category></entry><entry><title>Drone Pilots August 2018</title><link href="https://foxrow.com/drone-pilots-august-2018" rel="alternate"></link><published>2018-08-28T12:00:00-05:00</published><updated>2018-08-28T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-08-28:/drone-pilots-august-2018</id><summary type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; If this
is interesting or you want more information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of August 28 2018, there are at least 61,613 pilots …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are some statistics on drone pilot activity in the US. I obtained all the
data from publicly available sources on &lt;a href="https://www.faa.gov"&gt;faa.gov.&lt;/a&gt; If this
is interesting or you want more information, &lt;a href="https://foxrow.com/about"&gt;get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Remote Pilot Certifications&lt;/h2&gt;
&lt;p&gt;As of August 28 2018, there are at least 61,613 pilots with a part 107 UAS (drone)
certificate. I say at least because the FAA allows pilots to exclude themselves
from the public database, in which case they do not show up in the data set.&lt;/p&gt;
&lt;p&gt;Per &lt;a href="https://www.faa.gov/news/updates/?newsId=91086"&gt;this press release,&lt;/a&gt; the
FAA hit 100,000 certificates about a month ago, so a first order estimate puts
the number of pilots who don't exclude themselves around 60%.&lt;/p&gt;
&lt;p&gt;The FAA publishes new data every month, so in a couple days there will be
more available. This is the first month I have obtained data, so I don't
have anything to compare to yet.&lt;/p&gt;
&lt;h2&gt;Part 107 Waivers&lt;/h2&gt;
&lt;p&gt;As of today, there have been 1,999 waivers granted to 1,854 individuals at 1,544
companies.&lt;/p&gt;
&lt;p&gt;The first waiver to expire does so November of this year. Breakdown of waivers
granted by type:&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regulation&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;107.29&lt;/td&gt;
&lt;td&gt;Daylight operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1829&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.41&lt;/td&gt;
&lt;td&gt;Operation in controlled airspace&lt;/td&gt;
&lt;td style="text-align: right;"&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.35&lt;/td&gt;
&lt;td&gt;Operation of multiple small unmanned aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.31&lt;/td&gt;
&lt;td&gt;Visual line of sight aircraft operation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(b)&lt;/td&gt;
&lt;td&gt;Altitude&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(b)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(d)&lt;/td&gt;
&lt;td&gt;Minimum distance from clouds&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(c)&lt;/td&gt;
&lt;td&gt;Minimum visibility&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)(2)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.25(b)&lt;/td&gt;
&lt;td&gt;Operation from a moving vehicle or aircraft&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.51(a)&lt;/td&gt;
&lt;td&gt;Maximum groundspeed&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.39(a)&lt;/td&gt;
&lt;td&gt;Operation over human beings&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;107.33(c)&lt;/td&gt;
&lt;td&gt;Visual observer&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The number of total waivers granted over time:
&lt;img alt="number of part 107 waivers granted" class="m-image" src="https://foxrow.com/assets/waivers201808.png" title="number of part 107 waivers granted"/&gt;&lt;/p&gt;</content><category term="Drone"></category><category term="drone"></category><category term="dataviz"></category></entry><entry><title>Technical Writing</title><link href="https://foxrow.com/technical-writing" rel="alternate"></link><published>2018-07-30T00:00:00-05:00</published><updated>2018-07-30T00:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-07-30:/technical-writing</id><summary type="html">&lt;p&gt;Years ago I remember coming across a comment on &lt;a href="edwardtufte.com"&gt;edwardtufte.com&lt;/a&gt; about technical writing. I had forgotten exactly what it said, but remembered it was short, had a snappy title, and followed its own advice.&lt;/p&gt;
&lt;p&gt;I discovered it again. The page at the original url is long gone, but the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Years ago I remember coming across a comment on &lt;a href="edwardtufte.com"&gt;edwardtufte.com&lt;/a&gt; about technical writing. I had forgotten exactly what it said, but remembered it was short, had a snappy title, and followed its own advice.&lt;/p&gt;
&lt;p&gt;I discovered it again. The page at the original url is long gone, but the Wayback Machine comes to the rescue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Best Writing Advice for Engineers I've Ever Seen. Period.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How to make engineers write concisely with sentences? By combining journalism with the technical report format. In a newspaper article, the paragraphs are ordered by importance, so that the reader can stop reading the article at whatever point they lose interest, knowing that the part they have read was more important than the part left unread.&lt;/p&gt;
&lt;p&gt;State your message in one sentence. That is your title. Write one paragraph justifying the message. That is your abstract. Circle each phrase in the abstract that needs clarification or more context. Write a paragraph or two for each such phrase. That is the body of your report. Identify each sentence in the body that needs clarification and write a paragraph or two in the appendix. Include your contact information for readers who require further detail.&lt;/p&gt;
&lt;p&gt;– William A. Wood, September 8, 2005&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;More accurately, the Wayback Machine has an archive of a page on Jottit, which appears to be a pastebin-style site. It looks like Jottit is running on Heroku, and currently returns an error for any page. Shame.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001yB"&gt;Here is the original link&lt;/a&gt;, &lt;a href="http://engineerwriting.jottit.com"&gt;the original jottit&lt;/a&gt;, and &lt;a href="https://web.archive.org/web/20160209151141/http://engineerwriting.jottit.com"&gt;the archived version on the Wayback Machine.&lt;/a&gt; Thanks William Wood, Edward Tufte, and &lt;a href="archive.org"&gt;archive.org!&lt;/a&gt;&lt;/p&gt;</content><category term="misc"></category><category term="writing howto"></category></entry><entry><title>Milwaukee Air Show</title><link href="https://foxrow.com/milwaukee-air-show" rel="alternate"></link><published>2018-07-29T00:00:00-05:00</published><updated>2018-07-29T00:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-07-29:/milwaukee-air-show</id><summary type="html">&lt;p&gt;Photos from the Milwaukee air and water show&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Update July 2022&lt;/em&gt;: Added photos from the 2022 show, featuring the Blue Angels and an F-22.&lt;/p&gt;
&lt;p&gt;The 2018 Milwaukee air and water show featured the USAF Thunderbirds. I got some photos of their practice flight around downtown Milwaukee the day before the show. Super loud!&lt;/p&gt;</content><category term="Photo"></category><category term="photo"></category><category term="planes"></category></entry><entry><title>Troubleshooting Raspberry Pi Headless Wifi Config</title><link href="https://foxrow.com/raspberry-pi-headless-wifi-config" rel="alternate"></link><published>2018-05-31T12:00:00-05:00</published><updated>2018-05-31T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-05-31:/raspberry-pi-headless-wifi-config</id><summary type="html">&lt;p&gt;There are several models of Raspberry Pi with built-in wireless support. They
can be configured to connect to a wireless network before powering on, by
creating a file named &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; in the boot partition of your SD
card.&lt;/p&gt;
&lt;p&gt;If you have your card properly configured, but the Pi won't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There are several models of Raspberry Pi with built-in wireless support. They
can be configured to connect to a wireless network before powering on, by
creating a file named &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; in the boot partition of your SD
card.&lt;/p&gt;
&lt;p&gt;If you have your card properly configured, but the Pi won't connect, make sure
you have UNIX-style &lt;code&gt;\n&lt;/code&gt; line endings and not windows-style &lt;code&gt;\r\n&lt;/code&gt; endings in
&lt;code&gt;wpa_supplicant.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is what the file should look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;
&lt;span class="n"&gt;update_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;ctrl_interface&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wpa_supplicant&lt;/span&gt;

&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan_ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"My Network Name"&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;psk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your password"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Make sure you have the right line endings, place that file in the boot
partition, and you're good to go.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="rpi"></category><category term="howto"></category></entry><entry><title>Markdown Tables in Sphinx</title><link href="https://foxrow.com/sphinx-markdown-tables" rel="alternate"></link><published>2018-05-30T12:00:00-05:00</published><updated>2018-05-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-05-30:/sphinx-markdown-tables</id><summary type="html">&lt;p&gt;&lt;a href="http://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; is a really useful tool, but one
shortcoming is its markdown support. Specifically, it uses the
&lt;a href="https://github.com/rtfd/recommonmark"&gt;recommonmark&lt;/a&gt; specification, which does
not support tables. I wrote a Sphinx extension to render tables authored in
&lt;a href="https://pypi.org/project/sphinx-markdown-tables/"&gt;markdown.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It can be installed via &lt;code&gt;pip install sphinx-markdown-tables&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you don't already have Sphinx configured …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; is a really useful tool, but one
shortcoming is its markdown support. Specifically, it uses the
&lt;a href="https://github.com/rtfd/recommonmark"&gt;recommonmark&lt;/a&gt; specification, which does
not support tables. I wrote a Sphinx extension to render tables authored in
&lt;a href="https://pypi.org/project/sphinx-markdown-tables/"&gt;markdown.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It can be installed via &lt;code&gt;pip install sphinx-markdown-tables&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you don't already have Sphinx configured to parse markdown, run
&lt;code&gt;pip install recommonmark&lt;/code&gt;, and edit &lt;code&gt;conf.py&lt;/code&gt; to tell Sphinx to parse your
markdown files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;source_parsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'.md'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'recommonmark.parser.CommonMarkParser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;source_suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'.rst'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.md'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once Sphinx is configured appropriately, add sphinx-markdown-tables to extensions, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'sphinx-markdown-tables'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can find the source on github &lt;a href="https://github.com/ryanfox/sphinx-markdown-tables"&gt;here.&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="sphinx"></category><category term="web"></category><category term="markdown"></category></entry><entry><title>Cyphercon 2018</title><link href="https://foxrow.com/cyphercon" rel="alternate"></link><published>2018-05-09T12:00:00-05:00</published><updated>2018-05-09T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-05-09:/cyphercon</id><summary type="html">&lt;p&gt;I gave a talk on &lt;a href="https://en.wikipedia.org/wiki/Steganography"&gt;Steganography&lt;/a&gt; at
&lt;a href="https://cyphercon.com/"&gt;Cyphercon 3.0.&lt;/a&gt; I want to give thanks to the organizers
who put everything together. It was a blast - really fun conference with a lot
of things to see, cool demos, and gadgets to try out.&lt;/p&gt;
&lt;p&gt;The slides from my talk &lt;a href="/assets/stego.pdf"&gt;are available …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I gave a talk on &lt;a href="https://en.wikipedia.org/wiki/Steganography"&gt;Steganography&lt;/a&gt; at
&lt;a href="https://cyphercon.com/"&gt;Cyphercon 3.0.&lt;/a&gt; I want to give thanks to the organizers
who put everything together. It was a blast - really fun conference with a lot
of things to see, cool demos, and gadgets to try out.&lt;/p&gt;
&lt;p&gt;The slides from my talk &lt;a href="/assets/stego.pdf"&gt;are available here.&lt;/a&gt; There was video
recorded of the talk, I will update this when it becomes available.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="talk"></category><category term="conference"></category></entry><entry><title>Tips for drone video - camera settings</title><link href="https://foxrow.com/drone-video-tips" rel="alternate"></link><published>2018-01-09T12:00:00-06:00</published><updated>2018-01-09T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-01-09:/drone-video-tips</id><summary type="html">&lt;p&gt;Drones can get great aerial photos and video, but they're only as good as the person piloting them. In the right hands, &lt;a href="https://www.flickr.com/search/?text=iphone"&gt;a phone camera can get fantastic shots&lt;/a&gt;, and all the equipment in the world won't help if you don't know how to use it.&lt;/p&gt;
&lt;p&gt;The first time you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Drones can get great aerial photos and video, but they're only as good as the person piloting them. In the right hands, &lt;a href="https://www.flickr.com/search/?text=iphone"&gt;a phone camera can get fantastic shots&lt;/a&gt;, and all the equipment in the world won't help if you don't know how to use it.&lt;/p&gt;
&lt;p&gt;The first time you take your drone out, you're probably not getting dramatic, hollywood shots.&lt;/p&gt;
&lt;p&gt;Here's some tips I've found help me get better photos and videos.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The biggest thing is that aerial photos and video are &lt;em&gt;photos and video&lt;/em&gt; - all the same rules and tips apply, your camera just happens to be up in the air.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;First things first, find the image quality settings and turn the image quality all the way up. If your drone supports RAW, go ahead if you want to use it. For most purposes, the highest-quality JPG will be more than sufficient. Memory cards give you effectively infinite space - no sense saving room for photos you'll never take.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another setting you should turn on is the grid. This lets you easily check your composition against the &lt;a href="https://en.wikipedia.org/wiki/Rule_of_thirds"&gt;rule of thirds.&lt;/a&gt; It still makes for good shots when you're airborne!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Last thing in the settings - white balance and color settings. Most drones have pretty blah defaults. If you're flying outside, you probably want sunny or cloudy white balance. If your drone supports it, you can enable different color modes for a more vibrant look. DJI has a few options, try them out and see which you like best. D-Cinelike in particular works pretty well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I plan on making more tips for aerial video. If you're interested in more, check out &lt;a href="https://dronestable.com"&gt;Drone Stable&lt;/a&gt; and sign up for the newsletter. Good luck flying :)&lt;/p&gt;</content><category term="Drone"></category><category term="drone"></category><category term="photo"></category></entry><entry><title>What I learned in 2017</title><link href="https://foxrow.com/2017-year-in-review" rel="alternate"></link><published>2018-01-05T12:00:00-06:00</published><updated>2018-01-05T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2018-01-05:/2017-year-in-review</id><summary type="html">&lt;p&gt;Here are some things I learned in 2017, a summary of what I worked on the last year, and what I hope to accomplish in 2018.&lt;/p&gt;
&lt;p&gt;A lot of them are things that have been said before. I think they bear repeating partially thanks to &lt;a href="https://xkcd.com/1053/"&gt;the lucky 10,000 effect …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here are some things I learned in 2017, a summary of what I worked on the last year, and what I hope to accomplish in 2018.&lt;/p&gt;
&lt;p&gt;A lot of them are things that have been said before. I think they bear repeating partially thanks to &lt;a href="https://xkcd.com/1053/"&gt;the lucky 10,000 effect.&lt;/a&gt; Another reason is what's obvious to you isn't necessarily so to others - the most heavily-trafficked page on the site by far was &lt;a href="https://foxrow.com/installing-opencv-in-a-virtualenv"&gt;Installing OpenCV in a virtualenv,&lt;/a&gt; hardly groundbreaking research.&lt;/p&gt;
&lt;p&gt;I include my future self in this: the page &lt;em&gt;I&lt;/em&gt; visited the most in 2017 was &lt;a href="https://foxrow.com/generating-django-secret-keys"&gt;generating Django secret keys.&lt;/a&gt; I wrote it, and I still refer to it all the time. You in the future is a different person.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Consulting&lt;/h2&gt;
&lt;p&gt;2017 was a big year for drones. I consulted primarily on the data processing side. I got to work on some really fun projects.&lt;/p&gt;
&lt;p&gt;Many of the interesting things I helped build were photo-based: panoramas, orthomosaics (fancy name for maps), image recognition. Others used photos for more complex products: photogrammetry, point clouds, 3d modeling, video processing, etc.&lt;/p&gt;
&lt;p&gt;A big highlight was building out an automated aerial mapping data pipeline. I worked with a client collecting drone photos and video from sites across the country. We were able to consolidate their process and automate much of it. We got a ~5x improvement in throughput over manually processing the data.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Speaking&lt;/h2&gt;
&lt;p&gt;I gave my &lt;a href="https://foxrow.com/pyohio-2017"&gt;first&lt;/a&gt; conference talk at &lt;a href="https://pyohio.org/"&gt;PyOhio&lt;/a&gt; and reprised the talk at &lt;a href="https://www.milwaukeecodecamp.com/"&gt;Milwaukee Code Camp&lt;/a&gt;. It was a blast.&lt;/p&gt;
&lt;p&gt;I also gave my first, second, and third presentations at a meetup. These were fun too, and I made some friends working on really interesting stuff as well.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Personal projects&lt;/h2&gt;
&lt;p&gt;I created &lt;a href="https://foxrow.com/DIL"&gt;DIL,&lt;/a&gt; software for processing drone imagery. &lt;/p&gt;
&lt;p&gt;I also created &lt;a href="https://dronestable.com"&gt;Drone Stable.&lt;/a&gt; I started showing it to pilots in October, and I've gotten a really good response. Here's what it does:&lt;/p&gt;
&lt;video autoplay="" controls="" loop="" muted="" src="https://dronestable.com/static/cropped.mp4" style="width: 100%; max-width: 600px;"&gt;&lt;/video&gt;
&lt;p&gt;Visit &lt;a href="https://dronestable.com"&gt;dronestable.com&lt;/a&gt; to check it out.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Goals for 2018&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I have not made as much progress on the &lt;a href="https://foxrow.com/computer-vision-cheat-sheet"&gt;computer vision cheat sheet&lt;/a&gt; as I would like. I want to write at least one item per month for it, either adding an example to the list or creating new items.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Continue consulting with drone companies. I think businesses can get a lot of value out of machine learning and automation. If you're one of those people and want to talk, &lt;a href="mailto:ryan@foxrow.com"&gt;email me!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep up on public speaking - I would like to give a talk at two conferences this year.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I am thrilled about the feedback I've gotten on Drone Stable. I want to dedicate the time it needs to really grow in 2018. It will likely be my biggest project for a while. There's certainly enough shaky footage around. I hope it can start to pay the rent :)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Publish the 2017 year in review on time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See you in 2018!&lt;/p&gt;</content><category term="Review"></category><category term="review"></category></entry><entry><title>DIL - Drone Imaging Language</title><link href="https://foxrow.com/DIL" rel="alternate"></link><published>2017-12-13T12:00:00-06:00</published><updated>2017-12-13T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-12-13:/DIL</id><summary type="html">&lt;p&gt;I work a lot with drone data. Two common tasks are stitching 360 degee
panoramas and detecting objects in an image.&lt;/p&gt;
&lt;p&gt;Both of those are technically complicated - panorama stitching involves feature
point detection, correlation, determining homographies, seam carving, and more.&lt;/p&gt;
&lt;p&gt;Object detection is a whole field of research unto itself …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I work a lot with drone data. Two common tasks are stitching 360 degee
panoramas and detecting objects in an image.&lt;/p&gt;
&lt;p&gt;Both of those are technically complicated - panorama stitching involves feature
point detection, correlation, determining homographies, seam carving, and more.&lt;/p&gt;
&lt;p&gt;Object detection is a whole field of research unto itself, but suffice to say
it is not a simple process.&lt;/p&gt;
&lt;p&gt;There are programming tools for both of those tasks, but if you're not a
programmer it is going to be difficult to take advantage of most of them.&lt;/p&gt;
&lt;p&gt;One way to deal with that barrier to entry is to create &lt;a href="https://en.wikipedia.org/wiki/Domain-specific_language"&gt;a DSL, or domain
specific language.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The idea is not to create a general-purpose language, but one tailored to a
specific use case (domain). This lets you create a simple interface, for example
one that closely follows English, so non-technical users can take advantage of
it.&lt;/p&gt;
&lt;p&gt;To that end, I created a DSL for drone imagery:
&lt;a href="https://github.com/foxrow/dil"&gt;DIL, the Drone Imaging Language.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It's deliberately designed to be very simple. Here's a sample of DIL code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DJI_001&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DJI_002&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DJI_003&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;
&lt;span class="n"&gt;highlight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
&lt;span class="n"&gt;save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That will load the 3 specified images, highlight any boats or people in them,
and save the results. There's 80 object classes available for highlighting,
see &lt;a href="https://github.com/FoxRow/DIL/blob/master/lib/classes.txt"&gt;classes.txt&lt;/a&gt; for
the full list.&lt;/p&gt;
&lt;p&gt;There are 5 commands available in DIL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;load&lt;/span&gt;
&lt;span class="n"&gt;highlight&lt;/span&gt;
&lt;span class="n"&gt;stitch&lt;/span&gt;
&lt;span class="n"&gt;show&lt;/span&gt;
&lt;span class="n"&gt;save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Commands are run one at a time from top to bottom. Each command does the
following:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;load&lt;/code&gt; - Load one or more images. Loaded images are the ones that will be
stitched and/or highlighted. This should be the first command in a DIL file.
There may be more &lt;code&gt;load&lt;/code&gt;s in a file. The most &lt;code&gt;load&lt;/code&gt; will replace old ones -
&lt;code&gt;save&lt;/code&gt; your operations if you don't want to lose them.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;highlight&lt;/code&gt; - Highlight the given classes in the loaded images. More than one
class may be specified. This is what a highlighted image looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="highighted cars" class="m-image" src="https://foxrow.com/assets/highlighted_cars.jpg" title="Highlighted cars"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stitch&lt;/code&gt; - stitch the loaded files into a panorama. This may be a regular
pano or 360.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;show&lt;/code&gt; - launch a web browser to visualize a panorama. Requires a &lt;code&gt;load&lt;/code&gt;ed set
of files to have already been &lt;code&gt;stitch&lt;/code&gt;ed. This is what the web viewer looks
like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="panorama" class="m-image" src="https://foxrow.com/assets/downtown_panorama.jpg" title="Milwaukee Panorama"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;save&lt;/code&gt; - save changes to files. This includes any highlighted images or stitched
panoramas.&lt;/p&gt;
&lt;p&gt;I talked about DIL at the end of my talk about computer vision and image
processing. &lt;a href="https://www.youtube.com/watch?v=IxA9KLIlLLQ"&gt;There is video here if you want to learn
more.&lt;/a&gt; Happy programming!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="DSL"></category><category term="drone"></category></entry><entry><title>What is Bootstrap?</title><link href="https://foxrow.com/what-is-bootstrap" rel="alternate"></link><published>2017-12-13T12:00:00-06:00</published><updated>2017-12-13T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-12-13:/what-is-bootstrap</id><summary type="html">&lt;p&gt;Maybe you're heard of Bootstrap, but don't know exactly what it is, or how to
use it? The landing page description is less than helpful:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build responsive, mobile-first projects on the web with the world's most popular front-end component library.
Bootstrap is an open source toolkit for developing with HTML …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;Maybe you're heard of Bootstrap, but don't know exactly what it is, or how to
use it? The landing page description is less than helpful:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build responsive, mobile-first projects on the web with the world's most popular front-end component library.
Bootstrap is an open source toolkit for developing with HTML, CSS, and JS. Quickly prototype your ideas or build your entire app with our Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful plugins built on jQuery. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's a quick primer with what you need to get started.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;What is Bootstrap?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://getbootstrap.com/"&gt;Twitter Bootstrap?&lt;/a&gt; It's a UI framework for websites.
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Ok, but what does that mean?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Web pages are a combination of HTML, CSS, and JavaScript. Bootstrap is a set of
CSS and (optional) JavaScript files with a boatload of common widgets and styles
for making a page look good with a minimum of time and effort.
&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Why would I use it?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Unstyled web pages look pretty rough, especially on very large or small
screens. Bootstrap gives you tools for easily styling a web page. The bigggest
win in my opinion is ease of use - add a CSS class to your tags and you're off
to the races. 
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;How do I get it?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;They're just regular old CSS and JS files. You can either link them &lt;a href="https://getbootstrap.com/docs/4.0/getting-started/download/"&gt;from the
Bootstrap CDN:&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.2.1.slim.min.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or you can download the files and serve them yourself.
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;How do I use it?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the files are included in your HTML, you get Bootstrap components by
applying CSS classes to HTML tags. Mostly &lt;code&gt;div&lt;/code&gt;s. Once you have the appropriate
files linked in your HTML document, add the classes you are interested in.
For instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;CLICK&lt;span class="w"&gt; &lt;/span&gt;HERE&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Renders like this: &lt;button style="display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#fff;background-color:#007bff;border-color:#007bff"&gt;CLICK HERE&lt;/button&gt;
&lt;br/&gt;
&lt;a href="https://getbootstrap.com/docs/4.0/components/alerts/"&gt;There's a ton more components here,&lt;/a&gt;
and a good starter template &lt;a href="https://getbootstrap.com/docs/4.0/getting-started/introduction/#starter-template"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You probably want to use the skeleton, at least in the beginning. It is
responsive by default, and includes all the files you need.
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Ok that's easy enough, but what about page layout?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Glad you asked! Bootstrap also includes a nifty grid system. The general pattern
is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- wraps all your content --&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- wraps a single row --&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- wraps a single column --&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;My&lt;span class="w"&gt; &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;column&lt;span class="w"&gt; &lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;My&lt;span class="w"&gt; &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;column&lt;span class="w"&gt; &lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;My&lt;span class="w"&gt; &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;column&lt;span class="w"&gt; &lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That will get you a centered, reasonably-sized container with 3 equally wide
columns of text. You can specify widths, content alignment, and much more by
adding more CSS classes. &lt;a href="https://getbootstrap.com/docs/4.0/layout/overview/"&gt;Tons more on the layout system here.&lt;/a&gt;
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Great, anything else I should know?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The project documentation is very extensive. &lt;a href="https://getbootstrap.com/docs/4.0/"&gt;Find out more about the components,
grid system, and utilities here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy styling!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="web"></category><category term="howto"></category></entry><entry><title>Django at Milwaukee Python</title><link href="https://foxrow.com/mke-python-django" rel="alternate"></link><published>2017-10-11T12:00:00-05:00</published><updated>2017-10-11T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-10-11:/mke-python-django</id><summary type="html">&lt;p&gt;On Wednesday night, the Milwaukee Python group met up to discuss &lt;a href="https://djangoproject.com"&gt;Django.&lt;/a&gt;
The meeting was hosted by &lt;a href="http://www.digitalmeasures.com/"&gt;Digital Measures&lt;/a&gt; in Milwaukee's third ward.
We had a great turnout and lots of good questions.&lt;/p&gt;
&lt;p&gt;Andrew Glassman and I both spoke on Django, its features, and how to use it.
&lt;a href="https://foxrow.com/assets/django.pdf"&gt;The notes …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;On Wednesday night, the Milwaukee Python group met up to discuss &lt;a href="https://djangoproject.com"&gt;Django.&lt;/a&gt;
The meeting was hosted by &lt;a href="http://www.digitalmeasures.com/"&gt;Digital Measures&lt;/a&gt; in Milwaukee's third ward.
We had a great turnout and lots of good questions.&lt;/p&gt;
&lt;p&gt;Andrew Glassman and I both spoke on Django, its features, and how to use it.
&lt;a href="https://foxrow.com/assets/django.pdf"&gt;The notes from my portion of the talk are here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The Django project I mentioned is &lt;a href="https://fastergs.com"&gt;Fast Ergs.&lt;/a&gt; A less-complicated app using Flask lives at &lt;a href="https://needsmorelensflare.com"&gt;Needsmorelensflare.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks to Digital Measures and &lt;a href="https://www.meetup.com/MKE-Python-Meetup"&gt;MKE Python&lt;/a&gt; for hosting us!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="web"></category><category term="meetup"></category><category term="talk"></category></entry><entry><title>Detecting circles in an image with Hough transforms</title><link href="https://foxrow.com/detecting-circles-in-an-image-with-hough-transforms" rel="alternate"></link><published>2017-09-13T12:00:00-05:00</published><updated>2017-09-13T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-09-13:/detecting-circles-in-an-image-with-hough-transforms</id><summary type="html">&lt;p&gt;&lt;em&gt;Part of a series of examples from the CV cheat sheet.  &lt;a href="https://foxrow.com/computer-vision-cheat-sheet"&gt;Click here for other computer vision
applications.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Detecting circles in an image is a common task in computer vision.  One way to do this is
with the &lt;a href="https://en.wikipedia.org/wiki/Hough_transform"&gt;Hough (pronounced "Huff") transform.&lt;/a&gt;
The math behind it is beyond the scope …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Part of a series of examples from the CV cheat sheet.  &lt;a href="https://foxrow.com/computer-vision-cheat-sheet"&gt;Click here for other computer vision
applications.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Detecting circles in an image is a common task in computer vision.  One way to do this is
with the &lt;a href="https://en.wikipedia.org/wiki/Hough_transform"&gt;Hough (pronounced "Huff") transform.&lt;/a&gt;
The math behind it is beyond the scope of this example, but we should be able to get you off the
ground using it with OpenCV and Python.  We're going to assume you have OpenCV installed already
and are able to use it in Python.&lt;/p&gt;
&lt;p&gt;Take an input image, say, these solar system moons:&lt;/p&gt;
&lt;p&gt;&lt;img alt="moons of the solar system" class="m-image" src="https://foxrow.com/assets/moons.jpg" title="Moons"/&gt;&lt;/p&gt;
&lt;p&gt;Let's detect the circles in this image.  The gist is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open an image&lt;/li&gt;
&lt;li&gt;Detect circles in it&lt;/li&gt;
&lt;li&gt;Draw those circles back on the image (you don't need to do this necessarily, but it's a nice visual)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the code to extract circles in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="m-table highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# import libraries we'll need&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# read in your image&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'moons.jpg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# convert to grayscale&lt;/span&gt;
&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# detect circles in the image&lt;/span&gt;
&lt;span class="c1"&gt;# dp and minDist are parameters that you can tune depending on&lt;/span&gt;
&lt;span class="c1"&gt;# your data.&lt;/span&gt;
&lt;span class="c1"&gt;# dp is the accumulator resolution. Think of it as accuracy of circle center location. Lower values take longer&lt;/span&gt;
&lt;span class="c1"&gt;# minDist is the minimum distance permitted between circle centers&lt;/span&gt;
&lt;span class="c1"&gt;# param1 is the upper threshold for the edge detection preprocessing. If you have low-contrast circles, increasing this may help&lt;/span&gt;
&lt;span class="c1"&gt;# param2 is &lt;/span&gt;
&lt;span class="c1"&gt;# param1 and param2 are optional.&lt;/span&gt;
&lt;span class="n"&gt;circles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HoughCircles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HOUGH_GRADIENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minDist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# circles are returned in (x, y, radius) format&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;circles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LINE_AA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# write the image to disk&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'houghcircles.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Here's the result:&lt;/p&gt;
&lt;p&gt;&lt;img alt="circled moons" class="m-image" src="https://foxrow.com/assets/houghcircles.jpg" title="Hough Circles"/&gt;&lt;/p&gt;
&lt;p&gt;As you can see, there is still some error!
The most elliptical of our &lt;a href="https://en.wikipedia.org/wiki/Galilean_moons"&gt;Galilean satellites&lt;/a&gt;
wasn't detected at all, and the radius of a couple others is off.
Tweaking the parameters for your use case will help this.
If you want to count almost-circles or not count them, how sharp the edge needs
to be to count as a detection, and so on.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="computer vision"></category><category term="python"></category></entry><entry><title>PyOhio 2017</title><link href="https://foxrow.com/pyohio-2017" rel="alternate"></link><published>2017-07-30T12:00:00-05:00</published><updated>2017-07-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-07-30:/pyohio-2017</id><summary type="html">&lt;p&gt;&lt;img alt="title slide" class="m-image" src="https://foxrow.com/assets/toinfinity.png" title="Title slide"/&gt;&lt;/p&gt;
&lt;p&gt;I was really happy to give a talk at &lt;a href="https://pyohio.org/"&gt;PyOhio&lt;/a&gt; this year.
My presentation was &lt;em&gt;To Infinity and Beyond&lt;/em&gt;, a talk about image processing,
computer vision, and astronomy.&lt;/p&gt;
&lt;p&gt;You can find the slides from my talk &lt;a href="https://foxrow.com/assets/toinfinity.pdf"&gt;in PDF format here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The programming language I talked about, &lt;a href="https://github.com/foxrow/dil"&gt;DIL, lives on GitHub …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="title slide" class="m-image" src="https://foxrow.com/assets/toinfinity.png" title="Title slide"/&gt;&lt;/p&gt;
&lt;p&gt;I was really happy to give a talk at &lt;a href="https://pyohio.org/"&gt;PyOhio&lt;/a&gt; this year.
My presentation was &lt;em&gt;To Infinity and Beyond&lt;/em&gt;, a talk about image processing,
computer vision, and astronomy.&lt;/p&gt;
&lt;p&gt;You can find the slides from my talk &lt;a href="https://foxrow.com/assets/toinfinity.pdf"&gt;in PDF format here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The programming language I talked about, &lt;a href="https://github.com/foxrow/dil"&gt;DIL, lives on GitHub.&lt;/a&gt;
Contributions are welcome!&lt;/p&gt;
&lt;p&gt;Video of the talk:&lt;/p&gt;
&lt;iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/IxA9KLIlLLQ" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;I'd like to give a big thanks to the organizers of the conference for putting
on such a great event, and for having me!&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Slides under &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;CC-BY 4.0.&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="conference"></category><category term="talk"></category></entry><entry><title>Which commits to squash when rebasing in Git</title><link href="https://foxrow.com/which-commits-to-squash-when-rebasing-in-git" rel="alternate"></link><published>2017-06-16T12:00:00-05:00</published><updated>2017-06-16T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-06-16:/which-commits-to-squash-when-rebasing-in-git</id><summary type="html">&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; is a really powerful tool, but I always forget which commits
to pick and squash.  Suppose your commit history looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bd7bf7d&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;keep
&lt;span class="m"&gt;2313327&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
d4ac9ee&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
c56d9c3&lt;span class="w"&gt; &lt;/span&gt;latest&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;don&lt;span class="err"&gt;'&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;change …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; is a really powerful tool, but I always forget which commits
to pick and squash.  Suppose your commit history looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bd7bf7d&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;keep
&lt;span class="m"&gt;2313327&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
d4ac9ee&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
c56d9c3&lt;span class="w"&gt; &lt;/span&gt;latest&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;don&lt;span class="err"&gt;'&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;change&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;one&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You want to run &lt;code&gt;git rebase -i HEAD~3&lt;/code&gt;, which will bring up your editor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pick&lt;span class="w"&gt; &lt;/span&gt;d4ac9ee&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
pick&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2313327&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;checkpoint&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
pick&lt;span class="w"&gt; &lt;/span&gt;bd7bf7d&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;keep

&lt;span class="c1"&gt;# Rebase c56d9c3..bd7bf7d onto c56d9c3 (3 command(s))&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Commands:&lt;/span&gt;
&lt;span class="c1"&gt;# p, pick = use commit&lt;/span&gt;
&lt;span class="c1"&gt;# r, reword = use commit, but edit the commit message&lt;/span&gt;
&lt;span class="c1"&gt;# e, edit = use commit, but stop for amending&lt;/span&gt;
&lt;span class="c1"&gt;# s, squash = use commit, but meld into previous commit&lt;/span&gt;
&lt;span class="c1"&gt;# f, fixup = like "squash", but discard this commit's log message&lt;/span&gt;
&lt;span class="c1"&gt;# x, exec = run command (the rest of the line) using shell&lt;/span&gt;
&lt;span class="c1"&gt;# d, drop = remove commit&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# These lines can be re-ordered; they are executed from top to bottom.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# If you remove a line here THAT COMMIT WILL BE LOST.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# However, if you remove everything, the rebase will be aborted.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Note that empty commits are commented out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Change the second two &lt;code&gt;picks&lt;/code&gt; to &lt;code&gt;s&lt;/code&gt; or &lt;code&gt;squash&lt;/code&gt;.  The editor should
look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;d4ac9ee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;checkpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;
&lt;span class="nt"&gt;squash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;2313327&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;checkpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;2&lt;/span&gt;
&lt;span class="nt"&gt;squash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;bd7bf7d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;keep&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Rebase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;c56d9c3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;bd7bf7d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;onto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;c56d9c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Commands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;r&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;reword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;e&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;amending&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;squash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;meld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;previous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;f&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fixup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"squash"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;discard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;x&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;line&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shell&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;d&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;drop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;These&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;re-ordered&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;they&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;executed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;top&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;bottom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;THAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;COMMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;WILL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;BE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;LOST&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;However&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;everything&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rebase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aborted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Note&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commented&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Save the file and exit.  Now you need to fix the commit messages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# This is a combination of 3 commits.
# The first commit's message is:
checkpoint commit 1

# This is the 2nd commit message:

checkpoint commit 2

# This is the 3rd commit message:

this is the commit message I want to keep
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Delete everything up to "this is the commit message I want to keep".
Save and quit your editor.  Now the rebase will proceed.  Your commit
history should now look like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="m"&gt;5151034&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;keep
c56d9c3&lt;span class="w"&gt; &lt;/span&gt;latest&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the latest commit has a new hash - it's a new commit, created out
of the three previous commits, which are now gone.  So use it carefully!&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="git"></category><category term="version control"></category><category term="howto"></category></entry><entry><title>Stitching 360 degree panoramas with Hugin</title><link href="https://foxrow.com/stitching-panoramas-with-hugin" rel="alternate"></link><published>2017-06-15T12:00:00-05:00</published><updated>2017-06-15T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-06-15:/stitching-panoramas-with-hugin</id><summary type="html">&lt;p&gt;&lt;a href="https://foxrow.com/stitching-panoramas-with-photoshop"&gt;In my last post&lt;/a&gt;,
I talked about stitching panoramas with Photoshop.  There is an open
source application named &lt;a href="http://hugin.sourceforge.net/"&gt;Hugin&lt;/a&gt; that
can do the same and more.  It is dedicated panorama stitching software,
so there are many more options available to you.&lt;/p&gt;
&lt;p&gt;This guide uses Hugin 2016.2.0.&lt;/p&gt;
&lt;p&gt;Hugin supports …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://foxrow.com/stitching-panoramas-with-photoshop"&gt;In my last post&lt;/a&gt;,
I talked about stitching panoramas with Photoshop.  There is an open
source application named &lt;a href="http://hugin.sourceforge.net/"&gt;Hugin&lt;/a&gt; that
can do the same and more.  It is dedicated panorama stitching software,
so there are many more options available to you.&lt;/p&gt;
&lt;p&gt;This guide uses Hugin 2016.2.0.&lt;/p&gt;
&lt;p&gt;Hugin supports basic panorama stitching, the same as Photoshop.  But where
it really shines is 360 degree panoramas.  With a panorama viewer like
&lt;a href="https://pannellum.org"&gt;Pannellum&lt;/a&gt;, you can view panos much more easily
on the web.  Here's how you can create one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Take a set of 360 degree panoramic photos.  This is the same
as a regular panorama, you just end up rotating 360 degrees.  You
can also stack multiple strips to get a bigger vertical field of view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open Hugin.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to Interface -&amp;gt; Expert
&lt;img alt="hugin control panel" class="m-image" src="https://foxrow.com/assets/hugin_advanced.jpg" title="Hugin control panel"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the lower left quadrant of the window, click Load Images.
Choose the images you want to stitch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under Feature Matching, Select Cpfind (multirow/stacked)
&lt;img alt="finding control points" class="m-image" src="https://foxrow.com/assets/hugin_cpfind.jpg" title="Hugin control point finder"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click Create Control Points.  This may take a few minutes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under Optimize, for Geometric, keep
Positions (incremental, starting from anchor).  Click Calculate.
This should be fairly quick.  Once it's done, click Yes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For Photometric optimization, keep Low dynamic range.  Click Yes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on the Stitcher tab.
&lt;img alt="hugin stitching tab" class="m-image" src="https://foxrow.com/assets/hugin_stitcher.jpg" title="Hugin Stitcher Tab"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave projection as Equirectangular.  This is the projection
360 degree panorama viewers are expecting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want, you can change the canvas size.  Clicking Calculate
Optimal Size makes the panorama as big as if the original images were
not resized at all.  You probably don't want this to be bigger than
10,000 pixels wide.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you like, you can click "Fit crop to images".  This will trim
any whitespace off the final panorama.  This is useful if you have
e.g. a hole in the sky directly above the camera.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the output format you want.  It defaults to TIF, which can
give very big image sizes.  JPG is a nice alternative for smaller files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click Stitch!  This may take a while, if the output size is large.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your panorama is ready.  While the raw image doesn't look like
a panorama, your 360 pano viewer of choice will re-warp it back to its
original state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;link href="https://cdn.pannellum.org/2.3/pannellum.css" rel="stylesheet"/&gt;&lt;/p&gt;
&lt;script src="https://cdn.pannellum.org/2.3/pannellum.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;div id="panorama" style="width: 600px; height: 400px;"&gt;&lt;/div&gt;
&lt;script&gt;
pannellum.viewer('panorama', {
    "type": "equirectangular",
    "panorama": "/assets/pano.jpg"
});
&lt;/script&gt;
&lt;p&gt;Happy shooting!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="image processing"></category></entry><entry><title>Stitching panoramas with Photoshop</title><link href="https://foxrow.com/stitching-panoramas-with-photoshop" rel="alternate"></link><published>2017-06-14T12:00:00-05:00</published><updated>2017-06-14T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2017-06-14:/stitching-panoramas-with-photoshop</id><summary type="html">&lt;p&gt;One of Photoshop's less frequently used features is the ability
to stitch together photos into a panorama.  Here's how you can go
about making them.&lt;/p&gt;
&lt;p&gt;This guide uses Photoshop CC 2017.1.1 on Windows 10.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In Photoshop, go to File -&amp;gt; Automate -&amp;gt; Photomerge.
&lt;img alt="photomerge panel" class="m-image" src="https://foxrow.com/assets/photomerge.jpg" title="Photomerge"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The photomerge dialog will come up.  You …&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;One of Photoshop's less frequently used features is the ability
to stitch together photos into a panorama.  Here's how you can go
about making them.&lt;/p&gt;
&lt;p&gt;This guide uses Photoshop CC 2017.1.1 on Windows 10.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In Photoshop, go to File -&amp;gt; Automate -&amp;gt; Photomerge.
&lt;img alt="photomerge panel" class="m-image" src="https://foxrow.com/assets/photomerge.jpg" title="Photomerge"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The photomerge dialog will come up.  You can experiment with different
layouts, but I have found "Auto" does the best job.
&lt;img alt="photomerge panel 2" class="m-image" src="https://foxrow.com/assets/dialog.jpg" title="Photomerge 2"/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hit browse and select the files you want to stitch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;You can change the options selected at the bottom, but the defaults
are a good starting point.  They don't affect the stitching results,
just the blending of images once they are aligned.&lt;/li&gt;
&lt;li&gt;Hit Ok.  Photoshop will load the images and start stitching.  This
may take a few minutes, depending on how big your final panorama is.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's it.  Once the stitching is complete, you can crop the image to
your liking, make modifications, or anything else Photoshop permits.&lt;/p&gt;
&lt;p&gt;Happy shooting!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="image processing"></category></entry><entry><title>Online QR code reader</title><link href="https://foxrow.com/qr" rel="alternate"></link><published>2016-12-20T00:00:00-06:00</published><updated>2016-12-20T00:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-12-20:/qr</id><summary type="html"></summary><content type="html">&lt;script src="assets/js/grid.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/version.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/detector.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/formatinf.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/errorlevel.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/bitmat.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/datablock.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/bmparser.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/datamask.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/rsdecoder.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/gf256poly.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/gf256.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/decoder.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/qrcode.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/findpat.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/alignpat.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="assets/js/databr.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
qrcode.callback = function(result) {
    var resultDiv = document.getElementById("result");
    resultDiv.textContent = result;
};

function update() {
    var result = document.getElementById("result");
    var file = document.getElementById("fileselector").files[0];
    var reader = new FileReader();

    reader.addEventListener("load", function() {
        qrcode.decode(reader.result);
    }, false);

    reader.readAsDataURL(file);

}
&lt;/script&gt;
&lt;p&gt;Choose an image to read a QR code:&lt;/p&gt;
&lt;p&gt;&lt;input accept="image/*" id="fileselector" type="file"/&gt;&lt;/p&gt;
&lt;div class="m-button m-primary m-left-s"&gt;
&lt;a href="#" onclick="update();"&gt;Read QR code&lt;/a&gt;
&lt;/div&gt;
&lt;div id="result"&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;div&gt;
&lt;small&gt;&lt;em&gt;QR-decoding code courtesy of &lt;a href="https://github.com/LazarSoft/jsqrcode"&gt;LazarSoft&lt;/a&gt;, available under the Apache License.&lt;/em&gt;&lt;/small&gt;
&lt;/div&gt;</content><category term="misc"></category><category term="tool"></category></entry><entry><title>Detecting lines in an image with Hough transforms</title><link href="https://foxrow.com/detecting-lines-in-an-image-with-hough-transforms" rel="alternate"></link><published>2016-12-02T12:00:00-06:00</published><updated>2016-12-02T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-12-02:/detecting-lines-in-an-image-with-hough-transforms</id><summary type="html">&lt;p&gt;&lt;em&gt;Part of a series of examples from the CV cheat sheet.  &lt;a href="https://foxrow.com/computer-vision-cheat-sheet"&gt;Click here for other computer vision
applications.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Detecting straight lines in an image is a common task in computer vision.  One way to do this is
with the &lt;a href="https://en.wikipedia.org/wiki/Hough_transform"&gt;Hough (pronounced "Huff") transform.&lt;/a&gt;
The math behind it is beyond the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Part of a series of examples from the CV cheat sheet.  &lt;a href="https://foxrow.com/computer-vision-cheat-sheet"&gt;Click here for other computer vision
applications.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Detecting straight lines in an image is a common task in computer vision.  One way to do this is
with the &lt;a href="https://en.wikipedia.org/wiki/Hough_transform"&gt;Hough (pronounced "Huff") transform.&lt;/a&gt;
The math behind it is beyond the scope of this example, but we should be able to get you off the
ground using it with OpenCV and Python.  We're going to assume you have OpenCV installed already
and are able to use it in Python.&lt;/p&gt;
&lt;p&gt;Take an input image, say, this basketball court:&lt;/p&gt;
&lt;p&gt;&lt;img alt="basketball court" class="m-image" src="https://foxrow.com/assets/court.jpg" title="Basketball court"/&gt;
&lt;em&gt;Credit: &lt;a href="https://commons.wikimedia.org/wiki/File:Basketball_Courts,_Sun_Yat_Sen_Memorial_Park_1.jpg"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's detect the straight lines in this image.  The gist is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open an image&lt;/li&gt;
&lt;li&gt;Detect edges in it&lt;/li&gt;
&lt;li&gt;Use those edges to detect straight lines&lt;/li&gt;
&lt;li&gt;Draw those lines back on the image (you don't need to do this necessarily, but it's a nice visual)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's the code to extract lines in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="m-table highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# import libraries we'll need&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# read in your image&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'court.jpg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# convert to grayscale so we can detect edges&lt;/span&gt;
&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# use Canny edge detector to find edges in the image.  The thresholds determine how&lt;/span&gt;
&lt;span class="c1"&gt;# weak or strong an edge will be detected.  These can be tweaked.&lt;/span&gt;
&lt;span class="n"&gt;lower_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;upper_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
&lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lower_threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upper_threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# detect lines in the image.  This is where the real work is done.  Higher threshold&lt;/span&gt;
&lt;span class="c1"&gt;# means a line needs to be stronger to be detected, so again, this can be tweaked.&lt;/span&gt;
&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;
&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HoughLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# convert each line to coordinates back in the original image&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;
        &lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;
        &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# draw each line on the image&lt;/span&gt;
        &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# write the image to disk&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'houghlines.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Here's the result:
&lt;img alt="hough lines" class="m-image" src="https://foxrow.com/assets/houghlines.jpg" title="Hough Lines"/&gt;
We can see it found the majority of lines on the court - and quite a few in the skyline.  This is
why you'll want to tune the threshold in &lt;code&gt;cv2.HoughLines(edges, 1, np.pi / 180, threshold)&lt;/code&gt; based
on your needs to prevent false positives or negatives.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="computer vision"></category><category term="python"></category></entry><entry><title>Computer vision cheat sheet</title><link href="https://foxrow.com/computer-vision-cheat-sheet" rel="alternate"></link><published>2016-11-15T12:00:00-06:00</published><updated>2016-11-15T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-11-15:/computer-vision-cheat-sheet</id><summary type="html">&lt;p&gt;Computer vision is a broad field - there are many algorithms and it can be hard to tell what's
the best way to attack a given problem.  This aims to provide a guide on what to use.&lt;/p&gt;
&lt;h3&gt;What this isn't&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The bleeding edge.  CV moves very quickly.  I haven't always heard …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Computer vision is a broad field - there are many algorithms and it can be hard to tell what's
the best way to attack a given problem.  This aims to provide a guide on what to use.&lt;/p&gt;
&lt;h3&gt;What this isn't&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The bleeding edge.  CV moves very quickly.  I haven't always heard about the newest bestest
way to analyze $YOUR_FAVORITE_THING.  &lt;a href="/about"&gt;Email me&lt;/a&gt; and I'll take it under consideration though!&lt;/li&gt;
&lt;li&gt;The best way.  "Best" often isn't even well-defined.  Preprocessing time, CPU usage, memory
usage, accuracy, and simplicity are all axes you might value differently for different
applications.  All else equal, I will try and show the simplest way to implement a solution.&lt;/li&gt;
&lt;li&gt;Comprehensive.  There are many, &lt;em&gt;many&lt;/em&gt;, areas in CV.  Covering them all is beyond the scope of
this guide.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What this is&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;An illustration of what is possible.  It's not always obvious a task can be done, let
alone knowing what magic words to Google.  I want to provide an overview of what is out there.&lt;/li&gt;
&lt;li&gt;Translation.  Often you know &lt;em&gt;what&lt;/em&gt; you want, but no way to know that it's called, say,
histogram backprojection.  This should help bridge that gap.&lt;/li&gt;
&lt;li&gt;A living document.  I'll be adding entries, updating, and adding examples.  If you have
suggestions for more, &lt;a href="/about"&gt;let me know!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;I want to...&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;You should use...&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detect lines in an image&lt;/td&gt;&lt;td&gt;&lt;a href="detecting-lines-in-an-image-with-hough-transforms"&gt;Hough Transform&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detect circles in an image&lt;/td&gt;&lt;td&gt;&lt;a href="detecting-circles-in-an-image-with-hough-transforms"&gt;Circular Hough Transform&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Find images similar to the one I have&lt;/td&gt;&lt;td&gt;phash, dhash&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Find images containing a particular object&lt;/td&gt;&lt;td&gt;Feature detector - orb, sift, surf, kaze/akaze&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Find images containing any particular object&lt;/td&gt;&lt;td&gt;Haar cascade, convolutional neural net&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Track an object in a video&lt;/td&gt;&lt;td&gt;Meanshift, camshift&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detect faces in an image&lt;/td&gt;&lt;td&gt;Haar cascade&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detect people in an image&lt;/td&gt;&lt;td&gt;Histogram of Oriented Gradients (HOG) people detector&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Remove defects (like passers-by) from an image&lt;/td&gt;&lt;td&gt;Median filter&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detect motion in an image&lt;/td&gt;&lt;td&gt;Background subtraction + frame differences&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Read words or numbers in an image&lt;/td&gt;&lt;td&gt;Optical character recognition (OCR)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Classify what an image contains (e.g. horse, Golden Gate Bridge,
human, etc)&lt;/td&gt;&lt;td&gt;Deep learning, especially Convolutional Neural Nets&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</content><category term="Programming"></category><category term="programming"></category><category term="computer vision"></category></entry><entry><title>How to generate Django secret keys</title><link href="https://foxrow.com/generating-django-secret-keys" rel="alternate"></link><published>2016-07-05T12:00:00-05:00</published><updated>2016-07-05T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-07-05:/generating-django-secret-keys</id><summary type="html">&lt;p&gt;You accidentally committed your secret key to version control, or the server got compromised,
or you lost control of the secret key for some reason. Using an online service to generate one is
a bad idea - nobody else should ever have your secret key. How do you generate a new …&lt;/p&gt;</summary><content type="html">&lt;p&gt;You accidentally committed your secret key to version control, or the server got compromised,
or you lost control of the secret key for some reason. Using an online service to generate one is
a bad idea - nobody else should ever have your secret key. How do you generate a new secret key?
The otherwise-excellent Django docs are silent on this. &lt;a href="https://github.com/django/django/blob/08f360355a0f47b35df355aae6ba88957b0e3a8d/django/core/management/utils.py#L76"&gt;Here's how Django generates one when you
run startproject&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemRandom&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&amp;amp;*(-_=+)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Generate a new one and you're good to go.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="web"></category><category term="howto"></category></entry><entry><title>retread.py - detect duplicate frames in Video, TV, or Film</title><link href="https://foxrow.com/duplicate-video-frame-detection-retread.py" rel="alternate"></link><published>2016-06-05T12:00:00-05:00</published><updated>2016-06-05T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-06-05:/duplicate-video-frame-detection-retread.py</id><summary type="html">&lt;p&gt;&lt;em&gt;The Force Awakens&lt;/em&gt; has a lot of shots that are callbacks to &lt;em&gt;A New Hope&lt;/em&gt; - they evoke very particular scenes and
shots, if not outright identical.  That got me thinking about recycling shots in films.  How could that be quantified?&lt;/p&gt;
&lt;p&gt;A common task in image processing is finding sets of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;The Force Awakens&lt;/em&gt; has a lot of shots that are callbacks to &lt;em&gt;A New Hope&lt;/em&gt; - they evoke very particular scenes and
shots, if not outright identical.  That got me thinking about recycling shots in films.  How could that be quantified?&lt;/p&gt;
&lt;p&gt;A common task in image processing is finding sets of similar images.  Often you have a picture, and you want to
search for identical or near-identical copies from some large set.  A few ways to do this are described
&lt;a href="http://www.hackerfactor.com/blog/?/archives/529-Kind-of-Like-That.html"&gt;here&lt;/a&gt; - pHash and dHash in particular are
quite effective.&lt;/p&gt;
&lt;p&gt;We can use this to our advantage.  Given a video clip, we should be able to go frame-by-frame and hash each image.
We can then count the number of frames that hash to the same value, telling us how many occurred earlier.
I wrote up a python tool to do this: &lt;a href="https://github.com/ryanfox/retread"&gt;retread.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Retread measures how much any given frame is reused in a video clip.  It can be a TV show, online video, any video file
you can get your hands on.  It spits out nice JSON, so you can plot it nicely via &lt;a href="https://d3js.org/"&gt;d3.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, let's see what some movies look like.  First up is Mad Max: Fury Road, a 2015 action blockbuster with many
fast cuts and short shots overall.&lt;/p&gt;
&lt;p&gt;Often the most common frame ends up being black.  This is usually credits, maybe a handful of frames from the beginning
of the movie, and any fade-to-blacks in the middle.  This shows up as big, solid bars near the beginning and end.
The interesting stuff is in the middle.  Here we see Max doesn't have any monster repetitive sections:&lt;/p&gt;
&lt;p&gt;&lt;img alt="mad max duplicate frames" class="m-image" src="https://foxrow.com/assets/madmax.png" title="Mad Max duplicate frames"/&gt;&lt;/p&gt;
&lt;p&gt;On the other hand, &lt;em&gt;Memento&lt;/em&gt;, a film with a nonlinear story, has heavier and more spikes.  The film
is presented in sort of an outside-in fashion - the end comes first, then the beginning, then back to the end,
working toward the middle the whole time:&lt;/p&gt;
&lt;p&gt;&lt;img alt="memento duplicate frames" class="m-image" src="https://foxrow.com/assets/memento.png" title="Memento duplicate frames"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Inception&lt;/em&gt; somewhat famously wraps multiple stories within one another, with the narrative jumping between all 3 in
parallel.  Christopher Nolan directed both &lt;em&gt;Memento&lt;/em&gt; and &lt;em&gt;Inception&lt;/em&gt;.  He seems to be a fan of cutting across time
and space.  This results in a moderately busy graph:&lt;/p&gt;
&lt;p&gt;&lt;img alt="inception duplicate frames" class="m-image" src="https://foxrow.com/assets/inception.png" title="Inception duplicate frames"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Paprika&lt;/em&gt;, one of the inspirations for &lt;em&gt;Inception&lt;/em&gt;, has even more.  Flashbacks occur throughout the film, and it shows.
The chart is almost totally full:&lt;/p&gt;
&lt;p&gt;&lt;img alt="paprika duplicate frames" class="m-image" src="https://foxrow.com/assets/paprika.png" title="Paprika duplicate frames"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/ryanfox/retread"&gt;See retread here&lt;/a&gt; to analyze your own films, TV shows, or clips.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="computer vision"></category><category term="python"></category><category term="dataviz"></category></entry><entry><title>Online stroke watch</title><link href="https://foxrow.com/strokewatch" rel="alternate"></link><published>2016-05-15T12:00:00-05:00</published><updated>2016-05-15T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-05-15:/strokewatch</id><summary type="html">&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div&gt;
            Time
        &lt;/div&gt;
&lt;div id="time"&gt;
            00:00.0
        &lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div&gt;
            Rate
        &lt;/div&gt;
&lt;div id="rate"&gt;
            -
        &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div class="m-button m-primary m-left-s"&gt;
&lt;a href="#" onclick="startStop();"&gt;Start/Stop&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div class="m-button m-primary m-left-s"&gt;
&lt;a href="#" onclick="stroke();"&gt;Stroke&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;a class="m-button m-info" href=""&gt;Reset&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;How to use: Hit Start/Stop to begin or pause the timer.  At either the catch or the finish,
hit 'Stroke'.  You don't need the stopwatch running to calcuate rate. No downloads or app required!&lt;/i&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;If this was helpful to you …&lt;/p&gt;</summary><content type="html">&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div&gt;
            Time
        &lt;/div&gt;
&lt;div id="time"&gt;
            00:00.0
        &lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div&gt;
            Rate
        &lt;/div&gt;
&lt;div id="rate"&gt;
            -
        &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div class="m-button m-primary m-left-s"&gt;
&lt;a href="#" onclick="startStop();"&gt;Start/Stop&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="m-col-s-6"&gt;
&lt;div class="m-button m-primary m-left-s"&gt;
&lt;a href="#" onclick="stroke();"&gt;Stroke&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="m-row"&gt;
&lt;div class="m-col-s-6"&gt;
&lt;a class="m-button m-info" href=""&gt;Reset&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;How to use: Hit Start/Stop to begin or pause the timer.  At either the catch or the finish,
hit 'Stroke'.  You don't need the stopwatch running to calcuate rate. No downloads or app required!&lt;/i&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;If this was helpful to you, check out &lt;a href="https://virtualcox.com"&gt;Virtual Cox&lt;/a&gt;.
It's an online service for rowing video analysis and athlete tracking.  &lt;a href="/introducing-virtual-cox"&gt;I wrote some more about it here.

&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"&gt;&lt;/script&gt;
&lt;script&gt;

var totalTime = 0;
var lastStartTime = 0;
var started = false;
var timeElement = $("#time");
var rateElement = $("#rate");
var timer;
var currentCatch = null;
var lastCatch = null;
var twoCatchesAgo = null;

function startStop() {
    if (!started) {
        started = true;
        lastStartTime = Date.now();
        timer = setInterval(update, 1);
    } else {
        started = false;
        clearInterval(timer);
    }
}

function stroke() {
    twoCatchesAgo = lastCatch;
    lastCatch = currentCatch;
    currentCatch = Date.now();

    var newRate = 1 / (currentCatch - lastCatch) * 60 * 1000;
    var oldRate = 1 / (lastCatch - twoCatchesAgo) * 60 * 1000;
    if (oldRate !== Infinity &amp;&amp; Math.round(oldRate) !== 0) {
        var avg = (newRate + oldRate) / 2;
        rateElement.text(formatRate(avg));
    } else {
        rateElement.text(formatRate(newRate));
    }
}

function update() {
    totalTime = Date.now() - lastStartTime;
    timeElement.text(formatTime(totalTime));
}

function formatRate(rate) {
    var whole = Math.round(rate);
    var fraction = (Math.round(rate * 2) / 2) % 1;

    if (fraction !== 0) {
        return whole + " 1/2";
    }

    return whole
}

function formatTime(t) {
    var minutes = Math.floor(t / 60 / 1000);
    var seconds = Math.floor(t / 1000) % 60;
    var millis = t % 1000;

    return pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(millis, 3);
}

function pad(num, len) {
    var newString = "0000" + num;
    return newString.substr(newString.length - len);
}
&lt;/script&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Rowing"></category><category term="rowing"></category><category term="tool"></category></entry><entry><title>Introducing Virtual Cox</title><link href="https://foxrow.com/introducing-virtual-cox" rel="alternate"></link><published>2016-04-15T12:00:00-05:00</published><updated>2016-04-15T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-04-15:/introducing-virtual-cox</id><summary type="html">&lt;p&gt;&lt;em&gt;Update: I'm changing the name of Virtual Cox to Fast Ergs.  It now lives at &lt;a href="https://fastergs.com"&gt;fastergs.com&lt;/a&gt;.
I'm using the name Virtual Cox for another project, coming spring 2017.  You can subscribe for updates
and to be notified when we launch &lt;a href="https://virtualcox.com"&gt;here.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I'm pleased to announce the launch of a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Update: I'm changing the name of Virtual Cox to Fast Ergs.  It now lives at &lt;a href="https://fastergs.com"&gt;fastergs.com&lt;/a&gt;.
I'm using the name Virtual Cox for another project, coming spring 2017.  You can subscribe for updates
and to be notified when we launch &lt;a href="https://virtualcox.com"&gt;here.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I'm pleased to announce the launch of a project I have been working on for a while.  It's called Virtual Cox, and it's
an online tool to help rowers and rowing coaches row faster and more efficiently. The homepage can be found at
&lt;a href="https://virtualcox.com"&gt;virtualcox.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a sentence: Virtual Cox lets rowers and rowing coaches analyze video to go faster on the water.&lt;/p&gt;
&lt;p&gt;Elevator pitch: Virtual Cox lets rowers and rowing coaches analyze video to go faster on the water. Video can be
captured via any device, and data analysis tools enable closer critique of form, technique, and consistency.  Sharing
tools let coaches provide feedback to athletes for later review.&lt;/p&gt;
&lt;p&gt;Even a short amount of footage can make rough edges glaringly obvious, even things the rowers don't realize they're
doing themselves! Despite being such a rich source of feedback to both athletes and coaches, there are often barriers
to using it effectively (or at all). Like photography, the best camera is the one you have with you. My goal is to give
crews the tools to take and analyze more video.&lt;/p&gt;
&lt;p&gt;Video can be uploaded from any device, so you don't have to bring along a video camera or mount any equipment on the
boat. (Though you certainly can if you want!) Simply using your phone in the launch, GoPro up close, or video camera
can take all the footage you need. Erging video works too, for those winter training sessions.&lt;/p&gt;
&lt;p&gt;Once you've uploaded your video, tools allow you to slow down playback to closely review technique. For more advanced
measurements, coaches and rowers can measure ratio, timing, and other metrics.&lt;/p&gt;
&lt;p&gt;Coaches can save and easily share video and data with other coaches, coxswains, or athletes via a sharing link.&lt;/p&gt;
&lt;p&gt;I'm always looking to hear from you! Send me an email at ryan@virtualcox.com, I read all of them, and try to get back
to everyone. If it's been a few days and you haven't heard back from me, send me a quick ping. It's more likely it
slipped off my radar than I took a personal disliking to your email.  If you're a rower or coach looking to get faster
on the water, sign up at &lt;a href="https://virtualcox.com/pricing"&gt;Virtual Cox&lt;/a&gt;, and follow
&lt;a href="https://twitter.com/virtualcox"&gt;@virtualcox&lt;/a&gt;.&lt;/p&gt;</content><category term="Rowing"></category><category term="rowing"></category><category term="web"></category></entry><entry><title>Brainfunction - a new programming language</title><link href="https://foxrow.com/brainfunction-a-new-programming-language" rel="alternate"></link><published>2016-02-26T12:00:00-06:00</published><updated>2016-02-26T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2016-02-26:/brainfunction-a-new-programming-language</id><summary type="html">&lt;p&gt;Some time ago, I wrote an interpreter for the language &lt;a href="https://github.com/ryanfox/brainfuck"&gt;brainfuck.&lt;/a&gt;
It's nice and all, but I thought, "you know what would really make this great?  More inscrutable
instructions!"  How to extend it, though?  It already has such a great feature set. You may
remember it from hits such as …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some time ago, I wrote an interpreter for the language &lt;a href="https://github.com/ryanfox/brainfuck"&gt;brainfuck.&lt;/a&gt;
It's nice and all, but I thought, "you know what would really make this great?  More inscrutable
instructions!"  How to extend it, though?  It already has such a great feature set. You may
remember it from hits such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hilarious-looking source code!&lt;/li&gt;
&lt;li&gt;Turing completeness!&lt;/li&gt;
&lt;li&gt;It somehow manages ultra-verbosity in a language with only 8 valid characters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite that saliva-inducing list, one item you can never get enough of in computing is recursion.  So let's add
functions!  Brainfuck already has an array of data cells, let's expand that to an array of functions to call as well.&lt;/p&gt;
&lt;h2&gt;Official brainfunction v0.1 spec (subject to revision):&lt;/h2&gt;
&lt;p&gt;The official brainfunction repository can be found at
&lt;a href="https://github.com/ryanfox/brainfunction"&gt;https://github.com/ryanfox/brainfunction&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The brain&lt;/h3&gt;
&lt;p&gt;A brainfunction program runs on the brain, which is an extended version of the brainfuck VM.  It supports the eight
standard brainfuck symbols, instruction pointer, data tape, and data pointer.  In addition to this, it has an array of
functions.  When a brainfunction program is parsed, each line of text in the source file becomes a function.  These
constitute the function array, starting with the first line of text in position 0.  A function is exactly one line
of source code.  This implies one cannot break up logical units multiple lines as is done in brainfuck.&lt;/p&gt;
&lt;h3&gt;Functions&lt;/h3&gt;
&lt;p&gt;Each function has a function pointer, initialized to 0 (the first line in the file).  The function pointer is moved by
the symbols &lt;code&gt;v&lt;/code&gt; and &lt;code&gt;^&lt;/code&gt;, meaning "move the function pointer down" and "move the function pointer up", respectively.
Each function also has a local data pointer, data tape, and instruction pointer.&lt;/p&gt;
&lt;h4&gt;Calling&lt;/h4&gt;
&lt;p&gt;To call another function, the &lt;code&gt;:&lt;/code&gt; symbol is used.  When this occurs, the function pointed to by the current function
pointer is called.  The value in the caller's current data cell is passed to the callee.  This argument is placed in
cell 0 of the callee's data tape.&lt;/p&gt;
&lt;h4&gt;Returning&lt;/h4&gt;
&lt;p&gt;A function returns when the symbol &lt;code&gt;;&lt;/code&gt; is encountered or the function runs out of instructions.  When a function
returns, the value in the current data cell is returned.  The return value is placed in the caller's current data cell.&lt;/p&gt;
&lt;h3&gt;Evaluation&lt;/h3&gt;
&lt;p&gt;The brain begins execution in the zeroth function in the function array, and continues until an error is encountered
or the function runs out of symbols.  All symbols except the 12 specified are ignored.  When a function calls
another, execution blocks in the caller until the callee returns.&lt;/p&gt;
&lt;p&gt;The reference implementation is an interpreter written in python,
&lt;a href="https://github.com/ryanfox/brainfunction"&gt;which can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;p&gt;Example hello world:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;v:vv:^^:vv:^:v:^:v:^:vv:&lt;/span&gt;
&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;---&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;+++++++&lt;/span&gt;&lt;span class="nt"&gt;..&lt;/span&gt;&lt;span class="nb"&gt;+++&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;                    print hello&lt;/span&gt;
&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;--------&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;+++&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;------&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;--------&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;         print world&lt;/span&gt;
&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;                                                 print space character&lt;/span&gt;
&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;                                                print !&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;
When executed, this will print&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;hello hello world world world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;
Example factorial calculator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="c"&gt;v&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------&lt;/span&gt;&lt;span class="c"&gt;:v:v:^^&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;v:&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;
&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;++++++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;++++++&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;
&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;++++++++++&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;
When executed, this will prompt the user for input (limited to a single character), calculate the factorial of that
number, and print the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bf &amp;gt; 5
120
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Roadmap&lt;/h2&gt;
&lt;p&gt;Future developments may include real error messages, and perhaps a debugger, to make it easy to inspect the state of
the brain.  Maybe even zanier symbols!&lt;/p&gt;
&lt;p&gt;That's it!  Look on my works, ye mighty, and despair!&lt;/p&gt;
&lt;p&gt;&lt;img alt="wtf?" class="m-image" src="https://i.imgur.com/x6AnRkL.png?fb"/&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="esolang"></category></entry><entry><title>Needs more lens flare</title><link href="https://foxrow.com/needs-more-lens-flare" rel="alternate"></link><published>2015-12-02T12:00:00-06:00</published><updated>2015-12-02T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-12-02:/needs-more-lens-flare</id><summary type="html">&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Lens_flare"&gt;Lens flare&lt;/a&gt; is an effect created
by light reflecting internally in the lens of a camera.  Whether it's good or
bad is up to how it's used.  Some filmmakers are notorious for this (looking at
you, J.J. Abrams).&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="http://needsmorejpeg.com/"&gt;needsmorejpeg&lt;/a&gt;, I created an app to
take user-uploaded photos …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Lens_flare"&gt;Lens flare&lt;/a&gt; is an effect created
by light reflecting internally in the lens of a camera.  Whether it's good or
bad is up to how it's used.  Some filmmakers are notorious for this (looking at
you, J.J. Abrams).&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="http://needsmorejpeg.com/"&gt;needsmorejpeg&lt;/a&gt;, I created an app to
take user-uploaded photos and add lens flare to them.  Wish your photo had more
lens flare?  No worries, get it automatically added for you.  Just upload your
photo and we'll take care of the rest.
The site is &lt;a href="http://needsmorelensflare.com"&gt;needsmorelensflare.com&lt;/a&gt;.
Happy flaring!&lt;/p&gt;
&lt;p&gt;&lt;a href="//needsmorelensflare.com/lFOj8oz.jpg"&gt;&lt;img alt="owl with lens flare" class="m-image" src="//needsmorelensflare.com/lFOj8oz.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="web"></category></entry><entry><title>Get Play-By-Play NBA SportVU Data</title><link href="https://foxrow.com/get-nba-sportvu-data" rel="alternate"></link><published>2015-10-30T12:00:00-05:00</published><updated>2015-10-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-10-30:/get-nba-sportvu-data</id><summary type="html">&lt;p&gt;&lt;em&gt;Update 06/14/16: It appears &lt;a href="http://stats.nba.com"&gt;stats.nba.com&lt;/a&gt; has taken down the SportVU logs.  However,
neilmj has posted logs from the 2015-16 season &lt;a href="https://github.com/neilmj/BasketballData/tree/master/2016.NBA.Raw.SportVU.Game.Logs"&gt;on github&lt;/a&gt;,
for games through January 23.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.stats.com/pro-team-solutions/"&gt;SportVU&lt;/a&gt; is a video system made by
STATS Inc, used by  teams to track movement of players and the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Update 06/14/16: It appears &lt;a href="http://stats.nba.com"&gt;stats.nba.com&lt;/a&gt; has taken down the SportVU logs.  However,
neilmj has posted logs from the 2015-16 season &lt;a href="https://github.com/neilmj/BasketballData/tree/master/2016.NBA.Raw.SportVU.Game.Logs"&gt;on github&lt;/a&gt;,
for games through January 23.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.stats.com/pro-team-solutions/"&gt;SportVU&lt;/a&gt; is a video system made by
STATS Inc, used by  teams to track movement of players and the ball on the
court.  I looked, but couldn't find anywhere describing how to get play-by-play
data, so did some investgating myself.  The great
&lt;a href="http://www.stats.nba.com"&gt;stats.nba.com&lt;/a&gt; has every game
&lt;a href="http://stats.nba.com/league/team/#!/gamelogs/"&gt;here.&lt;/a&gt;  Clicking on any of the
linked values in the table brings up a popup with an option 'Movement':&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/sportvu.png"&gt;&lt;img alt="NBA play-by-play SportVU data" class="m-image" src="/assets/sportvu.png"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Inspecting the network request that goes out when clicking on Movement shows
us that fires off an AJAX request to &lt;code&gt;http://stats.nba.com/stats/locations_getmoments/?eventid=2&amp;amp;gameid=0021500003&lt;/code&gt;
We can infer that &lt;code&gt;eventid=2&lt;/code&gt; refers to field goal attempts, and &lt;code&gt;gameid=0021500003&lt;/code&gt;
likely refers to the third game in 2015.  Indeed, changing the &lt;code&gt;gameid&lt;/code&gt; to &lt;code&gt;0021400003&lt;/code&gt;
returns data for a game dated October 28, 2014, between the Rockets and Lakers.&lt;/p&gt;
&lt;p&gt;Once you get the eventid of the type you want, you could theoretically
enumerate all the games in a season this way, or across multiple seasons.  The
response comes in JSON format, and
&lt;a href="http://savvastjortjoglou.com/nba-play-by-play-movements.html"&gt;Savvas Tjortjoglou nicely detailed that in this post.&lt;/a&gt;
Happy number crunching!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="basketball"></category><category term="API"></category><category term="howto"></category></entry><entry><title>Unshorten URLs with unshorten.py</title><link href="https://foxrow.com/unshorten-urls-with-unshorten.py" rel="alternate"></link><published>2015-09-11T12:00:00-05:00</published><updated>2015-09-11T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-09-11:/unshorten-urls-with-unshorten.py</id><summary type="html">&lt;p&gt;Obfuscated urls stink.  Being able to hover over a link and see where it goes is
a tremendous feature of any modern browser.  Use unshorten on your command line
to see where they go.  &lt;a href="https://github.com/ryanfox/unshorten.py"&gt;The project is at github&lt;/a&gt;.
Put unshorten.py somewhere on your path and make it executable …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Obfuscated urls stink.  Being able to hover over a link and see where it goes is
a tremendous feature of any modern browser.  Use unshorten on your command line
to see where they go.  &lt;a href="https://github.com/ryanfox/unshorten.py"&gt;The project is at github&lt;/a&gt;.
Put unshorten.py somewhere on your path and make it executable.  You don't even
have to leave the .py on the end!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;git@github.com:ryanfox/unshorten.py.git
$&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;u+x&lt;span class="w"&gt; &lt;/span&gt;unshorten.py/unshorten.py
$&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;unshorten.py/unshorten.py&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/unshorten
$&lt;span class="w"&gt; &lt;/span&gt;unshorten&lt;span class="w"&gt; &lt;/span&gt;https://goo.gl/hnpGjq
https://github.com/ryanfox/unshorten.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hopefully it's useful.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="web"></category><category term="tool"></category></entry><entry><title>Multi-line statements with PLY</title><link href="https://foxrow.com/multi-line-statements-with-ply" rel="alternate"></link><published>2015-09-04T12:00:00-05:00</published><updated>2015-09-04T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-09-04:/multi-line-statements-with-ply</id><summary type="html">&lt;p&gt;I've been experimenting with &lt;a href="http://www.dabeaz.com/ply"&gt;PLY&lt;/a&gt; and was encountering an obscure
error that was surprisingly Google-resistant.  I've been building up a language
and a REPL to interpret commands.  When trying to parse a statement spanning multiple lines,
I would get an error &lt;code&gt;{TypeError}Can't convert 'type' object to str implicitly&lt;/code&gt;.
Debugging …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been experimenting with &lt;a href="http://www.dabeaz.com/ply"&gt;PLY&lt;/a&gt; and was encountering an obscure
error that was surprisingly Google-resistant.  I've been building up a language
and a REPL to interpret commands.  When trying to parse a statement spanning multiple lines,
I would get an error &lt;code&gt;{TypeError}Can't convert 'type' object to str implicitly&lt;/code&gt;.
Debugging revealed the parser was running into an EOF.  The usual sources online didn't
appear to have any information about it, either.&lt;/p&gt;
&lt;p&gt;It turns out the problem wasn't PLY, but Python's &lt;code&gt;input()&lt;/code&gt;.  It only reads one line at
a time.  So even if you paste in&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stuff'&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;the method will only pass along the string &lt;code&gt;'def foo(bar):&lt;/code&gt;.  As a workaround,
try something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;evaluate&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;function&lt;/span&gt;

&lt;span class="nv"&gt;parser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;yacc&lt;/span&gt;.&lt;span class="nv"&gt;yacc&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reap&amp;gt; '&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;:
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;evaluate&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;parser&lt;/span&gt;.&lt;span class="nv"&gt;parse&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="ss"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There's your read-eval-print loop, spanning multiple lines of user input.  After your block is
finished, hit enter twice to finish the block, and you should be good to go.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="language"></category><category term="howto"></category></entry><entry><title>US states and words with no letters in common</title><link href="https://foxrow.com/states-and-words-with-no-letters-in-common" rel="alternate"></link><published>2015-08-14T12:00:00-05:00</published><updated>2015-08-14T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-08-14:/states-and-words-with-no-letters-in-common</id><summary type="html">&lt;p&gt;"Ohio is the only state not to share a letter with the word mackerel" my buddy tells me.&lt;/p&gt;
&lt;p&gt;Of course, I need to find out what other words don't share any letters with
states. So I start hunting for a large English dictionary.  It turns out the
Brown corpus, included …&lt;/p&gt;</summary><content type="html">&lt;p&gt;"Ohio is the only state not to share a letter with the word mackerel" my buddy tells me.&lt;/p&gt;
&lt;p&gt;Of course, I need to find out what other words don't share any letters with
states. So I start hunting for a large English dictionary.  It turns out the
Brown corpus, included in NLTK has ~1 million words.  So I whip up a python
script to figure out what state-word combos pass.
&lt;a href="https://github.com/ryanfox/states-words"&gt;The code lives here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Spoiler alert: apparently there's 38969.  At least.  That's just words from the
particular corpus I used.  Ohio is the most popular (least popular?) state,
clocking in at 1085.  Next closest is Mississippi, at 678.  Third is Alabama
with 599.  Ohio doesn't have any A's or E's, so that helps.&lt;/p&gt;</content><category term="misc"></category><category term="programming"></category><category term="data analysis"></category><category term="word"></category></entry><entry><title>NBA winning percentages</title><link href="https://foxrow.com/nba-winning-percentages" rel="alternate"></link><published>2015-04-08T12:00:00-05:00</published><updated>2015-04-08T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2015-04-08:/nba-winning-percentages</id><summary type="html">&lt;p&gt;During a Milwaukee Bucks broadcast on February 11, John McGlocklin mentioned something to the effect of "the first
team to get to 100 points usually wins."  Intuitively, this makes some sense.  If you're the first team to a given
score, you have the lead at that point.  100 points is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;During a Milwaukee Bucks broadcast on February 11, John McGlocklin mentioned something to the effect of "the first
team to get to 100 points usually wins."  Intuitively, this makes some sense.  If you're the first team to a given
score, you have the lead at that point.  100 points is usually a late-game score, and having the lead near the end of
the game makes it more likely you are going to win.&lt;/p&gt;
&lt;p&gt;I was curious as to whether the data backed this up. I collected play-by-play scoring data for the 2013-2014 season,
and ran some analysis on it.  First, the number of times the first team to X points won or lost:&lt;/p&gt;
&lt;p&gt;&lt;img alt="point totals" class="m-image" src="https://foxrow.com/assets/nba_point_total.png" title="NBA point totals"/&gt;&lt;/p&gt;
&lt;p&gt;That's a little hard to read in the higher-scoring (interesting) portion of the graph.  Here's the winning percentages
plotted as a function of score:&lt;/p&gt;
&lt;p&gt;&lt;img alt="nba point total ratios" class="m-image" src="https://foxrow.com/assets/nba_point_ratio.png" title="NBA point total ratios"/&gt;&lt;/p&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There were 1319 games played, including playoffs.&lt;/li&gt;
&lt;li&gt;Teams win at a .932 clip for scoring 100 first.  Not a bad rule of thumb!&lt;/li&gt;
&lt;li&gt;On the other hand: at no point having the lead gives you a sub-.500 winning percentage.  So you could also say "the first team to 1 point usually wins".&lt;/li&gt;
&lt;li&gt;The worst is 1 or 2 points, yielding wins at .547.  Even getting to 3 first improves your chances a bit, an extra percent and a half, all the way up to .562.&lt;/li&gt;
&lt;li&gt;The highest point total 145.  The Rockets beat Lakers on the road on April 8.  The game didn't even go to overtime!&lt;/li&gt;
&lt;li&gt;There is a curious bump in the ratio at 121.  This is an artifact of not many teams scoring that many and still losing - only 3 to 5 losses in that range.  Makes for a noisy signal.&lt;/li&gt;
&lt;li&gt;No team scoring 128 or more lost.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Bucks were first to 100 that night, and beat the Kings 111-103 :)&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Thanks to &lt;a href="http://www.basketball-reference.com"&gt;basketball-reference.com&lt;/a&gt; for the play-by-play data.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="dataviz"></category><category term="basketball"></category><category term="stats"></category><category term="data analysis"></category></entry><entry><title>Installing OpenCV in a virtualenv on Ubuntu and OSX</title><link href="https://foxrow.com/installing-opencv-in-a-virtualenv" rel="alternate"></link><published>2014-10-14T12:00:00-05:00</published><updated>2014-10-14T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2014-10-14:/installing-opencv-in-a-virtualenv</id><summary type="html">&lt;p&gt;&lt;code&gt;$ pip install opencv&lt;/code&gt; never seems to work quite right.  If you get the message&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Could&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;downloads&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;satisfy&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;requirement&lt;span class="w"&gt; &lt;/span&gt;opencv
Cleaning&lt;span class="w"&gt; &lt;/span&gt;up...
No&lt;span class="w"&gt; &lt;/span&gt;distributions&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opencv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are a handful of answers strewn across the internet about installing
OpenCV's Python bindings, but none …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;$ pip install opencv&lt;/code&gt; never seems to work quite right.  If you get the message&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Could&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;downloads&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;satisfy&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;requirement&lt;span class="w"&gt; &lt;/span&gt;opencv
Cleaning&lt;span class="w"&gt; &lt;/span&gt;up...
No&lt;span class="w"&gt; &lt;/span&gt;distributions&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opencv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are a handful of answers strewn across the internet about installing
OpenCV's Python bindings, but none of them seem to apply to installing them
in a virtualenv in Linux. In the interest of collecting all that information
in one place, here's what I did to get it running. I'm using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu 14.04 64 bit&lt;/li&gt;
&lt;li&gt;Python 2.7.6&lt;/li&gt;
&lt;li&gt;OpenCV 2.4.9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Update: You may find luck using &lt;a href="https://pypi.python.org/pypi/opencv-python"&gt;opencv-python,&lt;/a&gt; an unofficial package of the python bindings for OpenCV.
If you only need to use the Python interface, you can just &lt;code&gt;pip install opencv-python&lt;/code&gt; and should be good to go.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This will install OpenCV, the Python bindings, and Numpy system-wide, but afterward you will be able to use them
inside a virtualenv. This assumes you have pip, virtualenv, and virtualenvwrapper installed and properly configured.
If you aren't familiar with these, Googling yields many resources, for example
&lt;a href="http://simononsoftware.com/virtualenv-tutorial-part-2/"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, install OpenCV's dependencies, per &lt;a href="http://docs.opencv.org/doc/tutorials/introduction/linux_install/linux_install.html#linux-installation"&gt;the installation instructions.&lt;/a&gt; (Some of these were pre-installed on my system)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;build-essential&lt;span class="w"&gt; &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;libgtk2.0-dev&lt;span class="w"&gt; &lt;/span&gt;pkg-config&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
python-dev&lt;span class="w"&gt; &lt;/span&gt;libavcodec-dev&lt;span class="w"&gt; &lt;/span&gt;libavformat-dev&lt;span class="w"&gt; &lt;/span&gt;libswscale-dev&lt;span class="o"&gt;{&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;endhighlight&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The next part had me tripped up for a little bit. OpenCV doesn't play particularly well with virtualenvs, so numpy
&lt;em&gt;needs to be installed on the system Python:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;numpy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After that, continue to build OpenCV per the instructions. Download the source (I'm using version 2.4.9.0,
&lt;a href="http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.9/"&gt;from here&lt;/a&gt; and unzip it in the directory of
your choice.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;unzip&lt;span class="w"&gt; &lt;/span&gt;opencv-2.4.9.zip
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opencv-2.4.9
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;build
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Configure the make files using cmake. There is a flag required for the Python bindings that I couldn't find in the
official documentation, only in StackOverflow questions: BUILD_NEW_PYTHON_SUPPORT. Also note the two trailing
periods.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CMAKE_BUILD_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;RELEASE&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CMAKE_INSTALL_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BUILD_NEW_PYTHON_SUPPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON&lt;span class="w"&gt; &lt;/span&gt;..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will output a lot of text, but if you scroll up you should find a section referring to Python. It will refer
to the system Python binary. This is fine. We will set up our virtualenv later.&lt;/p&gt;
&lt;p&gt;After the build is configured, time to make the project. This will take a few minutes to run.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After the build completes, you need to set up your virutalenv if you haven't already. Numpy also needs to be installed
in the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkvirutalenv&lt;span class="w"&gt; &lt;/span&gt;opencv
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;opencv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;numpy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that our virtualenv is ready to go, we just need to copy the OpenCV binary into the virtualenv's site-packages
directory.  It should be somewhere under the location you installed it, mine was in
&lt;code&gt;/usr/local/lib/python2.7/dist-packages/cv2.so&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;opencv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;lib
&lt;span class="o"&gt;(&lt;/span&gt;opencv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/python2.7/dist-packages/cv2.so&lt;span class="w"&gt; &lt;/span&gt;~/.virtualenvs/opencv/lib/python2.7/site-packages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can now delete the &lt;code&gt;opencv-2.4.9&lt;/code&gt; directory if you want.  The reason we don't want the &lt;code&gt;cv2.so&lt;/code&gt; that got built
there is the libraries it's linked to.  If you run &lt;code&gt;$ ldd cv2.so&lt;/code&gt; you'll get something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;libpython2.7.so.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007fe78eca2000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_core.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007fe78e7f9000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_flann.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007fe78e585000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_imgproc.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007fe78e096000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See that first one with an absolute path &lt;code&gt;(/usr/lib/...)&lt;/code&gt;?  The other ones are linked to copies in the build directory
so if you delete them, cv2.so won't have its full feature set, even though it may import correctly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ldd&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/python2.7/dist-packages/cv2.so
libpython2.7.so.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007f9f27e63000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_core.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/libopencv_core.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007f9f279bb000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_flann.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/libopencv_flann.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007f9f27746000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_imgproc.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/libopencv_imgproc.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007f9f27257000&lt;span class="o"&gt;)&lt;/span&gt;
libopencv_highgui.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/lib/libopencv_highgui.so.2.4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0x00007f9f26e1e000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ahh, much better.&lt;/p&gt;
&lt;p&gt;Everything should be good to go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opencv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endhighlight&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you get &lt;code&gt;ImportError: no module named cv2&lt;/code&gt; double check you had the BUILD_NEW_PYTHON_SUPPORT flag set, and that
numpy is installed for the system Python. If you get the error message
&lt;code&gt;ImportError: numpy.core.multiarray failed to import&lt;/code&gt; you need to install numpy in your virtualenv. From inside the
virtualenv, &lt;code&gt;(opencv) $ pip install numpy&lt;/code&gt; should fix that.  I believe that should do the trick.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;Update June 14, 2017:&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;OSX troubleshooting&lt;/h2&gt;
&lt;p&gt;These tips from a reader may help you troubleshoot errors on OSX Sierra.&lt;/p&gt;
&lt;p&gt;If you get &lt;a href="https://stackoverflow.com/questions/40262928/error-compiling-opencv-fatal-error-stdlib-h-no-such-file-or-directory"&gt;Error compiling OpenCV, fatal error: stdlib.h&lt;/a&gt;,
try adding the cmake flag &lt;code&gt;-DENABLE_PRECOMPILED_HEADERS=OFF&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/11561261/how-to-compile-without-warnings-being-treated-as-errors"&gt;cc1plus: some warnings being treated as errors&lt;/a&gt;
&lt;code&gt;-Wno-error&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/opencv/opencv/issues/6195"&gt;error: "if (&amp;amp;annotate_img!=NULL)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This issue has been fixed as of OpenCV version 2.4.13, try upgrading to the
latest version of OpenCV 2 or 3.&lt;/p&gt;
&lt;p&gt;Good luck with your computer vision!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="computer-vision"></category><category term="virtualenv"></category><category term="howto"></category></entry><entry><title>New host</title><link href="https://foxrow.com/new-host" rel="alternate"></link><published>2014-07-07T12:00:00-05:00</published><updated>2014-07-07T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2014-07-07:/new-host</id><content type="html">&lt;p&gt;Fox row was down for a while due to some issues with the old web host.  It's on a new and improved hosting service
now, so there shouldn't be any more problems.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Sweeping the 1-seeds</title><link href="https://foxrow.com/sweeping-the-1-seeds" rel="alternate"></link><published>2014-03-18T12:00:00-05:00</published><updated>2014-03-18T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2014-03-18:/sweeping-the-1-seeds</id><summary type="html">&lt;p&gt;&lt;a href="http://www.cbssports.com/collegebasketball/ncaa-tournament/brackets/viewable_men"&gt;The Bracket&lt;/a&gt; is out, and once Virginia was announced as the final 1-seed, I realized Wisconsin may be in a unique position.  The Badgers beat both Florida and Virginia during the regular season, and have the possibility to beat both Arizona and Wichita State in the tournament.  If they do …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://www.cbssports.com/collegebasketball/ncaa-tournament/brackets/viewable_men"&gt;The Bracket&lt;/a&gt; is out, and once Virginia was announced as the final 1-seed, I realized Wisconsin may be in a unique position.  The Badgers beat both Florida and Virginia during the regular season, and have the possibility to beat both Arizona and Wichita State in the tournament.  If they do so, they will have beaten all four 1-seeds this season.  I had to find out if anyone ever has.  There have been 116 1-seeds since the field expanded to 64 in 1985.  Naturally, all the 1-seeds are excluded by virtue of being unable to lose to themselves.&lt;/p&gt;
&lt;p&gt;It turns out there have only been 2 teams that have 4 wins against 1-seeds in the same year, and they're both Arizona
squads.  In 1997, the Wildcats beat North Carolina twice, Kansas, and Kentucky en route to the national championship.
They never played Minnesota, the other 1-seed.  In an even crazier 2001 season, they came within a game of doing it:
they split 2 regular-season games against Illinois before beating them in the tournament, split regular season-games
against Stanford, and beat Michigan State in the Final Four.  The last 1-seed was Duke, who they lost to in the
National Championship.  They played 7 games against the 1-seeds that year!&lt;/p&gt;
&lt;p&gt;There have been 7 teams with 3 wins against 1-seeds (what was it with the Wildcats those 4 years?):
1985 Illinois
1985 Georgetown
1986 Duke
1991 Duke
1992 Southern California
1992 Indiana
2000 Arizona&lt;/p&gt;
&lt;p&gt;The distribution falls off rapidly after that: there have been 64 teams with 2 wins against 1-seeds and 352 with 1 win.&lt;/p&gt;
&lt;p&gt;The all-time leaders contain no surprises.  If we assume that most of the 1-seeds come from historically "power"
conferences, being in the same conference provides more opportunity over the course of a season to play against them.
Going deep in the tournament doesn't hurt either.&lt;/p&gt;
&lt;table class="m-table"&gt;
&lt;tr&gt;
&lt;td&gt;Duke&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arizona&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North Carolina&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indiana&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kansas&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maryland&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;On the flip side, Wake Forest has the dubious honor of most losses to 1-seeds in a year.  In 2002 they also played 7
games against the eventual 1-seeds.  They went 0-7, losing to Cincinnati and Kansas once each, Maryland twice, and
Duke 3 times.  There have been 16 5-loss teams and 73 4-loss teams.  In terms of all-time futility, NC State takes the
cake.  Since 1985, they have a paltry .114 winning percentage against 1-seeds:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;table class="m-table"&gt;
&lt;tr&gt;
&lt;td&gt;NC State&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virginia&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Georgia Tech&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clemson&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Michigan&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/p&gt;
&lt;p&gt;So if the Badgers pull it off, they'll be the first in the 64+ team era to do so.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stats courtesy &lt;a href="http://www.sports-reference.com/cbb"&gt;Sports-Reference&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</content><category term="misc"></category><category term="data analysis"></category><category term="basketball"></category></entry><entry><title>Pypeline update</title><link href="https://foxrow.com/pypeline-update" rel="alternate"></link><published>2013-12-04T12:00:00-06:00</published><updated>2013-12-04T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-12-04:/pypeline-update</id><summary type="html">&lt;p&gt;I've pushed some changes to the pypeline repository, adding basic stacking functionality.  There isn't any
registration, it only takes the median of each channel (R, G, B) for each pixel.  It's currently way slow, but I
suspect there is substantial room for improvement there.  Wrangling NEF files has proven more …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've pushed some changes to the pypeline repository, adding basic stacking functionality.  There isn't any
registration, it only takes the median of each channel (R, G, B) for each pixel.  It's currently way slow, but I
suspect there is substantial room for improvement there.  Wrangling NEF files has proven more difficult than I
anticipated, so currently the state of the art in pypeline is JPGs.&lt;/p&gt;
&lt;p&gt;My camera is rated down to 32°F, and nightly lows have been around 0, so I'm scared to take it out into the elements.
On the plus side, the stacking works with regular images too!  Any particular pixel just needs to have the "right"
value for at least half the shots.&lt;/p&gt;
&lt;p&gt;Inputs:&lt;br/&gt;
&lt;a href="https://www.flickr.com/photos/41053808@N06/11201438903"&gt;&lt;img alt="dsc_0031 by Wisco crew, on Flickr" class="m-image" src="https://farm4.staticflickr.com/3685/11201438903_dcc1ac9648_m.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/41053808@N06/11201321034"&gt;&lt;img alt="dsc_0030 by Wisco crew, on Flickr" class="m-image" src="https://farm8.staticflickr.com/7347/11201321034_0d67d1e392_m.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/41053808@N06/11201442173"&gt;&lt;img alt="dsc_0029 by Wisco crew, on Flickr" class="m-image" src="https://farm6.staticflickr.com/5542/11201442173_2c08326977_m.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/41053808@N06/11201337696"&gt;&lt;img alt="dsc_0028 by Wisco crew, on Flickr" class="m-image" src="https://farm8.staticflickr.com/7367/11201337696_ccc4dd0fe5_m.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/41053808@N06/11201339206"&gt;&lt;img alt="dsc_0027 by Wisco crew, on Flickr" class="m-image" src="https://farm3.staticflickr.com/2840/11201339206_98fef7671b_m.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And the stacked result:  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/41053808@N06/11201437453"&gt;&lt;img alt="dsc_0027 by Wisco crew, on Flickr" class="m-image" src="http://farm8.staticflickr.com/7425/11201437453_19b19598a7.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is a little ghosting, but quite good considering, I'd say.  I am not sure how to get rid of that totally.
More pictures should quash the error, but at 5 I would have thought it would wipe out any traces of the marker.  Also,
a better algorithm should be able to push down the &amp;gt; 50% requirement to only a plurality.  Maybe with some sort of
clustering of values?  I'm also taking the mean of each channel independently, maybe a better way would be to use
luminance.  In any case, baby steps!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="language"></category><category term="photo"></category><category term="astro"></category></entry><entry><title>Visual cryptography</title><link href="https://foxrow.com/visual-cryptography" rel="alternate"></link><published>2013-11-19T12:00:00-06:00</published><updated>2013-11-19T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-11-19:/visual-cryptography</id><summary type="html">&lt;p&gt;Inspired by &lt;a href="http://www.datagenetics.com/blog/november32013/index.html"&gt;this post at DataGenetics,&lt;/a&gt;, I
implemented a quick-and-dirty script in python to test it out.  The first takes an input image and iterates over it
pixel by pixel, splitting it into two output images.  Ideally, the outputs are randomly assigned, so it is impossible
to recover the original …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Inspired by &lt;a href="http://www.datagenetics.com/blog/november32013/index.html"&gt;this post at DataGenetics,&lt;/a&gt;, I
implemented a quick-and-dirty script in python to test it out.  The first takes an input image and iterates over it
pixel by pixel, splitting it into two output images.  Ideally, the outputs are randomly assigned, so it is impossible
to recover the original without both outputs.  The outputs can be combined to recover the original.  Here's an example
of it in action:&lt;/p&gt;
&lt;p&gt;Original image:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="original" class="m-image" src="https://farm8.staticflickr.com/7394/10939623264_f3ae281941.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Intermediate images (hopefully look like static):&lt;/p&gt;
&lt;p&gt;&lt;img alt="static fuzz" class="m-image" src="https://farm8.staticflickr.com/7452/10939476225_ff5e697615.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="static fuzz 2" class="m-image" src="https://farm6.staticflickr.com/5479/10939537316_a7c8a346e9.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Final output:&lt;/p&gt;
&lt;p&gt;&lt;img alt="revealed output" class="m-image" src="https://farm4.staticflickr.com/3759/10939475545_74d6bf36ef.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Not perfect, but it is definitely recognizable.  The idea can apparently be extended to 9x9 (and 16x16, and 25x25...
I presume) images, for a wider-shared secret.  In any case, this scheme should make it possible for any number of
people to share a secret, but none of them individually can recover it.  I uploaded the code
&lt;a href="https://github.com/ryanfox/visual-cryptography"&gt;here on github.&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="crypto"></category><category term="dataviz"></category></entry><entry><title>New PC</title><link href="https://foxrow.com/new-pc" rel="alternate"></link><published>2013-11-17T12:00:00-06:00</published><updated>2013-11-17T12:00:00-06:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-11-17:/new-pc</id><content type="html">&lt;p&gt;I've been a little busy lately, but should be able to pick things up again with the new PC I built.  Made a time-lapse
of assembling it too:&lt;/p&gt;
&lt;iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/fMiFqzKsV6g" width="560"&gt;&lt;/iframe&gt;</content><category term="misc"></category></entry><entry><title>SSL on Fox Row</title><link href="https://foxrow.com/ssl-on-fox-row" rel="alternate"></link><published>2013-09-25T12:00:00-05:00</published><updated>2013-09-25T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-09-25:/ssl-on-fox-row</id><summary type="html">&lt;p&gt;I've just enabled SSL on the site.  Technically it's TLS, but the name SSL seems to be sticking.  Now you can
access it at &lt;a href="https://www.foxrow.com"&gt;https://www.foxrow.com&lt;/a&gt; (note http&lt;strong&gt;s&lt;/strong&gt;).
The not-encrypted version should still be available at &lt;a href="http://www.foxrow.com"&gt;http://www.foxrow.com&lt;/a&gt;.
I'm not sure with Heroku hosting the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've just enabled SSL on the site.  Technically it's TLS, but the name SSL seems to be sticking.  Now you can
access it at &lt;a href="https://www.foxrow.com"&gt;https://www.foxrow.com&lt;/a&gt; (note http&lt;strong&gt;s&lt;/strong&gt;).
The not-encrypted version should still be available at &lt;a href="http://www.foxrow.com"&gt;http://www.foxrow.com&lt;/a&gt;.
I'm not sure with Heroku hosting the apps if I can get it set up for ergs and weather, still looking into that.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: due to Heartbleed, I disabled this.  Hopefully with &lt;a href="https://letsencrypt.org"&gt;let's encrypt&lt;/a&gt; I'll have SSL back.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Another update&lt;/em&gt;: Thanks to &lt;a href="https://letsencrypt.org"&gt;Let's Encrypt&lt;/a&gt;, secure connections are back.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Pillow and NEF files</title><link href="https://foxrow.com/pillow-and-nef-files" rel="alternate"></link><published>2013-08-29T12:00:00-05:00</published><updated>2013-08-29T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-08-29:/pillow-and-nef-files</id><content type="html">&lt;p&gt;Apparently PIL, and therefore Pillow, do not support Nikon RAW (.NEF) files.  From what I can tell, my camera shoots
RAWs in a 14-bit grayscale format.  With the help of &lt;a href="https://github.com/timotheeg/nefarious"&gt;nefarious&lt;/a&gt;,
I've found a way to get the image data out of my RAW files.  Images coming soon!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="image processing"></category><category term="photo"></category></entry><entry><title>Project consolidation</title><link href="https://foxrow.com/project-consolidation" rel="alternate"></link><published>2013-08-22T12:00:00-05:00</published><updated>2013-08-22T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-08-22:/project-consolidation</id><summary type="html">&lt;p&gt;As part of my ongoing effort to consolidate my various projects onto foxrow.com, I have moved the rowing weather app
to &lt;a href="http://weather.foxrow.com"&gt;weather.foxrow.com&lt;/a&gt;.  Eventually I hope to get some interactive form of
pypeline online as well.  I am not sure yet what form that might take.  Because RAWs …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As part of my ongoing effort to consolidate my various projects onto foxrow.com, I have moved the rowing weather app
to &lt;a href="http://weather.foxrow.com"&gt;weather.foxrow.com&lt;/a&gt;.  Eventually I hope to get some interactive form of
pypeline online as well.  I am not sure yet what form that might take.  Because RAWs are so big, uploading enough to
get a reasonable outcome may be prohibitively bandwith-intensive.  Also processing power on the server side may prove
too big a bottleneck, but I won't know until I try it.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Setting up Python 3 and pillow</title><link href="https://foxrow.com/setting-up-python-3-and-pillow" rel="alternate"></link><published>2013-08-03T12:00:00-05:00</published><updated>2013-08-03T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-08-03:/setting-up-python-3-and-pillow</id><summary type="html">&lt;p&gt;I'm using Linux Mint 15 ("Olivia") for my pypeline development - and ran into a little difficulty
right off the bat getting pillow installed.  Virtualenv and Virtualenvwrapper are pretty much a must-have if you're
developing multiple projects, and &lt;code&gt;pip install pillow&lt;/code&gt; was failing with messages like
&lt;code&gt;Python.h: No such file …&lt;/code&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm using Linux Mint 15 ("Olivia") for my pypeline development - and ran into a little difficulty
right off the bat getting pillow installed.  Virtualenv and Virtualenvwrapper are pretty much a must-have if you're
developing multiple projects, and &lt;code&gt;pip install pillow&lt;/code&gt; was failing with messages like
&lt;code&gt;Python.h: No such file or directory&lt;/code&gt; despite following the instructions to a T.  Turns out since I'm using Python 3,
&lt;code&gt;sudo apt-get install python-dev&lt;/code&gt; doesn't cut it.  What you want is &lt;code&gt;sudo apt-get install python3-dev&lt;/code&gt;.
Run that once and you should be good to go in your virtualenv.&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="image processing"></category></entry><entry><title>"pypeline v0.0.1 - a python pipeline"</title><link href="https://foxrow.com/pypeline-v001-a-python-pipeline" rel="alternate"></link><published>2013-08-03T11:00:00-05:00</published><updated>2013-08-03T11:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-08-03:/pypeline-v001-a-python-pipeline</id><summary type="html">&lt;p&gt;Since I plan on taking quite a few night sky pictures, I'd like to automate the process as much as possible.  The
project is &lt;a href="https://github.com/ryanfox/pypeline"&gt;hosted on github here&lt;/a&gt;.  Right now it's essentially empty,
but eventually features I'd like it to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image calibration - support for flat frames, bias frames, dark …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Since I plan on taking quite a few night sky pictures, I'd like to automate the process as much as possible.  The
project is &lt;a href="https://github.com/ryanfox/pypeline"&gt;hosted on github here&lt;/a&gt;.  Right now it's essentially empty,
but eventually features I'd like it to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image calibration - support for flat frames, bias frames, dark frames&lt;/li&gt;
&lt;li&gt;De-mosaicing&lt;/li&gt;
&lt;li&gt;Image registration&lt;/li&gt;
&lt;li&gt;Image stacking&lt;/li&gt;
&lt;li&gt;An optional GUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Why roll your own pipeline, when other programs exist?&lt;/em&gt;&lt;br/&gt;
I'd like to learn more about the process and get some experience with astrophotography.  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why python?&lt;/em&gt;&lt;br/&gt;
The majority of stacking/alignment applications I've found are windows-only.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why open source?&lt;/em&gt;&lt;br/&gt;
The majority of stacking/alignment applications I've found are closed-source, even if they're free as in beer.  I like
free as in everything.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What formats/algorithms do you support?&lt;/em&gt;&lt;br/&gt;
Right now, none.  Since my camera is a Nikon D5200, the first thing I'll implement is Nikon RAW (NEF) files.
Various stacking/averaging algorithms will have to come later.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What's your release schedule?&lt;/em&gt;&lt;br/&gt;
When I get around to it.  There's no hard timetable on anything, but I'd like to get a good chunk implemented sooner
rather than later.  Got some pictures to process!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Python 3?  Really?&lt;/em&gt;&lt;br/&gt;
Yes, really.  Python 3 is the future of the language, and Django and PIL were the two major things holding me back.
Well, now Django is py3k compatible, and &lt;a href="https://github.com/python-imaging/Pillow"&gt;now we have pillow&lt;/a&gt;,
the friendly PIL fork.  Lucky!&lt;/p&gt;
&lt;p&gt;That's all for now, follow the project on github or fork it and contribute!&lt;/p&gt;</content><category term="Programming"></category><category term="programming"></category><category term="python"></category><category term="image processing"></category><category term="astro"></category></entry><entry><title>"Night 2: the big dipper"</title><link href="https://foxrow.com/night-2-the-big-dipper" rel="alternate"></link><published>2013-08-02T10:00:00-05:00</published><updated>2013-08-02T10:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-08-02:/night-2-the-big-dipper</id><summary type="html">&lt;p&gt;With one set of exposures under my belt, I adjusted my camera settings a little and tried my hand again.  As luck
would have it, my window framed the big dipper quite nicely, so I set up in front of that and let it rip.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;51 light frames&lt;/li&gt;
&lt;li&gt;f/3 …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;With one set of exposures under my belt, I adjusted my camera settings a little and tried my hand again.  As luck
would have it, my window framed the big dipper quite nicely, so I set up in front of that and let it rip.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;51 light frames&lt;/li&gt;
&lt;li&gt;f/3.5&lt;/li&gt;
&lt;li&gt;10 second exposures&lt;/li&gt;
&lt;li&gt;ISO 1000&lt;/li&gt;
&lt;li&gt;18mm focal length&lt;/li&gt;
&lt;li&gt;31 dark frames&lt;/li&gt;
&lt;li&gt;31 bias frames - same everything except exposure time: 1/4000th second&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stacked in deep sky stacker and processed in Picasa.  I really need to get some better software.  This was closer to
the horizon than the previous shot, so there was heavy light pollution near the bottom of the frame.  After abusing
the stacked result pretty severely, I ended up with a recognizable big dipper and black sky.  I ended up cropping a
fair amount, so the effective focal length is quite a bit longer than 18mm.  I think it turned out fairly good,
considering:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://secure.flickr.com/photos/41053808@N06/9417522085/sizes/o/"&gt;&lt;img alt="dipper picture" class="m-image" src="https://farm4.staticflickr.com/3710/9417522085_30ab984d8f_b.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Photo"></category><category term="photo"></category><category term="astro"></category></entry><entry><title>First light from the Nikon</title><link href="https://foxrow.com/first-light-from-the-nikon" rel="alternate"></link><published>2013-07-30T12:00:00-05:00</published><updated>2013-07-30T12:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-07-30:/first-light-from-the-nikon</id><summary type="html">&lt;p&gt;Well, sort of.  This is my first attempt at any sort of serious astrophotography.  I wasn't aiming for anything in
particular, I just wanted to get a feel for my camera.  The conditions were subpar to say the least: it was a mildly
cloudy night, and I'm near a metropolitan …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Well, sort of.  This is my first attempt at any sort of serious astrophotography.  I wasn't aiming for anything in
particular, I just wanted to get a feel for my camera.  The conditions were subpar to say the least: it was a mildly
cloudy night, and I'm near a metropolitan downtown, so the light pollution was &lt;em&gt;terrible.&lt;/em&gt;  Collection details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nikon D5200 w/18-55mm Nikkor lens - RAW (NEF) format&lt;/li&gt;
&lt;li&gt;Focal length: 55mm&lt;/li&gt;
&lt;li&gt;Exposure time: 10secs&lt;/li&gt;
&lt;li&gt;f/11&lt;/li&gt;
&lt;li&gt;ISO 100&lt;/li&gt;
&lt;li&gt;31 light frames, 11 dark frames&lt;/li&gt;
&lt;li&gt;Stacked with Deep Sky Stacker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Considering all that, my results weren't too terrible:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://secure.flickr.com/photos/41053808@N06/9406934244/in/photostream/"&gt;&lt;img alt="night sky picture" class="m-image" src="https://farm8.staticflickr.com/7460/9406934244_6b6783d145_c.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looks pretty, but that's about it.  Technical note: I was having trouble getting my RAWs to align in DSS.  I suspect
my ISO was way too low for it to detect stars, but after converting to jpg it seemed to do reasonably well.&lt;/p&gt;</content><category term="Photo"></category><category term="photo"></category><category term="astro"></category></entry><entry><title>Fox Row ergs is moving</title><link href="https://foxrow.com/fox-row-ergs-is-moving" rel="alternate"></link><published>2013-07-30T10:00:00-05:00</published><updated>2013-07-30T10:00:00-05:00</updated><author><name>Ryan</name></author><id>tag:foxrow.com,2013-07-30:/fox-row-ergs-is-moving</id><summary type="html">&lt;p&gt;I'm redesigning the site a little.  Since I haven't been able to devote much time to Fox Row Ergs,
formerly at &lt;a href="http://www.foxrow.com"&gt;foxrow.com&lt;/a&gt;, I am moving that to &lt;a href="http://ergs.foxrow.com"&gt;ergs.foxrow.com&lt;/a&gt;,
and this blog is now at &lt;a href="http://www.foxrow.com"&gt;foxrow.com&lt;/a&gt;.  The app itself isn't changing, just my
focus on the site …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm redesigning the site a little.  Since I haven't been able to devote much time to Fox Row Ergs,
formerly at &lt;a href="http://www.foxrow.com"&gt;foxrow.com&lt;/a&gt;, I am moving that to &lt;a href="http://ergs.foxrow.com"&gt;ergs.foxrow.com&lt;/a&gt;,
and this blog is now at &lt;a href="http://www.foxrow.com"&gt;foxrow.com&lt;/a&gt;.  The app itself isn't changing, just my
focus on the site is.&lt;/p&gt;</content><category term="misc"></category></entry></feed>