<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>One more robot learns to see</title>
        <description>Kevin Hughes&apos; blog</description>
        <link>https://kevinhughes.ca/</link>
        <atom:link href="https://kevinhughes.ca/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Mon, 20 Apr 2026 16:10:54 +0000</pubDate>
        <lastBuildDate>Mon, 20 Apr 2026 16:10:54 +0000</lastBuildDate>
        <generator>Jekyll v4.4.1</generator>
        
            <item>
                <title>An Over-Engineered Travel Blog</title>
                <description>&lt;p&gt;Most people would just have a pin board to hang on the wall and track their travels by adding new pins for each trip; I have a custom ReactJS component which plots trips from a set of JSON files acting as a sort of database.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
  &lt;a href=&quot;https://kevinandsam.travel/map&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/posts/travel_map.png&quot; alt=&quot;map&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption class=&quot;caption-text&quot;&gt;
    The locations on the map are clickable for more details like the place and date. In the top right there is a layer control I recently added to toggle on and off different datasets like Sam&apos;s and my childhood trips.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I created &lt;a href=&quot;https://kevinandsam.travel&quot;&gt;kevinandsam.travel&lt;/a&gt; back in 2018 when my partner and I quit our jobs to follow our dream of backpacking around the world. We traveled to 31 countries across 4 continents over the course of 14 months - the experience of a lifetime. I often worked on the site among other &lt;a href=&quot;https://www.kevinhughes.ca/blog/building-my-own-saas-ultimate-tournament&quot;&gt;projects&lt;/a&gt; along with some consulting work in the downtime between destinations. I loved being a nomad but I also realized that too much work can detract from the true purpose of long-term travel and I would encourage anyone considering this lifestyle to save up more and work less.&lt;/p&gt;

&lt;p&gt;The site is built with &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;GatsbyJS&lt;/a&gt;. I’d used a couple of static site tools in the past (&lt;a href=&quot;https://middlemanapp.com/&quot;&gt;Middleman&lt;/a&gt; and &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; for this blog) and I wanted to try something new this time. Gatsby caught my attention because of how it combines React, server side rendering, rehydration and preloading for subsequent page navigation. This blog achieves a similar result using &lt;a href=&quot;https://github.com/turbolinks/turbolinks&quot;&gt;Turbolinks&lt;/a&gt; on top of Jekyll but it is cool to see this functionality as part of the framework.&lt;/p&gt;

&lt;p&gt;The framework delivered great performance which is best illustrated through a typical user visit:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The first page loads a completely server rendered HTML page with inlined CSS and an initial JS bundle. For &lt;a href=&quot;https://kevinandsam.travel&quot;&gt;kevinandsam.travel&lt;/a&gt; the home page is 16kb of HTML and ~100kb of Javascript. Thanks to rehydration the app behaves like a React app going forwards.&lt;/li&gt;
  &lt;li&gt;Hovering over the “Map” link in the nav bar preloads 3 more Javascript files and a page data JSON file. These files total ~60kb and include the additional Javascript required for the Map. If we inspect the files we can see most of the size is coming from &lt;a href=&quot;https://leafletjs.com/&quot;&gt;leaflet&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;For comparison if we hover over “About Us” only two Javascript files totalling ~10kb are preloaded.&lt;/li&gt;
  &lt;li&gt;When a link is clicked the navigation to the new page is quick and smooth because the data and code has been pre-fetched.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More important than the measured performance the site feels fast thanks to smart animation of the content. Anecdotally it is fast everywhere in the world - at least everywhere I’ve tried :wink:.&lt;/p&gt;

&lt;p&gt;Another reason I picked Gatsby is React itself. I had a few features in mind for the site which I knew would be easier to build using open source components. In my opinion, this is one of the main benefits of React, especially for solo projects or small teams, it is so much better and easier to grab off the shelf UI pieces and integrate them than in the old jquery days. One of these features is the &lt;a href=&quot;https://kevinandsam.travel/visit&quot;&gt;Come Visit&lt;/a&gt; form which has a country dropdown and a datepicker powered by the same data that draws the map.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
  &lt;img src=&quot;/images/posts/visit.gif&quot; alt=&quot;visit&quot; /&gt;
  &lt;figcaption class=&quot;caption-text&quot;&gt;
    The conversational response that fades in after making a selection has links to send an email with a pre-filled subject and to Google flights with the destination airport pre-filled. It used to set the dates too but Google doesn&apos;t accept dates in the past so it doesn&apos;t anymore.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Having our route data in a structured JSON format was also useful for planning while we were travelling. I had a few scripts I used to quickly convert dates into durations at each location. It was often much easier to think in terms of how many days we wanted to spend somewhere than in dates. This script enabled an iterative workflow where I could print the durations while tweaking dates and see where everything landed. This was also useful to arrange meeting people in certain places and times as well as managing seasonal constraints.&lt;/p&gt;

&lt;p&gt;Another nice feature of Gatsby is the image plugins. The site is very image heavy and the framework handles pre-processing and resizing of images at build time which makes it easier to manage assets. I can commit the full size images and then resize appropriately during the build but crucially it is easy to change the size at any time. Gatsby also automatically creates inlined placeholders for images while they load for a nice effect.&lt;/p&gt;

&lt;p&gt;Later in the development, I added custom markdown components with &lt;a href=&quot;https://github.com/rehypejs/rehype-react&quot;&gt;rehype&lt;/a&gt; (and even later migrated to &lt;a href=&quot;https://github.com/mdx-js/mdx/&quot;&gt;MDX&lt;/a&gt;) which after a mildly painful setup made writing posts featuring &lt;a href=&quot;https://kevinandsam.travel/in-bruges&quot;&gt;advanced layouts&lt;/a&gt; and &lt;a href=&quot;https://kevinandsam.travel/kevins-top-ten&quot;&gt;photo carousels&lt;/a&gt; a blast. Overall, I have been pretty happy with the framework - I got the result and performance I wanted but occasionally I did have to fight with confusing build errors.&lt;/p&gt;

&lt;p&gt;The newest addition to the blog is a re-presentation of our social media posts from our trip. While I have mixed feelings about social media, I value the content we shared about our journey and the connections it facilitated. To reduce my dependence on these platforms I decided to preserve the posts that matter to me on my own blog. This way I can maintain control over the content and have one less reason to keep my accounts.&lt;/p&gt;

&lt;p&gt;I started by exporting my data and rebuilding my &lt;a href=&quot;https://kevinandsam.travel/facebook&quot;&gt;Facebook feed&lt;/a&gt;. I’ve used a few of these export tools and I am always impressed with the data I receive back, it wasn’t a drop in but with a minimal python script I could easily filter and transform the data into something appropriate for a Gatsby source. The initial feed was pretty straight forward to build thanks to &lt;a href=&quot;https://react-photo-album.com/&quot;&gt;react-photo-album&lt;/a&gt; and &lt;a href=&quot;https://www.gatsbyjs.com/blog/gatsbygram-case-study/&quot;&gt;Gatsbygram&lt;/a&gt; for an infinite scroll example. The toughest part was drawing a nice arc on the map for the “travelling to” posts. After getting my cloned feed working I was able to take things further and add functionality to search, filter and change the order.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
  &lt;img src=&quot;/images/posts/facebook.gif&quot; alt=&quot;facebook&quot; /&gt;
  &lt;figcaption class=&quot;caption-text&quot;&gt;
    I had to rig up a setTimeout and fiddle with some parameters to actually capture the loading spinner for this gif. The production parameters load more in advance and the user never sees the spinner for a very true infinite scroll experience.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://kevinandsam.travel/instagram&quot;&gt;Instagram&lt;/a&gt; was the harder of the two to build. I re-used quite a bit of the import logic and basic component setup but the modal I wanted required new state and logic. Getting the styling right and maximising the space used by the image in all possible aspect ratios was a challenge and took longer than I’d like to admit. I was able to leverage react-photo-album again along with &lt;a href=&quot;https://react-responsive-carousel.js.org/&quot;&gt;react-responsive-carousel&lt;/a&gt; and an underlying library for swipe events to build a mobile and desktop friendly instagram-esque page on our site.&lt;/p&gt;

&lt;style&gt;
/* https://www.w3schools.com/howto/howto_css_devices.asp */

/* The device with borders */
.smartphone {
  position: relative;
  width: 270px;
  height: 480px;
  margin: auto;
  margin-bottom: 24px;
  border: 16px black solid;
  border-top-width: 60px;
  border-bottom-width: 60px;
  border-radius: 36px;
}

/* The horizontal line on the top of the device */
.smartphone:before {
  content: &apos;&apos;;
  display: block;
  width: 60px;
  height: 5px;
  position: absolute;
  top: -30px;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #333;
  border-radius: 10px;
}

/* The circle on the bottom of the device */
.smartphone:after {
  content: &apos;&apos;;
  display: block;
  width: 35px;
  height: 35px;
  position: absolute;
  left: 50%;
  bottom: -65px;
  transform: translate(-50%, -50%);
  background: #333;
  border-radius: 50%;
}

/* The screen (or content) of the device */
.smartphone .content {
  width: 270px;
  height: 480px;
  background: black;
}
&lt;/style&gt;

&lt;div class=&quot;smartphone&quot;&gt;
  &lt;div class=&quot;content&quot;&gt;
    &lt;img src=&quot;/images/posts/instagram.gif&quot; alt=&quot;instagram&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Afterwards, I generalized the infinite scroll pattern with a higher order component and added search to the original blog section of the site. Search could be further abstracted to remove some duplication present in each of the components but I am happy enough with the implementation for now.&lt;/p&gt;

&lt;p&gt;I enjoyed revisiting these old posts and exploring my data in this way. It served as a fun but admittedly odd way to reminisce about our time travelling. The recent push to finish these features also motivated me to finally open source the site and share more about the process of building it. You can check out all the code on github at &lt;a href=&quot;https://github.com/kevinhughes27/kevinandsam.travel&quot;&gt;kevinhughes27/kevinandsam.travel&lt;/a&gt;; sometimes I even wrote PRs to myself :joy:.&lt;/p&gt;

&lt;p&gt;If you made it all the way here thank you for reading, and I hope this post has inspired you to build for yourself, for fun, or simply because you can.&lt;/p&gt;
</description>
                <pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/an-over-engineered-travel-blog</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/an-over-engineered-travel-blog</guid>
                
                
            </item>
        
            <item>
                <title>TensorKart: self-driving MarioKart with TensorFlow</title>
                <description>&lt;p&gt;This winter break, I decided to try and finish a project I started a few years ago:  training an &lt;a href=&quot;https://en.wikipedia.org/wiki/Artificial_neural_network&quot;&gt;artificial neural network&lt;/a&gt; to play MarioKart 64. It had been a few years since I’d done any serious machine learning, and I wanted to try out some of the new hotness (aka &lt;a href=&quot;https://www.tensorflow.org/&quot;&gt;TensorFlow&lt;/a&gt;) I’d been hearing about. The timing was right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project&lt;/strong&gt; - use TensorFlow to train an agent that can play MarioKart 64.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt; - I wanted to make the end-to-end process easy to understand and follow, since I find this is often missing from machine learning demos.&lt;/p&gt;

&lt;p&gt;Finally, after playing way too much MarioKart and writing an &lt;a href=&quot;https://github.com/kevinhughes27/mupen64plus-input-bot&quot;&gt;emulator plugin in C&lt;/a&gt;, I managed to get some decent results.&lt;/p&gt;

&lt;p&gt;Driving a new (untrained) section of the Royal Raceway:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/1435VvCosVezQY/giphy.gif&quot; alt=&quot;RoyalRaceway.gif&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Driving Luigi Raceway:&lt;/p&gt;
&lt;iframe width=&quot;480&quot; height=&quot;270&quot; style=&quot;padding-bottom: 1.5em;&quot; src=&quot;https://www.youtube.com/embed/vrccd3yeXnc&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Getting to this point wasn’t easy and I’d like to share my process and what I learned along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To create a training dataset, I wrote a program to take screenshots of my desktop synced with input from my Xbox controller. Then I ran an N64 emulator and positioned the window in the capture area. Using this program, I recorded a dataset about what my AI would &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;see&lt;/code&gt; and what the appropriate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt; was.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/record_setup.png&quot; alt=&quot;record&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This was the only part I finished back when I started this project. It was interesting to see the difference in my own coding style and tool choices from several years ago. My urge to update this code was strong, but if it ain’t broke don’t fix it. So other than a bit of clean up, I mostly left things as they were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I started by modifying the &lt;a href=&quot;https://www.tensorflow.org/tutorials/mnist/pros/&quot;&gt;TensorFlow tutorial&lt;/a&gt; for a character recognizer using the &lt;a href=&quot;http://yann.lecun.com/exdb/mnist/&quot;&gt;MNIST dataset&lt;/a&gt;. I had to change the input and output layer sizes as well as the inner layers since my images were much larger than the 28x28 characters from MNIST. This was a bit tedious and I feel like TensorFlow could have been more helpful with these changes.&lt;/p&gt;

&lt;p&gt;Later, I switched to use &lt;a href=&quot;https://github.com/SullyChen/Autopilot-TensorFlow&quot;&gt;Nvidia’s Autopilot&lt;/a&gt; developed specifically for self-driving vehicles. This also simplified my data preparation code. Instead of converting to grayscale and flattening the image to a vector manually, this model took colour images directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To train a TensorFlow model, you have to define a function for TensorFlow to optimize. In the MNIST tutorial, this function is the sum of incorrect classifications. Nvidia’s Autopilot uses the difference between the predicted and recorded steering angle. For my project, there are several important joystick outputs. The function I used was the euclidean distance between the predicted output vector and the recorded one.&lt;/p&gt;

&lt;p&gt;One of TensorFlow’s best features is this explicit function definition. Higher level machine learning frameworks might abstract this, but TensorFlow forces the developer to think about what is happening and helps dispel the magic around machine learning.&lt;/p&gt;

&lt;p&gt;Training was actually the easiest part of this project. TensorFlow has great documentation and there are plenty of tutorials and source code available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With my model trained, I was ready to let it loose on MarioKart 64. But there was still a piece missing: how was I going to transfer output from my AI to the N64 emulator? The first thing I tried was to send input events using &lt;a href=&quot;https://github.com/tuomasjjrasanen/python-uinput&quot;&gt;python-uinput&lt;/a&gt;. This almost worked and several joystick utilities believed my program was a proper joystick, but unfortunately not &lt;a href=&quot;http://www.mupen64plus.org/&quot;&gt;mupen64plus&lt;/a&gt;  (the N64 emulator).&lt;/p&gt;

&lt;p&gt;By this point, I was already digging into how &lt;a href=&quot;https://github.com/mupen64plus/mupen64plus-input-sdl&quot;&gt;mupen64plus-input-sdl&lt;/a&gt; worked (to see why it didn’t like my fake input), so it seemed like a better idea to write my own input plugin rather than trying to hack through multiple layers with fake joystick events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rabbit Hole - writing a mupen64plus input plugin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I hadn’t written a proper C program in quite a while, and I was excited to give it a go. I started by doing a curated copy paste of the original input driver. My goal was to get a bare bones plugin compiled and running inside the emulator. When the plugin is loaded, the emulator checks for several function definitions and errors if any are missing. This meant my plugin needed to have several empty functions defined. I thought this was interesting and pretty different from how I’m used to connecting things.&lt;/p&gt;

&lt;p&gt;With my plugin working, I figured out how to set the controller output. I made Mario drive donuts forever.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/ff2JC19n5NMre/giphy.gif&quot; alt=&quot;donuts.gif&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The next step was to ask a local server what the input should be. This required making an http request in C, which turned out to be quite the task. People complain that distributed systems are hard - and they are! - but they forget that it used to be pretty hard to distribute them in the first place. It took me a while to figure out that my http request was missing an extra newline (and was thus malformed), which caused my python server to hang and never respond since it was still waiting for the request to finish. After a few more interesting mishaps, I finally finished consuming data and outputting it to the N64 emulator internally.&lt;/p&gt;

&lt;p&gt;My completed input plugin is available on &lt;a href=&quot;https://github.com/kevinhughes27/mupen64plus-input-bot&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playing Revisited&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now my AI could play! The first race was pretty disappointing - Mario drove straight into the wall and made no attempt to turn :sob:.&lt;/p&gt;

&lt;p&gt;To debug, I added a manual override, allowing me to take control from the AI when needed. I observed the output while playing and stepped through the whole system again. I came up with 2 things to fix for the next iteration:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;While re-inspecting my training data, I found that the screenshot was occasionally a picture of my desktop behind the emulator window. I didn’t bother looking into why this happened since I’d been down enough rabbit holes on this project already. My solution was simply to remove these bad samples from my data and move on.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I noticed that MarioKart is a very jerky game in that you typically don’t take corners smoothly. Instead, players usually make several sharp adjustments throughout a large turn. If you think about what the agent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sees&lt;/code&gt; at each iteration, this jerkiness could explain why Mario didn’t turn. Thanks to aliasing of the data, there would be images of Mario in the middle of a turn both with and without a joystick output indicating the turn. I suspect the model learned to never turn and that this actually resulted in the smallest error over the dataset. Training is still a dumb optimization problem and it will happily settle on this if the data leads it there.&lt;/p&gt;

    &lt;p&gt;With this in mind I played more MarioKart to record new training data. I remember thinking to myself while trying to drive perfectly, “is this how parents feel when they’re driving with their children who are almost 16?”&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those 2 adjustments I got the results you saw at the beginning of this blog post. Hooray!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.tensorflow.org/&quot;&gt;TensorFlow&lt;/a&gt; is super cool despite being a lower level abstraction than tools I used several years ago (&lt;a href=&quot;http://scikit-learn.org/stable/&quot;&gt;scikit-learn&lt;/a&gt; and &lt;a href=&quot;http://shogun-toolbox.org/&quot;&gt;shogun&lt;/a&gt;). The speed it offers (thanks to &lt;a href=&quot;https://developer.nvidia.com/cudnn&quot;&gt;cuDNN&lt;/a&gt;) is worth it and their gradient descent / optimizers seem really top notch. If I was doing another deep learning project, I probably wouldn’t use TensorFlow directly; I’d try &lt;a href=&quot;https://keras.io/&quot;&gt;keras&lt;/a&gt; since doing the math for layer sizes sucks (keras is built on TensorFlow though so it’s basically syntactic sugar).&lt;/p&gt;

&lt;p&gt;In a couple of days over winter break, I was able to to train an AI to drive a virtual vehicle using the same technique Google uses for their self-driving cars. With approximately 20 minutes of training data, my AI was able to drive the majority of the simplest course, Luigi Raceway, and generalized to at least one section of an untrained racetrack. With more data, I bet we could build a complete AI for MarioKart 64.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kevinhughes27/TensorKart&quot;&gt;github.com/kevinhughes27/TensorKart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kevinhughes27/mupen64plus-input-bot&quot;&gt;github.com/kevinhughes27/mupen64plus-input-bot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update / Reception&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post went viral on the internet!&lt;/p&gt;

&lt;p&gt;It started by topping &lt;a href=&quot;https://news.ycombinator.com/item?id=13317902&quot;&gt;hacker news&lt;/a&gt;: &lt;img src=&quot;/images/posts/hacker-news-tensorkart.jpg&quot; alt=&quot;hacker news&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It did pretty good on &lt;a href=&quot;https://www.reddit.com/r/programming/comments/5lz67q/tensorkart_selfdriving_mariokart_with_tensorflow/&quot;&gt;/r/programming&lt;/a&gt; as well. Then it got picked up by Google News, LinkedIn and all sorts of content sites. I personally got a kick out of it when Google suggested my own article to me 😂&lt;/p&gt;

&lt;p&gt;Tensor Kart is still doing laps of the internet to this day thanks to various twitter bots.&lt;/p&gt;
</description>
                <pubDate>Tue, 03 Jan 2017 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/tensor-kart</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/tensor-kart</guid>
                
                <category>machine-learning</category>
                
                <category>python</category>
                
                
            </item>
        
            <item>
                <title>Building my own SaaS: Ultimate Tournament</title>
                <description>&lt;figure class=&quot;caption&quot;&gt;
    &lt;img src=&quot;/images/posts/ultimate-tournament.png&quot; alt=&quot;&quot; /&gt;
    
&lt;/figure&gt;

&lt;p&gt;&lt;i&gt;
  Update - I shutdown and open sourced Ultimate Tournament in 2020. I learned a lot from working on this project but it was time to move on. If you’d like to read more about my final thoughts or check out the code it is available on &lt;a href=&quot;https://github.com/kevinhughes27/ultimate-tournament&quot;&gt;GitHub&lt;/a&gt;.
&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;For the past while I’ve been working on an ambitious side project - building a complete Software as a Service (SaaS) for creating and managing Ultimate Frisbee tournaments. I started the project leading a team at a charity hackathon and have since continued working on it all on my own. This project has pushed me to learn many new things and wear a lot of different hats in order to build an entire product I am comfortable launching.&lt;/p&gt;

&lt;p&gt;There are a lot of moving of moving pieces in a complete SaaS product: a landing page with signup, an onboarding or setup process, analytics to help improve the product, analytics for marketing, an internal admin application for managing the app and of course there is the application itself. Ultimate Tournament actually has two main user applications: the management software for tournament directors and the mobile schedule and score reporting app for players and captains. From a technical standpoint it needs good infrastructure and reliable CI both preferrably scalable on demand.&lt;/p&gt;

&lt;p&gt;With so much to build I learned a lot more about how to configure the frontend part of a Rails application and evolved my opinion on server side rendering vs a single page applications (it always depends!). In order to achieve some of my user interface ideas I ended up learning and adopting React. I also built out an entire authentication stack which included social logins, an internal area and the ability for staff to login to tournaments to help troubleshoot.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
    &lt;img src=&quot;/images/posts/ut_og_schedule_editor.png&quot; alt=&quot;&quot; /&gt;
    
    &lt;figcaption class=&quot;caption-text&quot;&gt;Drag and Drop schedule editor&lt;/figcaption&gt; 
    
&lt;/figure&gt;

&lt;p&gt;Tournament logistics are tricky! I under estimated the complexity of this logic on the backend - Brackets especially are a pretty non-trivial graph structure which is quite different from the data I’ve been working with lately so it was a learning experience.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
    &lt;img src=&quot;/images/posts/ut_og_bracket.png&quot; alt=&quot;&quot; /&gt;
    
    &lt;figcaption class=&quot;caption-text&quot;&gt;Visualizing the bracket graphs&lt;/figcaption&gt; 
    
&lt;/figure&gt;

&lt;p&gt;This project really helped me appreciate everything that goes into a SaaS product and helped me better understand all the things that happen around me at work. There is a lot more technical work I still want to do on this project like adding ActionCable for websockets and expanding it to other sports. I’ll probably build out an API soon as well.&lt;/p&gt;

&lt;p&gt;Now I’m trying my hand at marketing to get the word out so people use my app. If you happen to need tournament software or know someone who does please check it out! &lt;a href=&quot;https://www.ultimate-tournament.io&quot;&gt;ultimate-tournament.io&lt;/a&gt;&lt;/p&gt;
</description>
                <pubDate>Sun, 15 May 2016 21:25:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/building-my-own-saas-ultimate-tournament</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/building-my-own-saas-ultimate-tournament</guid>
                
                
            </item>
        
            <item>
                <title>2015 a Year in Review</title>
                <description>&lt;p&gt;2015 was a pretty awesome year full of new experiences, growth and accomplishments. It marked my second year of working in software professionally and school seems like a faint memory at this point. Shopify, the company where I work, IPO’d this summer which was an exciting and [possibly] once in a lifetime experience. I think this kind of post is worth doing to reflect before simply setting our sights on the next goal. So here it is my year in review in bullet points:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Travel&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Barcelona for &lt;a href=&quot;http://2015.fullstackfest.com/&quot;&gt;FullStack Fest&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Stockholm Sweden for &lt;a href=&quot;https://nordicjs2015.confetti.events/&quot;&gt;Nordic.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Paris France (for just over 24 hours but we saw a lot!)&lt;/li&gt;
  &lt;li&gt;Helsinki Finland for &lt;a href=&quot;http://hackjunction.com/&quot;&gt;Junction 2015&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Costa Rica for vacation&lt;/li&gt;
  &lt;li&gt;Peru for a vacation and to hike the Inca Trail!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sports and Fitness&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;new PR squating at 255 lbs for 1 (although now I’m injured as I write this and my squat is low :sadface:)&lt;/li&gt;
  &lt;li&gt;ran 3k in 12:35 (mm::ss)&lt;/li&gt;
  &lt;li&gt;made a lot of improvement in mobility (I can now touch my toes!)&lt;/li&gt;
  &lt;li&gt;I discovered and became addicted to &lt;a href=&quot;https://www.yogatuneup.com/&quot;&gt;Yoga Tune Up&lt;/a&gt; which has really helped support my other fitness goals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ultimate&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I played my first season of competitive Ultimate with &lt;a href=&quot;https://twitter.com/SwiftFlatball&quot;&gt;Swift&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I attended 3 away tournaments with Swift in Toronto, New York and Barrie finishing our season at No Borders in Ottawa.&lt;/li&gt;
  &lt;li&gt;We didn’t win a lot of games (we were essentially the C team) but we did beat Goose when it counted!&lt;/li&gt;
  &lt;li&gt;I played in my first beach tournament ever!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Side Projects&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I wrote my first text editor extension &lt;a href=&quot;https://github.com/kevinhughes27/RailsRunMigration&quot;&gt;RailsRunMigration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I made a script to alert me when I spend too much time coding (the irony) &lt;a href=&quot;https://gist.github.com/kevinhughes27/26bfe9dd408972c581f7&quot;&gt;coding_timer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I created some cool &lt;a href=&quot;https://github.com/kevinhughes27/ocua-parity-league&quot;&gt;infographics&lt;/a&gt; for the OCUA Parity League&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kevinhughes27/merch-my-tweet&quot;&gt;merch-my-tweet&lt;/a&gt; a bot who automatically creates products from your tweets&lt;/li&gt;
  &lt;li&gt;I wrote a &lt;a href=&quot;https://github.com/kevinhughes27/money-logic-cli&quot;&gt;javascript library&lt;/a&gt; to add a CLI to my online banking&lt;/li&gt;
  &lt;li&gt;I built my first &lt;a href=&quot;https://github.com/kevinhughes27/audiogrep-docker&quot;&gt;docker image&lt;/a&gt; (trivial but felt cool)&lt;/li&gt;
  &lt;li&gt;I also shipped improvements to &lt;a href=&quot;https://github.com/kevinhughes27/shopify-sinatra-app&quot;&gt;shopify-sinatra-app&lt;/a&gt; and completely re-wrote &lt;a href=&quot;https://github.com/Shopify/shopify_app&quot;&gt;shopify-app&lt;/a&gt; as a proper rails engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;New Tech&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://facebook.github.io/react/&quot;&gt;React.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://guides.rubyonrails.org/engines.html&quot;&gt;Rails engines&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;npm / common js build tools&lt;/li&gt;
  &lt;li&gt;javascript testing with &lt;a href=&quot;http://jasmine.github.io/2.0/introduction.html&quot;&gt;jasmine&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;capybara / browser testing&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://middlemanapp.com/&quot;&gt;middleman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Chrome Extensions&lt;/li&gt;
  &lt;li&gt;small task automation with &lt;a href=&quot;https://developers.google.com/apps-script/?hl=en&quot;&gt;AppScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Fri, 01 Jan 2016 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/2015-a-year-in-review.html</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/2015-a-year-in-review.html</guid>
                
                
            </item>
        
            <item>
                <title>Averaging That 70&apos;s Show</title>
                <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; I averaged every episode of That 70’s Show into a single video&lt;/p&gt;

&lt;iframe width=&quot;420&quot; height=&quot;315&quot; style=&quot;padding-bottom: 1.5em&quot; src=&quot;https://www.youtube.com/embed/GaD5w8eEQD0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;A couple of weeks ago I was hanging out with my buddy and we were going to watch some TV. I was making a pitch for my new favorite show &lt;a href=&quot;http://www.adultswim.ca/tv/rick-and-morty/&quot;&gt;Rick &amp;amp; Morty&lt;/a&gt; which we couldn’t find using the PS3 internet browser. We did however find excellent &lt;a href=&quot;https://youtu.be/Or1WvX88lng&quot;&gt;video&lt;/a&gt; syncing Rick and Morty clips with Eminem’s Rap God.&lt;/p&gt;

&lt;p&gt;One good YouTube find is never enough though so as soon as the video finished we eagerly checked the user’s other videos and thats when we stumbled upon this gem:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; style=&quot;padding-bottom: 1.5em&quot; src=&quot;https://www.youtube.com/embed/c5h3qXAQOQI&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;After watching a bit too much of ‘Every episode of Friends simultaneously’ I went looking for two things of which I found neither. Maybe I live in too much of a software bubble but I was genuinely surprised when I couldn’t find a link to the code used to make the video. I was equally surprised that this was the only video of its kind that I could find.&lt;/p&gt;

&lt;p&gt;And thats how this project got started - I set out to create a script that would let me average all the episodes of any show into a new video. I wrote a &lt;a href=&quot;https://gist.github.com/kevinhughes27/93f639e421cfc75d7678&quot;&gt;script&lt;/a&gt; in python and opencv to do the work and then I picked That 70’s Show as my first test (I would have preferred Seinfeld but I don’t have it). That 70’s Show is a pretty good candidate - my main hypothesis was that shows that re-use the same locations frequently will be more interesting.&lt;/p&gt;

&lt;p&gt;I am pretty pleased with the results! It’s pretty fun to watch and you can definitely pick out distinct patterns in how they frame shots and recognize scenes like the car from the opening credits (at ~2 minutes in). If I’d ever taken a course in film I’m sure I’d have some more intelligent things to say.&lt;/p&gt;

&lt;p&gt;I also made this video where I ramp up to the average adding a new episode every 30 frames or around 1 second. So for the first second you’re only watching 1 episode then 2, 3 etc:&lt;/p&gt;

&lt;iframe width=&quot;420&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/nLEgH4Fkpgc&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
</description>
                <pubDate>Tue, 22 Dec 2015 16:26:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/averaging-that-70-s-show</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/averaging-that-70-s-show</guid>
                
                <category>computer-vision</category>
                
                <category>science</category>
                
                
            </item>
        
            <item>
                <title>Static Blog Search with Awesomplete</title>
                <description>&lt;p&gt;This is a post about developing my blog on my blog - take a second to appreciate how meta that is.&lt;/p&gt;

&lt;p&gt;I recently transitioned my personal site from Wordpress to a static site on Github pages built using &lt;a href=&quot;https://middlemanapp.com/&quot;&gt;Middleman&lt;/a&gt;. Overall it was an awesome change and I feel much better having my personal site and assets etc in plain text and source control. The transition was pretty smooth however there were a few things that I had with Wordpress that took a bit of finesse to achieve with Middleman. Blog search was one of those things and I eventually came up with a pretty elegant solution that I would like to share.&lt;/p&gt;

&lt;p&gt;The reason search is interesting is because Middleman generates static sites which means any search solution needs to be entirely client side. If you google for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Middleman search&lt;/code&gt; you’ll find some other projects and gems but I think my solution is simpler.&lt;/p&gt;

&lt;p&gt;I built my blog search using this nice little autocomplete library called &lt;a href=&quot;https://leaverou.github.io/awesomplete/&quot;&gt;Awesomplete&lt;/a&gt;. To start I added the following markup to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sidebar&lt;/code&gt; partial which is rendered on all the blog sections of my website:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;search&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form-control&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;list=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;articleList&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Search...&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;datalist&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;articleList&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;blog.articles.each&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;option&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;article.title&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/datalist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This creates the base list of all my blog articles. Now we need to bring it to life with Awesomplete:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#search&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awesomplete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Awesomplete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minChars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;maxItems&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;autoFirst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;searchIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to_json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;%= article.url %&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;awesomplete-selectcomplete&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;articleTitle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;articleUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;searchIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articleTitle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Turbolinks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;articleUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code does a couple of things - first it initializes the Awesomplete widget. Next it defines a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;searchIdx&lt;/code&gt; variable which just maps article title to url and stores it in javascript land so we can access it later. The last bit of code listens to the awesomplete-selectcomplete event then looks up the url for the selected article and then loads the page with Turbolinks (if you’re not familiar this is just a slicker way of doing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.location = &lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I was really happy with how this turned out, super fast, simple and the code is nicely contained in my sidebar partial. Give it a try in the search box to the right!&lt;/p&gt;
</description>
                <pubDate>Tue, 27 Oct 2015 18:49:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/static-blog-search-with-awesomeplete</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/static-blog-search-with-awesomeplete</guid>
                
                <category>javascript</category>
                
                <category>ruby</category>
                
                
            </item>
        
            <item>
                <title>Some fun with D3.js</title>
                <description>&lt;p&gt;&lt;img src=&quot;/images/posts/player-comparer.png&quot; alt=&quot;player-comparer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve been intrigued by D3.js and the impressive visualizations I’ve seen people make for quite some time and I finally got to check it out for myself. I wanted to write this blog post sharing my experiences and revelations from the perspective of someone who at one point spent a fair amount of time with Matlab/Octave and my personal fave matplotlib.&lt;/p&gt;

&lt;p&gt;The first major point I want to make is that D3.js is not a plotting library - it should not be compared directly to Matlab/Octave or matplotlib because they serve different purposes. D3.js is a visualization library, sure this visualization may be a graph but you shouldn’t be playing with D3.js until you already understand your data and simply want to show it off. While exploring and figuring out your data matplotlib is going to be way faster and more effective (note there are some wrappers building standard plotting functions ontop of D3.js like NVD3 if javascript is really your thing).&lt;/p&gt;

&lt;p&gt;Once you’ve figured out your data and it’s time to show-off D3.js really shines and here is the main reason why in my opinion. It’s due to a fundamental shift in a way of thinking that makes D3.js so powerful: rather than calling plot with an array of x and y values I am binding an array of javascript objects to my plot and then telling it which attributes to use for different things. Essentially you are pulling more data into the plot and you can write code at each level - let me show you the snippet that made me realize how powerful this shift in thinking is:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;d3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.chart&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;d3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;d3-tip&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;selectAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;translate(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;barWidth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,0)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;barWidth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mouseover&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mouseout&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The important parts to note are this: I grab the value for my y axis of the rect from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d.value&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; is one of the objects in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; array that I am plotting. So far this is pretty standard but look at the tip function that gets called on mouseover - it shows a span which contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d.text&lt;/code&gt;! I’ve attached another piece of data here called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt; that can be used to display more information! This was the wow moment for me because this kind of thing isn’t possible in matplotlib and showing a simple tooltip is really only the beginning of what you can do with all this added context.&lt;/p&gt;

&lt;p&gt;Also something about plots with a :hover effect is kind of awesome!&lt;/p&gt;

&lt;p&gt;If you haven’t played with D3 yet and have some cool data I definetly recommend it, I used it to make some pretty sweet visualizations/tools for our &lt;a href=&quot;http://www.ocua.ca/Winter-Parity&quot; target=&quot;_blank&quot;&gt;Parity Ultimate Frisbee League&lt;/a&gt; here in Ottawa, check’em out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/trade_dashboard.png&quot; alt=&quot;trade-dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kevinhughes27/ocua-parity-league&quot;&gt;https://github.com/kevinhughes27/ocua-parity-league&lt;/a&gt;&lt;/p&gt;
</description>
                <pubDate>Thu, 20 Nov 2014 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/Some-fun-with-D3js</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/Some-fun-with-D3js</guid>
                
                <category>javascript</category>
                
                
            </item>
        
            <item>
                <title>Building my first Shopify App</title>
                <description>&lt;p&gt;A few weeks ago in my first quarterly hackathon at Shopify I joined a team that was building a Shopify App to help charities issue donation receipts for orders on their store. We got pretty far during the hackathon and afterwards I kept working on it in my free time. It was a good way to dog food our API and tooling which was my responsibility at work.&lt;/p&gt;

&lt;p&gt;I finally finished the app and a few days ago it launched on the Shopify App Store. The app automates the process of sending customers tax receipts for their donations to a non-profit Shopify store using webhooks. It’s a pretty cool little app and a good example of how to build a simple piece of automation using webhooks.&lt;/p&gt;

&lt;p&gt;I knew the scope of the app was going to be small so I wanted to pick an appropriately minimalist framework instead of Rails. I went with Sinatra and ended up extracting a small gem &lt;a href=&quot;https://github.com/kevinhughes27/shopify-sinatra-app&quot;&gt;shopify-sinatra-app&lt;/a&gt; for others to use. The app itself is also open source, you can check out all the code and follow the ongoing development and maintenance &lt;a href=&quot;https://github.com/kevinhughes27/shopify-tax-receipts&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Tue, 09 Sep 2014 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/building-my-first-shopify-app</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/building-my-first-shopify-app</guid>
                
                <category>ruby</category>
                
                <category>shopify</category>
                
                
            </item>
        
            <item>
                <title>Testing javascript with python</title>
                <description>&lt;p&gt;I was recently tasked with adding Mailcheck.js to some of our production pages and I want to describe a bit of the process I went through because I did some things a bit differently and had some fun along the way.&lt;/p&gt;

&lt;p&gt;Lets start with a PSA - do not simply drop Mailcheck onto your website as is! In my opinion / findings the default algorithm is way too greedy - aka it will mostly suggest all emails should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;____@gmail.com&lt;/code&gt;. It is worth taking the time to tweak mailcheck for your particular userbase, on one wants to see a correction for their proper email address!&lt;/p&gt;

&lt;p&gt;The first thing I did was dumped a ton of emails from our database to create a dataset to work with. I could have used Node to write some scripts to test out the Mailcheck behaviour but Python is just so much more convient for doing numerical analysis. Plus it’s what our data team uses so I could leverage some of their knowledge and code. So now for the fun part - I ended up using PyV8 (a python wrapper for calling out to Google’s V8 javascript engine). With this setup I was able to slice and dice through our production emails using python and pandas calling the exact javascript mailcheck algorithm and collecting my results. After tweaking the algorithm I could take the settings and new js code and put it in production.&lt;/p&gt;

&lt;p&gt;Check out this wacky franken script that got the job done (pandas not included):&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PyV8&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init_mailcheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PyV8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;JSContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mailcheck.js&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_sift3Distance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mailcheck.mailcheck.sift3Distance(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_splitEmail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mailcheck.mailcheck.splitEmail(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_mailcheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; Mailcheck.mailcheck.run({
         email: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;,
       })
   &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;ctxt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;AttributeError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;init_mailcheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_mailcheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;kevinhughes27@gmil.com&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# &amp;gt;&amp;gt;&amp;gt; @kevinhughes27@gmail.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
                <pubDate>Sat, 01 Feb 2014 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/Testing-javascript-with-python</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/Testing-javascript-with-python</guid>
                
                <category>python</category>
                
                <category>javascript</category>
                
                
            </item>
        
            <item>
                <title>A python library for Incremental PCA (pyIPCA)</title>
                <description>&lt;p&gt;I extracted some of the useful code and nifty examples from the background of my Thesis as a python library for your enjoyment. PCA or &lt;a href=&quot;http://en.wikipedia.org/wiki/Principal_component_analysis&quot;&gt;Principal Component Analysis&lt;/a&gt; is a pretty common data analysis technique, incremental PCA lets you perform the same type of analysis but uses the input data one sample at a time rather than all at once.&lt;/p&gt;

&lt;p&gt;The code fully conforms to the scikit-learn api and you should be able to easily use it anywhere you are currently using one of the sklearn.decomposition classes. In fact this library is sort of on the waiting &lt;a href=&quot;https://github.com/scikit-learn/scikit-learn/wiki/Third-party-projects-and-code-snippets&quot;&gt;list for sklearn&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;caption&quot;&gt;
    &lt;img src=&quot;/images/posts/ipca_ellipse.png&quot; alt=&quot;&quot; /&gt;
    
    &lt;figcaption class=&quot;caption-text&quot;&gt;IPCA on 2D point cloud shaped like an ellipse&lt;/figcaption&gt; 
    
&lt;/figure&gt;

&lt;p&gt;Check it out if you’re interested and holla at sklearn if you want this feature!
&lt;a href=&quot;https://github.com/kevinhughes27/pyIPCA&quot;&gt;github.com/kevinhughes27/pyIPCA&lt;/a&gt;&lt;/p&gt;
</description>
                <pubDate>Sat, 11 Jan 2014 00:00:00 +0000</pubDate>
                <link>https://kevinhughes.ca/blog/a-python-library-for-incremental-pca</link>
                <guid isPermaLink="true">https://kevinhughes.ca/blog/a-python-library-for-incremental-pca</guid>
                
                <category>python</category>
                
                
            </item>
        
    </channel>
</rss>